View Javadoc

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