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.Enumeration;
29  import java.util.LinkedHashSet;
30  import java.util.Set;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarFile;
33  import java.util.zip.ZipEntry;
34  import java.util.zip.ZipInputStream;
35  
36  import org.apache.myfaces.shared.util.ClassUtils;
37  import java.nio.ByteBuffer;
38  import java.nio.charset.Charset;
39  /**
40   * @author Jacob Hookom
41   * @author Roland Huss
42   * @author Ales Justin (ales.justin@jboss.org)
43   * @version $Id: Classpath.java 1187700 2011-10-22 12:19:37Z bommel $
44   */
45  public final class Classpath
46  {
47      private static final Charset UTF8 = Charset.forName("UTF-8");
48      private Classpath()
49      {
50      }
51  
52      public static URL[] search(String prefix, String suffix) throws IOException
53      {
54          return search(ClassUtils.getContextClassLoader(), prefix, suffix);
55      }
56  
57      public static URL[] search(ClassLoader loader, String prefix, String suffix) throws IOException
58      {
59          Set<URL> all = new LinkedHashSet<URL>();
60  
61          _searchResource(all, loader, prefix, prefix, suffix);
62          _searchResource(all, loader, prefix + "MANIFEST.MF", prefix, suffix);
63  
64          URL[] urlArray = (URL[]) all.toArray(new URL[all.size()]);
65  
66          return urlArray;
67      }
68  
69      private static void _searchResource(Set<URL> result, ClassLoader loader, String resource, String prefix,
70                                          String suffix) throws IOException
71      {
72          for (Enumeration<URL> urls = loader.getResources(resource); urls.hasMoreElements();)
73          {
74              URL url = urls.nextElement();
75              URLConnection conn = url.openConnection();
76              conn.setUseCaches(false);
77              conn.setDefaultUseCaches(false);
78  
79              JarFile jar = null;
80              if (conn instanceof JarURLConnection)
81              {
82                  try {
83                      jar = ((JarURLConnection) conn).getJarFile();
84                  }
85                  
86                  catch (Throwable e) {
87                      // This can happen if the classloader provided us a URL that it thinks exists
88                      // but really doesn't.  In particular, if a JAR contains META-INF/MANIFEST.MF
89                      // but not META-INF/, some classloaders may incorrectly report that META-INF/
90                      // exists and we'll end up here.  Just ignore this case.
91                      
92                      continue;
93                  }
94              }
95              else
96              {
97                  jar = _getAlternativeJarFile(url);
98              }
99  
100             if (jar != null)
101             {
102                 _searchJar(loader, result, jar, prefix, suffix);
103             }
104             else
105             {
106                 if (!_searchDir(result, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix))
107                 {
108                     _searchFromURL(result, prefix, suffix, url);
109                 }
110             }
111         }
112     }
113 
114     private static boolean _searchDir(Set<URL> result, File dir, String suffix) throws IOException
115     {
116         if (dir.exists() && dir.isDirectory())
117         {
118             for (File file : dir.listFiles())
119             {
120                 String path = file.getAbsolutePath();
121                 if (file.isDirectory())
122                 {
123                     _searchDir(result, file, suffix);
124                 }
125                 else if (path.endsWith(suffix))
126                 {
127                     result.add(file.toURI().toURL());
128                 }
129             }
130 
131             return true;
132         }
133 
134         return false;
135     }
136 
137     /**
138      * Search from URL. Fall back on prefix tokens if not able to read from original url param.
139      * 
140      * @param result
141      *            the result urls
142      * @param prefix
143      *            the current prefix
144      * @param suffix
145      *            the suffix to match
146      * @param url
147      *            the current url to start search
148      * @throws IOException
149      *             for any error
150      */
151     private static void _searchFromURL(Set<URL> result, String prefix, String suffix, URL url) throws IOException
152     {
153         boolean done = false;
154 
155         InputStream is = _getInputStream(url);
156         if (is != null)
157         {
158             try
159             {
160                 ZipInputStream zis;
161                 if (is instanceof ZipInputStream)
162                 {
163                     zis = (ZipInputStream) is;
164                 }
165                 else
166                 {
167                     zis = new ZipInputStream(is);
168                 }
169 
170                 try
171                 {
172                     ZipEntry entry = zis.getNextEntry();
173                     // initial entry should not be null
174                     // if we assume this is some inner jar
175                     done = entry != null;
176 
177                     while (entry != null)
178                     {
179                         String entryName = entry.getName();
180                         if (entryName.endsWith(suffix))
181                         {
182                             result.add(new URL(url.toExternalForm() + entryName));
183                         }
184 
185                         entry = zis.getNextEntry();
186                     }
187                 }
188                 finally
189                 {
190                     zis.close();
191                 }
192             }
193             catch (Exception ignore)
194             {
195             }
196         }
197 
198         if (!done && prefix.length() > 0)
199         {
200             // we add '/' at the end since join adds it as well
201             String urlString = url.toExternalForm() + "/";
202 
203             String[] split = prefix.split("/");
204 
205             prefix = _join(split, true);
206 
207             String end = _join(split, false);
208 
209             url = new URL(urlString.substring(0, urlString.lastIndexOf(end)));
210 
211             _searchFromURL(result, prefix, suffix, url);
212         }
213     }
214 
215     /**
216      * Join tokens, exlude last if param equals true.
217      * 
218      * @param tokens
219      *            the tokens
220      * @param excludeLast
221      *            do we exclude last token
222      * @return joined tokens
223      */
224     private static String _join(String[] tokens, boolean excludeLast)
225     {
226         StringBuilder join = new StringBuilder();
227         int length = tokens.length - (excludeLast ? 1 : 0);
228         for (int i = 0; i < length; i++)
229         {
230             join.append(tokens[i]).append("/");
231         }
232 
233         return join.toString();
234     }
235 
236     /**
237      * Open input stream from url. Ignore any errors.
238      * 
239      * @param url
240      *            the url to open
241      * @return input stream or null if not possible
242      */
243     private static InputStream _getInputStream(URL url)
244     {
245         try
246         {
247             return url.openStream();
248         }
249         catch (Throwable t)
250         {
251             return null;
252         }
253     }
254 
255     /**
256      * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
257      * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
258      * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
259      */
260     private static JarFile _getAlternativeJarFile(URL url) throws IOException
261     {
262         String urlFile = url.getFile();
263 
264         // Trim off any suffix - which is prefixed by "!/" on Weblogic
265         int separatorIndex = urlFile.indexOf("!/");
266 
267         // OK, didn't find that. Try the less safe "!", used on OC4J
268         if (separatorIndex == -1)
269         {
270             separatorIndex = urlFile.indexOf('!');
271         }
272 
273         if (separatorIndex != -1)
274         {
275             String jarFileUrl = urlFile.substring(0, separatorIndex);
276             // And trim off any "file:" prefix.
277             if (jarFileUrl.startsWith("file:"))
278             {
279                 jarFileUrl = jarFileUrl.substring("file:".length());
280             }
281             // make sure this is a valid file system path by removing escaping of white-space characters, etc. 
282             jarFileUrl = decodeFilesystemUrl(jarFileUrl);
283             return new JarFile(jarFileUrl);
284         }
285 
286         return null;
287     }
288 
289     private static void _searchJar(ClassLoader loader, Set<URL> result, JarFile file, String prefix, String suffix)
290             throws IOException
291     {
292         Enumeration<JarEntry> e = file.entries();
293         while (e.hasMoreElements())
294         {
295             try
296             {
297                 String name = e.nextElement().getName();
298                 if (name.startsWith(prefix) && name.endsWith(suffix))
299                 {
300                     Enumeration<URL> e2 = loader.getResources(name);
301                     while (e2.hasMoreElements())
302                     {
303                         result.add(e2.nextElement());
304                     }
305                 }
306             }
307             catch (Throwable t)
308             {
309                 // shallow
310             }
311         }
312     }
313     
314     private static String decodeFilesystemUrl(String url) {
315         //borrowed from commons-io FileUtils.
316         String decoded = url;
317         if (url != null && url.indexOf('%') >= 0) {
318             int n = url.length();
319             StringBuffer buffer = new StringBuffer();
320             ByteBuffer bytes = ByteBuffer.allocate(n);
321             for (int i = 0; i < n;) {
322                 if (url.charAt(i) == '%') {
323                     try {
324                         do {
325                             byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
326                             bytes.put(octet);
327                             i += 3;
328                         } while (i < n && url.charAt(i) == '%');
329                         continue;
330                     } catch (RuntimeException e) {
331                         // malformed percent-encoded octet, fall through and
332                         // append characters literally
333                     } finally {
334                         if (bytes.position() > 0) {
335                             bytes.flip();
336                             buffer.append(UTF8.decode(bytes).toString());
337                             bytes.clear();
338                         }
339                     }
340                 }
341                 buffer.append(url.charAt(i++));
342             }
343             decoded = buffer.toString();
344         }
345         return decoded;
346     }
347 
348 }