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