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