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