blob: 069fe744d582c521c34f7e5f4552791820be72b4 [file] [log] [blame]
/*
* 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 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.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.
*/
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,
"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) {
@Override
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,
"\"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,
"Expected \"params\" to unwrap into a java.util.Map. It unwrapped into ",
unwrapped.getClass().getName(), " instead.");
}
paramsMap = (Map) unwrapped;
} else {
paramsMap = Collections.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);
}
}
}
}
@Override
public String[] getParameterValues(String name) {
String[] value = ((String[]) paramsMap.get(name));
return value != null ? (String[]) value.clone() : null;
}
@Override
public String getParameter(String name) {
String[] values = (String[]) paramsMap.get(name);
return values != null && values.length > 0 ? values[0] : null;
}
@Override
public Enumeration getParameterNames() {
return Collections.enumeration(paramsMap.keySet());
}
@Override
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);
}
}
}