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