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