View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.webapp;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.URI;
24  import java.net.URISyntaxException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Set;
31  import java.util.StringTokenizer;
32  import java.util.regex.Pattern;
33  
34  import org.eclipse.jetty.server.Connector;
35  import org.eclipse.jetty.server.NetworkConnector;
36  import org.eclipse.jetty.server.Server;
37  import org.eclipse.jetty.util.IO;
38  import org.eclipse.jetty.util.PatternMatcher;
39  import org.eclipse.jetty.util.URIUtil;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  import org.eclipse.jetty.util.resource.JarResource;
43  import org.eclipse.jetty.util.resource.Resource;
44  import org.eclipse.jetty.util.resource.ResourceCollection;
45  
46  public class WebInfConfiguration extends AbstractConfiguration
47  {
48      private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
49  
50      public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
51      public static final String CONTAINER_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
52      public static final String WEBINF_JAR_PATTERN = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern";
53  
54      /**
55       * If set, to a list of URLs, these resources are added to the context
56       * resource base as a resource collection.
57       */
58      public static final String RESOURCE_DIRS = "org.eclipse.jetty.resources";
59      
60  
61      protected Resource _preUnpackBaseResource;
62      
63  
64  
65      @Override
66      public void preConfigure(final WebAppContext context) throws Exception
67      {
68          //Make a temp directory for the webapp if one is not already set
69          resolveTempDirectory(context);
70  
71          //Extract webapp if necessary
72          unpack (context);
73  
74  
75          //Apply an initial ordering to the jars which governs which will be scanned for META-INF
76          //info and annotations. The ordering is based on inclusion patterns.
77          String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
78          Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
79          tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
80          Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));
81  
82          //Apply ordering to container jars - if no pattern is specified, we won't
83          //match any of the container jars
84          PatternMatcher containerJarNameMatcher = new PatternMatcher ()
85          {
86              public void matched(URI uri) throws Exception
87              {
88                  context.getMetaData().addContainerResource(Resource.newResource(uri));
89              }
90          };
91          ClassLoader loader = null;
92          if (context.getClassLoader() != null)
93              loader = context.getClassLoader().getParent();
94  
95          while (loader != null && (loader instanceof URLClassLoader))
96          {
97              URL[] urls = ((URLClassLoader)loader).getURLs();
98              if (urls != null)
99              {
100                 URI[] containerUris = new URI[urls.length];
101                 int i=0;
102                 for (URL u : urls)
103                 {
104                     try
105                     {
106                         containerUris[i] = u.toURI();
107                     }
108                     catch (URISyntaxException e)
109                     {
110                         containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
111                     }
112                     i++;
113                 }
114                 containerJarNameMatcher.match(containerPattern, containerUris, false);
115             }
116             loader = loader.getParent();
117         }
118 
119         //Apply ordering to WEB-INF/lib jars
120         PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
121         {
122             @Override
123             public void matched(URI uri) throws Exception
124             {
125                 context.getMetaData().addWebInfJar(Resource.newResource(uri));
126             }
127         };
128         List<Resource> jars = findJars(context);
129 
130         //Convert to uris for matching
131         URI[] uris = null;
132         if (jars != null)
133         {
134             uris = new URI[jars.size()];
135             int i=0;
136             for (Resource r: jars)
137             {
138                 uris[i++] = r.getURI();
139             }
140         }
141         webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match
142        
143         //No pattern to appy to classes, just add to metadata
144         context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
145     }
146 
147 
148     @Override
149     public void configure(WebAppContext context) throws Exception
150     {
151         //cannot configure if the context is already started
152         if (context.isStarted())
153         {
154             if (LOG.isDebugEnabled())
155                 LOG.debug("Cannot configure webapp "+context+" after it is started");
156             return;
157         }
158 
159         Resource web_inf = context.getWebInf();
160 
161         // Add WEB-INF classes and lib classpaths
162         if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
163         {
164             // Look for classes directory
165             Resource classes= web_inf.addPath("classes/");
166             if (classes.exists())
167                 ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);
168 
169             // Look for jars
170             Resource lib= web_inf.addPath("lib/");
171             if (lib.exists() || lib.isDirectory())
172                 ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
173         }
174 
175         // Look for extra resource
176         @SuppressWarnings("unchecked")
177         Set<Resource> resources = (Set<Resource>)context.getAttribute(RESOURCE_DIRS);
178         if (resources!=null)
179         {
180             Resource[] collection=new Resource[resources.size()+1];
181             int i=0;
182             collection[i++]=context.getBaseResource();
183             for (Resource resource : resources)
184                 collection[i++]=resource;
185             context.setBaseResource(new ResourceCollection(collection));
186         }
187     }
188 
189     @Override
190     public void deconfigure(WebAppContext context) throws Exception
191     {
192         //if we're not persisting the temp dir contents delete it
193         if (!context.isPersistTempDirectory())
194         {
195             IO.delete(context.getTempDirectory());
196         }
197         
198         //if it wasn't explicitly configured by the user, then unset it
199         Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
200         if (tmpdirConfigured != null && !tmpdirConfigured) 
201             context.setTempDirectory(null);
202 
203         //reset the base resource back to what it was before we did any unpacking of resources
204         context.setBaseResource(_preUnpackBaseResource);
205     }
206 
207     /* ------------------------------------------------------------ */
208     /**
209      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
210      */
211     @Override
212     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
213     {
214         File tmpDir=File.createTempFile(WebInfConfiguration.getCanonicalNameForWebAppTmpDir(context),"",template.getTempDirectory().getParentFile());
215         if (tmpDir.exists())
216         {
217             IO.delete(tmpDir);
218         }
219         tmpDir.mkdir();
220         tmpDir.deleteOnExit();
221         context.setTempDirectory(tmpDir);
222     }
223 
224 
225     /* ------------------------------------------------------------ */
226     /**
227      * Get a temporary directory in which to unpack the war etc etc.
228      * The algorithm for determining this is to check these alternatives
229      * in the order shown:
230      *
231      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
232      * <ol>
233      * <li>
234      * Iff an explicit directory is set for this webapp, use it. Set delete on
235      * exit depends on value of persistTempDirectory.
236      * </li>
237      * <li>
238      * Iff javax.servlet.context.tempdir context attribute is set for
239      * this webapp && exists && writeable, then use it. Set delete on exit depends on
240      * value of persistTempDirectory.
241      * </li>
242      * </ol>
243      *
244      * <p>B. Create a directory based on global settings. The new directory
245      * will be called "Jetty-"+host+"-"+port+"__"+context+"-"+virtualhost+"-"+randomdigits+".dir"
246      * </p>
247      * <p>
248      * If the user has specified the context attribute org.eclipse.jetty.webapp.basetempdir, the
249      * directory specified by this attribute will be the parent of the temp dir created. Otherwise,
250      * the parent dir is $(java.io.tmpdir). Set delete on exit depends on value of persistTempDirectory. 
251      * </p>
252      */
253     public void resolveTempDirectory (WebAppContext context)
254     throws Exception
255     {
256         //If a tmp directory is already set we should use it
257         File tmpDir = context.getTempDirectory();
258         if (tmpDir != null)
259         {
260             configureTempDirectory(tmpDir, context);
261             context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
262             return;
263         }
264 
265         // No temp directory configured, try to establish one via the javax.servlet.context.tempdir.
266         File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR));
267         if (servletTmpDir != null)
268         {
269             // Use as tmpDir
270             tmpDir = servletTmpDir;
271             configureTempDirectory(tmpDir, context);
272             // Ensure Attribute has File object
273             context.setAttribute(WebAppContext.TEMPDIR,tmpDir);
274             // Set as TempDir in context.
275             context.setTempDirectory(tmpDir);
276             return;
277         }
278 
279         //We need to make a temp dir. Check if the user has set a directory to use instead
280         //of java.io.tmpdir as the parent of the dir
281         File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
282         if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
283         {
284             //Make a temp directory as a child of the given base dir
285             makeTempDirectory(baseTemp,context);
286         }
287         else
288         {
289             //Make a temp directory in java.io.tmpdir
290             makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context);
291         }
292     }
293 
294     /**
295      * Given an Object, return File reference for object.
296      * Typically used to convert anonymous Object from getAttribute() calls to a File object.
297      * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null
298      * @return the File object, null if null, or null if not a File or String
299      */
300     private File asFile(Object fileattr)
301     {
302         if (fileattr == null)
303         {
304             return null;
305         }
306         if (fileattr instanceof File)
307         {
308             return (File)fileattr;
309         }
310         if (fileattr instanceof String)
311         {
312             return new File((String)fileattr);
313         }
314         return null;
315     }
316 
317 
318 
319     public void makeTempDirectory (File parent, WebAppContext context)
320             throws Exception
321     {
322         if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
323             throw new IllegalStateException("Parent for temp dir not configured correctly: "+(parent==null?"null":"writeable="+parent.canWrite()));
324 
325         String temp = getCanonicalNameForWebAppTmpDir(context);
326         File tmpDir = File.createTempFile(temp, ".dir", parent);
327         //delete the file that was created
328         tmpDir.delete();
329         //and make a directory of the same name
330         tmpDir.mkdirs();
331         configureTempDirectory(tmpDir, context);
332 
333         if(LOG.isDebugEnabled())
334             LOG.debug("Set temp dir "+tmpDir);
335         context.setTempDirectory(tmpDir);
336     }
337 
338     private void configureTempDirectory (File dir, WebAppContext context)
339     {
340         if (dir == null)
341             throw new IllegalArgumentException("Null temp dir");
342 
343         //if dir exists and we don't want it persisted, delete it
344         if (dir.exists() && !context.isPersistTempDirectory())
345         {
346             if (!IO.delete(dir))
347                 throw new IllegalStateException("Failed to delete temp dir "+dir);
348         }
349 
350         //if it doesn't exist make it
351         if (!dir.exists())
352             dir.mkdirs();
353 
354         if (!context.isPersistTempDirectory())
355             dir.deleteOnExit();
356 
357         //is it useable
358         if (!dir.canWrite() || !dir.isDirectory())   
359             throw new IllegalStateException("Temp dir "+dir+" not useable: writeable="+dir.canWrite()+", dir="+dir.isDirectory());
360     }
361 
362 
363     public void unpack (WebAppContext context) throws IOException
364     {
365         Resource web_app = context.getBaseResource();
366         _preUnpackBaseResource = context.getBaseResource();
367 
368         if (web_app == null)
369         {
370             String war = context.getWar();
371             if (war!=null && war.length()>0)
372                 web_app = context.newResource(war);
373             else
374                 web_app=context.getBaseResource();
375 
376             // Accept aliases for WAR files
377             if (web_app.getAlias() != null)
378             {
379                 LOG.debug(web_app + " anti-aliased to " + web_app.getAlias());
380                 web_app = context.newResource(web_app.getAlias());
381             }
382 
383             if (LOG.isDebugEnabled())
384                 LOG.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()+" file="+(web_app.getFile()));
385             // Is the WAR usable directly?
386             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
387             {
388                 // No - then lets see if it can be turned into a jar URL.
389                 Resource jarWebApp = JarResource.newJarResource(web_app);
390                 if (jarWebApp.exists() && jarWebApp.isDirectory())
391                     web_app= jarWebApp;
392             }
393 
394             // If we should extract or the URL is still not usable
395             if (web_app.exists()  && (
396                     (context.isCopyWebDir() && web_app.getFile() != null && web_app.getFile().isDirectory()) ||
397                     (context.isExtractWAR() && web_app.getFile() != null && !web_app.getFile().isDirectory()) ||
398                     (context.isExtractWAR() && web_app.getFile() == null) ||
399                     !web_app.isDirectory())
400                             )
401             {
402                 // Look for sibling directory.
403                 File extractedWebAppDir = null;
404 
405                 if (war!=null)
406                 {
407                     // look for a sibling like "foo/" to a "foo.war"
408                     File warfile=Resource.newResource(war).getFile();
409                     if (warfile!=null && warfile.getName().toLowerCase(Locale.ENGLISH).endsWith(".war"))
410                     {
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 
417                 if (extractedWebAppDir==null)
418                     // Then extract it if necessary to the temporary location
419                     extractedWebAppDir= new File(context.getTempDirectory(), "webapp");
420 
421                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
422                 {
423                     // Copy directory
424                     LOG.debug("Copy " + web_app + " to " + extractedWebAppDir);
425                     web_app.copyTo(extractedWebAppDir);
426                 }
427                 else
428                 {
429                     //Use a sentinel file that will exist only whilst the extraction is taking place.
430                     //This will help us detect interrupted extractions.
431                     File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
432 
433                     if (!extractedWebAppDir.exists())
434                     {
435                         //it hasn't been extracted before so extract it
436                         extractionLock.createNewFile();
437                         extractedWebAppDir.mkdir();
438                         LOG.debug("Extract " + web_app + " to " + extractedWebAppDir);
439                         Resource jar_web_app = JarResource.newJarResource(web_app);
440                         jar_web_app.copyTo(extractedWebAppDir);
441                         extractionLock.delete();
442                     }
443                     else
444                     {
445                         //only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
446                         if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
447                         {
448                             extractionLock.createNewFile();
449                             IO.delete(extractedWebAppDir);
450                             extractedWebAppDir.mkdir();
451                             LOG.debug("Extract " + web_app + " to " + extractedWebAppDir);
452                             Resource jar_web_app = JarResource.newJarResource(web_app);
453                             jar_web_app.copyTo(extractedWebAppDir);
454                             extractionLock.delete();
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             context.setBaseResource(web_app);
469 
470             if (LOG.isDebugEnabled())
471                 LOG.debug("webapp=" + web_app);
472         }
473 
474 
475         // Do we need to extract WEB-INF/lib?
476         if (context.isCopyWebInf() && !context.isCopyWebDir())
477         {
478             Resource web_inf= web_app.addPath("WEB-INF/");
479 
480             File extractedWebInfDir= new File(context.getTempDirectory(), "webinf");
481             if (extractedWebInfDir.exists())
482                 IO.delete(extractedWebInfDir);
483             extractedWebInfDir.mkdir();
484             Resource web_inf_lib = web_inf.addPath("lib/");
485             File webInfDir=new File(extractedWebInfDir,"WEB-INF");
486             webInfDir.mkdir();
487 
488             if (web_inf_lib.exists())
489             {
490                 File webInfLibDir = new File(webInfDir, "lib");
491                 if (webInfLibDir.exists())
492                     IO.delete(webInfLibDir);
493                 webInfLibDir.mkdir();
494 
495                 LOG.debug("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir);
496                 web_inf_lib.copyTo(webInfLibDir);
497             }
498 
499             Resource web_inf_classes = web_inf.addPath("classes/");
500             if (web_inf_classes.exists())
501             {
502                 File webInfClassesDir = new File(webInfDir, "classes");
503                 if (webInfClassesDir.exists())
504                     IO.delete(webInfClassesDir);
505                 webInfClassesDir.mkdir();
506                 LOG.debug("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath());
507                 web_inf_classes.copyTo(webInfClassesDir);
508             }
509 
510             web_inf=Resource.newResource(extractedWebInfDir.getCanonicalPath());
511 
512             ResourceCollection rc = new ResourceCollection(web_inf,web_app);
513 
514             if (LOG.isDebugEnabled())
515                 LOG.debug("context.resourcebase = "+rc);
516 
517             context.setBaseResource(rc);
518         }
519     }
520 
521 
522 
523 
524     /**
525      * Create a canonical name for a webapp temp directory.
526      * The form of the name is:
527      *  <code>"jetty-"+host+"-"+port+"-"+resourceBase+"-_"+context+"-"+virtualhost+"-"+randomdigits+".dir"</code>
528      *
529      *  host and port uniquely identify the server
530      *  context and virtual host uniquely identify the webapp
531      *  randomdigits ensure every tmp directory is unique
532      *  
533      * @return the canonical name for the webapp temp directory
534      */
535     public static String getCanonicalNameForWebAppTmpDir (WebAppContext context)
536     {
537         StringBuffer canonicalName = new StringBuffer();
538         canonicalName.append("jetty-");
539 
540         //get the host and the port from the first connector
541         Server server=context.getServer();
542         if (server!=null)
543         {
544             Connector[] connectors = context.getServer().getConnectors();
545 
546             if (connectors.length>0)
547             {
548                 //Get the host
549                 String host=null;
550                 int port=0;
551                 if (connectors!=null && (connectors[0] instanceof NetworkConnector))
552                 {
553                     NetworkConnector connector = (NetworkConnector)connectors[0];
554                     host=connector.getHost();
555                     port=connector.getLocalPort();
556                     if (port < 0)
557                         port = connector.getPort();
558                 }
559                 if (host == null)
560                     host = "0.0.0.0";
561                 canonicalName.append(host);
562 
563                 //Get the port
564                 canonicalName.append("-");
565 
566                 //if not available (eg no connectors or connector not started),
567                 //try getting one that was configured.
568                 canonicalName.append(port);
569                 canonicalName.append("-");
570             }
571         }
572 
573 
574         //Resource  base
575         try
576         {
577             Resource resource = context.getBaseResource();
578             if (resource == null)
579             {
580                 if (context.getWar()==null || context.getWar().length()==0)
581                     resource=context.newResource(context.getResourceBase());
582 
583                 // Set dir or WAR
584                 resource = context.newResource(context.getWar());
585             }
586 
587             String tmp = URIUtil.decodePath(resource.getURL().getPath());
588             if (tmp.endsWith("/"))
589                 tmp = tmp.substring(0, tmp.length()-1);
590             if (tmp.endsWith("!"))
591                 tmp = tmp.substring(0, tmp.length() -1);
592             //get just the last part which is the filename
593             int i = tmp.lastIndexOf("/");
594             canonicalName.append(tmp.substring(i+1, tmp.length()));
595             canonicalName.append("-");
596         }
597         catch (Exception e)
598         {
599             LOG.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
600         }
601 
602         //Context name
603         String contextPath = context.getContextPath();
604         contextPath=contextPath.replace('/','_');
605         contextPath=contextPath.replace('\\','_');
606         canonicalName.append(contextPath);
607 
608         //Virtual host (if there is one)
609         canonicalName.append("-");
610         String[] vhosts = context.getVirtualHosts();
611         if (vhosts == null || vhosts.length <= 0)
612             canonicalName.append("any");
613         else
614             canonicalName.append(vhosts[0]);
615 
616         // sanitize
617         for (int i=0;i<canonicalName.length();i++)
618         {
619             char c=canonicalName.charAt(i);
620             if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c)<0)
621                 canonicalName.setCharAt(i,'.');
622         }
623 
624         canonicalName.append("-");
625 
626         return canonicalName.toString();
627     }
628 
629     
630     protected List<Resource> findClassDirs (WebAppContext context)
631     throws Exception
632     {
633         if (context == null)
634             return null;
635         
636         List<Resource> classDirs = new ArrayList<Resource>();
637 
638         Resource webInfClasses = findWebInfClassesDir(context);
639         if (webInfClasses != null)
640             classDirs.add(webInfClasses);
641         List<Resource> extraClassDirs = findExtraClasspathDirs(context);
642         if (extraClassDirs != null)
643             classDirs.addAll(extraClassDirs);
644         
645         return classDirs;
646     }
647     
648     
649     /**
650      * Look for jars that should be treated as if they are in WEB-INF/lib
651      * 
652      * @param context
653      * @return the list of jar resources found within context
654      * @throws Exception
655      */
656     protected List<Resource> findJars (WebAppContext context)
657     throws Exception
658     {
659         List<Resource> jarResources = new ArrayList<Resource>();
660         List<Resource> webInfLibJars = findWebInfLibJars(context);
661         if (webInfLibJars != null)
662             jarResources.addAll(webInfLibJars);
663         List<Resource> extraClasspathJars = findExtraClasspathJars(context);
664         if (extraClasspathJars != null)
665             jarResources.addAll(extraClasspathJars);
666         return jarResources;
667     }
668     
669     /**
670      *  Look for jars in WEB-INF/lib
671      *  
672      * @param context
673      * @return
674      * @throws Exception
675      */
676     protected List<Resource> findWebInfLibJars(WebAppContext context)
677     throws Exception
678     {
679         Resource web_inf = context.getWebInf();
680         if (web_inf==null || !web_inf.exists())
681             return null;
682 
683         List<Resource> jarResources = new ArrayList<Resource>();
684         Resource web_inf_lib = web_inf.addPath("/lib");
685         if (web_inf_lib.exists() && web_inf_lib.isDirectory())
686         {
687             String[] files=web_inf_lib.list();
688             for (int f=0;files!=null && f<files.length;f++)
689             {
690                 try
691                 {
692                     Resource file = web_inf_lib.addPath(files[f]);
693                     String fnlc = file.getName().toLowerCase(Locale.ENGLISH);
694                     int dot = fnlc.lastIndexOf('.');
695                     String extension = (dot < 0 ? null : fnlc.substring(dot));
696                     if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
697                     {
698                         jarResources.add(file);
699                     }
700                 }
701                 catch (Exception ex)
702                 {
703                     LOG.warn(Log.EXCEPTION,ex);
704                 }
705             }
706         }
707         return jarResources;
708     }
709     
710     
711     
712     /**
713      * Get jars from WebAppContext.getExtraClasspath as resources
714      * 
715      * @param context
716      * @return
717      * @throws Exception
718      */
719     protected List<Resource>  findExtraClasspathJars(WebAppContext context)
720     throws Exception
721     { 
722         if (context == null || context.getExtraClasspath() == null)
723             return null;
724         
725         List<Resource> jarResources = new ArrayList<Resource>();
726         StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
727         while (tokenizer.hasMoreTokens())
728         {
729             Resource resource = context.newResource(tokenizer.nextToken().trim());
730             String fnlc = resource.getName().toLowerCase(Locale.ENGLISH);
731             int dot = fnlc.lastIndexOf('.');
732             String extension = (dot < 0 ? null : fnlc.substring(dot));
733             if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
734             {
735                 jarResources.add(resource);
736             }
737         }
738         
739         return jarResources;
740     }
741     
742     /**
743      * Get WEB-INF/classes dir
744      * 
745      * @param context
746      * @return
747      * @throws Exception
748      */
749     protected Resource findWebInfClassesDir (WebAppContext context)
750     throws Exception
751     {
752         if (context == null)
753             return null;
754         
755         Resource web_inf = context.getWebInf();
756 
757         // Find WEB-INF/classes
758         if (web_inf != null && web_inf.isDirectory())
759         {
760             // Look for classes directory
761             Resource classes= web_inf.addPath("classes/");
762             if (classes.exists())
763                 return classes;
764         }
765         return null;
766     }
767     
768     
769     /**
770      * Get class dirs from WebAppContext.getExtraClasspath as resources
771      * 
772      * @param context
773      * @return
774      * @throws Exception
775      */
776     protected List<Resource>  findExtraClasspathDirs(WebAppContext context)
777     throws Exception
778     { 
779         if (context == null || context.getExtraClasspath() == null)
780             return null;
781         
782         List<Resource> dirResources = new ArrayList<Resource>();
783         StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
784         while (tokenizer.hasMoreTokens())
785         {
786             Resource resource = context.newResource(tokenizer.nextToken().trim());
787             if (resource.exists() && resource.isDirectory())
788                 dirResources.add(resource);
789         }
790         
791         return dirResources;
792     }
793     
794     
795 }