/*
 * 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.
/** * 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); } } }