View Javadoc

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