/*
* 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, 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.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.history.CmsHistoryResourceHandler;
import org.opencms.file.types.CmsResourceTypeXmlContent;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.flex.CmsFlexController;
import org.opencms.jsp.util.CmsJspStandardContextBean;
import org.opencms.loader.CmsLoaderException;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsStringUtil;
import org.opencms.xml.CmsXmlContentDefinition;
import org.opencms.xml.containerpage.CmsContainerElementBean;
import org.opencms.xml.containerpage.CmsContainerPageBean;
import org.opencms.xml.containerpage.CmsXmlContainerPage;
import org.opencms.xml.containerpage.CmsXmlContainerPageFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.commons.logging.Log;
/**
* This tag includes required CSS or JavaScript resources that are to be places in the HTML head.<p>
*
* Required resources can be configured in the resource type schema.
* Set attribute type to 'css' to include css resources or to 'javascript' to include JavaScript resources.<p>
*
* @since 8.0
*/
public class CmsJspTagHeadIncludes extends BodyTagSupport implements I_CmsJspTagParamParent {
/** The include type CSS. */
public static final String TYPE_CSS = "css";
/** The include type java-script. */
public static final String TYPE_JAVASCRIPT = "javascript";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsJspTagHeadIncludes.class);
/** Serial version UID required for safe serialisation. */
private static final long serialVersionUID = 5496349529835666345L;
/** The value of the closetags attribute. */
private String m_closeTags;
/** The default include resources separated by '|'. */
private String m_defaults;
/** Map to save parameters to the include in. */
private Map<String, String[]> m_parameterMap;
/** The include type. */
private String m_type;
/**
* 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);
}
}
/**
* Returns the configured CSS head include resources.<p>
*
* @param cms the current cms context
* @param resource the resource
*
* @return the configured CSS head include resources
*/
public static Set<String> getCSSHeadIncludes(CmsObject cms, CmsResource resource) {
if (CmsResourceTypeXmlContent.isXmlContent(resource)) {
try {
CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.getContentDefinitionForResource(
cms,
resource);
return contentDefinition.getContentHandler().getCSSHeadIncludes(cms, resource);
} catch (CmsException e) {
LOG.warn(e.getLocalizedMessage(), e);
// NOOP, use the empty set
}
}
return Collections.emptySet();
}
/**
* Returns the configured JavaScript head include resources.<p>
*
* @param cms the current cms context
* @param resource the resource
*
* @return the configured JavaScript head include resources
*
* @throws CmsLoaderException if something goes wrong reading the resource type
*/
public static Set<String> getJSHeadIncludes(CmsObject cms, CmsResource resource) throws CmsLoaderException {
I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
if (resType instanceof CmsResourceTypeXmlContent) {
try {
CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.getContentDefinitionForResource(
cms,
resource);
return contentDefinition.getContentHandler().getJSHeadIncludes(cms, resource);
} catch (CmsException e) {
LOG.warn(e.getLocalizedMessage(), e);
// NOOP, use the empty set
}
}
return Collections.emptySet();
}
/**
* @see org.opencms.jsp.I_CmsJspTagParamParent#addParameter(java.lang.String, java.lang.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();
CmsFlexController controller = CmsFlexController.getController(req);
CmsObject cms = controller.getCmsObject();
try {
if (TYPE_CSS.equals(m_type)) {
tagCssAction(cms, req);
}
if (TYPE_JAVASCRIPT.equals(m_type)) {
tagJSAction(cms, req);
}
} catch (Exception e) {
throw new JspException(e);
} finally {
m_parameterMap = null;
}
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 default include resources separated by '|'.<p>
*
* @return the default include resources
*/
public String getDefaults() {
return m_defaults;
}
/**
* Returns the type.<p>
*
* @return the type
*/
public String getType() {
return m_type;
}
/**
* Sets the value of the closetags attribute.<p>
*
* @param closeTags the value of the closetags attribute
*/
public void setClosetags(String closeTags) {
m_closeTags = closeTags;
}
/**
* Sets the default include resources separated by '|'.<p>
*
* @param defaults the default include resources to set
*/
public void setDefaults(String defaults) {
m_defaults = defaults;
}
/**
* Sets the type.<p>
*
* @param type the type to set
*/
public void setType(String type) {
m_type = type;
}
/**
* Returns true if the headincludes tag should be closed.<p>
*
* @return true if the headincludes tag should be closed
*/
public boolean shouldCloseTags() {
if (m_closeTags == null) {
return true;
}
return Boolean.parseBoolean(m_closeTags);
}
/**
* Action to include the CSS resources.<p>
*
* @param cms the current cms context
* @param req the current request
*
* @throws CmsException if something goes wrong reading the resources
* @throws IOException if something goes wrong writing to the response out
*/
public void tagCssAction(CmsObject cms, ServletRequest req) throws CmsException, IOException {
CmsJspStandardContextBean standardContext = getStandardContext(cms, req);
CmsContainerPageBean containerPage = standardContext.getPage();
Set<String> cssIncludes = new LinkedHashSet<String>();
// add defaults
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_defaults)) {
String[] defaults = m_defaults.split("\\|");
for (int i = 0; i < defaults.length; i++) {
cssIncludes.add(defaults[i]);
}
}
if ((containerPage != null) && (containerPage.getElements() != null)) {
for (CmsContainerElementBean element : containerPage.getElements()) {
try {
element.initResource(cms);
cssIncludes.addAll(getCSSHeadIncludes(cms, element.getResource()));
} catch (CmsException e) {
LOG.error(
Messages.get().getBundle().key(Messages.ERR_READING_REQUIRED_RESOURCE_1, element.getSitePath()),
e);
}
}
}
if (standardContext.getDetailContentId() != null) {
try {
CmsResource detailContent = cms.readResource(standardContext.getDetailContentId());
cssIncludes.addAll(getCSSHeadIncludes(cms, detailContent));
} catch (CmsException e) {
LOG.error(
Messages.get().getBundle().key(
Messages.ERR_READING_REQUIRED_RESOURCE_1,
standardContext.getDetailContentId()),
e);
}
}
for (String cssUri : cssIncludes) {
pageContext.getOut().print(
"<link href=\""
+ CmsJspTagLink.linkTagAction(cssUri, req)
+ generateReqParams()
+ "\" rel=\"stylesheet\" type=\"text/css\">");
if (shouldCloseTags()) {
pageContext.getOut().print("</link>");
}
}
}
/**
* Action to include the java-script resources.<p>
*
* @param cms the current cms context
* @param req the current request
*
* @throws CmsException if something goes wrong reading the resources
* @throws IOException if something goes wrong writing to the response out
*/
public void tagJSAction(CmsObject cms, ServletRequest req) throws CmsException, IOException {
CmsJspStandardContextBean standardContext = getStandardContext(cms, req);
CmsContainerPageBean containerPage = standardContext.getPage();
Set<String> jsIncludes = new LinkedHashSet<String>();
// add defaults
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_defaults)) {
String[] defaults = m_defaults.split("\\|");
for (int i = 0; i < defaults.length; i++) {
jsIncludes.add(defaults[i]);
}
}
if ((containerPage != null) && (containerPage.getElements() != null)) {
for (CmsContainerElementBean element : containerPage.getElements()) {
try {
element.initResource(cms);
jsIncludes.addAll(getJSHeadIncludes(cms, element.getResource()));
} catch (CmsException e) {
LOG.error(
Messages.get().getBundle().key(Messages.ERR_READING_REQUIRED_RESOURCE_1, element.getSitePath()),
e);
}
}
}
if (standardContext.getDetailContentId() != null) {
try {
CmsResource detailContent = cms.readResource(standardContext.getDetailContentId());
jsIncludes.addAll(getJSHeadIncludes(cms, detailContent));
} catch (CmsException e) {
LOG.error(
Messages.get().getBundle().key(
Messages.ERR_READING_REQUIRED_RESOURCE_1,
standardContext.getDetailContentId()),
e);
}
}
for (String jsUri : jsIncludes) {
pageContext.getOut().print(
"<script type=\"text/javascript\" src=\""
+ CmsJspTagLink.linkTagAction(jsUri, req)
+ generateReqParams()
+ "\"></script>");
}
}
/**
* Generates the request parameter string.<p>
*
* @return the request parameter string
*
* @throws UnsupportedEncodingException if something goes wrong encoding the request parameters
*/
private String generateReqParams() throws UnsupportedEncodingException {
String params = "";
if ((m_parameterMap != null) && !m_parameterMap.isEmpty()) {
for (Entry<String, String[]> paramEntry : m_parameterMap.entrySet()) {
if (paramEntry.getValue() != null) {
for (int i = 0; i < paramEntry.getValue().length; i++) {
params += "&"
+ paramEntry.getKey()
+ "="
+ URLEncoder.encode(paramEntry.getValue()[i], "UTF-8");
}
}
}
params = "?" + params.substring(1);
}
return params;
}
/**
* Returns the standard context bean.<p>
*
* @param cms the current cms context
* @param req the current request
*
* @return the standard context bean
*
* @throws CmsException if something goes wrong
*/
private CmsJspStandardContextBean getStandardContext(CmsObject cms, ServletRequest req) throws CmsException {
String requestUri = cms.getRequestContext().getUri();
CmsJspStandardContextBean standardContext = CmsJspStandardContextBean.getInstance(req);
CmsContainerPageBean containerPage = standardContext.getPage();
if (containerPage == null) {
// get the container page itself, checking the history first
CmsResource pageResource = (CmsResource)CmsHistoryResourceHandler.getHistoryResource(req);
if (pageResource == null) {
pageResource = cms.readResource(requestUri);
}
CmsXmlContainerPage xmlContainerPage = CmsXmlContainerPageFactory.unmarshal(cms, pageResource, req);
containerPage = xmlContainerPage.getContainerPage(cms, cms.getRequestContext().getLocale());
standardContext.setPage(containerPage);
}
return standardContext;
}
}