001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 package org.apache.felix.framework; 020 021 import java.io.IOException; 022 import java.lang.reflect.InvocationHandler; 023 import java.lang.reflect.InvocationTargetException; 024 import java.lang.reflect.Method; 025 import java.lang.reflect.Proxy; 026 import java.net.InetAddress; 027 import java.net.MalformedURLException; 028 import java.net.URL; 029 import java.net.URLConnection; 030 import java.net.URLStreamHandler; 031 032 import org.apache.felix.framework.util.SecureAction; 033 import org.osgi.service.url.URLStreamHandlerService; 034 import org.osgi.service.url.URLStreamHandlerSetter; 035 036 /** 037 * <p> 038 * This class implements a stream handler proxy. When the stream handler 039 * proxy instance is created, it is associated with a particular protocol 040 * and will answer all future requests for handling of that stream type. It 041 * does not directly handle the stream handler requests, but delegates the 042 * requests to an underlying stream handler service. 043 * </p> 044 * <p> 045 * The proxy instance for a particular protocol is used for all framework 046 * instances that may contain their own stream handler services. When 047 * performing a stream handler operation, the proxy retrieves the handler 048 * service from the framework instance associated with the current call 049 * stack and delegates the call to the handler service. 050 * </p> 051 * <p> 052 * The proxy will create simple stream handler service trackers for each 053 * framework instance. The trackers will listen to service events in its 054 * respective framework instance to maintain a reference to the "best" 055 * stream handler service at any given time. 056 * </p> 057 **/ 058 public class URLHandlersStreamHandlerProxy extends URLStreamHandler 059 implements URLStreamHandlerSetter, InvocationHandler 060 { 061 private static final Class[] URL_PROXY_CLASS; 062 private static final Class[] STRING_TYPES = new Class[]{String.class}; 063 private static final Method EQUALS; 064 private static final Method GET_DEFAULT_PORT; 065 private static final Method GET_HOST_ADDRESS; 066 private static final Method HASH_CODE; 067 private static final Method HOSTS_EQUAL; 068 private static final Method OPEN_CONNECTION; 069 private static final Method OPEN_CONNECTION_PROXY; 070 private static final Method SAME_FILE; 071 private static final Method TO_EXTERNAL_FORM; 072 073 static { 074 SecureAction action = new SecureAction(); 075 try 076 { 077 EQUALS = URLStreamHandler.class.getDeclaredMethod("equals", 078 new Class[]{URL.class, URL.class}); 079 action.setAccesssible(EQUALS); 080 GET_DEFAULT_PORT = URLStreamHandler.class.getDeclaredMethod("getDefaultPort", 081 (Class[]) null); 082 action.setAccesssible(GET_DEFAULT_PORT); 083 GET_HOST_ADDRESS = URLStreamHandler.class.getDeclaredMethod( 084 "getHostAddress", new Class[]{URL.class}); 085 action.setAccesssible(GET_HOST_ADDRESS); 086 HASH_CODE = URLStreamHandler.class.getDeclaredMethod( 087 "hashCode", new Class[]{URL.class}); 088 action.setAccesssible(HASH_CODE); 089 HOSTS_EQUAL = URLStreamHandler.class.getDeclaredMethod( 090 "hostsEqual", new Class[]{URL.class, URL.class}); 091 action.setAccesssible(HOSTS_EQUAL); 092 OPEN_CONNECTION = URLStreamHandler.class.getDeclaredMethod( 093 "openConnection", new Class[]{URL.class}); 094 action.setAccesssible(OPEN_CONNECTION); 095 SAME_FILE = URLStreamHandler.class.getDeclaredMethod( 096 "sameFile", new Class[]{URL.class, URL.class}); 097 action.setAccesssible(SAME_FILE); 098 TO_EXTERNAL_FORM = URLStreamHandler.class.getDeclaredMethod( 099 "toExternalForm", new Class[]{URL.class}); 100 action.setAccesssible(TO_EXTERNAL_FORM); 101 } 102 catch (Exception ex) 103 { 104 throw new RuntimeException(ex.getMessage()); 105 } 106 107 Method open_connection_proxy = null; 108 Class[] url_proxy_class = null; 109 try 110 { 111 url_proxy_class = new Class[]{URL.class, java.net.Proxy.class}; 112 open_connection_proxy = URLStreamHandler.class.getDeclaredMethod( 113 "openConnection", url_proxy_class); 114 action.setAccesssible(open_connection_proxy); 115 } 116 catch (Throwable ex) 117 { 118 open_connection_proxy = null; 119 url_proxy_class = null; 120 } 121 OPEN_CONNECTION_PROXY = open_connection_proxy; 122 URL_PROXY_CLASS = url_proxy_class; 123 } 124 125 private final Object m_service; 126 private final SecureAction m_action; 127 private final URLStreamHandler m_builtIn; 128 private final URL m_builtInURL; 129 private final String m_protocol; 130 131 public URLHandlersStreamHandlerProxy(String protocol, 132 SecureAction action, URLStreamHandler builtIn, URL builtInURL) 133 { 134 m_protocol = protocol; 135 m_service = null; 136 m_action = action; 137 m_builtIn = builtIn; 138 m_builtInURL = builtInURL; 139 } 140 141 private URLHandlersStreamHandlerProxy(Object service, SecureAction action) 142 { 143 m_protocol = null; 144 m_service = service; 145 m_action = action; 146 m_builtIn = null; 147 m_builtInURL = null; 148 } 149 150 // 151 // URLStreamHandler interface methods. 152 // 153 protected boolean equals(URL url1, URL url2) 154 { 155 Object svc = getStreamHandlerService(); 156 if (svc == null) 157 { 158 throw new IllegalStateException( 159 "Unknown protocol: " + url1.getProtocol()); 160 } 161 if (svc instanceof URLStreamHandlerService) 162 { 163 return ((URLStreamHandlerService) svc).equals(url1, url2); 164 } 165 try 166 { 167 return ((Boolean) EQUALS.invoke(svc, new Object[]{url1, url2})).booleanValue(); 168 } 169 catch (Exception ex) 170 { 171 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 172 } 173 } 174 175 protected int getDefaultPort() 176 { 177 Object svc = getStreamHandlerService(); 178 if (svc == null) 179 { 180 throw new IllegalStateException("Stream handler unavailable."); 181 } 182 if (svc instanceof URLStreamHandlerService) 183 { 184 return ((URLStreamHandlerService) svc).getDefaultPort(); 185 } 186 try 187 { 188 return ((Integer) GET_DEFAULT_PORT.invoke(svc, null)).intValue(); 189 } 190 catch (Exception ex) 191 { 192 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 193 } 194 } 195 196 protected InetAddress getHostAddress(URL url) 197 { 198 Object svc = getStreamHandlerService(); 199 if (svc == null) 200 { 201 throw new IllegalStateException( 202 "Unknown protocol: " + url.getProtocol()); 203 } 204 if (svc instanceof URLStreamHandlerService) 205 { 206 return ((URLStreamHandlerService) svc).getHostAddress(url); 207 } 208 try 209 { 210 return (InetAddress) GET_HOST_ADDRESS.invoke(svc, new Object[]{url}); 211 } 212 catch (Exception ex) 213 { 214 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 215 } 216 } 217 218 protected int hashCode(URL url) 219 { 220 Object svc = getStreamHandlerService(); 221 if (svc == null) 222 { 223 throw new IllegalStateException( 224 "Unknown protocol: " + url.getProtocol()); 225 } 226 if (svc instanceof URLStreamHandlerService) 227 { 228 return ((URLStreamHandlerService) svc).hashCode(url); 229 } 230 try 231 { 232 return ((Integer) HASH_CODE.invoke(svc, new Object[]{url})).intValue(); 233 } 234 catch (Exception ex) 235 { 236 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 237 } 238 } 239 240 protected boolean hostsEqual(URL url1, URL url2) 241 { 242 Object svc = getStreamHandlerService(); 243 if (svc == null) 244 { 245 throw new IllegalStateException( 246 "Unknown protocol: " + url1.getProtocol()); 247 } 248 if (svc instanceof URLStreamHandlerService) 249 { 250 return ((URLStreamHandlerService) svc).hostsEqual(url1, url2); 251 } 252 try 253 { 254 return ((Boolean) HOSTS_EQUAL.invoke(svc, new Object[]{url1, url2})).booleanValue(); 255 } 256 catch (Exception ex) 257 { 258 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 259 } 260 } 261 262 protected URLConnection openConnection(URL url) throws IOException 263 { 264 Object svc = getStreamHandlerService(); 265 if (svc == null) 266 { 267 throw new MalformedURLException("Unknown protocol: " + url.toString()); 268 } 269 if (svc instanceof URLStreamHandlerService) 270 { 271 return ((URLStreamHandlerService) svc).openConnection(url); 272 } 273 try 274 { 275 if ("http".equals(url.getProtocol()) && 276 "felix.extensions".equals(url.getHost()) && 277 9 == url.getPort()) 278 { 279 try 280 { 281 Object handler = m_action.getDeclaredField( 282 ExtensionManager.class, "m_extensionManager", null); 283 284 if (handler != null) 285 { 286 return (URLConnection) m_action.invoke( 287 m_action.getMethod(handler.getClass(), 288 "openConnection", new Class[]{URL.class}), handler, 289 new Object[]{url}); 290 } 291 292 throw new IOException("Extensions not supported or ambiguous context."); 293 } 294 catch (IOException ex) 295 { 296 throw ex; 297 } 298 catch (Exception ex) 299 { 300 throw new IOException(ex.getMessage()); 301 } 302 } 303 return (URLConnection) OPEN_CONNECTION.invoke(svc, new Object[]{url}); 304 } 305 catch (IOException ex) 306 { 307 throw ex; 308 } 309 catch (Exception ex) 310 { 311 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 312 } 313 } 314 315 protected URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException 316 { 317 Object svc = getStreamHandlerService(); 318 if (svc == null) 319 { 320 throw new MalformedURLException("Unknown protocol: " + url.toString()); 321 } 322 if (svc instanceof URLStreamHandlerService) 323 { 324 Method method; 325 try 326 { 327 method = svc.getClass().getMethod("openConnection", URL_PROXY_CLASS); 328 } 329 catch (NoSuchMethodException e) 330 { 331 throw new UnsupportedOperationException(e); 332 } 333 try 334 { 335 m_action.setAccesssible(method); 336 return (URLConnection) method.invoke(svc, new Object[]{url, proxy}); 337 } 338 catch (Exception e) 339 { 340 if (e instanceof IOException) 341 { 342 throw (IOException) e; 343 } 344 throw new IOException(e.getMessage()); 345 } 346 } 347 try 348 { 349 return (URLConnection) OPEN_CONNECTION_PROXY.invoke(svc, new Object[]{url, proxy}); 350 } 351 catch (Exception ex) 352 { 353 if (ex instanceof IOException) 354 { 355 throw (IOException) ex; 356 } 357 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 358 } 359 } 360 361 // We use this thread local to detect whether we have a reentrant entry to the parseURL 362 // method. This can happen do to some difference between gnu/classpath and sun jvms 363 // For more see inside the method. 364 private static final ThreadLocal m_loopCheck = new ThreadLocal(); 365 protected void parseURL(URL url, String spec, int start, int limit) 366 { 367 Object svc = getStreamHandlerService(); 368 if (svc == null) 369 { 370 throw new IllegalStateException( 371 "Unknown protocol: " + url.getProtocol()); 372 } 373 if (svc instanceof URLStreamHandlerService) 374 { 375 ((URLStreamHandlerService) svc).parseURL(this, url, spec, start, limit); 376 } 377 else 378 { 379 try 380 { 381 URL test = null; 382 // In order to cater for built-in urls being over-writable we need to use a 383 // somewhat strange hack. We use a hidden feature inside the jdk which passes 384 // the handler of the url given as a context to a new URL to that URL as its 385 // handler. This way, we can create a new URL which will use the given built-in 386 // handler to parse the url. Subsequently, we can use the information from that 387 // URL to call set with the correct values. 388 if (m_builtInURL != null) 389 { 390 // However, if we are on gnu/classpath we have to pass the handler directly 391 // because the hidden feature is not there. Funnily, the workaround to pass 392 // pass the handler directly doesn't work on sun as their handler detects 393 // that it is not the same as the one inside the url and throws an exception 394 // Luckily it doesn't do that on gnu/classpath. We detect that we need to 395 // pass the handler directly by using the m_loopCheck thread local to detect 396 // that we parseURL has been called inside a call to parseURL. 397 if (m_loopCheck.get() != null) 398 { 399 test = new URL(new URL(m_builtInURL, url.toExternalForm()), spec, (URLStreamHandler) svc); 400 } 401 else 402 { 403 // Set-up the thread local as we don't expect to be called again until we are 404 // done. Otherwise, we are on gnu/classpath 405 m_loopCheck.set(Thread.currentThread()); 406 try 407 { 408 test = new URL(new URL(m_builtInURL, url.toExternalForm()), spec); 409 } 410 finally 411 { 412 m_loopCheck.set(null); 413 } 414 } 415 } 416 else 417 { 418 // We don't have a url with a built-in handler for this but still want to create 419 // the url with the buil-in handler as we could find one now. This might not 420 // work for all handlers on sun but it is better then doing nothing. 421 test = m_action.createURL(url, spec, (URLStreamHandler) svc); 422 } 423 424 super.setURL(url, test.getProtocol(), test.getHost(), test.getPort(),test.getAuthority(), 425 test.getUserInfo(), test.getPath(), test.getQuery(), test.getRef()); 426 } 427 catch (Exception ex) 428 { 429 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 430 } 431 } 432 } 433 434 protected boolean sameFile(URL url1, URL url2) 435 { 436 Object svc = getStreamHandlerService(); 437 if (svc == null) 438 { 439 throw new IllegalStateException( 440 "Unknown protocol: " + url1.getProtocol()); 441 } 442 if (svc instanceof URLStreamHandlerService) 443 { 444 return ((URLStreamHandlerService) svc).sameFile(url1, url2); 445 } 446 try 447 { 448 return ((Boolean) SAME_FILE.invoke( 449 svc, new Object[]{url1, url2})).booleanValue(); 450 } 451 catch (Exception ex) 452 { 453 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 454 } 455 } 456 457 public void setURL( 458 URL url, String protocol, String host, int port, String authority, 459 String userInfo, String path, String query, String ref) 460 { 461 super.setURL(url, protocol, host, port, authority, userInfo, path, query, ref); 462 } 463 464 public void setURL( 465 URL url, String protocol, String host, int port, String file, String ref) 466 { 467 super.setURL(url, protocol, host, port, null, null, file, null, ref); 468 } 469 470 protected String toExternalForm(URL url) 471 { 472 return toExternalForm(url, getStreamHandlerService()); 473 } 474 475 private String toExternalForm(URL url, Object svc) 476 { 477 if (svc == null) 478 { 479 throw new IllegalStateException( 480 "Unknown protocol: " + url.getProtocol()); 481 } 482 if (svc instanceof URLStreamHandlerService) 483 { 484 return ((URLStreamHandlerService) svc).toExternalForm(url); 485 } 486 try 487 { 488 try 489 { 490 String result = (String) TO_EXTERNAL_FORM.invoke( 491 svc, new Object[]{url}); 492 493 // mika does return an invalid format if we have a url with the 494 // protocol only (<proto>://null) - we catch this case now 495 if ((result != null) && (result.equals(url.getProtocol() + "://null"))) 496 { 497 result = url.getProtocol() + ":"; 498 } 499 500 return result; 501 } 502 catch (InvocationTargetException ex) 503 { 504 Throwable t = ex.getTargetException(); 505 if (t instanceof Exception) 506 { 507 throw (Exception) t; 508 } 509 else if (t instanceof Error) 510 { 511 throw (Error) t; 512 } 513 else 514 { 515 throw new IllegalStateException("Unknown throwable: " + t); 516 } 517 } 518 } 519 catch (NullPointerException ex) 520 { 521 // workaround for harmony and possibly J9. The issue is that 522 // their implementation of URLStreamHandler.toExternalForm() 523 // assumes that URL.getFile() doesn't return null but in our 524 // case it can -- hence, we catch the NPE and do the work 525 // ourselvs. The only difference is that we check whether the 526 // URL.getFile() is null or not. 527 StringBuffer answer = new StringBuffer(); 528 answer.append(url.getProtocol()); 529 answer.append(':'); 530 String authority = url.getAuthority(); 531 if ((authority != null) && (authority.length() > 0)) 532 { 533 answer.append("//"); //$NON-NLS-1$ 534 answer.append(url.getAuthority()); 535 } 536 537 String file = url.getFile(); 538 String ref = url.getRef(); 539 if (file != null) 540 { 541 answer.append(file); 542 } 543 if (ref != null) 544 { 545 answer.append('#'); 546 answer.append(ref); 547 } 548 return answer.toString(); 549 } 550 catch (Exception ex) 551 { 552 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage()); 553 } 554 } 555 556 /** 557 * <p> 558 * Private method to retrieve the stream handler service from the 559 * framework instance associated with the current call stack. A 560 * simple service tracker is created and cached for the associated 561 * framework instance when this method is called. 562 * </p> 563 * @return the stream handler service from the framework instance 564 * associated with the current call stack or <tt>null</tt> 565 * is no service is available. 566 **/ 567 private Object getStreamHandlerService() 568 { 569 try 570 { 571 // Get the framework instance associated with call stack. 572 Object framework = URLHandlers.getFrameworkFromContext(); 573 574 if (framework == null) 575 { 576 return m_builtIn; 577 } 578 579 Object service = null; 580 if (framework instanceof Felix) 581 { 582 service = ((Felix) framework).getStreamHandlerService(m_protocol); 583 } 584 else 585 { 586 service = m_action.invoke( 587 m_action.getMethod(framework.getClass(), "getStreamHandlerService", STRING_TYPES), 588 framework, new Object[]{m_protocol}); 589 } 590 591 if (service == null) 592 { 593 return m_builtIn; 594 } 595 if (service instanceof URLStreamHandlerService) 596 { 597 return (URLStreamHandlerService) service; 598 } 599 return (URLStreamHandlerService) Proxy.newProxyInstance( 600 URLStreamHandlerService.class.getClassLoader(), 601 new Class[]{URLStreamHandlerService.class}, 602 new URLHandlersStreamHandlerProxy(service, m_action)); 603 } 604 catch (ThreadDeath td) 605 { 606 throw td; 607 } 608 catch (Throwable t) 609 { 610 // In case that we are inside tomcat - the problem is that the webapp classloader 611 // creates a new url to load a class. This gets us to this method. Now, if we 612 // trigger a classload while executing tomcat is creating a new url and we end-up with 613 // a loop which is cut short after two iterations (because of a circularclassload). 614 // We catch this exception (and all others) and just return the built-in handler 615 // (if we have any) as this way we at least eventually get started (this just means 616 // that we don't use the potentially provided built-in handler overwrite). 617 return m_builtIn; 618 } 619 } 620 621 public Object invoke(Object obj, Method method, Object[] params) 622 throws Throwable 623 { 624 try 625 { 626 Class[] types = method.getParameterTypes(); 627 if ("parseURL".equals(method.getName())) 628 { 629 types[0] = m_service.getClass().getClassLoader().loadClass( 630 URLStreamHandlerSetter.class.getName()); 631 params[0] = Proxy.newProxyInstance( 632 m_service.getClass().getClassLoader(), new Class[]{types[0]}, 633 (URLHandlersStreamHandlerProxy) params[0]); 634 } 635 return m_action.invokeDirect(m_action.getMethod(m_service.getClass(), 636 method.getName(), types), m_service, params); 637 } 638 catch (Exception ex) 639 { 640 throw ex; 641 } 642 } 643 }