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