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.config.util;
20  
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Enumeration;
27  import java.util.Set;
28  import java.util.Stack;
29  import java.util.TreeSet;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarFile;
32  import javax.faces.context.ExternalContext;
33  import org.apache.myfaces.shared.util.StringUtils;
34  
35  /**
36   * Utility methods to use in Google Application Engine (GAE)
37   * 
38   * @author Leonardo Uribe
39   */
40  public class GAEUtils
41  {
42  
43      public static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";
44      
45  
46      /**
47       * Look in all jars located inside /WEB-INF/lib/ folder for files that has
48       * some specified prefix and suffix. It is a simplification that can be done
49       * in GAE, because no JSF libraries are outside /WEB-INF/lib
50       *
51       * @param context
52       * @param classloader
53       * @param prefix
54       * @param suffix
55       * @return
56       * @throws IOException
57       */
58      public static Collection<URL> searchInWebLib(
59              ExternalContext context, ClassLoader classloader, String filter, 
60              String prefix, String suffix) throws IOException
61      {
62          if (!filter.equals("none"))
63          {
64              String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
65              Set<URL> urlSet = null;
66              Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
67              if (paths != null)
68              {
69                  for (Object pathObject : paths)
70                  {
71                      String path = (String) pathObject;
72                      if (path.endsWith(".jar") && wildcardMatch(path, jarFilesToScan, WEB_LIB_PREFIX))
73                      {
74                          // GAE does not use WAR format, so the app is just uncompressed in a directory
75                          // What we need here is just take the path of the file, and open the file as a
76                          // jar file. Then, if the jar should be scanned, try to find the required file.
77                          URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/");
78                          JarFile jarFile = JarUtils.getJarFile(jarUrl);
79  
80                          Enumeration<JarEntry> entries = jarFile.entries();
81                          while (entries.hasMoreElements())
82                          {
83                              JarEntry entry = entries.nextElement();
84                              if (entry.isDirectory())
85                              {
86                                  continue; // This is a directory
87                              }
88                              String name = entry.getName();
89                              if (!name.startsWith(prefix))
90                              {
91                                  continue; // Attribute files
92                              }
93                              if (name.endsWith(suffix))
94                              {
95                                  // Get it from classloader, because no URL can be
96                                  // derived from JarEntry
97                                  Enumeration<URL> alternateFacesConfigs = classloader.getResources(name);
98                                  while (alternateFacesConfigs.hasMoreElements())
99                                  {
100                                     if (urlSet == null)
101                                     {
102                                         urlSet = new TreeSet<URL>();
103                                     }
104                                     urlSet.add(alternateFacesConfigs.nextElement());
105                                 }
106                             }
107                         }
108                     }
109                 }
110             }
111             return urlSet;
112         }
113         return null;
114     }
115 
116     public static boolean wildcardMatch(String filename, String[] wildcardMatchers, String prefix)
117     {
118         for (String matcher : wildcardMatchers)
119         {
120             if (wildcardMatch(filename, prefix + matcher))
121             {
122                 return true;
123             }
124         }
125         return false;
126     }
127     
128     // NOTE: CODE TAKEN FROM COMMONS-IO AND REFACTORED TO USE INSIDE GAE
129     //-----------------------------------------------------------------------
130     /**
131      * Checks a filename to see if it matches the specified wildcard matcher,
132      * always testing case-sensitive. <p> The wildcard matcher uses the
133      * characters '?' and '*' to represent a single or multiple (zero or more)
134      * wildcard characters. This is the same as often found on Dos/Unix command
135      * lines. The check is case-sensitive always.
136      * <pre>
137      * wildcardMatch("c.txt", "*.txt")      --> true
138      * wildcardMatch("c.txt", "*.jpg")      --> false
139      * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
140      * wildcardMatch("c.txt", "*.???")      --> true
141      * wildcardMatch("c.txt", "*.????")     --> false
142      * </pre> N.B. the sequence "*?" does not work properly at present in match
143      * strings.
144      *
145      * @param filename the filename to match on
146      * @param wildcardMatcher the wildcard string to match against
147      * @return true if the filename matches the wilcard string
148      * @see IOCase#SENSITIVE
149      */
150     static boolean wildcardMatch(String filename, String wildcardMatcher)
151     {
152         return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);
153     }
154 
155     /**
156      * Checks a filename to see if it matches the specified wildcard matcher
157      * using the case rules of the system. <p> The wildcard matcher uses the
158      * characters '?' and '*' to represent a single or multiple (zero or more)
159      * wildcard characters. This is the same as often found on Dos/Unix command
160      * lines. The check is case-sensitive on Unix and case-insensitive on
161      * Windows.
162      * <pre>
163      * wildcardMatch("c.txt", "*.txt")      --> true
164      * wildcardMatch("c.txt", "*.jpg")      --> false
165      * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
166      * wildcardMatch("c.txt", "*.???")      --> true
167      * wildcardMatch("c.txt", "*.????")     --> false
168      * </pre> N.B. the sequence "*?" does not work properly at present in match
169      * strings.
170      *
171      * @param filename the filename to match on
172      * @param wildcardMatcher the wildcard string to match against
173      * @return true if the filename matches the wilcard string
174      * @see IOCase#SYSTEM
175      */
176     static boolean wildcardMatchOnSystem(String filename, String wildcardMatcher)
177     {
178         //return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM);
179         return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);
180     }
181 
182     /**
183      * Checks a filename to see if it matches the specified wildcard matcher
184      * allowing control over case-sensitivity. <p> The wildcard matcher uses the
185      * characters '?' and '*' to represent a single or multiple (zero or more)
186      * wildcard characters. N.B. the sequence "*?" does not work properly at
187      * present in match strings.
188      *
189      * @param filename the filename to match on
190      * @param wildcardMatcher the wildcard string to match against
191      * @param caseSensitivity what case sensitivity rule to use, null means
192      * case-sensitive
193      * @return true if the filename matches the wilcard string
194      * @since 1.3
195      */
196     static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity)
197     {
198         if (filename == null && wildcardMatcher == null)
199         {
200             return true;
201         }
202         if (filename == null || wildcardMatcher == null)
203         {
204             return false;
205         }
206         if (caseSensitivity == null)
207         {
208             caseSensitivity = IOCase.SENSITIVE;
209         }
210         String[] wcs = splitOnTokens(wildcardMatcher);
211         boolean anyChars = false;
212         int textIdx = 0;
213         int wcsIdx = 0;
214         Stack<int[]> backtrack = new Stack<int[]>();
215 
216         // loop around a backtrack stack, to handle complex * matching
217         do
218         {
219             if (backtrack.size() > 0)
220             {
221                 int[] array = backtrack.pop();
222                 wcsIdx = array[0];
223                 textIdx = array[1];
224                 anyChars = true;
225             }
226 
227             // loop whilst tokens and text left to process
228             while (wcsIdx < wcs.length)
229             {
230 
231                 if (wcs[wcsIdx].equals("?"))
232                 {
233                     // ? so move to next text char
234                     textIdx++;
235                     if (textIdx > filename.length())
236                     {
237                         break;
238                     }
239                     anyChars = false;
240 
241                 }
242                 else if (wcs[wcsIdx].equals("*"))
243                 {
244                     // set any chars status
245                     anyChars = true;
246                     if (wcsIdx == wcs.length - 1)
247                     {
248                         textIdx = filename.length();
249                     }
250 
251                 }
252                 else
253                 {
254                     // matching text token
255                     if (anyChars)
256                     {
257                         // any chars then try to locate text token
258                         textIdx = caseSensitivity.checkIndexOf(filename, textIdx, wcs[wcsIdx]);
259                         if (textIdx == -1)
260                         {
261                             // token not found
262                             break;
263                         }
264                         int repeat = caseSensitivity.checkIndexOf(filename, textIdx + 1, wcs[wcsIdx]);
265                         if (repeat >= 0)
266                         {
267                             backtrack.push(new int[]
268                                     {
269                                         wcsIdx, repeat
270                                     });
271                         }
272                     }
273                     else
274                     {
275                         // matching from current position
276                         if (!caseSensitivity.checkRegionMatches(filename, textIdx, wcs[wcsIdx]))
277                         {
278                             // couldnt match token
279                             break;
280                         }
281                     }
282 
283                     // matched text token, move text index to end of matched token
284                     textIdx += wcs[wcsIdx].length();
285                     anyChars = false;
286                 }
287 
288                 wcsIdx++;
289             }
290 
291             // full match
292             if (wcsIdx == wcs.length && textIdx == filename.length())
293             {
294                 return true;
295             }
296 
297         } while (backtrack.size() > 0);
298 
299         return false;
300     }
301 
302     /**
303      * Splits a string into a number of tokens. The text is split by '?' and
304      * '*'. Where multiple '*' occur consecutively they are collapsed into a
305      * single '*'.
306      *
307      * @param text the text to split
308      * @return the array of tokens, never null
309      */
310     static String[] splitOnTokens(String text)
311     {
312         // used by wildcardMatch
313         // package level so a unit test may run on this
314 
315         if (text.indexOf('?') == -1 && text.indexOf('*') == -1)
316         {
317             return new String[]
318                     {
319                         text
320                     };
321         }
322 
323         char[] array = text.toCharArray();
324         ArrayList<String> list = new ArrayList<String>();
325         StringBuilder buffer = new StringBuilder();
326         for (int i = 0; i < array.length; i++)
327         {
328             if (array[i] == '?' || array[i] == '*')
329             {
330                 if (buffer.length() != 0)
331                 {
332                     list.add(buffer.toString());
333                     buffer.setLength(0);
334                 }
335                 if (array[i] == '?')
336                 {
337                     list.add("?");
338                 }
339                 else if (list.isEmpty()
340                         || i > 0 && list.get(list.size() - 1).equals("*") == false)
341                 {
342                     list.add("*");
343                 }
344             }
345             else
346             {
347                 buffer.append(array[i]);
348             }
349         }
350         if (buffer.length() != 0)
351         {
352             list.add(buffer.toString());
353         }
354 
355         return list.toArray(new String[list.size()]);
356     }
357 
358     final static class IOCase implements Serializable
359     {
360 
361         /**
362          * The constant for case sensitive regardless of operating system.
363          */
364         public static final IOCase SENSITIVE = new IOCase("Sensitive", true);
365         /**
366          * The constant for case insensitive regardless of operating system.
367          */
368         public static final IOCase INSENSITIVE = new IOCase("Insensitive", false);
369         /**
370          * The constant for case sensitivity determined by the current operating
371          * system. Windows is case-insensitive when comparing filenames, Unix is
372          * case-sensitive. <p> <strong>Note:</strong> This only caters for
373          * Windows and Unix. Other operating systems (e.g. OSX and OpenVMS) are
374          * treated as case sensitive if they use the Unix file separator and
375          * case-insensitive if they use the Windows file separator (see {@link java.io.File#separatorChar}).
376          * <p> If you derialize this constant of Windows, and deserialize on
377          * Unix, or vice versa, then the value of the case-sensitivity flag will
378          * change.
379          */
380         //public static final IOCase SYSTEM = new IOCase("System", !FilenameUtils.isSystemWindows());
381         /**
382          * Serialization version.
383          */
384         private static final long serialVersionUID = -6343169151696340687L;
385         /**
386          * The enumeration name.
387          */
388         private final String name;
389         /**
390          * The sensitivity flag.
391          */
392         private final transient boolean sensitive;
393 
394         //-----------------------------------------------------------------------
395         /**
396          * Factory method to create an IOCase from a name.
397          *
398          * @param name the name to find
399          * @return the IOCase object
400          * @throws IllegalArgumentException if the name is invalid
401          */
402         public static IOCase forName(String name)
403         {
404             if (IOCase.SENSITIVE.name.equals(name))
405             {
406                 return IOCase.SENSITIVE;
407             }
408             if (IOCase.INSENSITIVE.name.equals(name))
409             {
410                 return IOCase.INSENSITIVE;
411             }
412             //if (IOCase.SYSTEM.name.equals(name)){
413             //    return IOCase.SYSTEM;
414             //}
415             throw new IllegalArgumentException("Invalid IOCase name: " + name);
416         }
417 
418         //-----------------------------------------------------------------------
419         /**
420          * Private constructor.
421          *
422          * @param name the name
423          * @param sensitive the sensitivity
424          */
425         private IOCase(String name, boolean sensitive)
426         {
427             this.name = name;
428             this.sensitive = sensitive;
429         }
430 
431         /**
432          * Replaces the enumeration from the stream with a real one. This
433          * ensures that the correct flag is set for SYSTEM.
434          *
435          * @return the resolved object
436          */
437         private Object readResolve()
438         {
439             return forName(name);
440         }
441 
442         //-----------------------------------------------------------------------
443         /**
444          * Gets the name of the constant.
445          *
446          * @return the name of the constant
447          */
448         public String getName()
449         {
450             return name;
451         }
452 
453         /**
454          * Does the object represent case sensitive comparison.
455          *
456          * @return true if case sensitive
457          */
458         public boolean isCaseSensitive()
459         {
460             return sensitive;
461         }
462 
463         //-----------------------------------------------------------------------
464         /**
465          * Compares two strings using the case-sensitivity rule. <p> This method
466          * mimics {@link String#compareTo} but takes case-sensitivity into
467          * account.
468          *
469          * @param str1 the first string to compare, not null
470          * @param str2 the second string to compare, not null
471          * @return true if equal using the case rules
472          * @throws NullPointerException if either string is null
473          */
474         public int checkCompareTo(String str1, String str2)
475         {
476             if (str1 == null || str2 == null)
477             {
478                 throw new NullPointerException("The strings must not be null");
479             }
480             return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
481         }
482 
483         /**
484          * Compares two strings using the case-sensitivity rule. <p> This method
485          * mimics {@link String#equals} but takes case-sensitivity into account.
486          *
487          * @param str1 the first string to compare, not null
488          * @param str2 the second string to compare, not null
489          * @return true if equal using the case rules
490          * @throws NullPointerException if either string is null
491          */
492         public boolean checkEquals(String str1, String str2)
493         {
494             if (str1 == null || str2 == null)
495             {
496                 throw new NullPointerException("The strings must not be null");
497             }
498             return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
499         }
500 
501         /**
502          * Checks if one string starts with another using the case-sensitivity
503          * rule. <p> This method mimics {@link String#startsWith(String)} but
504          * takes case-sensitivity into account.
505          *
506          * @param str the string to check, not null
507          * @param start the start to compare against, not null
508          * @return true if equal using the case rules
509          * @throws NullPointerException if either string is null
510          */
511         public boolean checkStartsWith(String str, String start)
512         {
513             return str.regionMatches(!sensitive, 0, start, 0, start.length());
514         }
515 
516         /**
517          * Checks if one string ends with another using the case-sensitivity
518          * rule. <p> This method mimics {@link String#endsWith} but takes
519          * case-sensitivity into account.
520          *
521          * @param str the string to check, not null
522          * @param end the end to compare against, not null
523          * @return true if equal using the case rules
524          * @throws NullPointerException if either string is null
525          */
526         public boolean checkEndsWith(String str, String end)
527         {
528             int endLen = end.length();
529             return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
530         }
531 
532         /**
533          * Checks if one string contains another starting at a specific index
534          * using the case-sensitivity rule. <p> This method mimics parts of {@link String#indexOf(String, int)}
535          * but takes case-sensitivity into account.
536          *
537          * @param str the string to check, not null
538          * @param strStartIndex the index to start at in str
539          * @param search the start to search for, not null
540          * @return the first index of the search String, -1 if no match or {@code null}
541          * string input
542          * @throws NullPointerException if either string is null
543          * @since 2.0
544          */
545         public int checkIndexOf(String str, int strStartIndex, String search)
546         {
547             int endIndex = str.length() - search.length();
548             if (endIndex >= strStartIndex)
549             {
550                 for (int i = strStartIndex; i <= endIndex; i++)
551                 {
552                     if (checkRegionMatches(str, i, search))
553                     {
554                         return i;
555                     }
556                 }
557             }
558             return -1;
559         }
560 
561         /**
562          * Checks if one string contains another at a specific index using the
563          * case-sensitivity rule. <p> This method mimics parts of {@link 
564          * String#regionMatches(boolean, int, String, int, int)}
565          * but takes case-sensitivity into account.
566          *
567          * @param str the string to check, not null
568          * @param strStartIndex the index to start at in str
569          * @param search the start to search for, not null
570          * @return true if equal using the case rules
571          * @throws NullPointerException if either string is null
572          */
573         public boolean checkRegionMatches(String str, int strStartIndex, String search)
574         {
575             return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
576         }
577 
578         //-----------------------------------------------------------------------
579         /**
580          * Gets a string describing the sensitivity.
581          *
582          * @return a string describing the sensitivity
583          */
584         @Override
585         public String toString()
586         {
587             return name;
588         }
589     }
590 }