View Javadoc

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