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