/*
* 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.xml;
import org.opencms.file.CmsObject;
import org.opencms.i18n.CmsEncoder;
import org.opencms.main.CmsLog;
import org.opencms.main.CmsRuntimeException;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsRole;
import org.opencms.security.CmsRoleViolationException;
import org.opencms.util.CmsStringUtil;
import org.opencms.widgets.I_CmsWidget;
import org.opencms.xml.content.I_CmsXmlContentHandler;
import org.opencms.xml.types.CmsXmlNestedContentDefinition;
import org.opencms.xml.types.I_CmsXmlSchemaType;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.FastHashMap;
import org.apache.commons.logging.Log;
import org.dom4j.Document;
import org.dom4j.Element;
/**
* Manager class for registered OpenCms XML content types and content collectors.<p>
*
* @since 6.0.0
*/
public class CmsXmlContentTypeManager {
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsXmlContentTypeManager.class);
/** Stores the initialized XML content handlers. */
private Map<String, I_CmsXmlContentHandler> m_contentHandlers;
/** Stores the registered content widgets. */
private Map<String, I_CmsWidget> m_defaultWidgets;
/** Stores the registered content types. */
private Map<String, I_CmsXmlSchemaType> m_registeredTypes;
/** Stores the registered content widgets by class name. */
private Map<String, I_CmsWidget> m_registeredWidgets;
/** The alias names for the widgets. */
private Map<String, String> m_widgetAliases;
/** The default configurations for the widgets. */
private Map<String, String> m_widgetDefaultConfigurations;
/**
* Creates a new content type manager.<p>
*/
@SuppressWarnings("unchecked")
public CmsXmlContentTypeManager() {
// use the fast hash map implementation since there will be far more read then write accesses
m_registeredTypes = new HashMap<String, I_CmsXmlSchemaType>();
m_defaultWidgets = new HashMap<String, I_CmsWidget>();
m_registeredWidgets = new HashMap<String, I_CmsWidget>();
m_widgetAliases = new HashMap<String, String>();
m_widgetDefaultConfigurations = new HashMap<String, String>();
FastHashMap fastMap = new FastHashMap();
fastMap.setFast(true);
m_contentHandlers = fastMap;
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_START_CONTENT_CONFIG_0));
}
}
/**
* Returns a statically initialized instance of an XML content type manager (for test cases only).<p>
*
* @return a statically initialized instance of an XML content type manager
*/
public static CmsXmlContentTypeManager createTypeManagerForTestCases() {
CmsXmlContentTypeManager typeManager = new CmsXmlContentTypeManager();
typeManager.addWidget("org.opencms.widgets.CmsCalendarWidget", null, null);
typeManager.addWidget("org.opencms.widgets.CmsHtmlWidget", null, null);
typeManager.addWidget("org.opencms.widgets.CmsInputWidget", null, null);
typeManager.addSchemaType("org.opencms.xml.types.CmsXmlDateTimeValue", "org.opencms.widgets.CmsCalendarWidget");
typeManager.addSchemaType("org.opencms.xml.types.CmsXmlHtmlValue", "org.opencms.widgets.CmsHtmlWidget");
typeManager.addSchemaType("org.opencms.xml.types.CmsXmlLocaleValue", "org.opencms.widgets.CmsInputWidget");
typeManager.addSchemaType("org.opencms.xml.types.CmsXmlStringValue", "org.opencms.widgets.CmsInputWidget");
typeManager.addSchemaType(
"org.opencms.xml.types.CmsXmlPlainTextStringValue",
"org.opencms.widgets.CmsInputWidget");
try {
typeManager.initialize(null);
} catch (CmsRoleViolationException e) {
// this should never happen
throw new CmsRuntimeException(Messages.get().container(Messages.ERR_INIT_TYPE_MANAGER_0));
}
return typeManager;
}
/**
* Adds a XML content schema type class to the registered XML content types.<p>
*
* @param clazz the XML content schema type class to add
*
* @return the created instance of the XML content schema type
*
* @throws CmsXmlException in case the class is not an instance of {@link I_CmsXmlSchemaType}
*/
public I_CmsXmlSchemaType addContentType(Class<?> clazz) throws CmsXmlException {
I_CmsXmlSchemaType type;
try {
type = (I_CmsXmlSchemaType)clazz.newInstance();
} catch (InstantiationException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_XCC_TYPE_REGISTERED_0));
} catch (IllegalAccessException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_XCC_TYPE_REGISTERED_0));
} catch (ClassCastException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_XCC_TYPE_REGISTERED_0));
}
m_registeredTypes.put(type.getTypeName(), type);
return type;
}
/**
* Adds a new XML content type schema class and XML widget to the manager by class names.<p>
*
* @param className class name of the XML content schema type class to add
* @param defaultWidget class name of the default XML widget class for the added XML content type
*/
public void addSchemaType(String className, String defaultWidget) {
Class<?> classClazz;
// initialize class for schema type
try {
classClazz = Class.forName(className);
} catch (ClassNotFoundException e) {
LOG.error(
Messages.get().getBundle().key(Messages.LOG_XML_CONTENT_SCHEMA_TYPE_CLASS_NOT_FOUND_1, className),
e);
return;
}
// create the schema type and add it to the internal list
I_CmsXmlSchemaType type;
try {
type = addContentType(classClazz);
} catch (Exception e) {
LOG.error(
Messages.get().getBundle().key(
Messages.LOG_INIT_XML_CONTENT_SCHEMA_TYPE_CLASS_ERROR_1,
classClazz.getName()),
e);
return;
}
// add the editor widget for the schema type
I_CmsWidget widget = getWidget(defaultWidget);
if (widget == null) {
LOG.error(Messages.get().getBundle().key(
Messages.LOG_INIT_DEFAULT_WIDGET_FOR_CONTENT_TYPE_2,
defaultWidget,
type.getTypeName()));
return;
}
// store the registered default widget
m_defaultWidgets.put(type.getTypeName(), widget);
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(
Messages.INIT_ADD_ST_USING_WIDGET_2,
type.getTypeName(),
widget.getClass().getName()));
}
}
/**
* Adds a XML content editor widget class, making this widget available for the XML content editor.<p>
*
* @param className the widget class to add
* @param aliasName the (optional) alias name to use for the widget class
* @param defaultConfiguration the default configuration of the widget
*/
public void addWidget(String className, String aliasName, String defaultConfiguration) {
Class<?> widgetClazz;
I_CmsWidget widget;
try {
widgetClazz = Class.forName(className);
widget = (I_CmsWidget)widgetClazz.newInstance();
} catch (Exception e) {
LOG.error(Messages.get().getBundle().key(Messages.LOG_XML_WIDGET_INITIALIZING_ERROR_1, className), e);
return;
}
m_registeredWidgets.put(widgetClazz.getName(), widget);
if (aliasName != null) {
m_widgetAliases.put(aliasName, widgetClazz.getName());
}
if (CmsStringUtil.isNotEmpty(defaultConfiguration)) {
// put the default configuration to the lookup Map
m_widgetDefaultConfigurations.put(className, defaultConfiguration);
}
if (CmsLog.INIT.isInfoEnabled()) {
if (aliasName == null) {
if (CmsStringUtil.isEmpty(defaultConfiguration)) {
CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ADD_WIDGET_1, widgetClazz.getName()));
} else {
CmsLog.INIT.info(Messages.get().getBundle().key(
Messages.INIT_ADD_WIDGET_CONFIG_2,
widgetClazz.getName(),
defaultConfiguration));
}
} else {
if (CmsStringUtil.isEmpty(defaultConfiguration)) {
CmsLog.INIT.info(Messages.get().getBundle().key(
Messages.INIT_ADD_WIDGET_ALIAS_2,
widgetClazz.getName(),
aliasName));
} else {
CmsLog.INIT.info(Messages.get().getBundle().key(
Messages.INIT_ADD_WIDGET_ALIAS_CONFIG_3,
widgetClazz.getName(),
aliasName,
defaultConfiguration));
}
}
}
}
/**
* Returns the XML content handler instance class for the specified class name.<p>
*
* Only one instance of an XML content handler class per content definition name will be generated,
* and that instance will be cached and re-used for all operations.<p>
*
* @param className the name of the XML content handler to return
* @param schemaLocation the schema location of the XML content definition that handler belongs to
*
* @return the XML content handler class
*
* @throws CmsXmlException if something goes wrong
*/
public I_CmsXmlContentHandler getContentHandler(String className, String schemaLocation) throws CmsXmlException {
// create a unique key for the content deinition / class name combo
StringBuffer buffer = new StringBuffer(128);
buffer.append(schemaLocation);
buffer.append('#');
buffer.append(className);
String key = buffer.toString();
// look up the content handler from the cache
I_CmsXmlContentHandler contentHandler = m_contentHandlers.get(key);
if (contentHandler != null) {
return contentHandler;
}
// generate an instance for the content handler
try {
contentHandler = (I_CmsXmlContentHandler)Class.forName(className).newInstance();
} catch (InstantiationException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, key));
} catch (IllegalAccessException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, key));
} catch (ClassCastException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, key));
} catch (ClassNotFoundException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, key));
}
// cache and return the content handler instance
m_contentHandlers.put(key, contentHandler);
return contentHandler;
}
/**
* Generates an initialized instance of a XML content type definition
* from the given XML schema element.<p>
*
* @param typeElement the element to generate the XML content type definition from
* @param nestedDefinitions the nested (included) XML content sub-definitions
*
* @return an initialized instance of a XML content type definition
* @throws CmsXmlException in case the element does not describe a valid XML content type definition
*/
public I_CmsXmlSchemaType getContentType(Element typeElement, Set<CmsXmlContentDefinition> nestedDefinitions)
throws CmsXmlException {
if (!CmsXmlContentDefinition.XSD_NODE_ELEMENT.equals(typeElement.getQName())) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CD_SCHEMA_STRUCTURE_0));
}
if (typeElement.elements().size() > 0) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CD_SCHEMA_STRUCTURE_0));
}
String elementName = typeElement.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_NAME);
String typeName = typeElement.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_TYPE);
String defaultValue = typeElement.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_DEFAULT);
String maxOccrs = typeElement.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_MAX_OCCURS);
String minOccrs = typeElement.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_MIN_OCCURS);
if (CmsStringUtil.isEmpty(elementName) || CmsStringUtil.isEmpty(typeName)) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CD_SCHEMA_STRUCTURE_0));
}
boolean simpleType = true;
I_CmsXmlSchemaType schemaType = m_registeredTypes.get(typeName);
if (schemaType == null) {
// the name is not a simple type, try to resolve from the nested schemas
Iterator<CmsXmlContentDefinition> i = nestedDefinitions.iterator();
while (i.hasNext()) {
CmsXmlContentDefinition cd = i.next();
if (typeName.equals(cd.getTypeName())) {
simpleType = false;
return new CmsXmlNestedContentDefinition(cd, elementName, minOccrs, maxOccrs);
}
}
if (simpleType) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_UNKNOWN_SCHEMA_1, typeName));
}
}
if (simpleType && (schemaType != null)) {
schemaType = schemaType.newInstance(elementName, minOccrs, maxOccrs);
if (CmsStringUtil.isNotEmpty(defaultValue)) {
schemaType.setDefault(defaultValue);
}
}
return schemaType;
}
/**
* Returns the content type registered with the given name, or <code>null</code>.<p>
*
* @param typeName the name to look up the content type for
* @return the content type registered with the given name, or <code>null</code>
*/
public I_CmsXmlSchemaType getContentType(String typeName) {
return m_registeredTypes.get(typeName);
}
/**
* Returns a fresh XML content handler instance for the specified class name.<p>
*
* @param className the name of the XML content handler to return
*
* @return the XML content handler class
*
* @throws CmsXmlException if something goes wrong
*/
public I_CmsXmlContentHandler getFreshContentHandler(String className) throws CmsXmlException {
I_CmsXmlContentHandler contentHandler;
// generate an instance for the content handler
try {
contentHandler = (I_CmsXmlContentHandler)Class.forName(className).newInstance();
} catch (InstantiationException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, className));
} catch (IllegalAccessException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, className));
} catch (ClassCastException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, className));
} catch (ClassNotFoundException e) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_INVALID_CONTENT_HANDLER_1, className));
}
return contentHandler;
}
/**
* Returns an alphabetically sorted list of all configured XML content schema types.<p>
*
* @return an alphabetically sorted list of all configured XML content schema types
*/
public List<I_CmsXmlSchemaType> getRegisteredSchemaTypes() {
List<I_CmsXmlSchemaType> result = new ArrayList<I_CmsXmlSchemaType>(m_registeredTypes.values());
Collections.sort(result);
return result;
}
/**
* Returns the alias for the given Widget class name, may be <code>null</code> if no alias is defined for
* the class.<p>
*
* @param className the name of the widget
* @return the alias for the given Widget class name, may be <code>null</code> if no alias is defined for
* the class
*/
public String getRegisteredWidgetAlias(String className) {
Iterator<Map.Entry<String, String>> i = m_widgetAliases.entrySet().iterator();
while (i.hasNext()) {
// key is alias name, value is class name
Map.Entry<String, String> e = i.next();
if (e.getValue().equals(className)) {
// the alias mapping was found
return e.getKey();
}
}
return null;
}
/**
* Returns an alphabetically sorted list of the class names of all configured XML widgets.<p>
*
* @return an alphabetically sorted list of the class names of all configured XML widgets
*/
public List<String> getRegisteredWidgetNames() {
List<String> result = new ArrayList<String>(m_registeredWidgets.keySet());
Collections.sort(result);
return result;
}
/**
* Returns an initialized widget class by it's class name or by it's alias.<p>
*
* @param name the class name or alias name to get the widget for
* @return the widget instance for the class name
*/
public I_CmsWidget getWidget(String name) {
// first look up by class name
I_CmsWidget result = m_registeredWidgets.get(name);
if (result == null) {
// not found by class name, look up an alias
String className = m_widgetAliases.get(name);
if (className != null) {
result = m_registeredWidgets.get(className);
}
}
if (result != null) {
result = result.newInstance();
}
return result;
}
/**
* Returns the editor widget for the specified XML content type.<p>
*
* @param typeName the name of the XML content type to get the widget for
* @return the editor widget for the specified XML content type
*/
public I_CmsWidget getWidgetDefault(String typeName) {
I_CmsWidget result = m_defaultWidgets.get(typeName);
if (result != null) {
result = result.newInstance();
}
return result;
}
/**
* Returns the default widget configuration set in <code>opencms-vfs.xml</code> or <code>null</code> if nothing is found.<p>
*
* @param widget the widget instance to get the default configuration for
*
* @return the default widget configuration
*/
public String getWidgetDefaultConfiguration(I_CmsWidget widget) {
return m_widgetDefaultConfigurations.get(widget.getClass().getName());
}
/**
* Returns the default widget configuration set in <code>opencms-vfs.xml</code> or <code>null</code> if nothing is found.<p>
*
* @param name the class name or alias name to get the default configuration for
*
* @return the default widget configuration
*/
public String getWidgetDefaultConfiguration(String name) {
if (m_registeredWidgets.containsKey(name)) {
return m_widgetDefaultConfigurations.get(name);
}
// not found by class name, look up an alias
String className = m_widgetAliases.get(name);
if (className != null) {
return m_widgetDefaultConfigurations.get(className);
}
return null;
}
/**
* Initializes XML content types managed in this XML content type manager.<p>
*
* @param cms an initialized OpenCms user context with "Administrator" role permissions
*
* @throws CmsRoleViolationException in case the provided OpenCms user context doea not have "Administrator" role permissions
*/
public synchronized void initialize(CmsObject cms) throws CmsRoleViolationException {
if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
// simple test cases don't require this check
OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN);
}
// initialize the special entity resolver
CmsXmlEntityResolver.initialize(cms, getSchemaBytes());
if (CmsLog.INIT.isInfoEnabled()) {
CmsLog.INIT.info(Messages.get().getBundle().key(
Messages.INIT_NUM_ST_INITIALIZED_1,
new Integer(m_registeredTypes.size())));
}
}
/**
* Returns a byte array to be used as input source for the configured XML content types.<p>
*
* @return a byte array to be used as input source for the configured XML content types
*/
private byte[] getSchemaBytes() {
StringBuffer schema = new StringBuffer(512);
schema.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
schema.append("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\">");
Iterator<I_CmsXmlSchemaType> i = m_registeredTypes.values().iterator();
while (i.hasNext()) {
I_CmsXmlSchemaType type = i.next();
schema.append(type.getSchemaDefinition());
}
schema.append("</xsd:schema>");
String schemaStr = schema.toString();
try {
// pretty print the XML schema
// this helps in debugging the auto-generated schema includes
// since it makes them more human-readable
Document doc = CmsXmlUtils.unmarshalHelper(schemaStr, null);
schemaStr = CmsXmlUtils.marshal(doc, CmsEncoder.ENCODING_UTF_8);
} catch (CmsXmlException e) {
// should not ever happen
LOG.error(Messages.get().getBundle().key(Messages.LOG_PRETTY_PRINT_SCHEMA_BYTES_ERROR_0), e);
}
if (LOG.isInfoEnabled()) {
LOG.info(Messages.get().getBundle().key(
Messages.LOG_XML_TYPE_DEFINITION_XSD_2,
CmsXmlContentDefinition.XSD_INCLUDE_OPENCMS,
schemaStr));
}
try {
return schemaStr.getBytes(CmsEncoder.ENCODING_UTF_8);
} catch (UnsupportedEncodingException e) {
// should not happen since the default encoding of UTF-8 is always valid
LOG.error(Messages.get().getBundle().key(Messages.LOG_CONVERTING_SCHEMA_BYTES_ERROR_0), e);
}
return null;
}
}