1   //========================================================================
2   //$Id: JarScanner.java 3736 2008-10-04 22:19:26Z gregw $
3   //Copyright 2008 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  
17  package org.mortbay.jetty.webapp;
18  
19  import java.io.InputStream;
20  import java.net.URL;
21  import java.net.URLClassLoader;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.jar.JarEntry;
25  import java.util.jar.JarInputStream;
26  import java.util.regex.Pattern;
27  
28  import org.mortbay.log.Log;
29  import org.mortbay.resource.Resource;
30  
31  /**
32   * JarScannerConfiguration
33   *
34   * Abstract base class for configurations that want to scan jars in
35   * WEB-INF/lib and the classloader hierarchy.
36   * 
37   * Jar name matching based on regexp patterns is provided.
38   * 
39   * Subclasses should implement the processEntry(URL jarUrl, JarEntry entry)
40   * method to handle entries in jar files whose names match the supplied 
41   * pattern.
42   */
43  public abstract class JarScanner
44  {
45  
46      public abstract void processEntry (URL jarUrl, JarEntry entry);
47      
48      
49      /**
50       * Find jar names from the classloader matching a pattern.
51       * 
52       * If the pattern is null and isNullInclusive is true, then
53       * all jar names in the classloader will match.
54       * 
55       * A pattern is a set of acceptable jar names. Each acceptable
56       * jar name is a regex. Each regex can be separated by either a
57       * "," or a "|". If you use a "|" this or's together the jar
58       * name patterns. This means that ordering of the matches is
59       * unimportant to you. If instead, you want to match particular
60       * jar names, and you want to match them in order, you should
61       * separate the regexs with "," instead. 
62       * 
63       * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
64       * Will iterate over the jar names in the classloader and match
65       * in any order.
66       * 
67       * Eg "aaa-*\\.jar,bbb-.*\\.jar"
68       * Will iterate over the jar names in the classloader, matching
69       * all those starting with "aaa-" first, then "bbb-".
70       * 
71       * If visitParent is true, then the pattern is applied to the
72       * parent loader hierarchy. If false, it is only applied to the
73       * classloader passed in.
74       * 
75       * @param pattern
76       * @param loader
77       * @param isNullInclusive
78       * @param visitParent
79       * @throws Exception
80       */
81      public void scan (Pattern pattern, ClassLoader loader, boolean isNullInclusive, boolean visitParent)
82      throws Exception
83      {
84          String[] patterns = (pattern==null?null:pattern.pattern().split(","));
85  
86          List<Pattern> subPatterns = new ArrayList<Pattern>();
87          for (int i=0; patterns!=null && i<patterns.length;i++)
88              subPatterns.add(Pattern.compile(patterns[i]));
89          if (subPatterns.isEmpty())
90              subPatterns.add(pattern);
91          
92          
93          while (loader!=null)
94          {
95              if (loader instanceof URLClassLoader)
96              {
97                  URL[] urls = ((URLClassLoader)loader).getURLs();
98  
99                  if (urls!=null)
100                 {
101                     if (subPatterns.isEmpty())
102                     {
103                         processJars(null, urls, isNullInclusive);
104                     }
105                     else
106                     {
107                         //for each subpattern, iterate over all the urls, processing those that match
108                         for (Pattern p : subPatterns)
109                         {
110                            processJars(p, urls, isNullInclusive);
111                         }
112                     }
113                 }
114             }     
115             if (visitParent)
116                 loader=loader.getParent();
117             else
118                 loader = null;
119         }  
120     }
121     
122     
123     
124     public void processJars (Pattern pattern, URL[] urls, boolean isNullInclusive)
125     throws Exception
126     {
127         for (int i=0; i<urls.length;i++)
128         {
129             if (urls[i].toString().toLowerCase().endsWith(".jar"))
130             {
131                 String jar = urls[i].toString();
132                 int slash=jar.lastIndexOf('/');
133                 jar=jar.substring(slash+1);
134                 
135                 if ((pattern == null && isNullInclusive)
136                     ||
137                     (pattern!=null && pattern.matcher(jar).matches()))
138                 {
139                     processJar(urls[i]);
140                 }
141             }
142         }
143     }
144     
145     public void processJar (URL url)
146     throws Exception
147     {
148         Log.debug("Search of {}",url);
149         
150         InputStream in = Resource.newResource(url).getInputStream();
151         if (in==null)
152             return;
153 
154         JarInputStream jar_in = new JarInputStream(in);
155         try
156         { 
157             JarEntry entry = jar_in.getNextJarEntry();
158             while (entry!=null)
159             {
160                 processEntry(url, entry);
161                 entry = jar_in.getNextJarEntry();
162             }
163         }
164         finally
165         {
166             jar_in.close();
167         }   
168     }
169 }