View Javadoc

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