You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by th...@apache.org on 2014/06/16 03:18:21 UTC

git commit: TAP5-1611 : out-of-the-box way in Tapestry for replacing components

Repository: tapestry-5
Updated Branches:
  refs/heads/master 9cd156f37 -> e6a83e031


TAP5-1611 : out-of-the-box way in Tapestry for replacing components


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/e6a83e03
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/e6a83e03
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/e6a83e03

Branch: refs/heads/master
Commit: e6a83e031ad65eac99614f108392d06a8ff6c8f9
Parents: 9cd156f
Author: Thiago H. de Paula Figueiredo <th...@apache.org>
Authored: Sun Jun 15 22:17:47 2014 -0300
Committer: Thiago H. de Paula Figueiredo <th...@apache.org>
Committed: Sun Jun 15 22:17:47 2014 -0300

----------------------------------------------------------------------
 .../internal/ComponentReplacerImpl.java         | 89 ++++++++++++++++++++
 .../tapestry5/modules/TapestryModule.java       | 32 +++++++
 .../tapestry5/services/ComponentReplacer.java   | 51 +++++++++++
 tapestry-core/src/test/app3/Login.tml           |  6 ++
 tapestry-core/src/test/app3/OverridePage.tml    | 12 +++
 .../src/test/app3/OverridePageAtComponent.tml   | 12 +++
 tapestry-core/src/test/app3/OverridenPage.tml   |  8 ++
 .../app3/AdditionalIntegrationTests.java        | 21 +++++
 .../app3/components/OverrideComponent.java      | 24 ++++++
 .../app3/components/OverridenComponent.java     | 24 ++++++
 .../integration/app3/mixins/OverrideMixin.java  | 32 +++++++
 .../integration/app3/mixins/OverridenMixin.java | 30 +++++++
 .../integration/app3/pages/OverridePage.java    | 18 ++++
 .../app3/pages/OverridePageAtComponent.java     | 35 ++++++++
 .../integration/app3/pages/OverridenPage.java   | 18 ++++
 .../integration/app3/services/AppModule.java    | 16 +++-
 16 files changed, 427 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
new file mode 100644
index 0000000..8d7fc75
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
@@ -0,0 +1,89 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.tapestry5.ioc.util.CaseInsensitiveMap;
+import org.apache.tapestry5.services.ComponentReplacer;
+import org.slf4j.Logger;
+
+public class ComponentReplacerImpl implements ComponentReplacer
+{
+
+    final Logger logger;
+    final private Map<Class, Class> replacements;
+    final private Map<String, Class> nameToClass;
+
+    @SuppressWarnings("rawtypes")
+    public ComponentReplacerImpl(Map<Class, Class> contributions, Logger logger)
+    {
+
+        this.logger = logger;
+        this.replacements = Collections.unmodifiableMap(contributions);
+        Map<String, Class> nameToClass = new HashMap<String, Class>();
+
+        int maxLength = 0;
+
+        for (Class<?> clasz : contributions.keySet())
+        {
+
+            final String name = clasz.getName();
+            if (name.length() > maxLength) {
+                maxLength = name.length();
+            }
+            nameToClass.put(name, contributions.get(clasz));
+
+        }
+
+        this.nameToClass = Collections.unmodifiableMap(nameToClass);
+        
+        if (replacements.size() > 0 && logger.isInfoEnabled())
+        {
+            
+            StringBuilder builder = new StringBuilder(1000);
+            final String format = "%" + maxLength + "s: %s\n";
+            builder.append("Component replacements (including components, pages and mixins):\n");
+            List<String> names = new ArrayList<String>(nameToClass.keySet());
+            Collections.sort(names);
+            
+            for (String name : names) {
+                builder.append(String.format(format, name, nameToClass.get(name).getName()));
+            }
+            
+            logger.info(builder.toString());
+            
+        }
+
+    }
+
+    @Override
+    public Map<Class, Class> getReplacements()
+    {
+        return replacements;
+    }
+
+    @Override
+    public Class getReplacement(String className)
+    {
+        return nameToClass.get(className);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index 63c114b..1a08540 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -65,7 +65,9 @@ import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.StrategyRegistry;
 import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.plastic.MethodAdvice;
 import org.apache.tapestry5.plastic.MethodDescription;
+import org.apache.tapestry5.plastic.MethodInvocation;
 import org.apache.tapestry5.runtime.Component;
 import org.apache.tapestry5.runtime.ComponentResourcesAware;
 import org.apache.tapestry5.runtime.RenderCommand;
@@ -95,6 +97,7 @@ import org.slf4j.Logger;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
 import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.math.BigDecimal;
@@ -372,6 +375,7 @@ public final class TapestryModule
         binder.bind(DateUtilities.class, DateUtilitiesImpl.class);
         binder.bind(PartialTemplateRenderer.class, PartialTemplateRendererImpl.class);
         binder.bind(org.apache.tapestry5.services.exceptions.ExceptionReporter.class, ExceptionReporterImpl.class);
+        binder.bind(ComponentReplacer.class, ComponentReplacerImpl.class).eagerLoad();
     }
 
     // ========================================================================
@@ -2660,4 +2664,32 @@ public final class TapestryModule
     {
         return strategyBuilder.build(ValueLabelProvider.class, configuration);
     }
+    
+    @Advise(serviceInterface = ComponentInstantiatorSource.class)
+    public static void componentReplacer(MethodAdviceReceiver methodAdviceReceiver, 
+          final ComponentReplacer componentReplacer) throws NoSuchMethodException, SecurityException {
+        
+        if (componentReplacer.getReplacements().size() > 0) {
+            
+            MethodAdvice advice = new MethodAdvice()
+            {
+                @Override
+                public void advise(MethodInvocation invocation)
+                {
+                    String className = (String) invocation.getParameter(0);
+                    final Class<?> replacement = componentReplacer.getReplacement(className);
+                    if (replacement != null) 
+                    {
+                        invocation.setParameter(0, replacement.getName());
+                    }
+                    invocation.proceed();
+                }
+            };
+            
+            methodAdviceReceiver.adviseMethod(
+                    ComponentInstantiatorSource.class.getMethod("getInstantiator", String.class), advice);
+            
+        }
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
new file mode 100644
index 0000000..375a8c6
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
@@ -0,0 +1,51 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.services;
+
+import java.util.Map;
+
+import org.apache.tapestry5.ioc.MethodAdviceReceiver;
+import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
+
+/**
+ * Service that allows replacing one component, page or mixin class by another without changing the sources.
+ * This service shouldn't be used directly: it's not an internal service just because it receives
+ * contributions.
+ * 
+ * Contributions to it are mapped: the key is the component, page or mixin class to be
+ * replaced, the value is the replacement.
+ *
+ * @since 5.4
+ * @see ComponentClassResolver.
+ */
+@UsesMappedConfiguration(key = Class.class, value = Class.class)
+public interface ComponentReplacer
+{
+
+    /**
+     * Returns an immutable map of replacements. Internal use only.
+     * 
+     * @return a {@link Map}.
+     */
+    Map<Class, Class> getReplacements();
+    
+    /**
+     * Returns the replacement for a class given its name.
+     * @param className the fully qualified class name.
+     * @return a {@link Class} or null.
+     */
+    Class getReplacement(String className);
+    
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/Login.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/app3/Login.tml b/tapestry-core/src/test/app3/Login.tml
index 9fba45c..6937cd2 100644
--- a/tapestry-core/src/test/app3/Login.tml
+++ b/tapestry-core/src/test/app3/Login.tml
@@ -22,6 +22,12 @@
             <li>
                 <t:pagelink page="BeanEditorWithOverridenCssClassesDemo">BeanEditor with overriden CSS classes demo</t:pagelink>
             </li>
+            <li>
+                <t:pagelink page="OverridenPage">ComponentReplacer demo</t:pagelink>
+            </li>
+            <li>
+                <t:pagelink page="OverridePageAtComponent">ComponentReplacer demo (using @Component to declare component instances)</t:pagelink>
+            </li>
         </ul>
 
     </body>

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/OverridePage.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/app3/OverridePage.tml b/tapestry-core/src/test/app3/OverridePage.tml
new file mode 100644
index 0000000..cddb3e3
--- /dev/null
+++ b/tapestry-core/src/test/app3/OverridePage.tml
@@ -0,0 +1,12 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    <head>
+        <title>Override Page</title>
+    </head>
+    <body>
+        <h1>Override Page</h1>
+        <p>
+        	Link with mixin <t:pagelink t:page="Index" t:mixins="OverridenMixin">Index</t:pagelink>
+        </p>
+        <t:overridenComponent/>
+    </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/OverridePageAtComponent.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/app3/OverridePageAtComponent.tml b/tapestry-core/src/test/app3/OverridePageAtComponent.tml
new file mode 100644
index 0000000..d10610b
--- /dev/null
+++ b/tapestry-core/src/test/app3/OverridePageAtComponent.tml
@@ -0,0 +1,12 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    <head>
+        <title>Override Page</title>
+    </head>
+    <body>
+        <h1>Override Page with @Component</h1>
+        <p>
+        	Link with mixin <a t:id="link" href="#">Index</a>
+        </p>
+        <div t:id="overridenComponent"/>
+    </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/OverridenPage.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/app3/OverridenPage.tml b/tapestry-core/src/test/app3/OverridenPage.tml
new file mode 100644
index 0000000..cf9b1a7
--- /dev/null
+++ b/tapestry-core/src/test/app3/OverridenPage.tml
@@ -0,0 +1,8 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    <head>
+        <title>Overriden</title>
+    </head>
+    <body>
+        <h1>Overriden</h1>
+    </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
index 8e03e34..774df44 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
@@ -15,12 +15,14 @@
 package org.apache.tapestry5.integration.app3;
 
 import org.apache.tapestry5.integration.TapestryCoreTestCase;
+import org.apache.tapestry5.test.TapestryTestConfiguration;
 import org.testng.annotations.Test;
 
 /**
  * Additional integration tests that do not fit with the main group due to the need for special
  * configuration.
  */
+@TapestryTestConfiguration(webAppFolder = "src/test/app3")
 public class AdditionalIntegrationTests extends TapestryCoreTestCase
 {
     /**
@@ -88,4 +90,23 @@ public class AdditionalIntegrationTests extends TapestryCoreTestCase
 
         assertTextPresent("Communication with the server failed: Server-side exception.");
     }
+    
+    // TAP5-1611
+    @Test
+    public void component_replacer() 
+    {
+        
+        final String[] pageNames = {"ComponentReplacer demo", "ComponentReplacer demo (using @Component to declare component instances)"};
+        for (String pageName : pageNames)
+        {
+            openLinks(pageName);
+            
+            assertTrue(isElementPresent("overrideMixin"));
+            assertFalse(isElementPresent("overridenMixin"));
+            assertTrue(isElementPresent("overrideComponent"));
+            assertFalse(isElementPresent("overridenComponent"));
+        }
+        
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
new file mode 100644
index 0000000..a43a745
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
@@ -0,0 +1,24 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.components;
+
+import org.apache.tapestry5.MarkupWriter;
+
+public class OverrideComponent
+{
+    void beginRender(MarkupWriter writer) {
+        writer.element("div", "id", "overrideComponent").element("p").text("Override component");
+        writer.end(); // div
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
new file mode 100644
index 0000000..01f5130
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
@@ -0,0 +1,24 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.components;
+
+import org.apache.tapestry5.MarkupWriter;
+
+public class OverridenComponent
+{
+    void beginRender(MarkupWriter writer) {
+        writer.element("div", "id", "overridenComponent").element("p").text("Overriden component");
+        writer.end(); // div
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
new file mode 100644
index 0000000..5614178
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
@@ -0,0 +1,32 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.mixins;
+
+import org.apache.tapestry5.ClientElement;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.InjectContainer;
+import org.apache.tapestry5.annotations.MixinAfter;
+import org.apache.tapestry5.dom.Element;
+
+@MixinAfter
+public class OverrideMixin
+{
+    @InjectContainer
+    private ClientElement clientElement;
+    
+    void afterRender(MarkupWriter writer) {
+        final Element element = writer.getDocument().getElementById(clientElement.getClientId());
+        element.element("span", "id", "overrideMixin").text(" [Override mixin]");
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
new file mode 100644
index 0000000..50ea142
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
@@ -0,0 +1,30 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.mixins;
+
+import org.apache.tapestry5.ClientElement;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.InjectContainer;
+import org.apache.tapestry5.dom.Element;
+
+public class OverridenMixin
+{
+    @InjectContainer
+    private ClientElement clientElement;
+    
+    void afterRender(MarkupWriter writer) {
+        final Element element = writer.getDocument().getElementById(clientElement.getClientId());
+        element.element("span", "id", "overridenMixin").text(" [Overriden mixin]");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
new file mode 100644
index 0000000..7f803b7
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
@@ -0,0 +1,18 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.pages;
+
+public class OverridePage
+{
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
new file mode 100644
index 0000000..33ba903
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
@@ -0,0 +1,35 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.pages;
+
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.MixinClasses;
+import org.apache.tapestry5.corelib.components.PageLink;
+import org.apache.tapestry5.integration.app3.components.OverridenComponent;
+import org.apache.tapestry5.integration.app3.mixins.OverridenMixin;
+
+/**
+ * Same as OverridePage, but using @Component to declare components.
+ */
+public class OverridePageAtComponent
+{
+    
+    @Component(id = "link", parameters={"page=Index"})
+    @MixinClasses(OverridenMixin.class)
+    private PageLink pageLink;
+    
+    @Component
+    private OverridenComponent overridenComponent;
+    
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
new file mode 100644
index 0000000..6a03091
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
@@ -0,0 +1,18 @@
+// Copyright 2014 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.integration.app3.pages;
+
+public class OverridenPage
+{
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
index 2f4a73d..58648bd 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
@@ -15,10 +15,17 @@
 package org.apache.tapestry5.integration.app3.services;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.integration.app3.components.OverrideComponent;
+import org.apache.tapestry5.integration.app3.components.OverridenComponent;
+import org.apache.tapestry5.integration.app3.mixins.OverrideMixin;
+import org.apache.tapestry5.integration.app3.mixins.OverridenMixin;
+import org.apache.tapestry5.integration.app3.pages.OverridePage;
+import org.apache.tapestry5.integration.app3.pages.OverridenPage;
 import org.apache.tapestry5.ioc.Configuration;
 import org.apache.tapestry5.ioc.MappedConfiguration;
 import org.apache.tapestry5.ioc.OrderedConfiguration;
 import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.services.ComponentReplacer;
 import org.apache.tapestry5.services.DisplayBlockContribution;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.compatibility.Compatibility;
@@ -58,7 +65,7 @@ public class AppModule
         configuration.add(SymbolConstants.FORM_FIELD_CSS_CLASS, FORM_FIELD_CSS_CLASS_VALUE);
         
     }
-
+    
     @Contribute(Compatibility.class)
     public static void disableBackwardsCompatibleFeatures(MappedConfiguration<Trait, Boolean> configuration)
     {
@@ -78,5 +85,12 @@ public class AppModule
             }
         }, "before:*");
     }
+    
+    @Contribute(ComponentReplacer.class)
+    public static void overridePageAndComponentAndMixin(MappedConfiguration<Class, Class> configuration) {
+        configuration.add(OverridenPage.class, OverridePage.class);
+        configuration.add(OverridenComponent.class, OverrideComponent.class);
+        configuration.add(OverridenMixin.class, OverrideMixin.class);
+    }
 
 }


Re: git commit: TAP5-1611 : out-of-the-box way in Tapestry for replacing components

Posted by "Nourredine K." <no...@gmail.com>.
I thought about a scenario where a component library overrides a tapestry
component and that you would replace it in its turn with your own
implementation. Also the case where 2 different component libraries
override the same component/mixin or page (you would choose which one to
keep - maybe a warning should be logged in this case).
But the .override() method should address both cases. So everything is ok
for me : )

Thank you!

Re: git commit: TAP5-1611 : out-of-the-box way in Tapestry for replacing components

Posted by Thiago H de Paula Figueiredo <th...@gmail.com>.
On Mon, 16 Jun 2014 05:17:43 -0300, Nourredine K. <no...@gmail.com>  
wrote:

> Hi Thiago!

Hi!

> Nice feature! Simple but very useful. Thanks you.

:)

> Just a thought. What about multiple contributions to the  
> ComponentReplacer
> for the same page/component or mixin ?

They would work in the same way every MappedConfiguration would: use  
configuration.override() instead of configuration.add().

> In a next improvement, it could be very handy to allow some kind of  
> ordered configuration.

I thought about it, but then I couldn't find a good scenario in which  
there would be more than a replacement for the same page, component or  
mixin. If you think of some, please let me know. I imagine this new  
feature/service to not be used often. Making it an ordered configuration  
would make contributions to it harder with almost no.

Changing it from MappedConfiguration to OrderedConfiguration would be a  
backward-compatible change so, if you want to present some compelling  
reason to do it, please do it quickly, so we can implement the change  
before 5.4 final is released.

-- 
Thiago H. de Paula Figueiredo
Tapestry, Java and Hibernate consultant and developer
http://machina.com.br

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tapestry.apache.org
For additional commands, e-mail: dev-help@tapestry.apache.org


Re: git commit: TAP5-1611 : out-of-the-box way in Tapestry for replacing components

Posted by "Nourredine K." <no...@gmail.com>.
Hi Thiago!

Nice feature! Simple but very useful. Thanks you.

Just a thought. What about multiple contributions to the ComponentReplacer
for the same page/component or mixin ?
In a next improvement, it could be very handy to allow some kind of ordered
configuration.

Nourredine.


2014-06-16 3:18 GMT+02:00 <th...@apache.org>:

> Repository: tapestry-5
> Updated Branches:
>   refs/heads/master 9cd156f37 -> e6a83e031
>
>
> TAP5-1611 : out-of-the-box way in Tapestry for replacing components
>
>
> Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
> Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/e6a83e03
> Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/e6a83e03
> Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/e6a83e03
>
> Branch: refs/heads/master
> Commit: e6a83e031ad65eac99614f108392d06a8ff6c8f9
> Parents: 9cd156f
> Author: Thiago H. de Paula Figueiredo <th...@apache.org>
> Authored: Sun Jun 15 22:17:47 2014 -0300
> Committer: Thiago H. de Paula Figueiredo <th...@apache.org>
> Committed: Sun Jun 15 22:17:47 2014 -0300
>
> ----------------------------------------------------------------------
>  .../internal/ComponentReplacerImpl.java         | 89 ++++++++++++++++++++
>  .../tapestry5/modules/TapestryModule.java       | 32 +++++++
>  .../tapestry5/services/ComponentReplacer.java   | 51 +++++++++++
>  tapestry-core/src/test/app3/Login.tml           |  6 ++
>  tapestry-core/src/test/app3/OverridePage.tml    | 12 +++
>  .../src/test/app3/OverridePageAtComponent.tml   | 12 +++
>  tapestry-core/src/test/app3/OverridenPage.tml   |  8 ++
>  .../app3/AdditionalIntegrationTests.java        | 21 +++++
>  .../app3/components/OverrideComponent.java      | 24 ++++++
>  .../app3/components/OverridenComponent.java     | 24 ++++++
>  .../integration/app3/mixins/OverrideMixin.java  | 32 +++++++
>  .../integration/app3/mixins/OverridenMixin.java | 30 +++++++
>  .../integration/app3/pages/OverridePage.java    | 18 ++++
>  .../app3/pages/OverridePageAtComponent.java     | 35 ++++++++
>  .../integration/app3/pages/OverridenPage.java   | 18 ++++
>  .../integration/app3/services/AppModule.java    | 16 +++-
>  16 files changed, 427 insertions(+), 1 deletion(-)
> ----------------------------------------------------------------------
>
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
> b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
> new file mode 100644
> index 0000000..8d7fc75
> --- /dev/null
> +++
> b/tapestry-core/src/main/java/org/apache/tapestry5/internal/ComponentReplacerImpl.java
> @@ -0,0 +1,89 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.internal;
> +
> +import java.util.ArrayList;
> +import java.util.Collections;
> +import java.util.HashMap;
> +import java.util.List;
> +import java.util.Map;
> +import java.util.Map.Entry;
> +import java.util.Set;
> +
> +import org.apache.tapestry5.ioc.util.CaseInsensitiveMap;
> +import org.apache.tapestry5.services.ComponentReplacer;
> +import org.slf4j.Logger;
> +
> +public class ComponentReplacerImpl implements ComponentReplacer
> +{
> +
> +    final Logger logger;
> +    final private Map<Class, Class> replacements;
> +    final private Map<String, Class> nameToClass;
> +
> +    @SuppressWarnings("rawtypes")
> +    public ComponentReplacerImpl(Map<Class, Class> contributions, Logger
> logger)
> +    {
> +
> +        this.logger = logger;
> +        this.replacements = Collections.unmodifiableMap(contributions);
> +        Map<String, Class> nameToClass = new HashMap<String, Class>();
> +
> +        int maxLength = 0;
> +
> +        for (Class<?> clasz : contributions.keySet())
> +        {
> +
> +            final String name = clasz.getName();
> +            if (name.length() > maxLength) {
> +                maxLength = name.length();
> +            }
> +            nameToClass.put(name, contributions.get(clasz));
> +
> +        }
> +
> +        this.nameToClass = Collections.unmodifiableMap(nameToClass);
> +
> +        if (replacements.size() > 0 && logger.isInfoEnabled())
> +        {
> +
> +            StringBuilder builder = new StringBuilder(1000);
> +            final String format = "%" + maxLength + "s: %s\n";
> +            builder.append("Component replacements (including components,
> pages and mixins):\n");
> +            List<String> names = new
> ArrayList<String>(nameToClass.keySet());
> +            Collections.sort(names);
> +
> +            for (String name : names) {
> +                builder.append(String.format(format, name,
> nameToClass.get(name).getName()));
> +            }
> +
> +            logger.info(builder.toString());
> +
> +        }
> +
> +    }
> +
> +    @Override
> +    public Map<Class, Class> getReplacements()
> +    {
> +        return replacements;
> +    }
> +
> +    @Override
> +    public Class getReplacement(String className)
> +    {
> +        return nameToClass.get(className);
> +    }
> +
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
> b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
> index 63c114b..1a08540 100644
> ---
> a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
> +++
> b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
> @@ -65,7 +65,9 @@ import org.apache.tapestry5.ioc.util.AvailableValues;
>  import org.apache.tapestry5.ioc.util.StrategyRegistry;
>  import org.apache.tapestry5.json.JSONArray;
>  import org.apache.tapestry5.json.JSONObject;
> +import org.apache.tapestry5.plastic.MethodAdvice;
>  import org.apache.tapestry5.plastic.MethodDescription;
> +import org.apache.tapestry5.plastic.MethodInvocation;
>  import org.apache.tapestry5.runtime.Component;
>  import org.apache.tapestry5.runtime.ComponentResourcesAware;
>  import org.apache.tapestry5.runtime.RenderCommand;
> @@ -95,6 +97,7 @@ import org.slf4j.Logger;
>  import javax.servlet.ServletContext;
>  import javax.servlet.http.HttpServletRequest;
>  import javax.servlet.http.HttpServletResponse;
> +
>  import java.io.IOException;
>  import java.lang.annotation.Annotation;
>  import java.math.BigDecimal;
> @@ -372,6 +375,7 @@ public final class TapestryModule
>          binder.bind(DateUtilities.class, DateUtilitiesImpl.class);
>          binder.bind(PartialTemplateRenderer.class,
> PartialTemplateRendererImpl.class);
>
>  binder.bind(org.apache.tapestry5.services.exceptions.ExceptionReporter.class,
> ExceptionReporterImpl.class);
> +        binder.bind(ComponentReplacer.class,
> ComponentReplacerImpl.class).eagerLoad();
>      }
>
>      //
> ========================================================================
> @@ -2660,4 +2664,32 @@ public final class TapestryModule
>      {
>          return strategyBuilder.build(ValueLabelProvider.class,
> configuration);
>      }
> +
> +    @Advise(serviceInterface = ComponentInstantiatorSource.class)
> +    public static void componentReplacer(MethodAdviceReceiver
> methodAdviceReceiver,
> +          final ComponentReplacer componentReplacer) throws
> NoSuchMethodException, SecurityException {
> +
> +        if (componentReplacer.getReplacements().size() > 0) {
> +
> +            MethodAdvice advice = new MethodAdvice()
> +            {
> +                @Override
> +                public void advise(MethodInvocation invocation)
> +                {
> +                    String className = (String)
> invocation.getParameter(0);
> +                    final Class<?> replacement =
> componentReplacer.getReplacement(className);
> +                    if (replacement != null)
> +                    {
> +                        invocation.setParameter(0, replacement.getName());
> +                    }
> +                    invocation.proceed();
> +                }
> +            };
> +
> +            methodAdviceReceiver.adviseMethod(
> +
>  ComponentInstantiatorSource.class.getMethod("getInstantiator",
> String.class), advice);
> +
> +        }
> +    }
> +
>  }
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
> b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
> new file mode 100644
> index 0000000..375a8c6
> --- /dev/null
> +++
> b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentReplacer.java
> @@ -0,0 +1,51 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +
> +package org.apache.tapestry5.services;
> +
> +import java.util.Map;
> +
> +import org.apache.tapestry5.ioc.MethodAdviceReceiver;
> +import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
> +
> +/**
> + * Service that allows replacing one component, page or mixin class by
> another without changing the sources.
> + * This service shouldn't be used directly: it's not an internal service
> just because it receives
> + * contributions.
> + *
> + * Contributions to it are mapped: the key is the component, page or
> mixin class to be
> + * replaced, the value is the replacement.
> + *
> + * @since 5.4
> + * @see ComponentClassResolver.
> + */
> +@UsesMappedConfiguration(key = Class.class, value = Class.class)
> +public interface ComponentReplacer
> +{
> +
> +    /**
> +     * Returns an immutable map of replacements. Internal use only.
> +     *
> +     * @return a {@link Map}.
> +     */
> +    Map<Class, Class> getReplacements();
> +
> +    /**
> +     * Returns the replacement for a class given its name.
> +     * @param className the fully qualified class name.
> +     * @return a {@link Class} or null.
> +     */
> +    Class getReplacement(String className);
> +
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/Login.tml
> ----------------------------------------------------------------------
> diff --git a/tapestry-core/src/test/app3/Login.tml
> b/tapestry-core/src/test/app3/Login.tml
> index 9fba45c..6937cd2 100644
> --- a/tapestry-core/src/test/app3/Login.tml
> +++ b/tapestry-core/src/test/app3/Login.tml
> @@ -22,6 +22,12 @@
>              <li>
>                  <t:pagelink
> page="BeanEditorWithOverridenCssClassesDemo">BeanEditor with overriden CSS
> classes demo</t:pagelink>
>              </li>
> +            <li>
> +                <t:pagelink page="OverridenPage">ComponentReplacer
> demo</t:pagelink>
> +            </li>
> +            <li>
> +                <t:pagelink
> page="OverridePageAtComponent">ComponentReplacer demo (using @Component to
> declare component instances)</t:pagelink>
> +            </li>
>          </ul>
>
>      </body>
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/OverridePage.tml
> ----------------------------------------------------------------------
> diff --git a/tapestry-core/src/test/app3/OverridePage.tml
> b/tapestry-core/src/test/app3/OverridePage.tml
> new file mode 100644
> index 0000000..cddb3e3
> --- /dev/null
> +++ b/tapestry-core/src/test/app3/OverridePage.tml
> @@ -0,0 +1,12 @@
> +<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
> +    <head>
> +        <title>Override Page</title>
> +    </head>
> +    <body>
> +        <h1>Override Page</h1>
> +        <p>
> +               Link with mixin <t:pagelink t:page="Index"
> t:mixins="OverridenMixin">Index</t:pagelink>
> +        </p>
> +        <t:overridenComponent/>
> +    </body>
> +</html>
> \ No newline at end of file
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/OverridePageAtComponent.tml
> ----------------------------------------------------------------------
> diff --git a/tapestry-core/src/test/app3/OverridePageAtComponent.tml
> b/tapestry-core/src/test/app3/OverridePageAtComponent.tml
> new file mode 100644
> index 0000000..d10610b
> --- /dev/null
> +++ b/tapestry-core/src/test/app3/OverridePageAtComponent.tml
> @@ -0,0 +1,12 @@
> +<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
> +    <head>
> +        <title>Override Page</title>
> +    </head>
> +    <body>
> +        <h1>Override Page with @Component</h1>
> +        <p>
> +               Link with mixin <a t:id="link" href="#">Index</a>
> +        </p>
> +        <div t:id="overridenComponent"/>
> +    </body>
> +</html>
> \ No newline at end of file
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/app3/OverridenPage.tml
> ----------------------------------------------------------------------
> diff --git a/tapestry-core/src/test/app3/OverridenPage.tml
> b/tapestry-core/src/test/app3/OverridenPage.tml
> new file mode 100644
> index 0000000..cf9b1a7
> --- /dev/null
> +++ b/tapestry-core/src/test/app3/OverridenPage.tml
> @@ -0,0 +1,8 @@
> +<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
> +    <head>
> +        <title>Overriden</title>
> +    </head>
> +    <body>
> +        <h1>Overriden</h1>
> +    </body>
> +</html>
> \ No newline at end of file
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
> index 8e03e34..774df44 100644
> ---
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/AdditionalIntegrationTests.java
> @@ -15,12 +15,14 @@
>  package org.apache.tapestry5.integration.app3;
>
>  import org.apache.tapestry5.integration.TapestryCoreTestCase;
> +import org.apache.tapestry5.test.TapestryTestConfiguration;
>  import org.testng.annotations.Test;
>
>  /**
>   * Additional integration tests that do not fit with the main group due
> to the need for special
>   * configuration.
>   */
> +@TapestryTestConfiguration(webAppFolder = "src/test/app3")
>  public class AdditionalIntegrationTests extends TapestryCoreTestCase
>  {
>      /**
> @@ -88,4 +90,23 @@ public class AdditionalIntegrationTests extends
> TapestryCoreTestCase
>
>          assertTextPresent("Communication with the server failed:
> Server-side exception.");
>      }
> +
> +    // TAP5-1611
> +    @Test
> +    public void component_replacer()
> +    {
> +
> +        final String[] pageNames = {"ComponentReplacer demo",
> "ComponentReplacer demo (using @Component to declare component instances)"};
> +        for (String pageName : pageNames)
> +        {
> +            openLinks(pageName);
> +
> +            assertTrue(isElementPresent("overrideMixin"));
> +            assertFalse(isElementPresent("overridenMixin"));
> +            assertTrue(isElementPresent("overrideComponent"));
> +            assertFalse(isElementPresent("overridenComponent"));
> +        }
> +
> +    }
> +
>  }
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
> new file mode 100644
> index 0000000..a43a745
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverrideComponent.java
> @@ -0,0 +1,24 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.components;
> +
> +import org.apache.tapestry5.MarkupWriter;
> +
> +public class OverrideComponent
> +{
> +    void beginRender(MarkupWriter writer) {
> +        writer.element("div", "id",
> "overrideComponent").element("p").text("Override component");
> +        writer.end(); // div
> +    }
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
> new file mode 100644
> index 0000000..01f5130
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/components/OverridenComponent.java
> @@ -0,0 +1,24 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.components;
> +
> +import org.apache.tapestry5.MarkupWriter;
> +
> +public class OverridenComponent
> +{
> +    void beginRender(MarkupWriter writer) {
> +        writer.element("div", "id",
> "overridenComponent").element("p").text("Overriden component");
> +        writer.end(); // div
> +    }
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
> new file mode 100644
> index 0000000..5614178
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverrideMixin.java
> @@ -0,0 +1,32 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.mixins;
> +
> +import org.apache.tapestry5.ClientElement;
> +import org.apache.tapestry5.MarkupWriter;
> +import org.apache.tapestry5.annotations.InjectContainer;
> +import org.apache.tapestry5.annotations.MixinAfter;
> +import org.apache.tapestry5.dom.Element;
> +
> +@MixinAfter
> +public class OverrideMixin
> +{
> +    @InjectContainer
> +    private ClientElement clientElement;
> +
> +    void afterRender(MarkupWriter writer) {
> +        final Element element =
> writer.getDocument().getElementById(clientElement.getClientId());
> +        element.element("span", "id", "overrideMixin").text(" [Override
> mixin]");
> +    }
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
> new file mode 100644
> index 0000000..50ea142
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/mixins/OverridenMixin.java
> @@ -0,0 +1,30 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.mixins;
> +
> +import org.apache.tapestry5.ClientElement;
> +import org.apache.tapestry5.MarkupWriter;
> +import org.apache.tapestry5.annotations.InjectContainer;
> +import org.apache.tapestry5.dom.Element;
> +
> +public class OverridenMixin
> +{
> +    @InjectContainer
> +    private ClientElement clientElement;
> +
> +    void afterRender(MarkupWriter writer) {
> +        final Element element =
> writer.getDocument().getElementById(clientElement.getClientId());
> +        element.element("span", "id", "overridenMixin").text(" [Overriden
> mixin]");
> +    }
> +}
> \ No newline at end of file
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
> new file mode 100644
> index 0000000..7f803b7
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePage.java
> @@ -0,0 +1,18 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.pages;
> +
> +public class OverridePage
> +{
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
> new file mode 100644
> index 0000000..33ba903
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridePageAtComponent.java
> @@ -0,0 +1,35 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.pages;
> +
> +import org.apache.tapestry5.annotations.Component;
> +import org.apache.tapestry5.annotations.MixinClasses;
> +import org.apache.tapestry5.corelib.components.PageLink;
> +import
> org.apache.tapestry5.integration.app3.components.OverridenComponent;
> +import org.apache.tapestry5.integration.app3.mixins.OverridenMixin;
> +
> +/**
> + * Same as OverridePage, but using @Component to declare components.
> + */
> +public class OverridePageAtComponent
> +{
> +
> +    @Component(id = "link", parameters={"page=Index"})
> +    @MixinClasses(OverridenMixin.class)
> +    private PageLink pageLink;
> +
> +    @Component
> +    private OverridenComponent overridenComponent;
> +
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
> new file mode 100644
> index 0000000..6a03091
> --- /dev/null
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/pages/OverridenPage.java
> @@ -0,0 +1,18 @@
> +// Copyright 2014 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
> +//
> +// Unless required by applicable law or agreed to in writing, software
> +// distributed under the License is distributed on an "AS IS" BASIS,
> +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> +// See the License for the specific language governing permissions and
> +// limitations under the License.
> +package org.apache.tapestry5.integration.app3.pages;
> +
> +public class OverridenPage
> +{
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e6a83e03/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
> ----------------------------------------------------------------------
> diff --git
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
> index 2f4a73d..58648bd 100644
> ---
> a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
> +++
> b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app3/services/AppModule.java
> @@ -15,10 +15,17 @@
>  package org.apache.tapestry5.integration.app3.services;
>
>  import org.apache.tapestry5.SymbolConstants;
> +import org.apache.tapestry5.integration.app3.components.OverrideComponent;
> +import
> org.apache.tapestry5.integration.app3.components.OverridenComponent;
> +import org.apache.tapestry5.integration.app3.mixins.OverrideMixin;
> +import org.apache.tapestry5.integration.app3.mixins.OverridenMixin;
> +import org.apache.tapestry5.integration.app3.pages.OverridePage;
> +import org.apache.tapestry5.integration.app3.pages.OverridenPage;
>  import org.apache.tapestry5.ioc.Configuration;
>  import org.apache.tapestry5.ioc.MappedConfiguration;
>  import org.apache.tapestry5.ioc.OrderedConfiguration;
>  import org.apache.tapestry5.ioc.annotations.Contribute;
> +import org.apache.tapestry5.services.ComponentReplacer;
>  import org.apache.tapestry5.services.DisplayBlockContribution;
>  import org.apache.tapestry5.services.Request;
>  import org.apache.tapestry5.services.compatibility.Compatibility;
> @@ -58,7 +65,7 @@ public class AppModule
>          configuration.add(SymbolConstants.FORM_FIELD_CSS_CLASS,
> FORM_FIELD_CSS_CLASS_VALUE);
>
>      }
> -
> +
>      @Contribute(Compatibility.class)
>      public static void
> disableBackwardsCompatibleFeatures(MappedConfiguration<Trait, Boolean>
> configuration)
>      {
> @@ -78,5 +85,12 @@ public class AppModule
>              }
>          }, "before:*");
>      }
> +
> +    @Contribute(ComponentReplacer.class)
> +    public static void
> overridePageAndComponentAndMixin(MappedConfiguration<Class, Class>
> configuration) {
> +        configuration.add(OverridenPage.class, OverridePage.class);
> +        configuration.add(OverridenComponent.class,
> OverrideComponent.class);
> +        configuration.add(OverridenMixin.class, OverrideMixin.class);
> +    }
>
>  }
>
>