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                 Resource r = Resource.newResource(jettyConfiguration);
275                 is = r.getInputStream();
276                 XmlConfiguration config = new XmlConfiguration(is);
277                 config.getIdMap().putAll(id_map);
278                 
279                 //#334062 compute the URL of the folder that contains the jetty.xml conf file
280                 //and set it as a property so we can compute relative paths from it.
281                 String urlPath = jettyConfiguration.toString();
282                 int lastSlash = urlPath.lastIndexOf('/');
283                 if (lastSlash > 4)
284                 {
285                     urlPath = urlPath.substring(0, lastSlash);
286                     Map<String,String> properties2 = new HashMap<String,String>(properties);
287                     properties2.put(PROPERTY_THIS_JETTY_XML_FOLDER_URL, urlPath);
288                     config.getProperties().putAll(properties2);
289                 }
290                 else
291                 {
292                     config.getProperties().putAll(properties);
293                 }
294                 config.configure();
295                 id_map=config.getIdMap();
296             }
297             catch (SAXParseException saxparse)
298             {
299                 __logger.warn("Unable to configure the jetty/etc file " + jettyConfiguration,saxparse);
300                 throw saxparse;
301             }
302             finally
303             {
304                 IO.close(is);
305             }
306         }
307 
308     }
309     
310     
311     /**
312      * Must be called after the server is configured.
313      * 
314      * Locate the actual instance of the ContextDeployer and WebAppDeployer that
315      * was created when configuring the server through jetty.xml. If there is no
316      * such thing it won't be possible to deploy webapps from a context and we
317      * throw IllegalStateExceptions.
318      */
319     private void init()
320     {
321         // Get the context handler
322         _ctxtHandler = (ContextHandlerCollection)_server.getChildHandlerByClass(ContextHandlerCollection.class);
323         
324         // get a deployerManager
325         List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class);
326         if (deployers != null && !deployers.isEmpty())
327         {
328             _deploymentManager = deployers.get(0);
329             
330             for (AppProvider provider : _deploymentManager.getAppProviders())
331             {
332                 if (provider instanceof OSGiAppProvider)
333                 {
334                     _provider=(OSGiAppProvider)provider;
335                     break;
336                 }
337             }
338             if (_provider == null)
339             {
340                 //create it on the fly with reasonable default values.
341                 try
342                 {
343                     _provider = new OSGiAppProvider();
344                     _provider.setMonitoredDirResource(
345                             Resource.newResource(getDefaultOSGiContextsHome(
346                                     new File(System.getProperty("jetty.home"))).toURI()));
347                 } catch (IOException e) {
348                     e.printStackTrace();
349                 }
350                 _deploymentManager.addAppProvider(_provider);
351             }
352         }
353 
354         if (_ctxtHandler == null || _provider==null)
355             throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured");
356         
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.
364      *         If the configuration file defines the OSGiAppProvider's context.
365      *         This will not be 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} '" + jettyContextsHome + " must exist and be a folder");
376             }
377             return contextsHome;
378         }
379         return new File(jettyHome, "/contexts");
380     }
381     
382     File getOSGiContextsHome()
383     {
384         return _provider.getContextXmlDirAsFile();
385     }
386     
387     /**
388      * @return the urls in this string.
389      */
390     private List<URL> extractResources(String propertyValue)
391     {
392         StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false);
393         List<URL> urls = new ArrayList<URL>();
394         while (tokenizer.hasMoreTokens())
395         {
396             String tok = tokenizer.nextToken();
397             try
398             {
399                 urls.add(((DefaultFileLocatorHelper) WebBundleDeployerHelper
400                         .BUNDLE_FILE_LOCATOR_HELPER).getLocalURL(new URL(tok)));
401             }
402             catch (Throwable mfe)
403             {
404                 
405             }
406         }
407         return urls;
408     }
409     
410     /**
411      * Get the folders that might contain jars for the legacy J2EE shared libraries
412      */
413     private List<File> extractFiles(String propertyValue)
414     {
415         StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false);
416         List<File> files = new ArrayList<File>();
417         while (tokenizer.hasMoreTokens())
418         {
419             String tok = tokenizer.nextToken();
420             try
421             {
422                 URL url = new URL(tok);
423                 url = ((DefaultFileLocatorHelper) WebBundleDeployerHelper
424                     .BUNDLE_FILE_LOCATOR_HELPER).getFileURL(url);
425                 if (url.getProtocol().equals("file"))
426                 {
427                     Resource res = Resource.newResource(url);
428                     File folder = res.getFile();
429                     if (folder != null)
430                     {
431                         files.add(folder);
432                     }
433                 }
434             }
435             catch (Throwable mfe)
436             {
437                 
438             }
439         }
440         return files;
441     }
442     
443 
444 }