View Javadoc

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