/* | |
* 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.click; | |
import java.io.ByteArrayInputStream; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.io.Reader; | |
import java.io.StringReader; | |
import java.io.Writer; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.Map; | |
import javax.servlet.http.HttpServletResponse; | |
import org.apache.click.util.ClickUtils; | |
/** | |
* Provides an ActionResult that is returned by Page Actions and AjaxBehaviors. | |
* ActionResults are often used to return a partial response to the browser | |
* instead of the full page content. | |
* <p/> | |
* An ActionResult can consist of a String (HTML, JSON, XML, plain text) or a byte | |
* array (jpg, gif, png, pdf or excel documents). The ActionResult {@link #contentType} | |
* must be set appropriately in order for the browser to recognize the action result. | |
* <p/> | |
* ActionResults are returned by {@link org.apache.click.ajax.AjaxBehavior Ajax Behaviors} | |
* and <tt>Page Action</tt> methods. | |
* | |
* <h3>Ajax Behavior</h3> | |
* | |
* Ajax requests are handled by adding an {@link org.apache.click.ajax.AjaxBehavior Ajax Behavior} | |
* to a control. The AjaxBehavior {@link org.apache.click.ajax.AjaxBehavior#onAction(org.apache.click.Control) onAction} | |
* method will handle the request and return a ActionResult instance that contains | |
* the response, thus bypassing the rendering of the Page template. For example: | |
* | |
* <pre class="prettyprint"> | |
* private ActionLink link = new ActionLink("link"); | |
* | |
* public void onInit() { | |
* addControl(link); | |
* | |
* link.addBehavior(new AjaxBehavior() { | |
* | |
* // The onAction method must return a ActionResult | |
* public ActionResult onAction(Control source) { | |
* // Create a new action result containing an HTML snippet and HTML content type | |
* ActionResult actionResult = new ActionResult("<span>Hello World</span>", ActionResult.HTML); | |
* return actionResult; | |
* } | |
* }); | |
* } </pre> | |
* | |
* <h3>Page Action</h3> | |
* | |
* A <tt>Page Action</tt> is a method on a Page that can be invoked directly | |
* from the browser. The Page Action method returns an ActionResult instance that | |
* is rendered to the browser, thus bypassing the rendering of the Page template. | |
* | |
* <pre class="prettyprint"> | |
* private ActionLink link = new ActionLink("link"); | |
* | |
* public void onInit() { | |
* link.addControl(link); | |
* | |
* // A "pageAction" is set as a parameter on the link. The "pageAction" | |
* // value is set to the Page method: "renderHelloWorld" | |
* link.setParameter(PAGE_ACTION, "renderHelloWorld"); | |
* } | |
* | |
* /** | |
* * This is a "pageAction" method that will render an HTML response. | |
* * | |
* * Note the signature of the pageAction: a public, no-argument method | |
* * returning a ActionResult instance. | |
* */ | |
* public ActionResult renderHelloWorld() { | |
* ActionResult actionResult = new ActionResult("<span>Hello World</span>", ActionResult.HTML); | |
* return actionResult; | |
* } </pre> | |
* | |
* <h3>Content Type</h3> | |
* | |
* The {@link #contentType} of the ActionResult must be set to the appropriate type | |
* in order for the client to recognize the response. ActionResult provides constants | |
* for some of the common <tt>content types</tt>, including: {@link #XML text/xml}, | |
* {@link #HTML text/html}, {@link #JSON application/json}, {@link #TEXT text/plain}. | |
* <p/> | |
* For example: | |
* <pre class="prettyprint"> | |
* ActionResult actionResult = new ActionResult("alert('hello world');", ActionResult.JAVASCRIPT); | |
* | |
* ... | |
* | |
* // content type can also be set through the setContentType method | |
* actionResult.setContentType(ActionResult.JAVASCRIPT); | |
* | |
* ... | |
* </pre> | |
* | |
* More content types can be retrieved through {@link org.apache.click.util.ClickUtils#getMimeType(java.lang.String)}: | |
* <pre class="prettyprint"> | |
* // lookup content type for PNG | |
* String contentType = ClickUtils.getMimeType("png"); | |
* actionResult.setContentType(contentType); </pre> | |
*/ | |
public class ActionResult { | |
// Constants -------------------------------------------------------------- | |
/** The plain text content type constant: <tt>text/plain</tt>. */ | |
public static final String TEXT = "text/plain"; | |
/** The html content type constant: <tt>text/html</tt>. */ | |
public static final String HTML = "text/html"; | |
/** The The xhtml content type constant: <tt>application/xhtml+xml</tt>. */ | |
public static final String XHTML = "application/xhtml+xml"; | |
/** The json content type constant: <tt>text/json</tt>. */ | |
public static final String JSON = "application/json"; | |
/** The javascript content type constant: <tt>text/javascript</tt>. */ | |
public static final String JAVASCRIPT = "text/javascript"; | |
/** The xml content type constant: <tt>text/xml</tt>. */ | |
public static final String XML = "text/xml"; | |
/** The ActionResult writer buffer size. */ | |
private static final int WRITER_BUFFER_SIZE = 256; // For text, set a small response size | |
/** The ActionResult output buffer size. */ | |
private static final int OUTPUT_BUFFER_SIZE = 4 * 1024; // For binary, set a a large response size | |
// Variables -------------------------------------------------------------- | |
/** The content to render. */ | |
private String content; | |
/** The content as a byte array. */ | |
private byte[] bytes; | |
/** The servlet response reader. */ | |
private Reader reader; | |
/** The servlet response input stream. */ | |
private InputStream inputStream; | |
/** The response content type. */ | |
private String contentType; | |
/** The response character encoding. */ | |
private String characterEncoding; | |
/** Indicates whether the ActionResult should be cached by browser. */ | |
private boolean cacheActionResult = false; | |
/** The path of the actionResult template to render. */ | |
private String template; | |
/** The model for the ActionResult {@link #template}. */ | |
private Map<String, Object> model; | |
// Constructors ----------------------------------------------------------- | |
/** | |
* Construct the ActionResult for the given template and model. | |
* <p/> | |
* When the ActionResult is rendered the template and model will be merged and | |
* the result will be streamed back to the client. | |
* <p/> | |
* For example: | |
* <pre class="prettyprint"> | |
* public class MyPage extends Page { | |
* public void onInit() { | |
* | |
* Behavior behavior = new DefaultAjaxBehavior() { | |
* | |
* public ActionResult onAction() { | |
* | |
* Map model = new HashMap(); | |
* model.put("id", "link"); | |
* | |
* // Note: we set XML as the content type | |
* ActionResult actionResult = new ActionResult("/js/actionResult.xml", model, ActionResult.XML); | |
* | |
* return actionResult; | |
* } | |
* } | |
* } | |
* } </pre> | |
* | |
* @param template the template to render and stream back to the client | |
* @param model the template data model | |
* @param contentType the response content type | |
*/ | |
public ActionResult(String template, Map<String, Object> model, String contentType) { | |
this.template = template; | |
this.model = model; | |
this.contentType = contentType; | |
} | |
/** | |
* Construct the ActionResult for the given reader and content type. | |
* | |
* @param reader the reader which characters must be streamed back to the | |
* client | |
* @param contentType the response content type | |
*/ | |
public ActionResult(Reader reader, String contentType) { | |
this.reader = reader; | |
this.contentType = contentType; | |
} | |
/** | |
* Construct the ActionResult for the given inputStream and content type. | |
* | |
* @param inputStream the input stream to stream back to the client | |
* @param contentType the response content type | |
*/ | |
public ActionResult(InputStream inputStream, String contentType) { | |
this.inputStream = inputStream; | |
this.contentType = contentType; | |
} | |
/** | |
* Construct the ActionResult for the given String content and content type. | |
* | |
* @param content the String content to stream back to the client | |
* @param contentType the response content type | |
*/ | |
public ActionResult(String content, String contentType) { | |
this.content = content; | |
this.contentType = contentType; | |
} | |
/** | |
* Construct the ActionResult for the given byte array and content type. | |
* | |
* @param bytes the byte array to stream back to the client | |
* @param contentType the response content type | |
*/ | |
public ActionResult(byte[] bytes, String contentType) { | |
this.bytes = bytes; | |
this.contentType = contentType; | |
} | |
/** | |
* Construct the ActionResult for the given content. The | |
* <tt>{@link javax.servlet.http.HttpServletResponse#setContentType(java.lang.String) response content type}</tt> | |
* will default to {@link #TEXT}, unless overridden. | |
* | |
* @param content the content to stream back to the client | |
*/ | |
public ActionResult(String content) { | |
this.content = content; | |
} | |
/** | |
* Construct a new empty ActionResult. The | |
* <tt>{@link javax.servlet.http.HttpServletResponse#setContentType(java.lang.String) response content type}</tt> | |
* will default to {@link #TEXT}, unless overridden. | |
*/ | |
public ActionResult() { | |
} | |
// Public Methods --------------------------------------------------------- | |
/** | |
* Set whether the action result should be cached by the client browser or | |
* not. | |
* <p/> | |
* If false, Click will set the following headers to prevent browsers | |
* from caching the result: | |
* <pre class="prettyprint"> | |
* response.setHeader("Pragma", "no-cache"); | |
* response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); | |
* response.setDateHeader("Expires", new Date(1L).getTime()); </pre> | |
* | |
* @param cacheActionResult indicates whether the action result should be cached | |
* by the client browser or not | |
*/ | |
public void setCacheActionResult(boolean cacheActionResult) { | |
this.cacheActionResult = cacheActionResult; | |
} | |
/** | |
* Return true if the action result should be cached by the client browser, | |
* defaults to false. It is highly unlikely that you would turn action result | |
* caching on. | |
* | |
* @return true if the action result should be cached by the client browser, | |
* false otherwise | |
*/ | |
public boolean isCacheActionRestul() { | |
return cacheActionResult; | |
} | |
/** | |
* Return the action result character encoding. If no character encoding is specified | |
* the request character encoding will be used. | |
* | |
* @return the action result character encoding | |
*/ | |
public String getCharacterEncoding() { | |
return characterEncoding; | |
} | |
/** | |
* Set the action result character encoding. If no character encoding is set the | |
* request character encoding will be used. | |
* | |
* @param characterEncoding the action result character encoding | |
*/ | |
public void setCharacterEncoding(String characterEncoding) { | |
this.characterEncoding = characterEncoding; | |
} | |
/** | |
* Set the action result response content type. If no content type is set it will | |
* default to {@value #TEXT}. | |
* | |
* @param contentType the action result response content type | |
*/ | |
public void setContentType(String contentType) { | |
this.contentType = contentType; | |
} | |
/** | |
* Return the action result content type, default is {@value #TEXT}. | |
* | |
* @return the response content type | |
*/ | |
public String getContentType() { | |
if (contentType == null) { | |
contentType = TEXT; | |
} | |
return contentType; | |
} | |
/** | |
* Set the content String to stream back to the client. | |
* | |
* @param content the content String to stream back to the client | |
*/ | |
public void setContent(String content) { | |
this.content = content; | |
} | |
/** | |
* Return the content String to stream back to the client. | |
* | |
* @return the content String to stream back to the client | |
*/ | |
public String getContent() { | |
return content; | |
} | |
/** | |
* Set the byte array to stream back to the client. | |
* | |
* @param bytes the byte array to stream back to the client | |
*/ | |
public void setBytes(byte[] bytes, String contentType) { | |
this.bytes = bytes; | |
this.contentType = contentType; | |
} | |
/** | |
* Return the byte array to stream back to the client. | |
* | |
* @return the byte array to stream back to the client | |
*/ | |
public byte[] getBytes() { | |
return bytes; | |
} | |
/** | |
* Set the content to stream back to the client. | |
* | |
* @param inputStream the inputStream to stream back to the client | |
*/ | |
public void setInputStream(InputStream inputStream) { | |
this.inputStream = inputStream; | |
} | |
/** | |
* Return the inputStream to stream back to the client. | |
* | |
* @return the inputStream to stream back to the client | |
*/ | |
public InputStream getInputStream() { | |
return inputStream; | |
} | |
/** | |
* Set the reader which characters are streamed back to the client. | |
* | |
* @param reader the reader which characters are streamed back to the client. | |
*/ | |
public void setReader(Reader reader) { | |
this.reader = reader; | |
} | |
/** | |
* Return the reader which characters are streamed back to the client. | |
* | |
* @return the reader which characters are streamed back to the client. | |
*/ | |
public Reader getReader() { | |
return reader; | |
} | |
/** | |
* Return the data model for the ActionResult {@link #template}. | |
* | |
* @return the data model for the ActionResult template | |
*/ | |
public Map<String, Object> getModel() { | |
if (model == null) { | |
model = new HashMap<String, Object>(); | |
} | |
return model; | |
} | |
/** | |
* Set the model of the ActionResult template to render. | |
* <p/> | |
* If the {@link #template} property is set, the template and {@link #model} | |
* will be merged and the result will be streamed back to the client. | |
* | |
* @param model the model of the template to render | |
*/ | |
public void setModel(Map<String, Object> model) { | |
this.model = model; | |
} | |
/** | |
* Return the template to render for this ActionResult. | |
* | |
* @return the template to render for this ActionResult | |
*/ | |
public String getTemplate() { | |
return template; | |
} | |
/** | |
* Set the template to render for this ActionResult. | |
* | |
* @param template the template to render for this ActionResult | |
*/ | |
public void setTemplate(String template) { | |
this.template = template; | |
} | |
/** | |
* Render the ActionResult to the client. | |
* | |
* @param context the request context | |
*/ | |
public final void render(Context context) { | |
prepare(context); | |
renderActionResult(context); | |
} | |
// Protected Methods ------------------------------------------------------ | |
/** | |
* Render the ActionResult to the client. This method can be overridden | |
* by subclasses if custom rendering or direct access to the | |
* HttpServletResponse is required. | |
* | |
* @param context the request context | |
*/ | |
protected void renderActionResult(Context context) { | |
HttpServletResponse response = context.getResponse(); | |
Reader localReader = getReader(); | |
InputStream localInputStream = getInputStream(); | |
try { | |
String localContent = getContent(); | |
byte[] localBytes = getBytes(); | |
String localTemplate = getTemplate(); | |
if (localTemplate != null) { | |
Map<String, Object> templateModel = getModel(); | |
if (templateModel == null) { | |
templateModel = new HashMap<String, Object>(); | |
} | |
String result = context.renderTemplate(localTemplate, templateModel); | |
localReader = new StringReader(result); | |
} else if (localContent != null) { | |
localReader = new StringReader(localContent); | |
} else if (localBytes != null) { | |
localInputStream = new ByteArrayInputStream(localBytes); | |
} | |
if (localReader != null) { | |
Writer writer = response.getWriter(); | |
char[] buffer = new char[WRITER_BUFFER_SIZE]; | |
int len = 0; | |
while (-1 != (len = localReader.read(buffer))) { | |
writer.write(buffer, 0, len); | |
} | |
writer.flush(); | |
writer.close(); | |
} else if (localInputStream != null) { | |
byte[] buffer = new byte[OUTPUT_BUFFER_SIZE]; | |
int len = 0; | |
OutputStream outputStream = response.getOutputStream(); | |
while (-1 != (len = localInputStream.read(buffer))) { | |
outputStream.write(buffer, 0, len); | |
} | |
outputStream.flush(); | |
outputStream.close(); | |
} | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} finally { | |
ClickUtils.close(localInputStream); | |
ClickUtils.close(localReader); | |
} | |
} | |
// Private Methods -------------------------------------------------------- | |
/** | |
* Prepare the ActionResult for rendering. | |
* | |
* @param context the request context | |
*/ | |
private void prepare(Context context) { | |
HttpServletResponse response = context.getResponse(); | |
if (!isCacheActionRestul()) { | |
// Set headers to disable cache | |
response.setHeader("Pragma", "no-cache"); | |
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); | |
response.setDateHeader("Expires", new Date(1L).getTime()); | |
} | |
response.setContentType(getContentType()); | |
String localCharacterEncoding = getCharacterEncoding(); | |
if (localCharacterEncoding == null) { | |
// Fallback to request character encoding | |
localCharacterEncoding = context.getRequest().getCharacterEncoding(); | |
} | |
response.setCharacterEncoding(localCharacterEncoding); | |
} | |
} |