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