View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.view.facelets.util;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.JarURLConnection;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.net.URLDecoder;
28  import java.util.Arrays;
29  import java.util.Enumeration;
30  import java.util.HashSet;
31  import java.util.LinkedHashSet;
32  import java.util.Set;
33  import java.util.jar.JarEntry;
34  import java.util.jar.JarFile;
35  import java.util.zip.ZipEntry;
36  import java.util.zip.ZipInputStream;
37  
38  import org.apache.myfaces.shared.util.ClassUtils;
39  import java.nio.ByteBuffer;
40  import java.nio.charset.Charset;
41  import java.security.AccessController;
42  import java.security.PrivilegedAction;
43  /**
44   * @author Jacob Hookom
45   * @author Roland Huss
46   * @author Ales Justin (ales.justin@jboss.org)
47   * @version $Id$
48   */
49  public final class Classpath
50  {
51      private static final Charset UTF8 = Charset.forName("UTF-8");
52      private static final Set<String> EXCLUDED_PREFIX_SET = new HashSet<String>(Arrays.asList("rar:", "sar:"));
53      private static final Set<String> EXCLUDED_SUFFIX_SET = new HashSet<String>(Arrays.asList(".rar", ".sar"));
54  
55      private Classpath()
56      {
57      }
58  
59      public static URL[] search(String prefix, String suffix) throws IOException
60      {
61          return search(ClassUtils.getContextClassLoader(), prefix, suffix);
62      }
63  
64      public static URL[] search(ClassLoader loader, String prefix, String suffix) throws IOException
65      {
66          Set<URL> all = new LinkedHashSet<URL>();
67  
68          _searchResource(all, loader, prefix, prefix, suffix);
69          _searchResource(all, loader, prefix + "MANIFEST.MF", prefix, suffix);
70  
71          URL[] urlArray = (URL[]) all.toArray(new URL[all.size()]);
72  
73          return urlArray;
74      }
75  
76      private static void _searchResource(Set<URL> result, ClassLoader loader, String resource, String prefix,
77                                          String suffix) throws IOException
78      {
79          for (Enumeration<URL> urls = loader.getResources(resource); urls.hasMoreElements();)
80          {
81              URL url = urls.nextElement();
82              URLConnection conn = url.openConnection();
83              conn.setUseCaches(false);
84              conn.setDefaultUseCaches(false);
85  
86              JarFile jar = null;
87              try 
88              {
89                  if (conn instanceof JarURLConnection)
90                  {
91                          jar = ((JarURLConnection) conn).getJarFile();
92                  }
93                  else
94                  {
95                      jar = _getAlternativeJarFile(url);
96                  }
97      
98                  if (jar != null)
99                  {
100                     _searchJar(loader, result, jar, prefix, suffix);
101                 }
102                 else
103                 {
104                     if (!_searchDir(result, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix))
105                     {
106                         _searchFromURL(result, prefix, suffix, url);
107                     }
108                 }    
109             }
110             catch (Throwable e)
111             {
112                 // This can happen if the classloader provided us a URL that it thinks exists
113                 // but really doesn't.  In particular, if a JAR contains META-INF/MANIFEST.MF
114                 // but not META-INF/, some classloaders may incorrectly report that META-INF/
115                 // exists and we'll end up here.  Just ignore this case.
116                 
117                 continue;
118             }
119             finally 
120             {
121                 if (jar != null) 
122                 {
123                     jar.close();
124                 }
125             }
126         }
127     }
128 
129     private static boolean _searchDir(Set<URL> result, File dir, String suffix) throws IOException
130     {
131         boolean dirExists = false;
132         if (System.getSecurityManager() != null)
133         {
134             final File finalDir = dir;
135             dirExists = (Boolean) AccessController.doPrivileged(new PrivilegedAction()
136             {
137                 public Object run() 
138                 {
139                     return finalDir.exists();
140                 }
141             });
142         }  
143         else
144         {
145             dirExists = dir.exists();
146         }
147         if (dirExists && dir.isDirectory())
148         {
149             File[] dirFiles = dir.listFiles();
150             if (dirFiles != null) 
151             {
152                 for (File file : dirFiles)
153                 {
154                     String path = file.getAbsolutePath();
155                     if (file.isDirectory())
156                     {
157                         _searchDir(result, file, suffix);
158                     }
159                     else if (path.endsWith(suffix))
160                     {
161                         result.add(file.toURI().toURL());
162                     }
163                 }
164                 return true;
165             }
166         }
167 
168         return false;
169     }
170 
171     /**
172      * Search from URL. Fall back on prefix tokens if not able to read from original url param.
173      * 
174      * @param result
175      *            the result urls
176      * @param prefix
177      *            the current prefix
178      * @param suffix
179      *            the suffix to match
180      * @param url
181      *            the current url to start search
182      * @throws IOException
183      *             for any error
184      */
185     private static void _searchFromURL(Set<URL> result, String prefix, String suffix, URL url) throws IOException
186     {
187         boolean done = false;
188 
189         InputStream is = _getInputStream(url);
190         if (is != null)
191         {
192             try
193             {
194                 ZipInputStream zis;
195                 if (is instanceof ZipInputStream)
196                 {
197                     zis = (ZipInputStream) is;
198                 }
199                 else
200                 {
201                     zis = new ZipInputStream(is);
202                 }
203 
204                 try
205                 {
206                     ZipEntry entry = zis.getNextEntry();
207                     // initial entry should not be null
208                     // if we assume this is some inner jar
209                     done = entry != null;
210 
211                     while (entry != null)
212                     {
213                         String entryName = entry.getName();
214                         if (entryName.endsWith(suffix))
215                         {
216                             result.add(new URL(url.toExternalForm() + entryName));
217                         }
218 
219                         entry = zis.getNextEntry();
220                     }
221                 }
222                 finally
223                 {
224                     zis.close();
225                 }
226             }
227             catch (Exception ignore)
228             {
229             }
230         }
231 
232         if (!done && prefix.length() > 0)
233         {
234             // we add '/' at the end since join adds it as well
235             String urlString = url.toExternalForm() + "/";
236 
237             String[] split = prefix.split("/");
238 
239             prefix = _join(split, true);
240 
241             String end = _join(split, false);
242             urlString = urlString.substring(0, urlString.lastIndexOf(end));
243             if (isExcludedPrefix(urlString))
244             {
245                 // excluded URL found, ignore it
246                 return;
247             }
248             url = new URL(urlString);
249 
250             _searchFromURL(result, prefix, suffix, url);
251         }
252     }
253 
254     /**
255      * Join tokens, exlude last if param equals true.
256      * 
257      * @param tokens
258      *            the tokens
259      * @param excludeLast
260      *            do we exclude last token
261      * @return joined tokens
262      */
263     private static String _join(String[] tokens, boolean excludeLast)
264     {
265         StringBuilder join = new StringBuilder();
266         int length = tokens.length - (excludeLast ? 1 : 0);
267         for (int i = 0; i < length; i++)
268         {
269             join.append(tokens[i]).append("/");
270         }
271 
272         return join.toString();
273     }
274 
275     /**
276      * Open input stream from url. Ignore any errors.
277      * 
278      * @param url
279      *            the url to open
280      * @return input stream or null if not possible
281      */
282     private static InputStream _getInputStream(URL url)
283     {
284         try
285         {
286             return url.openStream();
287         }
288         catch (Throwable t)
289         {
290             return null;
291         }
292     }
293 
294     /**
295      * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
296      * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
297      * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
298      */
299     private static JarFile _getAlternativeJarFile(URL url) throws IOException
300     {
301         String urlFile = url.getFile();
302 
303         // Find suffix prefixed by "!/" on Weblogic
304         int wlIndex = urlFile.indexOf("!/");
305         // Find suffix prefixed by '!' on OC4J
306         int oc4jIndex = urlFile.indexOf('!');
307         // Take the first found suffix
308         int separatorIndex = wlIndex == -1 && oc4jIndex == -1 ? -1 : wlIndex < oc4jIndex ? wlIndex : oc4jIndex;
309 
310         if (separatorIndex != -1)
311         {
312             String jarFileUrl = urlFile.substring(0, separatorIndex);
313             // And trim off any "file:" prefix.
314             if (jarFileUrl.startsWith("file:"))
315             {
316                 jarFileUrl = jarFileUrl.substring("file:".length());
317             }
318             // make sure this is a valid file system path by removing escaping of white-space characters, etc. 
319             jarFileUrl = decodeFilesystemUrl(jarFileUrl);
320             if (isExcludedPrefix(jarFileUrl) || isExcludedSuffix(jarFileUrl))
321             {
322                 // excluded URL found, ignore it
323                 return null;
324             }
325             return new JarFile(jarFileUrl);
326         }
327 
328         return null;
329     }
330 
331     private static boolean isExcludedPrefix(String url)
332     {
333         return EXCLUDED_PREFIX_SET.contains(url.substring(0, 4));
334     }
335 
336     private static boolean isExcludedSuffix(String url)
337     {
338         int length = url.length();
339         return EXCLUDED_SUFFIX_SET.contains(url.substring(length - 4, length));
340     }
341 
342     private static void _searchJar(ClassLoader loader, Set<URL> result, JarFile file, String prefix, String suffix)
343             throws IOException
344     {
345         Enumeration<JarEntry> e = file.entries();
346         while (e.hasMoreElements())
347         {
348             try
349             {
350                 String name = e.nextElement().getName();
351                 if (name.startsWith(prefix) && name.endsWith(suffix))
352                 {
353                     Enumeration<URL> e2 = loader.getResources(name);
354                     while (e2.hasMoreElements())
355                     {
356                         result.add(e2.nextElement());
357                     }
358                 }
359             }
360             catch (Throwable t)
361             {
362                 // shallow
363             }
364         }
365     }
366 
367     private static String decodeFilesystemUrl(String url)
368     {
369         //borrowed from commons-io FileUtils.
370         String decoded = url;
371         if (url != null && url.indexOf('%') >= 0)
372         {
373             int n = url.length();
374             StringBuffer buffer = new StringBuffer();
375             ByteBuffer bytes = ByteBuffer.allocate(n);
376             for (int i = 0; i < n; )
377             {
378                 if (url.charAt(i) == '%')
379                 {
380                     try
381                     {
382                         do
383                         {
384                             byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
385                             bytes.put(octet);
386                             i += 3;
387                         } while (i < n && url.charAt(i) == '%');
388                         continue;
389                     }
390                     catch (RuntimeException e)
391                     {
392                         // malformed percent-encoded octet, fall through and
393                         // append characters literally
394                     }
395                     finally
396                     {
397                         if (bytes.position() > 0)
398                         {
399                             bytes.flip();
400                             buffer.append(UTF8.decode(bytes).toString());
401                             bytes.clear();
402                         }
403                     }
404                 }
405                 buffer.append(url.charAt(i++));
406             }
407             decoded = buffer.toString();
408         }
409         return decoded;
410     }
411 
412 }