blob: 77a9aff27f5a1fd5c84fca258c830edf1de1aad4 [file] [log] [blame]
package freemarker.ext.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import freemarker.core.Environment;
import freemarker.core._DelayedFTLTypeDescription;
import freemarker.core._MiscTemplateException;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.utility.Collections12;
import freemarker.template.utility.DeepUnwrap;
/**
* A model that when invoked with a 'path' parameter will perform a servlet
* include. It also support an optional hash named 'params' which specifies
* request parameters for the include - its keys are strings, its values
* should be either strings or sequences of strings (for multiple valued
* parameters). A third optional parameter 'inherit_params' should be a boolean
* when specified, and it defaults to true when not specified. A value of true
* means that the include inherits the request parameters from the current
* request. In this case values in 'params' will get prepended to the existing
* values of parameters.
* @author Attila Szegedi
*/
public class IncludePage implements TemplateDirectiveModel
{
private final HttpServletRequest request;
private final HttpServletResponse response;
public IncludePage(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
public void execute(final Environment env, Map params,
TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException
{
// Determine the path
final TemplateModel path = (TemplateModel)params.get("path");
if(path == null) {
throw new _MiscTemplateException(env, "Missing required parameter \"path\"");
}
if(!(path instanceof TemplateScalarModel)) {
throw new _MiscTemplateException(env, new Object[] {
"Expected a scalar model. \"path\" is instead ",
new _DelayedFTLTypeDescription(path) });
}
final String strPath = ((TemplateScalarModel)path).getAsString();
if(strPath == null) {
throw new _MiscTemplateException(env, "String value of \"path\" parameter is null");
}
// See whether we need to use a custom response (if we're inside a TTM
// or TDM or macro nested body, we'll need to as then the current
// FM environment writer is not identical to HTTP servlet response
// writer.
final Writer envOut = env.getOut();
final HttpServletResponse wrappedResponse;
if(envOut == response.getWriter()) {
// Don't bother wrapping if environment's writer is same as
// response writer
wrappedResponse = response;
}
else {
final PrintWriter printWriter = (envOut instanceof PrintWriter) ?
(PrintWriter)envOut :
new PrintWriter(envOut);
// Otherwise, create a response wrapper that will pass the
// env writer, potentially first wrapping it in a print
// writer when it ain't one already.
wrappedResponse = new HttpServletResponseWrapper(response) {
public PrintWriter getWriter() {
return printWriter;
}
};
}
// Determine inherit_params value
final boolean inheritParams;
final TemplateModel inheritParamsModel = (TemplateModel)params.get("inherit_params");
if(inheritParamsModel == null) {
// defaults to true when not specified
inheritParams = true;
}
else {
if(!(inheritParamsModel instanceof TemplateBooleanModel)) {
throw new _MiscTemplateException(env, new Object[] {
"\"inherit_params\" should be a boolean but it's a(n) ",
inheritParamsModel.getClass().getName(), " instead" });
}
inheritParams = ((TemplateBooleanModel)inheritParamsModel).getAsBoolean();
}
// Get explicit params, if any
final TemplateModel paramsModel = (TemplateModel)params.get("params");
// Determine whether we need to wrap the request
final HttpServletRequest wrappedRequest;
if(paramsModel == null && inheritParams) {
// Inherit original request params & no params explicitly
// specified, so use the original request
wrappedRequest = request;
}
else {
// In any other case, use a custom request wrapper
final Map paramsMap;
if(paramsModel != null) {
// Convert params to a Map
final Object unwrapped = DeepUnwrap.unwrap(paramsModel);
if(!(unwrapped instanceof Map)) {
throw new _MiscTemplateException(env, new Object[] {
"Expected \"params\" to unwrap into a java.util.Map. It unwrapped into ",
unwrapped.getClass().getName(), " instead." });
}
paramsMap = (Map)unwrapped;
}
else {
paramsMap = Collections12.EMPTY_MAP;
}
wrappedRequest = new CustomParamsRequest(request, paramsMap,
inheritParams);
}
// Finally, do the include
try {
request.getRequestDispatcher(strPath).include(wrappedRequest,
wrappedResponse);
}
catch (ServletException e) {
throw new _MiscTemplateException(e, env);
}
}
private static final class CustomParamsRequest extends HttpServletRequestWrapper
{
private final HashMap paramsMap;
private CustomParamsRequest(HttpServletRequest request, Map paramMap,
boolean inheritParams) {
super(request);
paramsMap = inheritParams ? new HashMap(request.getParameterMap()) : new HashMap();
for (Iterator it = paramMap.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry)it.next();
String name = String.valueOf(entry.getKey());
Object value = entry.getValue();
final String[] valueArray;
if(value == null) {
// Null values are explicitly added (so, among other
// things, we can hide inherited param values).
valueArray = new String[] { null };
}
else if(value instanceof String[]) {
// String[] arrays are just passed through
valueArray = (String[])value;
}
else if(value instanceof Collection) {
// Collections are converted to String[], with
// String.valueOf() used on elements
Collection col = (Collection)value;
valueArray = new String[col.size()];
int i = 0;
for (Iterator it2 = col.iterator(); it2.hasNext();) {
valueArray[i++] = String.valueOf(it2.next());
}
}
else if(value.getClass().isArray()) {
// Other array types are too converted to String[], with
// String.valueOf() used on elements
int len = Array.getLength(value);
valueArray = new String[len];
for(int i = 0; i < len; ++i) {
valueArray[i] = String.valueOf(Array.get(value, i));
}
}
else {
// All other values (including strings) are converted to a
// single-element String[], with String.valueOf applied to
// the value.
valueArray = new String[] { String.valueOf(value) };
}
String[] existingParams = (String[])paramsMap.get(name);
int el = existingParams == null ? 0 : existingParams.length;
if(el == 0)
{
// No original params, just put our array
paramsMap.put(name, valueArray);
}
else
{
int vl = valueArray.length;
if(vl > 0)
{
// Both original params and new params, prepend our
// params to original params
String[] newValueArray = new String[el + vl];
System.arraycopy(valueArray, 0, newValueArray, 0, vl);
System.arraycopy(existingParams, 0, newValueArray, vl, el);
paramsMap.put(name, newValueArray);
}
}
}
}
public String[] getParameterValues(String name) {
String[] value = ((String[])paramsMap.get(name));
return value != null ? (String[])value.clone() : null;
}
public String getParameter(String name) {
String[] values = (String[])paramsMap.get(name);
return values != null && values.length > 0 ? values[0] : null;
}
public Enumeration getParameterNames() {
return Collections.enumeration(paramsMap.keySet());
}
public Map getParameterMap() {
HashMap clone = (HashMap)paramsMap.clone();
for (Iterator it = clone.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry)it.next();
entry.setValue(((String[])entry.getValue()).clone());
}
return Collections.unmodifiableMap(clone);
}
}
}