Coverage Report - org.apache.tapestry.services.impl.ComponentMessagesSourceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ComponentMessagesSourceImpl
0%
0/136
0%
0/44
2.389
 
 1  
 // Copyright 2004, 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry.services.impl;
 16  
 
 17  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
 18  
 import org.apache.commons.io.IOUtils;
 19  
 import org.apache.hivemind.ApplicationRuntimeException;
 20  
 import org.apache.hivemind.Messages;
 21  
 import org.apache.hivemind.Resource;
 22  
 import org.apache.hivemind.util.Defense;
 23  
 import org.apache.hivemind.util.LocalizedNameGenerator;
 24  
 import org.apache.tapestry.IComponent;
 25  
 import org.apache.tapestry.INamespace;
 26  
 import org.apache.tapestry.event.ResetEventListener;
 27  
 import org.apache.tapestry.resolver.IComponentResourceResolver;
 28  
 import org.apache.tapestry.services.ClasspathResourceFactory;
 29  
 import org.apache.tapestry.services.ComponentMessagesSource;
 30  
 import org.apache.tapestry.services.ComponentPropertySource;
 31  
 import org.apache.tapestry.util.text.LocalizedProperties;
 32  
 
 33  
 import java.io.BufferedInputStream;
 34  
 import java.io.IOException;
 35  
 import java.io.InputStream;
 36  
 import java.net.URL;
 37  
 import java.util.*;
 38  
 
 39  
 /**
 40  
  * Service used to access localized properties for a component.
 41  
  *
 42  
  * @author Howard Lewis Ship
 43  
  * @since 2.0.4
 44  
  */
 45  
 
 46  0
 public class ComponentMessagesSourceImpl implements ComponentMessagesSource, ResetEventListener
 47  
 {
 48  
     /**
 49  
      * The name of the component/application/etc property that will be used to
 50  
      * determine the encoding to use when loading the messages.
 51  
      */
 52  
 
 53  
     public static final String MESSAGES_ENCODING_PROPERTY_NAME = "org.apache.tapestry.messages-encoding";
 54  
 
 55  
     /**
 56  
      * The alternate file name of a namespace properties file to lookup. Can be used to override the default
 57  
      * behaviour which is to look for a <namespace name>.properties file to find localized/global properties.
 58  
      */
 59  
 
 60  
     public static final String NAMESPACE_PROPERTIES_NAME = "org.apache.tapestry.namespace-properties-name";
 61  
 
 62  
     private static final String SUFFIX = ".properties";
 63  
 
 64  0
     private Properties _emptyProperties = new Properties();
 65  
 
 66  
     /**
 67  
      * Map of Maps. The outer map is keyed on component specification location
 68  
      * (a{@link Resource}).  This inner map is keyed on locale and the value is
 69  
      * a {@link Properties}.
 70  
      */
 71  
 
 72  0
     private Map _componentCache = new ConcurrentHashMap();
 73  
 
 74  
     private ComponentPropertySource _componentPropertySource;
 75  
 
 76  
     /**
 77  
      * For locating resources on the classpath as well as context path.
 78  
      */
 79  
     private ClasspathResourceFactory _classpathResourceFactory;
 80  
 
 81  
     private IComponentResourceResolver _resourceResolver;
 82  
 
 83  
     /**
 84  
      * Returns an instance of {@link Properties} containing the properly
 85  
      * localized messages for the component, in the {@link Locale} identified by
 86  
      * the component's containing page.
 87  
      *
 88  
      * @param component
 89  
      *          The component to get properties for.
 90  
      *
 91  
      * @return A new {@link Properties} instance representing the localized properties for
 92  
      *          the specified component.
 93  
      */
 94  
 
 95  
     protected Properties getLocalizedProperties(IComponent component)
 96  
     {
 97  0
         Defense.notNull(component, "component");
 98  
 
 99  0
         Resource specificationLocation = component.getSpecification().getSpecificationLocation();
 100  
 
 101  0
         Locale locale = component.getPage().getLocale();
 102  
 
 103  0
         Map propertiesMap = findPropertiesMapForResource(specificationLocation);
 104  
 
 105  0
         Properties result = (Properties) propertiesMap.get(locale);
 106  
 
 107  0
         if (result == null)
 108  
         {
 109  
             // Not found, create it now.
 110  
 
 111  0
             result = assembleComponentProperties(component, specificationLocation,
 112  
                                                  propertiesMap, locale);
 113  
 
 114  0
             propertiesMap.put(locale, result);
 115  
         }
 116  
 
 117  0
         return result;
 118  
     }
 119  
 
 120  
     private Map findPropertiesMapForResource(Resource resource)
 121  
     {
 122  0
         Map result = (Map) _componentCache.get(resource);
 123  
 
 124  0
         if (result == null)
 125  
         {
 126  0
             result = new HashMap();
 127  
             
 128  0
             _componentCache.put(resource, result);
 129  
         }
 130  
 
 131  0
         return result;
 132  
     }
 133  
 
 134  
     private Properties getNamespaceProperties(IComponent component, Locale locale)
 135  
     {
 136  0
         INamespace namespace = component.getNamespace();
 137  
 
 138  0
         Resource namespaceLocation = namespace.getSpecificationLocation();
 139  
 
 140  0
         Map propertiesMap = findPropertiesMapForResource(namespaceLocation);
 141  
 
 142  0
         Properties result = (Properties) propertiesMap.get(locale);
 143  
 
 144  0
         if (result == null)
 145  
         {
 146  0
             result = new Properties();
 147  
 
 148  
             // recurse through parent properties
 149  
             
 150  0
             List spaceList = new ArrayList();
 151  0
             spaceList.add(namespace);
 152  
 
 153  0
             INamespace parent = namespace;
 154  0
             while (parent.getParentNamespace() != null)
 155  
             {
 156  0
                 parent = parent.getParentNamespace();
 157  
 
 158  0
                 spaceList.add(parent);
 159  
             }
 160  
 
 161  
             // reverse it so top most namespace comes first
 162  
 
 163  0
             for (int i=spaceList.size() - 1; i > -1; i--)
 164  
             {
 165  0
                 INamespace space = (INamespace)spaceList.get(i);
 166  
 
 167  0
                 result.putAll(assembleNamespaceProperties(space, findPropertiesMapForResource(space.getSpecificationLocation()), locale));
 168  
             }
 169  
 
 170  0
             propertiesMap.put(locale, result);
 171  
         }
 172  
 
 173  0
         return result;
 174  
     }
 175  
 
 176  
     private Properties assembleComponentProperties(IComponent component, Resource baseResourceLocation,
 177  
                                                    Map propertiesMap, Locale locale)
 178  
     {
 179  0
         List localizations =  findLocalizationsForResource(component, baseResourceLocation, locale,
 180  
                                                            component.getSpecification().getProperty(NAMESPACE_PROPERTIES_NAME));
 181  
 
 182  0
         Properties parent = null;
 183  0
         Properties assembledProperties = null;
 184  
 
 185  0
         Iterator i = localizations.iterator();
 186  
 
 187  0
         while(i.hasNext())
 188  
         {
 189  0
             ResourceLocalization rl = (ResourceLocalization) i.next();
 190  0
             Locale l = rl.getLocale();
 191  
 
 192  
             // Retrieve namespace properties for current locale (and parent
 193  
             // locales)
 194  
 
 195  0
             Properties namespaceProperties = getNamespaceProperties(component, l);
 196  
 
 197  
             // Use the namespace properties as default for assembled properties
 198  
 
 199  0
             assembledProperties = new Properties(namespaceProperties);
 200  
 
 201  
             // Read localized properties for component
 202  
             
 203  0
             Properties properties = readComponentProperties(component, l, rl.getResource(), null);
 204  
 
 205  
             // Override parent properties with current locale
 206  
 
 207  0
             if (parent != null)
 208  
             {
 209  0
                 if (properties != null)
 210  0
                     parent.putAll(properties);
 211  
             }
 212  
             else
 213  0
                 parent = properties;
 214  
 
 215  
             // Add to assembled properties
 216  0
             if (parent != null)
 217  0
                 assembledProperties.putAll(parent);
 218  
 
 219  
             // Save result in cache
 220  0
             propertiesMap.put(l, assembledProperties);
 221  0
         }
 222  
 
 223  0
         if (assembledProperties == null)
 224  0
             assembledProperties = new Properties();
 225  
 
 226  0
         return assembledProperties;
 227  
     }
 228  
 
 229  
     private Properties assembleNamespaceProperties(INamespace namespace, Map propertiesMap, Locale locale)
 230  
     {
 231  0
         List localizations = findLocalizationsForResource(namespace.getSpecificationLocation(), locale,
 232  
                                                           namespace.getPropertyValue(NAMESPACE_PROPERTIES_NAME));
 233  
         
 234  
         // Build them back up in reverse order.
 235  
 
 236  0
         Properties parent = _emptyProperties;
 237  
 
 238  0
         Iterator i = localizations.iterator();
 239  
 
 240  0
         while(i.hasNext())
 241  
         {
 242  0
             ResourceLocalization rl = (ResourceLocalization) i.next();
 243  
             
 244  0
             Locale l = rl.getLocale();
 245  
 
 246  0
             Properties properties = (Properties) propertiesMap.get(l);
 247  
 
 248  0
             if (properties == null)
 249  
             {
 250  0
                 properties = readNamespaceProperties(namespace, l, rl.getResource(), parent);
 251  
 
 252  0
                 propertiesMap.put(l, properties);
 253  
             }
 254  
 
 255  0
             parent = properties;
 256  0
         }
 257  
 
 258  0
         return parent;
 259  
 
 260  
     }
 261  
 
 262  
     /**
 263  
      * Finds the localizations of the provided resource. Returns a List of
 264  
      * {@link ResourceLocalization}(each pairing a locale with a localized
 265  
      * resource). The list is ordered from most general (i.e., "foo.properties")
 266  
      * to most specific (i.e., "foo_en_US_yokel.properties").
 267  
      */
 268  
 
 269  
     private List findLocalizationsForResource(Resource resource, Locale locale, String alternateName)
 270  
     {
 271  0
         List result = new ArrayList();
 272  
 
 273  0
         String baseName = null;
 274  0
         if (alternateName != null) {
 275  
 
 276  0
             baseName = alternateName.replace('.', '/');
 277  
         } else {
 278  
 
 279  0
             baseName = extractBaseName(resource);
 280  
         }
 281  
 
 282  0
         LocalizedNameGenerator g = new LocalizedNameGenerator(baseName, locale, SUFFIX);
 283  
 
 284  0
         while(g.more())
 285  
         {
 286  0
             String localizedName = g.next();
 287  0
             Locale l = g.getCurrentLocale();
 288  
 
 289  0
             Resource localizedResource = resource.getRelativeResource(localizedName);
 290  
 
 291  0
             if (localizedResource.getResourceURL() == null)
 292  
             {
 293  0
                 localizedResource = _classpathResourceFactory.newResource(baseName + SUFFIX);
 294  
             }
 295  
 
 296  0
             result.add(new ResourceLocalization(l, localizedResource));
 297  0
         }
 298  
 
 299  0
         Collections.reverse(result);
 300  
 
 301  0
         return result;
 302  
     }
 303  
 
 304  
     private List findLocalizationsForResource(IComponent component, Resource resource, Locale locale, String alternateName)
 305  
     {
 306  0
         List result = new ArrayList();
 307  
 
 308  0
         String baseName = null;
 309  0
         if (alternateName != null) {
 310  
 
 311  0
             baseName = alternateName.replace('.', '/');
 312  
         } else {
 313  
 
 314  0
             baseName = extractBaseName(resource);
 315  
         }
 316  
 
 317  0
         LocalizedNameGenerator g = new LocalizedNameGenerator(baseName, locale, "");
 318  
 
 319  0
         while(g.more())
 320  
         {
 321  0
             String localizedName = g.next();
 322  0
             Locale l = g.getCurrentLocale();
 323  
 
 324  0
             Resource localizedResource = _resourceResolver.findComponentResource(component, null, localizedName, SUFFIX, null);
 325  
             
 326  0
             if (localizedResource == null)
 327  0
                 continue;
 328  
 
 329  0
             result.add(new ResourceLocalization(l, localizedResource));
 330  0
         }
 331  
 
 332  0
         Collections.reverse(result);
 333  
 
 334  0
         return result;
 335  
     }
 336  
 
 337  
     private String extractBaseName(Resource baseResourceLocation)
 338  
     {
 339  0
         String fileName = baseResourceLocation.getName();
 340  0
         int dotx = fileName.lastIndexOf('.');
 341  
 
 342  0
         return dotx > -1 ? fileName.substring(0, dotx) : fileName;
 343  
     }
 344  
 
 345  
     private Properties readComponentProperties(IComponent component,
 346  
                                                Locale locale, Resource propertiesResource, Properties parent)
 347  
     {
 348  0
         String encoding = getComponentMessagesEncoding(component, locale);
 349  
 
 350  0
         return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent);
 351  
     }
 352  
 
 353  
     private Properties readNamespaceProperties(INamespace namespace,
 354  
                                                Locale locale, Resource propertiesResource, Properties parent)
 355  
     {
 356  0
         String encoding = getNamespaceMessagesEncoding(namespace, locale);
 357  
 
 358  0
         return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent);
 359  
     }
 360  
 
 361  
     private Properties readPropertiesResource(URL resourceURL, String encoding, Properties parent)
 362  
     {
 363  0
         if (resourceURL == null)
 364  0
             return parent;
 365  
 
 366  0
         Properties result = new Properties(parent);
 367  
 
 368  0
         LocalizedProperties wrapper = new LocalizedProperties(result);
 369  
 
 370  0
         InputStream input = null;
 371  
 
 372  
         try
 373  
         {
 374  0
             input = new BufferedInputStream(resourceURL.openStream());
 375  
 
 376  0
             if (encoding == null)
 377  0
                 wrapper.load(input);
 378  
             else
 379  0
                 wrapper.load(input, encoding);
 380  
 
 381  0
             input.close();
 382  
         }
 383  0
         catch (IOException ex)
 384  
         {
 385  0
             throw new ApplicationRuntimeException(ImplMessages.unableToLoadProperties(resourceURL, ex), ex);
 386  
         }
 387  
         finally
 388  
         {
 389  0
             IOUtils.closeQuietly(input);
 390  0
         }
 391  
 
 392  0
         return result;
 393  
     }
 394  
 
 395  
     /**
 396  
      * Clears the cache of read properties files.
 397  
      */
 398  
 
 399  
     public void resetEventDidOccur()
 400  
     {
 401  0
         _componentCache.clear();
 402  0
     }
 403  
 
 404  
     public Messages getMessages(IComponent component)
 405  
     {
 406  0
         return new ComponentMessages(component.getPage().getLocale(),
 407  
                                      getLocalizedProperties(component));
 408  
     }
 409  
 
 410  
     private String getComponentMessagesEncoding(IComponent component, Locale locale)
 411  
     {
 412  0
         String encoding = _componentPropertySource.getLocalizedComponentProperty(component, locale,
 413  
                                                                                  MESSAGES_ENCODING_PROPERTY_NAME);
 414  
 
 415  0
         if (encoding == null)
 416  0
             encoding = _componentPropertySource.
 417  
               getLocalizedComponentProperty(component, locale, TemplateSourceImpl.TEMPLATE_ENCODING_PROPERTY_NAME);
 418  
 
 419  0
         return encoding;
 420  
     }
 421  
 
 422  
     private String getNamespaceMessagesEncoding(INamespace namespace, Locale locale)
 423  
     {
 424  0
         return _componentPropertySource.
 425  
           getLocalizedNamespaceProperty(namespace, locale, MESSAGES_ENCODING_PROPERTY_NAME);
 426  
     }
 427  
 
 428  
     public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
 429  
     {
 430  0
         _componentPropertySource = componentPropertySource;
 431  0
     }
 432  
 
 433  
     public void setClasspathResourceFactory(ClasspathResourceFactory factory)
 434  
     {
 435  0
         _classpathResourceFactory = factory;
 436  0
     }
 437  
 
 438  
     public void setComponentResourceResolver(IComponentResourceResolver resourceResolver)
 439  
     {
 440  0
         _resourceResolver = resourceResolver;
 441  0
     }
 442  
 }