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