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.webapp;
14  
15  import java.io.File;
16  import java.io.IOException;
17  import java.util.HashMap;
18  import java.util.Map;
19  
20  import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
21  import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
22  import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
23  import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
24  import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry;
25  import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
26  import org.eclipse.jetty.server.handler.ContextHandler;
27  import org.eclipse.jetty.util.Scanner;
28  import org.eclipse.jetty.webapp.WebAppContext;
29  import org.osgi.framework.Bundle;
30  import org.osgi.framework.BundleContext;
31  import org.osgi.framework.FrameworkUtil;
32  import org.osgi.framework.ServiceEvent;
33  import org.osgi.framework.ServiceListener;
34  import org.osgi.framework.ServiceReference;
35  
36  /**
37   * When a {@link ContextHandler} service is activated we look into it and if the
38   * corresponding webapp is actually not configured then we go and register it.
39   * <p>
40   * The idea is to always go through this class when we deploy a new webapp on
41   * jetty.
42   * </p>
43   * <p>
44   * We are exposing each web-application as an OSGi service. This lets us update
45   * the webapps and stop/start them directly at the OSGi layer. It also give us
46   * many ways to declare those services: Declarative Services for example. <br/>
47   * It is a bit different from the way the HttpService works where we would have
48   * a WebappService and we woud register a webapp onto it. <br/>
49   * It does not go against RFC-66 nor does it prevent us from supporting the
50   * WebappContainer semantics.
51   * </p>
52   */
53  public class JettyContextHandlerServiceTracker implements ServiceListener
54  {
55  
56  	/** New style: ability to manage multiple jetty instances */
57  	private final IManagedJettyServerRegistry _registry;
58  
59      /** The context-handler to deactivate indexed by context handler */
60      private Map<ServiceReference, ContextHandler> _indexByServiceReference = new HashMap<ServiceReference, ContextHandler>();
61  
62      /**
63       * The index is the bundle-symbolic-name/path/to/context/file when there is such thing
64       */
65      private Map<String, ServiceReference> _indexByContextFile = new HashMap<String, ServiceReference>();
66  
67      /** in charge of detecting changes in the osgi contexts home folder. */
68      private Scanner _scanner;
69  
70      /**
71       * @param registry
72       */
73      public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception
74      {
75      	_registry = registry;
76      }
77  
78      public void stop() throws Exception
79      {
80          if (_scanner != null)
81          {
82              _scanner.stop();
83          }
84          // the class that created the server is also in charge of stopping it.
85          // nothing to stop in the WebappRegistrationHelper
86  
87      }
88      
89      /**
90       * @param contextHome Parent folder where the context files can override the context files
91       * defined in the web bundles: equivalent to the contexts folder in a traditional
92       * jetty installation.
93       * when null, just do nothing.
94       */
95      protected void setupContextHomeScanner(File contextHome) throws IOException
96      {
97          if (contextHome == null)
98          {
99          	return;
100         }
101         final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath();
102         _scanner = new Scanner();
103         _scanner.setRecursive(true);
104         _scanner.setReportExistingFilesOnStartup(false);
105         _scanner.addListener(new Scanner.DiscreteListener()
106         {
107             public void fileAdded(String filename) throws Exception
108             {
109                 // adding a file does not create a new app,
110                 // it just reloads it with the new custom file.
111                 // well, if the file does not define a context handler,
112                 // then in fact it does remove it.
113                 reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
114             }
115 
116             public void fileChanged(String filename) throws Exception
117             {
118                 reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
119             }
120 
121             public void fileRemoved(String filename) throws Exception
122             {
123                 // removing a file does not remove the app:
124                 // it just goes back to the default embedded in the bundle.
125                 // well, if there was no default then it does remove it.
126                 reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
127             }
128         });
129 
130     }
131 
132     /**
133      * Receives notification that a service has had a lifecycle change.
134      * 
135      * @param ev
136      *            The <code>ServiceEvent</code> object.
137      */
138     public void serviceChanged(ServiceEvent ev)
139     {
140         ServiceReference sr = ev.getServiceReference();
141         switch (ev.getType())
142         {
143             case ServiceEvent.MODIFIED:
144             case ServiceEvent.UNREGISTERING:
145             {
146                 ContextHandler ctxtHandler = unregisterInIndex(ev.getServiceReference());
147                 if (ctxtHandler != null && !ctxtHandler.isStopped())
148                 {
149                     try
150                     {
151                     	getWebBundleDeployerHelp(sr).unregister(ctxtHandler);
152                     }
153                     catch (Exception e)
154                     {
155                         // TODO Auto-generated catch block
156                         e.printStackTrace();
157                     }
158                 }
159             }
160             if (ev.getType() == ServiceEvent.UNREGISTERING)
161             {
162                 break;
163             }
164             else
165             {
166                 // modified, meaning: we reload it. now that we stopped it;
167                 // we can register it.
168             }
169             case ServiceEvent.REGISTERED:
170             {
171                 Bundle contributor = sr.getBundle();
172                 BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
173                 ContextHandler contextHandler = (ContextHandler)context.getService(sr);
174                 if (contextHandler.getServer() != null)
175                 {
176                     // is configured elsewhere.
177                     return;
178                 }
179                 String contextFilePath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
180                 if (contextHandler instanceof WebAppContext && contextFilePath == null)
181                 //it could be a web-application that will in fact be configured via a context file.
182                 //that case is identified by the fact that the contextFilePath is not null
183                 //in that case we must use the register context methods.
184                 {
185                     WebAppContext webapp = (WebAppContext)contextHandler;
186                     String contextPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH);
187                     if (contextPath == null)
188                     {
189                         contextPath = webapp.getContextPath();
190                     }
191                     String webXmlPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH);
192                     if (webXmlPath == null)
193                     {
194                         webXmlPath = webapp.getDescriptor();
195                     }
196                     String defaultWebXmlPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH);
197                     if (defaultWebXmlPath == null)
198                     {
199                         String jettyHome = System.getProperty(DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME);
200                         if (jettyHome != null)
201                         {
202                             File etc = new File(jettyHome, "etc");
203                             if (etc.exists() && etc.isDirectory())
204                             {
205                                 File webDefault = new File (etc, "webdefault.xml");
206                                 if (webDefault.exists())
207                                     defaultWebXmlPath = webDefault.getAbsolutePath();
208                                 else
209                                     defaultWebXmlPath = webapp.getDefaultsDescriptor();
210                             }
211                             else
212                                 defaultWebXmlPath = webapp.getDefaultsDescriptor();
213                         }
214                     }
215                     String war = (String)sr.getProperty("war");
216                     try
217                     {
218                     	IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr);
219                     	if (deployerHelper == null)
220                     	{
221                     		
222                     	}
223                     	else
224                     	{
225                     		WebAppContext handler = deployerHelper
226                         		.registerWebapplication(contributor,war,contextPath,
227                         		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),
228                         		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),
229                         		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE),
230                                 webXmlPath,defaultWebXmlPath,webapp);
231                             if (handler != null)
232                             {
233                                 registerInIndex(handler,sr);
234                             }
235                     	}
236                     }
237                     catch (Throwable e)
238                     {
239                         e.printStackTrace();
240                     }
241                 }
242                 else
243                 {
244                     // consider this just an empty skeleton:
245                     if (contextFilePath == null)
246                     {
247                         throw new IllegalArgumentException("the property contextFilePath is required");
248                     }
249                     try
250                     {
251                     	IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr);
252                     	if (deployerHelper == null)
253                     	{
254                     		//more warnings?
255                     	}
256                     	else
257                     	{
258                     		if (Boolean.TRUE.toString().equals(sr.getProperty(
259                     				IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE)))
260                     		{
261                     			contextHandler = null;
262                     		}
263 	                        ContextHandler handler = deployerHelper.registerContext(contributor,contextFilePath,
264 	                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),
265 	                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),
266 	                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE),
267 	                                contextHandler);
268 	                        if (handler != null)
269 	                        {
270 	                            registerInIndex(handler,sr);
271 	                        }
272                     	}
273                     }
274                     catch (Throwable e)
275                     {
276                         // TODO Auto-generated catch block
277                         e.printStackTrace();
278                     }
279                 }
280             }
281                 break;
282         }
283     }
284 
285     private void registerInIndex(ContextHandler handler, ServiceReference sr)
286     {
287         _indexByServiceReference.put(sr,handler);
288         String key = getSymbolicNameAndContextFileKey(sr);
289         if (key != null)
290         {
291             _indexByContextFile.put(key,sr);
292         }
293     }
294 
295     /**
296      * Returns the ContextHandler to stop.
297      * 
298      * @param reg
299      * @return the ContextHandler to stop.
300      */
301     private ContextHandler unregisterInIndex(ServiceReference sr)
302     {
303         ContextHandler handler = _indexByServiceReference.remove(sr);
304         String key = getSymbolicNameAndContextFileKey(sr);
305         if (key != null)
306         {
307             _indexByContextFile.remove(key);
308         }
309         if (handler == null)
310         {
311             // a warning?
312             return null;
313         }
314         return handler;
315     }
316 
317     /**
318      * @param sr
319      * @return The key for a context file within the osgi contexts home folder.
320      */
321     private String getSymbolicNameAndContextFileKey(ServiceReference sr)
322     {
323         String contextFilePath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
324         if (contextFilePath != null)
325         {
326             return sr.getBundle().getSymbolicName() + "/" + contextFilePath;
327         }
328         return null;
329     }
330 
331     /**
332      * Called by the scanner when one of the context files is changed.
333      * 
334      * @param contextFileFully
335      */
336     public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath)
337     {
338         String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath);
339         if (key == null)
340         {
341             return;
342         }
343         ServiceReference sr = _indexByContextFile.get(key);
344         if (sr == null)
345         {
346             // nothing to do?
347             return;
348         }
349         serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED,sr));
350     }
351 
352     /**
353      * @param canFilename
354      * @return
355      */
356     private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath)
357     {
358         if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath))
359         {
360             // why are we here: this does not look like a child of the osgi
361             // contexts home.
362             // warning?
363             return null;
364         }
365         return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\','/');
366     }
367     
368     /**
369      * @return The server on which this webapp is meant to be deployed
370      */
371     private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName)
372     {
373     	if (_registry == null)
374     	{
375     		return null;
376     	}
377     	if (managedServerName == null)
378     	{
379     		managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
380     	}
381     	ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName);
382     	//System.err.println("Returning " + managedServerName + "  = " + wrapper);
383     	return wrapper;
384     }
385     
386     private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr)
387     {
388     	if (_registry == null)
389     	{
390     		return null;
391     	}
392     	String managedServerName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
393     	ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName);
394     	return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null;
395     }
396     
397 }