View Javadoc

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