View Javadoc

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