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    }