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