View Javadoc

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