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