// Copyright Mar 18, 2006 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.tapestry.services.impl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import org.apache.hivemind.Resource;
import org.apache.hivemind.util.Defense;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IJSONRender;
import org.apache.tapestry.IMarkupWriter;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IRender;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.TapestryUtils;
import org.apache.tapestry.asset.AssetFactory;
import org.apache.tapestry.engine.NullWriter;
import org.apache.tapestry.json.IJSONWriter;
import org.apache.tapestry.markup.MarkupWriterSource;
import org.apache.tapestry.services.RequestLocaleManager;
import org.apache.tapestry.services.ResponseBuilder;
import org.apache.tapestry.services.ServiceConstants;
import org.apache.tapestry.util.ContentType;
import org.apache.tapestry.util.PageRenderSupportImpl;
import org.apache.tapestry.util.io.GzipUtil;
import org.apache.tapestry.web.WebRequest;
import org.apache.tapestry.web.WebResponse;
/**
* Class that implements JSON responses in tapestry.
*
* @see <a href="http://json.org">json.org</a>
* @author jkuhnert
*/
public class JSONResponseBuilder implements ResponseBuilder
{
/** Writer that creates JSON output response. */
protected IJSONWriter _writer;
/** Passed in to bypass normal rendering. */
protected IMarkupWriter _nullWriter = NullWriter.getSharedInstance();
/** Parts that will be updated. */
protected List _parts = new ArrayList();
protected RequestLocaleManager _localeManager;
protected MarkupWriterSource _markupWriterSource;
private WebRequest _request;
private WebResponse _response;
private ByteArrayOutputStream _output;
private ContentType _contentType;
private final AssetFactory _assetFactory;
private final String _namespace;
private PageRenderSupportImpl _prs;
private IRequestCycle _cycle;
/**
* Creates a new response builder with the required services it needs
* to render the response when {@link #renderResponse(IRequestCycle)} is called.
*
* @param localeManager
* Used to set the locale on the response.
* @param markupWriterSource
* Creates IJSONWriter instance to be used.
* @param webResponse
* Web response for output stream.
*/
public JSONResponseBuilder(IRequestCycle cycle, RequestLocaleManager localeManager,
MarkupWriterSource markupWriterSource,
WebResponse webResponse, WebRequest request, AssetFactory assetFactory, String namespace)
{
Defense.notNull(cycle, "cycle");
_cycle = cycle;
_localeManager = localeManager;
_markupWriterSource = markupWriterSource;
_response = webResponse;
_request = request;
// Used by PageRenderSupport
_assetFactory = assetFactory;
_namespace = namespace;
}
/**
*
* {@inheritDoc}
*/
public boolean isDynamic()
{
return true;
}
/**
* {@inheritDoc}
*/
public void renderResponse(IRequestCycle cycle)
throws IOException
{
_localeManager.persistLocale();
IPage page = cycle.getPage();
_contentType = page.getResponseContentType();
String encoding = _contentType.getParameter(ENCODING_KEY);
if (encoding == null)
{
encoding = cycle.getEngine().getOutputEncoding();
_contentType.setParameter(ENCODING_KEY, encoding);
}
// _output = new ByteArrayOutputStream();
PrintWriter printWriter = _response.getPrintWriter(_contentType);
// PrintWriter printWriter = new PrintWriter(_output, true);
_writer = _markupWriterSource.newJSONWriter(printWriter, _contentType);
// render response
parseParameters(cycle);
_prs = new PageRenderSupportImpl(_assetFactory, _namespace, cycle.getPage().getLocation(), this);
TapestryUtils.storePageRenderSupport(cycle, _prs);
cycle.renderPage(this);
TapestryUtils.removePageRenderSupport(cycle);
_writer.close();
// writeResponse();
}
/**
* Causes the actual / real response to be written to the output stream.
*/
void writeResponse()
throws IOException
{
byte[] data = _output.toByteArray();
if (GzipUtil.isGzipCapable(_request)) {
// reset data buffer
_output.reset();
GZIPOutputStream gzip = new GZIPOutputStream(_output);
gzip.write(data);
gzip.close();
data = _output.toByteArray();
_response.setHeader("Content-Encoding", "gzip");
}
_response.setContentLength(data.length);
OutputStream output = _response.getOutputStream(_contentType);
output.write(data);
output.flush();
output.close();
}
/**
* Grabs the incoming parameters needed for json responses, most notable the
* {@link ServiceConstants#UPDATE_PARTS} parameter.
*
* @param cycle
* The request cycle to parse from
*/
protected void parseParameters(IRequestCycle cycle)
{
Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
if (updateParts == null)
return;
for(int i = 0; i < updateParts.length; i++)
_parts.add(updateParts[i].toString());
}
/**
* {@inheritDoc}
*/
public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
{
if (IJSONRender.class.isInstance(render)
&& IComponent.class.isInstance(render))
{
IJSONRender json = (IJSONRender) render;
IComponent component = (IComponent) render;
if (!contains(component, component.peekClientId()))
{
render.render(_nullWriter, cycle);
return;
}
json.renderComponent(_writer, cycle);
}
render.render(_nullWriter, cycle);
}
/**
* {@inheritDoc}
*/
public void updateComponent(String id)
{
if (!_parts.contains(id))
_parts.add(id);
}
/**
* Determines if the specified component is contained in the
* responses requested update parts.
* @param target
* The component to check for.
* @return True if the request should capture the components output.
*/
public boolean contains(IComponent target)
{
if (target == null)
return false;
String id = target.getClientId();
return contains(target, id);
}
boolean contains(IComponent target, String id)
{
if (_parts.contains(id))
return true;
Iterator it = _cycle.renderStackIterator();
while (it.hasNext()) {
IComponent comp = (IComponent)it.next();
String compId = comp.getClientId();
if (comp != target && _parts.contains(compId))
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean explicitlyContains(IComponent target)
{
if (target == null)
return false;
return _parts.contains(target.getId());
}
/**
* {@inheritDoc}
*/
public IMarkupWriter getWriter()
{
return _nullWriter;
}
/**
* {@inheritDoc}
*/
public IMarkupWriter getWriter(String id, String type)
{
return _nullWriter;
}
/**
* {@inheritDoc}
*/
public boolean isBodyScriptAllowed(IComponent target)
{
return false;
}
/**
* {@inheritDoc}
*/
public boolean isExternalScriptAllowed(IComponent target)
{
return false;
}
/**
* {@inheritDoc}
*/
public boolean isInitializationScriptAllowed(IComponent target)
{
return false;
}
/**
* {@inheritDoc}
*/
public boolean isImageInitializationAllowed(IComponent target)
{
return false;
}
/**
* {@inheritDoc}
*/
public String getPreloadedImageReference(IComponent target, IAsset source)
{
return _prs.getPreloadedImageReference(target, source);
}
/**
* {@inheritDoc}
*/
public String getPreloadedImageReference(IComponent target, String url)
{
return _prs.getPreloadedImageReference(target, url);
}
/**
* {@inheritDoc}
*/
public String getPreloadedImageReference(String url)
{
return _prs.getPreloadedImageReference(url);
}
/**
* {@inheritDoc}
*/
public void addBodyScript(IComponent target, String script)
{
_prs.addBodyScript(target, script);
}
/**
* {@inheritDoc}
*/
public void addBodyScript(String script)
{
_prs.addBodyScript(script);
}
/**
* {@inheritDoc}
*/
public void addExternalScript(IComponent target, Resource resource)
{
_prs.addExternalScript(target, resource);
}
/**
* {@inheritDoc}
*/
public void addExternalScript(Resource resource)
{
_prs.addExternalScript(resource);
}
/**
* {@inheritDoc}
*/
public void addInitializationScript(IComponent target, String script)
{
_prs.addInitializationScript(target, script);
}
/**
* {@inheritDoc}
*/
public void addInitializationScript(String script)
{
_prs.addInitializationScript(script);
}
/**
* {@inheritDoc}
*/
public String getUniqueString(String baseValue)
{
return _prs.getUniqueString(baseValue);
}
/**
* {@inheritDoc}
*/
public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
{
_prs.writeBodyScript(writer, cycle);
}
/**
* {@inheritDoc}
*/
public void writeInitializationScript(IMarkupWriter writer)
{
_prs.writeInitializationScript(writer);
}
/**
* {@inheritDoc}
*/
public void beginBodyScript(IMarkupWriter writer, IRequestCycle cycle)
{
// does nothing
}
/**
* {@inheritDoc}
*/
public void endBodyScript(IMarkupWriter writer, IRequestCycle cycle)
{
// does nothing
}
/**
* {@inheritDoc}
*/
public void writeBodyScript(IMarkupWriter writer, String script, IRequestCycle cycle)
{
// does nothing
}
/**
* {@inheritDoc}
*/
public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
{
// does nothing
}
/**
* {@inheritDoc}
*/
public void writeImageInitializations(IMarkupWriter writer, String script, String preloadName, IRequestCycle cycle)
{
// does nothing
}
/**
* {@inheritDoc}
*/
public void writeInitializationScript(IMarkupWriter writer, String script)
{
// does nothing
}
}