/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (C) Alkacon Software (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.ade.configuration;
import org.opencms.ade.detailpage.CmsDetailPageInfo;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.CmsRuntimeException;
import org.opencms.main.OpenCms;
import org.opencms.module.CmsModule;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.xml.containerpage.CmsFormatterBean;
import org.opencms.xml.containerpage.CmsFormatterConfiguration;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentFactory;
import org.opencms.xml.content.CmsXmlContentProperty;
import org.opencms.xml.content.CmsXmlContentRootLocation;
import org.opencms.xml.content.I_CmsXmlContentLocation;
import org.opencms.xml.content.I_CmsXmlContentValueLocation;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apache.commons.logging.Log;
import com.google.common.collect.Lists;
/**
* A class to parse ADE sitemap or module configuration files and create configuration objects from them.<p>
*/
public class CmsConfigurationReader {
/** The default locale for configuration objects. */
public static final Locale DEFAULT_LOCALE = CmsLocaleManager.getLocale("en");
/** The folder name node name. */
public static final String N_FOLDER_NAME = "Name";
/** The folder path node name. */
public static final String N_FOLDER_PATH = "Path";
/** The resource type node name. */
public static final String N_RESOURCE_TYPE = "ResourceType";
/** The model page node name. */
public static final String N_MODEL_PAGE = "ModelPage";
/** The property node name. */
public static final String N_PROPERTY = "Property";
/** The detail page node name. */
public static final String N_DETAIL_PAGE = "DetailPage";
/** The function reference node name. */
public static final String N_FUNCTION_REF = "FunctionRef";
/** The discard types node name. */
public static final String N_DISCARD_TYPES = "DiscardTypes";
/** The discard properties node name. */
public static final String N_DISCARD_PROPERTIES = "DiscardProperties";
/** The discard model pages node name. */
public static final String N_DISCARD_MODEL_PAGES = "DiscardModelPages";
/** The create content locally node name. */
public static final String N_CREATE_CONTENTS_LOCALLY = "CreateContentsLocally";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsConfigurationReader.class);
/** The CMS context used for reading the configuration data. */
private CmsObject m_cms;
/** The parsed detail page configuration elements. */
private List<CmsDetailPageInfo> m_detailPageConfigs = new ArrayList<CmsDetailPageInfo>();
/** The list of configured function references. */
private List<CmsFunctionReference> m_functionReferences = new ArrayList<CmsFunctionReference>();
/** The parsed model page configuration elements. */
private List<CmsModelPageConfig> m_modelPageConfigs = new ArrayList<CmsModelPageConfig>();
/** The parsed property configuration elements. */
private List<CmsPropertyConfig> m_propertyConfigs = new ArrayList<CmsPropertyConfig>();
/** The resource type configuration objects. */
private List<CmsResourceTypeConfig> m_resourceTypeConfigs = new ArrayList<CmsResourceTypeConfig>();
/**
* Creates a new configuration reader.<p>
*
* @param cms the CMS context which should be used to read the configuration data.<p>
*/
public CmsConfigurationReader(CmsObject cms) {
m_cms = cms;
}
/**
* Returns the list of function references.<p>
*
* @return the list of function references
*/
public List<CmsFunctionReference> getFunctionReferences() {
return new ArrayList<CmsFunctionReference>(m_functionReferences);
}
/**
* Parses a configuration XML content and creates a configuration object from it.<p>
*
* @param basePath the base path
* @param content the XML content
*
* @return the created configuration object with the data from the XML content
* @throws CmsException if something goes wrong
*/
public CmsADEConfigData parseConfiguration(String basePath, CmsXmlContent content) throws CmsException {
m_detailPageConfigs = Lists.newArrayList();
m_functionReferences = Lists.newArrayList();
m_modelPageConfigs = Lists.newArrayList();
m_propertyConfigs = Lists.newArrayList();
m_resourceTypeConfigs = Lists.newArrayList();
if (!content.hasLocale(DEFAULT_LOCALE)) {
return CmsADEConfigData.emptyConfiguration(basePath);
}
CmsXmlContentRootLocation root = new CmsXmlContentRootLocation(content, DEFAULT_LOCALE);
for (I_CmsXmlContentValueLocation node : root.getSubValues(N_RESOURCE_TYPE)) {
try {
parseResourceTypeConfig(basePath, node);
} catch (CmsException e) {
LOG.warn(e.getLocalizedMessage(), e);
}
}
for (I_CmsXmlContentValueLocation node : root.getSubValues(N_MODEL_PAGE)) {
try {
parseModelPage(node);
} catch (CmsException e) {
LOG.warn(e.getLocalizedMessage(), e);
}
}
for (I_CmsXmlContentLocation node : root.getSubValues(N_PROPERTY)) {
parseProperty(node);
}
for (I_CmsXmlContentLocation node : root.getSubValues(N_DETAIL_PAGE)) {
try {
parseDetailPage(node);
} catch (CmsException e) {
LOG.warn(e.getLocalizedMessage(), e);
}
}
for (I_CmsXmlContentLocation node : root.getSubValues(N_FUNCTION_REF)) {
parseFunctionReference(node);
}
boolean discardInheritedTypes = getBoolean(root, N_DISCARD_TYPES);
boolean discardInheritedProperties = getBoolean(root, N_DISCARD_PROPERTIES);
boolean discardInheritedModelPages = getBoolean(root, N_DISCARD_MODEL_PAGES);
boolean createContentsLocally = getBoolean(root, N_CREATE_CONTENTS_LOCALLY);
CmsADEConfigData result = new CmsADEConfigData(
basePath,
m_resourceTypeConfigs,
discardInheritedTypes,
m_propertyConfigs,
discardInheritedProperties,
m_detailPageConfigs,
m_modelPageConfigs,
m_functionReferences,
discardInheritedModelPages,
createContentsLocally);
result.setResource(content.getFile());
if (OpenCms.getResourceManager().getResourceType(content.getFile().getTypeId()).getTypeName().equals(
CmsADEManager.MODULE_CONFIG_TYPE)) {
result.setIsModuleConfig(true);
}
return result;
}
/**
* Parses a folder which may either be given as a path or as a folder name.<p>
*
* @param basePath the base path for the configuration
* @param location the XML content node from which to parse the folder
* @return the folder bean
*
* @throws CmsException if something goes wrong
*/
public CmsFolderOrName parseFolderOrName(String basePath, I_CmsXmlContentLocation location) throws CmsException {
if (location == null) {
return null;
}
I_CmsXmlContentValueLocation nameLoc = location.getSubValue(N_FOLDER_NAME);
I_CmsXmlContentValueLocation pathLoc = location.getSubValue(N_FOLDER_PATH);
if (nameLoc != null) {
String name = nameLoc.asString(m_cms);
return new CmsFolderOrName(basePath == null ? null : CmsStringUtil.joinPaths(basePath, ".content"), name);
} else if (pathLoc != null) {
String path = pathLoc.asString(m_cms);
CmsResource folder = m_cms.readResource(path);
return new CmsFolderOrName(folder);
} else {
return null;
}
}
/**
* Parses a formatter bean.<p>
*
* @param typeName the type name for which the formatter is being parsed
* @param node the node from which to parse the formatter data
*
* @return the formatter bean from the XML
*/
public CmsFormatterBean parseFormatter(String typeName, I_CmsXmlContentLocation node) {
String type = getString(node.getSubValue("Type"));
String minWidth = getString(node.getSubValue("MinWidth"));
String maxWidth = getString(node.getSubValue("MaxWidth"));
boolean preview = false;
I_CmsXmlContentValueLocation previewLoc = node.getSubValue("IsPreview");
preview = (previewLoc != null) && Boolean.parseBoolean(previewLoc.asString(m_cms));
String jsp = m_cms.getRequestContext().addSiteRoot(getString(node.getSubValue("Jsp")));
boolean searchContent = true;
CmsFormatterBean formatterBean = new CmsFormatterBean(type, jsp, minWidth, maxWidth, "" + preview, ""
+ searchContent, null);
return formatterBean;
}
/**
* Parses model page data from the XML content.<p>
*
* @param node the XML content node
* @throws CmsException if something goes wrong
*/
public void parseModelPage(I_CmsXmlContentLocation node) throws CmsException {
String page = getString(node.getSubValue("Page"));
I_CmsXmlContentValueLocation disabledLoc = node.getSubValue("Disabled");
boolean disabled = (disabledLoc != null) && Boolean.parseBoolean(disabledLoc.asString(m_cms));
I_CmsXmlContentValueLocation defaultLoc = node.getSubValue("IsDefault");
boolean isDefault = (defaultLoc != null) && Boolean.parseBoolean(defaultLoc.asString(m_cms));
CmsModelPageConfig modelPage = new CmsModelPageConfig(m_cms.readResource(page), isDefault, disabled);
m_modelPageConfigs.add(modelPage);
}
/**
* Parses a resource type configuration element from the XML content.<p>
*
* @param basePath the base path of the configuration
* @param node the XML configuration node
* @throws CmsException if something goes wrong
*/
public void parseResourceTypeConfig(String basePath, I_CmsXmlContentLocation node) throws CmsException {
I_CmsXmlContentValueLocation typeNameLoc = node.getSubValue("TypeName");
String typeName = typeNameLoc.asString(m_cms);
I_CmsXmlContentValueLocation disabledLoc = node.getSubValue("Disabled");
CmsFolderOrName folderOrName = parseFolderOrName(basePath, node.getSubValue("Folder"));
boolean disabled = (disabledLoc != null) && Boolean.parseBoolean(disabledLoc.asString(m_cms));
I_CmsXmlContentValueLocation namePatternLoc = node.getSubValue("NamePattern");
String namePattern = null;
if (namePatternLoc != null) {
namePattern = namePatternLoc.asString(m_cms);
}
boolean detailPagesDisabled = false;
I_CmsXmlContentValueLocation detailDisabledLoc = node.getSubValue("DetailPagesDisabled");
if (detailDisabledLoc != null) {
String detailPagesDisabledStr = detailDisabledLoc.asString(m_cms);
detailPagesDisabled = Boolean.parseBoolean(detailPagesDisabledStr);
}
int order = I_CmsConfigurationObject.DEFAULT_ORDER;
I_CmsXmlContentValueLocation orderLoc = node.getSubValue("Order");
if (orderLoc != null) {
try {
String orderStr = orderLoc.asString(m_cms);
order = Integer.parseInt(orderStr);
} catch (NumberFormatException e) {
// noop
}
}
List<CmsFormatterBean> formatters = new ArrayList<CmsFormatterBean>();
for (I_CmsXmlContentValueLocation formatterLoc : node.getSubValues("Formatter")) {
CmsFormatterBean formatter = parseFormatter(typeName, formatterLoc);
formatters.add(formatter);
}
CmsFormatterConfiguration formatterConfig = CmsFormatterConfiguration.create(m_cms, formatters);
CmsResourceTypeConfig typeConfig = new CmsResourceTypeConfig(
typeName,
disabled,
folderOrName,
namePattern,
formatterConfig,
detailPagesDisabled,
order);
m_resourceTypeConfigs.add(typeConfig);
}
/**
* Parses the sitemap configuration given the configuration file and base path.<p>
*
* @param basePath the base path
* @param configRes the configuration file resource
* @return the parsed configuration data
* @throws CmsException if something goes wrong
*/
public CmsADEConfigData parseSitemapConfiguration(String basePath, CmsResource configRes) throws CmsException {
LOG.info("Parsing configuration " + configRes.getRootPath());
CmsFile configFile = m_cms.readFile(configRes);
CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, configFile);
return parseConfiguration(basePath, content);
}
/**
* Reads the configurations of all modules and combines them into a single configuration object.<p>
*
* @return the combined configuration object
*/
public CmsADEConfigData readModuleConfigurations() {
List<CmsADEConfigData> configurations = new ArrayList<CmsADEConfigData>();
List<CmsModule> modules = OpenCms.getModuleManager().getAllInstalledModules();
for (CmsModule module : modules) {
String configPath = module.getConfigurationPath();
try {
CmsResource configFile = m_cms.readResource(configPath);
LOG.info("Found module configuration " + configPath + " for module " + module.getName());
CmsADEConfigData config = parseSitemapConfiguration(null, configFile);
configurations.add(config);
} catch (CmsVfsResourceNotFoundException e) {
// ignore
} catch (CmsException e) {
// errors while parsing configuration
LOG.error(e.getLocalizedMessage(), e);
} catch (CmsRuntimeException e) {
// may happen during import of org.opencms.ade.configuration module
LOG.warn(e.getLocalizedMessage(), e);
}
}
return mergeConfigurations(configurations);
}
/**
* Helper method to read a boolean value from the XML.<p>
*
* If the element is not found in the XML, false is returned.<p>
*
* @param parent the parent node
* @param name the name of the XML content value
* @return the boolean value
*/
protected boolean getBoolean(I_CmsXmlContentLocation parent, String name) {
I_CmsXmlContentValueLocation location = parent.getSubValue(name);
if (location == null) {
return false;
}
String value = location.getValue().getStringValue(m_cms);
return Boolean.parseBoolean(value);
}
/**
* Gets the string value of an XML content location.<p>
*
* @param location an XML content location
*
* @return the string value of that XML content location
*/
protected String getString(I_CmsXmlContentValueLocation location) {
if (location == null) {
return null;
}
return location.asString(m_cms);
}
/**
* Merges a list of multiple configuration objects into a single configuration object.<p>
*
* @param configurations the list of configuration objects.<p>
*
* @return the merged configuration object
*/
protected CmsADEConfigData mergeConfigurations(List<CmsADEConfigData> configurations) {
if (configurations.isEmpty()) {
return new CmsADEConfigData();
}
for (int i = 0; i < (configurations.size() - 1); i++) {
configurations.get(i + 1).mergeParent(configurations.get(i));
}
CmsADEConfigData result = configurations.get(configurations.size() - 1);
result.processModuleOrdering();
return result;
}
/**
* Parses the detail pages from an XML content node.<p>
*
* @param node the XML content node
*
* @throws CmsException if something goes wrong
*/
protected void parseDetailPage(I_CmsXmlContentLocation node) throws CmsException {
I_CmsXmlContentValueLocation pageLoc = node.getSubValue("Page");
String page = pageLoc.asString(m_cms);
CmsResource detailPageRes = m_cms.readResource(page);
CmsUUID id = detailPageRes.getStructureId();
String typeName = getString(node.getSubValue("Type"));
CmsDetailPageInfo detailPage = new CmsDetailPageInfo(id, page, typeName);
m_detailPageConfigs.add(detailPage);
}
/**
* Parses a function reference node.<p>
*
* @param node the function reference node
*/
protected void parseFunctionReference(I_CmsXmlContentLocation node) {
String name = node.getSubValue("Name").asString(m_cms);
CmsUUID functionId = node.getSubValue("Function").asId(m_cms);
I_CmsXmlContentValueLocation orderNode = node.getSubValue("Order");
int order = I_CmsConfigurationObject.DEFAULT_ORDER;
if (orderNode != null) {
String orderStr = orderNode.asString(m_cms);
try {
order = Integer.parseInt(orderStr);
} catch (NumberFormatException e) {
// noop
}
}
m_functionReferences.add(new CmsFunctionReference(name, functionId, order));
}
/**
* Parses a single field definition from a content value.<p>
*
* @param field the content value to parse the field from
*/
private void parseProperty(I_CmsXmlContentLocation field) {
String name = getString(field.getSubValue("PropertyName"));
String widget = getString(field.getSubValue("Widget"));
String widgetConfig = getString(field.getSubValue("WidgetConfig"));
String ruleRegex = getString(field.getSubValue("RuleRegex"));
String ruleType = getString(field.getSubValue("RuleType"));
String default1 = getString(field.getSubValue("Default"));
String error = getString(field.getSubValue("Error"));
String niceName = getString(field.getSubValue("DisplayName"));
String description = getString(field.getSubValue("Description"));
String preferFolder = getString(field.getSubValue("PreferFolder"));
String disabledStr = getString(field.getSubValue("Disabled"));
boolean disabled = ((disabledStr != null) && Boolean.parseBoolean(disabledStr));
String orderStr = getString(field.getSubValue("Order"));
int order = I_CmsConfigurationObject.DEFAULT_ORDER;
try {
order = Integer.parseInt(orderStr);
} catch (NumberFormatException e) {
// noop
}
CmsXmlContentProperty prop = new CmsXmlContentProperty(
name,
"string",
widget,
widgetConfig,
ruleRegex,
ruleType,
default1,
niceName,
description,
error,
preferFolder);
// since these are real properties, using type vfslist makes no sense, so we always use the "string" type
CmsPropertyConfig propConfig = new CmsPropertyConfig(prop, disabled, order);
m_propertyConfigs.add(propConfig);
}
}