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