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>"); } - } }