/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.wicket.page;
import java.util.Collection;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link PartialPageUpdate} that serializes itself to XML.
*/
public class XmlPartialPageUpdate extends PartialPageUpdate
{
private static final Logger LOG = LoggerFactory.getLogger(XmlPartialPageUpdate.class);
/**
* The name of the root element in the produced XML document.
*/
public static final String START_ROOT_ELEMENT = "<ajax-response>";
public static final String END_ROOT_ELEMENT = "</ajax-response>";
public XmlPartialPageUpdate(final Page page)
{
super(page);
}
@Override
public void setContentType(WebResponse response, String encoding)
{
response.setContentType("text/xml; charset=" + encoding);
}
@Override
protected void writeHeader(Response response, String encoding)
{
response.write("<?xml version=\"1.0\" encoding=\"");
response.write(encoding);
response.write("\"?>");
response.write(START_ROOT_ELEMENT);
}
@Override
protected void writeComponent(Response response, String markupId, Component component, String encoding)
{
if (component.getRenderBodyOnly() == true)
{
throw new IllegalStateException(
"A partial update is not possible for a component that has renderBodyOnly enabled. Component: " +
component.toString());
}
component.setOutputMarkupId(true);
// Initialize temporary variables
final Page page = component.findParent(Page.class);
if (page == null)
{
// dont throw an exception but just ignore this component, somehow
// it got removed from the page.
LOG.warn("Component '{}' with markupid: '{}' not rendered because it was already removed from page",
component, markupId);
return;
}
// substitute our encoding response for the old one so we can capture
// component's markup in a manner safe for transport inside CDATA block
Response oldResponse = RequestCycle.get().setResponse(bodyBuffer);
try
{
bodyBuffer.reset();
page.startComponentRender(component);
try
{
component.prepareForRender();
// render any associated headers of the component
writeHeaderContribution(response, component);
}
catch (RuntimeException e)
{
try
{
component.afterRender();
}
catch (RuntimeException e2)
{
// ignore this one could be a result off.
}
bodyBuffer.reset();
throw e;
}
try
{
component.render();
}
catch (RuntimeException e)
{
bodyBuffer.reset();
throw e;
}
page.endComponentRender(component);
}
finally
{
// Restore original response
RequestCycle.get().setResponse(oldResponse);
}
response.write("<component id=\"");
response.write(markupId);
response.write("\" ><![CDATA[");
response.write(encode(bodyBuffer.getContents()));
response.write("]]></component>");
bodyBuffer.reset();
}
@Override
protected void writeFooter(Response response, String encoding)
{
response.write(END_ROOT_ELEMENT);
}
@Override
protected void writeHeaderContribution(Response response)
{
CharSequence contents = headerBuffer.getContents();
if (Strings.isEmpty(contents) == false)
{
response.write("<header-contribution>");
// we need to write response as CDATA and parse it on client,
// because konqueror crashes when there is a <script> element
response.write("<![CDATA[<head xmlns:wicket=\"http://wicket.apache.org\">");
response.write(encode(contents));
response.write("</head>]]>");
response.write("</header-contribution>");
}
}
@Override
protected void writeNormalEvaluations(final Response response, final Collection<CharSequence> scripts)
{
writeEvaluations(response, "evaluate", scripts);
}
@Override
protected void writePriorityEvaluations(Response response, Collection<CharSequence> scripts)
{
writeEvaluations(response, "priority-evaluate", scripts);
}
private void writeEvaluations(final Response response, String elementName, Collection<CharSequence> scripts)
{
if (scripts.size() > 0)
{
StringBuilder combinedScript = new StringBuilder(1024);
for (CharSequence script : scripts)
{
combinedScript.append("(function(){").append(script).append("})();");
}
writeEvaluation(elementName, response, combinedScript);
}
}
/**
* @param invocation
* type of invocation tag, usually {@literal evaluate} or
* {@literal priority-evaluate}
* @param response
* @param js
*/
private void writeEvaluation(final String invocation, final Response response, final CharSequence js)
{
response.write("<");
response.write(invocation);
response.write(">");
response.write("<![CDATA[");
response.write(encode(js));
response.write("]]>");
response.write("</");
response.write(invocation);
response.write(">");
bodyBuffer.reset();
}
protected CharSequence encode(CharSequence str)
{
return Strings.replaceAll(str, "]]>", "]]]]><![CDATA[>");
}
}