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