/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software GmbH, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.jsp;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.flex.CmsFlexController;
import org.opencms.flex.CmsFlexResponse;
import org.opencms.loader.CmsLoaderException;
import org.opencms.loader.I_CmsResourceLoader;
import org.opencms.loader.I_CmsResourceStringDumpLoader;
import org.opencms.main.CmsException;
import org.opencms.main.OpenCms;
import org.opencms.staticexport.CmsLinkManager;
import org.opencms.util.CmsCollectionsGenericWrapper;
import org.opencms.util.CmsRequestUtil;
import org.opencms.util.CmsStringUtil;
import org.opencms.workplace.editors.directedit.CmsDirectEditParams;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
/**
* Implementation of the <code><cms:include/></code> tag,
* used to include another OpenCms managed resource in a JSP.<p>
*
* @since 6.0.0
*/
public class CmsJspTagInclude extends BodyTagSupport implements I_CmsJspTagParamParent {
/** Serial version UID required for safe serialization. */
private static final long serialVersionUID = 705978510743164951L;
/** The value of the "attribute" attribute. */
private String m_attribute;
/** The value of the "cacheable" attribute. */
private boolean m_cacheable;
/** The value of the "editable" attribute. */
private boolean m_editable;
/** The value of the "element" attribute. */
private String m_element;
/** Map to save parameters to the include in. */
private Map<String, String[]> m_parameterMap;
/** The value of the "property" attribute. */
private String m_property;
/** The value of the "suffix" attribute. */
private String m_suffix;
/** The value of the "page" attribute. */
private String m_target;
/**
* Empty constructor, required for attribute value initialization.<p>
*/
public CmsJspTagInclude() {
super();
m_cacheable = true;
}
/**
* Adds parameters to a parameter Map that can be used for a http request.<p>
*
* @param parameters the Map to add the parameters to
* @param name the name to add
* @param value the value to add
* @param overwrite if <code>true</code>, a parameter in the map will be overwritten by
* a parameter with the same name, otherwise the request will have multiple parameters
* with the same name (which is possible in http requests)
*/
public static void addParameter(Map<String, String[]> parameters, String name, String value, boolean overwrite) {
// No null values allowed in parameters
if ((parameters == null) || (name == null) || (value == null)) {
return;
}
// Check if the parameter name (key) exists
if (parameters.containsKey(name) && (!overwrite)) {
// Yes: Check name values if value exists, if so do nothing, else add new value
String[] values = parameters.get(name);
String[] newValues = new String[values.length + 1];
System.arraycopy(values, 0, newValues, 0, values.length);
newValues[values.length] = value;
parameters.put(name, newValues);
} else {
// No: Add new parameter name / value pair
String[] values = new String[] {value};
parameters.put(name, values);
}
}
/**
* Includes the selected target.<p>
*
* @param context the current JSP page context
* @param target the target for the include, might be <code>null</code>
* @param element the element to select form the target might be <code>null</code>
* @param editable flag to indicate if the target is editable
* @param paramMap a map of parameters for the include, will be merged with the request
* parameters, might be <code>null</code>
* @param attrMap a map of attributes for the include, will be merged with the request
* attributes, might be <code>null</code>
* @param req the current request
* @param res the current response
*
* @throws JspException in case something goes wrong
*/
public static void includeTagAction(
PageContext context,
String target,
String element,
boolean editable,
Map<String, String[]> paramMap,
Map<String, Object> attrMap,
ServletRequest req,
ServletResponse res) throws JspException {
// no locale and no cachable parameter are used by default
includeTagAction(context, target, element, null, editable, true, paramMap, attrMap, req, res);
}
/**
* Includes the selected target.<p>
*
* @param context the current JSP page context
* @param target the target for the include, might be <code>null</code>
* @param element the element to select form the target, might be <code>null</code>
* @param locale the locale to use for the selected element, might be <code>null</code>
* @param editable flag to indicate if the target is editable
* @param cacheable flag to indicate if the target should be cacheable in the Flex cache
* @param paramMap a map of parameters for the include, will be merged with the request
* parameters, might be <code>null</code>
* @param attrMap a map of attributes for the include, will be merged with the request
* attributes, might be <code>null</code>
* @param req the current request
* @param res the current response
*
* @throws JspException in case something goes wrong
*/
public static void includeTagAction(
PageContext context,
String target,
String element,
Locale locale,
boolean editable,
boolean cacheable,
Map<String, String[]> paramMap,
Map<String, Object> attrMap,
ServletRequest req,
ServletResponse res) throws JspException {
// the Flex controller provides access to the internal OpenCms structures
CmsFlexController controller = CmsFlexController.getController(req);
if (target == null) {
// set target to default
target = controller.getCmsObject().getRequestContext().getUri();
}
// resolve possible relative URI
target = CmsLinkManager.getAbsoluteUri(target, controller.getCurrentRequest().getElementUri());
// include direct edit "start" element (if enabled)
boolean directEditOpen = editable
&& CmsJspTagEditable.startDirectEdit(context, new CmsDirectEditParams(target, element));
// save old parameters from request
Map<String, String[]> oldParameterMap = CmsCollectionsGenericWrapper.map(req.getParameterMap());
try {
// each include will have it's unique map of parameters
Map<String, String[]> parameterMap = (paramMap == null)
? new HashMap<String, String[]>()
: new HashMap<String, String[]>(paramMap);
if (cacheable && (element != null)) {
// add template element selector for JSP templates (only required if cacheable)
addParameter(parameterMap, I_CmsResourceLoader.PARAMETER_ELEMENT, element, true);
}
// add parameters to set the correct element
controller.getCurrentRequest().addParameterMap(parameterMap);
// each include will have it's unique map of attributes
Map<String, Object> attributeMap = (attrMap == null)
? new HashMap<String, Object>()
: new HashMap<String, Object>(attrMap);
// add attributes to set the correct element
controller.getCurrentRequest().addAttributeMap(attributeMap);
if (cacheable) {
// use include with cache
includeActionWithCache(controller, context, target, parameterMap, attributeMap, req, res);
} else {
// no cache required
includeActionNoCache(controller, context, target, element, locale, req, res);
}
} finally {
// restore old parameter map (if required)
if (oldParameterMap != null) {
controller.getCurrentRequest().setParameterMap(oldParameterMap);
}
}
// include direct edit "end" element (if required)
if (directEditOpen) {
CmsJspTagEditable.endDirectEdit(context);
}
}
/**
* Includes the selected target without caching.<p>
*
* @param controller the current JSP controller
* @param context the current JSP page context
* @param target the target for the include
* @param element the element to select form the target
* @param locale the locale to select from the target
* @param req the current request
* @param res the current response
*
* @throws JspException in case something goes wrong
*/
private static void includeActionNoCache(
CmsFlexController controller,
PageContext context,
String target,
String element,
Locale locale,
ServletRequest req,
ServletResponse res) throws JspException {
try {
// include is not cachable
CmsFile file = controller.getCmsObject().readFile(target);
CmsObject cms = controller.getCmsObject();
if (locale == null) {
locale = cms.getRequestContext().getLocale();
}
// get the loader for the requested file
I_CmsResourceLoader loader = OpenCms.getResourceManager().getLoader(file);
String content;
if (loader instanceof I_CmsResourceStringDumpLoader) {
// loader can provide content as a String
I_CmsResourceStringDumpLoader strLoader = (I_CmsResourceStringDumpLoader)loader;
content = strLoader.dumpAsString(cms, file, element, locale, req, res);
} else {
if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) {
// http type is required for loader (no refactoring to avoid changes to interface)
CmsLoaderException e = new CmsLoaderException(Messages.get().container(
Messages.ERR_BAD_REQUEST_RESPONSE_0));
throw new JspException(e);
}
// get the bytes from the loader and convert them to a String
byte[] result = loader.dump(
cms,
file,
element,
locale,
(HttpServletRequest)req,
(HttpServletResponse)res);
// use the encoding from the property or the system default if not available
String encoding = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue(
OpenCms.getSystemInfo().getDefaultEncoding());
// If the included target issued a redirect null will be returned from loader
if (result == null) {
result = new byte[0];
}
content = new String(result, encoding);
}
// write the content String to the JSP output writer
context.getOut().print(content);
} catch (ServletException e) {
// store original Exception in controller in order to display it later
Throwable t = (e.getRootCause() != null) ? e.getRootCause() : e;
t = controller.setThrowable(t, target);
throw new JspException(t);
} catch (IOException e) {
// store original Exception in controller in order to display it later
Throwable t = controller.setThrowable(e, target);
throw new JspException(t);
} catch (CmsException e) {
// store original Exception in controller in order to display it later
Throwable t = controller.setThrowable(e, target);
throw new JspException(t);
}
}
/**
* Includes the selected target using the Flex cache.<p>
*
* @param controller the current JSP controller
* @param context the current JSP page context
* @param target the target for the include, might be <code>null</code>
* @param parameterMap a map of parameters for the include
* @param attributeMap a map of request attributes for the include
* @param req the current request
* @param res the current response
*
* @throws JspException in case something goes wrong
*/
private static void includeActionWithCache(
CmsFlexController controller,
PageContext context,
String target,
Map<String, String[]> parameterMap,
Map<String, Object> attributeMap,
ServletRequest req,
ServletResponse res) throws JspException {
try {
// add the target to the include list (the list will be initialized if it is currently empty)
controller.getCurrentResponse().addToIncludeList(target, parameterMap, attributeMap);
// now use the Flex dispatcher to include the target (this will also work for targets in the OpenCms VFS)
controller.getCurrentRequest().getRequestDispatcher(target).include(req, res);
// write out a FLEX_CACHE_DELIMITER char on the page, this is used as a parsing delimiter later
context.getOut().print(CmsFlexResponse.FLEX_CACHE_DELIMITER);
} catch (ServletException e) {
// store original Exception in controller in order to display it later
Throwable t = (e.getRootCause() != null) ? e.getRootCause() : e;
t = controller.setThrowable(t, target);
throw new JspException(t);
} catch (IOException e) {
// store original Exception in controller in order to display it later
Throwable t = controller.setThrowable(e, target);
throw new JspException(t);
}
}
/**
* This methods adds parameters to the current request.<p>
*
* Parameters added here will be treated like parameters from the
* HttpRequest on included pages.<p>
*
* Remember that the value for a parameter in a HttpRequest is a
* String array, not just a simple String. If a parameter added here does
* not already exist in the HttpRequest, it will be added. If a parameter
* exists, another value will be added to the array of values. If the
* value already exists for the parameter, nothing will be added, since a
* value can appear only once per parameter.<p>
*
* @param name the name to add
* @param value the value to add
* @see org.opencms.jsp.I_CmsJspTagParamParent#addParameter(String, String)
*/
public void addParameter(String name, String value) {
// No null values allowed in parameters
if ((name == null) || (value == null)) {
return;
}
// Check if internal map exists, create new one if not
if (m_parameterMap == null) {
m_parameterMap = new HashMap<String, String[]>();
}
addParameter(m_parameterMap, name, value, false);
}
/**
* @return <code>EVAL_PAGE</code>
*
* @see javax.servlet.jsp.tagext.Tag#doEndTag()
*
* @throws JspException by interface default
*/
@Override
public int doEndTag() throws JspException {
ServletRequest req = pageContext.getRequest();
ServletResponse res = pageContext.getResponse();
if (CmsFlexController.isCmsRequest(req)) {
// this will always be true if the page is called through OpenCms
CmsObject cms = CmsFlexController.getCmsObject(req);
String target = null;
// try to find out what to do
if (m_target != null) {
// option 1: target is set with "page" or "file" parameter
target = m_target + getSuffix();
} else if (m_property != null) {
// option 2: target is set with "property" parameter
try {
String prop = cms.readPropertyObject(cms.getRequestContext().getUri(), m_property, true).getValue();
if (prop != null) {
target = prop + getSuffix();
}
} catch (RuntimeException e) {
// target must be null
target = null;
} catch (Exception e) {
// target will be null
e = null;
}
} else if (m_attribute != null) {
// option 3: target is set in "attribute" parameter
try {
String attr = (String)req.getAttribute(m_attribute);
if (attr != null) {
target = attr + getSuffix();
}
} catch (RuntimeException e) {
// target must be null
target = null;
} catch (Exception e) {
// target will be null
e = null;
}
} else {
// option 4: target might be set in body
String body = null;
if (getBodyContent() != null) {
body = getBodyContent().getString();
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(body)) {
// target IS set in body
target = body + getSuffix();
}
// else target is not set at all, default will be used
}
}
// now perform the include action
includeTagAction(
pageContext,
target,
m_element,
null,
m_editable,
m_cacheable,
m_parameterMap,
CmsRequestUtil.getAtrributeMap(req),
req,
res);
release();
}
return EVAL_PAGE;
}
/**
* Returns <code>{@link #EVAL_BODY_BUFFERED}</code>.<p>
*
* @return <code>{@link #EVAL_BODY_BUFFERED}</code>
*
* @see javax.servlet.jsp.tagext.Tag#doStartTag()
*/
@Override
public int doStartTag() {
return EVAL_BODY_BUFFERED;
}
/**
* Returns the attribute.<p>
*
* @return the attribute
*/
public String getAttribute() {
return m_attribute != null ? m_attribute : "";
}
/**
* Returns the cacheable flag.<p>
*
* @return the cacheable flag
*/
public String getCacheable() {
return String.valueOf(m_cacheable);
}
/**
* Returns the editable flag.<p>
*
* @return the editable flag
*/
public String getEditable() {
return String.valueOf(m_editable);
}
/**
* Returns the element.<p>
*
* @return the element
*/
public String getElement() {
return m_element;
}
/**
* Returns the value of <code>{@link #getPage()}</code>.<p>
*
* @return the value of <code>{@link #getPage()}</code>
* @see #getPage()
*/
public String getFile() {
return getPage();
}
/**
* Returns the include page target.<p>
*
* @return the include page target
*/
public String getPage() {
return m_target != null ? m_target : "";
}
/**
* Returns the property.<p>
*
* @return the property
*/
public String getProperty() {
return m_property != null ? m_property : "";
}
/**
* Returns the suffix.<p>
*
* @return the suffix
*/
public String getSuffix() {
return m_suffix != null ? m_suffix : "";
}
/**
* @see javax.servlet.jsp.tagext.Tag#release()
*/
@Override
public void release() {
super.release();
m_target = null;
m_suffix = null;
m_property = null;
m_element = null;
m_parameterMap = null;
m_editable = false;
m_cacheable = true;
}
/**
* Sets the attribute.<p>
*
* @param attribute the attribute to set
*/
public void setAttribute(String attribute) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(attribute)) {
m_attribute = attribute;
}
}
/**
* Sets the cacheable flag.<p>
*
* Cachable is <code>true</code> by default.<p>
*
* @param cacheable the flag to set
*/
public void setCacheable(String cacheable) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(cacheable)) {
m_cacheable = Boolean.valueOf(cacheable).booleanValue();
}
}
/**
* Sets the editable flag.<p>
*
* Editable is <code>false</code> by default.<p>
*
* @param editable the flag to set
*/
public void setEditable(String editable) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(editable)) {
m_editable = Boolean.valueOf(editable).booleanValue();
}
}
/**
* Sets the element.<p>
*
* @param element the element to set
*/
public void setElement(String element) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(element)) {
m_element = element;
}
}
/**
* Sets the file, same as using <code>setPage()</code>.<p>
*
* @param file the file to set
* @see #setPage(String)
*/
public void setFile(String file) {
setPage(file);
}
/**
* Sets the include page target.<p>
*
* @param target the target to set
*/
public void setPage(String target) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(target)) {
m_target = target;
}
}
/**
* Sets the property.<p>
*
* @param property the property to set
*/
public void setProperty(String property) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(property)) {
m_property = property;
}
}
/**
* Sets the suffix.<p>
*
* @param suffix the suffix to set
*/
public void setSuffix(String suffix) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(suffix)) {
m_suffix = suffix.toLowerCase();
}
}
}