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