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