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: Classpath.java 1368953 2012-08-03 13:43:38Z lu4242 $
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              if (conn instanceof JarURLConnection)
88              {
89                  try
90                  {
91                      jar = ((JarURLConnection) conn).getJarFile();
92                  }
93                  
94                  catch (Throwable e)
95                  {
96                      // This can happen if the classloader provided us a URL that it thinks exists
97                      // but really doesn't.  In particular, if a JAR contains META-INF/MANIFEST.MF
98                      // but not META-INF/, some classloaders may incorrectly report that META-INF/
99                      // exists and we'll end up here.  Just ignore this case.
100                     
101                     continue;
102                 }
103             }
104             else
105             {
106                 jar = _getAlternativeJarFile(url);
107             }
108 
109             if (jar != null)
110             {
111                 _searchJar(loader, result, jar, prefix, suffix);
112             }
113             else
114             {
115                 if (!_searchDir(result, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix))
116                 {
117                     _searchFromURL(result, prefix, suffix, url);
118                 }
119             }
120         }
121     }
122 
123     private static boolean _searchDir(Set<URL> result, File dir, String suffix) throws IOException
124     {
125         boolean dirExists = false;
126         if (System.getSecurityManager() != null)
127         {
128             final File finalDir = dir;
129             dirExists = (Boolean) AccessController.doPrivileged(new PrivilegedAction()
130             {
131                 public Object run() 
132                 {
133                     return finalDir.exists();
134                 }
135             });
136         }  
137         else
138         {
139             dirExists = dir.exists();
140         }
141         if (dirExists && dir.isDirectory())
142         {
143             for (File file : dir.listFiles())
144             {
145                 String path = file.getAbsolutePath();
146                 if (file.isDirectory())
147                 {
148                     _searchDir(result, file, suffix);
149                 }
150                 else if (path.endsWith(suffix))
151                 {
152                     result.add(file.toURI().toURL());
153                 }
154             }
155 
156             return true;
157         }
158 
159         return false;
160     }
161 
162     /**
163      * Search from URL. Fall back on prefix tokens if not able to read from original url param.
164      * 
165      * @param result
166      *            the result urls
167      * @param prefix
168      *            the current prefix
169      * @param suffix
170      *            the suffix to match
171      * @param url
172      *            the current url to start search
173      * @throws IOException
174      *             for any error
175      */
176     private static void _searchFromURL(Set<URL> result, String prefix, String suffix, URL url) throws IOException
177     {
178         boolean done = false;
179 
180         InputStream is = _getInputStream(url);
181         if (is != null)
182         {
183             try
184             {
185                 ZipInputStream zis;
186                 if (is instanceof ZipInputStream)
187                 {
188                     zis = (ZipInputStream) is;
189                 }
190                 else
191                 {
192                     zis = new ZipInputStream(is);
193                 }
194 
195                 try
196                 {
197                     ZipEntry entry = zis.getNextEntry();
198                     // initial entry should not be null
199                     // if we assume this is some inner jar
200                     done = entry != null;
201 
202                     while (entry != null)
203                     {
204                         String entryName = entry.getName();
205                         if (entryName.endsWith(suffix))
206                         {
207                             result.add(new URL(url.toExternalForm() + entryName));
208                         }
209 
210                         entry = zis.getNextEntry();
211                     }
212                 }
213                 finally
214                 {
215                     zis.close();
216                 }
217             }
218             catch (Exception ignore)
219             {
220             }
221         }
222 
223         if (!done && prefix.length() > 0)
224         {
225             // we add '/' at the end since join adds it as well
226             String urlString = url.toExternalForm() + "/";
227 
228             String[] split = prefix.split("/");
229 
230             prefix = _join(split, true);
231 
232             String end = _join(split, false);
233             urlString = urlString.substring(0, urlString.lastIndexOf(end));
234             if (isExcludedPrefix(urlString))
235             {
236                 // excluded URL found, ignore it
237                 return;
238             }
239             url = new URL(urlString);
240 
241             _searchFromURL(result, prefix, suffix, url);
242         }
243     }
244 
245     /**
246      * Join tokens, exlude last if param equals true.
247      * 
248      * @param tokens
249      *            the tokens
250      * @param excludeLast
251      *            do we exclude last token
252      * @return joined tokens
253      */
254     private static String _join(String[] tokens, boolean excludeLast)
255     {
256         StringBuilder join = new StringBuilder();
257         int length = tokens.length - (excludeLast ? 1 : 0);
258         for (int i = 0; i < length; i++)
259         {
260             join.append(tokens[i]).append("/");
261         }
262 
263         return join.toString();
264     }
265 
266     /**
267      * Open input stream from url. Ignore any errors.
268      * 
269      * @param url
270      *            the url to open
271      * @return input stream or null if not possible
272      */
273     private static InputStream _getInputStream(URL url)
274     {
275         try
276         {
277             return url.openStream();
278         }
279         catch (Throwable t)
280         {
281             return null;
282         }
283     }
284 
285     /**
286      * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
287      * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
288      * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
289      */
290     private static JarFile _getAlternativeJarFile(URL url) throws IOException
291     {
292         String urlFile = url.getFile();
293 
294         // Find suffix prefixed by "!/" on Weblogic
295         int wlIndex = urlFile.indexOf("!/");
296         // Find suffix prefixed by '!' on OC4J
297         int oc4jIndex = urlFile.indexOf('!');
298         // Take the first found suffix
299         int separatorIndex = wlIndex == -1 && oc4jIndex == -1 ? -1 : wlIndex < oc4jIndex ? wlIndex : oc4jIndex;
300 
301         if (separatorIndex != -1)
302         {
303             String jarFileUrl = urlFile.substring(0, separatorIndex);
304             // And trim off any "file:" prefix.
305             if (jarFileUrl.startsWith("file:"))
306             {
307                 jarFileUrl = jarFileUrl.substring("file:".length());
308             }
309             // make sure this is a valid file system path by removing escaping of white-space characters, etc. 
310             jarFileUrl = decodeFilesystemUrl(jarFileUrl);
311             if (isExcludedPrefix(jarFileUrl) || isExcludedSuffix(jarFileUrl))
312             {
313                 // excluded URL found, ignore it
314                 return null;
315             }
316             return new JarFile(jarFileUrl);
317         }
318 
319         return null;
320     }
321 
322     private static boolean isExcludedPrefix(String url)
323     {
324         return EXCLUDED_PREFIX_SET.contains(url.substring(0, 4));
325     }
326 
327     private static boolean isExcludedSuffix(String url)
328     {
329         int length = url.length();
330         return EXCLUDED_SUFFIX_SET.contains(url.substring(length - 4, length));
331     }
332 
333     private static void _searchJar(ClassLoader loader, Set<URL> result, JarFile file, String prefix, String suffix)
334             throws IOException
335     {
336         Enumeration<JarEntry> e = file.entries();
337         while (e.hasMoreElements())
338         {
339             try
340             {
341                 String name = e.nextElement().getName();
342                 if (name.startsWith(prefix) && name.endsWith(suffix))
343                 {
344                     Enumeration<URL> e2 = loader.getResources(name);
345                     while (e2.hasMoreElements())
346                     {
347                         result.add(e2.nextElement());
348                     }
349                 }
350             }
351             catch (Throwable t)
352             {
353                 // shallow
354             }
355         }
356     }
357 
358     private static String decodeFilesystemUrl(String url)
359     {
360         //borrowed from commons-io FileUtils.
361         String decoded = url;
362         if (url != null && url.indexOf('%') >= 0)
363         {
364             int n = url.length();
365             StringBuffer buffer = new StringBuffer();
366             ByteBuffer bytes = ByteBuffer.allocate(n);
367             for (int i = 0; i < n; )
368             {
369                 if (url.charAt(i) == '%')
370                 {
371                     try
372                     {
373                         do
374                         {
375                             byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
376                             bytes.put(octet);
377                             i += 3;
378                         } while (i < n && url.charAt(i) == '%');
379                         continue;
380                     }
381                     catch (RuntimeException e)
382                     {
383                         // malformed percent-encoded octet, fall through and
384                         // append characters literally
385                     }
386                     finally
387                     {
388                         if (bytes.position() > 0)
389                         {
390                             bytes.flip();
391                             buffer.append(UTF8.decode(bytes).toString());
392                             bytes.clear();
393                         }
394                     }
395                 }
396                 buffer.append(url.charAt(i++));
397             }
398             decoded = buffer.toString();
399         }
400         return decoded;
401     }
402 
403 }