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 }