1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.deploy.providers;
20
21 import java.io.File;
22 import java.io.FilenameFilter;
23 import java.util.Locale;
24
25 import org.eclipse.jetty.deploy.App;
26 import org.eclipse.jetty.deploy.ConfigurationManager;
27 import org.eclipse.jetty.deploy.util.FileID;
28 import org.eclipse.jetty.server.Server;
29 import org.eclipse.jetty.server.handler.ContextHandler;
30 import org.eclipse.jetty.util.URIUtil;
31 import org.eclipse.jetty.util.annotation.ManagedAttribute;
32 import org.eclipse.jetty.util.annotation.ManagedObject;
33 import org.eclipse.jetty.util.resource.Resource;
34 import org.eclipse.jetty.webapp.WebAppContext;
35 import org.eclipse.jetty.xml.XmlConfiguration;
36
37 /**
38 * The webapps directory scanning provider.
39 * <p>
40 * This provider scans one or more directories (typically "webapps") for contexts to
41 * deploy, which may be:<ul>
42 * <li>A standard WAR file (must end in ".war")</li>
43 * <li>A directory containing an expanded WAR file</li>
44 * <li>A directory containing static content</li>
45 * <li>An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance</li>
46 * </ul>
47 * <p>
48 * To avoid double deployments and allow flexibility of the content of the scanned directories, the provider
49 * implements some heuristics to ignore some files found in the scans: <ul>
50 * <li>Hidden files (starting with ".") are ignored</li>
51 * <li>Directories with names ending in ".d" are ignored</li>
52 * <li>If a directory and a WAR file exist ( eg foo/ and foo.war) then the directory is assumed to be
53 * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)</li>
54 * <li>If a directory and a matching XML file exist ( eg foo/ and foo.xml) then the directory is assumed to be
55 * an unpacked WAR and only the XML is deployed (which may used the directory in it's configuration)</li>
56 * <li>If a WAR file and a matching XML exist (eg foo.war and foo.xml) then the WAR is assumed to
57 * be configured by the XML and only the XML is deployed.
58 * </ul>
59 * <p>For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and
60 * properties for the webapp file as "jetty.webapp" and directory as "jetty.webapps".
61 */
62 @ManagedObject("Provider for start-up deployement of webapps based on presence in directory")
63 public class WebAppProvider extends ScanningAppProvider
64 {
65 private boolean _extractWars = false;
66 private boolean _parentLoaderPriority = false;
67 private ConfigurationManager _configurationManager;
68 private String _defaultsDescriptor;
69 private File _tempDirectory;
70 private String[] _configurationClasses;
71
72 public class Filter implements FilenameFilter
73 {
74 @Override
75 public boolean accept(File dir, String name)
76 {
77 if (!dir.exists())
78 {
79 return false;
80 }
81 String lowername = name.toLowerCase(Locale.ENGLISH);
82
83 File file = new File(dir,name);
84
85 // ignore hidden files
86 if (lowername.startsWith("."))
87 return false;
88
89 // Ignore some directories
90 if (file.isDirectory())
91 {
92 // is it a nominated config directory
93 if (lowername.endsWith(".d"))
94 return false;
95
96 // is it an unpacked directory for an existing war file?
97 if (exists(name+".war")||exists(name+".WAR"))
98 return false;
99
100 // is it a directory for an existing xml file?
101 if (exists(name+".xml")||exists(name+".XML"))
102 return false;
103
104 //is it a sccs dir?
105 if ("cvs".equals(lowername) || "cvsroot".equals(lowername))
106 return false;
107
108 // OK to deploy it then
109 return true;
110 }
111
112 // else is it a war file
113 if (lowername.endsWith(".war"))
114 {
115 //defer deployment decision to fileChanged()
116 return true;
117 }
118
119 // else is it a context XML file
120 if (lowername.endsWith(".xml"))
121 return true;
122
123 return false;
124 }
125 }
126
127 /* ------------------------------------------------------------ */
128 public WebAppProvider()
129 {
130 super();
131 setFilenameFilter(new Filter());
132 setScanInterval(0);
133 }
134
135 /* ------------------------------------------------------------ */
136 /** Get the extractWars.
137 * @return the extractWars
138 */
139 @ManagedAttribute("extract war files")
140 public boolean isExtractWars()
141 {
142 return _extractWars;
143 }
144
145 /* ------------------------------------------------------------ */
146 /** Set the extractWars.
147 * @param extractWars the extractWars to set
148 */
149 public void setExtractWars(boolean extractWars)
150 {
151 _extractWars = extractWars;
152 }
153
154 /* ------------------------------------------------------------ */
155 /** Get the parentLoaderPriority.
156 * @return the parentLoaderPriority
157 */
158 @ManagedAttribute("parent classloader has priority")
159 public boolean isParentLoaderPriority()
160 {
161 return _parentLoaderPriority;
162 }
163
164 /* ------------------------------------------------------------ */
165 /** Set the parentLoaderPriority.
166 * @param parentLoaderPriority the parentLoaderPriority to set
167 */
168 public void setParentLoaderPriority(boolean parentLoaderPriority)
169 {
170 _parentLoaderPriority = parentLoaderPriority;
171 }
172
173 /* ------------------------------------------------------------ */
174 /** Get the defaultsDescriptor.
175 * @return the defaultsDescriptor
176 */
177 @ManagedAttribute("default descriptor for webapps")
178 public String getDefaultsDescriptor()
179 {
180 return _defaultsDescriptor;
181 }
182
183 /* ------------------------------------------------------------ */
184 /** Set the defaultsDescriptor.
185 * @param defaultsDescriptor the defaultsDescriptor to set
186 */
187 public void setDefaultsDescriptor(String defaultsDescriptor)
188 {
189 _defaultsDescriptor = defaultsDescriptor;
190 }
191
192 /* ------------------------------------------------------------ */
193 public ConfigurationManager getConfigurationManager()
194 {
195 return _configurationManager;
196 }
197
198 /* ------------------------------------------------------------ */
199 /** Set the configurationManager.
200 * @param configurationManager the configurationManager to set
201 */
202 public void setConfigurationManager(ConfigurationManager configurationManager)
203 {
204 _configurationManager = configurationManager;
205 }
206
207 /* ------------------------------------------------------------ */
208 /**
209 * @param configurations The configuration class names.
210 */
211 public void setConfigurationClasses(String[] configurations)
212 {
213 _configurationClasses = configurations==null?null:(String[])configurations.clone();
214 }
215
216 /* ------------------------------------------------------------ */
217 @ManagedAttribute("configuration classes for webapps to be processed through")
218 public String[] getConfigurationClasses()
219 {
220 return _configurationClasses;
221 }
222
223 /**
224 * Set the Work directory where unpacked WAR files are managed from.
225 * <p>
226 * Default is the same as the <code>java.io.tmpdir</code> System Property.
227 *
228 * @param directory the new work directory
229 */
230 public void setTempDir(File directory)
231 {
232 _tempDirectory = directory;
233 }
234
235 /* ------------------------------------------------------------ */
236 /**
237 * Get the user supplied Work Directory.
238 *
239 * @return the user supplied work directory (null if user has not set Temp Directory yet)
240 */
241 @ManagedAttribute("temp directory for use, null if no user set temp directory")
242 public File getTempDir()
243 {
244 return _tempDirectory;
245 }
246
247 /* ------------------------------------------------------------ */
248 @Override
249 public ContextHandler createContextHandler(final App app) throws Exception
250 {
251 Resource resource = Resource.newResource(app.getOriginId());
252 File file = resource.getFile();
253 if (!resource.exists())
254 throw new IllegalStateException("App resouce does not exist "+resource);
255
256 String context = file.getName();
257
258 if (resource.exists() && FileID.isXmlFile(file))
259 {
260 XmlConfiguration xmlc = new XmlConfiguration(resource.getURI().toURL())
261 {
262 @Override
263 public void initializeDefaults(Object context)
264 {
265 super.initializeDefaults(context);
266
267 if (context instanceof WebAppContext)
268 {
269 WebAppContext webapp = (WebAppContext)context;
270 webapp.setParentLoaderPriority(_parentLoaderPriority);
271 if (_defaultsDescriptor != null)
272 webapp.setDefaultsDescriptor(_defaultsDescriptor);
273 }
274 }
275 };
276
277 xmlc.getIdMap().put("Server", getDeploymentManager().getServer());
278 xmlc.getProperties().put("jetty.home",System.getProperty("jetty.home","."));
279 xmlc.getProperties().put("jetty.base",System.getProperty("jetty.base","."));
280 xmlc.getProperties().put("jetty.webapp",file.getCanonicalPath());
281 xmlc.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
282
283 if (getConfigurationManager() != null)
284 xmlc.getProperties().putAll(getConfigurationManager().getProperties());
285 return (ContextHandler)xmlc.configure();
286 }
287 else if (file.isDirectory())
288 {
289 // must be a directory
290 }
291 else if (FileID.isWebArchiveFile(file))
292 {
293 // Context Path is the same as the archive.
294 context = context.substring(0,context.length() - 4);
295 }
296 else
297 {
298 throw new IllegalStateException("unable to create ContextHandler for "+app);
299 }
300
301 // Ensure "/" is Not Trailing in context paths.
302 if (context.endsWith("/") && context.length() > 0)
303 {
304 context = context.substring(0,context.length() - 1);
305 }
306
307 // Start building the webapplication
308 WebAppContext webAppContext = new WebAppContext();
309 webAppContext.setDisplayName(context);
310
311 // special case of archive (or dir) named "root" is / context
312 if (context.equalsIgnoreCase("root"))
313 {
314 context = URIUtil.SLASH;
315 }
316 else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-"))
317 {
318 int dash=context.toLowerCase(Locale.ENGLISH).indexOf('-');
319 String virtual = context.substring(dash+1);
320 webAppContext.setVirtualHosts(new String[]{virtual});
321 context = URIUtil.SLASH;
322 }
323
324 // Ensure "/" is Prepended to all context paths.
325 if (context.charAt(0) != '/')
326 {
327 context = "/" + context;
328 }
329
330
331 webAppContext.setContextPath(context);
332 webAppContext.setWar(file.getAbsolutePath());
333 if (_defaultsDescriptor != null)
334 {
335 webAppContext.setDefaultsDescriptor(_defaultsDescriptor);
336 }
337 webAppContext.setExtractWAR(_extractWars);
338 webAppContext.setParentLoaderPriority(_parentLoaderPriority);
339 if (_configurationClasses != null)
340 {
341 webAppContext.setConfigurationClasses(_configurationClasses);
342 }
343
344 if (_tempDirectory != null)
345 {
346 /* Since the Temp Dir is really a context base temp directory,
347 * Lets set the Temp Directory in a way similar to how WebInfConfiguration does it,
348 * instead of setting the
349 * WebAppContext.setTempDirectory(File).
350 * If we used .setTempDirectory(File) all webapps will wind up in the
351 * same temp / work directory, overwriting each others work.
352 */
353 webAppContext.setAttribute(WebAppContext.BASETEMPDIR, _tempDirectory);
354 }
355 return webAppContext;
356 }
357
358
359 /* ------------------------------------------------------------ */
360 @Override
361 protected void fileChanged(String filename) throws Exception
362 {
363 File file = new File(filename);
364 if (!file.exists())
365 return;
366
367 File parent = file.getParentFile();
368
369 //is the file that changed a directory?
370 if (file.isDirectory())
371 {
372 //is there a .xml file of the same name?
373 if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
374 return; //ignore it
375
376 //is there .war file of the same name?
377 if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
378 return; //ignore it
379
380 super.fileChanged(filename);
381 return;
382 }
383
384
385 String lowname = file.getName().toLowerCase(Locale.ENGLISH);
386 //is the file that changed a .war file?
387 if (lowname.endsWith(".war"))
388 {
389 String name = file.getName();
390 String base=name.substring(0,name.length()-4);
391 String xmlname = base+".xml";
392 if (exists(xmlname))
393 {
394 //if a .xml file exists for it, then redeploy that instead
395 File xml = new File (parent, xmlname);
396 super.fileChanged(xml.getCanonicalPath());
397 return;
398 }
399
400 xmlname = base+".XML";
401 if (exists(xmlname))
402 {
403 //if a .XML file exists for it, then redeploy that instead
404 File xml = new File(parent, xmlname);
405 super.fileChanged(xml.getCanonicalPath());
406 return;
407 }
408
409 //redeploy the changed war
410 super.fileChanged(filename);
411 return;
412 }
413
414 //is the file that changed a .xml file?
415 if (lowname.endsWith(".xml"))
416 super.fileChanged(filename);
417 }
418
419 /* ------------------------------------------------------------ */
420 @Override
421 protected void fileAdded(String filename) throws Exception
422 {
423 File file = new File(filename);
424 if (!file.exists())
425 return;
426
427 //is the file that was added a directory?
428 if (file.isDirectory())
429 {
430 //is there a .xml file of the same name?
431 if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
432 return; //assume we will get added events for the xml file
433
434 //is there .war file of the same name?
435 if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
436 return; //assume we will get added events for the war file
437
438 super.fileAdded(filename);
439 return;
440 }
441
442
443 //is the file that was added a .war file?
444 String lowname = file.getName().toLowerCase(Locale.ENGLISH);
445 if (lowname.endsWith(".war"))
446 {
447 String name = file.getName();
448 String base=name.substring(0,name.length()-4);
449 //is there a .xml file of the same name?
450 if (exists(base+".xml")||exists(base+".XML"))
451 return; //ignore it as we should get addition of the xml file
452
453 super.fileAdded(filename);
454 return;
455 }
456
457 //is the file that was added an .xml file?
458 if (lowname.endsWith(".xml"))
459 super.fileAdded(filename);
460 }
461
462
463 /* ------------------------------------------------------------ */
464 @Override
465 protected void fileRemoved(String filename) throws Exception
466 {
467 File file = new File(filename);
468
469 //is the file that was removed a directory?
470 if (file.isDirectory())
471 {
472 //is there a .xml file of the same name?
473 if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
474 return; //assume we will get removed events for the xml file
475
476 //is there .war file of the same name?
477 if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
478 return; //assume we will get removed events for the war file
479
480 super.fileRemoved(filename);
481 return;
482 }
483
484 //is the file that was removed a .war file?
485 String lowname = file.getName().toLowerCase(Locale.ENGLISH);
486 if (lowname.endsWith(".war"))
487 {
488 //is there a .xml file of the same name?
489 String name = file.getName();
490 String base=name.substring(0,name.length()-4);
491 if (exists(base+".xml")||exists(base+".XML"))
492 return; //ignore it as we should get removal of the xml file
493
494 super.fileRemoved(filename);
495 return;
496 }
497
498 //is the file that was removed an .xml file?
499 if (lowname.endsWith(".xml"))
500 super.fileRemoved(filename);
501 }
502
503 }