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.BufferedInputStream;
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.net.URLClassLoader;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.StringTokenizer;
32  import java.util.jar.JarFile;
33  import java.util.zip.ZipEntry;
34  
35  import org.eclipse.jetty.deploy.AppProvider;
36  import org.eclipse.jetty.deploy.ContextDeployer;
37  import org.eclipse.jetty.deploy.DeploymentManager;
38  import org.eclipse.jetty.deploy.WebAppDeployer;
39  import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
40  import org.eclipse.jetty.osgi.boot.OSGiAppProvider;
41  import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
42  import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader;
43  import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper;
44  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
45  import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
46  import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper;
47  import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper;
48  import org.eclipse.jetty.server.Handler;
49  import org.eclipse.jetty.server.Server;
50  import org.eclipse.jetty.server.handler.ContextHandler;
51  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
52  import org.eclipse.jetty.server.handler.DefaultHandler;
53  import org.eclipse.jetty.server.handler.HandlerCollection;
54  import org.eclipse.jetty.server.handler.RequestLogHandler;
55  import org.eclipse.jetty.server.nio.SelectChannelConnector;
56  import org.eclipse.jetty.util.log.Log;
57  import org.eclipse.jetty.util.log.Logger;
58  import org.eclipse.jetty.util.resource.Resource;
59  import org.eclipse.jetty.webapp.WebAppContext;
60  import org.eclipse.jetty.xml.XmlConfiguration;
61  import org.osgi.framework.Bundle;
62  import org.osgi.framework.BundleContext;
63  import org.xml.sax.SAXException;
64  import org.xml.sax.SAXParseException;
65  
66  /**
67   * Bridges the jetty deployers with the OSGi lifecycle where applications are
68   * managed inside OSGi-bundles.
69   * <p>
70   * This class should be called as a consequence of the activation of a new
71   * service that is a ContextHandler.<br/>
72   * This way the new webapps are exposed as OSGi services.
73   * </p>
74   * <p>
75   * Helper methods to register a bundle that is a web-application or a context.
76   * </p>
77   * Limitations:
78   * <ul>
79   * <li>support for jarred webapps is somewhat limited.</li>
80   * </ul>
81   */
82  public class WebappRegistrationHelper
83  {
84  
85      private static Logger __logger = Log.getLogger(WebappRegistrationHelper.class.getName());
86  
87      private static boolean INITIALIZED = false;
88  
89      /**
90       * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports
91       * equinox and apache-felix fragment bundles that are specific to an OSGi
92       * implementation should set a different implementation.
93       */
94      public static BundleClassLoaderHelper BUNDLE_CLASS_LOADER_HELPER = null;
95      /**
96       * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports
97       * equinox and apache-felix fragment bundles that are specific to an OSGi
98       * implementation should set a different implementation.
99       */
100     public static BundleFileLocatorHelper BUNDLE_FILE_LOCATOR_HELPER = null;
101 
102     /**
103      * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports
104      * equinox and apache-felix fragment bundles that are specific to an OSGi
105      * implementation should set a different implementation.
106      * <p>
107      * Several of those objects can be added here: For example we could have an optional fragment that setups
108      * a specific implementation of JSF for the whole of jetty-osgi.
109      * </p>
110      */
111     public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>();
112 
113     private Server _server;
114     private ContextHandlerCollection _ctxtHandler;
115 
116     /**
117      * this class loader loads the jars inside {$jetty.home}/lib/ext it is meant
118      * as a migration path and for jars that are not OSGi ready. also gives
119      * access to the jsp jars.
120      */
121     // private URLClassLoader _libExtClassLoader;
122 
123     /**
124      * This is the class loader that should be the parent classloader of any
125      * webapp classloader. It is in fact the _libExtClassLoader with a trick to
126      * let the TldScanner find the jars where the tld files are.
127      */
128     private URLClassLoader _commonParentClassLoaderForWebapps;
129 
130     private DeploymentManager _deploymentManager;
131 
132     private OSGiAppProvider _provider;
133 
134     public WebappRegistrationHelper(Server server)
135     {
136         _server = server;
137         staticInit();
138     }
139 
140     // Inject the customizing classes that might be defined in fragment bundles.
141     private static synchronized void staticInit()
142     {
143         if (!INITIALIZED)
144         {
145             INITIALIZED = true;
146             // setup the custom BundleClassLoaderHelper
147             try
148             {
149                 BUNDLE_CLASS_LOADER_HELPER = (BundleClassLoaderHelper)Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance();
150             }
151             catch (Throwable t)
152             {
153                 // System.err.println("support for equinox and felix");
154                 BUNDLE_CLASS_LOADER_HELPER = new DefaultBundleClassLoaderHelper();
155             }
156             // setup the custom FileLocatorHelper
157             try
158             {
159                 BUNDLE_FILE_LOCATOR_HELPER = (BundleFileLocatorHelper)Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance();
160             }
161             catch (Throwable t)
162             {
163                 // System.err.println("no jsp/jasper support");
164                 BUNDLE_FILE_LOCATOR_HELPER = new DefaultFileLocatorHelper();
165             }
166         }
167     }
168 
169     /**
170      * Removes quotes around system property values before we try to make them
171      * into file pathes.
172      */
173     public static String stripQuotesIfPresent(String filePath)
174     {
175         if (filePath == null)
176             return null;
177 
178         if ((filePath.startsWith("\"") || filePath.startsWith("'")) && (filePath.endsWith("\"") || filePath.endsWith("'")))
179             return filePath.substring(1,filePath.length() - 1);
180         return filePath;
181     }
182     
183     /**
184      * Look for the home directory of jetty as defined by the system property
185      * 'jetty.home'. If undefined, look at the current bundle and uses its own
186      * jettyhome folder for this feature.
187      * <p>
188      * Special case: inside eclipse-SDK:<br/>
189      * If the bundle is jarred, see if we are inside eclipse-PDE itself. In that
190      * case, look for the installation directory of eclipse-PDE, try to create a
191      * jettyhome folder there and install the sample jettyhome folder at that
192      * location. This makes the installation in eclipse-SDK easier. <br/>
193      * This is a bit redundant with the work done by the jetty configuration
194      * launcher.
195      * </p>
196      * 
197      * @param context
198      * @throws Exception
199      */
200     public void setup(BundleContext context, Map<String, String> configProperties) throws Exception
201     {
202         File _installLocation = BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(context.getBundle());
203         // debug:
204         // new File("~/proj/eclipse-install/eclipse-3.5.1-SDK-jetty7/" +
205         // "dropins/jetty7/plugins/org.eclipse.jetty.osgi.boot_0.0.1.001-SNAPSHOT.jar");
206         boolean bootBundleCanBeJarred = true;
207         String jettyHome = stripQuotesIfPresent(System.getProperty("jetty.home"));
208 
209         if (jettyHome == null || jettyHome.length() == 0)
210         {
211             if (_installLocation.getName().endsWith(".jar"))
212             {
213                 jettyHome = JettyHomeHelper.setupJettyHomeInEclipsePDE(_installLocation);
214             }
215             if (jettyHome == null)
216             {
217                 jettyHome = _installLocation.getAbsolutePath() + "/jettyhome";
218                 bootBundleCanBeJarred = false;
219             }
220         }
221         // in case we stripped the quotes.
222         System.setProperty("jetty.home",jettyHome);
223 
224         String jettyLogs = stripQuotesIfPresent(System.getProperty("jetty.logs"));
225         if (jettyLogs == null || jettyLogs.length() == 0)
226         {
227             System.setProperty("jetty.logs",jettyHome + "/logs");
228         }
229 
230         if (!bootBundleCanBeJarred && !_installLocation.isDirectory())
231         {
232             String install = _installLocation != null?_installLocation.getCanonicalPath():" unresolved_install_location";
233             throw new IllegalArgumentException("The system property -Djetty.home" + " must be set to a directory or the bundle "
234                     + context.getBundle().getSymbolicName() + " installed here " + install + " must be unjarred.");
235 
236         }
237         try
238         {
239             System.err.println("JETTY_HOME set to " + new File(jettyHome).getCanonicalPath());
240         }
241         catch (Throwable t)
242         {
243             System.err.println("JETTY_HOME _set to " + new File(jettyHome).getAbsolutePath());
244         }
245 
246         ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
247         try
248         {
249 
250             // passing this bundle's classloader as the context classlaoder
251             // makes sure there is access to all the jetty's bundles
252 
253             File jettyHomeF = new File(jettyHome);
254             URLClassLoader libExtClassLoader = null;
255             try
256             {
257             	libExtClassLoader = LibExtClassLoaderHelper.createLibEtcClassLoaderHelper(jettyHomeF,_server,
258             			JettyBootstrapActivator.class.getClassLoader());
259             }
260             catch (MalformedURLException e)
261             {
262                 e.printStackTrace();
263             }
264 
265             Thread.currentThread().setContextClassLoader(libExtClassLoader);
266 
267             String jettyetc = System.getProperty(OSGiWebappConstants.SYS_PROP_JETTY_ETC_FILES,"etc/jetty.xml");
268             StringTokenizer tokenizer = new StringTokenizer(jettyetc,";,");
269             
270             Map<Object,Object> id_map = new HashMap<Object,Object>();
271             id_map.put("Server",_server);
272             Map<Object,Object> properties = new HashMap<Object,Object>();
273             properties.put("jetty.home",jettyHome);
274             properties.put("jetty.host",System.getProperty("jetty.host",""));
275             properties.put("jetty.port",System.getProperty("jetty.port","8080"));
276             properties.put("jetty.port.ssl",System.getProperty("jetty.port.ssl","8443"));
277 
278             while (tokenizer.hasMoreTokens())
279             {
280                 String etcFile = tokenizer.nextToken().trim();
281                 File conffile = etcFile.startsWith("/")?new File(etcFile):new File(jettyHomeF,etcFile);
282                 if (!conffile.exists())
283                 {
284                     __logger.warn("Unable to resolve the jetty/etc file " + etcFile);
285 
286                     if ("etc/jetty.xml".equals(etcFile))
287                     {
288                         // Missing jetty.xml file, so create a minimal Jetty configuration
289                         __logger.info("Configuring default server on 8080");
290                         SelectChannelConnector connector = new SelectChannelConnector();
291                         connector.setPort(8080);
292                         _server.addConnector(connector);
293 
294                         HandlerCollection handlers = new HandlerCollection();
295                         ContextHandlerCollection contexts = new ContextHandlerCollection();
296                         RequestLogHandler requestLogHandler = new RequestLogHandler();
297                         handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(), requestLogHandler });
298                         _server.setHandler(handlers);
299                     }
300                 }
301                 else
302                 {
303                     try
304                     {
305                         // Execute a Jetty configuration file
306                         XmlConfiguration config = new XmlConfiguration(new FileInputStream(conffile));
307                         config.setIdMap(id_map);
308                         config.setProperties(properties);
309                         config.configure();
310                         id_map=config.getIdMap();
311                     }
312                     catch (SAXParseException saxparse)
313                     {
314                         Log.getLogger(WebappRegistrationHelper.class.getName()).warn("Unable to configure the jetty/etc file " + etcFile,saxparse);
315                         throw saxparse;
316                     }
317                 }
318             }
319 
320             init();
321 
322             //now that we have an app provider we can call the registration customizer.
323             try
324             {
325                 URL[] jarsWithTlds = getJarsWithTlds();
326                 _commonParentClassLoaderForWebapps = jarsWithTlds == null?libExtClassLoader:new TldLocatableURLClassloader(libExtClassLoader,getJarsWithTlds());
327             }
328             catch (MalformedURLException e)
329             {
330                 e.printStackTrace();
331             }
332 
333             
334             _server.start();
335         }
336         catch (Throwable t)
337         {
338             t.printStackTrace();
339         }
340         finally
341         {
342             Thread.currentThread().setContextClassLoader(contextCl);
343         }
344 
345     }
346 
347     /**
348      * Must be called after the server is configured.
349      * 
350      * Locate the actual instance of the ContextDeployer and WebAppDeployer that
351      * was created when configuring the server through jetty.xml. If there is no
352      * such thing it won't be possible to deploy webapps from a context and we
353      * throw IllegalStateExceptions.
354      */
355     private void init()
356     {
357         // Get the context handler
358         _ctxtHandler = (ContextHandlerCollection)_server.getChildHandlerByClass(ContextHandlerCollection.class);
359         
360         // get a deployerManager
361         List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class);
362         if (deployers != null && !deployers.isEmpty())
363         {
364             _deploymentManager = deployers.get(0);
365             
366             for (AppProvider provider : _deploymentManager.getAppProviders())
367             {
368                 if (provider instanceof OSGiAppProvider)
369                 {
370                     _provider=(OSGiAppProvider)provider;
371                     break;
372                 }
373             }
374             if (_provider == null)
375             {
376             	//create it on the fly with reasonable default values.
377             	try
378             	{
379 					_provider = new OSGiAppProvider();
380 					_provider.setMonitoredDir(
381 							Resource.newResource(getDefaultOSGiContextsHome(
382 									new File(System.getProperty("jetty.home"))).toURI()));
383 				} catch (IOException e) {
384 					e.printStackTrace();
385 				}
386             	_deploymentManager.addAppProvider(_provider);
387             }
388         }
389 
390         if (_ctxtHandler == null || _provider==null)
391             throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured");
392         
393 
394     }
395 
396     /**
397      * Deploy a new web application on the jetty server.
398      * 
399      * @param context
400      *            The current bundle context
401      * @param webappFolderPath
402      *            The path to the root of the webapp. Must be a path relative to
403      *            bundle; either an absolute path.
404      * @param contextPath
405      *            The context path. Must start with "/"
406      * @param classInBundle
407      *            A class that belongs to the current bundle to inherit from the
408      *            osgi classloader. Null to not have access to the OSGI
409      *            classloader.
410      * @throws Exception
411      */
412     public ContextHandler registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath,
413             String overrideBundleInstallLocation, String webXmlPath, String defaultWebXmlPath) throws Exception
414     {
415         File bundleInstall = overrideBundleInstallLocation == null?BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle):new File(
416                 overrideBundleInstallLocation);
417         File webapp = null;
418         if (webappFolderPath != null && webappFolderPath.length() != 0 && !webappFolderPath.equals("."))
419         {
420             if (webappFolderPath.startsWith("/") || webappFolderPath.startsWith("file:/"))
421             {
422                 webapp = new File(webappFolderPath);
423             }
424             else
425             {
426                 webapp = new File(bundleInstall,webappFolderPath);
427             }
428         }
429         else
430         {
431             webapp = bundleInstall;
432         }
433         if (!webapp.exists())
434         {
435             throw new IllegalArgumentException("Unable to locate " + webappFolderPath + " inside "
436                     + (bundleInstall != null?bundleInstall.getAbsolutePath():"unlocated bundle '" + bundle.getSymbolicName() + "'"));
437         }
438         return registerWebapplication(bundle,webapp,contextPath,extraClasspath,bundleInstall,webXmlPath,defaultWebXmlPath);
439     }
440 
441     /**
442      * @See {@link WebAppDeployer#scan()}
443      * TODO: refacotr this into the createContext method of OSGiAppProvider.
444      * 
445      * @param webapp
446      * @param contextPath
447      * @param classInBundle
448      * @return The contexthandler created and started
449      * @throws Exception
450      */
451     public ContextHandler registerWebapplication(Bundle contributor, File webapp, String contextPath, String extraClasspath, File bundleInstall,
452             String webXmlPath, String defaultWebXmlPath) throws Exception
453     {
454 
455         ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
456         String[] oldServerClasses = null;
457         WebAppContext context = null;
458         try
459         {
460             // make sure we provide access to all the jetty bundles by going
461             // through this bundle.
462             OSGiWebappClassLoader composite = createWebappClassLoader(contributor);
463             // configure with access to all jetty classes and also all the classes
464             // that the contributor gives access to.
465             Thread.currentThread().setContextClassLoader(composite);
466 
467             context = new WebAppContext(webapp.getAbsolutePath(),contextPath);
468             context.setExtraClasspath(extraClasspath);
469 
470             if (webXmlPath != null && webXmlPath.length() != 0)
471             {
472                 File webXml = null;
473                 if (webXmlPath.startsWith("/") || webXmlPath.startsWith("file:/"))
474                 {
475                     webXml = new File(webXmlPath);
476                 }
477                 else
478                 {
479                     webXml = new File(bundleInstall,webXmlPath);
480                 }
481                 if (webXml.exists())
482                 {
483                     context.setDescriptor(webXml.getAbsolutePath());
484                 }
485             }
486 
487             if (defaultWebXmlPath == null || defaultWebXmlPath.length() == 0)
488             {
489             	//use the one defined by the OSGiAppProvider.
490             	defaultWebXmlPath = _provider.getDefaultsDescriptor();
491             }
492             if (defaultWebXmlPath != null && defaultWebXmlPath.length() != 0)
493             {
494                 File defaultWebXml = null;
495                 if (defaultWebXmlPath.startsWith("/") || defaultWebXmlPath.startsWith("file:/"))
496                 {
497                     defaultWebXml = new File(webXmlPath);
498                 }
499                 else
500                 {
501                     defaultWebXml = new File(bundleInstall,defaultWebXmlPath);
502                 }
503                 if (defaultWebXml.exists())
504                 {
505                     context.setDefaultsDescriptor(defaultWebXml.getAbsolutePath());
506                 }
507             }
508             
509             //other parameters that might be defines on the OSGiAppProvider:
510             context.setParentLoaderPriority(_provider.isParentLoaderPriority());
511 
512             configureWebAppContext(context,contributor);
513             configureWebappClassLoader(contributor,context,composite);
514 
515             // @see
516             // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext)
517             // during initialization of the webapp all the jetty packages are
518             // visible
519             // through the webapp classloader.
520             oldServerClasses = context.getServerClasses();
521             context.setServerClasses(null);
522             _provider.addContext(context);
523 
524             return context;
525         }
526         finally
527         {
528             if (context != null && oldServerClasses != null)
529             {
530                 context.setServerClasses(oldServerClasses);
531             }
532             Thread.currentThread().setContextClassLoader(contextCl);
533         }
534 
535     }
536 
537     /**
538      * Stop a ContextHandler and remove it from the collection.
539      * 
540      * @See ContextDeployer#undeploy
541      * @param contextHandler
542      * @throws Exception
543      */
544     public void unregister(ContextHandler contextHandler) throws Exception
545     {
546         contextHandler.stop();
547         _ctxtHandler.removeHandler(contextHandler);
548     }
549 
550     /**
551      * @return The default folder in which the context files of the osgi bundles
552      *         are located and watched. Or null when the system property
553      *         "jetty.osgi.contexts.home" is not defined.
554      *         If the configuration file defines the OSGiAppProvider's context.
555      *         This will not be taken into account.
556      */
557     File getDefaultOSGiContextsHome(File jettyHome)
558     {
559         String jettyContextsHome = System.getProperty("jetty.osgi.contexts.home");
560         if (jettyContextsHome != null)
561         {
562             File contextsHome = new File(jettyContextsHome);
563             if (!contextsHome.exists() || !contextsHome.isDirectory())
564             {
565                 throw new IllegalArgumentException("the ${jetty.osgi.contexts.home} '" + jettyContextsHome + " must exist and be a folder");
566             }
567             return contextsHome;
568         }
569         return new File(jettyHome, "/contexts");
570     }
571     
572     File getOSGiContextsHome()
573     {
574     	return _provider.getContextXmlDirAsFile();
575     }
576 
577     /**
578      * This type of registration relies on jetty's complete context xml file.
579      * Context encompasses jndi and all other things. This makes the definition
580      * of the webapp a lot more self-contained.
581      * 
582      * @param webapp
583      * @param contextPath
584      * @param classInBundle
585      * @throws Exception
586      */
587     public ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath, String overrideBundleInstallLocation)
588             throws Exception
589     {
590         File contextsHome = _provider.getContextXmlDirAsFile();
591         if (contextsHome != null)
592         {
593             File prodContextFile = new File(contextsHome,contributor.getSymbolicName() + "/" + contextFileRelativePath);
594             if (prodContextFile.exists())
595             {
596                 return registerContext(contributor,prodContextFile,extraClasspath,overrideBundleInstallLocation);
597             }
598         }
599         File contextFile = overrideBundleInstallLocation != null?new File(overrideBundleInstallLocation,contextFileRelativePath):new File(
600                 BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(contributor),contextFileRelativePath);
601         if (contextFile.exists())
602         {
603             return registerContext(contributor,contextFile,extraClasspath,overrideBundleInstallLocation);
604         }
605         else
606         {
607             if (contextFileRelativePath.startsWith("./"))
608             {
609                 contextFileRelativePath = contextFileRelativePath.substring(1);
610             }
611             if (!contextFileRelativePath.startsWith("/"))
612             {
613                 contextFileRelativePath = "/" + contextFileRelativePath;
614             }
615             if (overrideBundleInstallLocation == null)
616             {
617                 URL contextURL = contributor.getEntry(contextFileRelativePath);
618                 if (contextURL != null)
619                 {
620                     return registerContext(contributor,contextURL.openStream(),extraClasspath,overrideBundleInstallLocation);
621                 }
622             }
623             else
624             {
625                 JarFile zipFile = null;
626                 try
627                 {
628                     zipFile = new JarFile(overrideBundleInstallLocation);
629                     ZipEntry entry = zipFile.getEntry(contextFileRelativePath.substring(1));
630                     return registerContext(contributor,zipFile.getInputStream(entry),extraClasspath,overrideBundleInstallLocation);
631                 }
632                 catch (Throwable t)
633                 {
634 
635                 }
636                 finally
637                 {
638                     if (zipFile != null)
639                         try
640                         {
641                             zipFile.close();
642                         }
643                         catch (IOException ioe)
644                         {
645                         }
646                 }
647             }
648             throw new IllegalArgumentException("Could not find the context " + "file " + contextFileRelativePath + " for the bundle "
649                     + contributor.getSymbolicName() + (overrideBundleInstallLocation != null?" using the install location " + overrideBundleInstallLocation:""));
650         }
651     }
652 
653     /**
654      * This type of registration relies on jetty's complete context xml file.
655      * Context encompasses jndi and all other things. This makes the definition
656      * of the webapp a lot more self-contained.
657      * 
658      * @param webapp
659      * @param contextPath
660      * @param classInBundle
661      * @throws Exception
662      */
663     private ContextHandler registerContext(Bundle contributor, File contextFile, String extraClasspath, String overrideBundleInstallLocation) throws Exception
664     {
665         InputStream contextFileInputStream = null;
666         try
667         {
668             contextFileInputStream = new BufferedInputStream(new FileInputStream(contextFile));
669             return registerContext(contributor,contextFileInputStream,extraClasspath,overrideBundleInstallLocation);
670         }
671         finally
672         {
673             if (contextFileInputStream != null)
674                 try
675                 {
676                     contextFileInputStream.close();
677                 }
678                 catch (IOException ioe)
679                 {
680                 }
681         }
682     }
683 
684     /**
685      * @param contributor
686      * @param contextFileInputStream
687      * @return The ContextHandler created and registered or null if it did not
688      *         happen.
689      * @throws Exception
690      */
691     private ContextHandler registerContext(Bundle contributor, InputStream contextFileInputStream, String extraClasspath, String overrideBundleInstallLocation)
692             throws Exception
693     {
694         ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
695         String[] oldServerClasses = null;
696         WebAppContext webAppContext = null;
697         try
698         {
699             // make sure we provide access to all the jetty bundles by going
700             // through this bundle.
701             OSGiWebappClassLoader composite = createWebappClassLoader(contributor);
702             // configure with access to all jetty classes and also all the
703             // classes
704             // that the contributor gives access to.
705             Thread.currentThread().setContextClassLoader(composite);
706             ContextHandler context = createContextHandler(contributor,contextFileInputStream,extraClasspath,overrideBundleInstallLocation);
707             if (context == null)
708             {
709                 return null;// did not happen
710             }
711 
712             // ok now register this webapp. we checked when we started jetty
713             // that there
714             // was at least one such handler for webapps.
715             //the actual registration must happen via the new Deployment API.
716 //            _ctxtHandler.addHandler(context);
717 
718             configureWebappClassLoader(contributor,context,composite);
719             if (context instanceof WebAppContext)
720             {
721                 webAppContext = (WebAppContext)context;
722                 // @see
723                 // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext)
724                 oldServerClasses = webAppContext.getServerClasses();
725                 webAppContext.setServerClasses(null);
726             }
727 
728             context.start();
729             return context;
730         }
731         finally
732         {
733             if (webAppContext != null)
734             {
735                 webAppContext.setServerClasses(oldServerClasses);
736             }
737             Thread.currentThread().setContextClassLoader(contextCl);
738         }
739 
740     }
741 
742     /**
743      * TODO: right now only the jetty-jsp bundle is scanned for common taglibs.
744      * Should support a way to plug more bundles that contain taglibs.
745      * 
746      * The jasper TldScanner expects a URLClassloader to parse a jar for the
747      * /META-INF/*.tld it may contain. We place the bundles that we know contain
748      * such tag-libraries. Please note that it will work if and only if the
749      * bundle is a jar (!) Currently we just hardcode the bundle that contains
750      * the jstl implemenation.
751      * 
752      * A workaround when the tld cannot be parsed with this method is to copy
753      * and paste it inside the WEB-INF of the webapplication where it is used.
754      * 
755      * Support only 2 types of packaging for the bundle: - the bundle is a jar
756      * (recommended for runtime.) - the bundle is a folder and contain jars in
757      * the root and/or in the lib folder (nice for PDE developement situations)
758      * Unsupported: the bundle is a jar that embeds more jars.
759      * 
760      * @return
761      * @throws Exception
762      */
763     private URL[] getJarsWithTlds() throws Exception
764     {
765         ArrayList<URL> res = new ArrayList<URL>();
766         for (WebappRegistrationCustomizer regCustomizer : JSP_REGISTRATION_HELPERS)
767         {
768             URL[] urls = regCustomizer.getJarsWithTlds(_provider, BUNDLE_FILE_LOCATOR_HELPER);
769             for (URL url : urls)
770             {
771                 if (!res.contains(url))
772                     res.add(url);
773             }
774         }
775         if (!res.isEmpty())
776             return res.toArray(new URL[res.size()]);
777         else
778             return null;
779     }
780 
781     /**
782      * Applies the properties of WebAppDeployer as defined in jetty.xml.
783      * 
784      * @see {WebAppDeployer#scan} around the comment
785      *      <code>// configure it</code>
786      */
787     protected void configureWebAppContext(WebAppContext wah, Bundle contributor)
788     {
789         // rfc66
790         wah.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT,contributor.getBundleContext());
791 
792         //spring-dm-1.2.1 looks for the BundleContext as a different attribute.
793         //not a spec... but if we want to support 
794         //org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
795         //then we need to do this to:
796         wah.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(),
797                         contributor.getBundleContext());
798         
799     }
800 
801     /**
802      * @See {@link ContextDeployer#scan}
803      * @param contextFile
804      * @return
805      */
806     protected ContextHandler createContextHandler(Bundle bundle, File contextFile, String extraClasspath, String overrideBundleInstallLocation)
807     {
808         try
809         {
810             return createContextHandler(bundle,new BufferedInputStream(new FileInputStream(contextFile)),extraClasspath,overrideBundleInstallLocation);
811         }
812         catch (FileNotFoundException e)
813         {
814             e.printStackTrace();
815         }
816         return null;
817     }
818 
819     /**
820      * @See {@link ContextDeployer#scan}
821      * @param contextFile
822      * @return
823      */
824     @SuppressWarnings("unchecked")
825     protected ContextHandler createContextHandler(Bundle bundle, InputStream contextInputStream, String extraClasspath, String overrideBundleInstallLocation)
826     {
827         /*
828          * Do something identical to what the ContextProvider would have done:
829          * XmlConfiguration xmlConfiguration=new
830          * XmlConfiguration(resource.getURL()); HashMap properties = new
831          * HashMap(); properties.put("Server", _contexts.getServer()); if
832          * (_configMgr!=null) properties.putAll(_configMgr.getProperties());
833          * 
834          * xmlConfiguration.setProperties(properties); ContextHandler
835          * context=(ContextHandler)xmlConfiguration.configure();
836          * context.setAttributes(new AttributesMap(_contextAttributes));
837          */
838         try
839         {
840             XmlConfiguration xmlConfiguration = new XmlConfiguration(contextInputStream);
841             HashMap properties = new HashMap();
842             properties.put("Server",_server);
843             
844             // insert the bundle's location as a property.
845             setThisBundleHomeProperty(bundle,properties,overrideBundleInstallLocation);
846             xmlConfiguration.setProperties(properties);
847 
848             ContextHandler context = (ContextHandler)xmlConfiguration.configure();
849             if (context instanceof WebAppContext)
850             {
851                 ((WebAppContext)context).setExtraClasspath(extraClasspath);
852                 ((WebAppContext)context).setParentLoaderPriority(_provider.isParentLoaderPriority());
853                 if (_provider.getDefaultsDescriptor() != null && _provider.getDefaultsDescriptor().length() != 0)
854                 {
855                 	((WebAppContext)context).setDefaultsDescriptor(_provider.getDefaultsDescriptor());
856                 }
857             }
858 
859             // rfc-66:
860             context.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT,bundle.getBundleContext());
861 
862             //spring-dm-1.2.1 looks for the BundleContext as a different attribute.
863             //not a spec... but if we want to support 
864             //org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
865             //then we need to do this to:
866             context.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(),
867                             bundle.getBundleContext());
868             return context;
869         }
870         catch (FileNotFoundException e)
871         {
872             return null;
873         }
874         catch (SAXException e)
875         {
876             // TODO Auto-generated catch block
877             e.printStackTrace();
878         }
879         catch (IOException e)
880         {
881             // TODO Auto-generated catch block
882             e.printStackTrace();
883         }
884         catch (Throwable e)
885         {
886             // TODO Auto-generated catch block
887             e.printStackTrace();
888         }
889         finally
890         {
891             if (contextInputStream != null)
892                 try
893                 {
894                     contextInputStream.close();
895                 }
896                 catch (IOException ioe)
897                 {
898                 }
899         }
900         return null;
901     }
902 
903     /**
904      * Configure a classloader onto the context. If the context is a
905      * WebAppContext, build a WebAppClassLoader that has access to all the jetty
906      * classes thanks to the classloader of the JettyBootStrapper bundle and
907      * also has access to the classloader of the bundle that defines this
908      * context.
909      * <p>
910      * If the context is not a WebAppContext, same but with a simpler
911      * URLClassLoader. Note that the URLClassLoader is pretty much fake: it
912      * delegate all actual classloading to the parent classloaders.
913      * </p>
914      * <p>
915      * The URL[] returned by the URLClassLoader create contained specifically
916      * the jars that some j2ee tools expect and look into. For example the jars
917      * that contain tld files for jasper's jstl support.
918      * </p>
919      * <p>
920      * Also as the jars in the lib folder and the classes in the classes folder
921      * might already be in the OSGi classloader we filter them out of the
922      * WebAppClassLoader
923      * </p>
924      * 
925      * @param context
926      * @param contributor
927      * @param webapp
928      * @param contextPath
929      * @param classInBundle
930      * @throws Exception
931      */
932     protected void configureWebappClassLoader(Bundle contributor, ContextHandler context, OSGiWebappClassLoader webappClassLoader) throws Exception
933     {
934         if (context instanceof WebAppContext)
935         {
936             WebAppContext webappCtxt = (WebAppContext)context;
937             context.setClassLoader(webappClassLoader);
938             webappClassLoader.setWebappContext(webappCtxt);
939         }
940         else
941         {
942             context.setClassLoader(webappClassLoader);
943         }
944     }
945 
946     /**
947      * No matter what the type of webapp, we create a WebappClassLoader.
948      */
949     protected OSGiWebappClassLoader createWebappClassLoader(Bundle contributor) throws Exception
950     {
951         // we use a temporary WebAppContext object.
952         // if this is a real webapp we will set it on it a bit later: once we
953         // know.
954         OSGiWebappClassLoader webappClassLoader = new OSGiWebappClassLoader(_commonParentClassLoaderForWebapps,new WebAppContext(),contributor);
955         return webappClassLoader;
956     }
957 
958     /**
959      * Set the property &quot;this.bundle.install&quot; to point to the location
960      * of the bundle. Useful when <SystemProperty name="this.bundle.home"/> is
961      * used.
962      */
963     private void setThisBundleHomeProperty(Bundle bundle, HashMap<String, Object> properties, String overrideBundleInstallLocation)
964     {
965         try
966         {
967             File location = overrideBundleInstallLocation != null?new File(overrideBundleInstallLocation):BUNDLE_FILE_LOCATOR_HELPER
968                     .getBundleInstallLocation(bundle);
969             properties.put("this.bundle.install",location.getCanonicalPath());
970         }
971         catch (Throwable t)
972         {
973             System.err.println("Unable to set 'this.bundle.install' " + " for the bundle " + bundle.getSymbolicName());
974             t.printStackTrace();
975         }
976     }
977 
978 
979 }