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