blob: 87192b5f0e19a6aea633ca9c95b4d963b3888d66 [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 org.ofbiz.widget.renderer;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.ParserConfigurationException;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.GeneralException;
import org.ofbiz.base.util.UtilDateTime;
import org.ofbiz.base.util.UtilFormatOut;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilHttp;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.collections.MapStack;
import org.ofbiz.base.util.template.FreeMarkerWorker;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntity;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.security.Security;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.GenericServiceException;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.webapp.control.LoginWorker;
import org.ofbiz.webapp.website.WebSiteWorker;
import org.ofbiz.widget.cache.GenericWidgetOutput;
import org.ofbiz.widget.cache.ScreenCache;
import org.ofbiz.widget.cache.WidgetContextCacheKey;
import org.ofbiz.widget.model.ModelScreen;
import org.ofbiz.widget.model.ScreenFactory;
import org.xml.sax.SAXException;
import freemarker.ext.jsp.TaglibFactory;
import freemarker.ext.servlet.HttpRequestHashModel;
import freemarker.ext.servlet.HttpSessionHashModel;
/**
* Widget Library - Screen model class
*/
public class ScreenRenderer {
public static final String module = ScreenRenderer.class.getName();
protected Appendable writer;
protected MapStack<String> context;
protected ScreenStringRenderer screenStringRenderer;
protected int renderFormSeqNumber = 0;
public ScreenRenderer(Appendable writer, MapStack<String> context, ScreenStringRenderer screenStringRenderer) {
this.writer = writer;
this.context = context;
if (this.context == null) this.context = MapStack.create();
this.screenStringRenderer = screenStringRenderer;
}
/**
* Renders the named screen using the render environment configured when this ScreenRenderer was created.
*
* @param combinedName A combination of the resource name/location for the screen XML file and the name of the screen within that file, separated by a pound sign ("#"). This is the same format that is used in the view-map elements on the controller.xml file.
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public String render(String combinedName) throws GeneralException, IOException, SAXException, ParserConfigurationException {
String resourceName = ScreenFactory.getResourceNameFromCombined(combinedName);
String screenName = ScreenFactory.getScreenNameFromCombined(combinedName);
this.render(resourceName, screenName);
return "";
}
/**
* Renders the named screen using the render environment configured when this ScreenRenderer was created.
*
* @param resourceName The name/location of the resource to use, can use "component://[component-name]/" and "ofbiz://" and other special OFBiz style URLs
* @param screenName The name of the screen within the XML file specified by the resourceName.
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public String render(String resourceName, String screenName) throws GeneralException, IOException, SAXException, ParserConfigurationException {
ModelScreen modelScreen = ScreenFactory.getScreenFromLocation(resourceName, screenName);
if (modelScreen.getUseCache()) {
// if in the screen definition use-cache is set to true
// then try to get an already built screen output from the cache:
// 1) if we find it then we get it and attach it to the passed in writer
// 2) if we can't find one, we create a new StringWriter,
// and pass it to the renderScreenString;
// then we wrap its content and put it in the cache;
// and we attach it to the passed in writer
WidgetContextCacheKey wcck = new WidgetContextCacheKey(context);
String screenCombinedName = resourceName + ":" + screenName;
ScreenCache screenCache = new ScreenCache();
GenericWidgetOutput gwo = screenCache.get(screenCombinedName, wcck);
if (gwo == null) {
Writer sw = new StringWriter();
modelScreen.renderScreenString(sw, context, screenStringRenderer);
gwo = new GenericWidgetOutput(sw.toString());
screenCache.put(screenCombinedName, wcck, gwo);
writer.append(gwo.toString());
} else {
writer.append(gwo.toString());
}
} else {
context.put("renderFormSeqNumber", String.valueOf(renderFormSeqNumber));
modelScreen.renderScreenString(writer, context, screenStringRenderer);
}
return "";
}
public void setRenderFormUniqueSeq (int renderFormSeqNumber) {
this.renderFormSeqNumber = renderFormSeqNumber;
}
public ScreenStringRenderer getScreenStringRenderer() {
return this.screenStringRenderer;
}
public void populateBasicContext(Map<String, Object> parameters, Delegator delegator, LocalDispatcher dispatcher, Security security, Locale locale, GenericValue userLogin) {
populateBasicContext(context, this, parameters, delegator, dispatcher, security, locale, userLogin);
}
public static void populateBasicContext(MapStack<String> context, ScreenRenderer screens, Map<String, Object> parameters, Delegator delegator, LocalDispatcher dispatcher, Security security, Locale locale, GenericValue userLogin) {
// ========== setup values that should always be in a screen context
// include an object to more easily render screens
context.put("screens", screens);
// make a reference for high level variables, a global context
context.put("globalContext", context.standAloneStack());
// make sure the "nullField" object is in there for entity ops; note this is nullField and not null because as null causes problems in FreeMarker and such...
context.put("nullField", GenericEntity.NULL_FIELD);
context.put("parameters", parameters);
context.put("delegator", delegator);
context.put("dispatcher", dispatcher);
context.put("security", security);
context.put("locale", locale);
context.put("userLogin", userLogin);
context.put("nowTimestamp", UtilDateTime.nowTimestamp());
try {
Map<String, Object> result = dispatcher.runSync("getUserPreferenceGroup", UtilMisc.toMap("userLogin", userLogin, "userPrefGroupTypeId", "GLOBAL_PREFERENCES"));
context.put("userPreferences", result.get("userPrefMap"));
} catch (GenericServiceException e) {
Debug.logError(e, "Error while getting user preferences: ", module);
}
}
/**
* This method populates the context for this ScreenRenderer based on the HTTP Request and Response objects and the ServletContext.
* It leverages various conventions used in other places, namely the ControlServlet and so on, of OFBiz to get the different resources needed.
*
* @param request
* @param response
* @param servletContext
*/
public void populateContextForRequest(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
populateContextForRequest(context, this, request, response, servletContext);
}
@SuppressWarnings("rawtypes")
public static void populateContextForRequest(MapStack<String> context, ScreenRenderer screens, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
HttpSession session = request.getSession();
// attribute names to skip for session and application attributes; these are all handled as special cases, duplicating results and causing undesired messages
Set<String> attrNamesToSkip = UtilMisc.toSet("delegator", "dispatcher", "security", "webSiteId",
"org.apache.catalina.jsp_classpath");
Map<String, Object> parameterMap = UtilHttp.getCombinedMap(request, attrNamesToSkip);
GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
populateBasicContext(context, screens, parameterMap, (Delegator) request.getAttribute("delegator"),
(LocalDispatcher) request.getAttribute("dispatcher"),
(Security) request.getAttribute("security"), UtilHttp.getLocale(request), userLogin);
context.put("autoUserLogin", session.getAttribute("autoUserLogin"));
context.put("person", session.getAttribute("person"));
context.put("partyGroup", session.getAttribute("partyGroup"));
// some things also seem to require this, so here it is:
request.setAttribute("userLogin", userLogin);
// set up the user's time zone
context.put("timeZone", UtilHttp.getTimeZone(request));
// ========== setup values that are specific to OFBiz webapps
context.put("request", request);
context.put("response", response);
context.put("session", session);
context.put("application", servletContext);
if (session != null) {
context.put("webappName", session.getAttribute("_WEBAPP_NAME_"));
}
if (servletContext != null) {
String rootDir = (String) context.get("rootDir");
String webSiteId = (String) context.get("webSiteId");
String https = (String) context.get("https");
if (UtilValidate.isEmpty(rootDir)) {
rootDir = servletContext.getRealPath("/");
context.put("rootDir", rootDir);
}
if (UtilValidate.isEmpty(webSiteId)) {
webSiteId = WebSiteWorker.getWebSiteId(request);
context.put("webSiteId", webSiteId);
}
if (UtilValidate.isEmpty(https)) {
https = (String) servletContext.getAttribute("https");
context.put("https", https);
}
}
context.put("javaScriptEnabled", Boolean.valueOf(UtilHttp.isJavaScriptEnabled(request)));
// these ones are FreeMarker specific and will only work in FTL templates, mainly here for backward compatibility
context.put("sessionAttributes", new HttpSessionHashModel(session, FreeMarkerWorker.getDefaultOfbizWrapper()));
context.put("requestAttributes", new HttpRequestHashModel(request, FreeMarkerWorker.getDefaultOfbizWrapper()));
TaglibFactory JspTaglibs = new TaglibFactory(servletContext);
context.put("JspTaglibs", JspTaglibs);
context.put("requestParameters", UtilHttp.getParameterMap(request));
// this is a dummy object to stand-in for the JPublish page object for backward compatibility
context.put("page", new HashMap());
// some information from/about the ControlServlet environment
context.put("controlPath", request.getAttribute("_CONTROL_PATH_"));
context.put("contextRoot", request.getAttribute("_CONTEXT_ROOT_"));
context.put("serverRoot", request.getAttribute("_SERVER_ROOT_URL_"));
context.put("checkLoginUrl", LoginWorker.makeLoginUrl(request));
String externalLoginKey = LoginWorker.getExternalLoginKey(request);
String externalKeyParam = externalLoginKey == null ? "" : "&amp;externalLoginKey=" + externalLoginKey;
context.put("externalLoginKey", externalLoginKey);
context.put("externalKeyParam", externalKeyParam);
// setup message lists
List<String> eventMessageList = UtilGenerics.toList(request.getAttribute("eventMessageList"));
if (eventMessageList == null) eventMessageList = new LinkedList<String>();
List<String> errorMessageList = UtilGenerics.toList(request.getAttribute("errorMessageList"));
if (errorMessageList == null) errorMessageList = new LinkedList<String>();
if (request.getAttribute("_EVENT_MESSAGE_") != null) {
eventMessageList.add(UtilFormatOut.replaceString((String) request.getAttribute("_EVENT_MESSAGE_"), "\n", "<br/>"));
request.removeAttribute("_EVENT_MESSAGE_");
}
List<String> msgList = UtilGenerics.toList(request.getAttribute("_EVENT_MESSAGE_LIST_"));
if (msgList != null) {
eventMessageList.addAll(msgList);
request.removeAttribute("_EVENT_MESSAGE_LIST_");
}
if (request.getAttribute("_ERROR_MESSAGE_") != null) {
errorMessageList.add(UtilFormatOut.replaceString((String) request.getAttribute("_ERROR_MESSAGE_"), "\n", "<br/>"));
request.removeAttribute("_ERROR_MESSAGE_");
}
if (session.getAttribute("_ERROR_MESSAGE_") != null) {
errorMessageList.add(UtilFormatOut.replaceString((String) session.getAttribute("_ERROR_MESSAGE_"), "\n", "<br/>"));
session.removeAttribute("_ERROR_MESSAGE_");
}
msgList = UtilGenerics.toList(request.getAttribute("_ERROR_MESSAGE_LIST_"));
if (msgList != null) {
errorMessageList.addAll(msgList);
request.removeAttribute("_ERROR_MESSAGE_LIST_");
}
context.put("eventMessageList", eventMessageList);
context.put("errorMessageList", errorMessageList);
if (request.getAttribute("serviceValidationException") != null) {
context.put("serviceValidationException", request.getAttribute("serviceValidationException"));
request.removeAttribute("serviceValidationException");
}
// if there was an error message, this is an error
context.put("isError", errorMessageList.size() > 0 ? Boolean.TRUE : Boolean.FALSE);
// if a parameter was passed saying this is an error, it is an error
if ("true".equals(parameterMap.get("isError"))) {
context.put("isError", Boolean.TRUE);
}
// to preserve these values, push the MapStack
context.push();
}
public Map<String, Object> getContext() {
return context;
}
public void populateContextForService(DispatchContext dctx, Map<String, Object> serviceContext) {
this.populateBasicContext(serviceContext, dctx.getDelegator(), dctx.getDispatcher(),
dctx.getSecurity(), (Locale) serviceContext.get("locale"), (GenericValue) serviceContext.get("userLogin"));
}
}