001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.myfaces.tobago.context; 021 022 import org.apache.commons.logging.Log; 023 import org.apache.commons.logging.LogFactory; 024 import org.apache.myfaces.tobago.config.TobagoConfig; 025 import org.apache.myfaces.tobago.renderkit.RendererBase; 026 027 import javax.faces.component.UIViewRoot; 028 import javax.faces.render.Renderer; 029 import java.util.ArrayList; 030 import java.util.HashMap; 031 import java.util.List; 032 import java.util.Locale; 033 import java.util.Map; 034 import java.util.StringTokenizer; 035 import java.util.concurrent.ConcurrentHashMap; 036 037 import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT; 038 039 public class ResourceManagerImpl implements ResourceManager { 040 041 private static final Log LOG = LogFactory.getLog(ResourceManagerImpl.class); 042 private static final String PROPERTY = "property"; 043 private static final String JSP = "jsp"; 044 private static final String TAG = "tag"; 045 private static final Renderer NULL_CACHE_RENDERER = new RendererBase(); 046 047 private final HashMap<String, String> resourceList; 048 049 private final Map<RendererCacheKey, Renderer> rendererCache = 050 new ConcurrentHashMap<RendererCacheKey, Renderer>(100, 0.75f, 1); 051 private final Map<ImageCacheKey, String> imageCache = new ConcurrentHashMap<ImageCacheKey, String>(100, 0.75f, 1); 052 private final Map<JspCacheKey, String> jspCache = new ConcurrentHashMap<JspCacheKey, String>(100, 0.75f, 1); 053 private final Map<MiscCacheKey, String[]> miscCache = new ConcurrentHashMap<MiscCacheKey, String[]>(100, 0.75f, 1); 054 private final Map<PropertyCacheKey, CachedString> propertyCache = 055 new ConcurrentHashMap<PropertyCacheKey, CachedString>(100, 0.75f, 1); 056 057 private TobagoConfig tobagoConfig; 058 059 public ResourceManagerImpl(TobagoConfig tobagoConfig) { 060 resourceList = new HashMap<String, String>(); 061 this.tobagoConfig = tobagoConfig; 062 } 063 064 public void add(String resourceKey) { 065 if (LOG.isDebugEnabled()) { 066 LOG.debug("adding resourceKey = '" + resourceKey + "'"); 067 } 068 resourceList.put(resourceKey, ""); 069 } 070 071 public void add(String resourceKey, String value) { 072 if (LOG.isDebugEnabled()) { 073 LOG.debug( 074 "adding resourceKey = '" + resourceKey + "' value='" + value + "'"); 075 } 076 resourceList.put(resourceKey, value); 077 } 078 079 080 public String getImage(UIViewRoot viewRoot, String name) { 081 return getImage(viewRoot, name, false); 082 } 083 084 public String getImage(UIViewRoot viewRoot, String name, boolean ignoreMissing) { 085 String result = null; 086 if (name != null) { 087 int dot = name.lastIndexOf('.'); 088 if (dot == -1) { 089 dot = name.length(); 090 } 091 CacheKey key = getCacheKey(viewRoot); 092 093 ImageCacheKey imageKey = new ImageCacheKey(key, name); 094 095 result = imageCache.get(imageKey); 096 if (result == null) { 097 try { 098 List paths = getPaths(key.getClientPropertyId(), key.getLocale(), "", null, name.substring(0, dot), 099 name.substring(dot), false, true, true, null, true, ignoreMissing); 100 if (paths != null) { 101 result = (String) paths.get(0); 102 } else { 103 result = ""; 104 } 105 synchronized (imageCache) { 106 imageCache.put(imageKey, result); 107 } 108 } catch (Exception e) { 109 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 110 } 111 } 112 } 113 114 if (result == null || result.length() == 0) { 115 if (LOG.isDebugEnabled()) { 116 LOG.debug("Can't find image for \"" + name + "\""); 117 } 118 return null; 119 } 120 return result; 121 } 122 123 private CacheKey getCacheKey(UIViewRoot viewRoot) { 124 CacheKey key; 125 if (viewRoot instanceof org.apache.myfaces.tobago.component.UIViewRoot) { 126 key = ((org.apache.myfaces.tobago.component.UIViewRoot) viewRoot).getRendererCacheKey(); 127 } else { 128 String clientPropertyId = ClientProperties.getInstance(viewRoot).getId(); 129 Locale locale = viewRoot.getLocale(); 130 key = new CacheKey(clientPropertyId, locale); 131 } 132 return key; 133 } 134 135 public String getJsp(UIViewRoot viewRoot, String name) { 136 String result = null; 137 if (name != null) { 138 CacheKey key = getCacheKey(viewRoot); 139 140 JspCacheKey jspKey = new JspCacheKey(key, name); 141 142 result = jspCache.get(jspKey); 143 if (result == null) { 144 try { 145 result = (String) getPaths(key.getClientPropertyId(), key.getLocale(), "", 146 JSP, name, "", false, true, true, null, true, false).get(0); 147 synchronized (jspCache) { 148 jspCache.put(jspKey, result); 149 } 150 } catch (Exception e) { 151 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 152 } 153 } 154 if (result != null && result.length() == 0) { 155 return null; 156 } 157 } 158 return result; 159 } 160 161 public String getProperty( 162 UIViewRoot viewRoot, String bundle, String propertyKey) { 163 if (bundle != null && propertyKey != null) { 164 CacheKey key = getCacheKey(viewRoot); 165 166 PropertyCacheKey propertyCacheKey = new PropertyCacheKey(key, bundle, propertyKey); 167 CachedString result = propertyCache.get(propertyCacheKey); 168 if (result == null) { 169 List properties = getPaths(key.getClientPropertyId(), key.getLocale(), "", PROPERTY, bundle, 170 "", false, true, false, propertyKey, true, false); 171 if (properties != null) { 172 result = new CachedString((String) properties.get(0)); 173 } else { 174 result = new CachedString(null); 175 } 176 synchronized (propertyCache) { 177 propertyCache.put(propertyCacheKey, result); 178 } 179 } 180 return result.getValue(); 181 } 182 return null; 183 } 184 185 private List getPaths( 186 String clientProperties, Locale locale, String prefix, String subDir, String name, String suffix, 187 boolean reverseOrder, boolean single, boolean returnKey, String key, boolean returnStrings, 188 boolean ignoreMissing) { 189 List matches = new ArrayList(); 190 191 StringTokenizer tokenizer = new StringTokenizer(clientProperties, "/"); 192 String contentType = tokenizer.nextToken(); 193 Theme theme = tobagoConfig.getTheme(tokenizer.nextToken()); 194 UserAgent browser = UserAgent.getInstanceForId(tokenizer.nextToken()); 195 List<String> locales = ClientProperties.getLocaleList(locale, false); 196 197 String path; 198 199 if (tobagoConfig.isFixResourceOrder()) { 200 if (getLocalPaths(prefix, name, suffix, reverseOrder, single, returnKey, key, returnStrings, matches, locales)) { 201 return matches; 202 } 203 } 204 205 // e.g. 1. application, 2. library or renderkit 206 for (Theme themeName : theme.getFallbackList()) { // theme loop 207 for (String resourceDirectory : tobagoConfig.getResourceDirs()) { 208 for (String browserType : browser.getFallbackList()) { // browser loop 209 for (String localeSuffix : locales) { // locale loop 210 path = makePath( 211 resourceDirectory, 212 contentType, 213 themeName, 214 browserType, 215 subDir, 216 name, 217 localeSuffix, 218 suffix, 219 key); 220 if (checkPath(prefix, reverseOrder, single, returnKey, returnStrings, matches, path)) { 221 return matches; 222 } 223 } 224 } 225 } 226 } 227 228 if (!tobagoConfig.isFixResourceOrder()) { 229 if (getLocalPaths(prefix, name, suffix, reverseOrder, single, returnKey, key, returnStrings, matches, locales)) { 230 return matches; 231 } 232 } 233 234 if (matches.isEmpty()) { 235 if (!ignoreMissing) { 236 LOG.error("Path not found, and no fallback. Using empty string.\n" 237 + "resourceDirs = '" + tobagoConfig.getResourceDirs() 238 + "' contentType = '" + contentType 239 + "' theme = '" + theme 240 + "' browser = '" + browser 241 + "' subDir = '" + subDir 242 + "' name = '" + name 243 + "' suffix = '" + suffix 244 + "' key = '" + key 245 + "'"); 246 if (LOG.isDebugEnabled()) { 247 LOG.debug("Show stacktrace", new Exception()); 248 } 249 } 250 return null; 251 } else { 252 return matches; 253 } 254 } 255 256 /** 257 * @return indicates, if the search should be terminated. 258 */ 259 private boolean getLocalPaths( 260 String prefix, String name, String suffix, boolean reverseOrder, boolean single, boolean returnKey, String key, 261 boolean returnStrings, List matches, List<String> locales) { 262 String path; 263 for (String localeSuffix : locales) { // locale loop 264 path = makePath(name, localeSuffix, suffix, key); 265 if (checkPath(prefix, reverseOrder, single, returnKey, returnStrings, matches, path)) { 266 return true; 267 } 268 } 269 return false; 270 } 271 272 /** 273 * @return indicates, if the search should be terminated. 274 */ 275 private boolean checkPath( 276 String prefix, boolean reverseOrder, boolean single, boolean returnKey, boolean returnStrings, 277 List matches, String path) { 278 if (returnStrings && resourceList.containsKey(path)) { 279 String result = 280 returnKey 281 ? prefix + path 282 : prefix + resourceList.get(path); 283 284 if (reverseOrder) { 285 matches.add(0, result); 286 } else { 287 matches.add(result); 288 } 289 if (LOG.isDebugEnabled()) { 290 LOG.debug("testing path: " + path + " *"); // match 291 } 292 293 if (single) { 294 return true; 295 } 296 } else if (!returnStrings) { 297 try { 298 path = path.substring(1).replace('/', '.'); 299 Class clazz = Class.forName(path); 300 if (LOG.isDebugEnabled()) { 301 LOG.debug("testing path: " + path + " *"); // match 302 } 303 matches.add(clazz); 304 return true; 305 } catch (ClassNotFoundException e) { 306 // not found 307 if (LOG.isDebugEnabled()) { 308 LOG.debug("testing path: " + path); // no match 309 } 310 } 311 } else { 312 if (LOG.isDebugEnabled()) { 313 LOG.debug("testing path: " + path); // no match 314 } 315 } 316 return false; 317 } 318 319 private String makePath( 320 String project, String language, Theme theme, String browser, String subDir, String name, String localeSuffix, 321 String extension, String key) { 322 StringBuilder searchtext = new StringBuilder(64); 323 324 searchtext.append('/'); 325 searchtext.append(project); 326 searchtext.append('/'); 327 searchtext.append(language); 328 searchtext.append('/'); 329 searchtext.append(theme.getName()); 330 searchtext.append('/'); 331 searchtext.append(browser); 332 if (subDir != null) { 333 searchtext.append('/'); 334 searchtext.append(subDir); 335 } 336 searchtext.append('/'); 337 searchtext.append(name); 338 searchtext.append(localeSuffix); 339 searchtext.append(extension); 340 if (key != null) { 341 searchtext.append('/'); 342 searchtext.append(key); 343 } 344 345 return searchtext.toString(); 346 } 347 348 private String makePath( 349 String name, String localeSuffix, String extension, String key) { 350 StringBuilder searchtext = new StringBuilder(32); 351 352 searchtext.append('/'); 353 searchtext.append(name); 354 searchtext.append(localeSuffix); 355 searchtext.append(extension); 356 if (key != null) { 357 searchtext.append('/'); 358 searchtext.append(key); 359 } 360 361 return searchtext.toString(); 362 } 363 364 public Renderer getRenderer(UIViewRoot viewRoot, String name) { 365 Renderer renderer = null; 366 367 if (name != null) { 368 CacheKey key = getCacheKey(viewRoot); 369 370 RendererCacheKey rendererKey = new RendererCacheKey(key, name); 371 renderer = rendererCache.get(rendererKey); 372 if (renderer == null) { 373 try { 374 name = getRendererClassName(name); 375 List<Class> classes = getPaths(key.getClientPropertyId(), key.getLocale(), "", TAG, name, "", 376 false, true, true, null, false, false); 377 if (classes != null && !classes.isEmpty()) { 378 Class clazz = classes.get(0); 379 renderer = (Renderer) clazz.newInstance(); 380 } else { 381 renderer = NULL_CACHE_RENDERER; 382 LOG.error("Don't find any RendererClass for " + name + ". Please check you configuration."); 383 } 384 synchronized (rendererCache) { 385 rendererCache.put(rendererKey, renderer); 386 } 387 } catch (InstantiationException e) { 388 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 389 } catch (IllegalAccessException e) { 390 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 391 } 392 if (renderer == NULL_CACHE_RENDERER) { 393 return null; 394 } 395 } 396 } 397 return renderer; 398 } 399 400 401 private String getRendererClassName(String rendererType) { 402 String name; 403 if (LOG.isDebugEnabled()) { 404 LOG.debug("rendererType = '" + rendererType + "'"); 405 } 406 if ("javax.faces.Text".equals(rendererType)) { // TODO: find a better way 407 name = RENDERER_TYPE_OUT; 408 } else { 409 name = rendererType; 410 } 411 name = name + "Renderer"; 412 if (name.startsWith("javax.faces.")) { // FIXME: this is a hotfix from jsf1.0beta to jsf1.0fr 413 LOG.warn("patching renderer from " + name); 414 name = name.substring("javax.faces.".length()); 415 LOG.warn("patching renderer to " + name); 416 } 417 return name; 418 } 419 420 public String[] getScripts(UIViewRoot viewRoot, String name) { 421 return getStrings(viewRoot, name, null); 422 } 423 424 public String[] getStyles(UIViewRoot viewRoot, String name) { 425 return getStrings(viewRoot, name, null); 426 } 427 428 private String[] getStrings(UIViewRoot viewRoot, String name, String type) { 429 String[] result = null; 430 if (name != null) { 431 int dot = name.lastIndexOf('.'); 432 if (dot == -1) { 433 dot = name.length(); 434 } 435 CacheKey key = getCacheKey(viewRoot); 436 MiscCacheKey miscKey = new MiscCacheKey(key, name); 437 result = miscCache.get(miscKey); 438 if (result == null) { 439 try { 440 List matches = getPaths(key.getClientPropertyId(), key.getLocale(), "", type, 441 name.substring(0, dot), name.substring(dot), true, false, true, null, true, false); 442 result = (String[]) matches.toArray(new String[matches.size()]); 443 synchronized (miscCache) { 444 miscCache.put(miscKey, result); 445 } 446 } catch (Exception e) { 447 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 448 } 449 } 450 } 451 return result; 452 } 453 454 public String getThemeProperty(UIViewRoot viewRoot, String bundle, String propertyKey) { 455 if (bundle != null && propertyKey != null) { 456 CacheKey key = getCacheKey(viewRoot); 457 458 PropertyCacheKey propertyCacheKey = new PropertyCacheKey(key, bundle, propertyKey); 459 CachedString result = propertyCache.get(propertyCacheKey); 460 if (result == null) { 461 List properties = getPaths(key.getClientPropertyId(), key.getLocale(), "", PROPERTY, 462 bundle, "", false, true, false, propertyKey, true, true); 463 if (properties != null) { 464 result = new CachedString((String) properties.get(0)); 465 } else { 466 result = new CachedString(null); 467 } 468 synchronized (propertyCache) { 469 propertyCache.put(propertyCacheKey, result); 470 } 471 } 472 return result.getValue(); 473 } 474 return null; 475 } 476 477 public static CacheKey getRendererCacheKey(String clientPropertyId, Locale locale) { 478 return new CacheKey(clientPropertyId, locale); 479 } 480 481 482 private static final class ImageCacheKey { 483 private CacheKey cacheKey; 484 private String name; 485 private int hashCode; 486 487 private ImageCacheKey(CacheKey cacheKey, String name) { 488 this.name = name; 489 this.cacheKey = cacheKey; 490 hashCode = calcHashCode(); 491 } 492 493 @Override 494 public boolean equals(Object o) { 495 if (this == o) { 496 return true; 497 } 498 if (o == null || getClass() != o.getClass()) { 499 return false; 500 } 501 502 ImageCacheKey that = (ImageCacheKey) o; 503 504 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 505 } 506 507 private int calcHashCode() { 508 int result; 509 result = cacheKey.hashCode(); 510 result = 31 * result + name.hashCode(); 511 return result; 512 } 513 514 @Override 515 public int hashCode() { 516 return hashCode; 517 } 518 519 @Override 520 public String toString() { 521 return cacheKey + " + " + name; 522 } 523 } 524 525 private static final class JspCacheKey { 526 private final CacheKey cacheKey; 527 private final String name; 528 private final int hashCode; 529 530 private JspCacheKey(CacheKey cacheKey, String name) { 531 this.cacheKey = cacheKey; 532 this.name = name; 533 hashCode = calcHashCode(); 534 } 535 536 @Override 537 public boolean equals(Object o) { 538 if (this == o) { 539 return true; 540 } 541 if (o == null || getClass() != o.getClass()) { 542 return false; 543 } 544 545 JspCacheKey that = (JspCacheKey) o; 546 547 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 548 549 } 550 551 private int calcHashCode() { 552 int result; 553 result = cacheKey.hashCode(); 554 result = 31 * result + name.hashCode(); 555 return result; 556 } 557 558 @Override 559 public int hashCode() { 560 return hashCode; 561 } 562 } 563 564 private static final class PropertyCacheKey { 565 private final CacheKey cacheKey; 566 private final String name; 567 private final String key; 568 private final int hashCode; 569 570 private PropertyCacheKey(CacheKey cacheKey, String name, String key) { 571 this.cacheKey = cacheKey; 572 this.name = name; 573 this.key = key; 574 hashCode = calcHashCode(); 575 } 576 577 @Override 578 public boolean equals(Object o) { 579 if (this == o) { 580 return true; 581 } 582 if (o == null || getClass() != o.getClass()) { 583 return false; 584 } 585 586 PropertyCacheKey that = (PropertyCacheKey) o; 587 588 return cacheKey.equals(that.cacheKey) && key.equals(that.key) && name.equals(that.name); 589 590 } 591 592 private int calcHashCode() { 593 int result; 594 result = cacheKey.hashCode(); 595 result = 31 * result + name.hashCode(); 596 result = 31 * result + key.hashCode(); 597 return result; 598 } 599 600 @Override 601 public int hashCode() { 602 return hashCode; 603 } 604 } 605 606 private static final class MiscCacheKey { 607 private final CacheKey cacheKey; 608 private final String name; 609 private final int hashCode; 610 611 private MiscCacheKey(CacheKey cacheKey, String name) { 612 this.cacheKey = cacheKey; 613 this.name = name; 614 hashCode = calcHashCode(); 615 } 616 617 @Override 618 public boolean equals(Object o) { 619 if (this == o) { 620 return true; 621 } 622 if (o == null || getClass() != o.getClass()) { 623 return false; 624 } 625 626 MiscCacheKey that = (MiscCacheKey) o; 627 628 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 629 630 } 631 632 private int calcHashCode() { 633 int result; 634 result = cacheKey.hashCode(); 635 result = 31 * result + name.hashCode(); 636 return result; 637 } 638 639 @Override 640 public int hashCode() { 641 return hashCode; 642 } 643 } 644 645 private static final class RendererCacheKey { 646 private final CacheKey cacheKey; 647 private final String name; 648 private final int hashCode; 649 650 private RendererCacheKey(CacheKey cacheKey, String name) { 651 this.cacheKey = cacheKey; 652 this.name = name; 653 hashCode = calcHashCode(); 654 } 655 656 @Override 657 public boolean equals(Object o) { 658 if (this == o) { 659 return true; 660 } 661 if (o == null || getClass() != o.getClass()) { 662 return false; 663 } 664 665 RendererCacheKey that = (RendererCacheKey) o; 666 667 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 668 669 } 670 671 private int calcHashCode() { 672 int result; 673 result = cacheKey.hashCode(); 674 result = 31 * result + name.hashCode(); 675 return result; 676 } 677 678 @Override 679 public int hashCode() { 680 return hashCode; 681 } 682 } 683 684 public static final class CacheKey { 685 private final String clientPropertyId; 686 private final Locale locale; 687 private final int hashCode; 688 689 private CacheKey(String clientPropertyId, Locale locale) { 690 this.clientPropertyId = clientPropertyId; 691 if (locale == null) { // FIXME: should not happen, but does. 692 LOG.warn("locale == null"); 693 locale = Locale.getDefault(); 694 } 695 this.locale = locale; 696 hashCode = calcHashCode(); 697 } 698 699 public String getClientPropertyId() { 700 return clientPropertyId; 701 } 702 703 public Locale getLocale() { 704 return locale; 705 } 706 707 @Override 708 public boolean equals(Object o) { 709 if (this == o) { 710 return true; 711 } 712 if (o == null || getClass() != o.getClass()) { 713 return false; 714 } 715 716 CacheKey cacheKey = (CacheKey) o; 717 718 return clientPropertyId.equals(cacheKey.clientPropertyId) && locale.equals(cacheKey.locale); 719 720 } 721 722 private int calcHashCode() { 723 int result; 724 result = clientPropertyId.hashCode(); 725 result = 31 * result + locale.hashCode(); 726 return result; 727 } 728 729 @Override 730 public int hashCode() { 731 return hashCode; 732 } 733 734 @Override 735 public String toString() { 736 return clientPropertyId + " + " + locale; 737 } 738 } 739 740 public static final class CachedString { 741 private String value; 742 743 public CachedString(String value) { 744 this.value = value; 745 } 746 747 public String getValue() { 748 return value; 749 } 750 751 @Override 752 public boolean equals(Object o) { 753 if (this == o) { 754 return true; 755 } 756 if (o == null || getClass() != o.getClass()) { 757 return false; 758 } 759 760 CachedString that = (CachedString) o; 761 762 if (value != null ? !value.equals(that.value) : that.value != null) { 763 return false; 764 } 765 766 return true; 767 } 768 769 @Override 770 public int hashCode() { 771 return (value != null ? value.hashCode() : 0); 772 } 773 } 774 } 775