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.myfaces.tobago.application.ProjectStage;
021    import org.apache.myfaces.tobago.component.RendererTypes;
022    import org.apache.myfaces.tobago.config.Configurable;
023    import org.apache.myfaces.tobago.config.TobagoConfig;
024    import org.apache.myfaces.tobago.internal.context.ClientPropertiesKey;
025    import org.apache.myfaces.tobago.internal.context.ImageCacheKey;
026    import org.apache.myfaces.tobago.internal.context.JspCacheKey;
027    import org.apache.myfaces.tobago.internal.context.MeasureValue;
028    import org.apache.myfaces.tobago.internal.context.MiscCacheKey;
029    import org.apache.myfaces.tobago.internal.context.PropertyCacheKey;
030    import org.apache.myfaces.tobago.internal.context.RendererCacheKey;
031    import org.apache.myfaces.tobago.internal.context.StringValue;
032    import org.apache.myfaces.tobago.internal.context.ThemeConfigCacheKey;
033    import org.apache.myfaces.tobago.layout.Measure;
034    import org.apache.myfaces.tobago.util.LocaleUtils;
035    import org.slf4j.Logger;
036    import org.slf4j.LoggerFactory;
037    
038    import javax.faces.component.UIViewRoot;
039    import javax.faces.context.FacesContext;
040    import javax.faces.render.Renderer;
041    import java.util.ArrayList;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.concurrent.ConcurrentHashMap;
045    
046    public class ResourceManagerImpl implements ResourceManager {
047    
048      private static final Logger LOG = LoggerFactory.getLogger(ResourceManagerImpl.class);
049      private static final String PROPERTY = "property";
050      private static final String JSP = "jsp";
051      private static final String TAG = "tag";
052      private static final String MINIMIZE_SUFFIX = ".min";
053      private boolean production;
054    
055      private final Map<String, String> resourceList 
056          = new ConcurrentHashMap<String, String>(100, 0.75f, 1);
057    
058      private final Map<RendererCacheKey, Renderer> rendererCache 
059          = new ConcurrentHashMap<RendererCacheKey, Renderer>(100, 0.75f, 1);
060      private final Map<ImageCacheKey, StringValue> imageCache 
061          = new ConcurrentHashMap<ImageCacheKey, StringValue>(100, 0.75f, 1);
062      private final Map<JspCacheKey, String> jspCache 
063          = new ConcurrentHashMap<JspCacheKey, String>(100, 0.75f, 1);
064      private final Map<MiscCacheKey, String[]> miscCache 
065          = new ConcurrentHashMap<MiscCacheKey, String[]>(100, 0.75f, 1);
066      private final Map<PropertyCacheKey, StringValue> propertyCache 
067          = new ConcurrentHashMap<PropertyCacheKey, StringValue>(100, 0.75f, 1);
068      private final Map<ThemeConfigCacheKey, MeasureValue> themeCache 
069          = new ConcurrentHashMap<ThemeConfigCacheKey, MeasureValue>(100, 0.75f, 1);
070      
071      private TobagoConfig tobagoConfig;
072    
073      public ResourceManagerImpl(TobagoConfig tobagoConfig) {
074        this.tobagoConfig = tobagoConfig;
075        this.production = tobagoConfig.getProjectStage() == ProjectStage.Production;
076      }
077    
078      public void add(String resourceKey) {
079        if (LOG.isDebugEnabled()) {
080          LOG.debug("adding resourceKey = '{}'", resourceKey);
081        }
082        resourceList.put(resourceKey, "");
083      }
084    
085      public void add(String resourceKey, String value) {
086        if (LOG.isDebugEnabled()) {
087          LOG.debug("adding resourceKey = '{}' value= '{}'", resourceKey, value);
088        }
089        resourceList.put(resourceKey, value);
090      }
091    
092      @Deprecated
093      public String getJsp(UIViewRoot viewRoot, String name) {
094        String result = null;
095        if (name != null) {
096    
097          ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance());
098          JspCacheKey cacheKey = new JspCacheKey(clientKey, name);
099    
100          result = jspCache.get(cacheKey);
101          if (result != null) {
102            return result;
103          }
104          try {
105            result = (String) getPaths(clientKey, "",
106                JSP, name, "", false, true, true, null, true, false).get(0);
107            jspCache.put(cacheKey, result);
108          } catch (Exception e) {
109            LOG.error("name = '" + name + "' clientProperties = '" + clientKey.toString() + "'", e);
110          }
111        }
112        return result;
113      }
114    
115      @Deprecated
116      public String getProperty(UIViewRoot viewRoot, String bundle, String propertyKey) {
117        return getProperty(FacesContext.getCurrentInstance(), bundle, propertyKey);
118      }
119    
120      public String getProperty(FacesContext facesContext, String bundle, String propertyKey) {
121    
122        if (bundle != null && propertyKey != null) {
123          ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
124          PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey);
125          
126          StringValue result = propertyCache.get(cacheKey);
127          if (result == null) {
128            List properties = getPaths(clientKey, "", PROPERTY, bundle, "", false, true, false, propertyKey, true, false);
129            if (properties != null) {
130              result = new StringValue((String) properties.get(0));
131            } else {
132              result = StringValue.NULL;
133            }
134            propertyCache.put(cacheKey, result);
135          }
136          return result.getValue();
137        }
138        return null;
139      }
140    
141      @Deprecated
142      public Renderer getRenderer(UIViewRoot viewRoot, String rendererType) {
143        return getRenderer(FacesContext.getCurrentInstance(), rendererType);
144      }
145    
146      public Renderer getRenderer(FacesContext facesContext, String rendererType) {
147        Renderer renderer = null;
148    
149        if (rendererType != null) {
150          ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
151          RendererCacheKey cacheKey = new RendererCacheKey(clientKey, rendererType);
152    
153          renderer = rendererCache.get(cacheKey);
154          if (renderer != null) {
155            return renderer;
156          }
157          String simpleClassName = null;
158          try {
159            simpleClassName = getRendererClassName(rendererType);
160            List<Class> classes = getPaths(clientKey, "", TAG, simpleClassName, "", false, true, true, null, false, false);
161            if (classes != null && !classes.isEmpty()) {
162              Class clazz = classes.get(0);
163              renderer = (Renderer) clazz.newInstance();
164              rendererCache.put(cacheKey, renderer);
165            } else {
166              LOG.error("Don't find any RendererClass for " + simpleClassName + ". Please check you configuration.");
167            }
168          } catch (InstantiationException e) {
169            LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e);
170          } catch (IllegalAccessException e) {
171            LOG.error("name = '" + simpleClassName + "' clientProperties = '" + clientKey.toString() + "'", e);
172          }
173        }
174        return renderer;
175      }
176      
177      @Deprecated
178      public String[] getScripts(UIViewRoot viewRoot, String name) {
179        return getScripts(FacesContext.getCurrentInstance(), name);
180      }
181      
182      public String[] getScripts(FacesContext facesContext, String name) {
183        return getStrings(facesContext, name, null);
184      }
185    
186      @Deprecated
187      public String[] getStyles(UIViewRoot viewRoot, String name) {
188        return getStyles(FacesContext.getCurrentInstance(), name);
189      }
190    
191      public String[] getStyles(FacesContext facesContext, String name) {
192        return getStrings(facesContext, name, null);
193      }
194    
195      @Deprecated
196      public String getThemeProperty(UIViewRoot viewRoot, String bundle, String propertyKey) {
197        if (bundle != null && propertyKey != null) {
198    
199          ClientPropertiesKey clientKey = ClientPropertiesKey.get(FacesContext.getCurrentInstance());
200          PropertyCacheKey cacheKey = new PropertyCacheKey(clientKey, bundle, propertyKey);
201    
202          StringValue result = propertyCache.get(cacheKey);
203          if (result == null) {
204            List properties = 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(FacesContext facesContext, Configurable configurable, String name) {
218        return getThemeMeasure(facesContext, configurable.getRendererType(), configurable.getCurrentMarkup(), name);
219      }
220    
221      public Measure getThemeMeasure(FacesContext facesContext, String rendererType, Markup markup, String name) {
222    
223        ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
224        ThemeConfigCacheKey cacheKey = new ThemeConfigCacheKey(clientKey, rendererType, markup, name);
225    
226        MeasureValue result = themeCache.get(cacheKey);
227    
228        if (result == null) {
229          List properties = getPaths(clientKey, "", PROPERTY, "tobago-theme-config", "",
230              false, true, false, rendererType + "." + name, true, true);
231          if (properties != null) {
232            Measure measure = Measure.valueOf(properties.get(0));
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 = measure.add(summand);
241                }
242              }
243            }
244    
245            result = new MeasureValue(measure);
246          } else {
247            result = MeasureValue.NULL;  // to mark and cache that this value is undefined.
248          }
249          themeCache.put(cacheKey, result);
250        }
251        return result.getValue();
252      }
253      
254      @Deprecated
255      public String getImage(UIViewRoot viewRoot, String name) {
256        return getImage(FacesContext.getCurrentInstance(), name);
257      }
258      
259      public String getImage(FacesContext facesContext, String name) {
260        return getImage(facesContext, name, false);
261      }
262    
263      @Deprecated
264      public String getImage(UIViewRoot viewRoot, String name, boolean ignoreMissing) {
265        return getImage(FacesContext.getCurrentInstance(), name, ignoreMissing);
266      }
267    
268      public String getImage(FacesContext facesContext, String name, boolean ignoreMissing) {
269        if (name != null) {
270          int dot = name.lastIndexOf('.');
271          if (dot == -1) {
272            dot = name.length();
273          }
274    
275          ClientPropertiesKey clientKey = ClientPropertiesKey.get(facesContext);
276          ImageCacheKey cacheKey = new ImageCacheKey(clientKey, name);
277    
278          StringValue result = imageCache.get(cacheKey);
279          if (result == null) {
280            List paths = getPaths(clientKey, "", null, name.substring(0, dot),
281                name.substring(dot), false, true, true, null, true, ignoreMissing);
282            if (paths != null) {
283              result = new StringValue((String) paths.get(0));
284            } else {
285              result = StringValue.NULL;
286            }
287            imageCache.put(cacheKey, result);
288          }
289          if (LOG.isDebugEnabled()) {
290            if (result.getValue() == null) {
291              LOG.debug("Can't find image for \"{}\"", name);
292            }
293          }
294    
295          return result.getValue();
296        }
297    
298        return null;
299      }
300    
301      private List getPaths(
302          ClientPropertiesKey clientkey, String prefix, String subDir, String name, String suffix,
303          boolean reverseOrder, boolean single, boolean returnKey, String key, boolean returnStrings,
304          boolean ignoreMissing) {
305        List matches = new ArrayList();
306        String contentType = clientkey.getContentType();
307        Theme theme = clientkey.getTheme();
308        UserAgent browser = clientkey.getUserAgent();
309        List<String> locales = LocaleUtils.getLocaleSuffixList(clientkey.getLocale());
310    
311        String path;
312    
313        // check first the local web application directory
314        for (String localeSuffix : locales) {
315          if (production) {
316            path = makePath(name, MINIMIZE_SUFFIX, localeSuffix, suffix, key);
317            boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path);
318            if (found && (single || !returnStrings)) {
319              return matches;
320            }
321            if (!found) {
322              path = makePath(name, null, localeSuffix, suffix, key);
323              found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path);
324              if (found && (single || !returnStrings)) {
325                return matches;
326              }
327            }
328          } else {
329            path = makePath(name, null, localeSuffix, suffix, key);
330            boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path);
331            if (found && (single || !returnStrings)) {
332              return matches;
333            }
334          }
335        }
336    
337        // after that check the whole resources tree
338        // e.g. 1. application, 2. library or renderkit
339        for (Theme themeName : theme.getFallbackList()) { // theme loop
340          for (String resourceDirectory : tobagoConfig.getResourceDirs()) {
341            for (String browserType : browser.getFallbackList()) { // browser loop
342              for (String localeSuffix : locales) { // locale loop
343                if (production) {
344                  path = makePath(resourceDirectory, contentType, themeName, browserType, subDir, name, MINIMIZE_SUFFIX,
345                      localeSuffix, suffix, key);
346                  boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path);
347                  if (found && (single || !returnStrings)) {
348                    return matches;
349                  }
350                  if (!found) {
351                    path = makePath(resourceDirectory, contentType, themeName, browserType, subDir, name, null,
352                      localeSuffix, suffix, key);
353                    found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path);
354                    if (found && (single || !returnStrings)) {
355                      return matches;
356                    }
357                  }
358                } else {
359                  path = makePath(resourceDirectory, contentType, themeName, browserType, subDir, name, null,
360                      localeSuffix, suffix, key);
361                  boolean found = checkPath(prefix, reverseOrder, returnKey, returnStrings, matches, path);
362                  if (found && (single || !returnStrings)) {
363                    return matches;
364                  }
365                }
366              }
367            }
368          }
369        }
370    
371        if (matches.isEmpty()) {
372          if (!ignoreMissing) {
373            LOG.error("Path not found, and no fallback. Using empty string.\n"
374                + "resourceDirs = '" + tobagoConfig.getResourceDirs()
375                + "' contentType = '" + contentType
376                + "' theme = '" + theme
377                + "' browser = '" + browser
378                + "' subDir = '" + subDir
379                + "' name = '" + name
380                + "' suffix = '" + suffix
381                + "' key = '" + key
382                + "'"/*, new Exception()*/);
383          }
384          return null;
385        } else {
386          return matches;
387        }
388      }
389    
390      private boolean checkPath(
391          String prefix, boolean reverseOrder, boolean returnKey, boolean returnStrings,
392          List matches, String path) {
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 String makePath(
433          String project, String language, Theme theme, String browser,
434          String subDir, String name, String minimizeSuffix, String localeSuffix, String extension, String key) {
435        StringBuilder searchtext = new StringBuilder(64);
436    
437        searchtext.append('/');
438        searchtext.append(project);
439        searchtext.append('/');
440        searchtext.append(language);
441        searchtext.append('/');
442        searchtext.append(theme.getName());
443        searchtext.append('/');
444        searchtext.append(browser);
445        if (subDir != null) {
446          searchtext.append('/');
447          searchtext.append(subDir);
448        }
449        searchtext.append('/');
450        searchtext.append(name);
451        if (minimizeSuffix != null) {
452          searchtext.append(minimizeSuffix);
453        }
454        searchtext.append(localeSuffix);
455        searchtext.append(extension);
456        if (key != null) {
457          searchtext.append('/');
458          searchtext.append(key);
459        }
460    
461        return searchtext.toString();
462      }
463    
464      private String makePath(
465          String name, String minimizeSuffix, String localeSuffix, String extension, String key) {
466        StringBuilder searchtext = new StringBuilder(64);
467    
468        searchtext.append('/');
469        searchtext.append(name);
470        if (minimizeSuffix != null) {
471          searchtext.append(minimizeSuffix);
472        }
473        searchtext.append(localeSuffix);
474        searchtext.append(extension);
475        if (key != null) {
476          searchtext.append('/');
477          searchtext.append(key);
478        }
479    
480        return searchtext.toString();
481      }
482    
483      private String getRendererClassName(String rendererType) {
484        String name;
485        if (LOG.isDebugEnabled()) {
486          LOG.debug("rendererType = '{}'", rendererType);
487        }
488        if ("javax.faces.Text".equals(rendererType)) { // TODO: find a better way
489          name = RendererTypes.OUT;
490        } else {
491          name = rendererType;
492        }
493        name = name + "Renderer";
494        if (name.startsWith("javax.faces.")) { // FIXME: this is a hotfix from jsf1.0beta to jsf1.0fr
495          LOG.warn("patching renderer from {}", name);
496          name = name.substring("javax.faces.".length());
497          LOG.warn("patching renderer to {}", name);
498        }
499        return name;
500      }
501    
502      private String[] getStrings(FacesContext facesContext, String name, String type) {
503        String[] result = new String[0];
504        if (name != null) {
505          int dot = name.lastIndexOf('.');
506          if (dot == -1) {
507            dot = name.length();
508          }
509    
510          ClientPropertiesKey key = ClientPropertiesKey.get(facesContext);
511          MiscCacheKey miscKey = new MiscCacheKey(key, name);
512          String[] cacheResult = miscCache.get(miscKey);
513          if (cacheResult != null) {
514            return cacheResult;
515          }
516          try {
517            List matches = getPaths(key, "", type,
518                name.substring(0, dot), name.substring(dot), true, false, true, null, true, false);
519            if (matches != null) {
520              result = (String[]) matches.toArray(new String[matches.size()]);
521            }
522            miscCache.put(miscKey, result);
523          } catch (Exception e) {
524            LOG.error("name = '" + name + "' clientProperties = '" + key.toString() + "'", e);
525          }
526        }
527        return result;
528      }
529    
530    }