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 }