framework/webapp/src/com/ilscipio/scipio/ce/webapp/ftl/context/ContextFtlUtil.java
package com.ilscipio.scipio.ce.webapp.ftl.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilHttp;
import org.ofbiz.base.util.template.FreeMarkerWorker;
import org.ofbiz.entity.Delegator;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.webapp.FullWebappInfo;
import org.ofbiz.webapp.renderer.RenderEnvType;
import org.ofbiz.webapp.website.WebSiteProperties;
import com.ilscipio.scipio.ce.webapp.ftl.lang.LangFtlUtil;
import com.ilscipio.scipio.ce.webapp.ftl.lang.LangFtlUtil.TemplateValueTargetType;
import freemarker.core.Environment;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
/**
* SCIPIO: Ofbiz and Freemarker context-handling Freemarker utils.
* <p>
* Manages request attributes, context vars, and FTL context.
* <p>
* <strong>WARN:</strong> All utility methods here (except special wrap methods)
* using ObjectWrapper should take an ObjectWrapper from caller - let caller decide which - and never
* call Environment.getObjectWrapper anymore.
*
* @see com.ilscipio.scipio.ce.webapp.ftl.CommonFtlUtil
*/
public abstract class ContextFtlUtil {
private static final Debug.OfbizLogger module = Debug.getOfbizLogger(java.lang.invoke.MethodHandles.lookup().lookupClass());
/**
* Global unique names of scipio request variables container maps, or in other words,
* the scipio request variables namespace name.
* <p>
* <em>NOTE</em>: 2015-10-30: All set/getRequestVar and push/read/popRequestStack variables are now stored in
* a second map within request attributes and context to give them their own namespace and make them trackable.
* <p>
* <em>NOTE</em>: Each context must use a different variable name, because contexts may get merged by the
* renderer in some situations and leads to unexpected type conflicts.
* <p>
* <strong>WARN</strong>: These names are subject to change; never rely on them. Nor should ever need to
* access them directly outside of this code.
*
* @see #pushRequestStack
* @see #getRequestVar
*/
public static final String REQUEST_VAR_MAP_NAME_REQATTRIBS = "scpLibReqVarsAttr";
public static final String REQUEST_VAR_MAP_NAME_GLOBALCONTEXT = "scpLibReqVarsCtx";
public static final String REQUEST_VAR_MAP_NAME_PLAINCONTEXT = "scpLibReqVarsCtx";
public static final String REQUEST_VAR_MAP_NAME_FTLGLOBALS = "scpLibReqVarsFtl";
public static final int REQUEST_STACK_INITIAL_CAPACITY = 10;
protected ContextFtlUtil() {
}
private static Environment getEnv() {
return FreeMarkerWorker.getCurrentEnvironment();
}
public static HttpServletRequest getRequest(Environment env) throws TemplateModelException {
WrapperTemplateModel req = (WrapperTemplateModel) env.getVariable("request");
return (req != null) ? (HttpServletRequest) req.getWrappedObject() : null;
}
public static HttpServletResponse getResponse(Environment env) throws TemplateModelException {
WrapperTemplateModel req = (WrapperTemplateModel) env.getVariable("response");
return (req != null) ? (HttpServletResponse) req.getWrappedObject() : null;
}
/**
* Fishes the unwrapped/raw screen context out of FTL environment.
* <p>
* <strong>WARNING/FIXME?</strong>: in current scipio-patched macro and survey renderers, when this called from
* macros/templates it will be null; only globalContext is present. note "context" is not
* a real var but a special MapStack key (only on MapStack.get(); not part of MapStack.keySet() at time of writing).
*/
public static Map<String, Object> getContext(Environment env) throws TemplateModelException {
// this is what Ofbiz code currently does; should be a BeanModel wrapping the real context.
return FreeMarkerWorker.getWrappedObject("context", env);
}
/**
* Fishes the unwrapped/raw screen globalContext out of FTL environment.
*/
public static Map<String, Object> getGlobalContext(Environment env) throws TemplateModelException {
Map<String, Object> res = FreeMarkerWorker.getWrappedObject("globalContext", env);
// I think globalContext is always present as a top-level #global or is supposed to be,
// at least in standard render and scipio-patched other renders, and that it unwraps
// to real globalContext from BeanModel, but in case not, check context.globalContext.
if (res == null) {
Map<String, Object> context = getContext(env);
if (context != null) {
res = UtilGenerics.checkMap(context.get("globalContext"));
}
}
return res;
}
/**
* Get screen globalContext from given context, or from FTL env if (and only if) passed context is null.
*/
public static Map<String, Object> getGlobalContext(Map<String, Object> context, Environment env) throws TemplateModelException {
Map<String, Object> res = null;
if (context != null) {
res = UtilGenerics.checkMap(context.get("globalContext"));
}
else if (env != null) {
res = getGlobalContext(env);
}
return res;
}
public static Delegator getDelegator(Environment env) throws TemplateModelException {
return FreeMarkerWorker.getWrappedObject("delegator", env);
}
public static Delegator getDelegator() throws TemplateModelException {
return getDelegator(getEnv());
}
public static Delegator getDelegator(HttpServletRequest request, Environment env) throws TemplateModelException {
return (request != null) ? (Delegator) request.getAttribute("delegator") : FreeMarkerWorker.getWrappedObject("delegator", env);
}
public static LocalDispatcher getDispatcher(Environment env) throws TemplateModelException {
return FreeMarkerWorker.getWrappedObject("dispatcher", env);
}
public static LocalDispatcher getDispatcher() throws TemplateModelException {
return getDispatcher(getEnv());
}
public static LocalDispatcher getDispatcher(HttpServletRequest request, Environment env) throws TemplateModelException {
return (request != null) ? (LocalDispatcher) request.getAttribute("dispatcher") : FreeMarkerWorker.getWrappedObject("dispatcher", env);
}
/**
* Removes the whole request vars map.
*/
public static void removeRequestVars(HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
if (request != null) {
request.removeAttribute(ContextFtlUtil.REQUEST_VAR_MAP_NAME_REQATTRIBS);
}
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
globalContext.remove(ContextFtlUtil.REQUEST_VAR_MAP_NAME_GLOBALCONTEXT);
}
if (env != null) {
env.setGlobalVariable(ContextFtlUtil.REQUEST_VAR_MAP_NAME_FTLGLOBALS, null);
}
}
/**
* Wipes all request vars and sets a new holder map.
* <p>
* Attempts to set the same map for as many contexts present as possible, for compatibility.
* <p>
* <em>NOTE:</em> in some cases this call is not necessary, but it's a good idea anyway
* because it will set the same map for request and context (but not ftl - FIXME?),
* which might prevent problems in odd rendering cases.
* This should be called at beginning of rendering at a point where as many of the parameters
* are non-null as possible (but env will probably usually be null).
*/
public static void resetRequestVars(HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
RequestVarMapWrapper mapWrapper = new RequestVarMapWrapper();
if (request != null) {
request.setAttribute(ContextFtlUtil.REQUEST_VAR_MAP_NAME_REQATTRIBS, mapWrapper);
}
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
globalContext.put(ContextFtlUtil.REQUEST_VAR_MAP_NAME_GLOBALCONTEXT, mapWrapper);
}
if (env != null) {
// Here we "hide" our variables in freemarker globals
env.setGlobalVariable(ContextFtlUtil.REQUEST_VAR_MAP_NAME_FTLGLOBALS, new RequestVarMapWrapperModel(mapWrapper.getRawMap()));
}
}
public static void resetRequestVars(HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
resetRequestVars(request, context, null);
}
private static Map<String, Object> getRequestVarMapFromReqAttribs(HttpServletRequest request) {
RequestVarMapWrapper mapWrapper = (RequestVarMapWrapper) request.getAttribute(ContextFtlUtil.REQUEST_VAR_MAP_NAME_REQATTRIBS);
if (mapWrapper == null) {
// FIXME: should try to get underlying map from globalContext or FTL globals
mapWrapper = new RequestVarMapWrapper();
request.setAttribute(ContextFtlUtil.REQUEST_VAR_MAP_NAME_REQATTRIBS, mapWrapper);
}
return mapWrapper.getRawMap();
}
private static Map<String, Object> getRequestVarMapFromGlobalContext(Map<String, Object> parentMap) {
RequestVarMapWrapper mapWrapper = (RequestVarMapWrapper) parentMap.get(ContextFtlUtil.REQUEST_VAR_MAP_NAME_GLOBALCONTEXT);
if (mapWrapper == null) {
// FIXME: should try to get underlying map from request or FTL globals
mapWrapper = new RequestVarMapWrapper();
parentMap.put(ContextFtlUtil.REQUEST_VAR_MAP_NAME_GLOBALCONTEXT, mapWrapper);
}
return mapWrapper.getRawMap();
}
private static Map<String, Object> getRequestVarMapFromPlainContext(Map<String, Object> context) {
RequestVarMapWrapper mapWrapper = (RequestVarMapWrapper) context.get(ContextFtlUtil.REQUEST_VAR_MAP_NAME_PLAINCONTEXT);
if (mapWrapper == null) {
// FIXME: should try to get underlying map from request or FTL globals
mapWrapper = new RequestVarMapWrapper();
context.put(ContextFtlUtil.REQUEST_VAR_MAP_NAME_PLAINCONTEXT, mapWrapper);
}
return mapWrapper.getRawMap();
}
private static Map<String, Object> getRequestVarMapFromFtlGlobals(Environment env) {
RequestVarMapWrapperModel mapWrapper = null;
try {
mapWrapper = (RequestVarMapWrapperModel) env.getGlobalVariable(ContextFtlUtil.REQUEST_VAR_MAP_NAME_FTLGLOBALS);
} catch (TemplateModelException e) {
Debug.logError(e, "Scipio: Error getting request var map from FTL globals", module);
}
if (mapWrapper == null) {
// FIXME: should try to get underlying map from request or globalContext
mapWrapper = new RequestVarMapWrapperModel();
env.setGlobalVariable(ContextFtlUtil.REQUEST_VAR_MAP_NAME_FTLGLOBALS, mapWrapper);
}
return mapWrapper.getRawMap();
}
/**
* This silly wrapper is needed to prevent Ofbiz context's auto-escaping mechanism from wrapping our map and
* creating auto-escaping issues.
* <p>
* It must NOT extend Map interface.
*/
public static class RequestVarMapWrapper {
private final Map<String, Object> map;
public RequestVarMapWrapper(Map<String, Object> map) {
this.map = map;
}
public RequestVarMapWrapper() {
this.map = new HashMap<>();
}
public Map<String, Object> getRawMap() {
return map;
}
}
/**
* This wrapper essentially hides the request var map values from Freemarker
* so we don't have to deal with wrapping/unwrapping anymore. We simply
* store this in FTL vars and use methods provided here to read/write it.
*/
public static class RequestVarMapWrapperModel implements TemplateModel {
private final Map<String, Object> map;
public RequestVarMapWrapperModel(Map<String, Object> map) {
this.map = map;
}
public RequestVarMapWrapperModel() {
this.map = new HashMap<>();
}
public Map<String, Object> getRawMap() {
return map;
}
}
/**
* Method for setting request-scope variables, with fallback to globals.
* <p>
* Values set by this method should only be read using {@link #getRequestVar} (or a transform that calls it).
* The values set in request and context may be stored wrapped as <code>TemplateModel</code> or raw object at
* implementation's discretion. Likewise in general values read back may be either wrapped or raw object and caller
* has to check and handle (get/read methods do not convert result), which freemarker calls do anyway.
* <p>
* Name should be globally unique across request attribs, screen contexts and FTL context at same time.
* <p>
* Currently this sets request attributes above all. If request is missing, tries to set a var in screen globalContext
* (from passed context; if context null, fished out of FTL env). If globalContext is missing, last resort is to set
* an FTL #global var. i.e. tries to use longest-lived scope possible.
* <p>
* <em>WARN</em>: In general the values are NOT unwrapped by this method before being stored; they preserve their original types
* wherever possible; caller handles unwrapping if desired. In theory we should unwrap EVERYTHING before
* storage because the request var map may be used across different rendering contexts;
* however this adds even more performance overhead.
* FIXME?: Maybe should force unwrapping to be safe (and prevent auto-escape wrapping as well)...
* but will affect all callers.
* <p>
* <em>NOTE</em>: 2015-10-30: All set/getRequestVar and push/read/popRequestStack variables are now stored in
* a second map within request attributes and context to give them their own namespace and make them trackable.
* <p>
* <em>NOTE</em>: decision to use request, globalContext or FTL globals is based
* on whether these contexts are passed, so "statically". it's not based on whether var itself exists.
* <p>
* <em>DEV NOTE</em>: could also have set var in all contexts every time but then pushRequestStack
* has to do the same for consistency and there it would affect performance.
* setting in all contexts I think would only hide renderer bugs anyway.
* <p>
* <em>DEV NOTE</em>: could also set all these vars in their own separate map which is then stored
* in req attribs/globals. help to avoid name clashes but don't see need yet.
*
* @param name the multi-context unique global var name
* @param value the value, either raw or <code>TemplateModel</code>
* @param request the servlet request, or null if not available
* @param context the screen context, or null if not available
* @param env the Freemarker environment, or null if not available
* @throws TemplateModelException
* @see #getRequestVar
*/
static void setRequestVar(String name, Object value, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
if (request != null) {
getRequestVarMapFromReqAttribs(request).put(name, value);
//Debug.logInfo("setRequestVar: request attrib (name: " + name + ")", module);
} else {
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
getRequestVarMapFromGlobalContext(globalContext).put(name, value);
//globalContext.put(name, value);
//Debug.logInfo("setRequestVar: globalContext var (name: " + name + ")", module);
} else if (env != null) {
getRequestVarMapFromFtlGlobals(env).put(name, value);
//Debug.logInfo("setRequestVar: ftl global var (name: " + name + ")", module);
} else {
// For cases with non-standard or plain context
//throw new IllegalArgumentException("No request, context or ftl environment to set request scope var (name: " + name + ")");
getRequestVarMapFromPlainContext(context).put(name, value);
}
}
}
public static void setRequestVar(String name, Object value, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) { // optimization: don't need to look this up if has request (true in most cases now)
context = getContext(env);
}
setRequestVar(name, value, request, context, env);
}
public static void setRequestVar(String name, Object value, HttpServletRequest request,
Map<String, Object> context) throws TemplateModelException {
setRequestVar(name, value, request, context, null);
}
/**
* Method for getting request-scope variables, with fallback to globals.
* <p>
* Must and should only be used to read values set by {@link #setRequestVar}.
* <p>
* Return value may or may not be a <code>TemplateModel</code>; caller must wrap or unwrap as needed.
* Can use {@link com.ilscipio.scipio.ce.webapp.ftl.context.TransformUtil} <code>unwrapXxx</code> methods.
*
* @see #setRequestVar
*/
static Object getRequestVar(String name, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
Object res = null;
if (request != null) {
res = getRequestVarMapFromReqAttribs(request).get(name);
//Debug.logInfo("getRequestVar: request attrib (name: " + name + ")", module);
} else {
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
res = getRequestVarMapFromGlobalContext(globalContext).get(name);
//Debug.logInfo("getRequestVar: globalContext var (name: " + name + ")", module);
} else if (env != null) {
res = getRequestVarMapFromFtlGlobals(env).get(name);
//Debug.logInfo("getRequestVar: ftl global var (name: " + name + ")", module);
} else {
// For cases with non-standard or plain context
//throw new IllegalArgumentException("No request, context or ftl environment to set request scope var (name: " + name + ")");
res = getRequestVarMapFromPlainContext(context).get(name);
}
}
return res;
}
public static Object getRequestVar(String name, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
return getRequestVar(name, request, context, env);
}
public static Object getRequestVar(String name, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
return getRequestVar(name, request, context, null);
}
/**
* Method providing support for a stack structure having request scope, with fallback to globals.
* <p>
* <strong>Do not access underlying structure directly.</strong>
*
* @see #setRequestVar
*/
static void pushRequestStack(String name, Object value, boolean setLast, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
// NOTE: There is no value wrapping or unwrapping in these methods anymore; we just store them and all wrapping/unwrapping
// is left to caller unless absolutely necessary (and in those cases, he specifies the objectWrapper for behavior).
//if (value instanceof TemplateModel) {
// value = FtlTransformUtil.unwrapPermissive((TemplateModel) value);
//}
// if env.setGlobalVariable:
if (request != null) {
updateStack(getRequestVarMapFromReqAttribs(request), name, value, setLast, "request attributes");
}
else {
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
updateStack(getRequestVarMapFromGlobalContext(globalContext), name, value, setLast, "globalContext");
}
else if (env != null) {
updateStack(getRequestVarMapFromFtlGlobals(env), name, value, setLast, "FTL globals");
}
else {
throw new IllegalArgumentException("No request, context or ftl environment to push request scope stack (name: " + name + ")");
}
}
}
private static void updateStack(Map<String, Object> varMap, String name, Object value, boolean setLast, String desc) {
List<Object> stack;
Object stackObj = varMap.get(name);
if (stackObj instanceof List) {
stack = UtilGenerics.checkList(stackObj);
}
else {
if (stackObj != null) {
Debug.logWarning("Overriding " + desc + " var with new stack (name: " + name + ")", module);
}
stack = new ArrayList<>(ContextFtlUtil.REQUEST_STACK_INITIAL_CAPACITY);
}
if (setLast) {
if (stack.isEmpty()) {
stack.add(value);
}
else {
stack.set(stack.size() - 1, value);
}
}
else {
stack.add(value);
}
varMap.put(name, stack);
//Debug.logInfo("pushRequestStack: " + desc + " var (name: " + name + ")", module);
}
static void pushRequestStack(String name, Object value, boolean setLast, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
pushRequestStack(name, value, setLast, request, context, env);
}
static void pushRequestStack(String name, Object value, boolean setLast, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
pushRequestStack(name, value, setLast, request, context, null);
}
static void pushRequestStack(String name, Object value, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
pushRequestStack(name, value, false, request, context, env);
}
public static void pushRequestStack(String name, Object value, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
pushRequestStack(name, value, false, request, context, env);
}
public static void pushRequestStack(String name, Object value, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
pushRequestStack(name, value, false, request, context, null);
}
/**
* Similar to pushRequestStack but replaces last elem and never fails.
* <p>
* <strong>Do not access underlying structure directly.</strong>
*
* @see #setRequestVar
*/
static void setLastRequestStack(String name, Object value, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
pushRequestStack(name, value, true, request, context, env);
}
public static void setLastRequestStack(String name, Object value, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
pushRequestStack(name, value, true, request, context, env);
}
public static void setLastRequestStack(String name, Object value, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
pushRequestStack(name, value, true, request, context, null);
}
/**
* Method providing support for a stack structure having request scope, with fallback to globals.
* <p>
* <strong>Do not access underlying structure directly.</strong>
* <p>
* Return value may or may not be a <code>TemplateModel</code>; caller must wrap or unwrap as needed.
*
* @see #setRequestVar
*/
static Object readRequestStack(String name, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
return ContextFtlUtil.readRequestStack(name, false, request, context, env);
}
public static Object readRequestStack(String name, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
return ContextFtlUtil.readRequestStack(name, false, request, context, env);
}
public static Object readRequestStack(String name, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
return ContextFtlUtil.readRequestStack(name, false, request, context, null);
}
static Object readRequestStack(String name, boolean pop, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
Object res = null;
if (request != null) {
res = readStack(getRequestVarMapFromReqAttribs(request), name, pop, "request attributes");
}
else {
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
res = readStack(getRequestVarMapFromGlobalContext(globalContext), name, pop, "globalContext");
}
else if (env != null) {
res = readStack(getRequestVarMapFromFtlGlobals(env), name, pop, "FTL globals");
}
else {
throw new IllegalArgumentException("No request, context or ftl environment to " + (pop ? "pop" : "read") + " request scope stack (name: " + name + ")");
}
}
return res;
}
private static Object readStack(Map<String, Object> varMap, String name, boolean pop, String desc) {
Object res = null;
List<Object> stack = null;
Object stackObj = varMap.get(name);
if (stackObj instanceof List) {
stack = UtilGenerics.checkList(stackObj);
}
if (stack != null && !stack.isEmpty()) {
res = pop ? stack.remove(stack.size() - 1) : stack.get(stack.size() - 1);
// don't need, just rely on references
//if (pop) {
// request.setAttribute(name, stack);
//}
}
else if (pop) {
Debug.logError("Trying to pop empty " + desc + " stack (name: " + name + ")", module);
}
//Debug.logInfo((pop ? "pop" : "read") + "RequestStack: " + desc + " (name: " + name + ")", module);
return res;
}
static Object readRequestStack(String name, boolean pop, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
return readRequestStack(name, pop, request, context, env);
}
static Object readRequestStack(String name, boolean pop, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
return readRequestStack(name, pop, request, context, null);
}
/**
* Method providing support for a stack structure having request scope, with fallback to globals.
* <p>
* <strong>Do not access underlying structure directly.</strong>
* <p>
* Return value may or may not be a <code>TemplateModel</code>; caller must wrap or unwrap as needed.
*
* @see #setRequestVar
*/
static Object popRequestStack(String name, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
return readRequestStack(name, true, request, context, env);
}
public static Object popRequestStack(String name, Environment env, ObjectWrapper objectWrapper) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
return readRequestStack(name, true, request, context, env);
}
public static Object popRequestStack(String name, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
return readRequestStack(name, true, request, context, null);
}
/**
* Gets the request stack as a list. The stack cannot be modified using this list.
* It may be TemplateModel-wrapped or unwrapped as may be the individual values.
*
* @param name
* @param request
* @param context
* @param env
* @param copyTargetType target type for list copy. if null, does not copy (should be avoided in most cases!).
* @return
* @throws TemplateModelException
*/
static Object getRequestStackAsList(String name, HttpServletRequest request,
Map<String, Object> context, Environment env, ObjectWrapper objectWrapper, TemplateValueTargetType copyTargetType) throws TemplateModelException {
if (request != null) {
return getStackAsList(getRequestVarMapFromReqAttribs(request), name, copyTargetType, objectWrapper, "request attributes");
}
else {
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
return getStackAsList(getRequestVarMapFromGlobalContext(globalContext), name, copyTargetType, objectWrapper, "globalContext");
}
else if (env != null) {
return getStackAsList(getRequestVarMapFromFtlGlobals(env), name, copyTargetType, objectWrapper, "FTL globals");
}
else {
throw new IllegalArgumentException("No request, context or ftl environment to get request scope stack (name: " + name + ")");
}
}
}
private static Object getStackAsList(Map<String, Object> varMap, String name, TemplateValueTargetType copyTargetType, ObjectWrapper objectWrapper, String desc) throws TemplateModelException {
List<Object> stack = null;
Object stackObj = varMap.get(name);
if (stackObj instanceof List) {
stack = UtilGenerics.checkList(stackObj);
}
if (stack != null) {
if (copyTargetType == null) {
return Collections.unmodifiableList(stack);
}
else {
return LangFtlUtil.copyList(stack, copyTargetType, objectWrapper);
}
}
else {
return null;
}
}
/**
* Returns copy of request stack as a SimpleSequence.
*/
public static Object getRequestStackAsList(String name, Environment env, ObjectWrapper objectWrapper) throws TemplateModelException {
return ContextFtlUtil.getRequestStackAsList(name, LangFtlUtil.TemplateValueTargetType.SIMPLEMODEL, env, objectWrapper);
}
/**
* Returns copy of request stack in request copyTargetType.
* <strong>WARN</strong>: if copyTargetType is null, no copy is made and unmodifiable list is returned.
* This list must be discarded by caller as soon as possible, before any more changes to the stack.
*/
public static Object getRequestStackAsList(String name, LangFtlUtil.TemplateValueTargetType copyTargetType, Environment env, ObjectWrapper objectWrapper) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
return getRequestStackAsList(name, request, context, env, objectWrapper, copyTargetType);
}
static Integer getRequestStackSize(String name, HttpServletRequest request,
Map<String, Object> context, Environment env) throws TemplateModelException {
if (request != null) {
return getStackSize(getRequestVarMapFromReqAttribs(request), name);
}
else {
Map<String, Object> globalContext = getGlobalContext(context, env);
if (globalContext != null) {
return getStackSize(getRequestVarMapFromGlobalContext(globalContext), name);
}
else if (env != null) {
return getStackSize(getRequestVarMapFromFtlGlobals(env), name);
}
else {
return null;
}
}
}
private static Integer getStackSize(Map<String, Object> varMap, String name) throws TemplateModelException {
List<Object> stack = null;
Object stackObj = varMap.get(name);
if (stackObj instanceof List) {
stack = UtilGenerics.checkList(stackObj);
}
if (stack != null) {
return stack.size();
}
else {
return null;
}
}
public static Integer getRequestStackSize(String name, Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
Map<String, Object> context = null;
if (request == null) {
context = getContext(env);
}
return getRequestStackSize(name, request, context, env);
}
/**
* Returns copy of request stack as a raw List (but elements may still be TemplateModels).
*/
public static Object getRequestStackAsList(String name, HttpServletRequest request, Map<String, Object> context) throws TemplateModelException {
return getRequestStackAsList(name, request, context, null, null, LangFtlUtil.TemplateValueTargetType.RAW);
}
/**
* Merges macro argument maps following the args/inlineArgs pattern.
* Auto-converts maps to simple maps (but only those where this is logical; usually only
* the args parameter).
* <p>
* NOTE: The objectWrapper is the one the resulting SimpleHash is initialized with, but usually it is
* not used for any of the initial values merged into it by this call.
* <p>
* FIXME? suboptimal; must optimize; hard to do. complication is we have no access to the concatenation operator "+"
* and its implementation is private in Freemarker.
*
*/
public static TemplateHashModelEx mergeArgMaps(TemplateHashModelEx args, TemplateHashModelEx inlineArgs,
TemplateHashModelEx defaultArgs, TemplateHashModelEx overrideArgs, boolean recordArgNames, Environment env, ObjectWrapper objectWrapper) throws TemplateModelException {
SimpleHash res = new SimpleHash(objectWrapper);
if (args != null) {
args = (TemplateHashModelEx) LangFtlUtil.toSimpleMap(args, null, objectWrapper);
}
if (defaultArgs != null && !defaultArgs.isEmpty()) {
LangFtlUtil.addToSimpleMap(res, defaultArgs);
}
if (args != null && !args.isEmpty()) {
LangFtlUtil.addToSimpleMap(res, args);
}
if (inlineArgs != null && !inlineArgs.isEmpty()) {
LangFtlUtil.addToSimpleMap(res, inlineArgs);
}
if (overrideArgs != null && !overrideArgs.isEmpty()) {
LangFtlUtil.addToSimpleMap(res, overrideArgs);
}
if (recordArgNames) {
// FIXME: this whole part definitely too inefficient, but freemarker wants it...
// TODO: for allArgNames, use bean-wrapped set and modify in-place (screw immutability)
// TODO: for localArgNames, would be better to have the "+" operator result...
// Problem: we have no access to the concatenation operator "+" which in some cases is desirable
TemplateCollectionModel defaultKeys = defaultArgs != null ? defaultArgs.keys() : null;
TemplateCollectionModel overrideKeys = overrideArgs != null ? overrideArgs.keys() : null;
SimpleSequence localArgNames = new SimpleSequence(objectWrapper);
if (defaultKeys != null) {
LangFtlUtil.addToSimpleList(localArgNames, defaultKeys);
}
if (overrideKeys != null) {
LangFtlUtil.addToSimpleList(localArgNames, overrideKeys);
}
TemplateModel allArgNamesPrev = args != null ? args.get("allArgNames") : null;
SimpleSequence allArgNames = new SimpleSequence(objectWrapper);
if (allArgNamesPrev != null && allArgNamesPrev != TemplateModel.NOTHING) {
LangFtlUtil.addToSimpleList(allArgNames, allArgNamesPrev);
}
LangFtlUtil.addToSimpleList(allArgNames, localArgNames);
res.put("localArgNames", localArgNames);
res.put("allArgNames", allArgNames);
}
return res;
}
/**
* Gets "current" locale from the context or otherwise using the HttpServletRequest object in context using UtilHttp
* (abstracted method, behavior could change).
* NOTE: This is the preferred method in most cases.
*/
public static Locale getCurrentLocale(Environment env) throws TemplateModelException {
return getContextOrRequestLocale(env);
}
/**
* Attempts to get the current "user" or "screen" locale normally found in screen context
* as the simple "locale" variable.
* TODO: REVIEW: this is currently using getGlobalVariable as most likely the fastest that
* will avoid problems from macros - unclear if more or less reliable than trying to read
* out of "context" map (which not guaranteed present).
* <p>
* NOTE: In most transforms, the preferred method is: {@link #getCurrentLocale(Environment)}.
*/
public static Locale getContextLocale(Environment env) throws TemplateModelException {
WrapperTemplateModel model = (WrapperTemplateModel) env.getGlobalVariable("locale");
return (model != null) ? (Locale) ((WrapperTemplateModel) model).getWrappedObject() : null;
}
/**
* Gets locale from the HttpServletRequest object in context using UtilHttp.
* <p>
* NOTE: In most transforms, the preferred method is: {@link #getCurrentLocale(Environment)}.
*/
public static Locale getRequestLocale(Environment env) throws TemplateModelException {
HttpServletRequest request = getRequest(env);
return (request != null) ? UtilHttp.getLocale(request) : null;
}
/**
* Gets "current" locale from the context or otherwise using the HttpServletRequest object in context using UtilHttp.
* <p>
* NOTE: In most transforms, the preferred method is: {@link #getCurrentLocale(Environment)}.
*/
public static Locale getContextOrRequestLocale(Environment env) throws TemplateModelException {
Locale locale = getContextLocale(env);
return (locale != null) ? locale : getRequestLocale(env);
}
/**
* Determines the render context type.
*/
public static RenderEnvType getRenderEnvType(Environment env, HttpServletRequest request) throws TemplateModelException {
return RenderEnvType.fromRequestOrFtlEnv(request, env);
}
/**
* Determines if the render is being done through a non-webapp context.
* <p>
* NOTE: null request does not guarantee non-webapp context, because some specialized utilities
* render templates using small makeshift contexts (e.g. surveys).
*/
public static RenderEnvType getRenderEnvType(Environment env) throws TemplateModelException {
return RenderEnvType.fromFtlEnv(env);
}
public static Map<String, WebSiteProperties> getWebSitePropertiesCache(Environment env, HttpServletRequest request) throws TemplateModelException {
@SuppressWarnings("unchecked")
Map<String, WebSiteProperties> cache = (Map<String, WebSiteProperties>) getRequestVar("scpWebSitePropsCache", env);
if (cache == null) {
cache = new HashMap<>();
setRequestVar("scpWebSitePropsCache", cache, env);
}
return cache;
}
/**
* Gets the FullWebappInfo cache from request or context and pre-caches the
* "current" webapp info, which can be retrieved using
* {@link org.ofbiz.webapp.FullWebappInfo.Cache#getCurrentWebappInfo()}.
*/
public static FullWebappInfo.Cache getWebappInfoCacheAndCurrent(Environment env, HttpServletRequest request,
RenderEnvType renderEnvType) throws TemplateModelException, IllegalArgumentException {
FullWebappInfo.Cache cache;
if (request != null) {
cache = FullWebappInfo.Cache.fromRequest(request);
FullWebappInfo.fromRequest(request, cache);
} else {
Map<String, Object> context = getContext(env);
if (context == null) {
// TODO?: don't bother with env.getVariable, this will always be here...
Debug.logWarning("getCurrentWebappInfo: 'context' variable not"
+ " found in Freemarker environment; cannot determine current website or cache webapp info", module);
return FullWebappInfo.Cache.newCache(getDelegator(env));
}
cache = FullWebappInfo.Cache.fromContext(context, renderEnvType);
FullWebappInfo.fromContext(context, renderEnvType, cache);
return cache;
}
return cache;
}
/**
* Gets the FullWebappInfo cache from request or context and pre-caches the
* "current" webapp info, which can be retrieved using
* {@link org.ofbiz.webapp.FullWebappInfo.Cache#getCurrentWebappInfo()}.
*/
public static FullWebappInfo.Cache getWebappInfoCacheAndCurrent(HttpServletRequest request, Map<String, Object> context,
RenderEnvType renderEnvType) throws IllegalArgumentException {
FullWebappInfo.Cache cache;
if (request != null) {
cache = FullWebappInfo.Cache.fromRequest(request);
FullWebappInfo.fromRequest(request, cache);
} else {
cache = FullWebappInfo.Cache.fromContext(context, renderEnvType);
FullWebappInfo.fromContext(context, renderEnvType, cache);
return cache;
}
return cache;
}
}