View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009-2010 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.net.URL;
16  import java.util.Dictionary;
17  
18  import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
19  import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
20  import org.eclipse.jetty.util.log.Log;
21  import org.eclipse.jetty.util.log.Logger;
22  import org.osgi.framework.Bundle;
23  import org.osgi.framework.BundleEvent;
24  import org.osgi.util.tracker.BundleTracker;
25  import org.osgi.util.tracker.BundleTrackerCustomizer;
26  
27  
28  /**
29   * Support bundles that declare the webapp directly through headers in their
30   * manifest.
31   * <p>
32   * Those headers will define a new WebApplication:
33   * <ul>
34   * <li>Web-ContextPath</li>
35   * <li>Jetty-WarFolderPath</li>
36   * </ul>
37   * </p>
38   * <p>
39   * Those headers will define a new app started via a jetty-context or a list of
40   * them. ',' column is the separator between the various context files.
41   * <ul>
42   * <li>Jetty-ContextFilePath</li>
43   * </ul>
44   * </p>
45   * And generate a jetty WebAppContext or another ContextHandler then registers
46   * it as service. Kind of simpler than declarative services and their xml files.
47   * Also avoid having the contributing bundle depend on jetty's package for
48   * WebApp.
49   * 
50   * @author hmalphettes
51   */
52  public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer {
53      private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class);
54  
55  	
56  
57  	/**
58  	 * A bundle is being added to the <code>BundleTracker</code>.
59  	 * 
60  	 * <p>
61  	 * This method is called before a bundle which matched the search parameters
62  	 * of the <code>BundleTracker</code> is added to the
63  	 * <code>BundleTracker</code>. This method should return the object to be
64  	 * tracked for the specified <code>Bundle</code>. The returned object is
65  	 * stored in the <code>BundleTracker</code> and is available from the
66  	 * {@link BundleTracker#getObject(Bundle) getObject} method.
67  	 * 
68  	 * @param bundle The <code>Bundle</code> being added to the
69  	 *        <code>BundleTracker</code>.
70  	 * @param event The bundle event which caused this customizer method to be
71  	 *        called or <code>null</code> if there is no bundle event associated
72  	 *        with the call to this method.
73  	 * @return The object to be tracked for the specified <code>Bundle</code>
74  	 *         object or <code>null</code> if the specified <code>Bundle</code>
75  	 *         object should not be tracked.
76  	 */
77  	public Object addingBundle(Bundle bundle, BundleEvent event)
78  	{
79  		if (bundle.getState() == Bundle.ACTIVE)
80  		{
81  			boolean isWebBundle = register(bundle);
82  			return isWebBundle ? bundle : null;
83  		}
84  		else if (bundle.getState() == Bundle.STOPPING)
85  		{
86  			unregister(bundle);
87  		}
88  		else
89  		{
90  			//we should not be called in that state as
91  			//we are registered only for ACTIVE and STOPPING
92  		}
93  		return null;
94  	}
95  
96  	/**
97  	 * A bundle tracked by the <code>BundleTracker</code> has been modified.
98  	 * 
99  	 * <p>
100 	 * This method is called when a bundle being tracked by the
101 	 * <code>BundleTracker</code> has had its state modified.
102 	 * 
103 	 * @param bundle The <code>Bundle</code> whose state has been modified.
104 	 * @param event The bundle event which caused this customizer method to be
105 	 *        called or <code>null</code> if there is no bundle event associated
106 	 *        with the call to this method.
107 	 * @param object The tracked object for the specified bundle.
108 	 */
109 	public void modifiedBundle(Bundle bundle, BundleEvent event,
110 			Object object)
111 	{
112 		//nothing the web-bundle was already track. something changed.
113 		//we only reload the webapps if the bundle is stopped and restarted.
114 //		System.err.println(bundle.getSymbolicName());
115 		if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE)
116 		{
117 			unregister(bundle);
118 		}
119 		if (bundle.getState() == Bundle.ACTIVE)
120 		{
121 			register(bundle);
122 		}
123 	}
124 
125 	/**
126 	 * A bundle tracked by the <code>BundleTracker</code> has been removed.
127 	 * 
128 	 * <p>
129 	 * This method is called after a bundle is no longer being tracked by the
130 	 * <code>BundleTracker</code>.
131 	 * 
132 	 * @param bundle The <code>Bundle</code> that has been removed.
133 	 * @param event The bundle event which caused this customizer method to be
134 	 *        called or <code>null</code> if there is no bundle event associated
135 	 *        with the call to this method.
136 	 * @param object The tracked object for the specified bundle.
137 	 */
138 	public void removedBundle(Bundle bundle, BundleEvent event,
139 			Object object)
140 	{
141 		unregister(bundle);
142 	}
143 	
144 	
145 	/**
146 	 * @param bundle
147 	 * @return true if this bundle in indeed a web-bundle.
148 	 */
149     private boolean register(Bundle bundle)
150     {
151         Dictionary<?, ?> dic = bundle.getHeaders();
152         String warFolderRelativePath = (String)dic.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH);
153         if (warFolderRelativePath != null)
154         {
155             String contextPath = (String)dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH);
156             if (contextPath == null || !contextPath.startsWith("/"))
157             {
158             	LOG.warn("The manifest header '" + OSGiWebappConstants.JETTY_WAR_FOLDER_PATH +
159                 		": " + warFolderRelativePath + "' in the bundle " + bundle.getSymbolicName() + 
160                 		" is not valid: there is no Web-ContextPath defined in the manifest.");
161             	return false;
162             }
163             // create the corresponding service and publish it in the context of
164             // the contributor bundle.
165             try
166             {
167                 JettyBootstrapActivator.registerWebapplication(bundle,warFolderRelativePath,contextPath);
168                 return true;
169             }
170             catch (Throwable e)
171             {
172             	LOG.warn("Starting the web-bundle " + bundle.getSymbolicName() + " threw an exception.", e);
173                 return true;//maybe it did not work maybe it did. safer to track this bundle.
174             }
175         }
176         else if (dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH) != null)
177         {
178             String contextFileRelativePath = (String)dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
179             if (contextFileRelativePath == null)
180             {
181                 // nothing to register here.
182                 return false;
183             }
184             // support for multiple webapps in the same bundle:
185             String[] pathes = contextFileRelativePath.split(",;");
186             for (String path : pathes)
187             {
188                 try
189                 {
190                     JettyBootstrapActivator.registerContext(bundle,path.trim());
191                 }
192                 catch (Throwable e)
193                 {
194                     // TODO Auto-generated catch block
195                     e.printStackTrace();
196                 }
197             }
198             return true;
199         }
200         else
201         {
202             // support for OSGi-RFC66; disclaimer, no access to the actual
203             // (draft) of the spec: just a couple of posts on the
204             // world-wide-web.
205             URL rfc66Webxml = bundle.getEntry("/WEB-INF/web.xml");
206             if (rfc66Webxml == null && dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) == null)
207             {
208                 return false;// no webapp in here
209             }
210             // this is risky: should we make sure that there is no classes and
211             // jars directly available
212             // at the root of the of the bundle: otherwise they are accessible
213             // through the browser. we should enforce that the whole classpath
214             // is
215             // pointing to files and folders inside WEB-INF. We should
216             // filter-out
217             // META-INF too
218             String rfc66ContextPath = getWebContextPath(bundle,dic);
219             try
220             {
221                 JettyBootstrapActivator.registerWebapplication(bundle,".",rfc66ContextPath);
222                 return true;
223             }
224             catch (Throwable e)
225             {
226                 // TODO Auto-generated catch block
227                 e.printStackTrace();
228                 return true;//maybe it did not work maybe it did. safer to track this bundle.
229             }
230         }
231     }
232 
233     private String getWebContextPath(Bundle bundle, Dictionary<?, ?> dic)
234     {
235         String rfc66ContextPath = (String)dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH);
236         if (rfc66ContextPath == null)
237         {
238             // extract from the last token of the bundle's location:
239             // (really ?
240             // could consider processing the symbolic name as an alternative
241             // the location will often reflect the version.
242             // maybe this is relevant when the file is a war)
243             String location = bundle.getLocation();
244             String toks[] = location.replace('\\','/').split("/");
245             rfc66ContextPath = toks[toks.length - 1];
246             // remove .jar, .war etc:
247             int lastDot = rfc66ContextPath.lastIndexOf('.');
248             if (lastDot != -1)
249             {
250                 rfc66ContextPath = rfc66ContextPath.substring(0,lastDot);
251             }
252         }
253         if (!rfc66ContextPath.startsWith("/"))
254         {
255             rfc66ContextPath = "/" + rfc66ContextPath;
256         }
257         return rfc66ContextPath;
258     }
259 
260     private void unregister(Bundle bundle)
261     {
262         // nothing to do: when the bundle is stopped, each one of its service
263         // reference is also stopped and that is what we use to stop the
264         // corresponding
265         // webapps registered in that bundle.
266     }
267 
268 
269 	
270 	
271 }