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