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