1 //
2 // ========================================================================
3 // Copyright (c) 1995-2013 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 String base=name.substring(0,name.length()-4);
116 // ignore if it is a war for an existing xml file?
117 if (exists(base+".xml")||exists(base+".XML"))
118 return false;
119
120 // OK to deploy it then
121 return true;
122 }
123
124 // else is it a context XML file
125 if (lowername.endsWith(".xml"))
126 return true;
127
128 return false;
129 }
130 }
131
132 /* ------------------------------------------------------------ */
133 public WebAppProvider()
134 {
135 super();
136 setFilenameFilter(new Filter());
137 setScanInterval(0);
138 }
139
140 /* ------------------------------------------------------------ */
141 /** Get the extractWars.
142 * @return the extractWars
143 */
144 @ManagedAttribute("extract war files")
145 public boolean isExtractWars()
146 {
147 return _extractWars;
148 }
149
150 /* ------------------------------------------------------------ */
151 /** Set the extractWars.
152 * @param extractWars the extractWars to set
153 */
154 public void setExtractWars(boolean extractWars)
155 {
156 _extractWars = extractWars;
157 }
158
159 /* ------------------------------------------------------------ */
160 /** Get the parentLoaderPriority.
161 * @return the parentLoaderPriority
162 */
163 @ManagedAttribute("parent classloader has priority")
164 public boolean isParentLoaderPriority()
165 {
166 return _parentLoaderPriority;
167 }
168
169 /* ------------------------------------------------------------ */
170 /** Set the parentLoaderPriority.
171 * @param parentLoaderPriority the parentLoaderPriority to set
172 */
173 public void setParentLoaderPriority(boolean parentLoaderPriority)
174 {
175 _parentLoaderPriority = parentLoaderPriority;
176 }
177
178 /* ------------------------------------------------------------ */
179 /** Get the defaultsDescriptor.
180 * @return the defaultsDescriptor
181 */
182 @ManagedAttribute("default descriptor for webapps")
183 public String getDefaultsDescriptor()
184 {
185 return _defaultsDescriptor;
186 }
187
188 /* ------------------------------------------------------------ */
189 /** Set the defaultsDescriptor.
190 * @param defaultsDescriptor the defaultsDescriptor to set
191 */
192 public void setDefaultsDescriptor(String defaultsDescriptor)
193 {
194 _defaultsDescriptor = defaultsDescriptor;
195 }
196
197 /* ------------------------------------------------------------ */
198 public ConfigurationManager getConfigurationManager()
199 {
200 return _configurationManager;
201 }
202
203 /* ------------------------------------------------------------ */
204 /** Set the configurationManager.
205 * @param configurationManager the configurationManager to set
206 */
207 public void setConfigurationManager(ConfigurationManager configurationManager)
208 {
209 _configurationManager = configurationManager;
210 }
211
212 /* ------------------------------------------------------------ */
213 /**
214 * @param configurations The configuration class names.
215 */
216 public void setConfigurationClasses(String[] configurations)
217 {
218 _configurationClasses = configurations==null?null:(String[])configurations.clone();
219 }
220
221 /* ------------------------------------------------------------ */
222 /**
223 *
224 */
225 @ManagedAttribute("configuration classes for webapps to be processed through")
226 public String[] getConfigurationClasses()
227 {
228 return _configurationClasses;
229 }
230
231 /**
232 * Set the Work directory where unpacked WAR files are managed from.
233 * <p>
234 * Default is the same as the <code>java.io.tmpdir</code> System Property.
235 *
236 * @param directory the new work directory
237 */
238 public void setTempDir(File directory)
239 {
240 _tempDirectory = directory;
241 }
242
243 /**
244 * Get the user supplied Work Directory.
245 *
246 * @return the user supplied work directory (null if user has not set Temp Directory yet)
247 */
248 @ManagedAttribute("temp directory for use, null if no user set temp directory")
249 public File getTempDir()
250 {
251 return _tempDirectory;
252 }
253
254 /* ------------------------------------------------------------ */
255 @Override
256 public ContextHandler createContextHandler(final App app) throws Exception
257 {
258 Resource resource = Resource.newResource(app.getOriginId());
259 File file = resource.getFile();
260 if (!resource.exists())
261 throw new IllegalStateException("App resouce does not exist "+resource);
262
263 String context = file.getName();
264
265 if (resource.exists() && FileID.isXmlFile(file))
266 {
267 XmlConfiguration xmlc = new XmlConfiguration(resource.getURL());
268
269 xmlc.getIdMap().put("Server",getDeploymentManager().getServer());
270 xmlc.getProperties().put("jetty.webapp",file.getCanonicalPath());
271 xmlc.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
272
273 if (getConfigurationManager() != null)
274 xmlc.getProperties().putAll(getConfigurationManager().getProperties());
275 return (ContextHandler)xmlc.configure();
276 }
277 else if (file.isDirectory())
278 {
279 // must be a directory
280 }
281 else if (FileID.isWebArchiveFile(file))
282 {
283 // Context Path is the same as the archive.
284 context = context.substring(0,context.length() - 4);
285 }
286 else
287 {
288 throw new IllegalStateException("unable to create ContextHandler for "+app);
289 }
290
291 // Ensure "/" is Not Trailing in context paths.
292 if (context.endsWith("/") && context.length() > 0)
293 {
294 context = context.substring(0,context.length() - 1);
295 }
296
297 // Start building the webapplication
298 WebAppContext wah = new WebAppContext();
299 wah.setDisplayName(context);
300
301 // special case of archive (or dir) named "root" is / context
302 if (context.equalsIgnoreCase("root"))
303 {
304 context = URIUtil.SLASH;
305 }
306 else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-"))
307 {
308 int dash=context.toLowerCase(Locale.ENGLISH).indexOf('-');
309 String virtual = context.substring(dash+1);
310 wah.setVirtualHosts(new String[]{virtual});
311 context = URIUtil.SLASH;
312 }
313
314 // Ensure "/" is Prepended to all context paths.
315 if (context.charAt(0) != '/')
316 {
317 context = "/" + context;
318 }
319
320
321 wah.setContextPath(context);
322 wah.setWar(file.getAbsolutePath());
323 if (_defaultsDescriptor != null)
324 {
325 wah.setDefaultsDescriptor(_defaultsDescriptor);
326 }
327 wah.setExtractWAR(_extractWars);
328 wah.setParentLoaderPriority(_parentLoaderPriority);
329 if (_configurationClasses != null)
330 {
331 wah.setConfigurationClasses(_configurationClasses);
332 }
333
334 if (_tempDirectory != null)
335 {
336 /* Since the Temp Dir is really a context base temp directory,
337 * Lets set the Temp Directory in a way similar to how WebInfConfiguration does it,
338 * instead of setting the
339 * WebAppContext.setTempDirectory(File).
340 * If we used .setTempDirectory(File) all webapps will wind up in the
341 * same temp / work directory, overwriting each others work.
342 */
343 wah.setAttribute(WebAppContext.BASETEMPDIR,_tempDirectory);
344 }
345 return wah;
346 }
347
348 }