View Javadoc

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 }