Guten Tag Thorsten Schöning,
am Freitag, 4. September 2020 um 10:40 schrieben Sie:

> That's why I asked: I most likely prefer an explicit container to add
> transformers to currently, something like "MultiAbstract[...]" or
> "[...]BehaviorMulti" or alike. That collects individual transformers
> and gets itself only added to the component.

I've created a container to work around the current limitation, which
is pretty easy overall and easily compatible with different versions
of Wicket. A corresponding PR is made, if it doesn't make it into the
project, simply copy&paste it into an individual class into your own
projects and use it.

https://github.com/apache/wicket/pull/457

Mit freundlichen Grüßen,

Thorsten Schöning

-- 
Thorsten Schöning       E-Mail: thorsten.schoen...@am-soft.de
AM-SoFT IT-Systeme      http://www.AM-SoFT.de/

Telefon...........05151-  9468- 55
Fax...............05151-  9468- 88
Mobil..............0178-8 9468- 04

AM-SoFT GmbH IT-Systeme, Brandenburger Str. 7c, 31789 Hameln
AG Hannover HRB 207 694 - Geschäftsführer: Andreas Muchow
 .../transformer/AbstractTransformerBehavior.java   | 112 ++++++++++++++++++++-
 .../AbstractTransformerBehaviorTest.java           |  88 +++++++++++++++-
 2 files changed, 190 insertions(+), 10 deletions(-)

diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/transformer/AbstractTransformerBehavior.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/transformer/AbstractTransformerBehavior.java
index 0359bbbcd6..d18c3dd664 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/markup/transformer/AbstractTransformerBehavior.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/transformer/AbstractTransformerBehavior.java
@@ -16,6 +16,11 @@
  */
 package org.apache.wicket.markup.transformer;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
 import org.apache.wicket.Component;
 import org.apache.wicket.Page;
 import org.apache.wicket.WicketRuntimeException;
@@ -26,17 +31,114 @@ import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.http.WebResponse;
 
 /**
- * A {@link Behavior} which can be added to any component. It allows to 
post-process (transform) the
+ * A {@link Behavior} which can be added to any component, allowing to 
post-process (transform) the
  * markup generated by the component.
- * 
+ * <p>
+ * There's one important limitation with the current implementation: Multiple 
different instances of
+ * this behavior CAN NOT be assigned to the same component! If one whiches to 
do so, the contained
+ * container needs to be used to wrap existing behaviors and that container 
needs to be added to the
+ * component instead. The current implementation of works with temporary 
responses, but does not
+ * support nesting itself properly, which results in missing rendered output 
and most likely broken
+ * HTML documents in the end.
+ * </p>
  * @see org.apache.wicket.markup.transformer.AbstractOutputTransformerContainer
- * 
+ * @see <a 
href="https://issues.apache.org/jira/projects/WICKET/issues/WICKET-6823";>JIRA 
issue</a>
+ *
  * @author Juergen Donnerstag
  */
 public abstract class AbstractTransformerBehavior extends Behavior implements 
ITransformer
 {
        private static final long serialVersionUID = 1L;
 
+       /**
+        * Container to apply multiple {@link AbstractTransformerBehavior} to 
some component.
+        * <p>
+        * This container is by design NOT about multiple arbitrary 
transformations, but really only for the
+        * one use case supporting multiple instances of {@link 
AbstractTransformerBehavior} on one and the
+        * same component. The current implementation of that works with 
temporary responses, but doesn't
+        * support nesting itself properly in case multiple behaviors assigned 
to the same component, which
+        * results in missing rendered output and most likely entirely broken 
HTML documents in the end.
+        * </p>
+        * <p>
+        * The easiest workaround for that problem is simply introducing this 
container which users need to
+        * use in those cases: An instance needs to be created with all 
transformers of interest in the
+        * order they should be applied and the container takes care of doing 
so. Because the container is
+        * an {@link AbstractTransformerBehavior} itself, things simply work 
like with individual behaviors,
+        * while response handling is only managed by the container. So when 
used with this container, the
+        * callbacks of the maintained instances like {@link 
AbstractTransformerBehavior#afterRender(Component)}
+        * etc., are NOT used anymore! OTOH, the individual behaviors stay 
useful without the container as
+        * well.
+        * </p>
+        * @see <a 
href="https://issues.apache.org/jira/projects/WICKET/issues/WICKET-6823";>JIRA 
issue</a>
+        */
+       public static class Multi extends AbstractTransformerBehavior
+       {
+               private static final long serialVersionUID = 1L;
+
+               /**
+                * All transformers which need to be applied in the order they 
need to be applied.
+                */
+               private final List<AbstractTransformerBehavior> transes;
+
+               /**
+                * CTOR simply storing the given transformers.
+                *
+                * @param transes, which must not be {@code null} or empty, as 
neither make sense here.
+                */
+               private Multi(List<AbstractTransformerBehavior> transes)
+               {
+                       if ((transes == null) || transes.isEmpty())
+                       {
+                               throw new IllegalArgumentException("No 
transformers given.");
+                       }
+
+                       this.transes = transes;
+               }
+
+               @Override
+               public CharSequence transform(Component component, CharSequence 
output) throws Exception
+               {
+                       CharSequence retVal = output;
+                       for (AbstractTransformerBehavior trans : this.transes)
+                       {
+                               retVal = trans.transform(component, retVal);
+                       }
+
+                       return retVal;
+               }
+
+               /**
+                * Create a new container with the given transformers and with 
keeping their order.
+                * <p>
+                * This factory expects multiple individual transformers 
already, because creating a container
+                * with less doesn't make too much sense and users should 
reconsider then if this container is
+                * of use at all. In most cases users do have individual 
transformers to apply only anyway and
+                * don't need to provide a collection themself this way. OTOH, 
a collection could be empty or
+                * contain only one element would then defeat the purpose of 
this container again.
+                * </p>
+                * @param first First transformer to apply.
+                * @param second Second transformer to apply.
+                * @param moreIf All other transformers to apply, if at all, in 
given order.
+                * @return A container with multiple transformers being applied.
+                */
+               public static Multi newFor(     AbstractTransformerBehavior     
        first,
+                                                                       
AbstractTransformerBehavior             second,
+                                                                       
AbstractTransformerBehavior...  moreIf)
+               {
+                       List<AbstractTransformerBehavior> transes = new 
ArrayList<>();
+
+                       transes.add(Objects.requireNonNull(first,       "No 
first transformer given."));
+                       transes.add(Objects.requireNonNull(second,      "No 
second transformer given."));
+
+                       if ((moreIf != null) && (moreIf.length > 0))
+                       {
+                               transes.addAll(Arrays.asList(moreIf));
+                       }
+
+                       return new Multi(transes);
+               }
+       }
+
        /**
         * The request cycle's response before the transformation.
         */
@@ -45,10 +147,10 @@ public abstract class AbstractTransformerBehavior extends 
Behavior implements IT
        /**
         * Create a new response object which is used to store the markup 
generated by the child
         * objects.
-        * 
+        *
         * @param originalResponse
         *            the original web response or {@code null} if it isn't a 
{@link WebResponse}
-        * 
+        *
         * @return Response object. Must not be null
         */
        protected BufferedWebResponse newResponse(final WebResponse 
originalResponse)
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/markup/transformer/AbstractTransformerBehaviorTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/markup/transformer/AbstractTransformerBehaviorTest.java
index adc30243a8..2c5b4d63cf 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/markup/transformer/AbstractTransformerBehaviorTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/markup/transformer/AbstractTransformerBehaviorTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.wicket.markup.transformer;
 
+import java.util.function.Function;
+
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -37,7 +39,10 @@ public class AbstractTransformerBehaviorTest extends 
WicketTestCase
        @Test
        public void responseTransformation()
        {
-               TestPage testPage = new TestPage();
+               TestPage        testPage        = new TestPage();
+               String          replacement     = "replacement";
+
+               testPage.add(new Label(TestPage.LABEL_ID, TestPage.LABEL_VAL));
                testPage.add(new AbstractTransformerBehavior()
                {
                        /** */
@@ -47,11 +52,12 @@ public class AbstractTransformerBehaviorTest extends 
WicketTestCase
                        public CharSequence transform(Component component, 
CharSequence output)
                                throws Exception
                        {
-                               return output.toString().replace("to be 
replaced", "replacement");
+                               return 
output.toString().replace(TestPage.LABEL_VAL, replacement);
                        }
                });
+
                tester.startPage(testPage);
-               
assertTrue(tester.getLastResponseAsString().contains("replacement"));
+               
assertTrue(tester.getLastResponseAsString().contains(replacement));
        }
 
        /**
@@ -69,7 +75,77 @@ public class AbstractTransformerBehaviorTest extends 
WicketTestCase
                tester.clickLink("updateLabel", true);
                tester.assertContains("ajax request");
                tester.assertContainsNot("normal request");
+       }
 
+       /**
+        * Test how multiple different transformers applied to the same 
component behave.
+        * <p>
+        * The current implementation of {@link AbstractTransformerBehavior} 
doesn't support multiple
+        * instances on the same component, a container needs to be used 
explicitly instead. So make
+        * sure the implementation is as expected, as otherwise the container 
might not be necessary at
+        * all anymore, and that the container really works around the problem.
+        * </p>
+        * @see <a 
href="https://issues.apache.org/jira/projects/WICKET/issues/WICKET-6823";>JIRA 
issue</a>
+        */
+       @Test
+       public void multiTransesSameComp()
+       {
+               TestPage        testPage        = new TestPage();
+               Label           label           = new Label(TestPage.LABEL_ID, 
TestPage.LABEL_VAL);
+
+               Function<String, AbstractTransformerBehavior> transProd = (val) 
->
+               {
+                       return new AbstractTransformerBehavior()
+                       {
+                               /** */
+                               private static final long serialVersionUID = 1L;
+
+                               @Override
+                               public CharSequence transform(Component 
component, CharSequence output)
+                                       throws Exception
+                               {
+                                       String outStr = output.toString();
+                                       if (outStr.contains(TestPage.LABEL_VAL))
+                                       {
+                                               return 
outStr.replace(TestPage.LABEL_VAL, val);
+                                       }
+
+                                       // Make somewhat sure to recognize that 
BOTH transformers have been applied.
+                                       return outStr.replaceAll
+                                       (
+                                               ">(.+)</span>",
+                                               String.format(">$1%s</span>", 
val)
+                                       );
+                               }
+                       };
+               };
+
+               label.add(transProd.apply("foo"));
+               label.add(transProd.apply("bar"));
+
+               // Make sure the expected limited implementation is still 
available, which makes a container
+               // necessary only at all. If that has changed, the container 
might be removed as well.
+               testPage.add(label);
+               tester.startPage(testPage);
+               tester.isVisible(TestPage.LABEL_ID);
+               tester.assertContains(">foo</span>");
+               tester.assertContainsNot("</html>");
+
+               testPage        = new TestPage();
+               label           = new Label(TestPage.LABEL_ID, 
TestPage.LABEL_VAL);
+
+               label.add(AbstractTransformerBehavior.Multi.newFor
+               (
+                       transProd.apply("foo"),
+                       transProd.apply("bar")
+               ));
+
+               // Maike sure that the container provided as workaround really 
fixes the problem.
+               testPage.add(label);
+               tester.startPage(testPage);
+               tester.isVisible(TestPage.LABEL_ID);
+               tester.assertContains(">foobar</span>");
+               tester.assertContains("</html>");
        }
 
        private static class AjaxTestPage extends WebPage implements 
IMarkupResourceStreamProvider
@@ -126,12 +202,14 @@ public class AbstractTransformerBehaviorTest extends 
WicketTestCase
        {
                private static final long serialVersionUID = 1L;
 
+               private static final String LABEL_ID    = "label";
+               private static final String LABEL_VAL   = "{to be replaced}";
+
                @Override
                public IResourceStream getMarkupResourceStream(MarkupContainer 
container,
                        Class<?> containerClass)
                {
-                       return new StringResourceStream("<html><body>{to be 
replaced}</body></html>");
+                       return new StringResourceStream("<html><body><span 
wicket:id='label'></span></body></html>");
                }
-
        }
 }

Reply via email to