View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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 && !resources.isEmpty())
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         if (context.getBaseResource() != null)
205             context.getBaseResource().close();
206         context.setBaseResource(_preUnpackBaseResource);
207     }
208 
209     /* ------------------------------------------------------------ */
210     /**
211      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
212      */
213     @Override
214     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
215     {
216         File tmpDir=File.createTempFile(WebInfConfiguration.getCanonicalNameForWebAppTmpDir(context),"",template.getTempDirectory().getParentFile());
217         if (tmpDir.exists())
218         {
219             IO.delete(tmpDir);
220         }
221         tmpDir.mkdir();
222         tmpDir.deleteOnExit();
223         context.setTempDirectory(tmpDir);
224     }
225 
226 
227     /* ------------------------------------------------------------ */
228     /**
229      * Get a temporary directory in which to unpack the war etc etc.
230      * The algorithm for determining this is to check these alternatives
231      * in the order shown:
232      * <p>
233      * A. Try to use an explicit directory specifically for this webapp:
234      * <ol>
235      * <li>
236      * Iff an explicit directory is set for this webapp, use it. Set delete on
237      * exit depends on value of persistTempDirectory.
238      * </li>
239      * <li>
240      * Iff javax.servlet.context.tempdir context attribute is set for
241      * this webapp &amp;&amp; exists &amp;&amp; writeable, then use it. Set delete on exit depends on
242      * value of persistTempDirectory.
243      * </li>
244      * </ol>
245      *
246      * <p>
247      * B. Create a directory based on global settings. The new directory
248      * will be called <code>"Jetty-"+host+"-"+port+"__"+context+"-"+virtualhost+"-"+randomdigits+".dir"</code>
249      * <p>
250      * If the user has specified the context attribute org.eclipse.jetty.webapp.basetempdir, the
251      * directory specified by this attribute will be the parent of the temp dir created. Otherwise,
252      * the parent dir is <code>${java.io.tmpdir}</code>. Set delete on exit depends on value of persistTempDirectory.
253      *  
254      * @param context the context to resolve the temp directory from
255      * @throws Exception if unable to resolve the temp directory
256      */
257     public void resolveTempDirectory (WebAppContext context)
258     throws Exception
259     {
260         //If a tmp directory is already set we should use it
261         File tmpDir = context.getTempDirectory();
262         if (tmpDir != null)
263         {
264             configureTempDirectory(tmpDir, context);
265             context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
266             return;
267         }
268 
269         // No temp directory configured, try to establish one via the javax.servlet.context.tempdir.
270         File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR));
271         if (servletTmpDir != null)
272         {
273             // Use as tmpDir
274             tmpDir = servletTmpDir;
275             configureTempDirectory(tmpDir, context);
276             // Ensure Attribute has File object
277             context.setAttribute(WebAppContext.TEMPDIR,tmpDir);
278             // Set as TempDir in context.
279             context.setTempDirectory(tmpDir);
280             return;
281         }
282 
283         //We need to make a temp dir. Check if the user has set a directory to use instead
284         //of java.io.tmpdir as the parent of the dir
285         File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
286         if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
287         {
288             //Make a temp directory as a child of the given base dir
289             makeTempDirectory(baseTemp,context);
290             return;
291         }
292 
293         //Look for a directory named "work" in ${jetty.base} and
294         //treat it as parent of a new temp dir (which we will persist)
295         File jettyBase = asFile(System.getProperty("jetty.base"));
296         if (jettyBase != null)
297         {
298             File work = new File (jettyBase, "work");
299             if (work.exists() && work.isDirectory() && work.canWrite())
300             {
301                 context.setPersistTempDirectory(true);
302                 makeTempDirectory(work,context);
303                 return;
304             }
305         }
306 
307         //Make a temp directory in java.io.tmpdir
308         makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context);
309     }
310 
311     /**
312      * Given an Object, return File reference for object.
313      * Typically used to convert anonymous Object from getAttribute() calls to a File object.
314      * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null
315      * @return the File object, null if null, or null if not a File or String
316      */
317     private File asFile(Object fileattr)
318     {
319         if (fileattr == null)
320         {
321             return null;
322         }
323         if (fileattr instanceof File)
324         {
325             return (File)fileattr;
326         }
327         if (fileattr instanceof String)
328         {
329             return new File((String)fileattr);
330         }
331         return null;
332     }
333 
334 
335 
336     public void makeTempDirectory (File parent, WebAppContext context)
337             throws Exception
338     {
339         if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
340             throw new IllegalStateException("Parent for temp dir not configured correctly: "+(parent==null?"null":"writeable="+parent.canWrite()));
341 
342         //Create a name for the webapp     
343         String temp = getCanonicalNameForWebAppTmpDir(context);
344         File tmpDir = null;
345         if (context.isPersistTempDirectory())
346         {
347             //if it is to be persisted, make sure it will be the same name
348             //by not using File.createTempFile, which appends random digits
349             tmpDir = new File (parent, temp);
350         }
351         else
352         {
353             //ensure file will always be unique by appending random digits
354             tmpDir = File.createTempFile(temp, ".dir", parent);
355             //delete the file that was created
356             tmpDir.delete();
357             //and make a directory of the same name
358             tmpDir.mkdirs();
359         }
360         configureTempDirectory(tmpDir, context);
361 
362         if(LOG.isDebugEnabled())
363             LOG.debug("Set temp dir "+tmpDir);
364         context.setTempDirectory(tmpDir);
365     }
366 
367     public void configureTempDirectory (File dir, WebAppContext context)
368     {
369         if (dir == null)
370             throw new IllegalArgumentException("Null temp dir");
371 
372         //if dir exists and we don't want it persisted, delete it
373         if (dir.exists() && !context.isPersistTempDirectory())
374         {
375             if (!IO.delete(dir))
376                 throw new IllegalStateException("Failed to delete temp dir "+dir);
377         }
378 
379         //if it doesn't exist make it
380         if (!dir.exists())
381             dir.mkdirs();
382 
383         if (!context.isPersistTempDirectory())
384             dir.deleteOnExit();
385 
386         //is it useable
387         if (!dir.canWrite() || !dir.isDirectory())   
388             throw new IllegalStateException("Temp dir "+dir+" not useable: writeable="+dir.canWrite()+", dir="+dir.isDirectory());
389     }
390 
391 
392     public void unpack (WebAppContext context) throws IOException
393     {
394         Resource web_app = context.getBaseResource();
395         _preUnpackBaseResource = context.getBaseResource();
396 
397         if (web_app == null)
398         {
399             String war = context.getWar();
400             if (war!=null && war.length()>0)
401                 web_app = context.newResource(war);
402             else
403                 web_app=context.getBaseResource();
404             
405             if (web_app == null)
406                 throw new IllegalStateException("No resourceBase or war set for context");
407 
408             // Accept aliases for WAR files
409             if (web_app.isAlias())
410             {
411                 LOG.debug(web_app + " anti-aliased to " + web_app.getAlias());
412                 web_app = context.newResource(web_app.getAlias());
413             }
414 
415             if (LOG.isDebugEnabled())
416                 LOG.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()+" file="+(web_app.getFile()));
417             // Is the WAR usable directly?
418             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
419             {
420                 // No - then lets see if it can be turned into a jar URL.
421                 Resource jarWebApp = JarResource.newJarResource(web_app);
422                 if (jarWebApp.exists() && jarWebApp.isDirectory())
423                     web_app= jarWebApp;
424             }
425 
426             // If we should extract or the URL is still not usable
427             if (web_app.exists()  && (
428                     (context.isCopyWebDir() && web_app.getFile() != null && web_app.getFile().isDirectory()) ||
429                     (context.isExtractWAR() && web_app.getFile() != null && !web_app.getFile().isDirectory()) ||
430                     (context.isExtractWAR() && web_app.getFile() == null) ||
431                     !web_app.isDirectory())
432                             )
433             {
434                 // Look for sibling directory.
435                 File extractedWebAppDir = null;
436 
437                 if (war!=null)
438                 {
439                     // look for a sibling like "foo/" to a "foo.war"
440                     File warfile=Resource.newResource(war).getFile();
441                     if (warfile!=null && warfile.getName().toLowerCase(Locale.ENGLISH).endsWith(".war"))
442                     {
443                         File sibling = new File(warfile.getParent(),warfile.getName().substring(0,warfile.getName().length()-4));
444                         if (sibling.exists() && sibling.isDirectory() && sibling.canWrite())
445                             extractedWebAppDir=sibling;
446                     }
447                 }
448 
449                 if (extractedWebAppDir==null)
450                     // Then extract it if necessary to the temporary location
451                     extractedWebAppDir= new File(context.getTempDirectory(), "webapp");
452 
453                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
454                 {
455                     // Copy directory
456                     LOG.debug("Copy " + web_app + " to " + extractedWebAppDir);
457                     web_app.copyTo(extractedWebAppDir);
458                 }
459                 else
460                 {
461                     //Use a sentinel file that will exist only whilst the extraction is taking place.
462                     //This will help us detect interrupted extractions.
463                     File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
464 
465                     if (!extractedWebAppDir.exists())
466                     {
467                         //it hasn't been extracted before so extract it
468                         extractionLock.createNewFile();
469                         extractedWebAppDir.mkdir();
470                         LOG.debug("Extract " + web_app + " to " + extractedWebAppDir);
471                         Resource jar_web_app = JarResource.newJarResource(web_app);
472                         jar_web_app.copyTo(extractedWebAppDir);
473                         extractionLock.delete();
474                     }
475                     else
476                     {
477                         //only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
478                         if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
479                         {
480                             extractionLock.createNewFile();
481                             IO.delete(extractedWebAppDir);
482                             extractedWebAppDir.mkdir();
483                             LOG.debug("Extract " + web_app + " to " + extractedWebAppDir);
484                             Resource jar_web_app = JarResource.newJarResource(web_app);
485                             jar_web_app.copyTo(extractedWebAppDir);
486                             extractionLock.delete();
487                         }
488                     }
489                 }
490                 web_app = Resource.newResource(extractedWebAppDir.getCanonicalPath());
491             }
492 
493             // Now do we have something usable?
494             if (!web_app.exists() || !web_app.isDirectory())
495             {
496                 LOG.warn("Web application not found " + war);
497                 throw new java.io.FileNotFoundException(war);
498             }
499 
500             context.setBaseResource(web_app);
501 
502             if (LOG.isDebugEnabled())
503                 LOG.debug("webapp=" + web_app);
504         }
505 
506 
507         // Do we need to extract WEB-INF/lib?
508         if (context.isCopyWebInf() && !context.isCopyWebDir())
509         {
510             Resource web_inf= web_app.addPath("WEB-INF/");
511 
512             File extractedWebInfDir= new File(context.getTempDirectory(), "webinf");
513             if (extractedWebInfDir.exists())
514                 IO.delete(extractedWebInfDir);
515             extractedWebInfDir.mkdir();
516             Resource web_inf_lib = web_inf.addPath("lib/");
517             File webInfDir=new File(extractedWebInfDir,"WEB-INF");
518             webInfDir.mkdir();
519 
520             if (web_inf_lib.exists())
521             {
522                 File webInfLibDir = new File(webInfDir, "lib");
523                 if (webInfLibDir.exists())
524                     IO.delete(webInfLibDir);
525                 webInfLibDir.mkdir();
526 
527                 LOG.debug("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir);
528                 web_inf_lib.copyTo(webInfLibDir);
529             }
530 
531             Resource web_inf_classes = web_inf.addPath("classes/");
532             if (web_inf_classes.exists())
533             {
534                 File webInfClassesDir = new File(webInfDir, "classes");
535                 if (webInfClassesDir.exists())
536                     IO.delete(webInfClassesDir);
537                 webInfClassesDir.mkdir();
538                 LOG.debug("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath());
539                 web_inf_classes.copyTo(webInfClassesDir);
540             }
541 
542             web_inf=Resource.newResource(extractedWebInfDir.getCanonicalPath());
543 
544             ResourceCollection rc = new ResourceCollection(web_inf,web_app);
545 
546             if (LOG.isDebugEnabled())
547                 LOG.debug("context.resourcebase = "+rc);
548 
549             context.setBaseResource(rc);
550         }
551     }
552 
553     /**
554      * Create a canonical name for a webapp temp directory.
555      * <p>
556      * The form of the name is:
557      * 
558      * <pre>"jetty-"+host+"-"+port+"-"+resourceBase+"-_"+context+"-"+virtualhost+"-"+randomdigits+".dir"</pre>
559      *
560      *  host and port uniquely identify the server
561      *  context and virtual host uniquely identify the webapp
562      *  randomdigits ensure every tmp directory is unique
563      *  
564      * @param context the context to get the canonical name from 
565      * @return the canonical name for the webapp temp directory
566      */
567     public static String getCanonicalNameForWebAppTmpDir (WebAppContext context)
568     {
569         StringBuffer canonicalName = new StringBuffer();
570         canonicalName.append("jetty-");
571 
572         //get the host and the port from the first connector
573         Server server=context.getServer();
574         if (server!=null)
575         {
576             Connector[] connectors = context.getServer().getConnectors();
577 
578             if (connectors.length>0)
579             {
580                 //Get the host
581                 String host=null;
582                 int port=0;
583                 if (connectors!=null && (connectors[0] instanceof NetworkConnector))
584                 {
585                     NetworkConnector connector = (NetworkConnector)connectors[0];
586                     host=connector.getHost();
587                     port=connector.getLocalPort();
588                     if (port < 0)
589                         port = connector.getPort();
590                 }
591                 if (host == null)
592                     host = "0.0.0.0";
593                 canonicalName.append(host);
594 
595                 //Get the port
596                 canonicalName.append("-");
597 
598                 //if not available (eg no connectors or connector not started),
599                 //try getting one that was configured.
600                 canonicalName.append(port);
601                 canonicalName.append("-");
602             }
603         }
604 
605         //Resource  base
606         try
607         {
608             Resource resource = context.getBaseResource();
609             if (resource == null)
610             {
611                 if (context.getWar()==null || context.getWar().length()==0)
612                    throw new IllegalStateException("No resourceBase or war set for context");
613 
614                 // Set dir or WAR
615                 resource = context.newResource(context.getWar());
616             }
617 
618             String tmp = URIUtil.decodePath(resource.getURL().getPath());
619             if (tmp.endsWith("/"))
620                 tmp = tmp.substring(0, tmp.length()-1);
621             if (tmp.endsWith("!"))
622                 tmp = tmp.substring(0, tmp.length() -1);
623             //get just the last part which is the filename
624             int i = tmp.lastIndexOf("/");
625             canonicalName.append(tmp.substring(i+1, tmp.length()));
626             canonicalName.append("-");
627         }
628         catch (Exception e)
629         {
630             LOG.warn("Can't generate resourceBase as part of webapp tmp dir name: " + e);
631             LOG.debug(e);
632         }
633 
634         //Context name
635         String contextPath = context.getContextPath();
636         contextPath=contextPath.replace('/','_');
637         contextPath=contextPath.replace('\\','_');
638         canonicalName.append(contextPath);
639 
640         //Virtual host (if there is one)
641         canonicalName.append("-");
642         String[] vhosts = context.getVirtualHosts();
643         if (vhosts == null || vhosts.length <= 0)
644             canonicalName.append("any");
645         else
646             canonicalName.append(vhosts[0]);
647 
648         // sanitize
649         for (int i=0;i<canonicalName.length();i++)
650         {
651             char c=canonicalName.charAt(i);
652             if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c)<0)
653                 canonicalName.setCharAt(i,'.');
654         }
655 
656         canonicalName.append("-");
657 
658         return canonicalName.toString();
659     }
660 
661     
662     protected List<Resource> findClassDirs (WebAppContext context)
663     throws Exception
664     {
665         if (context == null)
666             return null;
667         
668         List<Resource> classDirs = new ArrayList<Resource>();
669 
670         Resource webInfClasses = findWebInfClassesDir(context);
671         if (webInfClasses != null)
672             classDirs.add(webInfClasses);
673         List<Resource> extraClassDirs = findExtraClasspathDirs(context);
674         if (extraClassDirs != null)
675             classDirs.addAll(extraClassDirs);
676         
677         return classDirs;
678     }
679     
680     
681     /**
682      * Look for jars that should be treated as if they are in WEB-INF/lib
683      * 
684      * @param context the context to find the jars in
685      * @return the list of jar resources found within context
686      * @throws Exception if unable to find the jars
687      */
688     protected List<Resource> findJars (WebAppContext context)
689     throws Exception
690     {
691         List<Resource> jarResources = new ArrayList<Resource>();
692         List<Resource> webInfLibJars = findWebInfLibJars(context);
693         if (webInfLibJars != null)
694             jarResources.addAll(webInfLibJars);
695         List<Resource> extraClasspathJars = findExtraClasspathJars(context);
696         if (extraClasspathJars != null)
697             jarResources.addAll(extraClasspathJars);
698         return jarResources;
699     }
700     
701     /**
702      * Look for jars in <code>WEB-INF/lib</code>
703      *  
704      * @param context the context to find the lib jars in
705      * @return the list of jars as {@link Resource}
706      * @throws Exception if unable to scan for lib jars
707      */
708     protected List<Resource> findWebInfLibJars(WebAppContext context)
709     throws Exception
710     {
711         Resource web_inf = context.getWebInf();
712         if (web_inf==null || !web_inf.exists())
713             return null;
714 
715         List<Resource> jarResources = new ArrayList<Resource>();
716         Resource web_inf_lib = web_inf.addPath("/lib");
717         if (web_inf_lib.exists() && web_inf_lib.isDirectory())
718         {
719             String[] files=web_inf_lib.list();
720             for (int f=0;files!=null && f<files.length;f++)
721             {
722                 try
723                 {
724                     Resource file = web_inf_lib.addPath(files[f]);
725                     String fnlc = file.getName().toLowerCase(Locale.ENGLISH);
726                     int dot = fnlc.lastIndexOf('.');
727                     String extension = (dot < 0 ? null : fnlc.substring(dot));
728                     if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
729                     {
730                         jarResources.add(file);
731                     }
732                 }
733                 catch (Exception ex)
734                 {
735                     LOG.warn(Log.EXCEPTION,ex);
736                 }
737             }
738         }
739         return jarResources;
740     }
741     
742     
743     
744     /**
745      * Get jars from WebAppContext.getExtraClasspath as resources
746      * 
747      * @param context the context to find extra classpath jars in
748      * @return the list of Resources with the extra classpath, or null if not found
749      * @throws Exception if unable to find the extra classpath jars
750      */
751     protected List<Resource>  findExtraClasspathJars(WebAppContext context)
752     throws Exception
753     { 
754         if (context == null || context.getExtraClasspath() == null)
755             return null;
756         
757         List<Resource> jarResources = new ArrayList<Resource>();
758         StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
759         while (tokenizer.hasMoreTokens())
760         {
761             Resource resource = context.newResource(tokenizer.nextToken().trim());
762             String fnlc = resource.getName().toLowerCase(Locale.ENGLISH);
763             int dot = fnlc.lastIndexOf('.');
764             String extension = (dot < 0 ? null : fnlc.substring(dot));
765             if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
766             {
767                 jarResources.add(resource);
768             }
769         }
770         
771         return jarResources;
772     }
773     
774     /**
775      * Get <code>WEB-INF/classes</code> dir
776      * 
777      * @param context the context to look for the <code>WEB-INF/classes</code> directory
778      * @return the Resource for the <code>WEB-INF/classes</code> directory
779      * @throws Exception if unable to find the <code>WEB-INF/classes</code> directory
780      */
781     protected Resource findWebInfClassesDir (WebAppContext context)
782     throws Exception
783     {
784         if (context == null)
785             return null;
786         
787         Resource web_inf = context.getWebInf();
788 
789         // Find WEB-INF/classes
790         if (web_inf != null && web_inf.isDirectory())
791         {
792             // Look for classes directory
793             Resource classes= web_inf.addPath("classes/");
794             if (classes.exists())
795                 return classes;
796         }
797         return null;
798     }
799     
800     
801     /**
802      * Get class dirs from WebAppContext.getExtraClasspath as resources
803      * 
804      * @param context the context to look for extra classpaths in
805      * @return the list of Resources to the extra classpath 
806      * @throws Exception if unable to find the extra classpaths
807      */
808     protected List<Resource>  findExtraClasspathDirs(WebAppContext context)
809     throws Exception
810     { 
811         if (context == null || context.getExtraClasspath() == null)
812             return null;
813         
814         List<Resource> dirResources = new ArrayList<Resource>();
815         StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
816         while (tokenizer.hasMoreTokens())
817         {
818             Resource resource = context.newResource(tokenizer.nextToken().trim());
819             if (resource.exists() && resource.isDirectory())
820                 dirResources.add(resource);
821         }
822         
823         return dirResources;
824     }
825     
826     
827 }