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      private Bundle _contributor;
73      private boolean _lookInOsgiFirst = true;
74      private Set<String> _libsAlreadyInManifest = new HashSet<String>();
75  
76      /**
77       * @param parent The parent classloader. In this case 
78       * @param context The WebAppContext
79       * @param contributor The bundle that defines this web-application.
80       * @throws IOException
81       */
82      public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor,
83      		BundleClassLoaderHelper bundleClassLoaderHelper) throws IOException
84      {
85          super(parent,context);
86          _contributor = contributor;
87          _osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor);
88      }
89      
90  	/**
91  	 * Returns the <code>Bundle</code> that defined this web-application.
92  	 * 
93  	 * @return The <code>Bundle</code> object associated with this
94  	 *         <code>BundleReference</code>.
95  	 */
96  	public Bundle getBundle()
97  	{
98  		return _contributor;
99  	}
100 
101     /**
102      * Reads the manifest. If the manifest is already configured to loads a few
103      * libs we should not add them to the classpath of the webapp. Not really
104      * important as we resolve classes through the osgi classloader first and
105      * then default on the libs of the webapp.
106      */
107     private void computeLibsAlreadyInOSGiClassLoader()
108     {
109         // TODO
110     }
111 
112     @Override
113     public Enumeration<URL> getResources(String name) throws IOException
114     {
115         Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
116         Enumeration<URL> urls = super.getResources(name);
117         if (_lookInOsgiFirst)
118         {
119             return Collections.enumeration(toList(osgiUrls, urls));
120         }
121         else
122         {
123             return Collections.enumeration(toList(urls, osgiUrls));
124         }
125     }
126     
127     @Override
128     public URL getResource(String name)
129     {
130         if (_lookInOsgiFirst)
131         {
132             URL url = _osgiBundleClassLoader.getResource(name);
133             return url != null ? url : super.getResource(name);
134         }
135         else 
136         {
137             URL url = super.getResource(name);
138             return url != null ? url : _osgiBundleClassLoader.getResource(name);
139         }       
140     }
141     
142     private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
143     {
144         List<URL> list = new ArrayList<URL>();
145         while (e!=null && e.hasMoreElements())
146             list.add(e.nextElement());
147         while (e2!=null && e2.hasMoreElements())
148             list.add(e2.nextElement());
149         return list;
150     }
151 
152     /**
153 	 * 
154 	 */
155     protected Class<?> findClass(String name) throws ClassNotFoundException
156     {
157         try
158         {
159             return _lookInOsgiFirst?_osgiBundleClassLoader.loadClass(name):super.findClass(name);
160         }
161         catch (ClassNotFoundException cne)
162         {
163             try
164             {
165                 return _lookInOsgiFirst?super.findClass(name):_osgiBundleClassLoader.loadClass(name);
166             }
167             catch (ClassNotFoundException cne2)
168             {
169                 throw cne;
170             }
171         }
172     }
173 
174     /**
175      * Parse the classpath ourselves to be able to filter things. This is a
176      * derivative work of the super class
177      */
178     @Override
179     public void addClassPath(String classPath) throws IOException
180     {
181 
182         StringTokenizer tokenizer = new StringTokenizer(classPath,",;");
183         while (tokenizer.hasMoreTokens())
184         {
185             String path = tokenizer.nextToken();
186             Resource resource = getContext().newResource(path);
187 
188             // Resolve file path if possible
189             File file = resource.getFile();
190             if (file != null && isAcceptableLibrary(file,JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED))
191             {
192                 super.addClassPath(path);
193             }
194             else
195             {
196                 __logger.info("Did not add " + path + " to the classloader of the webapp " + getContext());
197             }
198         }
199 
200     }
201 
202     /**
203      * @param lib
204      * @return true if the lib should be included in the webapp classloader.
205      */
206     private boolean isAcceptableLibrary(File file, Set<String> pathToClassFiles)
207     {
208         try
209         {
210             if (file.isDirectory())
211             {
212                 for (String criteria : pathToClassFiles)
213                 {
214                     if (new File(file,criteria).exists())
215                     {
216                         return false;
217                     }
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)
229                         {
230                             return false;
231                         }
232                     }
233                 }
234                 finally
235                 {
236                     if (jar != null)
237                         try
238                         {
239                             jar.close();
240                         }
241                         catch (IOException ioe)
242                         {
243                         }
244                 }
245             }
246         }
247         catch (IOException e)
248         {
249             // nevermind. just trying our best
250             e.printStackTrace();
251         }
252         return true;
253     }
254 
255     private static Field _contextField;
256 
257     /**
258      * In the case of the generation of a webapp via a jetty context file we
259      * need a proper classloader to setup the app before we have the
260      * WebappContext So we place a fake one there to start with. We replace it
261      * with the actual webapp context with this method. We also apply the
262      * extraclasspath there at the same time.
263      */
264     public void setWebappContext(WebAppContext webappContext)
265     {
266         try
267         {
268             if (_contextField == null)
269             {
270                 _contextField = WebAppClassLoader.class.getDeclaredField("_context");
271                 _contextField.setAccessible(true);
272             }
273             _contextField.set(this,webappContext);
274             if (webappContext.getExtraClasspath() != null)
275             {
276                 addClassPath(webappContext.getExtraClasspath());
277             }
278         }
279         catch (Throwable t)
280         {
281             // humf that will hurt if it does not work.
282             t.printStackTrace();
283         }
284     }
285 }