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(final TobagoConfigImpl tobagoConfig) {
71      this.tobagoConfig = tobagoConfig;
72      this.production = tobagoConfig.getProjectStage() == ProjectStage.Production;
73    }
74  
75    public void add(final String resourceKey) {
76      if (LOG.isDebugEnabled()) {
77        LOG.debug("adding resourceKey = '{}'", resourceKey);
78      }
79      resourceList.put(resourceKey, "");
80    }
81  
82    public void add(final String resourceKey, final 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(final UIViewRoot viewRoot, final String name) {
91      String result = null;
92      if (name != null) {
93  
94        final ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance());
95        final 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 (final Exception e) {
106         LOG.error("name = '" + name + "' clientProperties = '" + clientKey.toString() + "'", e);
107       }
108     }
109     return result;
110   }
111 
112   @Deprecated
113   public String getProperty(final UIViewRoot viewRoot, final String bundle, final String propertyKey) {
114     return getProperty(FacesContext.getCurrentInstance(), bundle, propertyKey);
115   }
116 
117   public String getProperty(final FacesContext facesContext, final String bundle, final String propertyKey) {
118 
119     if (bundle != null && propertyKey != null) {
120       final ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
121       final PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey);
122       
123       StringValue result = propertyCache.get(cacheKey);
124       if (result == null) {
125         final List properties
126             = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, false);
127         if (properties != null) {
128           result = new StringValue((String) properties.get(0));
129         } else {
130           result = StringValue.NULL;
131         }
132         propertyCache.put(cacheKey, result);
133       }
134       return result.getValue();
135     }
136     return null;
137   }
138 
139   @Deprecated
140   public Renderer getRenderer(final UIViewRoot viewRoot, final String rendererType) {
141     return getRenderer(FacesContext.getCurrentInstance(), rendererType);
142   }
143 
144   public Renderer getRenderer(final FacesContext facesContext, final String rendererType) {
145     Renderer renderer = null;
146 
147     if (rendererType != null) {
148       final ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
149       final RendererCacheKey cacheKey = new RendererCacheKey(clientKey, rendererType);
150 
151       renderer = rendererCache.get(cacheKey);
152       if (renderer != null) {
153         return renderer;
154       }
155       String simpleClassName = null;
156       try {
157         simpleClassName = getRendererClassName(rendererType);
158         final List<Class> classes
159             = getPaths(clientKey, "", TAG, simpleClassName, "", false, true, true, null, false, false);
160         if (classes != null && !classes.isEmpty()) {
161           final Class clazz = classes.get(0);
162           renderer = (Renderer) clazz.newInstance();
163           rendererCache.put(cacheKey, renderer);
164         } else {
165           LOG.error("Don't find any RendererClass for " + simpleClassName + ". Please check you configuration.");
166         }
167       } catch (final InstantiationException e) {
168         LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e);
169       } catch (final IllegalAccessException e) {
170         LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e);
171       }
172     }
173     return renderer;
174   }
175   
176   @Deprecated
177   public String[] getScripts(final UIViewRoot viewRoot, final String name) {
178     return getScripts(FacesContext.getCurrentInstance(), name);
179   }
180   
181   public String[] getScripts(final FacesContext facesContext, final String name) {
182     return getStrings(facesContext, name, null);
183   }
184 
185   @Deprecated
186   public String[] getStyles(final UIViewRoot viewRoot, final String name) {
187     return getStyles(FacesContext.getCurrentInstance(), name);
188   }
189 
190   public String[] getStyles(final FacesContext facesContext, final String name) {
191     return getStrings(facesContext, name, null);
192   }
193 
194   @Deprecated
195   public String getThemeProperty(final UIViewRoot viewRoot, final String bundle, final String propertyKey) {
196     if (bundle != null && propertyKey != null) {
197 
198       final ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance());
199       final PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey);
200 
201       StringValue result = propertyCache.get(cacheKey);
202       if (result == null) {
203         final List properties
204             = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, true);
205         if (properties != null) {
206           result = new StringValue((String) properties.get(0));
207         } else {
208           result = StringValue.NULL;
209         }
210         propertyCache.put(cacheKey, result);
211       }
212       return result.getValue();
213     }
214     return null;
215   }
216 
217   public Measure getThemeMeasure(final FacesContext facesContext, final Configurable configurable, final String name) {
218     return getThemeMeasure(facesContext, configurable.getRendererType(), configurable.getCurrentMarkup(), name);
219   }
220 
221   public Measure getThemeMeasure(
222       final FacesContext facesContext, final String rendererType, final Markup markup, final String name) {
223 
224     final ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
225     final ThemeConfigCacheKey cacheKey = new ThemeConfigCacheKey(clientKey, rendererType, markup, name);
226 
227     MeasureValue result = themeCache.get(cacheKey);
228 
229     if (result == null) {
230       final List properties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "",
231           false, true, false, rendererType + "." + name, true, true);
232 
233       Measure measure = null;
234       if (properties != null) {
235         measure = Measure.valueOf(properties.get(0));
236       }
237 
238       if (markup != null) {
239         for (final String m : markup) {
240           final List mProperties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "",
241               false, true, false, rendererType + "[" + m + "]" + "." + name, true, true);
242           if (mProperties != null) {
243             final Measure summand = Measure.valueOf(mProperties.get(0));
244             measure = summand.add(measure);
245           }
246         }
247       }
248 
249       if (measure != null) {
250         result = new MeasureValue(measure);
251       } else {
252         result = MeasureValue.NULL;  // to mark and cache that this value is undefined.
253       }
254       themeCache.put(cacheKey, result);
255     }
256     return result.getValue();
257   }
258 
259   @Deprecated
260   public String getImage(final UIViewRoot viewRoot, final String name) {
261     return getImage(FacesContext.getCurrentInstance(), name);
262   }
263   
264   public String getImage(final FacesContext facesContext, final String name) {
265     return getImage(facesContext, name, false);
266   }
267 
268   @Deprecated
269   public String getImage(final UIViewRoot viewRoot, final String name, final boolean ignoreMissing) {
270     return getImage(FacesContext.getCurrentInstance(), name, ignoreMissing);
271   }
272 
273   public String getImage(final FacesContext facesContext, final String name, final boolean ignoreMissing) {
274     if (name != null) {
275       int dot = name.lastIndexOf('.');
276       if (dot == -1) {
277         dot = name.length();
278       }
279 
280       final ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
281       final ImageCacheKey cacheKey = new ImageCacheKey(clientKey, name);
282 
283       StringValue result = imageCache.get(cacheKey);
284       if (result == null) {
285         final List paths = getPaths(clientKey, "", null, name.substring(0, dot),
286             name.substring(dot), false, true, true, null, true, ignoreMissing);
287         if (paths != null) {
288           result = new StringValue((String) paths.get(0));
289         } else {
290           result = StringValue.NULL;
291         }
292         imageCache.put(cacheKey, result);
293       }
294       if (LOG.isDebugEnabled()) {
295         if (result.getValue() == null) {
296           LOG.debug("Can't find image for \"{}\"", name);
297         }
298       }
299 
300       return result.getValue();
301     }
302 
303     return null;
304   }
305 
306   private List getPaths(
307       final ClientPropertiesKey clientkey, final String prefix, final String subDir, final String name,
308       final String suffix, final boolean reverseOrder, final boolean single, final boolean returnKey, final String key,
309       final boolean returnStrings, boolean ignoreMissing) {
310     final List matches = new ArrayList();
311     final String contentType = clientkey.getContentType();
312     final Theme theme = clientkey.getTheme();
313     final UserAgent browser = clientkey.getUserAgent();
314     final List<String> locales = LocaleUtils.getLocaleSuffixList(clientkey.getLocale());
315 
316     // check first the local web application directory
317     for (final String localeSuffix : locales) {
318       if (production) {
319         boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
320             name, MINIMIZE_SUFFIX, localeSuffix, suffix, key);
321         if (found && (single || !returnStrings)) {
322           return matches;
323         }
324         if (!found) {
325           found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
326               name, null, localeSuffix, suffix, key);
327           if (found && (single || !returnStrings)) {
328             return matches;
329           }
330         }
331       } else {
332         final boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
333             name, null, localeSuffix, suffix, key);
334         if (found && (single || !returnStrings)) {
335           return matches;
336         }
337       }
338     }
339 
340     // after that check the whole resources tree
341     // e.g. 1. application, 2. library or renderkit
342     for (final Theme currentTheme : theme.getFallbackList()) {// theme loop
343       for (final String resourceDirectory : tobagoConfig.getResourceDirs()) {
344         for (final String browserType : browser.getFallbackList()) { // browser loop
345           for (final String localeSuffix : locales) { // locale loop
346             if (production) {
347               boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
348                   resourceDirectory, contentType, currentTheme, browserType, subDir, name, MINIMIZE_SUFFIX,
349                   localeSuffix, suffix, key);
350               if (found && (single || !returnStrings)) {
351                 return matches;
352               }
353               if (!found) {
354                 found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
355                     resourceDirectory, contentType, currentTheme, browserType, subDir, name, null,
356                     localeSuffix, suffix, key);
357                 if (found && (single || !returnStrings)) {
358                   return matches;
359                 }
360               }
361             } else {
362               final boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches,
363                   resourceDirectory, contentType, currentTheme, browserType, subDir, name, null,
364                   localeSuffix, suffix, key);
365               if (found && (single || !returnStrings)) {
366                 return matches;
367               }
368             }
369           }
370         }
371       }
372     }
373 
374     if (matches.isEmpty()) {
375 
376       // XXX hack for Tobago 2.0.x backward compatibility: renaming of style.css to tobago.css
377       // XXX style.css should be collected, but missing should be ignored
378       if ("style/style".equals(name) && ".css".equals(suffix)) {
379         ignoreMissing = true;
380       }
381 
382       if (!ignoreMissing) {
383         LOG.error("Path not found, and no fallback. Using empty string.\n"
384             + "resourceDirs = '" + tobagoConfig.getResourceDirs()
385             + "' contentType = '" + contentType
386             + "' theme = '" + theme.getName()
387             + "' browser = '" + browser
388             + "' subDir = '" + subDir
389             + "' name = '" + name
390             + "' suffix = '" + suffix
391             + "' key = '" + key
392             + "'");
393       }
394       return null;
395     } else {
396       return matches;
397     }
398   }
399 
400   private boolean checkPath(
401       final String prefix, final boolean reverseOrder, final boolean returnKey, final boolean returnStrings,
402       final List matches, final String name, final String minimizeSuffix, final String localeSuffix,
403       final String extension, final String key) {
404     String path = makePath(name, minimizeSuffix, localeSuffix, extension, key);
405     if (returnStrings && resourceList.containsKey(path)) {
406       final String result =
407           returnKey
408               ? prefix + path
409               : prefix + resourceList.get(path);
410 
411       if (reverseOrder) {
412         matches.add(0, result);
413       } else {
414         matches.add(result);
415       }
416       if (LOG.isTraceEnabled()) {
417         LOG.trace("testing path: {} *", path); // match
418       }
419 
420       return true;
421     } else if (!returnStrings) {
422       try {
423         path = path.substring(1).replace('/', '.');
424         final Class clazz = Class.forName(path);
425         if (LOG.isTraceEnabled()) {
426           LOG.trace("testing path: " + path + " *"); // match
427         }
428         matches.add(clazz);
429         return true;
430       } catch (final ClassNotFoundException e) {
431         // not found
432         if (LOG.isTraceEnabled()) {
433           LOG.trace("testing path: " + path); // no match
434         }
435       }
436     } else {
437       if (LOG.isTraceEnabled()) {
438         LOG.trace("testing path: " + path); // no match
439       }
440     }
441     return false;
442   }
443 
444   private boolean checkPath(
445       final String prefix, final boolean reverseOrder, final boolean returnKey, final boolean returnStrings,
446       final List matches, final String resourceDirectory, final String contentType, final Theme currentTheme,
447       final String browserType, final String subDir, final String name, final String minimizeSuffix,
448       final String localeSuffix, final String suffix, final String key) {
449     String path = makePath(resourceDirectory, contentType, currentTheme, browserType, subDir, name, minimizeSuffix,
450         localeSuffix, suffix, key, null);
451     if (returnStrings && resourceList.containsKey(path)) {
452       final String result;
453       if (prefix.length() == 0 && returnKey && resourceDirectory.equals(currentTheme.getResourcePath())) {
454         result = makePath(resourceDirectory, contentType, currentTheme, browserType, subDir, name, minimizeSuffix,
455             localeSuffix, suffix, key, currentTheme.getVersion());
456       } else {
457         result = returnKey
458             ? prefix + path : prefix + resourceList.get(path);
459       }
460       if (reverseOrder) {
461         matches.add(0, result);
462       } else {
463         matches.add(result);
464       }
465       if (LOG.isTraceEnabled()) {
466         LOG.trace("testing path: {} *", path); // match
467       }
468 
469       return true;
470     } else if (!returnStrings) {
471       try {
472         path = path.substring(1).replace('/', '.');
473         final Class clazz = Class.forName(path);
474         if (LOG.isTraceEnabled()) {
475           LOG.trace("testing path: " + path + " *"); // match
476         }
477         matches.add(clazz);
478         return true;
479       } catch (final ClassNotFoundException e) {
480         // not found
481         if (LOG.isTraceEnabled()) {
482           LOG.trace("testing path: " + path); // no match
483         }
484       }
485     } else {
486       if (LOG.isTraceEnabled()) {
487         LOG.trace("testing path: " + path); // no match
488       }
489     }
490     return false;
491   }
492 
493   private String makePath(
494       final String project, final String language, final Theme theme, final String browser, final String subDir,
495       final String name, final String minimizeSuffix, final String localeSuffix, final String extension,
496       final String key, final String version) {
497     final StringBuilder searchtext = new StringBuilder(64);
498 
499     searchtext.append('/');
500     searchtext.append(project);
501     if (version != null) {
502       searchtext.append('/');
503       searchtext.append(version);
504     }
505     searchtext.append('/');
506     searchtext.append(language);
507     searchtext.append('/');
508     searchtext.append(theme.getName());
509     searchtext.append('/');
510     searchtext.append(browser);
511     if (subDir != null) {
512       searchtext.append('/');
513       searchtext.append(subDir);
514     }
515     searchtext.append('/');
516     searchtext.append(name);
517     if (minimizeSuffix != null) {
518       searchtext.append(minimizeSuffix);
519     }
520     searchtext.append(localeSuffix);
521     searchtext.append(extension);
522     if (key != null) {
523       searchtext.append('/');
524       searchtext.append(key);
525     }
526 
527     return searchtext.toString();
528   }
529 
530   private String makePath(
531       final String name, final String minimizeSuffix, final String localeSuffix, final String extension,
532       final String key) {
533     final StringBuilder searchtext = new StringBuilder(64);
534 
535     searchtext.append('/');
536     searchtext.append(name);
537     if (minimizeSuffix != null) {
538       searchtext.append(minimizeSuffix);
539     }
540     searchtext.append(localeSuffix);
541     searchtext.append(extension);
542     if (key != null) {
543       searchtext.append('/');
544       searchtext.append(key);
545     }
546 
547     return searchtext.toString();
548   }
549 
550   private String getRendererClassName(final String rendererType) {
551     String name;
552     if (LOG.isDebugEnabled()) {
553       LOG.debug("rendererType = '{}'", rendererType);
554     }
555     if ("javax.faces.Text".equals(rendererType)) { // TODO: find a better way
556       name = RendererTypes.OUT;
557     } else {
558       name = rendererType;
559     }
560     name = name + "Renderer";
561     if (name.startsWith("javax.faces.")) { // FIXME: this is a hotfix from jsf1.0beta to jsf1.0fr
562       LOG.warn("patching renderer from {}", name);
563       name = name.substring("javax.faces.".length());
564       LOG.warn("patching renderer to {}", name);
565     }
566     return name;
567   }
568 
569   private String[] getStrings(final FacesContext facesContext, final String name, final String type) {
570     String[] result = new String[0];
571     if (name != null) {
572       int dot = name.lastIndexOf('.');
573       if (dot == -1) {
574         dot = name.length();
575       }
576 
577       final ClientPropertiesKey key = ClientPropertiesKey.get(facesContext);
578       final MiscCacheKey miscKey = new MiscCacheKey(key, name);
579       final String[] cacheResult = miscCache.get(miscKey);
580       if (cacheResult != null) {
581         return cacheResult;
582       }
583       try {
584         final List matches = getPaths(key, "", type,
585             name.substring(0, dot), name.substring(dot), true, false, true, null, true, false);
586         if (matches != null) {
587           result = (String[]) matches.toArray(new String[matches.size()]);
588         }
589         miscCache.put(miscKey, result);
590       } catch (final Exception e) {
591         LOG.error("name = '" + name + "' clientProperties = '" + key.toString() + "'", e);
592       }
593     }
594     return result;
595   }
596 
597   @Override
598   public String toString() {
599     return "ResourceManagerImpl{"
600         + "production=" + production
601         + ", resourceList=" + resourceList.size()
602         + ", rendererCache=" + rendererCache.size()
603         + ", imageCache=" + imageCache.size()
604         + ", jspCache=" + jspCache.size()
605         + ", miscCache=" + miscCache.size()
606         + ", propertyCache=" + propertyCache.size()
607         + ", themeCache=" + themeCache.size()
608         + '}';
609   }
610 }