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.StringTokenizer; 033 import java.util.Map; 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, 082 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(String clientProperties, Locale locale, String prefix, 184 String subDir, String name, String suffix, 185 boolean reverseOrder, boolean single, boolean returnKey, 186 String key, boolean returnStrings, 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 // e.g. 1. application, 2. library or renderkit 198 for (Theme themeName : theme.getFallbackList()) { // theme loop 199 for (String resourceDirectory : tobagoConfig.getResourceDirs()) { 200 for (String browserType : browser.getFallbackList()) { // browser loop 201 for (String localeSuffix : locales) { // locale loop 202 path = makePath(resourceDirectory, 203 contentType, 204 themeName, 205 browserType, 206 subDir, 207 name, 208 localeSuffix, 209 suffix, 210 key); 211 if (LOG.isDebugEnabled()) { 212 LOG.debug("testing path: " + path); 213 } 214 if (returnStrings && resourceList.containsKey(path)) { 215 String result = 216 returnKey 217 ? prefix + path 218 : prefix + resourceList.get(path); 219 220 if (reverseOrder) { 221 matches.add(0, result); 222 } else { 223 matches.add(result); 224 } 225 226 if (single) { 227 return matches; 228 } 229 } else if (!returnStrings) { 230 try { 231 path = path.substring(1).replace('/', '.'); 232 Class clazz = Class.forName(path); 233 matches.add(clazz); 234 return matches; 235 } catch (ClassNotFoundException e) { 236 // not found 237 } 238 } 239 } 240 } 241 } 242 } 243 for (String localeSuffix : locales) { // locale loop 244 path = makePath(name, localeSuffix, suffix, key); 245 if (LOG.isDebugEnabled()) { 246 LOG.debug("testing path: " + path); 247 } 248 if (returnStrings && resourceList.containsKey(path)) { 249 String result = 250 returnKey 251 ? prefix + path 252 : prefix + resourceList.get(path); 253 254 if (reverseOrder) { 255 matches.add(0, result); 256 } else { 257 matches.add(result); 258 } 259 260 if (single) { 261 return matches; 262 } 263 } else if (!returnStrings) { 264 try { 265 path = path.substring(1).replace('/', '.'); 266 Class clazz = Class.forName(path); 267 matches.add(clazz); 268 return matches; 269 } catch (ClassNotFoundException e) { 270 // not found 271 } 272 } 273 } 274 if (matches.isEmpty()) { 275 if (!ignoreMissing) { 276 LOG.error("Path not found, and no fallback. Using empty string.\n" 277 + "resourceDirs = '" + tobagoConfig.getResourceDirs() 278 + "' contentType = '" + contentType 279 + "' theme = '" + theme 280 + "' browser = '" + browser 281 + "' subDir = '" + subDir 282 + "' name = '" + name 283 + "' suffix = '" + suffix 284 + "' key = '" + key 285 + "'"/*, new Exception()*/); 286 } 287 return null; 288 } else { 289 return matches; 290 } 291 } 292 293 private String makePath(String project, 294 String language, Theme theme, String browser, 295 String subDir, String name, String localeSuffix, String extension, 296 String key) { 297 StringBuilder searchtext = new StringBuilder(64); 298 299 searchtext.append('/'); 300 searchtext.append(project); 301 searchtext.append('/'); 302 searchtext.append(language); 303 searchtext.append('/'); 304 searchtext.append(theme.getName()); 305 searchtext.append('/'); 306 searchtext.append(browser); 307 if (subDir != null) { 308 searchtext.append('/'); 309 searchtext.append(subDir); 310 } 311 searchtext.append('/'); 312 searchtext.append(name); 313 searchtext.append(localeSuffix); 314 searchtext.append(extension); 315 if (key != null) { 316 searchtext.append('/'); 317 searchtext.append(key); 318 } 319 320 return searchtext.toString(); 321 } 322 323 private String makePath( 324 String name, String localeSuffix, String extension, String key) { 325 StringBuilder searchtext = new StringBuilder(32); 326 327 searchtext.append('/'); 328 searchtext.append(name); 329 searchtext.append(localeSuffix); 330 searchtext.append(extension); 331 if (key != null) { 332 searchtext.append('/'); 333 searchtext.append(key); 334 } 335 336 return searchtext.toString(); 337 } 338 339 public Renderer getRenderer(UIViewRoot viewRoot, String name) { 340 Renderer renderer = null; 341 342 if (name != null) { 343 CacheKey key = getCacheKey(viewRoot); 344 345 RendererCacheKey rendererKey = new RendererCacheKey(key, name); 346 renderer = rendererCache.get(rendererKey); 347 if (renderer == null) { 348 try { 349 name = getRendererClassName(name); 350 List<Class> classes = getPaths(key.getClientPropertyId(), key.getLocale(), "", TAG, name, "", 351 false, true, true, null, false, false); 352 if (classes != null && !classes.isEmpty()) { 353 Class clazz = classes.get(0); 354 renderer = (Renderer) clazz.newInstance(); 355 } else { 356 renderer = NULL_CACHE_RENDERER; 357 LOG.error("Don't find any RendererClass for " + name + ". Please check you configuration."); 358 } 359 synchronized (rendererCache) { 360 rendererCache.put(rendererKey, renderer); 361 } 362 } catch (InstantiationException e) { 363 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 364 } catch (IllegalAccessException e) { 365 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 366 } 367 if (renderer == NULL_CACHE_RENDERER) { 368 return null; 369 } 370 } 371 } 372 return renderer; 373 } 374 375 376 private String getRendererClassName(String rendererType) { 377 String name; 378 if (LOG.isDebugEnabled()) { 379 LOG.debug("rendererType = '" + rendererType + "'"); 380 } 381 if ("javax.faces.Text".equals(rendererType)) { // TODO: find a better way 382 name = RENDERER_TYPE_OUT; 383 } else { 384 name = rendererType; 385 } 386 name = name + "Renderer"; 387 if (name.startsWith("javax.faces.")) { // FIXME: this is a hotfix from jsf1.0beta to jsf1.0fr 388 LOG.warn("patching renderer from " + name); 389 name = name.substring("javax.faces.".length()); 390 LOG.warn("patching renderer to " + name); 391 } 392 return name; 393 } 394 395 public String[] getScripts(UIViewRoot viewRoot, String name) { 396 return getStrings(viewRoot, name, null); 397 } 398 399 public String[] getStyles(UIViewRoot viewRoot, String name) { 400 return getStrings(viewRoot, name, null); 401 } 402 403 private String[] getStrings(UIViewRoot viewRoot, String name, String type) { 404 String[] result = null; 405 if (name != null) { 406 int dot = name.lastIndexOf('.'); 407 if (dot == -1) { 408 dot = name.length(); 409 } 410 CacheKey key = getCacheKey(viewRoot); 411 MiscCacheKey miscKey = new MiscCacheKey(key, name); 412 result = miscCache.get(miscKey); 413 if (result == null) { 414 try { 415 List matches = getPaths(key.getClientPropertyId(), key.getLocale(), "", type, 416 name.substring(0, dot), name.substring(dot), true, false, true, null, true, false); 417 result = (String[]) matches.toArray(new String[matches.size()]); 418 synchronized (miscCache) { 419 miscCache.put(miscKey, result); 420 } 421 } catch (Exception e) { 422 LOG.error("name = '" + name + "' clientProperties = '" + key.getClientPropertyId() + "'", e); 423 } 424 } 425 } 426 return result; 427 } 428 429 public String getThemeProperty(UIViewRoot viewRoot, 430 String bundle, String propertyKey) { 431 if (bundle != null && propertyKey != null) { 432 CacheKey key = getCacheKey(viewRoot); 433 434 PropertyCacheKey propertyCacheKey = new PropertyCacheKey(key, bundle, propertyKey); 435 CachedString result = propertyCache.get(propertyCacheKey); 436 if (result == null) { 437 List properties = getPaths(key.getClientPropertyId(), key.getLocale(), "", PROPERTY, 438 bundle, "", false, true, false, propertyKey, true, true); 439 if (properties != null) { 440 result = new CachedString((String) properties.get(0)); 441 } else { 442 result = new CachedString(null); 443 } 444 synchronized (propertyCache) { 445 propertyCache.put(propertyCacheKey, result); 446 } 447 } 448 return result.getValue(); 449 } 450 return null; 451 } 452 453 public static CacheKey getRendererCacheKey(String clientPropertyId, Locale locale) { 454 return new CacheKey(clientPropertyId, locale); 455 } 456 457 458 private static final class ImageCacheKey { 459 private CacheKey cacheKey; 460 private String name; 461 private int hashCode; 462 463 private ImageCacheKey(CacheKey cacheKey, String name) { 464 this.name = name; 465 this.cacheKey = cacheKey; 466 hashCode = calcHashCode(); 467 } 468 469 public boolean equals(Object o) { 470 if (this == o) { 471 return true; 472 } 473 if (o == null || getClass() != o.getClass()) { 474 return false; 475 } 476 477 ImageCacheKey that = (ImageCacheKey) o; 478 479 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 480 481 } 482 483 private int calcHashCode() { 484 int result; 485 result = cacheKey.hashCode(); 486 result = 31 * result + name.hashCode(); 487 return result; 488 } 489 490 public int hashCode() { 491 return hashCode; 492 } 493 } 494 495 private static final class JspCacheKey { 496 private final CacheKey cacheKey; 497 private final String name; 498 private final int hashCode; 499 500 private JspCacheKey(CacheKey cacheKey, String name) { 501 this.cacheKey = cacheKey; 502 this.name = name; 503 hashCode = calcHashCode(); 504 } 505 506 public boolean equals(Object o) { 507 if (this == o) { 508 return true; 509 } 510 if (o == null || getClass() != o.getClass()) { 511 return false; 512 } 513 514 JspCacheKey that = (JspCacheKey) o; 515 516 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 517 518 } 519 520 private int calcHashCode() { 521 int result; 522 result = cacheKey.hashCode(); 523 result = 31 * result + name.hashCode(); 524 return result; 525 } 526 527 public int hashCode() { 528 return hashCode; 529 } 530 } 531 532 private static final class PropertyCacheKey { 533 private final CacheKey cacheKey; 534 private final String name; 535 private final String key; 536 private final int hashCode; 537 538 private PropertyCacheKey(CacheKey cacheKey, String name, String key) { 539 this.cacheKey = cacheKey; 540 this.name = name; 541 this.key = key; 542 hashCode = calcHashCode(); 543 } 544 545 public boolean equals(Object o) { 546 if (this == o) { 547 return true; 548 } 549 if (o == null || getClass() != o.getClass()) { 550 return false; 551 } 552 553 PropertyCacheKey that = (PropertyCacheKey) o; 554 555 return cacheKey.equals(that.cacheKey) && key.equals(that.key) && name.equals(that.name); 556 557 } 558 559 private int calcHashCode() { 560 int result; 561 result = cacheKey.hashCode(); 562 result = 31 * result + name.hashCode(); 563 result = 31 * result + key.hashCode(); 564 return result; 565 } 566 567 public int hashCode() { 568 return hashCode; 569 } 570 } 571 572 private static final class MiscCacheKey { 573 private final CacheKey cacheKey; 574 private final String name; 575 private final int hashCode; 576 577 private MiscCacheKey(CacheKey cacheKey, String name) { 578 this.cacheKey = cacheKey; 579 this.name = name; 580 hashCode = calcHashCode(); 581 } 582 583 public boolean equals(Object o) { 584 if (this == o) { 585 return true; 586 } 587 if (o == null || getClass() != o.getClass()) { 588 return false; 589 } 590 591 MiscCacheKey that = (MiscCacheKey) o; 592 593 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 594 595 } 596 597 private int calcHashCode() { 598 int result; 599 result = cacheKey.hashCode(); 600 result = 31 * result + name.hashCode(); 601 return result; 602 } 603 604 public int hashCode() { 605 return hashCode; 606 } 607 } 608 609 private static final class RendererCacheKey { 610 private final CacheKey cacheKey; 611 private final String name; 612 private final int hashCode; 613 614 private RendererCacheKey(CacheKey cacheKey, String name) { 615 this.cacheKey = cacheKey; 616 this.name = name; 617 hashCode = calcHashCode(); 618 } 619 620 public boolean equals(Object o) { 621 if (this == o) { 622 return true; 623 } 624 if (o == null || getClass() != o.getClass()) { 625 return false; 626 } 627 628 RendererCacheKey that = (RendererCacheKey) o; 629 630 return cacheKey.equals(that.cacheKey) && name.equals(that.name); 631 632 } 633 634 private int calcHashCode() { 635 int result; 636 result = cacheKey.hashCode(); 637 result = 31 * result + name.hashCode(); 638 return result; 639 } 640 641 public int hashCode() { 642 return hashCode; 643 } 644 } 645 646 public static final class CacheKey { 647 private final String clientPropertyId; 648 private final Locale locale; 649 private final int hashCode; 650 651 private CacheKey(String clientPropertyId, Locale locale) { 652 this.clientPropertyId = clientPropertyId; 653 if (locale == null) { // FIXME: should not happen, but does. 654 LOG.warn("locale == null"); 655 locale = Locale.getDefault(); 656 } 657 this.locale = locale; 658 hashCode = calcHashCode(); 659 } 660 661 public String getClientPropertyId() { 662 return clientPropertyId; 663 } 664 665 public Locale getLocale() { 666 return locale; 667 } 668 669 public boolean equals(Object o) { 670 if (this == o) { 671 return true; 672 } 673 if (o == null || getClass() != o.getClass()) { 674 return false; 675 } 676 677 CacheKey cacheKey = (CacheKey) o; 678 679 return clientPropertyId.equals(cacheKey.clientPropertyId) && locale.equals(cacheKey.locale); 680 681 } 682 683 private int calcHashCode() { 684 int result; 685 result = clientPropertyId.hashCode(); 686 result = 31 * result + locale.hashCode(); 687 return result; 688 } 689 690 public int hashCode() { 691 return hashCode; 692 } 693 } 694 695 public static final class CachedString { 696 private String value; 697 698 public CachedString(String value) { 699 this.value = value; 700 } 701 702 public String getValue() { 703 return value; 704 } 705 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 CachedString that = (CachedString) o; 715 716 if (value != null ? !value.equals(that.value) : that.value != null) { 717 return false; 718 } 719 720 return true; 721 } 722 723 public int hashCode() { 724 return (value != null ? value.hashCode() : 0); 725 } 726 } 727 } 728