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