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 }