View Javadoc

1   package org.eclipse.jetty.webapp;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.net.URI;
6   import java.net.URL;
7   import java.net.URLClassLoader;
8   import java.util.ArrayList;
9   import java.util.List;
10  import java.util.regex.Pattern;
11  
12  import org.eclipse.jetty.server.Connector;
13  import org.eclipse.jetty.util.IO;
14  import org.eclipse.jetty.util.PatternMatcher;
15  import org.eclipse.jetty.util.URIUtil;
16  import org.eclipse.jetty.util.log.Log;
17  import org.eclipse.jetty.util.resource.JarResource;
18  import org.eclipse.jetty.util.resource.Resource;
19  import org.eclipse.jetty.util.resource.ResourceCollection;
20  
21  public class WebInfConfiguration implements Configuration
22  {
23      public static final String TEMPDIR_CREATED = "org.eclipse.jetty.tmpdirCreated";
24      public static final String JAR_RESOURCES = "org.eclipse.jetty.jarList";
25      public static final String CONTAINER_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
26      public static final String WEBINF_JAR_PATTERN = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern";
27      
28      /**
29       * If set, to a list of URLs, these resources are added to the context
30       * resource base as a resource collection. 
31       */
32      public static final String RESOURCE_URLS = "org.eclipse.jetty.resources";
33      
34      
35      
36      
37      public void preConfigure(final WebAppContext context) throws Exception
38      {
39          //Make a temp directory for the webapp if one is not already set
40          resolveTempDirectory(context);
41          
42          //Extract webapp if necessary
43          unpack (context);
44  
45          File work = findWorkDirectory(context);
46          if (work != null)
47              makeTempDirectory(work, context, false);
48          
49          //Apply an initial ordering to the jars which governs which will be scanned for META-INF
50          //info and annotations. The ordering is based on inclusion patterns.       
51          String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
52          Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
53          tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
54          Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));
55          
56          final ArrayList jarResources = new ArrayList<Resource>();
57          context.setAttribute(JAR_RESOURCES, jarResources);  
58          
59          PatternMatcher jarNameMatcher = new PatternMatcher ()
60          {
61              public void matched(URI uri) throws Exception
62              {
63                  jarResources.add(Resource.newResource(uri));
64              }
65              
66          };
67          
68          //Apply ordering to container jars
69          ClassLoader loader = context.getClassLoader();
70          while (loader != null && (loader instanceof URLClassLoader))
71          {
72              URL[] urls = ((URLClassLoader)loader).getURLs();
73              if (urls != null)
74              {
75                  URI[] containerUris = new URI[urls.length];
76                  int i=0;
77                  for (URL u : urls)
78                  {
79                      containerUris[i++] = u.toURI();
80                  }
81                  jarNameMatcher.match(containerPattern, containerUris, false);
82              }
83              loader = loader.getParent();
84          }
85          
86          //Apply ordering to WEB-INF/lib jars
87          //Find all jars in WEB-INF
88          List<Resource> jars = findJars(context);
89          //Convert to uris for matching
90          URI[] uris = null;
91          if (jars != null)
92          {
93              uris = new URI[jars.size()];
94              int i=0;
95              for (Resource r: jars)
96              {
97                  uris[i++] = r.getURI();
98              }
99          }
100         jarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match 
101     }
102     
103     
104     
105     public void postConfigure(WebAppContext context) throws Exception
106     {
107         // TODO Auto-generated method stub
108 
109     }
110     
111 
112     public void configure(WebAppContext context) throws Exception
113     {
114         //cannot configure if the context is already started
115         if (context.isStarted())
116         {
117             if (Log.isDebugEnabled()){Log.debug("Cannot configure webapp "+context+" after it is started");}
118             return;
119         }
120 
121         Resource web_inf = context.getWebInf();
122 
123         // Add WEB-INF classes and lib classpaths
124         if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
125         {
126             // Look for classes directory
127             Resource classes= web_inf.addPath("classes/");
128             if (classes.exists())
129                 ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes.toString());
130 
131             // Look for jars
132             Resource lib= web_inf.addPath("lib/");
133             if (lib.exists() || lib.isDirectory())
134                 ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
135         }
136         
137         // Look for extra resource
138         List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS);
139         if (resources!=null)
140         {
141             Resource[] collection=new Resource[resources.size()+1];
142             int i=0;
143             collection[i++]=context.getBaseResource();
144             for (Resource resource : resources)
145                 collection[i++]=resource;
146             context.setBaseResource(new ResourceCollection(collection));
147         }
148     }
149     
150     public void deconfigure(WebAppContext context) throws Exception
151     {
152         // delete temp directory if we had to create it or if it isn't called work
153         Boolean containerCreated = (Boolean)context.getAttribute(TEMPDIR_CREATED);
154         
155         if (context.getTempDirectory()!=null && (containerCreated != null && containerCreated.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory()))
156         {
157             IO.delete(context.getTempDirectory());
158             setTempDirectory(null, context);
159         }
160     }
161 
162    
163     
164 
165  
166     
167     /* ------------------------------------------------------------ */
168     /**
169      * Get a temporary directory in which to unpack the war etc etc.
170      * The algorithm for determining this is to check these alternatives
171      * in the order shown:
172      * 
173      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
174      * <ol>
175      * <li>
176      * Iff an explicit directory is set for this webapp, use it. Do NOT set
177      * delete on exit.
178      * </li>
179      * <li>
180      * Iff javax.servlet.context.tempdir context attribute is set for
181      * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
182      * </li>
183      * </ol>
184      * 
185      * <p>B. Create a directory based on global settings. The new directory 
186      * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
187      * Work out where to create this directory:
188      * <ol>
189      * <li>
190      * Iff $(jetty.home)/work exists create the directory there. Do NOT
191      * set delete on exit. Do NOT delete contents if dir already exists.
192      * </li>
193      * <li>
194      * Iff WEB-INF/work exists create the directory there. Do NOT set
195      * delete on exit. Do NOT delete contents if dir already exists.
196      * </li>
197      * <li>
198      * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
199      * contents if dir already exists.
200      * </li>
201      * </ol>
202      * 
203      * @return
204      */
205     public void resolveTempDirectory (WebAppContext context)
206     {
207       //If a tmp directory is already set, we're done
208         File tmpDir = context.getTempDirectory();
209         if (tmpDir!=null && tmpDir.isDirectory() && tmpDir.canWrite())
210             return; //Already have a suitable tmp dir configured
211         
212 
213         //None configured, try and come up with one
214         //First ... see if one is configured in a context attribute
215         //either as a File or name of a file
216         Object t = context.getAttribute(WebAppContext.TEMPDIR);
217         if (t != null)
218         {
219             //Is it a File?
220             if (t instanceof File)
221             {
222                 tmpDir=(File)t;
223                 if (tmpDir.isDirectory() && tmpDir.canWrite())
224                 {
225                     context.setTempDirectory(tmpDir);
226                     return;
227                 }
228             }
229             // The context attribute specified a name not a File
230             if (t instanceof String)
231             {
232                 try
233                 {
234                     tmpDir=new File((String)t);
235 
236                     if (tmpDir.isDirectory() && tmpDir.canWrite())
237                     {
238                         context.setAttribute(context.TEMPDIR,tmpDir);
239                         context.setTempDirectory(tmpDir);
240                         return;
241                     }
242                 }
243                 catch(Exception e)
244                 {
245                     Log.warn(Log.EXCEPTION,e);
246                 }
247             }
248         }
249 
250         // Second ... make a tmp directory, in a work directory if one exists
251         String temp = getCanonicalNameForWebAppTmpDir(context);
252         
253         try
254         {
255             //Put the tmp dir in the work directory if we had one
256             File work =  new File(System.getProperty("jetty.home"),"work");
257             if (!work.exists() || !work.canWrite() || !work.isDirectory())
258                     work = null;
259             
260             if (work!=null)
261                 makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists
262             else
263                 makeTempDirectory(new File(System.getProperty("java.io.tmpdir")), context, true); //make a tmpdir, delete if it already exists
264         }
265         catch(Exception e)
266         {
267             tmpDir=null;
268             Log.ignore(e);
269         }
270 
271         //Third ... Something went wrong trying to make the tmp directory, just make
272         //a jvm managed tmp directory
273         if (context.getTempDirectory() == null)
274         {
275             try
276             {
277                 // Last resort
278                 tmpDir=File.createTempFile("JettyContext","");
279                 if (tmpDir.exists())
280                     tmpDir.delete();
281                 tmpDir.mkdir();
282                 tmpDir.deleteOnExit();
283                 setTempDirectory(tmpDir, context);
284             }
285             catch(IOException e)
286             {
287                 Log.warn("tmpdir",e); System.exit(1);
288             }
289         }
290     }
291     
292     
293     public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting)
294     throws IOException
295     {
296         if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory())
297         {
298             String temp = getCanonicalNameForWebAppTmpDir(context);                    
299             File tmpDir = new File(parent,temp);
300 
301             if (deleteExisting && tmpDir.exists())
302             {
303                 if (!IO.delete(tmpDir))
304                 {
305                     if(Log.isDebugEnabled())Log.debug("Failed to delete temp dir "+tmpDir);
306                 }
307             
308                 //If we can't delete the existing tmp dir, create a new one
309                 if (tmpDir.exists())
310                 {
311                     String old=tmpDir.toString();
312                     tmpDir=File.createTempFile(temp+"_","");
313                     if (tmpDir.exists())
314                         tmpDir.delete();
315                     Log.warn("Can't reuse "+old+", using "+tmpDir);
316                 } 
317             }
318             
319             if (!tmpDir.exists())
320                 tmpDir.mkdir();
321 
322             //If the parent is not a work directory
323             if (!isTempWorkDirectory(tmpDir))
324             {
325                 tmpDir.deleteOnExit();
326                 //TODO why is this here?
327                 File sentinel = new File(tmpDir, ".active");
328                 if(!sentinel.exists())
329                     sentinel.mkdir();
330             }
331             setTempDirectory(tmpDir, context);
332         }
333     }
334 
335     
336     public void setTempDirectory (File tmpDir, WebAppContext context)
337     {
338         context.setAttribute(TEMPDIR_CREATED, Boolean.TRUE);
339         context.setAttribute(context.TEMPDIR,tmpDir);
340         context.setTempDirectory(tmpDir);
341         if(Log.isDebugEnabled())Log.debug("Set temp dir "+tmpDir);
342     }
343     
344     
345     public void unpack (WebAppContext context) throws IOException
346     {
347         Resource web_app = context.getBaseResource();
348         if (web_app == null)
349         {
350             String war = context.getWar();
351             if (war==null || war.length()==0)
352                 war=context.getResourceBase();
353 
354             // Set dir or WAR
355             web_app = context.newResource(war);
356 
357             // Accept aliases for WAR files
358             if (web_app.getAlias() != null)
359             {
360                 Log.debug(web_app + " anti-aliased to " + web_app.getAlias());
361                 web_app = context.newResource(web_app.getAlias());
362             }
363 
364             if (Log.isDebugEnabled())
365                 Log.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory());
366 
367             // Is the WAR usable directly?
368             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
369             {
370                 // No - then lets see if it can be turned into a jar URL.
371                 Resource jarWebApp = context.newResource("jar:" + web_app + "!/");
372                 if (jarWebApp.exists() && jarWebApp.isDirectory())
373                 {
374                     web_app= jarWebApp;
375                 }
376             }
377 
378             // If we should extract or the URL is still not usable
379             if (web_app.exists()  && 
380                     (
381                             (context.isCopyWebDir() && web_app.getFile()!= null && web_app.getFile().isDirectory()) 
382                             ||
383                             (context.isExtractWAR() && web_app.getFile()!= null && !web_app.getFile().isDirectory())
384                             ||
385                             (context.isExtractWAR() && web_app.getFile() == null)
386                             ||
387                             !web_app.isDirectory()
388                     ))
389             {
390                 // Then extract it if necessary to the temporary location
391                 File extractedWebAppDir= new File(context.getTempDirectory(), "webapp");
392 
393                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
394                 {
395                     // Copy directory
396                     Log.info("Copy " + web_app.getFile() + " to " + extractedWebAppDir);
397                     IO.copyDir(web_app.getFile(),extractedWebAppDir);
398                 }
399                 else
400                 {
401                     if (!extractedWebAppDir.exists())
402                     {
403                         //it hasn't been extracted before so extract it
404                         extractedWebAppDir.mkdir();
405                         Log.info("Extract " + war + " to " + extractedWebAppDir);
406                         JarResource.extract(web_app, extractedWebAppDir, false);
407                     }
408                     else
409                     {
410                         //only extract if the war file is newer
411                         if (web_app.lastModified() > extractedWebAppDir.lastModified())
412                         {
413                             extractedWebAppDir.delete();
414                             extractedWebAppDir.mkdir();
415                             Log.info("Extract " + war + " to " + extractedWebAppDir);
416                             JarResource.extract(web_app, extractedWebAppDir, false);
417                         }
418                     }
419                 } 
420                 web_app = Resource.newResource(extractedWebAppDir.getCanonicalPath());
421             }
422 
423             // Now do we have something usable?
424             if (!web_app.exists() || !web_app.isDirectory())
425             {
426                 Log.warn("Web application not found " + war);
427                 throw new java.io.FileNotFoundException(war);
428             }
429 
430         
431             context.setBaseResource(web_app);
432             
433             if (Log.isDebugEnabled())
434                 Log.debug("webapp=" + web_app);
435         }
436     }
437     
438     
439     public File findWorkDirectory (WebAppContext context) throws IOException
440     {
441         if (context.getBaseResource() != null)
442         {
443             Resource web_inf = context.getWebInf();
444             if (web_inf !=null && web_inf.exists())
445             {
446                return new File(web_inf.getFile(),"work");
447             }
448         }
449         return null;
450     }
451     
452  
453     
454     
455     /**
456      * Check if the tmpDir itself is called "work", or if the tmpDir
457      * is in a directory called "work".
458      * @return
459      */
460     public boolean isTempWorkDirectory (File tmpDir)
461     {
462         if (tmpDir == null)
463             return false;
464         if (tmpDir.getName().equalsIgnoreCase("work"))
465             return true;
466         File t = tmpDir.getParentFile();
467         if (t == null)
468             return false;
469         return (t.getName().equalsIgnoreCase("work"));
470     }
471     
472     
473     /**
474      * Create a canonical name for a webapp tmp directory.
475      * The form of the name is:
476      *  "Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36 hashcode of whole string
477      *  
478      *  host and port uniquely identify the server
479      *  context and virtual host uniquely identify the webapp
480      * @return
481      */
482     public String getCanonicalNameForWebAppTmpDir (WebAppContext context)
483     {
484         StringBuffer canonicalName = new StringBuffer();
485         canonicalName.append("Jetty");
486        
487         //get the host and the port from the first connector 
488         Connector[] connectors = context.getServer().getConnectors();
489         
490         
491         //Get the host
492         canonicalName.append("_");
493         String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
494         if (host == null)
495             host = "0.0.0.0";
496         canonicalName.append(host.replace('.', '_'));
497         
498         //Get the port
499         canonicalName.append("_");
500         //try getting the real port being listened on
501         int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
502         //if not available (eg no connectors or connector not started), 
503         //try getting one that was configured.
504         if (port < 0)
505             port = connectors[0].getPort();
506         canonicalName.append(port);
507 
508        
509         //Resource  base
510         canonicalName.append("_");
511         try
512         {
513             Resource resource = context.getBaseResource();
514             if (resource == null)
515             {
516                 if (context.getWar()==null || context.getWar().length()==0)
517                     resource=context.newResource(context.getResourceBase());
518                 
519                 // Set dir or WAR
520                 resource = context.newResource(context.getWar());
521             }
522                 
523             String tmp = URIUtil.decodePath(resource.getURL().getPath());
524             if (tmp.endsWith("/"))
525                 tmp = tmp.substring(0, tmp.length()-1);
526             if (tmp.endsWith("!"))
527                 tmp = tmp.substring(0, tmp.length() -1);
528             //get just the last part which is the filename
529             int i = tmp.lastIndexOf("/");
530             canonicalName.append(tmp.substring(i+1, tmp.length()));
531         }
532         catch (Exception e)
533         {
534             Log.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
535         }
536             
537         //Context name
538         canonicalName.append("_");
539         String contextPath = context.getContextPath();
540         contextPath=contextPath.replace('/','_');
541         contextPath=contextPath.replace('\\','_');
542         canonicalName.append(contextPath);
543         
544         //Virtual host (if there is one)
545         canonicalName.append("_");
546         String[] vhosts = context.getVirtualHosts();
547         if (vhosts == null || vhosts.length <= 0)
548             canonicalName.append("");
549         else
550             canonicalName.append(vhosts[0]);
551         
552         //base36 hash of the whole string for uniqueness
553         String hash = Integer.toString(canonicalName.toString().hashCode(),36);
554         canonicalName.append("_");
555         canonicalName.append(hash);
556         
557         // sanitize
558         for (int i=0;i<canonicalName.length();i++)
559         {
560             char c=canonicalName.charAt(i);
561             if (!Character.isJavaIdentifierPart(c))
562                 canonicalName.setCharAt(i,'.');
563         }        
564         
565         return canonicalName.toString();
566     }
567     
568     /**
569      * Look for jars in WEB-INF/lib
570      * @param context
571      * @return
572      * @throws Exception
573      */
574     protected List<Resource> findJars (WebAppContext context) 
575     throws Exception
576     {
577         List<Resource> jarResources = new ArrayList<Resource>();
578         
579         Resource web_inf = context.getWebInf();
580         Resource web_inf_lib = web_inf.addPath("/lib");
581        
582         
583         if (web_inf_lib.exists() && web_inf_lib.isDirectory())
584         {
585             String[] files=web_inf_lib.list();
586             for (int f=0;files!=null && f<files.length;f++)
587             {
588                 try 
589                 {
590                     Resource file = web_inf_lib.addPath(files[f]);
591                     String fnlc = file.getName().toLowerCase();
592                     int dot = fnlc.lastIndexOf('.');
593                     String extension = (dot < 0 ? null : fnlc.substring(dot));
594                     if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
595                     {
596                         jarResources.add(file);
597                     }
598                 }
599                 catch (Exception ex)
600                 {
601                     Log.warn(Log.EXCEPTION,ex);
602                 }
603             }
604         }
605         return jarResources;
606     }
607 }