View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.context;
21  
22  import org.apache.myfaces.tobago.application.ProjectStage;
23  import org.apache.myfaces.tobago.component.RendererTypes;
24  import org.apache.myfaces.tobago.config.Configurable;
25  import org.apache.myfaces.tobago.context.Markup;
26  import org.apache.myfaces.tobago.context.ResourceManager;
27  import org.apache.myfaces.tobago.context.Theme;
28  import org.apache.myfaces.tobago.context.UserAgent;
29  import org.apache.myfaces.tobago.internal.config.TobagoConfigImpl;
30  import org.apache.myfaces.tobago.layout.Measure;
31  import org.apache.myfaces.tobago.util.LocaleUtils;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.context.FacesContext;
37  import javax.faces.render.Renderer;
38  import java.util.ArrayList;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.concurrent.ConcurrentHashMap;
42  
43  public class ResourceManagerImpl implements ResourceManager {
44  
45    private static final Logger LOG = LoggerFactory.getLogger(ResourceManagerImpl.class);
46    private static final String PROPERTY = "property";
47    private static final String JSP = "jsp";
48    private static final String TAG = "tag";
49    private static final String MINIMIZE_SUFFIX = ".min";
50    private boolean production;
51  
52    private final Map<String, String> resourceList 
53        = new ConcurrentHashMap<String, String>(100, 0.75f, 1);
54  
55    private final Map<RendererCacheKey, Renderer> rendererCache 
56        = new ConcurrentHashMap<RendererCacheKey, Renderer>(100, 0.75f, 1);
57    private final Map<ImageCacheKey, StringValue> imageCache 
58        = new ConcurrentHashMap<ImageCacheKey, StringValue>(100, 0.75f, 1);
59    private final Map<JspCacheKey, String> jspCache 
60        = new ConcurrentHashMap<JspCacheKey, String>(100, 0.75f, 1);
61    private final Map<MiscCacheKey, String[]> miscCache 
62        = new ConcurrentHashMap<MiscCacheKey, String[]>(100, 0.75f, 1);
63    private final Map<PropertyCacheKey, StringValue> propertyCache 
64        = new ConcurrentHashMap<PropertyCacheKey, StringValue>(100, 0.75f, 1);
65    private final Map<ThemeConfigCacheKey, MeasureValue> themeCache 
66        = new ConcurrentHashMap<ThemeConfigCacheKey, MeasureValue>(100, 0.75f, 1);
67    
68    private TobagoConfigImpl tobagoConfig;
69  
70    public ResourceManagerImpl(TobagoConfigImpl tobagoConfig) {
71      this.tobagoConfig = tobagoConfig;
72      this.production = tobagoConfig.getProjectStage() == ProjectStage.Production;
73    }
74  
75    public void add(String resourceKey) {
76      if (LOG.isDebugEnabled()) {
77        LOG.debug("adding resourceKey = '{}'", resourceKey);
78      }
79      resourceList.put(resourceKey, "");
80    }
81  
82    public void add(String resourceKey, String value) {
83      if (LOG.isDebugEnabled()) {
84        LOG.debug("adding resourceKey = '{}' value= '{}'", resourceKey, value);
85      }
86      resourceList.put(resourceKey, value);
87    }
88  
89    @Deprecated
90    public String getJsp(UIViewRoot viewRoot, String name) {
91      String result = null;
92      if (name != null) {
93  
94        ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance());
95        JspCacheKey cacheKey = new JspCacheKey(clientKey, name);
96  
97        result = jspCache.get(cacheKey);
98        if (result != null) {
99          return result;
100       }
101       try {
102         result = (String) getPaths(clientKey, "",
103             JSP, name, "", false, true, true, null, true, false).get(0);
104         jspCache.put(cacheKey, result);
105       } catch (Exception e) {
106         LOG.error("name = '" + name + "' clientProperties = '" + clientKey.toString() + "'", e);
107       }
108     }
109     return result;
110   }
111 
112   @Deprecated
113   public String getProperty(UIViewRoot viewRoot, String bundle, String propertyKey) {
114     return getProperty(FacesContext.getCurrentInstance(), bundle, propertyKey);
115   }
116 
117   public String getProperty(FacesContext facesContext, String bundle, String propertyKey) {
118 
119     if (bundle != null && propertyKey != null) {
120       ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
121       PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey);
122       
123       StringValue result = propertyCache.get(cacheKey);
124       if (result == null) {
125         List properties = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, false);
126         if (properties != null) {
127           result = new StringValue((String) properties.get(0));
128         } else {
129           result = StringValue.NULL;
130         }
131         propertyCache.put(cacheKey, result);
132       }
133       return result.getValue();
134     }
135     return null;
136   }
137 
138   @Deprecated
139   public Renderer getRenderer(UIViewRoot viewRoot, String rendererType) {
140     return getRenderer(FacesContext.getCurrentInstance(), rendererType);
141   }
142 
143   public Renderer getRenderer(FacesContext facesContext, String rendererType) {
144     Renderer renderer = null;
145 
146     if (rendererType != null) {
147       ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
148       RendererCacheKey cacheKey = new RendererCacheKey(clientKey, rendererType);
149 
150       renderer = rendererCache.get(cacheKey);
151       if (renderer != null) {
152         return renderer;
153       }
154       String simpleClassName = null;
155       try {
156         simpleClassName = getRendererClassName(rendererType);
157         List<Class> classes = getPaths(clientKey, "", TAG, simpleClassName, "", false, true, true, null, false, false);
158         if (classes != null && !classes.isEmpty()) {
159           Class clazz = classes.get(0);
160           renderer = (Renderer) clazz.newInstance();
161           rendererCache.put(cacheKey, renderer);
162         } else {
163           LOG.error("Don't find any RendererClass for " + simpleClassName + ". Please check you configuration.");
164         }
165       } catch (InstantiationException e) {
166         LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e);
167       } catch (IllegalAccessException e) {
168         LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e);
169       }
170     }
171     return renderer;
172   }
173   
174   @Deprecated
175   public String[] getScripts(UIViewRoot viewRoot, String name) {
176     return getScripts(FacesContext.getCurrentInstance(), name);
177   }
178   
179   public String[] getScripts(FacesContext facesContext, String name) {
180     return getStrings(facesContext, name, null);
181   }
182 
183   @Deprecated
184   public String[] getStyles(UIViewRoot viewRoot, String name) {
185     return getStyles(FacesContext.getCurrentInstance(), name);
186   }
187 
188   public String[] getStyles(FacesContext facesContext, String name) {
189     return getStrings(facesContext, name, null);
190   }
191 
192   @Deprecated
193   public String getThemeProperty(UIViewRoot viewRoot, String bundle, String propertyKey) {
194     if (bundle != null && propertyKey != null) {
195 
196       ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance());
197       PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey);
198 
199       StringValue result = propertyCache.get(cacheKey);
200       if (result == null) {
201         List properties = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, true);
202         if (properties != null) {
203           result = new StringValue((String) properties.get(0));
204         } else {
205           result = StringValue.NULL;
206         }
207         propertyCache.put(cacheKey, result);
208       }
209       return result.getValue();
210     }
211     return null;
212   }
213 
214   public Measure getThemeMeasure(FacesContext facesContext, Configurable configurable, String name) {
215     return getThemeMeasure(facesContext, configurable.getRendererType(), configurable.getCurrentMarkup(), name);
216   }
217 
218   public Measure getThemeMeasure(FacesContext facesContext, String rendererType, Markup markup, String name) {
219 
220     ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
221     ThemeConfigCacheKey cacheKey = new ThemeConfigCacheKey(clientKey, rendererType, markup, name);
222 
223     MeasureValue result = themeCache.get(cacheKey);
224 
225     if (result == null) {
226       List properties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "",
227           false, true, false, rendererType + "." + name, true, true);
228 
229       Measure measure = null;
230       if (properties != null) {
231         measure = Measure.valueOf(properties.get(0));
232       }
233 
234       if (markup != null) {
235         for (String m : markup) {
236           List mProperties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "",
237               false, true, false, rendererType + "[" + m + "]" + "." + name, true, true);
238           if (mProperties != null) {
239             final Measure summand = Measure.valueOf(mProperties.get(0));
240             measure = summand.add(measure);
241           }
242         }
243       }
244 
245       if (measure != null) {
246         result = new MeasureValue(measure);
247       } else {
248         result = MeasureValue.NULL;  // to mark and cache that this value is undefined.
249       }
250       themeCache.put(cacheKey, result);
251     }
252     return result.getValue();
253   }
254 
255   @Deprecated
256   public String getImage(UIViewRoot viewRoot, String name) {
257     return getImage(FacesContext.getCurrentInstance(), name);
258   }
259   
260   public String getImage(FacesContext facesContext, String name) {
261     return getImage(facesContext, name, false);
262   }
263 
264   @Deprecated
265   public String getImage(UIViewRoot viewRoot, String name, boolean ignoreMissing) {
266     return getImage(FacesContext.getCurrentInstance(), name, ignoreMissing);
267   }
268 
269   public String getImage(FacesContext facesContext, String name, boolean ignoreMissing) {
270     if (name != null) {
271       int dot = name.lastIndexOf('.');
272       if (dot == -1) {
273         dot = name.length();
274       }
275 
276       ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
277       ImageCacheKey cacheKey = new ImageCacheKey(clientKey, name);
278 
279       StringValue result = imageCache.get(cacheKey);
280       if (result == null) {
281         List paths = getPaths(clientKey, "", null, name.substring(0, dot),
282             name.substring(dot), false, true, true, null, true, ignoreMissing);
283         if (paths != null) {
284           result = new StringValue((String) paths.get(0));
285         } else {
286           result = StringValue.NULL;
287         }
288         imageCache.put(cacheKey, result);
289       }
290       if (LOG.isDebugEnabled()) {
291         if (result.getValue() == null) {
292           LOG.debug("Can't find image for \"{}\"", name);
293         }
294       }
295 
296       return result.getValue();
297     }
298 
299     return null;
300   }
301 
302   private List getPaths(
303       ClientPropertiesKey clientkey, String prefix, String subDir, String name, String suffix,
304       boolean reverseOrder, boolean single, boolean returnKey, String key, boolean returnStrings,
305       boolean ignoreMissing) {
306     List matches = new ArrayList();
307     String contentType = clientkey.getContentType();
308     Theme theme = clientkey.getTheme();
309     UserAgent browser = clientkey.getUserAgent();
310     List<String> locales = LocaleUtils.getLocaleSuffixList(clientkey.getLocale());
311 
312     // check first the local web application directory
313     for (String localeSuffix : locales) {
314       if (production) {
315         boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
316             name, MINIMIZE_SUFFIX, localeSuffix, suffix, key);
317         if (found && (single || !returnStrings)) {
318           return matches;
319         }
320         if (!found) {
321           found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
322               name, null, localeSuffix, suffix, key);
323           if (found && (single || !returnStrings)) {
324             return matches;
325           }
326         }
327       } else {
328         boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
329             name, null, localeSuffix, suffix, key);
330         if (found && (single || !returnStrings)) {
331           return matches;
332         }
333       }
334     }
335 
336     // after that check the whole resources tree
337     // e.g. 1. application, 2. library or renderkit
338     for (Theme currentTheme : theme.getFallbackList()) {// theme loop
339       for (String resourceDirectory : tobagoConfig.getResourceDirs()) {
340         for (String browserType : browser.getFallbackList()) { // browser loop
341           for (String localeSuffix : locales) { // locale loop
342             if (production) {
343               boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
344                   resourceDirectory, contentType, currentTheme, browserType, subDir, name, MINIMIZE_SUFFIX,
345                   localeSuffix, suffix, key);
346               if (found && (single || !returnStrings)) {
347                 return matches;
348               }
349               if (!found) {
350                 found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
351                     resourceDirectory, contentType, currentTheme, browserType, subDir, name, null,
352                     localeSuffix, suffix, key);
353                 if (found && (single || !returnStrings)) {
354                   return matches;
355                 }
356               }
357             } else {
358               boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
359                   resourceDirectory, contentType, currentTheme, browserType, subDir, name, null,
360                   localeSuffix, suffix, key);
361               if (found && (single || !returnStrings)) {
362                 return matches;
363               }
364             }
365           }
366         }
367       }
368     }
369 
370     if (matches.isEmpty()) {
371       if (!ignoreMissing) {
372         LOG.error("Path not found, and no fallback. Using empty string.\n"
373             + "resourceDirs = '" + tobagoConfig.getResourceDirs()
374             + "' contentType = '" + contentType
375             + "' theme = '" + theme.getName()
376             + "' browser = '" + browser
377             + "' subDir = '" + subDir
378             + "' name = '" + name
379             + "' suffix = '" + suffix
380             + "' key = '" + key
381             + "'"/*, new Exception()*/);
382       }
383       return null;
384     } else {
385       return matches;
386     }
387   }
388 
389   private boolean checkPath(
390       String prefix, boolean reverseOrder, boolean returnKey, boolean returnStrings,
391       List matches, String name, String minimizeSuffix, String localeSuffix, String extension, String key) {
392     String path = makePath(name, minimizeSuffix, localeSuffix, extension, key);
393     if (returnStrings && resourceList.containsKey(path)) {
394       String result =
395           returnKey
396               ? prefix + path
397               : prefix + resourceList.get(path);
398 
399       if (reverseOrder) {
400         matches.add(0, result);
401       } else {
402         matches.add(result);
403       }
404       if (LOG.isTraceEnabled()) {
405         LOG.trace("testing path: {} *", path); // match
406       }
407 
408       return true;
409     } else if (!returnStrings) {
410       try {
411         path = path.substring(1).replace('/', '.');
412         Class clazz = Class.forName(path);
413         if (LOG.isTraceEnabled()) {
414           LOG.trace("testing path: " + path + " *"); // match
415         }
416         matches.add(clazz);
417         return true;
418       } catch (ClassNotFoundException e) {
419         // not found
420         if (LOG.isTraceEnabled()) {
421           LOG.trace("testing path: " + path); // no match
422         }
423       }
424     } else {
425       if (LOG.isTraceEnabled()) {
426         LOG.trace("testing path: " + path); // no match
427       }
428     }
429     return false;
430   }
431 
432   private boolean checkPath(
433       String prefix, boolean reverseOrder, boolean returnKey, boolean returnStrings,
434       List matches,  String resourceDirectory, String contentType, Theme currentTheme, String browserType,
435       String subDir, String name, String minimizeSuffix, String localeSuffix, String suffix, String key) {
436     String path = makePath(resourceDirectory, contentType, currentTheme, browserType, subDir, name, minimizeSuffix,
437         localeSuffix, suffix, key, null);
438     if (returnStrings && resourceList.containsKey(path)) {
439       String result;
440       if (prefix.length() == 0 && returnKey && resourceDirectory.equals(currentTheme.getResourcePath())) {
441         result = makePath(resourceDirectory, contentType, currentTheme, browserType, subDir, name, minimizeSuffix,
442             localeSuffix, suffix, key, currentTheme.getVersion());
443       } else {
444         result = returnKey
445             ? prefix + path : prefix + resourceList.get(path);
446       }
447       if (reverseOrder) {
448         matches.add(0, result);
449       } else {
450         matches.add(result);
451       }
452       if (LOG.isTraceEnabled()) {
453         LOG.trace("testing path: {} *", path); // match
454       }
455 
456       return true;
457     } else if (!returnStrings) {
458       try {
459         path = path.substring(1).replace('/', '.');
460         Class clazz = Class.forName(path);
461         if (LOG.isTraceEnabled()) {
462           LOG.trace("testing path: " + path + " *"); // match
463         }
464         matches.add(clazz);
465         return true;
466       } catch (ClassNotFoundException e) {
467         // not found
468         if (LOG.isTraceEnabled()) {
469           LOG.trace("testing path: " + path); // no match
470         }
471       }
472     } else {
473       if (LOG.isTraceEnabled()) {
474         LOG.trace("testing path: " + path); // no match
475       }
476     }
477     return false;
478   }
479 
480   private String makePath(
481       String project, String language, Theme theme, String browser, String subDir,
482       String name, String minimizeSuffix, String localeSuffix, String extension, String key, String version) {
483     StringBuilder searchtext = new StringBuilder(64);
484 
485     searchtext.append('/');
486     searchtext.append(project);
487     if (version != null) {
488       searchtext.append('/');
489       searchtext.append(version);
490     }
491     searchtext.append('/');
492     searchtext.append(language);
493     searchtext.append('/');
494     searchtext.append(theme.getName());
495     searchtext.append('/');
496     searchtext.append(browser);
497     if (subDir != null) {
498       searchtext.append('/');
499       searchtext.append(subDir);
500     }
501     searchtext.append('/');
502     searchtext.append(name);
503     if (minimizeSuffix != null) {
504       searchtext.append(minimizeSuffix);
505     }
506     searchtext.append(localeSuffix);
507     searchtext.append(extension);
508     if (key != null) {
509       searchtext.append('/');
510       searchtext.append(key);
511     }
512 
513     return searchtext.toString();
514   }
515 
516   private String makePath(
517       String name, String minimizeSuffix, String localeSuffix, String extension, String key) {
518     StringBuilder searchtext = new StringBuilder(64);
519 
520     searchtext.append('/');
521     searchtext.append(name);
522     if (minimizeSuffix != null) {
523       searchtext.append(minimizeSuffix);
524     }
525     searchtext.append(localeSuffix);
526     searchtext.append(extension);
527     if (key != null) {
528       searchtext.append('/');
529       searchtext.append(key);
530     }
531 
532     return searchtext.toString();
533   }
534 
535   private String getRendererClassName(String rendererType) {
536     String name;
537     if (LOG.isDebugEnabled()) {
538       LOG.debug("rendererType = '{}'", rendererType);
539     }
540     if ("javax.faces.Text".equals(rendererType)) { // TODO: find a better way
541       name = RendererTypes.OUT;
542     } else {
543       name = rendererType;
544     }
545     name = name + "Renderer";
546     if (name.startsWith("javax.faces.")) { // FIXME: this is a hotfix from jsf1.0beta to jsf1.0fr
547       LOG.warn("patching renderer from {}", name);
548       name = name.substring("javax.faces.".length());
549       LOG.warn("patching renderer to {}", name);
550     }
551     return name;
552   }
553 
554   private String[] getStrings(FacesContext facesContext, String name, String type) {
555     String[] result = new String[0];
556     if (name != null) {
557       int dot = name.lastIndexOf('.');
558       if (dot == -1) {
559         dot = name.length();
560       }
561 
562       ClientPropertiesKey key = ClientPropertiesKey.get(facesContext);
563       MiscCacheKey miscKey = new MiscCacheKey(key, name);
564       String[] cacheResult = miscCache.get(miscKey);
565       if (cacheResult != null) {
566         return cacheResult;
567       }
568       try {
569         List matches = getPaths(key, "", type,
570             name.substring(0, dot), name.substring(dot), true, false, true, null, true, false);
571         if (matches != null) {
572           result = (String[]) matches.toArray(new String[matches.size()]);
573         }
574         miscCache.put(miscKey, result);
575       } catch (Exception e) {
576         LOG.error("name = '" + name + "' clientProperties = '" + key.toString() + "'", e);
577       }
578     }
579     return result;
580   }
581 
582   @Override
583   public String toString() {
584     return "ResourceManagerImpl{"
585         + "production=" + production
586         + ", resourceList=" + resourceList.size()
587         + ", rendererCache=" + rendererCache.size()
588         + ", imageCache=" + imageCache.size()
589         + ", jspCache=" + jspCache.size()
590         + ", miscCache=" + miscCache.size()
591         + ", propertyCache=" + propertyCache.size()
592         + ", themeCache=" + themeCache.size()
593         + '}';
594   }
595 }