View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.osgi.boot.internal.webapp;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.lang.reflect.Field;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Enumeration;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.StringTokenizer;
32  import java.util.jar.JarFile;
33  
34  import javax.servlet.http.HttpServlet;
35  
36  import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.util.resource.Resource;
40  import org.eclipse.jetty.webapp.WebAppClassLoader;
41  import org.eclipse.jetty.webapp.WebAppContext;
42  import org.osgi.framework.Bundle;
43  import org.osgi.framework.BundleReference;
44  
45  /**
46   * OSGiWebappClassLoader
47   * 
48   * 
49   * Extends the webapp classloader to also use the classloader of the Bundle defining the webapp.
50   */
51  public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference
52  {
53  
54      private static final Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName());
55  
56      /**
57       * when a logging framework is setup in the osgi classloaders, it can access
58       * this and register the classes that must not be found in the jar.
59       */
60      public static final Set<String> JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED = new HashSet<String>();
61  
62      public static void addClassThatIdentifiesAJarThatMustBeRejected(Class<?> zclass)
63      {
64          JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclass.getName().replace('.', '/') + ".class");
65      }
66  
67      public static void addClassThatIdentifiesAJarThatMustBeRejected(String zclassName)
68      {
69          JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclassName.replace('.', '/') + ".class");
70      }
71  
72      static
73      {
74          addClassThatIdentifiesAJarThatMustBeRejected(HttpServlet.class);
75      }
76  
77      private ClassLoader _osgiBundleClassLoader;
78  
79      private Bundle _contributor;
80  
81      private boolean _lookInOsgiFirst = true;
82  
83      /* ------------------------------------------------------------ */
84      /**
85       * @param parent The parent classloader.
86       * @param context The WebAppContext
87       * @param contributor The bundle that defines this web-application.
88       * @throws IOException
89       */
90      public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor)
91      throws IOException
92      {
93          super(parent, context);
94          _contributor = contributor;
95          _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor);
96      }
97      
98      
99      
100     /* ------------------------------------------------------------ */
101     /**
102      * Returns the <code>Bundle</code> that defined this web-application.
103      * 
104      * @return The <code>Bundle</code> object associated with this
105      *         <code>BundleReference</code>.
106      */
107     public Bundle getBundle()
108     {
109         return _contributor;
110     }
111 
112     /* ------------------------------------------------------------ */
113     @Override
114     public Enumeration<URL> getResources(String name) throws IOException
115     {
116         Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
117         Enumeration<URL> urls = super.getResources(name);
118         if (_lookInOsgiFirst)
119         {
120             return Collections.enumeration(toList(osgiUrls, urls));
121         }
122         else
123         {
124             return Collections.enumeration(toList(urls, osgiUrls));
125         }
126     }
127     
128     
129     
130     /* ------------------------------------------------------------ */
131     @Override
132     public URL getResource(String name)
133     {
134         if (_lookInOsgiFirst)
135         {
136             URL url = _osgiBundleClassLoader.getResource(name);
137             return url != null ? url : super.getResource(name);
138         }
139         else
140         {
141             URL url = super.getResource(name);
142             return url != null ? url : _osgiBundleClassLoader.getResource(name);
143         }
144     }
145     
146     
147     
148     /* ------------------------------------------------------------ */
149     private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
150     {
151         List<URL> list = new ArrayList<URL>();
152         while (e != null && e.hasMoreElements())
153             list.add(e.nextElement());
154         while (e2 != null && e2.hasMoreElements())
155             list.add(e2.nextElement());
156         return list;
157     }
158 
159     
160     /* ------------------------------------------------------------ */
161     protected Class<?> findClass(String name) throws ClassNotFoundException
162     {
163         try
164         {
165             return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name);
166         }
167         catch (ClassNotFoundException cne)
168         {
169             try
170             {
171                 return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name);
172             }
173             catch (ClassNotFoundException cne2)
174             {
175                 throw cne;
176             }
177         }
178     }
179     
180     
181     
182     /* ------------------------------------------------------------ */
183     /**
184      * Parse the classpath ourselves to be able to filter things. This is a
185      * derivative work of the super class
186      */
187     @Override
188     public void addClassPath(String classPath) throws IOException
189     {
190 
191         StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
192         while (tokenizer.hasMoreTokens())
193         {
194             String path = tokenizer.nextToken();
195             Resource resource = getContext().newResource(path);
196 
197             // Resolve file path if possible
198             File file = resource.getFile();
199             if (file != null && isAcceptableLibrary(file, JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED))
200             {
201                 super.addClassPath(path);
202             }
203             else
204             {
205                 __logger.info("Did not add " + path + " to the classloader of the webapp " + getContext());
206             }
207         }
208 
209     }
210 
211     
212     /* ------------------------------------------------------------ */
213     /**
214      * @param lib
215      * @return true if the lib should be included in the webapp classloader.
216      */
217     private boolean isAcceptableLibrary(File file, Set<String> pathToClassFiles)
218     {
219         try
220         {
221             if (file.isDirectory())
222             {
223                 for (String criteria : pathToClassFiles)
224                 {
225                     if (new File(file, criteria).exists()) { return false; }
226                 }
227             }
228             else
229             {
230                 JarFile jar = null;
231                 try
232                 {
233                     jar = new JarFile(file);
234                     for (String criteria : pathToClassFiles)
235                     {
236                         if (jar.getEntry(criteria) != null) { return false; }
237                     }
238                 }
239                 finally
240                 {
241                     if (jar != null) try
242                     {
243                         jar.close();
244                     }
245                     catch (IOException ioe)
246                     {
247                     }
248                 }
249             }
250         }
251         catch (IOException e)
252         {
253             // nevermind. just trying our best
254             __logger.ignore(e);
255         }
256         return true;
257     }
258 
259     private static Field _contextField;
260 
261     
262     /* ------------------------------------------------------------ */
263     /**
264      * In the case of the generation of a webapp via a jetty context file we
265      * need a proper classloader to setup the app before we have the
266      * WebappContext So we place a fake one there to start with. We replace it
267      * with the actual webapp context with this method. We also apply the
268      * extraclasspath there at the same time.
269      */
270     public void setWebappContext(WebAppContext webappContext)
271     {
272         try
273         {
274             if (_contextField == null)
275             {
276                 _contextField = WebAppClassLoader.class.getDeclaredField("_context");
277                 _contextField.setAccessible(true);
278             }
279             _contextField.set(this, webappContext);
280             if (webappContext.getExtraClasspath() != null)
281             {
282                 addClassPath(webappContext.getExtraClasspath());
283             }
284         }
285         catch (Throwable t)
286         {
287             // humf that will hurt if it does not work.
288             __logger.warn("Unable to set webappcontext", t);
289         }
290     }
291 }