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.internal.serverfactory;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Dictionary;
27  import java.util.Enumeration;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.StringTokenizer;
32  
33  import org.eclipse.jetty.deploy.AppLifeCycle;
34  import org.eclipse.jetty.deploy.AppProvider;
35  import org.eclipse.jetty.deploy.DeploymentManager;
36  import org.eclipse.jetty.deploy.bindings.StandardStarter;
37  import org.eclipse.jetty.deploy.bindings.StandardStopper;
38  import org.eclipse.jetty.osgi.boot.BundleContextProvider;
39  import org.eclipse.jetty.osgi.boot.BundleWebAppProvider;
40  import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
41  import org.eclipse.jetty.osgi.boot.OSGiDeployer;
42  import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
43  import org.eclipse.jetty.osgi.boot.OSGiUndeployer;
44  import org.eclipse.jetty.osgi.boot.ServiceContextProvider;
45  import org.eclipse.jetty.osgi.boot.ServiceWebAppProvider;
46  import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader;
47  import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
48  import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper;
49  import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
50  import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
51  import org.eclipse.jetty.server.Server;
52  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
53  import org.eclipse.jetty.util.IO;
54  import org.eclipse.jetty.util.log.Log;
55  import org.eclipse.jetty.util.log.Logger;
56  import org.eclipse.jetty.util.resource.Resource;
57  import org.eclipse.jetty.xml.XmlConfiguration;
58  import org.xml.sax.SAXParseException;
59  
60  /**
61   * ServerInstanceWrapper
62   * 
63   *  Configures and starts a jetty Server instance. 
64   */
65  public class ServerInstanceWrapper
66  {
67  
68      /**
69       * The value of this property points to the parent director of the jetty.xml
70       * configuration file currently executed. Everything is passed as a URL to
71       * support the case where the bundle is zipped.
72       */
73      public static final String PROPERTY_THIS_JETTY_XML_FOLDER_URL = "this.jetty.xml.parent.folder.url";
74  
75      private static Logger LOG = Log.getLogger(ServerInstanceWrapper.class.getName());
76      
77   
78  
79      private final String _managedServerName;
80  
81      /**
82       * The managed jetty server
83       */
84      private Server _server;
85  
86      private ContextHandlerCollection _ctxtCollection;
87  
88      /**
89       * This is the class loader that should be the parent classloader of any
90       * webapp classloader. It is in fact the _libExtClassLoader with a trick to
91       * let the TldScanner find the jars where the tld files are.
92       */
93      private ClassLoader _commonParentClassLoaderForWebapps;
94  
95      private DeploymentManager _deploymentManager;
96      
97      
98      /* ------------------------------------------------------------ */
99      public ServerInstanceWrapper(String managedServerName)
100     {
101         _managedServerName = managedServerName;
102     }
103 
104     /* ------------------------------------------------------------ */ 
105     public String getManagedServerName()
106     {
107         return _managedServerName;
108     }
109     
110     
111     /* ------------------------------------------------------------ */
112     /**
113      * The classloader that should be the parent classloader for each webapp
114      * deployed on this server.
115      * 
116      * @return
117      */
118     public ClassLoader getParentClassLoaderForWebapps()
119     {
120         return _commonParentClassLoaderForWebapps;
121     }
122     
123     
124     /* ------------------------------------------------------------ */
125     /**
126      * @return The deployment manager registered on this server.
127      */
128     public DeploymentManager getDeploymentManager()
129     {
130         return _deploymentManager;
131     }
132     
133     
134     /* ------------------------------------------------------------ */
135     /**
136      * @return The app provider registered on this server.
137      */
138     public Server getServer()
139     {
140         return _server;
141     }
142 
143     /* ------------------------------------------------------------ */
144     /**
145      * @return The collection of context handlers
146      */
147     public ContextHandlerCollection getContextHandlerCollection()
148     {
149         return _ctxtCollection;
150     }
151     
152     
153     /* ------------------------------------------------------------ */
154     public void start(Server server, Dictionary props) throws Exception
155     {
156         _server = server;
157         ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
158         try
159         {
160             // passing this bundle's classloader as the context classloader
161             // makes sure there is access to all the jetty's bundles
162             ClassLoader libExtClassLoader = null;
163             String sharedURLs = (String) props.get(OSGiServerConstants.MANAGED_JETTY_SHARED_LIB_FOLDER_URLS);
164 
165             List<File> shared = sharedURLs != null ? extractFiles(sharedURLs) : null;
166             libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader(shared, null, server, JettyBootstrapActivator.class.getClassLoader());
167 
168             if (LOG.isDebugEnabled()) LOG.debug("LibExtClassLoader = "+libExtClassLoader);
169             
170             Thread.currentThread().setContextClassLoader(libExtClassLoader);
171 
172             configure(server, props);
173 
174             init();
175 
176             URL[] jarsWithTlds = getJarsWithTlds();
177             _commonParentClassLoaderForWebapps = jarsWithTlds == null ? libExtClassLoader : new TldLocatableURLClassloader(libExtClassLoader, jarsWithTlds);
178             
179             if (LOG.isDebugEnabled()) LOG.debug("common classloader = "+_commonParentClassLoaderForWebapps);
180 
181             server.start();
182         }
183         catch (Exception e)
184         {
185             if (server != null)
186             {
187                 try
188                 {
189                     server.stop();
190                 }
191                 catch (Exception x)
192                 {
193                     LOG.ignore(x);
194                 }
195             }
196             throw e;
197         }
198         finally
199         {
200             Thread.currentThread().setContextClassLoader(contextCl);
201         }
202     }
203     
204     /* ------------------------------------------------------------ */
205     public void stop()
206     {
207         try
208         {
209             if (_server.isRunning())
210             {
211                 _server.stop();
212             }
213         }
214         catch (Exception e)
215         {
216             LOG.warn(e);
217         }
218     }
219     
220     
221     /* ------------------------------------------------------------ */
222     /**
223      * TODO: right now only the jetty-jsp bundle is scanned for common taglibs.
224      * Should support a way to plug more bundles that contain taglibs.
225      * 
226      * The jasper TldScanner expects a URLClassloader to parse a jar for the
227      * /META-INF/*.tld it may contain. We place the bundles that we know contain
228      * such tag-libraries. Please note that it will work if and only if the
229      * bundle is a jar (!) Currently we just hardcode the bundle that contains
230      * the jstl implementation.
231      * 
232      * A workaround when the tld cannot be parsed with this method is to copy
233      * and paste it inside the WEB-INF of the webapplication where it is used.
234      * 
235      * Support only 2 types of packaging for the bundle: - the bundle is a jar
236      * (recommended for runtime.) - the bundle is a folder and contain jars in
237      * the root and/or in the lib folder (nice for PDE development situations)
238      * Unsupported: the bundle is a jar that embeds more jars.
239      * 
240      * @return
241      * @throws Exception
242      */
243     private URL[] getJarsWithTlds() throws Exception
244     {
245         
246         //Jars that are added onto the equivalent of the container classpath are:
247         // jstl jars: identified by the class WhenTag (and the boot-bundle manifest imports the jstl packages
248         // bundles identified by System property org.eclipse.jetty.osgi.tldbundles
249         // bundle symbolic name patterns defined in the DeploymentManager
250         //
251         // Any bundles mentioned in the Require-TldBundle manifest header of the webapp bundle MUST ALSO HAVE Import-Bundle
252         // in order to get them onto the classpath of the webapp.
253         
254         ArrayList<URL> res = new ArrayList<URL>();
255         for (WebappRegistrationCustomizer regCustomizer : WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS)
256         {
257             URL[] urls = regCustomizer.getJarsWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper());
258             for (URL url : urls)
259             {
260                 if (!res.contains(url)) res.add(url);
261             }
262         }
263         if (!res.isEmpty())
264             return res.toArray(new URL[res.size()]);
265         else
266             return null;
267     }
268     
269     
270     /* ------------------------------------------------------------ */
271     private void configure(Server server, Dictionary props) throws Exception
272     {
273         String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS);
274         List<URL> jettyConfigurations = jettyConfigurationUrls != null ? extractResources(jettyConfigurationUrls) : null;
275         if (jettyConfigurations == null || jettyConfigurations.isEmpty()) { return; }
276         Map<String, Object> id_map = new HashMap<String, Object>();
277         
278         //Put in a mapping for the id "Server" and the name of the server as the instance being configured
279         id_map.put("Server", server);
280         id_map.put((String)props.get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME), server);
281         
282         Map<String, String> properties = new HashMap<String, String>();
283         Enumeration<Object> en = props.keys();
284         while (en.hasMoreElements())
285         {
286             Object key = en.nextElement();
287             Object value = props.get(key);
288             String keyStr = String.valueOf(key);
289             String valStr = String.valueOf(value);
290             properties.put(keyStr, valStr);
291             server.setAttribute(keyStr, valStr);
292         }
293 
294         for (URL jettyConfiguration : jettyConfigurations)
295         {
296             InputStream is = null;
297             try
298             {
299                 // Execute a Jetty configuration file
300                 Resource r = Resource.newResource(jettyConfiguration);
301                 if (!r.exists())
302                 {
303                     LOG.warn("File does not exist "+r);
304                     continue;
305                 }
306                 is = r.getInputStream();
307                 XmlConfiguration config = new XmlConfiguration(is);
308                 config.getIdMap().putAll(id_map);
309 
310                 // #334062 compute the URL of the folder that contains the
311                 // jetty.xml conf file
312                 // and set it as a property so we can compute relative paths
313                 // from it.
314                 String urlPath = jettyConfiguration.toString();
315                 int lastSlash = urlPath.lastIndexOf('/');
316                 if (lastSlash > 4)
317                 {
318                     urlPath = urlPath.substring(0, lastSlash);
319                     Map<String, String> properties2 = new HashMap<String, String>(properties);
320                     properties2.put(PROPERTY_THIS_JETTY_XML_FOLDER_URL, urlPath);
321                     config.getProperties().putAll(properties2);
322                 }
323                 else
324                 {
325                     config.getProperties().putAll(properties);
326                 }
327                 config.configure();
328                 id_map = config.getIdMap();
329             }
330             catch (SAXParseException saxparse)
331             {
332                 LOG.warn("Unable to configure the jetty/etc file " + jettyConfiguration, saxparse);
333                 throw saxparse;
334             }
335             finally
336             {
337                 IO.close(is);
338             }
339         }
340 
341     }
342 
343     /**
344      * Must be called after the server is configured. 
345      * 
346      * It is assumed the server has already been configured with the ContextHandlerCollection structure.
347      * 
348      */
349     private void init()
350     {
351         // Get the context handler
352         _ctxtCollection = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class);
353 
354         if (_ctxtCollection == null) 
355             throw new IllegalStateException("ERROR: No ContextHandlerCollection configured in Server");
356         
357         List<String> providerClassNames = new ArrayList<String>();
358         
359         // get a deployerManager and some providers
360         List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class);
361         if (deployers != null && !deployers.isEmpty())
362         {
363             _deploymentManager = deployers.get(0);
364             
365             for (AppProvider provider : _deploymentManager.getAppProviders())
366             {
367                providerClassNames.add(provider.getClass().getName());
368             }
369         }
370         else
371         {
372             //add some kind of default
373             _deploymentManager = new DeploymentManager();
374             _deploymentManager.setContexts(_ctxtCollection);
375             _server.addBean(_deploymentManager);
376         }
377 
378         _deploymentManager.setUseStandardBindings(false);
379         List<AppLifeCycle.Binding> deploymentLifeCycleBindings = new ArrayList<AppLifeCycle.Binding>();
380         deploymentLifeCycleBindings.add(new OSGiDeployer());
381         deploymentLifeCycleBindings.add(new StandardStarter());
382         deploymentLifeCycleBindings.add(new StandardStopper());
383         deploymentLifeCycleBindings.add(new OSGiUndeployer());
384         _deploymentManager.setLifeCycleBindings(deploymentLifeCycleBindings);
385         
386         if (!providerClassNames.contains(BundleWebAppProvider.class.getName()))
387         {
388             // create it on the fly with reasonable default values.
389             try
390             {
391                 BundleWebAppProvider webAppProvider = new BundleWebAppProvider(this);
392                 _deploymentManager.addAppProvider(webAppProvider);
393             }
394             catch (Exception e)
395             {
396                 LOG.warn(e);
397             }
398         }
399 
400         if (!providerClassNames.contains(ServiceWebAppProvider.class.getName()))
401         {
402             // create it on the fly with reasonable default values.
403             try
404             {
405                 ServiceWebAppProvider webAppProvider = new ServiceWebAppProvider(this);
406                 _deploymentManager.addAppProvider(webAppProvider);
407             }
408             catch (Exception e)
409             {
410                 LOG.warn(e);
411             }
412         }
413 
414         if (!providerClassNames.contains(BundleContextProvider.class.getName()))
415         {
416             try
417             {
418                 BundleContextProvider contextProvider = new BundleContextProvider(this);
419                 _deploymentManager.addAppProvider(contextProvider);
420             }
421             catch (Exception e)
422             {
423                 LOG.warn(e);
424             }
425         }
426 
427         if (!providerClassNames.contains(ServiceContextProvider.class.getName()))
428         {
429             try
430             {
431                 ServiceContextProvider contextProvider = new ServiceContextProvider(this);
432                 _deploymentManager.addAppProvider(contextProvider);
433             }
434             catch (Exception e)
435             {
436                 LOG.warn(e);
437             }
438         }
439     }
440 
441     /**
442      * @return The default folder in which the context files of the osgi bundles
443      *         are located and watched. Or null when the system property
444      *         "jetty.osgi.contexts.home" is not defined. If the configuration
445      *         file defines the OSGiAppProvider's context. This will not be
446      *         taken into account.
447      */
448     File getDefaultOSGiContextsHome(File jettyHome)
449     {
450         String jettyContextsHome = System.getProperty("jetty.osgi.contexts.home");
451         if (jettyContextsHome != null)
452         {
453             File contextsHome = new File(jettyContextsHome);
454             if (!contextsHome.exists() || !contextsHome.isDirectory())
455             { 
456                 throw new IllegalArgumentException("the ${jetty.osgi.contexts.home} '" 
457                                                    + jettyContextsHome
458                                                    + " must exist and be a folder"); 
459             }
460             return contextsHome;
461         }
462         return new File(jettyHome, "/contexts");
463     }
464 
465 
466     /**
467      * @return the urls in this string.
468      */
469     private List<URL> extractResources(String propertyValue)
470     {
471         StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false);
472         List<URL> urls = new ArrayList<URL>();
473         while (tokenizer.hasMoreTokens())
474         {
475             String tok = tokenizer.nextToken();
476             try
477             {
478                 urls.add(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(new URL(tok)));
479             }
480             catch (Throwable mfe)
481             {
482                 LOG.warn(mfe);
483             }
484         }
485         return urls;
486     }
487 
488     /**
489      * Get the folders that might contain jars for the legacy J2EE shared
490      * libraries
491      */
492     private List<File> extractFiles(String propertyValue)
493     {
494         StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false);
495         List<File> files = new ArrayList<File>();
496         while (tokenizer.hasMoreTokens())
497         {
498             String tok = tokenizer.nextToken();
499             try
500             {
501                 URL url = new URL(tok);
502                 url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(url);
503                 if (url.getProtocol().equals("file"))
504                 {
505                     Resource res = Resource.newResource(url);
506                     File folder = res.getFile();
507                     if (folder != null)
508                     {
509                         files.add(folder);
510                     }
511                 }
512             }
513             catch (Throwable mfe)
514             {
515                 LOG.warn(mfe);
516             }
517         }
518         return files;
519     }
520 
521 }