View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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 if unable to cerate the OSGiWebappClassLoader
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     @Override
101     public Class<?> loadClass(String name) throws ClassNotFoundException
102     {
103         return super.loadClass(name);
104     }
105 
106     /* ------------------------------------------------------------ */
107     /**
108      * Returns the <code>Bundle</code> that defined this web-application.
109      * 
110      * @return The <code>Bundle</code> object associated with this
111      *         <code>BundleReference</code>.
112      */
113     public Bundle getBundle()
114     {
115         return _contributor;
116     }
117 
118     /* ------------------------------------------------------------ */
119     @Override
120     public Enumeration<URL> getResources(String name) throws IOException
121     {
122         Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
123         Enumeration<URL> urls = super.getResources(name);
124         if (_lookInOsgiFirst)
125         {
126             return Collections.enumeration(toList(osgiUrls, urls));
127         }
128         else
129         {
130             return Collections.enumeration(toList(urls, osgiUrls));
131         }
132     }
133     
134     
135     
136     /* ------------------------------------------------------------ */
137     @Override
138     public URL getResource(String name)
139     {
140         if (_lookInOsgiFirst)
141         {
142             URL url = _osgiBundleClassLoader.getResource(name);
143             return url != null ? url : super.getResource(name);
144         }
145         else
146         {
147             URL url = super.getResource(name);
148             return url != null ? url : _osgiBundleClassLoader.getResource(name);
149         }
150     }
151     
152     
153     
154     /* ------------------------------------------------------------ */
155     private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
156     {
157         List<URL> list = new ArrayList<URL>();
158         while (e != null && e.hasMoreElements())
159             list.add(e.nextElement());
160         while (e2 != null && e2.hasMoreElements())
161             list.add(e2.nextElement());
162         return list;
163     }
164 
165     
166     /* ------------------------------------------------------------ */
167     protected Class<?> findClass(String name) throws ClassNotFoundException
168     {
169         try
170         {
171             return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name);
172         }
173         catch (ClassNotFoundException cne)
174         {
175             try
176             {
177                 return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name);
178             }
179             catch (ClassNotFoundException cne2)
180             {
181                 throw cne;
182             }
183         }
184     }
185     
186     
187     
188     /* ------------------------------------------------------------ */
189     /**
190      * Parse the classpath ourselves to be able to filter things. This is a
191      * derivative work of the super class
192      */
193     @Override
194     public void addClassPath(String classPath) throws IOException
195     {
196 
197         StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
198         while (tokenizer.hasMoreTokens())
199         {
200             String path = tokenizer.nextToken();
201             Resource resource = getContext().newResource(path);
202 
203             // Resolve file path if possible
204             File file = resource.getFile();
205             if (file != null && isAcceptableLibrary(file, JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED))
206             {
207                 super.addClassPath(path);
208             }
209             else
210             {
211                 __logger.info("Did not add " + path + " to the classloader of the webapp " + getContext());
212             }
213         }
214 
215     }
216 
217     
218     /* ------------------------------------------------------------ */
219     /**
220      * @param lib
221      * @return true if the lib should be included in the webapp classloader.
222      */
223     private boolean isAcceptableLibrary(File file, Set<String> pathToClassFiles)
224     {
225         try
226         {
227             if (file.isDirectory())
228             {
229                 for (String criteria : pathToClassFiles)
230                 {
231                     if (new File(file, criteria).exists()) { return false; }
232                 }
233             }
234             else
235             {
236                 JarFile jar = null;
237                 try
238                 {
239                     jar = new JarFile(file);
240                     for (String criteria : pathToClassFiles)
241                     {
242                         if (jar.getEntry(criteria) != null) { return false; }
243                     }
244                 }
245                 finally
246                 {
247                     if (jar != null) try
248                     {
249                         jar.close();
250                     }
251                     catch (IOException ioe)
252                     {
253                     }
254                 }
255             }
256         }
257         catch (IOException e)
258         {
259             // nevermind. just trying our best
260             __logger.ignore(e);
261         }
262         return true;
263     }
264 
265     private static Field _contextField;
266 
267     
268     /* ------------------------------------------------------------ */
269     /**
270      * In the case of the generation of a webapp via a jetty context file we
271      * need a proper classloader to setup the app before we have the
272      * WebappContext So we place a fake one there to start with. We replace it
273      * with the actual webapp context with this method. We also apply the
274      * extraclasspath there at the same time.
275      * @param webappContext the web app context
276      */
277     public void setWebappContext(WebAppContext webappContext)
278     {
279         try
280         {
281             if (_contextField == null)
282             {
283                 _contextField = WebAppClassLoader.class.getDeclaredField("_context");
284                 _contextField.setAccessible(true);
285             }
286             _contextField.set(this, webappContext);
287             if (webappContext.getExtraClasspath() != null)
288             {
289                 addClassPath(webappContext.getExtraClasspath());
290             }
291         }
292         catch (Throwable t)
293         {
294             // humf that will hurt if it does not work.
295             __logger.warn("Unable to set webappcontext", t);
296         }
297     }
298 }