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