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.BundleClassLoaderHelper;
37  import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory;
38  import org.eclipse.jetty.util.log.Log;
39  import org.eclipse.jetty.util.log.Logger;
40  import org.eclipse.jetty.util.resource.Resource;
41  import org.eclipse.jetty.webapp.WebAppClassLoader;
42  import org.eclipse.jetty.webapp.WebAppContext;
43  import org.osgi.framework.Bundle;
44  import org.osgi.framework.BundleReference;
45  
46  /**
47   * Extends the webappclassloader to insert the classloader provided by the osgi
48   * bundle at the same level than any other jars palced in the webappclassloader.
49   */
50  public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference
51  {
52  
53      private Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName().toString());
54  
55      /**
56       * when a logging framework is setup in the osgi classloaders, it can access
57       * this and register the classes that must not be found in the jar.
58       */
59      public static Set<String> JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED = new HashSet<String>();
60  
61      public static void addClassThatIdentifiesAJarThatMustBeRejected(Class<?> zclass)
62      {
63          JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclass.getName().replace('.', '/') + ".class");
64      }
65  
66      public static void addClassThatIdentifiesAJarThatMustBeRejected(String zclassName)
67      {
68          JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclassName.replace('.', '/') + ".class");
69      }
70  
71      static
72      {
73          addClassThatIdentifiesAJarThatMustBeRejected(HttpServlet.class);
74      }
75  
76      private ClassLoader _osgiBundleClassLoader;
77  
78      private Bundle _contributor;
79  
80      private boolean _lookInOsgiFirst = true;
81  
82      private Set<String> _libsAlreadyInManifest = new HashSet<String>();
83  
84      /**
85       * @param parent The parent classloader. In this case
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       * Returns the <code>Bundle</code> that defined this web-application.
100      * 
101      * @return The <code>Bundle</code> object associated with this
102      *         <code>BundleReference</code>.
103      */
104     public Bundle getBundle()
105     {
106         return _contributor;
107     }
108 
109     /**
110      * Reads the manifest. If the manifest is already configured to loads a few
111      * libs we should not add them to the classpath of the webapp. Not really
112      * important as we resolve classes through the osgi classloader first and
113      * then default on the libs of the webapp.
114      */
115     private void computeLibsAlreadyInOSGiClassLoader()
116     {
117         // TODO
118     }
119 
120     @Override
121     public Enumeration<URL> getResources(String name) throws IOException
122     {
123         Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
124         Enumeration<URL> urls = super.getResources(name);
125         if (_lookInOsgiFirst)
126         {
127             return Collections.enumeration(toList(osgiUrls, urls));
128         }
129         else
130         {
131             return Collections.enumeration(toList(urls, osgiUrls));
132         }
133     }
134 
135     @Override
136     public URL getResource(String name)
137     {
138         if (_lookInOsgiFirst)
139         {
140             URL url = _osgiBundleClassLoader.getResource(name);
141             return url != null ? url : super.getResource(name);
142         }
143         else
144         {
145             URL url = super.getResource(name);
146             return url != null ? url : _osgiBundleClassLoader.getResource(name);
147         }
148     }
149 
150     private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
151     {
152         List<URL> list = new ArrayList<URL>();
153         while (e != null && e.hasMoreElements())
154             list.add(e.nextElement());
155         while (e2 != null && e2.hasMoreElements())
156             list.add(e2.nextElement());
157         return list;
158     }
159 
160     /**
161      * 
162      */
163     protected Class<?> findClass(String name) throws ClassNotFoundException
164     {
165         try
166         {
167             return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name);
168         }
169         catch (ClassNotFoundException cne)
170         {
171             try
172             {
173                 return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name);
174             }
175             catch (ClassNotFoundException cne2)
176             {
177                 throw cne;
178             }
179         }
180     }
181 
182     /**
183      * Parse the classpath ourselves to be able to filter things. This is a
184      * derivative work of the super class
185      */
186     @Override
187     public void addClassPath(String classPath) throws IOException
188     {
189 
190         StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
191         while (tokenizer.hasMoreTokens())
192         {
193             String path = tokenizer.nextToken();
194             Resource resource = getContext().newResource(path);
195 
196             // Resolve file path if possible
197             File file = resource.getFile();
198             if (file != null && isAcceptableLibrary(file, JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED))
199             {
200                 super.addClassPath(path);
201             }
202             else
203             {
204                 __logger.info("Did not add " + path + " to the classloader of the webapp " + getContext());
205             }
206         }
207 
208     }
209 
210     /**
211      * @param lib
212      * @return true if the lib should be included in the webapp classloader.
213      */
214     private boolean isAcceptableLibrary(File file, Set<String> pathToClassFiles)
215     {
216         try
217         {
218             if (file.isDirectory())
219             {
220                 for (String criteria : pathToClassFiles)
221                 {
222                     if (new File(file, criteria).exists()) { return false; }
223                 }
224             }
225             else
226             {
227                 JarFile jar = null;
228                 try
229                 {
230                     jar = new JarFile(file);
231                     for (String criteria : pathToClassFiles)
232                     {
233                         if (jar.getEntry(criteria) != null) { return false; }
234                     }
235                 }
236                 finally
237                 {
238                     if (jar != null) try
239                     {
240                         jar.close();
241                     }
242                     catch (IOException ioe)
243                     {
244                     }
245                 }
246             }
247         }
248         catch (IOException e)
249         {
250             // nevermind. just trying our best
251             __logger.ignore(e);
252         }
253         return true;
254     }
255 
256     private static Field _contextField;
257 
258     /**
259      * In the case of the generation of a webapp via a jetty context file we
260      * need a proper classloader to setup the app before we have the
261      * WebappContext So we place a fake one there to start with. We replace it
262      * with the actual webapp context with this method. We also apply the
263      * extraclasspath there at the same time.
264      */
265     public void setWebappContext(WebAppContext webappContext)
266     {
267         try
268         {
269             if (_contextField == null)
270             {
271                 _contextField = WebAppClassLoader.class.getDeclaredField("_context");
272                 _contextField.setAccessible(true);
273             }
274             _contextField.set(this, webappContext);
275             if (webappContext.getExtraClasspath() != null)
276             {
277                 addClassPath(webappContext.getExtraClasspath());
278             }
279         }
280         catch (Throwable t)
281         {
282             // humf that will hurt if it does not work.
283             __logger.warn("Unable to set webappcontext", t);
284         }
285     }
286 }