You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2011/05/28 20:43:50 UTC

svn commit: r1128750 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/internal/pageload/ main/java/org/apache/tapestry5/internal/services/ main/java/org/apache/tapestry5/services/pageload/ test/app5/WEB-INF/ test/app5/WE...

Author: hlship
Date: Sat May 28 18:43:49 2011
New Revision: 1128750

URL: http://svn.apache.org/viewvc?rev=1128750&view=rev
Log:
Add support for skinning message catalog files (for components and for the application message catalog)

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/app.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/
    tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/BARNEY/
    tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/BARNEY/app.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/BARNEY/
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/BARNEY/Index.properties
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/DefaultComponentResourceLocator.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSource.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/ComponentResourceLocator.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/SkinningTests.groovy
    tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.tml

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/DefaultComponentResourceLocator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/DefaultComponentResourceLocator.java?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/DefaultComponentResourceLocator.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/DefaultComponentResourceLocator.java Sat May 28 18:43:49 2011
@@ -14,7 +14,12 @@
 
 package org.apache.tapestry5.internal.pageload;
 
+import java.util.List;
+
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Mapper;
 import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.util.LocalizedNameGenerator;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
@@ -37,4 +42,17 @@ public class DefaultComponentResourceLoc
         return componentTemplateLocator.locateTemplate(model, selector.locale);
     }
 
+    public List<Resource> locateMessageCatalog(final Resource baseResource, ComponentResourceSelector selector)
+    {
+        String baseName = baseResource.getFile();
+
+        return F.flow(new LocalizedNameGenerator(baseName, selector.locale)).map(new Mapper<String, Resource>()
+        {
+            public Resource map(String element)
+            {
+                return baseResource.forFile(element);
+            }
+        }).toList();
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java Sat May 28 18:43:49 2011
@@ -32,6 +32,7 @@ import org.apache.tapestry5.services.Inv
 import org.apache.tapestry5.services.UpdateListener;
 import org.apache.tapestry5.services.messages.ComponentMessagesSource;
 import org.apache.tapestry5.services.messages.PropertiesFileParser;
+import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
 public class ComponentMessagesSourceImpl implements ComponentMessagesSource, UpdateListener
@@ -72,21 +73,22 @@ public class ComponentMessagesSourceImpl
 
     public ComponentMessagesSourceImpl(@Symbol(SymbolConstants.PRODUCTION_MODE)
     boolean productionMode, List<Resource> appCatalogResources, PropertiesFileParser parser,
-            ClasspathURLConverter classpathURLConverter)
+            ComponentResourceLocator resourceLocator, ClasspathURLConverter classpathURLConverter)
     {
-        this(productionMode, appCatalogResources, parser, new URLChangeTracker(classpathURLConverter));
+        this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter));
     }
 
-    ComponentMessagesSourceImpl(boolean productionMode, Resource appCatalogResource, PropertiesFileParser parser,
-            URLChangeTracker tracker)
+    ComponentMessagesSourceImpl(boolean productionMode, Resource appCatalogResource,
+            ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker)
     {
-        this(productionMode, Arrays.asList(appCatalogResource), parser, tracker);
+        this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker);
     }
 
     ComponentMessagesSourceImpl(boolean productionMode, List<Resource> appCatalogResources,
-            PropertiesFileParser parser, URLChangeTracker tracker)
+            ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker)
     {
-        messagesSource = new MessagesSourceImpl(productionMode, productionMode ? null : tracker, parser);
+        messagesSource = new MessagesSourceImpl(productionMode, productionMode ? null : tracker, resourceLocator,
+                parser);
 
         appCatalogBundle = createAppCatalogBundle(appCatalogResources);
     }
@@ -98,19 +100,19 @@ public class ComponentMessagesSourceImpl
 
     public Messages getMessages(ComponentModel componentModel, Locale locale)
     {
-        MessagesBundle bundle = new ComponentModelBundle(componentModel);
-
-        return messagesSource.getMessages(bundle, locale);
+        return getMessages(componentModel, new ComponentResourceSelector(locale));
     }
 
     public Messages getMessages(ComponentModel componentModel, ComponentResourceSelector selector)
     {
-        return getMessages(componentModel, selector.locale);
+        MessagesBundle bundle = new ComponentModelBundle(componentModel);
+
+        return messagesSource.getMessages(bundle, selector);
     }
 
     public Messages getApplicationCatalog(Locale locale)
     {
-        return messagesSource.getMessages(appCatalogBundle, locale);
+        return messagesSource.getMessages(appCatalogBundle, new ComponentResourceSelector(locale));
     }
 
     private MessagesBundle createAppCatalogBundle(List<Resource> resources)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSource.java?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSource.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSource.java Sat May 28 18:43:49 2011
@@ -1,10 +1,10 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2011 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,8 +17,7 @@ package org.apache.tapestry5.internal.se
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.services.InvalidationEventHub;
 import org.apache.tapestry5.services.UpdateListener;
-
-import java.util.Locale;
+import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
 public interface MessagesSource extends InvalidationEventHub, UpdateListener
 {
@@ -26,11 +25,13 @@ public interface MessagesSource extends 
      * Used to obtain a {@link Messages} instance for a particular component, within a particular locale. If the
      * component extends from another component, then its localized properties will merge with its parent's properties
      * (with the subclass overriding the super class on any conflicts).
-     *
-     * @param bundle defines the set of properties files to read, as well as a series of parent bundles to extend and
-     *               override
-     * @param locale
-     * @return the message catalog for the bundle, in the indicated locale
+     * 
+     * @param bundle
+     *            defines the set of properties files to read, as well as a series of parent bundles to extend and
+     *            override
+     * @param selector
+     *            defines the locale and other axes used to locate the necessary resources
+     * @return the message catalog for the bundle, for the indicated selector
      */
-    Messages getMessages(MessagesBundle bundle, Locale locale);
+    Messages getMessages(MessagesBundle bundle, ComponentResourceSelector selector);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java Sat May 28 18:43:49 2011
@@ -16,17 +16,18 @@ package org.apache.tapestry5.internal.se
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
+import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.internal.event.InvalidationEventHubImpl;
 import org.apache.tapestry5.internal.util.MultiKey;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
-import org.apache.tapestry5.ioc.util.LocalizedNameGenerator;
 import org.apache.tapestry5.services.messages.PropertiesFileParser;
+import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
+import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 
 /**
  * A utility class that encapsulates all the logic for reading properties files and assembling {@link Messages} from
@@ -47,14 +48,16 @@ public class MessagesSourceImpl extends 
 
     private final PropertiesFileParser propertiesFileParser;
 
+    private final ComponentResourceLocator resourceLocator;
+
     /**
-     * Keyed on bundle id and locale.
+     * Keyed on bundle id and ComponentResourceSelector.
      */
-    private final Map<MultiKey, Messages> messagesByBundleIdAndLocale = CollectionFactory.newConcurrentMap();
+    private final Map<MultiKey, Messages> messagesByBundleIdAndSelector = CollectionFactory.newConcurrentMap();
 
     /**
-     * Keyed on bundle id and locale, the cooked properties include properties inherited from less locale-specific
-     * properties files, or inherited from parent bundles.
+     * Keyed on bundle id and ComponentResourceSelector, the cooked properties include properties inherited from less
+     * locale-specific properties files, or inherited from parent bundles.
      */
     private final Map<MultiKey, Map<String, String>> cookedProperties = CollectionFactory.newConcurrentMap();
 
@@ -66,19 +69,20 @@ public class MessagesSourceImpl extends 
     private final Map<String, String> emptyMap = Collections.emptyMap();
 
     public MessagesSourceImpl(boolean productionMode, URLChangeTracker tracker,
-            PropertiesFileParser propertiesFileParser)
+            ComponentResourceLocator resourceLocator, PropertiesFileParser propertiesFileParser)
     {
         super(productionMode);
 
         this.tracker = tracker;
         this.propertiesFileParser = propertiesFileParser;
+        this.resourceLocator = resourceLocator;
     }
 
     public void checkForUpdates()
     {
         if (tracker != null && tracker.containsChanges())
         {
-            messagesByBundleIdAndLocale.clear();
+            messagesByBundleIdAndSelector.clear();
             cookedProperties.clear();
             rawProperties.clear();
 
@@ -88,26 +92,26 @@ public class MessagesSourceImpl extends 
         }
     }
 
-    public Messages getMessages(MessagesBundle bundle, Locale locale)
+    public Messages getMessages(MessagesBundle bundle, ComponentResourceSelector selector)
     {
-        MultiKey key = new MultiKey(bundle.getId(), locale);
+        MultiKey key = new MultiKey(bundle.getId(), selector);
 
-        Messages result = messagesByBundleIdAndLocale.get(key);
+        Messages result = messagesByBundleIdAndSelector.get(key);
 
         if (result == null)
         {
-            result = buildMessages(bundle, locale);
-            messagesByBundleIdAndLocale.put(key, result);
+            result = buildMessages(bundle, selector);
+            messagesByBundleIdAndSelector.put(key, result);
         }
 
         return result;
     }
 
-    private Messages buildMessages(MessagesBundle bundle, Locale locale)
+    private Messages buildMessages(MessagesBundle bundle, ComponentResourceSelector selector)
     {
-        Map<String, String> properties = findBundleProperties(bundle, locale);
+        Map<String, String> properties = findBundleProperties(bundle, selector);
 
-        return new MapMessages(locale, properties);
+        return new MapMessages(selector.locale, properties);
     }
 
     /**
@@ -115,12 +119,12 @@ public class MessagesSourceImpl extends 
      * reflect the properties of the bundles' parent (if any) for the locale, overalyed with any properties defined for
      * this bundle and its locale.
      */
-    private Map<String, String> findBundleProperties(MessagesBundle bundle, Locale locale)
+    private Map<String, String> findBundleProperties(MessagesBundle bundle, ComponentResourceSelector selector)
     {
         if (bundle == null)
             return emptyMap;
 
-        MultiKey key = new MultiKey(bundle.getId(), locale);
+        MultiKey key = new MultiKey(bundle.getId(), selector);
 
         Map<String, String> existing = cookedProperties.get(key);
 
@@ -133,30 +137,18 @@ public class MessagesSourceImpl extends 
 
         Resource propertiesResource = bundle.getBaseResource().withExtension("properties");
 
-        List<Resource> localizations = CollectionFactory.newList();
-
-        for (String localizedFile : new LocalizedNameGenerator(propertiesResource.getFile(), locale))
-        {
-            Resource localized = propertiesResource.forFile(localizedFile);
-
-            localizations.add(localized);
-        }
-
-        // We need them in least-specific to most-specific order, the opposite
-        // of how the LocalizedNameGenerator provides them.
-
-        Collections.reverse(localizations);
+        List<Resource> localizations = resourceLocator.locateMessageCatalog(propertiesResource, selector);
 
         // Localizations are now in least-specific to most-specific order.
 
-        Map<String, String> previous = findBundleProperties(bundle.getParent(), locale);
+        Map<String, String> previous = findBundleProperties(bundle.getParent(), selector);
 
-        for (Resource localization : localizations)
+        for (Resource localization : F.flow(localizations).reverse())
         {
             Map<String, String> rawProperties = getRawProperties(localization);
 
             // Woould be nice to write into the cookedProperties cache here,
-            // but we can't because we don't know the locale part of the MultiKey.
+            // but we can't because we don't know the selector part of the MultiKey.
 
             previous = extend(previous, rawProperties);
         }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/ComponentResourceLocator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/ComponentResourceLocator.java?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/ComponentResourceLocator.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/ComponentResourceLocator.java Sat May 28 18:43:49 2011
@@ -14,7 +14,10 @@
 
 package org.apache.tapestry5.services.pageload;
 
+import java.util.List;
+
 import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.util.LocalizedNameGenerator;
 import org.apache.tapestry5.model.ComponentModel;
 
 /**
@@ -39,4 +42,19 @@ public interface ComponentResourceLocato
      * @return Resource for component template, or null if not found
      */
     Resource locateTemplate(ComponentModel model, ComponentResourceSelector selector);
+
+    /**
+     * Locates the properties files that make up the message catalog for a specific component. The properties
+     * files are returned in order of specificity: the properties provided by the first resource override
+     * properties in later resources. Only resources specific to the class associated with the model
+     * should be concerned (message inheritance from base classes is handled by Tapestry).
+     * 
+     * @param baseResource
+     *            the resource for the base component properties file (i.e., with the ".properties" file extension)
+     * @param selector
+     *            defined the locale and other axes used to locate individual properties files
+     * @return list of properties file Resources
+     * @see LocalizedNameGenerator
+     */
+    List<Resource> locateMessageCatalog(Resource baseResource, ComponentResourceSelector selector);
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/app.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/app.properties?rev=1128750&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/app.properties (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/app.properties Sat May 28 18:43:49 2011
@@ -0,0 +1,2 @@
+app-message=Application catalog message
+app-perclient-message=Overridable app catalog message

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/BARNEY/app.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/BARNEY/app.properties?rev=1128750&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/BARNEY/app.properties (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/per-client/BARNEY/app.properties Sat May 28 18:43:49 2011
@@ -0,0 +1,2 @@
+app-perclient-message=Overriden app catalog message (Barney)
+

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/SkinningTests.groovy
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/SkinningTests.groovy?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/SkinningTests.groovy (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/SkinningTests.groovy Sat May 28 18:43:49 2011
@@ -24,7 +24,7 @@ class SkinningTests extends TapestryCore
 {
     @Test
     void template_selection() {
-        openLinks "reset session", "Fred Client"
+        openLinks "reset session"
 
         assertTitle "Default Layout"
 
@@ -36,4 +36,28 @@ class SkinningTests extends TapestryCore
 
         assertTitle "Barney Layout (French)"
     }
+
+    @Test
+    void application_catalog_overrides() {
+        openLinks "reset session"
+
+        assertText "app", "Application catalog message"
+        assertText "app-over", "Overridable app catalog message"
+
+        clickAndWait "Barney Client"
+
+        assertText "app-over", "Overriden app catalog message (Barney)"
+    }
+
+    @Test
+    void component_catalog_overrides() {
+        openLinks "reset session"
+
+        assertText "page", "Page catalog message"
+        assertText "page-over", "Overridable page catalog message"
+
+        clickAndWait "Barney Client"
+
+        assertText "page-over", "Overridden page catalog message (Barney)"
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy Sat May 28 18:43:49 2011
@@ -15,11 +15,11 @@
 package org.apache.tapestry5.integration.app5.services
 
 import org.apache.tapestry5.SymbolConstants
+import org.apache.tapestry5.func.F
 import org.apache.tapestry5.integration.app5.Client
 import org.apache.tapestry5.integration.app5.ClientTracker
 import org.apache.tapestry5.ioc.MappedConfiguration
 import org.apache.tapestry5.ioc.Resource
-import org.apache.tapestry5.ioc.ServiceBinder
 import org.apache.tapestry5.model.ComponentModel
 import org.apache.tapestry5.services.ApplicationStateManager
 import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer
@@ -61,6 +61,23 @@ class AppModule {
 
                 delegate.locateTemplate model, selector
             }
+
+            List<Resource> locateMessageCatalog(Resource baseResource, ComponentResourceSelector selector) {
+
+                def client = selector.getAxis(Client.class)
+
+                if (client != null) {
+
+                    def skinnedBase = baseResource.forFile("per-client/${client.name()}/${baseResource.file}")
+
+                    def skinned = F.flow(delegate.locateMessageCatalog(skinnedBase, selector))
+                    def standard = F.flow(delegate.locateMessageCatalog(baseResource, selector))
+
+                    return skinned.interleave(standard).toList()
+                }
+
+                delegate.locateMessageCatalog(baseResource, selector);
+            }
         }
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java Sat May 28 18:43:49 2011
@@ -28,6 +28,9 @@ import org.apache.tapestry5.ioc.internal
 import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.services.messages.ComponentMessagesSource;
+import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 /**
@@ -48,8 +51,24 @@ public class ComponentMessagesSourceImpl
     private final Resource simpleComponentResource = new ClasspathResource(
             "org/apache/tapestry5/internal/services/SimpleComponent.class");
 
-    private final ComponentMessagesSourceImpl source = new ComponentMessagesSourceImpl(false,
-            simpleComponentResource.forFile("AppCatalog.properties"), new PropertiesFileParserImpl(), tracker);
+    private ComponentMessagesSourceImpl source;
+
+    private ComponentResourceLocator resourceLocator;
+
+    @BeforeClass
+    public void setup()
+    {
+        resourceLocator = getService(ComponentResourceLocator.class);
+
+        source = new ComponentMessagesSourceImpl(false, simpleComponentResource.forFile("AppCatalog.properties"),
+                resourceLocator, new PropertiesFileParserImpl(), tracker);
+    }
+
+    @AfterClass
+    public void cleanup()
+    {
+        source = null;
+    }
 
     @Test
     public void simple_component()
@@ -212,7 +231,7 @@ public class ComponentMessagesSourceImpl
         List<Resource> resources = Arrays.asList(resource);
 
         ComponentMessagesSource source = new ComponentMessagesSourceImpl(true, resources,
-                new PropertiesFileParserImpl(), converter);
+                new PropertiesFileParserImpl(), resourceLocator, converter);
 
         Messages messages = source.getMessages(model, Locale.ENGLISH);
 

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.properties?rev=1128750&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.properties (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.properties Sat May 28 18:43:49 2011
@@ -0,0 +1,2 @@
+page-message=Page catalog message
+page-perclient-message=Overridable page catalog message

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.tml?rev=1128750&r1=1128749&r2=1128750&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.tml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/Index.tml Sat May 28 18:43:49 2011
@@ -1,5 +1,16 @@
-<t:layout xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"
-  xmlns:p="tapestry:parameter">
+<t:layout xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter">
+
+  <dl>
+    <dt>app</dt>
+    <dd id="app">${message:app-message}</dd>
+    <dt>app perclient</dt>
+    <dd id="app-over">${message:app-perclient-message}</dd>
+    <dt>page</dt>
+    <dd id="page">${message:page-message}</dd>
+    <dt>page perclient</dt>
+    <dd id="page-over">${message:page-perclient-message}</dd>
+  </dl>
+
 
   <ul>
     <li>

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/BARNEY/Index.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/BARNEY/Index.properties?rev=1128750&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/BARNEY/Index.properties (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/pages/per-client/BARNEY/Index.properties Sat May 28 18:43:49 2011
@@ -0,0 +1 @@
+page-perclient-message=Overridden page catalog message (Barney)