View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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;
20  
21  import java.io.File;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Dictionary;
26  import java.util.Enumeration;
27  import java.util.HashMap;
28  import java.util.List;
29  
30  import org.eclipse.jetty.deploy.App;
31  import org.eclipse.jetty.deploy.AppProvider;
32  import org.eclipse.jetty.deploy.DeploymentManager;
33  import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
34  import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader;
35  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
36  import org.eclipse.jetty.server.handler.ContextHandler;
37  import org.eclipse.jetty.util.ArrayUtil;
38  import org.eclipse.jetty.util.component.AbstractLifeCycle;
39  import org.eclipse.jetty.util.Loader;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  import org.eclipse.jetty.util.resource.JarResource;
43  import org.eclipse.jetty.util.resource.Resource;
44  import org.eclipse.jetty.webapp.WebAppContext;
45  import org.eclipse.jetty.xml.XmlConfiguration;
46  import org.osgi.framework.Bundle;
47  import org.osgi.framework.BundleContext;
48  import org.osgi.framework.ServiceReference;
49  import org.osgi.service.packageadmin.PackageAdmin;
50  
51  
52  
53  
54  /**
55   * AbstractWebAppProvider
56   *
57   * Base class for Jetty DeploymentManager Providers that are capable of deploying a webapp,
58   * either from a bundle or an OSGi service.
59   * 
60   */
61  public abstract class AbstractWebAppProvider extends AbstractLifeCycle implements AppProvider
62  {
63      private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class);
64      
65      public static String __defaultConfigurations[] = {
66                                                              "org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration",
67                                                              "org.eclipse.jetty.webapp.WebXmlConfiguration",
68                                                              "org.eclipse.jetty.webapp.MetaInfConfiguration",
69                                                              "org.eclipse.jetty.webapp.FragmentConfiguration",
70                                                              "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"                          
71                                                       };
72      
73      public static void setDefaultConfigurations (String[] defaultConfigs)
74      {
75          __defaultConfigurations = defaultConfigs;
76      }
77      
78      public static String[] getDefaultConfigurations ()
79      {
80          List<String> configs = ArrayUtil.asMutableList(__defaultConfigurations);
81          if (annotationsAvailable())
82          {
83              //add before JettyWebXmlConfiguration
84              int i = configs.indexOf("org.eclipse.jetty.webapp.JettyWebXmlConfiguration");
85              configs.add(i, "org.eclipse.jetty.osgi.annotations.AnnotationConfiguration");
86          }
87          
88          if (jndiAvailable())
89          {
90              //add in EnvConfiguration and PlusConfiguration just after FragmentConfiguration
91              int i = configs.indexOf("org.eclipse.jetty.webapp.FragmentConfiguration");
92              configs.add(++i, "org.eclipse.jetty.plus.webapp.EnvConfiguration");
93              configs.add(++i, "org.eclipse.jetty.plus.webapp.PlusConfiguration");
94          }
95  
96          return configs.toArray(new String[configs.size()]);
97      }
98  
99      private static boolean annotationsAvailable()
100     {
101         boolean result = false;
102         try
103         {
104             Loader.loadClass(AbstractWebAppProvider.class,"org.eclipse.jetty.annotations.AnnotationConfiguration");
105             result = true;
106             LOG.debug("Annotation support detected");
107         }
108         catch (ClassNotFoundException e)
109         {
110             result = false;
111             LOG.debug("No annotation support detected");
112         }
113 
114         return result;
115     }
116     
117     
118     private static boolean jndiAvailable()
119     {
120         try
121         {
122             Loader.loadClass(AbstractWebAppProvider.class, "org.eclipse.jetty.plus.jndi.Resource");
123             Loader.loadClass(AbstractWebAppProvider.class, "org.eclipse.jetty.plus.webapp.EnvConfiguration");
124             LOG.debug("JNDI support detected");
125             return true;
126         }
127         catch (ClassNotFoundException e)
128         {
129             LOG.debug("No JNDI support detected");
130             return false;
131         }
132     }
133     
134 
135     private boolean _parentLoaderPriority;
136 
137     private String _defaultsDescriptor;
138 
139     private boolean _extractWars = true; //See WebAppContext.extractWars
140 
141     private String _tldBundles;
142     
143     private DeploymentManager _deploymentManager;
144     
145     private String[] _configurationClasses;
146     
147     private ServerInstanceWrapper _serverWrapper;
148     
149     /* ------------------------------------------------------------ */
150     /**
151      * OSGiApp
152      *
153      *
154      */
155     public class OSGiApp extends AbstractOSGiApp
156     {
157         private String _contextPath;
158         private String _webAppPath;
159         private WebAppContext _webApp;
160 
161         public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId)
162         {
163             super(manager, provider, bundle, originId);
164         }
165         
166         public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId)
167         {
168             super(manager, provider, bundle, properties, originId);
169         }
170      
171         public void setWebAppContext (WebAppContext webApp)
172         {
173             _webApp = webApp;
174         }
175 
176         public String getContextPath()
177         {
178             return _contextPath;
179         }
180 
181         public void setContextPath(String contextPath)
182         {
183             this._contextPath = contextPath;
184         }
185 
186         public String getBundlePath()
187         {
188             return _webAppPath;
189         }
190 
191         public void setWebAppPath(String path)
192         {
193             this._webAppPath = path;
194         }
195         
196         
197         public ContextHandler createContextHandler()
198         throws Exception
199         {
200             if (_webApp != null)
201             {
202                 configureWebApp();
203                 return _webApp;
204             }
205             
206             createWebApp();
207             return _webApp;
208         }
209         
210       
211         
212         protected void createWebApp ()
213         throws Exception
214         {
215             _webApp = newWebApp();
216             configureWebApp();
217         }
218         
219         protected WebAppContext newWebApp ()
220         {
221             WebAppContext webApp = new WebAppContext();
222             webApp.setAttribute(OSGiWebappConstants.WATERMARK, OSGiWebappConstants.WATERMARK);
223 
224             //make sure we protect also the osgi dirs specified by OSGi Enterprise spec
225             String[] targets = webApp.getProtectedTargets();
226             String[] updatedTargets = null;
227             if (targets != null)
228             {
229                 updatedTargets = new String[targets.length+OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length];
230                 System.arraycopy(targets, 0, updatedTargets, 0, targets.length);
231             }
232             else
233                 updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length];
234             System.arraycopy(OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, targets.length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length);
235             webApp.setProtectedTargets(updatedTargets);
236 
237            return webApp;
238         }
239 
240 
241         public void configureWebApp() 
242         throws Exception
243         {                     
244             //TODO turn this around and let any context.xml file get applied first, and have the properties override
245             _webApp.setContextPath(_contextPath);
246             
247             //osgi Enterprise Spec r4 p.427
248             _webApp.setAttribute(OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext());
249 
250             String overrideBundleInstallLocation = (String)_properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE);
251             File bundleInstallLocation = 
252                 (overrideBundleInstallLocation == null 
253                         ? BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle) 
254                         : new File(overrideBundleInstallLocation));
255             
256             if (LOG.isDebugEnabled())
257             {
258                 LOG.debug("Bundle location is {}, install location: {}", _bundle.getLocation(), bundleInstallLocation);
259             }
260             
261             URL url = null;
262             Resource rootResource = Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(bundleInstallLocation.toURI().toURL()));
263             //try and make sure the rootResource is useable - if its a jar then make it a jar file url
264             if (rootResource.exists()&& !rootResource.isDirectory() && !rootResource.toString().startsWith("jar:"))
265             {
266                Resource jarResource = JarResource.newJarResource(rootResource);
267                if (jarResource.exists() && jarResource.isDirectory())
268                    rootResource = jarResource;
269             }
270             
271             //if the path wasn't set or it was ., then it is the root of the bundle's installed location
272             if (_webAppPath == null || _webAppPath.length() == 0 || ".".equals(_webAppPath))
273             {
274                 url = bundleInstallLocation.toURI().toURL();
275                 if (LOG.isDebugEnabled())
276                     LOG.debug("Webapp base using bundle install location: {}", url);
277             }
278             else
279             {
280                 //Get the location of the root of the webapp inside the installed bundle
281                 if (_webAppPath.startsWith("/") || _webAppPath.startsWith("file:"))
282                 {
283                     url = new File(_webAppPath).toURI().toURL();
284                     if (LOG.isDebugEnabled())
285                         LOG.debug("Webapp base using absolute location: {}", url);
286                 }
287                 else if (bundleInstallLocation != null && bundleInstallLocation.isDirectory())
288                 {
289                     url = new File(bundleInstallLocation, _webAppPath).toURI().toURL();
290                     if (LOG.isDebugEnabled())
291                         LOG.debug("Webapp base using path relative to bundle unpacked install location: {}", url);
292                 }
293                 else if (bundleInstallLocation != null)
294                 {
295                     Enumeration<URL> urls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(_bundle, _webAppPath);
296                     if (urls != null && urls.hasMoreElements())
297                     {
298                         url = urls.nextElement();
299                         if (LOG.isDebugEnabled())
300                             LOG.debug("Webapp base using path relative to packed bundle location: {}", url);
301                     }
302                 }
303             }
304 
305             if (url == null)
306             { 
307                 throw new IllegalArgumentException("Unable to locate " + _webAppPath
308                                                    + " in "
309                                                    + (bundleInstallLocation != null ? bundleInstallLocation.getAbsolutePath() : "unlocated bundle '" + _bundle.getSymbolicName()+ "'"));
310             }
311 
312             //Sets the location of the war file
313             // converts bundleentry: protocol if necessary
314             _webApp.setWar(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(url).toString());
315 
316             // Set up what has been configured on the provider
317             _webApp.setParentLoaderPriority(isParentLoaderPriority());
318             _webApp.setExtractWAR(isExtract());
319             if (getConfigurationClasses() != null)
320                 _webApp.setConfigurationClasses(getConfigurationClasses());
321             else
322                 _webApp.setConfigurationClasses(getDefaultConfigurations());
323 
324             if (getDefaultsDescriptor() != null)
325                 _webApp.setDefaultsDescriptor(getDefaultsDescriptor());
326 
327             //Set up configuration from manifest headers
328             //extra classpath
329             String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH);
330             if (tmp != null)
331                 _webApp.setExtraClasspath(tmp);
332 
333             //web.xml
334             tmp = (String)_properties.get(OSGiWebappConstants.JETTY_WEB_XML_PATH);
335             if (tmp != null && tmp.trim().length() != 0)
336             {
337                 File webXml = getFile (tmp, bundleInstallLocation);
338                 if (webXml != null && webXml.exists())
339                     _webApp.setDescriptor(webXml.getAbsolutePath());
340             }
341 
342             //webdefault.xml
343             tmp = (String)_properties.get(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH);
344             if (tmp != null)
345             {
346                 File defaultWebXml = getFile (tmp, bundleInstallLocation);
347                 if (defaultWebXml != null)
348                 {
349                     if (defaultWebXml.exists())
350                         _webApp.setDefaultsDescriptor(defaultWebXml.getAbsolutePath());
351                     else
352                         LOG.warn(defaultWebXml.getAbsolutePath()+" does not exist");
353                 }
354             }
355 
356             //Handle Require-TldBundle
357             //This is a comma separated list of names of bundles that contain tlds that this webapp uses.
358             //We add them to the webapp classloader.
359             String requireTldBundles = (String)_properties.get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
360             String pathsToTldBundles = getPathsToRequiredBundles(requireTldBundles);
361 
362 
363             // make sure we provide access to all the jetty bundles by going
364             // through this bundle.
365             OSGiWebappClassLoader webAppLoader = new OSGiWebappClassLoader(_serverWrapper.getParentClassLoaderForWebapps(), _webApp, _bundle);
366 
367             if (pathsToTldBundles != null)
368                 webAppLoader.addClassPath(pathsToTldBundles);
369             _webApp.setClassLoader(webAppLoader);
370 
371 
372             // apply any META-INF/context.xml file that is found to configure
373             // the webapp first
374             applyMetaInfContextXml(rootResource, overrideBundleInstallLocation);
375 
376             _webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles);
377 
378             //Set up some attributes
379             // rfc66
380             _webApp.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT, _bundle.getBundleContext());
381 
382             // spring-dm-1.2.1 looks for the BundleContext as a different attribute.
383             // not a spec... but if we want to support
384             // org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
385             // then we need to do this to:
386             _webApp.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), _bundle.getBundleContext());
387 
388             // also pass the bundle directly. sometimes a bundle does not have a
389             // bundlecontext.
390             // it is still useful to have access to the Bundle from the servlet
391             // context.
392             _webApp.setAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE, _bundle);
393         }
394 
395         protected String getPathsToRequiredBundles (String requireTldBundles)
396         throws Exception
397         {
398             if (requireTldBundles == null) return null;
399 
400             ServiceReference ref = _bundle.getBundleContext().getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
401             PackageAdmin packageAdmin = (ref == null) ? null : (PackageAdmin)_bundle.getBundleContext().getService(ref);
402             if (packageAdmin == null)
403                 throw new IllegalStateException("Unable to get PackageAdmin reference to locate required Tld bundles");
404 
405             StringBuilder paths = new StringBuilder();         
406             String[] symbNames = requireTldBundles.split(", ");
407 
408             for (String symbName : symbNames)
409             {
410                 Bundle[] bs = packageAdmin.getBundles(symbName, null);
411                 if (bs == null || bs.length == 0) 
412                 { 
413                     throw new IllegalArgumentException("Unable to locate the bundle '" + symbName
414                                                        + "' specified by "
415                                                        + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
416                                                        + " in manifest of "
417                                                        + (_bundle == null ? "unknown" : _bundle.getSymbolicName())); 
418                 }
419 
420                 File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bs[0]);
421                 if (paths.length() > 0) paths.append(", ");
422                 paths.append(f.toURI().toURL().toString());
423                 LOG.debug("getPathsToRequiredBundles: bundle path=" + bs[0].getLocation() + " uri=" + f.toURI());
424             }
425 
426             return paths.toString();
427         }
428         
429         
430         protected void applyMetaInfContextXml(Resource rootResource, String overrideBundleInstallLocation)
431         throws Exception
432         {
433             if (_bundle == null) return;
434             if (_webApp == null) return;
435 
436             ClassLoader cl = Thread.currentThread().getContextClassLoader();
437             LOG.debug("Context classloader = " + cl);
438             try
439             {
440                
441                 Thread.currentThread().setContextClassLoader(_webApp.getClassLoader());
442 
443                 //TODO replace this with getting the InputStream so we don't cache in URL
444                 //Try looking for a context xml file in META-INF with a specific name
445                 URL contextXmlUrl = _bundle.getEntry("/META-INF/jetty-webapp-context.xml");
446                 
447                 if (contextXmlUrl == null)
448                 {
449                     //Didn't find specially named file, try looking for a property that names a context xml file to use
450                     if (_properties != null)
451                     {
452                         String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
453                         if (tmp != null)
454                         {
455                             String[] filenames = tmp.split(",;");
456                             if (filenames != null && filenames.length > 0)
457                             {
458                                 String filename = filenames[0]; //should only be 1 filename in this usage
459                                 String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
460                                 if (jettyHome == null)
461                                     jettyHome =  System.getProperty(OSGiServerConstants.JETTY_HOME);
462                                 Resource res = findFile(filename, jettyHome, overrideBundleInstallLocation, _bundle);
463                                 if (res != null)
464                                     contextXmlUrl = res.getURL();
465                             }
466                         }
467                     }
468                 }
469                 if (contextXmlUrl == null) return;
470 
471                 // Apply it just as the standard jetty ContextProvider would do
472                 LOG.info("Applying " + contextXmlUrl + " to " + _webApp);
473 
474                 XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl);
475                 HashMap properties = new HashMap();
476                 properties.put("Server", getDeploymentManager().getServer());
477                 properties.put(OSGiWebappConstants.JETTY_BUNDLE_ROOT, rootResource.toString());
478                 properties.put(OSGiServerConstants.JETTY_HOME, getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME));
479                 xmlConfiguration.getProperties().putAll(properties);
480                 xmlConfiguration.configure(_webApp);
481             }
482             finally
483             {
484                 Thread.currentThread().setContextClassLoader(cl);
485             }
486         }
487         
488         private File getFile (String file, File bundleInstall)
489         {
490             if (file == null)
491                 return null;
492 
493             if (file.startsWith("/") || file.startsWith("file:/")) //absolute location
494                 return new File(file);
495             else
496             {
497                 //relative location
498                 //try inside the bundle first
499                 File f = new File (bundleInstall, file);
500                 if (f.exists()) return f;
501                 String jettyHome = (String)getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
502                 if (jettyHome != null)
503                     return new File(jettyHome, file);
504             }
505             
506             return null;
507         }
508     }
509    
510     /* ------------------------------------------------------------ */
511     public AbstractWebAppProvider (ServerInstanceWrapper wrapper)
512     {
513         _serverWrapper = wrapper;
514     }
515     
516     
517     
518     /* ------------------------------------------------------------ */
519     /**
520      * Get the parentLoaderPriority.
521      * 
522      * @return the parentLoaderPriority
523      */
524     public boolean isParentLoaderPriority()
525     {
526         return _parentLoaderPriority;
527     }
528 
529     /* ------------------------------------------------------------ */
530     /**
531      * Set the parentLoaderPriority.
532      * 
533      * @param parentLoaderPriority the parentLoaderPriority to set
534      */
535     public void setParentLoaderPriority(boolean parentLoaderPriority)
536     {
537         _parentLoaderPriority = parentLoaderPriority;
538     }
539 
540     /* ------------------------------------------------------------ */
541     /**
542      * Get the defaultsDescriptor.
543      * 
544      * @return the defaultsDescriptor
545      */
546     public String getDefaultsDescriptor()
547     {
548         return _defaultsDescriptor;
549     }
550 
551     /* ------------------------------------------------------------ */
552     /**
553      * Set the defaultsDescriptor.
554      * 
555      * @param defaultsDescriptor the defaultsDescriptor to set
556      */
557     public void setDefaultsDescriptor(String defaultsDescriptor)
558     {
559         _defaultsDescriptor = defaultsDescriptor;
560     }
561     
562     
563     /* ------------------------------------------------------------ */
564     public boolean isExtract()
565     {
566         return _extractWars;
567     }
568     
569     
570     /* ------------------------------------------------------------ */
571     public void setExtract(boolean extract)
572     {
573         _extractWars = extract;
574     }
575     
576     
577     /* ------------------------------------------------------------ */
578     /**
579      * @param tldBundles Comma separated list of bundles that contain tld jars
580      *            that should be setup on the jetty instances created here.
581      */
582     public void setTldBundles(String tldBundles)
583     {
584         _tldBundles = tldBundles;
585     }
586     
587     
588     /* ------------------------------------------------------------ */
589     /**
590      * @return The list of bundles that contain tld jars that should be setup on
591      *         the jetty instances created here.
592      */
593     public String getTldBundles()
594     {
595         return _tldBundles;
596     }
597     
598     /* ------------------------------------------------------------ */
599     /**
600      * @param configurations The configuration class names.
601      */
602     public void setConfigurationClasses(String[] configurations)
603     {
604         _configurationClasses = configurations == null ? null : (String[]) configurations.clone();
605     }
606 
607     /* ------------------------------------------------------------ */
608     /**
609      * 
610      */
611     public String[] getConfigurationClasses()
612     {
613         return _configurationClasses;
614     }
615 
616     /* ------------------------------------------------------------ */
617     public void setServerInstanceWrapper(ServerInstanceWrapper wrapper)
618     {
619         _serverWrapper = wrapper;
620     }
621 
622     public ServerInstanceWrapper getServerInstanceWrapper()
623     {
624         return _serverWrapper;
625     }
626 
627     /* ------------------------------------------------------------ */
628     /**
629      * @return
630      */
631     public DeploymentManager getDeploymentManager()
632     {
633         return _deploymentManager;
634     }
635 
636     /* ------------------------------------------------------------ */
637     /** 
638      * @see org.eclipse.jetty.deploy.AppProvider#setDeploymentManager(org.eclipse.jetty.deploy.DeploymentManager)
639      */
640     public void setDeploymentManager(DeploymentManager deploymentManager)
641     {
642         _deploymentManager = deploymentManager;
643     }
644     
645     
646     /* ------------------------------------------------------------ */
647     public ContextHandler createContextHandler(App app) throws Exception
648     {
649         if (app == null)
650             return null;
651         if (!(app instanceof OSGiApp))
652             throw new IllegalStateException(app+" is not a BundleApp");
653 
654         //Create a WebAppContext suitable to deploy in OSGi
655         ContextHandler ch = ((OSGiApp)app).createContextHandler();
656         return ch;
657     }
658 
659     
660     /* ------------------------------------------------------------ */
661     public static String getOriginId(Bundle contributor, String path)
662     {
663         return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() + (path.startsWith("/") ? path : "/" + path);
664     }
665     
666 }