Thorsten Schöning created WICKET-6823:
-----------------------------------------

             Summary: Two instances of AbstractTransformerBehavior on the same 
component results in incomplete output.
                 Key: WICKET-6823
                 URL: https://issues.apache.org/jira/browse/WICKET-6823
             Project: Wicket
          Issue Type: Bug
          Components: wicket-core
    Affects Versions: 8.9.0
            Reporter: Thorsten Schöning


I'm using Wicket as a renderer for HTML-reports WITHOUT browser, web server or 
requests, only by using ComponentRenderer. There are two implementations of 
AbstractTransformerBehavior to update "colspan" attributes of table cells and 
IDs of arbitrary HTML nodes. Both are used on the same component:

{noformat}
resultsCont.add(new DvResultsCont.ColSpanUpdater());
resultsCont.add(new MkIdReplacer
(
  "th", "id", "td", "headers",
  String.format("%d.%s", itemIdx, kindOfDetail)
));
{noformat}

When only ONE of both behaviours is used, the page renders successfully and it 
doesn't make any difference which one is used. If both of those are used OTOH, 
the page seems to be rendered at all as well, but some rendered content is 
simply missing in the overall output in the end. My component "resultsCont" 
renders to a table, so the last markup I have is the following:

{noformat}
<table class="detailsMeters ui-expandable ui-expandable-collapsed missing">
[...]
</table>
{noformat}

In theory, after that table there should be additional content like foots, 
closing elements for HTML itself etc. So the current rendering is invalid. It's 
important to note, though, that I don't get any exception, the output simply 
seems to not contain all data. When enabling DEBUG logging during rendering, 
the logs make pretty much clear that Wicket really tries to continue rendering, 
but the output is simply missing.

As no exceptions are thrown and output seems to simply be ignored at some 
point, I have the feeling the problem is in handling the response objects in 
"AbstractTransformerBehavior". All of those assigned transformers to some 
component are called before actually rendering anything and change the 
response. "Component.java" contains the following code:

{noformat}
/**
 * {@link Behavior#beforeRender(Component)} Notify all behaviors that are 
assigned to this
 * component that the component is about to be rendered.
 */
private void notifyBehaviorsComponentBeforeRender()
{
        for (Behavior behavior : getBehaviors())
        {
                if (isBehaviorAccepted(behavior))
                {
                        behavior.beforeRender(this);
                }
        }
}
{noformat}

Each call to "beforeRender" changes the response and all those changes are done 
one after another. This means that the current request to render two will be 
that one of the LAST applied transformer only.

{noformat}
@Override
public void beforeRender(Component component)
{
        super.beforeRender(component);

        final RequestCycle requestCycle = RequestCycle.get();

        // Temporarily replace the web response with a String response
        originalResponse = requestCycle.getResponse();

        WebResponse origResponse = (WebResponse)((originalResponse instanceof 
WebResponse)
                ? originalResponse : null);
        BufferedWebResponse tempResponse = newResponse(origResponse);

        // temporarily set StringResponse to collect the transformed output
        requestCycle.setResponse(tempResponse);
}
{noformat}

Only after all those changes to the current request, content is rendered and 
transformed, using the current request AND changing it back to what each 
individual transformer believes to be the original request.

{noformat}
@Override
public void afterRender(final Component component)
{
        final RequestCycle requestCycle = RequestCycle.get();

        try
        {
                BufferedWebResponse tempResponse = 
(BufferedWebResponse)requestCycle.getResponse();

                if (component instanceof Page && originalResponse instanceof 
WebResponse)
                {
                        tempResponse.writeMetaData((WebResponse) 
originalResponse);
                }

                // Transform the data
                CharSequence output = transform(component, 
tempResponse.getText());
                originalResponse.write(output);
        }
        catch (Exception ex)
        {
                throw new WicketRuntimeException("Error while transforming the 
output of component: " +
                        component, ex);
        }
        finally
        {
                // Restore the original response object
                requestCycle.setResponse(originalResponse);
        }
}
{noformat}

Because all transformers are executed in order at this stage, the FIRST 
executed transformer works on the content of the request of the LAST one which 
put requests into place. After applying its own transformation, it puts its own 
original request in place and the next transformer works on that and puts its 
own request into place etc.

So the problem seems to be that AFTER the first transformer executed 
"afterReder", all subsequent ones are rendering into temporary responses of 
former transformers and those results are never written to the original 
response. It can't be, because only the first transformer has access to that 
response at all, but is unaware of the results of later transformers.

So it needs to be decided if multiple transformers should be supported at all 
and if so, how to do it best with proper chaining of results. In my opinion it 
should be supported, as I have a valid use case and I couldn't find any docs 
that multiple of those transformers don't work. It simply seems the current 
implementation is too limited.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to