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