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.myfaces.tobago.application.ProjectStage; 021 import org.apache.myfaces.tobago.component.RendererTypes; 022 import org.apache.myfaces.tobago.config.Configurable; 023 import org.apache.myfaces.tobago.config.TobagoConfig; 024 import org.apache.myfaces.tobago.internal.context.ClientPropertiesKey; 025 import org.apache.myfaces.tobago.internal.context.ImageCacheKey; 026 import org.apache.myfaces.tobago.internal.context.JspCacheKey; 027 import org.apache.myfaces.tobago.internal.context.MeasureValue; 028 import org.apache.myfaces.tobago.internal.context.MiscCacheKey; 029 import org.apache.myfaces.tobago.internal.context.PropertyCacheKey; 030 import org.apache.myfaces.tobago.internal.context.RendererCacheKey; 031 import org.apache.myfaces.tobago.internal.context.StringValue; 032 import org.apache.myfaces.tobago.internal.context.ThemeConfigCacheKey; 033 import org.apache.myfaces.tobago.layout.Measure; 034 import org.apache.myfaces.tobago.util.LocaleUtils; 035 import org.slf4j.Logger; 036 import org.slf4j.LoggerFactory; 037 038 import javax.faces.component.UIViewRoot; 039 import javax.faces.context.FacesContext; 040 import javax.faces.render.Renderer; 041 import java.util.ArrayList; 042 import java.util.List; 043 import java.util.Map; 044 import java.util.concurrent.ConcurrentHashMap; 045 046 public class ResourceManagerImpl implements ResourceManager { 047 048 private static final Logger LOG = LoggerFactory.getLogger(ResourceManagerImpl.class); 049 private static final String PROPERTY = "property"; 050 private static final String JSP = "jsp"; 051 private static final String TAG = "tag"; 052 private static final String MINIMIZE_SUFFIX = ".min"; 053 private boolean production; 054 055 private final Map<String, String> resourceList 056 = new ConcurrentHashMap<String, String>(100, 0.75f, 1); 057 058 private final Map<RendererCacheKey, Renderer> rendererCache 059 = new ConcurrentHashMap<RendererCacheKey, Renderer>(100, 0.75f, 1); 060 private final Map<ImageCacheKey, StringValue> imageCache 061 = new ConcurrentHashMap<ImageCacheKey, StringValue>(100, 0.75f, 1); 062 private final Map<JspCacheKey, String> jspCache 063 = new ConcurrentHashMap<JspCacheKey, String>(100, 0.75f, 1); 064 private final Map<MiscCacheKey, String[]> miscCache 065 = new ConcurrentHashMap<MiscCacheKey, String[]>(100, 0.75f, 1); 066 private final Map<PropertyCacheKey, StringValue> propertyCache 067 = new ConcurrentHashMap<PropertyCacheKey, StringValue>(100, 0.75f, 1); 068 private final Map<ThemeConfigCacheKey, MeasureValue> themeCache 069 = new ConcurrentHashMap<ThemeConfigCacheKey, MeasureValue>(100, 0.75f, 1); 070 071 private TobagoConfig tobagoConfig; 072 073 public ResourceManagerImpl(TobagoConfig tobagoConfig) { 074 this.tobagoConfig = tobagoConfig; 075 this.production = tobagoConfig.getProjectStage() == ProjectStage.Production; 076 } 077 078 public void add(String resourceKey) { 079 if (LOG.isDebugEnabled()) { 080 LOG.debug("adding resourceKey = '{}'", resourceKey); 081 } 082 resourceList.put(resourceKey, ""); 083 } 084 085 public void add(String resourceKey, String value) { 086 if (LOG.isDebugEnabled()) { 087 LOG.debug("adding resourceKey = '{}' value= '{}'", resourceKey, value); 088 } 089 resourceList.put(resourceKey, value); 090 } 091 092 @Deprecated 093 public String getJsp(UIViewRoot viewRoot, String name) { 094 String result = null; 095 if (name != null) { 096 097 ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance()); 098 JspCacheKey cacheKey = new JspCacheKey(clientKey, name); 099 100 result = jspCache.get(cacheKey); 101 if (result != null) { 102 return result; 103 } 104 try { 105 result = (String) getPaths(clientKey, "", 106 JSP, name, "", false, true, true, null, true, false).get(0); 107 jspCache.put(cacheKey, result); 108 } catch (Exception e) { 109 LOG.error("name = '" + name + "' clientProperties = '" + clientKey.toString() + "'", e); 110 } 111 } 112 return result; 113 } 114 115 @Deprecated 116 public String getProperty(UIViewRoot viewRoot, String bundle, String propertyKey) { 117 return getProperty(FacesContext.getCurrentInstance(), bundle, propertyKey); 118 } 119 120 public String getProperty(FacesContext facesContext, String bundle, String propertyKey) { 121 122 if (bundle != null && propertyKey != null) { 123 ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext); 124 PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey); 125 126 StringValue result = propertyCache.get(cacheKey); 127 if (result == null) { 128 List properties = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, false); 129 if (properties != null) { 130 result = new StringValue((String) properties.get(0)); 131 } else { 132 result = StringValue.NULL; 133 } 134 propertyCache.put(cacheKey, result); 135 } 136 return result.getValue(); 137 } 138 return null; 139 } 140 141 @Deprecated 142 public Renderer getRenderer(UIViewRoot viewRoot, String rendererType) { 143 return getRenderer(FacesContext.getCurrentInstance(), rendererType); 144 } 145 146 public Renderer getRenderer(FacesContext facesContext, String rendererType) { 147 Renderer renderer = null; 148 149 if (rendererType != null) { 150 ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext); 151 RendererCacheKey cacheKey = new RendererCacheKey(clientKey, rendererType); 152 153 renderer = rendererCache.get(cacheKey); 154 if (renderer != null) { 155 return renderer; 156 } 157 String simpleClassName = null; 158 try { 159 simpleClassName = getRendererClassName(rendererType); 160 List<Class> classes = getPaths(clientKey, "", TAG, simpleClassName, "", false, true, true, null, false, false); 161 if (classes != null && !classes.isEmpty()) { 162 Class clazz = classes.get(0); 163 renderer = (Renderer) clazz.newInstance(); 164 rendererCache.put(cacheKey, renderer); 165 } else { 166 LOG.error("Don't find any RendererClass for " + simpleClassName + ". Please check you configuration."); 167 } 168 } catch (InstantiationException e) { 169 LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e); 170 } catch (IllegalAccessException e) { 171 LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e); 172 } 173 } 174 return renderer; 175 } 176 177 @Deprecated 178 public String[] getScripts(UIViewRoot viewRoot, String name) { 179 return getScripts(FacesContext.getCurrentInstance(), name); 180 } 181 182 public String[] getScripts(FacesContext facesContext, String name) { 183 return getStrings(facesContext, name, null); 184 } 185 186 @Deprecated 187 public String[] getStyles(UIViewRoot viewRoot, String name) { 188 return getStyles(FacesContext.getCurrentInstance(), name); 189 } 190 191 public String[] getStyles(FacesContext facesContext, String name) { 192 return getStrings(facesContext, name, null); 193 } 194 195 @Deprecated 196 public String getThemeProperty(UIViewRoot viewRoot, String bundle, String propertyKey) { 197 if (bundle != null && propertyKey != null) { 198 199 ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance()); 200 PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey); 201 202 StringValue result = propertyCache.get(cacheKey); 203 if (result == null) { 204 List properties = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, true); 205 if (properties != null) { 206 result = new StringValue((String) properties.get(0)); 207 } else { 208 result = StringValue.NULL; 209 } 210 propertyCache.put(cacheKey, result); 211 } 212 return result.getValue(); 213 } 214 return null; 215 } 216 217 public Measure getThemeMeasure(FacesContext facesContext, Configurable configurable, String name) { 218 return getThemeMeasure(facesContext, configurable.getRendererType(), configurable.getCurrentMarkup(), name); 219 } 220 221 public Measure getThemeMeasure(FacesContext facesContext, String rendererType, Markup markup, String name) { 222 223 ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext); 224 ThemeConfigCacheKey cacheKey = new ThemeConfigCacheKey(clientKey, rendererType, markup, name); 225 226 MeasureValue result = themeCache.get(cacheKey); 227 228 if (result == null) { 229 List properties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "", 230 false, true, false, rendererType + "." + name, true, true); 231 if (properties != null) { 232 Measure measure = Measure.valueOf(properties.get(0)); 233 234 if (markup != null) { 235 for (String m : markup) { 236 List mProperties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "", 237 false, true, false, rendererType + "[" + m + "]" + "." + name, true, true); 238 if (mProperties != null) { 239 final Measure summand = Measure.valueOf(mProperties.get(0)); 240 measure = measure.add(summand); 241 } 242 } 243 } 244 245 result = new MeasureValue(measure); 246 } else { 247 result = MeasureValue.NULL; // to mark and cache that this value is undefined. 248 } 249 themeCache.put(cacheKey, result); 250 } 251 return result.getValue(); 252 } 253 254 @Deprecated 255 public String getImage(UIViewRoot viewRoot, String name) { 256 return getImage(FacesContext.getCurrentInstance(), name); 257 } 258 259 public String getImage(FacesContext facesContext, String name) { 260 return getImage(facesContext, name, false); 261 } 262 263 @Deprecated 264 public String getImage(UIViewRoot viewRoot, String name, boolean ignoreMissing) { 265 return getImage(FacesContext.getCurrentInstance(), name, ignoreMissing); 266 } 267 268 public String getImage(FacesContext facesContext, String name, boolean ignoreMissing) { 269 if (name != null) { 270 int dot = name.lastIndexOf('.'); 271 if (dot == -1) { 272 dot = name.length(); 273 } 274 275 ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext); 276 ImageCacheKey cacheKey = new ImageCacheKey(clientKey, name); 277 278 StringValue result = imageCache.get(cacheKey); 279 if (result == null) { 280 List paths = getPaths(clientKey, "", null, name.substring(0, dot), 281 name.substring(dot), false, true, true, null, true, ignoreMissing); 282 if (paths != null) { 283 result = new StringValue((String) paths.get(0)); 284 } else { 285 result = StringValue.NULL; 286 } 287 imageCache.put(cacheKey, result); 288 } 289 if (LOG.isDebugEnabled()) { 290 if (result.getValue() == null) { 291 LOG.debug("Can't find image for \"{}\"", name); 292 } 293 } 294 295 return result.getValue(); 296 } 297 298 return null; 299 } 300 301 private List getPaths( 302 ClientPropertiesKey clientkey, String prefix, String subDir, String name, String suffix, 303 boolean reverseOrder, boolean single, boolean returnKey, String key, boolean returnStrings, 304 boolean ignoreMissing) { 305 List matches = new ArrayList(); 306 String contentType = clientkey.getContentType(); 307 Theme theme = clientkey.getTheme(); 308 UserAgent browser = clientkey.getUserAgent(); 309 List<String> locales = LocaleUtils.getLocaleSuffixList(clientkey.getLocale()); 310 311 String path; 312 313 // check first the local web application directory 314 for (String localeSuffix : locales) { 315 if (production) { 316 path = makePath(name, MINIMIZE_SUFFIX, localeSuffix, suffix, key); 317 boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path); 318 if (found && (single || !returnStrings)) { 319 return matches; 320 } 321 if (!found) { 322 path = makePath(name, null, localeSuffix, suffix, key); 323 found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path); 324 if (found && (single || !returnStrings)) { 325 return matches; 326 } 327 } 328 } else { 329 path = makePath(name, null, localeSuffix, suffix, key); 330 boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path); 331 if (found && (single || !returnStrings)) { 332 return matches; 333 } 334 } 335 } 336 337 // after that check the whole resources tree 338 // e.g. 1. application, 2. library or renderkit 339 for (Theme themeName : theme.getFallbackList()) { // theme loop 340 for (String resourceDirectory : tobagoConfig.getResourceDirs()) { 341 for (String browserType : browser.getFallbackList()) { // browser loop 342 for (String localeSuffix : locales) { // locale loop 343 if (production) { 344 path = makePath(resourceDirectory, contentType, themeName, browserType, subDir, name, MINIMIZE_SUFFIX, 345 localeSuffix, suffix, key); 346 boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path); 347 if (found && (single || !returnStrings)) { 348 return matches; 349 } 350 if (!found) { 351 path = makePath(resourceDirectory, contentType, themeName, browserType, subDir, name, null, 352 localeSuffix, suffix, key); 353 found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path); 354 if (found && (single || !returnStrings)) { 355 return matches; 356 } 357 } 358 } else { 359 path = makePath(resourceDirectory, contentType, themeName, browserType, subDir, name, null, 360 localeSuffix, suffix, key); 361 boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path); 362 if (found && (single || !returnStrings)) { 363 return matches; 364 } 365 } 366 } 367 } 368 } 369 } 370 371 if (matches.isEmpty()) { 372 if (!ignoreMissing) { 373 LOG.error("Path not found, and no fallback. Using empty string.\n" 374 + "resourceDirs = '" + tobagoConfig.getResourceDirs() 375 + "' contentType = '" + contentType 376 + "' theme = '" + theme 377 + "' browser = '" + browser 378 + "' subDir = '" + subDir 379 + "' name = '" + name 380 + "' suffix = '" + suffix 381 + "' key = '" + key 382 + "'"/*, new Exception()*/); 383 } 384 return null; 385 } else { 386 return matches; 387 } 388 } 389 390 private boolean checkPath( 391 String prefix, boolean reverseOrder, boolean returnKey, boolean returnStrings, 392 List matches, String path) { 393 if (returnStrings && resourceList.containsKey(path)) { 394 String result = 395 returnKey 396 ? prefix + path 397 : prefix + resourceList.get(path); 398 399 if (reverseOrder) { 400 matches.add(0, result); 401 } else { 402 matches.add(result); 403 } 404 if (LOG.isTraceEnabled()) { 405 LOG.trace("testing path: {} *", path); // match 406 } 407 408 return true; 409 } else if (!returnStrings) { 410 try { 411 path = path.substring(1).replace('/', '.'); 412 Class clazz = Class.forName(path); 413 if (LOG.isTraceEnabled()) { 414 LOG.trace("testing path: " + path + " *"); // match 415 } 416 matches.add(clazz); 417 return true; 418 } catch (ClassNotFoundException e) { 419 // not found 420 if (LOG.isTraceEnabled()) { 421 LOG.trace("testing path: " + path); // no match 422 } 423 } 424 } else { 425 if (LOG.isTraceEnabled()) { 426 LOG.trace("testing path: " + path); // no match 427 } 428 } 429 return false; 430 } 431 432 private String makePath( 433 String project, String language, Theme theme, String browser, 434 String subDir, String name, String minimizeSuffix, String localeSuffix, String extension, String key) { 435 StringBuilder searchtext = new StringBuilder(64); 436 437 searchtext.append('/'); 438 searchtext.append(project); 439 searchtext.append('/'); 440 searchtext.append(language); 441 searchtext.append('/'); 442 searchtext.append(theme.getName()); 443 searchtext.append('/'); 444 searchtext.append(browser); 445 if (subDir != null) { 446 searchtext.append('/'); 447 searchtext.append(subDir); 448 } 449 searchtext.append('/'); 450 searchtext.append(name); 451 if (minimizeSuffix != null) { 452 searchtext.append(minimizeSuffix); 453 } 454 searchtext.append(localeSuffix); 455 searchtext.append(extension); 456 if (key != null) { 457 searchtext.append('/'); 458 searchtext.append(key); 459 } 460 461 return searchtext.toString(); 462 } 463 464 private String makePath( 465 String name, String minimizeSuffix, String localeSuffix, String extension, String key) { 466 StringBuilder searchtext = new StringBuilder(64); 467 468 searchtext.append('/'); 469 searchtext.append(name); 470 if (minimizeSuffix != null) { 471 searchtext.append(minimizeSuffix); 472 } 473 searchtext.append(localeSuffix); 474 searchtext.append(extension); 475 if (key != null) { 476 searchtext.append('/'); 477 searchtext.append(key); 478 } 479 480 return searchtext.toString(); 481 } 482 483 private String getRendererClassName(String rendererType) { 484 String name; 485 if (LOG.isDebugEnabled()) { 486 LOG.debug("rendererType = '{}'", rendererType); 487 } 488 if ("javax.faces.Text".equals(rendererType)) { // TODO: find a better way 489 name = RendererTypes.OUT; 490 } else { 491 name = rendererType; 492 } 493 name = name + "Renderer"; 494 if (name.startsWith("javax.faces.")) { // FIXME: this is a hotfix from jsf1.0beta to jsf1.0fr 495 LOG.warn("patching renderer from {}", name); 496 name = name.substring("javax.faces.".length()); 497 LOG.warn("patching renderer to {}", name); 498 } 499 return name; 500 } 501 502 private String[] getStrings(FacesContext facesContext, String name, String type) { 503 String[] result = new String[0]; 504 if (name != null) { 505 int dot = name.lastIndexOf('.'); 506 if (dot == -1) { 507 dot = name.length(); 508 } 509 510 ClientPropertiesKey key = ClientPropertiesKey.get(facesContext); 511 MiscCacheKey miscKey = new MiscCacheKey(key, name); 512 String[] cacheResult = miscCache.get(miscKey); 513 if (cacheResult != null) { 514 return cacheResult; 515 } 516 try { 517 List matches = getPaths(key, "", type, 518 name.substring(0, dot), name.substring(dot), true, false, true, null, true, false); 519 if (matches != null) { 520 result = (String[]) matches.toArray(new String[matches.size()]); 521 } 522 miscCache.put(miscKey, result); 523 } catch (Exception e) { 524 LOG.error("name = '" + name + "' clientProperties = '" + key.toString() + "'", e); 525 } 526 } 527 return result; 528 } 529 530 }