1 // ========================================================================
2 // Copyright (c) 2009-2010 Mortbay, 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 // Contributors:
13 // Greg Wilkins - initial API and implementation
14 // ========================================================================
15 package org.eclipse.jetty.osgi.boot;
16
17 import java.io.File;
18 import java.io.FilenameFilter;
19 import java.io.IOException;
20 import java.util.Map.Entry;
21
22 import org.eclipse.jetty.deploy.App;
23 import org.eclipse.jetty.deploy.AppProvider;
24 import org.eclipse.jetty.deploy.DeploymentManager;
25 import org.eclipse.jetty.deploy.providers.ContextProvider;
26 import org.eclipse.jetty.deploy.providers.ScanningAppProvider;
27 import org.eclipse.jetty.server.handler.ContextHandler;
28 import org.eclipse.jetty.util.resource.Resource;
29 import org.eclipse.jetty.webapp.WebAppContext;
30 import org.osgi.framework.Bundle;
31
32 /**
33 * AppProvider for OSGi. Supports the configuration of ContextHandlers and
34 * WebApps. Extends the AbstractAppProvider to support the scanning of context
35 * files located outside of the bundles.
36 * <p>
37 * This provider must not be called outside of jetty.boot: it should always be
38 * called via the OSGi service listener.
39 * </p>
40 * <p>
41 * This provider supports the same set of parameters than the WebAppProvider as
42 * it supports the deployment of WebAppContexts. Except for the scanning of the
43 * webapps directory.
44 * </p>
45 */
46 public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
47 {
48
49 private boolean _extractWars = true;
50 private boolean _parentLoaderPriority = false;
51 private String _defaultsDescriptor;
52 private String _tldBundles;
53
54 /**
55 * When a context file corresponds to a deployed bundle and is changed we
56 * reload the corresponding bundle.
57 */
58 private static class Filter implements FilenameFilter
59 {
60 OSGiAppProvider _enclosedInstance;
61
62 public boolean accept(File dir, String name)
63 {
64 if (!new File(dir,name).isDirectory())
65 {
66 String contextName = getDeployedAppName(name);
67 if (contextName != null)
68 {
69 App app = _enclosedInstance.getDeployedApps().get(contextName);
70 return app != null;
71 }
72 }
73 return false;
74 }
75 }
76
77 /**
78 * @param contextFileName
79 * for example myContext.xml
80 * @return The context, for example: myContext; null if this was not a
81 * suitable contextFileName.
82 */
83 private static String getDeployedAppName(String contextFileName)
84 {
85 String lowername = contextFileName.toLowerCase();
86 if (lowername.endsWith(".xml"))
87 {
88 String contextName = contextFileName.substring(0,lowername.length() - ".xml".length());
89 return contextName;
90 }
91 return null;
92 }
93
94 /**
95 * Reading the display name of a webapp is really not sufficient for indexing the various
96 * deployed ContextHandlers.
97 *
98 * @param context
99 * @return
100 */
101 private String getContextHandlerAppName(ContextHandler context) {
102 String appName = context.getDisplayName();
103 if (appName == null || appName.length() == 0 || getDeployedApps().containsKey(appName)) {
104 if (context instanceof WebAppContext)
105 {
106 appName = ((WebAppContext)context).getContextPath();
107 if (getDeployedApps().containsKey(appName)) {
108 appName = "noDisplayName"+context.getClass().getSimpleName()+context.hashCode();
109 }
110 } else {
111 appName = "noDisplayName"+context.getClass().getSimpleName()+context.hashCode();
112 }
113 }
114 return appName;
115 }
116
117 /**
118 * Default OSGiAppProvider consutructed when none are defined in the
119 * jetty.xml configuration.
120 */
121 public OSGiAppProvider()
122 {
123 super(new Filter());
124 ((Filter)super._filenameFilter)._enclosedInstance = this;
125 }
126
127 /**
128 * Default OSGiAppProvider consutructed when none are defined in the
129 * jetty.xml configuration.
130 *
131 * @param contextsDir
132 */
133 public OSGiAppProvider(File contextsDir) throws IOException
134 {
135 this();
136 setMonitoredDir(Resource.newResource(contextsDir.toURI()));
137 }
138
139 /**
140 * Returns the ContextHandler that was created by WebappRegistractionHelper
141 *
142 * @see AppProvider
143 */
144 public ContextHandler createContextHandler(App app) throws Exception
145 {
146 // return pre-created Context
147 if (app.getContextHandler() != null)
148 {
149 return app.getContextHandler();
150 }
151 // for some reason it was not defined when the App was constructed.
152 // we don't support this situation at this point.
153 // once the WebAppRegistrationHelper is refactored, the code
154 // that creates the ContextHandler will actually be here.
155 throw new IllegalStateException("The App must be passed the " + "instance of the ContextHandler when it is construsted");
156 }
157
158 /**
159 * @see AppProvider
160 */
161 public void setDeploymentManager(DeploymentManager deploymentManager)
162 {
163 // _manager=deploymentManager;
164 super.setDeploymentManager(deploymentManager);
165 }
166
167 private static String getOriginId(Bundle contributor, String pathInBundle)
168 {
169 return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() +
170 (pathInBundle.startsWith("/") ? pathInBundle : "/" + pathInBundle);
171 }
172
173 /**
174 * @param context
175 * @throws Exception
176 */
177 public void addContext(Bundle contributor, String pathInBundle, ContextHandler context) throws Exception
178 {
179 addContext(getOriginId(contributor, pathInBundle), context);
180 }
181 /**
182 * @param context
183 * @throws Exception
184 */
185 public void addContext(String originId, ContextHandler context) throws Exception
186 {
187 // TODO apply configuration specific to this provider
188 if (context instanceof WebAppContext)
189 {
190 ((WebAppContext)context).setExtractWAR(isExtract());
191 }
192
193 // wrap context as an App
194 App app = new App(getDeploymentManager(),this,originId,context);
195 String appName = getContextHandlerAppName(context);
196 getDeployedApps().put(appName,app);
197 getDeploymentManager().addApp(app);
198 }
199
200
201
202 /**
203 * Called by the scanner of the context files directory. If we find the
204 * corresponding deployed App we reload it by returning the App. Otherwise
205 * we return null and nothing happens: presumably the corresponding OSGi
206 * webapp is not ready yet.
207 *
208 * @return the corresponding already deployed App so that it will be
209 * reloaded. Otherwise returns null.
210 */
211 @Override
212 protected App createApp(String filename)
213 {
214 // find the corresponding bundle and ContextHandler or WebAppContext
215 // and reload the corresponding App.
216 // see the 2 pass of the refactoring of the WebAppRegistrationHelper.
217 String name = getDeployedAppName(filename);
218 if (name != null)
219 {
220 return getDeployedApps().get(name);
221 }
222 return null;
223 }
224
225 public void removeContext(ContextHandler context) throws Exception
226 {
227 String appName = getContextHandlerAppName(context);
228 App app = getDeployedApps().remove(context.getDisplayName());
229 if (app == null) {
230 //try harder to undeploy this context handler.
231 //see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=330098
232 appName = null;
233 for (Entry<String,App> deployedApp : getDeployedApps().entrySet()) {
234 if (deployedApp.getValue().getContextHandler() == context) {
235 app = deployedApp.getValue();
236 appName = deployedApp.getKey();
237 break;
238 }
239 }
240 if (appName != null) {
241 getDeployedApps().remove(appName);
242 }
243 }
244 if (app != null)
245 {
246 getDeploymentManager().removeApp(app);
247 }
248 }
249
250 // //copied from WebAppProvider as the parameters are identical.
251 // //only removed the parameer related to extractWars.
252 /* ------------------------------------------------------------ */
253 /**
254 * Get the parentLoaderPriority.
255 *
256 * @return the parentLoaderPriority
257 */
258 public boolean isParentLoaderPriority()
259 {
260 return _parentLoaderPriority;
261 }
262
263 /* ------------------------------------------------------------ */
264 /**
265 * Set the parentLoaderPriority.
266 *
267 * @param parentLoaderPriority
268 * the parentLoaderPriority to set
269 */
270 public void setParentLoaderPriority(boolean parentLoaderPriority)
271 {
272 _parentLoaderPriority = parentLoaderPriority;
273 }
274
275 /* ------------------------------------------------------------ */
276 /**
277 * Get the defaultsDescriptor.
278 *
279 * @return the defaultsDescriptor
280 */
281 public String getDefaultsDescriptor()
282 {
283 return _defaultsDescriptor;
284 }
285
286 /* ------------------------------------------------------------ */
287 /**
288 * Set the defaultsDescriptor.
289 *
290 * @param defaultsDescriptor
291 * the defaultsDescriptor to set
292 */
293 public void setDefaultsDescriptor(String defaultsDescriptor)
294 {
295 _defaultsDescriptor = defaultsDescriptor;
296 }
297
298 /**
299 * The context xml directory. In fact it is the directory watched by the
300 * scanner.
301 */
302 public File getContextXmlDirAsFile()
303 {
304 try
305 {
306 Resource monitoredDir = getMonitoredDir();
307 if (monitoredDir == null)
308 return null;
309 return monitoredDir.getFile();
310 }
311 catch (IOException e)
312 {
313 e.printStackTrace();
314 return null;
315 }
316 }
317
318 /* ------------------------------------------------------------ */
319 /**
320 * The context xml directory. In fact it is the directory watched by the
321 * scanner.
322 */
323 public String getContextXmlDir()
324 {
325 try
326 {
327 Resource monitoredDir = getMonitoredDir();
328 if (monitoredDir == null)
329 return null;
330 return monitoredDir.getFile().toURI().toString();
331 }
332 catch (IOException e)
333 {
334 e.printStackTrace();
335 return null;
336 }
337 }
338
339 public boolean isExtract()
340 {
341 return _extractWars;
342 }
343
344 public void setExtract(boolean extract)
345 {
346 _extractWars=extract;
347 }
348
349
350 /* ------------------------------------------------------------ */
351 /**
352 * Set the directory in which to look for context XML files.
353 * <p>
354 * If a webapp call "foo/" or "foo.war" is discovered in the monitored
355 * directory, then the ContextXmlDir is examined to see if a foo.xml file
356 * exists. If it does, then this deployer will not deploy the webapp and the
357 * ContextProvider should be used to act on the foo.xml file.
358 *
359 * @see ContextProvider
360 * @param contextsDir
361 */
362 public void setContextXmlDir(String contextsDir)
363 {
364 setMonitoredDir(contextsDir);
365 }
366
367 /**
368 * @param tldBundles Comma separated list of bundles that contain tld jars
369 * that should be setup on the jetty instances created here.
370 */
371 public void setTldBundles(String tldBundles)
372 {
373 _tldBundles = tldBundles;
374 }
375
376 /**
377 * @return The list of bundles that contain tld jars that should be setup
378 * on the jetty instances created here.
379 */
380 public String getTldBundles()
381 {
382 return _tldBundles;
383 }
384
385 }