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