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