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