org.opencms.xml.CmsXmlContentDefinition Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.types.CmsResourceTypeXmlContent;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.relations.CmsRelation;
import org.opencms.relations.CmsRelationFilter;
import org.opencms.relations.CmsRelationType;
import org.opencms.util.CmsStringUtil;
import org.opencms.xml.content.CmsDefaultXmlContentHandler;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentFactory;
import org.opencms.xml.content.I_CmsXmlContentHandler;
import org.opencms.xml.types.CmsXmlDynamicCategoryValue;
import org.opencms.xml.types.CmsXmlLocaleValue;
import org.opencms.xml.types.CmsXmlNestedContentDefinition;
import org.opencms.xml.types.CmsXmlStringValue;
import org.opencms.xml.types.I_CmsXmlContentValue;
import org.opencms.xml.types.I_CmsXmlSchemaType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Describes the structure definition of an XML content object.
*
* @since 6.0.0
*/
public class CmsXmlContentDefinition implements Cloneable {
/**
* Enumeration of possible sequence types in a content definition.
*/
public enum SequenceType {
/** A xsd:choice
where the choice elements can appear more than once in a mix. */
MULTIPLE_CHOICE,
/** A simple xsd:sequence
. */
SEQUENCE,
/** A xsd:choice
where only one choice element can be selected. */
SINGLE_CHOICE
}
/** Constant for the XML schema attribute "mapto". */
public static final String XSD_ATTRIBUTE_DEFAULT = "default";
/** Constant for the XML schema attribute "elementFormDefault". */
public static final String XSD_ATTRIBUTE_ELEMENT_FORM_DEFAULT = "elementFormDefault";
/** Constant for the XML schema attribute "maxOccurs". */
public static final String XSD_ATTRIBUTE_MAX_OCCURS = "maxOccurs";
/** Constant for the XML schema attribute "minOccurs". */
public static final String XSD_ATTRIBUTE_MIN_OCCURS = "minOccurs";
/** Constant for the XML schema attribute "name". */
public static final String XSD_ATTRIBUTE_NAME = "name";
/** Constant for the XML schema attribute "schemaLocation". */
public static final String XSD_ATTRIBUTE_SCHEMA_LOCATION = "schemaLocation";
/** Constant for the XML schema attribute "type". */
public static final String XSD_ATTRIBUTE_TYPE = "type";
/** Constant for the XML schema attribute "use". */
public static final String XSD_ATTRIBUTE_USE = "use";
/** Constant for the XML schema attribute value "language". */
public static final String XSD_ATTRIBUTE_VALUE_LANGUAGE = "language";
/** Constant for the XML schema attribute value "1". */
public static final String XSD_ATTRIBUTE_VALUE_ONE = "1";
/** Constant for the XML schema attribute value "optional". */
public static final String XSD_ATTRIBUTE_VALUE_OPTIONAL = "optional";
/** Constant for the XML schema attribute value "qualified". */
public static final String XSD_ATTRIBUTE_VALUE_QUALIFIED = "qualified";
/** Constant for the XML schema attribute value "required". */
public static final String XSD_ATTRIBUTE_VALUE_REQUIRED = "required";
/** Constant for the XML schema attribute value "unbounded". */
public static final String XSD_ATTRIBUTE_VALUE_UNBOUNDED = "unbounded";
/** Constant for the XML schema attribute value "0". */
public static final String XSD_ATTRIBUTE_VALUE_ZERO = "0";
/** The opencms default type definition include. */
public static final String XSD_INCLUDE_OPENCMS = CmsXmlEntityResolver.OPENCMS_SCHEME + "opencms-xmlcontent.xsd";
/** The schema definition namespace. */
public static final Namespace XSD_NAMESPACE = Namespace.get("xsd", "http://www.w3.org/2001/XMLSchema");
/** Constant for the "annotation" node in the XML schema namespace. */
public static final QName XSD_NODE_ANNOTATION = QName.get("annotation", XSD_NAMESPACE);
/** Constant for the "appinfo" node in the XML schema namespace. */
public static final QName XSD_NODE_APPINFO = QName.get("appinfo", XSD_NAMESPACE);
/** Constant for the "attribute" node in the XML schema namespace. */
public static final QName XSD_NODE_ATTRIBUTE = QName.get("attribute", XSD_NAMESPACE);
/** Constant for the "choice" node in the XML schema namespace. */
public static final QName XSD_NODE_CHOICE = QName.get("choice", XSD_NAMESPACE);
/** Constant for the "complexType" node in the XML schema namespace. */
public static final QName XSD_NODE_COMPLEXTYPE = QName.get("complexType", XSD_NAMESPACE);
/** Constant for the "element" node in the XML schema namespace. */
public static final QName XSD_NODE_ELEMENT = QName.get("element", XSD_NAMESPACE);
/** Constant for the "include" node in the XML schema namespace. */
public static final QName XSD_NODE_INCLUDE = QName.get("include", XSD_NAMESPACE);
/** Constant for the "schema" node in the XML schema namespace. */
public static final QName XSD_NODE_SCHEMA = QName.get("schema", XSD_NAMESPACE);
/** Constant for the "sequence" node in the XML schema namespace. */
public static final QName XSD_NODE_SEQUENCE = QName.get("sequence", XSD_NAMESPACE);
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsXmlContentDefinition.class);
/** Null schema type value, required for map lookups. */
private static final I_CmsXmlSchemaType NULL_SCHEMA_TYPE = new CmsXmlStringValue("NULL", "0", "0");
/** Max occurs value for xsd:choice definitions. */
private int m_choiceMaxOccurs;
/** The XML content handler. */
private I_CmsXmlContentHandler m_contentHandler;
/** The Map of configured types indexed by the element xpath. */
private Map m_elementTypes;
/** The set of included additional XML content definitions. */
private Set m_includes;
/** The inner element name of the content definition (type sequence). */
private String m_innerName;
/** The outer element name of the content definition (language sequence). */
private String m_outerName;
/** The XML document from which the schema was unmarshalled. */
private Document m_schemaDocument;
/** The location from which the XML schema was read (XML system id). */
private String m_schemaLocation;
/** Indicates the sequence type of this content definition. */
private SequenceType m_sequenceType;
/** The main type name of this XML content definition. */
private String m_typeName;
/** The Map of configured types. */
private Map m_types;
/** The type sequence. */
private List m_typeSequence;
/**
* Creates a new XML content definition.
*
* @param innerName the inner element name to use for the content definiton
* @param schemaLocation the location from which the XML schema was read (system id)
*/
public CmsXmlContentDefinition(String innerName, String schemaLocation) {
this(innerName + "s", innerName, schemaLocation);
}
/**
* Creates a new XML content definition.
*
* @param outerName the outer element name to use for the content definition
* @param innerName the inner element name to use for the content definition
* @param schemaLocation the location from which the XML schema was read (system id)
*/
public CmsXmlContentDefinition(String outerName, String innerName, String schemaLocation) {
m_outerName = outerName;
m_innerName = innerName;
setInnerName(innerName);
m_typeSequence = new ArrayList();
m_types = new HashMap();
m_includes = new HashSet();
m_schemaLocation = schemaLocation;
m_contentHandler = new CmsDefaultXmlContentHandler();
m_sequenceType = SequenceType.SEQUENCE;
m_elementTypes = new ConcurrentHashMap();
}
/**
* Required empty constructor for clone operation.
*/
protected CmsXmlContentDefinition() {
// noop, required for clone operation
}
/**
* Factory method that returns the XML content definition instance for a given resource.
*
* @param cms the cms-object
* @param resource the resource
*
* @return the XML content definition
*
* @throws CmsException if something goes wrong
*/
public static CmsXmlContentDefinition getContentDefinitionForResource(CmsObject cms, CmsResource resource)
throws CmsException {
CmsXmlContentDefinition contentDef = null;
I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
String schema = resType.getConfiguration().get(CmsResourceTypeXmlContent.CONFIGURATION_SCHEMA);
if (schema != null) {
try {
// this wont in most cases read the file content because of caching
contentDef = unmarshal(cms, schema);
} catch (CmsException e) {
// this should never happen, unless the configured schema is different than the schema in the XML
if (!LOG.isDebugEnabled()) {
LOG.warn(e.getLocalizedMessage(), e);
}
LOG.debug(e.getLocalizedMessage(), e);
}
}
if (contentDef == null) {
// could still be empty since it is not mandatory to configure the resource type in the XML configuration
// try through the XSD relation
List relations = cms.getRelationsForResource(
resource,
CmsRelationFilter.TARGETS.filterType(CmsRelationType.XSD));
if ((relations != null) && !relations.isEmpty()) {
CmsXmlEntityResolver entityResolver = new CmsXmlEntityResolver(cms);
String xsd = cms.getSitePath(relations.get(0).getTarget(cms, CmsResourceFilter.ALL));
contentDef = entityResolver.getCachedContentDefinition(xsd);
}
}
if (contentDef == null) {
// could still be empty if the XML content has been saved with an OpenCms before 8.0.0
// so, to unmarshal is the only possibility left
CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, cms.readFile(resource));
contentDef = content.getContentDefinition();
}
return contentDef;
}
/**
* Reads the content definition which is configured for a resource type.
*
* @param cms the current CMS context
* @param typeName the type name
*
* @return the content definition
*
* @throws CmsException if something goes wrong
*/
public static CmsXmlContentDefinition getContentDefinitionForType(CmsObject cms, String typeName)
throws CmsException {
I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(typeName);
String schema = resType.getConfiguration().get(CmsResourceTypeXmlContent.CONFIGURATION_SCHEMA);
CmsXmlContentDefinition contentDef = null;
if (schema == null) {
return null;
}
contentDef = unmarshal(cms, schema);
return contentDef;
}
/**
* Returns a content handler instance for the given resource.
*
* @param cms the cms-object
* @param resource the resource
*
* @return the content handler
*
* @throws CmsException if something goes wrong
*/
public static I_CmsXmlContentHandler getContentHandlerForResource(CmsObject cms, CmsResource resource)
throws CmsException {
return getContentDefinitionForResource(cms, resource).getContentHandler();
}
/**
* Factory method to unmarshal (read) a XML content definition instance from a byte array
* that contains XML data.
*
* @param xmlData the XML data in a byte array
* @param schemaLocation the location from which the XML schema was read (system id)
* @param resolver the XML entity resolver to use
*
* @return a XML content definition instance unmarshalled from the byte array
*
* @throws CmsXmlException if something goes wrong
*/
public static CmsXmlContentDefinition unmarshal(byte[] xmlData, String schemaLocation, EntityResolver resolver)
throws CmsXmlException {
schemaLocation = translateSchema(schemaLocation);
CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
if (result == null) {
// content definition was not found in the cache, unmarshal the XML document
result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(xmlData, resolver), schemaLocation, resolver);
}
return result;
}
/**
* Factory method to unmarshal (read) a XML content definition instance from the OpenCms VFS resource name.
*
* @param cms the current users CmsObject
* @param resourcename the resource name to unmarshal the XML content definition from
*
* @return a XML content definition instance unmarshalled from the VFS resource
*
* @throws CmsXmlException if something goes wrong
*/
public static CmsXmlContentDefinition unmarshal(CmsObject cms, String resourcename) throws CmsXmlException {
CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);
String schemaLocation = CmsXmlEntityResolver.OPENCMS_SCHEME.concat(resourcename.substring(1));
schemaLocation = translateSchema(schemaLocation);
CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
if (result == null) {
// content definition was not found in the cache, unmarshal the XML document
InputSource source = null;
try {
source = resolver.resolveEntity(null, schemaLocation);
result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(source, resolver), schemaLocation, resolver);
} catch (IOException e) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_UNMARSHALLING_XML_SCHEMA_NOT_FOUND_2,
resourcename,
schemaLocation));
}
}
return result;
}
/**
* Factory method to unmarshal (read) a XML content definition instance from a XML document.
*
* This method does additional validation to ensure the document has the required
* XML structure for a OpenCms content definition schema.
*
* @param document the XML document to generate a XML content definition from
* @param schemaLocation the location from which the XML schema was read (system id)
*
* @return a XML content definition instance unmarshalled from the XML document
*
* @throws CmsXmlException if something goes wrong
*/
public static CmsXmlContentDefinition unmarshal(Document document, String schemaLocation) throws CmsXmlException {
schemaLocation = translateSchema(schemaLocation);
EntityResolver resolver = document.getEntityResolver();
CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
if (result == null) {
// content definition was not found in the cache, unmarshal the XML document
result = unmarshalInternal(document, schemaLocation, resolver);
}
return result;
}
/**
* Factory method to unmarshal (read) a XML content definition instance from a XML InputSource.
*
* @param source the XML InputSource to use
* @param schemaLocation the location from which the XML schema was read (system id)
* @param resolver the XML entity resolver to use
*
* @return a XML content definition instance unmarshalled from the InputSource
*
* @throws CmsXmlException if something goes wrong
*/
public static CmsXmlContentDefinition unmarshal(InputSource source, String schemaLocation, EntityResolver resolver)
throws CmsXmlException {
schemaLocation = translateSchema(schemaLocation);
CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
if (result == null) {
// content definition was not found in the cache, unmarshal the XML document
if (null == source) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_UNMARSHALLING_XML_DOC_1,
String.format("schemaLocation: '%s'. source: null!", schemaLocation)));
}
Document doc = CmsXmlUtils.unmarshalHelper(source, resolver);
result = unmarshalInternal(doc, schemaLocation, resolver);
}
return result;
}
/**
* Factory method to unmarshal (read) a XML content definition instance from a given XML schema location.
*
* The XML content definition data to unmarshal will be read from the provided schema location using
* an XML InputSource.
*
* @param schemaLocation the location from which to read the XML schema (system id)
* @param resolver the XML entity resolver to use
*
* @return a XML content definition instance unmarshalled from the InputSource
*
* @throws CmsXmlException if something goes wrong
* @throws SAXException if the XML schema location could not be converted to an XML InputSource
* @throws IOException if the XML schema location could not be converted to an XML InputSource
*/
public static CmsXmlContentDefinition unmarshal(String schemaLocation, EntityResolver resolver)
throws CmsXmlException, SAXException, IOException {
schemaLocation = translateSchema(schemaLocation);
CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
if (result == null) {
// content definition was not found in the cache, unmarshal the XML document
InputSource source = resolver.resolveEntity(null, schemaLocation);
result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(source, resolver), schemaLocation, resolver);
}
return result;
}
/**
* Factory method to unmarshal (read) a XML content definition instance from a String
* that contains XML data.
*
* @param xmlData the XML data in a String
* @param schemaLocation the location from which the XML schema was read (system id)
* @param resolver the XML entity resolver to use
*
* @return a XML content definition instance unmarshalled from the byte array
*
* @throws CmsXmlException if something goes wrong
*/
public static CmsXmlContentDefinition unmarshal(String xmlData, String schemaLocation, EntityResolver resolver)
throws CmsXmlException {
schemaLocation = translateSchema(schemaLocation);
CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
if (result == null) {
// content definition was not found in the cache, unmarshal the XML document
try {
Document doc = CmsXmlUtils.unmarshalHelper(xmlData, resolver);
result = unmarshalInternal(doc, schemaLocation, resolver);
} catch (CmsXmlException e) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_UNMARSHALLING_XML_DOC_1,
String.format("schemaLocation: '%s'. xml: '%s'", schemaLocation, xmlData)),
e);
}
}
return result;
}
/**
* Creates the name of the type attribute from the given content name.
*
* @param name the name to use
*
* @return the name of the type attribute
*/
protected static String createTypeName(String name) {
StringBuffer result = new StringBuffer(32);
result.append("OpenCms");
result.append(name.substring(0, 1).toUpperCase());
if (name.length() > 1) {
result.append(name.substring(1));
}
return result.toString();
}
/**
* Validates if a given attribute exists at the given element with an (optional) specified value.
*
* If the required value is not null
, the attribute must have exactly this
* value set.
*
* If no value is required, some simple validation is performed on the attribute value,
* like a check that the value does not have leading or trailing white spaces.
*
* @param element the element to validate
* @param attributeName the attribute to check for
* @param requiredValue the required value of the attribute, or null
if any value is allowed
*
* @return the value of the attribute
*
* @throws CmsXmlException if the element does not have the required attribute set, or if the validation fails
*/
protected static String validateAttribute(Element element, String attributeName, String requiredValue)
throws CmsXmlException {
Attribute attribute = element.attribute(attributeName);
if (attribute == null) {
throw new CmsXmlException(
Messages.get().container(Messages.ERR_EL_MISSING_ATTRIBUTE_2, element.getUniquePath(), attributeName));
}
String value = attribute.getValue();
if (requiredValue == null) {
if (CmsStringUtil.isEmptyOrWhitespaceOnly(value) || !value.equals(value.trim())) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_WS_3,
element.getUniquePath(),
attributeName,
value));
}
} else {
if (!requiredValue.equals(value)) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_VALUE_4,
new Object[] {element.getUniquePath(), attributeName, requiredValue, value}));
}
}
return value;
}
/**
* Validates if a given element has exactly the required attributes set.
*
* @param element the element to validate
* @param requiredAttributes the list of required attributes
* @param optionalAttributes the list of optional attributes
*
* @throws CmsXmlException if the validation fails
*/
protected static void validateAttributesExists(
Element element,
String[] requiredAttributes,
String[] optionalAttributes)
throws CmsXmlException {
if (element.attributeCount() < requiredAttributes.length) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_ATTRIBUTE_TOOFEW_3,
element.getUniquePath(),
Integer.valueOf(requiredAttributes.length),
Integer.valueOf(element.attributeCount())));
}
if (element.attributeCount() > (requiredAttributes.length + optionalAttributes.length)) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_ATTRIBUTE_TOOMANY_3,
element.getUniquePath(),
Integer.valueOf(requiredAttributes.length + optionalAttributes.length),
Integer.valueOf(element.attributeCount())));
}
for (int i = 0; i < requiredAttributes.length; i++) {
String attributeName = requiredAttributes[i];
if (element.attribute(attributeName) == null) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_MISSING_ATTRIBUTE_2,
element.getUniquePath(),
attributeName));
}
}
List rA = Arrays.asList(requiredAttributes);
List oA = Arrays.asList(optionalAttributes);
for (int i = 0; i < element.attributes().size(); i++) {
String attributeName = element.attribute(i).getName();
if (!rA.contains(attributeName) && !oA.contains(attributeName)) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_INVALID_ATTRIBUTE_2,
element.getUniquePath(),
attributeName));
}
}
}
/**
* Validates the given element as a complex type sequence.
*
* @param element the element to validate
* @param includes the XML schema includes
*
* @return a data structure containing the validated complex type sequence data
*
* @throws CmsXmlException if the validation fails
*/
protected static CmsXmlComplexTypeSequence validateComplexTypeSequence(
Element element,
Set includes)
throws CmsXmlException {
validateAttributesExists(element, new String[] {XSD_ATTRIBUTE_NAME}, new String[0]);
String name = validateAttribute(element, XSD_ATTRIBUTE_NAME, null);
// now check the type definition list
List mainElements = CmsXmlGenericWrapper.elements(element);
List attributes = mainElements.stream().filter(
elem -> XSD_NODE_ATTRIBUTE.equals(elem.getQName())).collect(Collectors.toList());
boolean hasLanguageAttribute = false;
// two elements in the master list: the second must be the "language" attribute definition
Element languageAttribute = attributes.stream().filter(
elem -> elem.attribute(XSD_ATTRIBUTE_NAME).getValue().equals(
XSD_ATTRIBUTE_VALUE_LANGUAGE)).findFirst().orElse(null);
if (languageAttribute != null) {
validateAttribute(languageAttribute, XSD_ATTRIBUTE_TYPE, CmsXmlLocaleValue.TYPE_NAME);
try {
validateAttribute(languageAttribute, XSD_ATTRIBUTE_USE, XSD_ATTRIBUTE_VALUE_REQUIRED);
} catch (CmsXmlException e) {
validateAttribute(languageAttribute, XSD_ATTRIBUTE_USE, XSD_ATTRIBUTE_VALUE_OPTIONAL);
}
// no error: then the language attribute is valid
hasLanguageAttribute = true;
}
// the type of the sequence
SequenceType sequenceType;
int choiceMaxOccurs = 0;
// check the main element type sequence
Element typeSequenceElement = mainElements.get(0);
if (!XSD_NODE_SEQUENCE.equals(typeSequenceElement.getQName())) {
if (!XSD_NODE_CHOICE.equals(typeSequenceElement.getQName())) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_CD_ELEMENT_NAME_4,
new Object[] {
typeSequenceElement.getUniquePath(),
XSD_NODE_SEQUENCE.getQualifiedName(),
XSD_NODE_CHOICE.getQualifiedName(),
typeSequenceElement.getQName().getQualifiedName()}));
} else {
// this is a xsd:choice, check if this is single or multiple choice
String minOccursStr = typeSequenceElement.attributeValue(XSD_ATTRIBUTE_MIN_OCCURS);
int minOccurs = 1;
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(minOccursStr)) {
try {
minOccurs = Integer.parseInt(minOccursStr.trim());
} catch (NumberFormatException e) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_3,
element.getUniquePath(),
XSD_ATTRIBUTE_MIN_OCCURS,
minOccursStr == null ? "1" : minOccursStr));
}
}
String maxOccursStr = typeSequenceElement.attributeValue(XSD_ATTRIBUTE_MAX_OCCURS);
choiceMaxOccurs = 1;
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(maxOccursStr)) {
if (CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_UNBOUNDED.equals(maxOccursStr.trim())) {
choiceMaxOccurs = Integer.MAX_VALUE;
} else {
try {
choiceMaxOccurs = Integer.parseInt(maxOccursStr.trim());
} catch (NumberFormatException e) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_3,
element.getUniquePath(),
XSD_ATTRIBUTE_MAX_OCCURS,
maxOccursStr));
}
}
}
if ((minOccurs == 0) && (choiceMaxOccurs == 1)) {
// minOccurs 0 and maxOccurs 1, this is a single choice sequence
sequenceType = SequenceType.SINGLE_CHOICE;
} else {
// this is a multiple choice sequence
if (minOccurs > choiceMaxOccurs) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_3,
element.getUniquePath(),
XSD_ATTRIBUTE_MIN_OCCURS,
minOccursStr == null ? "1" : minOccursStr));
}
sequenceType = SequenceType.MULTIPLE_CHOICE;
}
}
} else {
// this is a simple sequence
sequenceType = SequenceType.SEQUENCE;
}
// check the type definition sequence
List typeSequenceElements = CmsXmlGenericWrapper.elements(typeSequenceElement);
if (typeSequenceElements.size() < 1) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_TS_SUBELEMENT_TOOFEW_3,
typeSequenceElement.getUniquePath(),
Integer.valueOf(1),
Integer.valueOf(typeSequenceElements.size())));
}
// now add all type definitions from the schema
List sequence = new ArrayList();
if (hasLanguageAttribute) {
// only generate types for sequence node with language attribute
CmsXmlContentTypeManager typeManager = OpenCms.getXmlContentTypeManager();
Iterator i = typeSequenceElements.iterator();
while (i.hasNext()) {
Element typeElement = i.next();
if (sequenceType != SequenceType.SEQUENCE) {
// in case of xsd:choice, need to make sure "minOccurs" for all type elements is 0
String minOccursStr = typeElement.attributeValue(XSD_ATTRIBUTE_MIN_OCCURS);
int minOccurs = 1;
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(minOccursStr)) {
try {
minOccurs = Integer.parseInt(minOccursStr.trim());
} catch (NumberFormatException e) {
// ignore
}
}
// minOccurs must be "0"
if (minOccurs != 0) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_3,
typeElement.getUniquePath(),
XSD_ATTRIBUTE_MIN_OCCURS,
minOccursStr == null ? "1" : minOccursStr));
}
}
// create the type with the type manager
I_CmsXmlSchemaType type = typeManager.getContentType(typeElement, includes);
if (type.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)
&& ((type.getMaxOccurs() > 1) || (type.getMinOccurs() > 1))) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_OF_TYPE_MUST_OCCUR_AT_MOST_ONCE_2,
typeElement.getUniquePath(),
type.getTypeName()));
}
if (sequenceType == SequenceType.MULTIPLE_CHOICE) {
// if this is a multiple choice sequence,
// all elements must have "minOccurs" 0 or 1 and "maxOccurs" of 1
if ((type.getMinOccurs() < 0) || (type.getMinOccurs() > 1) || (type.getMaxOccurs() != 1)) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_EL_BAD_ATTRIBUTE_3,
typeElement.getUniquePath(),
XSD_ATTRIBUTE_MAX_OCCURS,
typeElement.attributeValue(XSD_ATTRIBUTE_MAX_OCCURS)));
}
}
sequence.add(type);
}
} else {
// generate a nested content definition for the main type sequence
Element e = typeSequenceElements.get(0);
String typeName = validateAttribute(e, XSD_ATTRIBUTE_NAME, null);
String minOccurs = validateAttribute(e, XSD_ATTRIBUTE_MIN_OCCURS, XSD_ATTRIBUTE_VALUE_ZERO);
String maxOccurs = validateAttribute(e, XSD_ATTRIBUTE_MAX_OCCURS, XSD_ATTRIBUTE_VALUE_UNBOUNDED);
validateAttribute(e, XSD_ATTRIBUTE_TYPE, createTypeName(typeName));
CmsXmlNestedContentDefinition cd = new CmsXmlNestedContentDefinition(null, typeName, minOccurs, maxOccurs);
sequence.add(cd);
}
// return a data structure with the collected values
return new CmsXmlComplexTypeSequence(name, sequence, hasLanguageAttribute, sequenceType, choiceMaxOccurs);
}
/**
* Looks up the given XML content definition system id in the internal content definition cache.
*
* @param schemaLocation the system id of the XML content definition to look up
* @param resolver the XML entity resolver to use (contains the cache)
*
* @return the XML content definition found, or null if no definition is cached for the given system id
*/
private static CmsXmlContentDefinition getCachedContentDefinition(String schemaLocation, EntityResolver resolver) {
if (resolver instanceof CmsXmlEntityResolver) {
// check for a cached version of this content definition
CmsXmlEntityResolver cmsResolver = (CmsXmlEntityResolver)resolver;
return cmsResolver.getCachedContentDefinition(schemaLocation);
}
return null;
}
/**
* Translates the XSD schema location.
*
* @param schemaLocation the location to translate
*
* @return the translated schema location
*/
private static String translateSchema(String schemaLocation) {
if (OpenCms.getRepositoryManager() != null) {
return OpenCms.getResourceManager().getXsdTranslator().translateResource(schemaLocation);
}
return schemaLocation;
}
/**
* Internal method to unmarshal (read) a XML content definition instance from a XML document.
*
* It is assumed that the XML content definition cache has already been tested and the document
* has not been found in the cache. After the XML content definition has been successfully created,
* it is placed in the cache.
*
* @param document the XML document to generate a XML content definition from
* @param schemaLocation the location from which the XML schema was read (system id)
* @param resolver the XML entity resolver used by the given XML document
*
* @return a XML content definition instance unmarshalled from the XML document
*
* @throws CmsXmlException if something goes wrong
*/
private static CmsXmlContentDefinition unmarshalInternal(
Document document,
String schemaLocation,
EntityResolver resolver)
throws CmsXmlException {
// analyze the document and generate the XML content type definition
Element root = document.getRootElement();
if (!XSD_NODE_SCHEMA.equals(root.getQName())) {
// schema node is required
throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_NO_SCHEMA_NODE_0));
}
List includes = CmsXmlGenericWrapper.elements(root, XSD_NODE_INCLUDE);
if (includes.size() < 1) {
// one include is required
throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_ONE_INCLUDE_REQUIRED_0));
}
Element include = includes.get(0);
String target = validateAttribute(include, XSD_ATTRIBUTE_SCHEMA_LOCATION, null);
if (!XSD_INCLUDE_OPENCMS.equals(target)) {
// the first include must point to the default OpenCms standard schema include
throw new CmsXmlException(
Messages.get().container(Messages.ERR_CD_FIRST_INCLUDE_2, XSD_INCLUDE_OPENCMS, target));
}
boolean recursive = false;
Set nestedDefinitions = new HashSet();
if (includes.size() > 1) {
// resolve additional, nested include calls
for (int i = 1; i < includes.size(); i++) {
Element inc = includes.get(i);
String schemaLoc = validateAttribute(inc, XSD_ATTRIBUTE_SCHEMA_LOCATION, null);
if (!(schemaLoc.equals(schemaLocation))) {
InputSource source = null;
try {
source = resolver.resolveEntity(null, schemaLoc);
} catch (Exception e) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_CD_BAD_INCLUDE_3,
schemaLoc,
schemaLocation,
document.asXML()),
e);
}
// Couldn't resolve the entity?
if (null == source) {
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_CD_BAD_INCLUDE_3,
schemaLoc,
schemaLocation,
document.asXML()));
}
CmsXmlContentDefinition xmlContentDefinition = unmarshal(source, schemaLoc, resolver);
nestedDefinitions.add(xmlContentDefinition);
} else {
// recursion
recursive = true;
}
}
}
List elements = CmsXmlGenericWrapper.elements(root, XSD_NODE_ELEMENT);
if (elements.size() != 1) {
// only one root element is allowed
throw new CmsXmlException(
Messages.get().container(
Messages.ERR_CD_ROOT_ELEMENT_COUNT_1,
XSD_INCLUDE_OPENCMS,
Integer.valueOf(elements.size())));
}
// collect the data from the root element node
Element main = elements.get(0);
String name = validateAttribute(main, XSD_ATTRIBUTE_NAME, null);
// now process the complex types
List complexTypes = CmsXmlGenericWrapper.elements(root, XSD_NODE_COMPLEXTYPE);
if (complexTypes.size() != 2) {
// exactly two complex types are required
throw new CmsXmlException(
Messages.get().container(Messages.ERR_CD_COMPLEX_TYPE_COUNT_1, Integer.valueOf(complexTypes.size())));
}
// get the outer element sequence, this must be the first element
CmsXmlComplexTypeSequence outerSequence = validateComplexTypeSequence(complexTypes.get(0), nestedDefinitions);
CmsXmlNestedContentDefinition outer = (CmsXmlNestedContentDefinition)outerSequence.getSequence().get(0);
// make sure the inner and outer element names are as required
String outerTypeName = createTypeName(name);
String innerTypeName = createTypeName(outer.getName());
validateAttribute(complexTypes.get(0), XSD_ATTRIBUTE_NAME, outerTypeName);
validateAttribute(complexTypes.get(1), XSD_ATTRIBUTE_NAME, innerTypeName);
validateAttribute(main, XSD_ATTRIBUTE_TYPE, outerTypeName);
// generate the result XML content definition
CmsXmlContentDefinition result = new CmsXmlContentDefinition(name, null, schemaLocation);
// set the nested definitions
result.m_includes = nestedDefinitions;
// set the schema document
result.m_schemaDocument = document;
// the inner name is the element name set in the outer sequence
result.setInnerName(outer.getName());
if (recursive) {
nestedDefinitions.add(result);
}
// get the inner element sequence, this must be the second element
CmsXmlComplexTypeSequence innerSequence = validateComplexTypeSequence(complexTypes.get(1), nestedDefinitions);
// add the types from the main sequence node
Iterator it = innerSequence.getSequence().iterator();
while (it.hasNext()) {
result.addType(it.next());
}
// store if this content definition contains a xsd:choice sequence
result.m_sequenceType = innerSequence.getSequenceType();
result.m_choiceMaxOccurs = innerSequence.getChoiceMaxOccurs();
// resolve the XML content handler information
List annotations = CmsXmlGenericWrapper.elements(root, XSD_NODE_ANNOTATION);
I_CmsXmlContentHandler contentHandler = null;
Element appInfoElement = null;
if (annotations.size() > 0) {
List appinfos = CmsXmlGenericWrapper.elements(annotations.get(0), XSD_NODE_APPINFO);
if (appinfos.size() > 0) {
// the first appinfo node contains the specific XML content data
appInfoElement = appinfos.get(0);
// check for a special content handler in the appinfo node
Element handlerElement = appInfoElement.element("handler");
if (handlerElement != null) {
String className = handlerElement.attributeValue("class");
if (className != null) {
contentHandler = OpenCms.getXmlContentTypeManager().getFreshContentHandler(className);
}
}
}
}
if (contentHandler == null) {
// if no content handler is defined, the default handler is used
contentHandler = OpenCms.getXmlContentTypeManager().getFreshContentHandler(
CmsDefaultXmlContentHandler.class.getName());
}
// analyze the app info node with the selected XML content handler
contentHandler.initialize(appInfoElement, result);
result.m_contentHandler = contentHandler;
result.freeze();
if (resolver instanceof CmsXmlEntityResolver) {
// put the generated content definition in the cache
((CmsXmlEntityResolver)resolver).cacheContentDefinition(schemaLocation, result);
}
return result;
}
/**
* Adds the missing default XML according to this content definition to the given document element.
*
* In case the root element already contains sub nodes, only missing sub nodes are added.
*
* @param cms the current users OpenCms context
* @param document the document where the XML is added in (required for default XML generation)
* @param root the root node to add the missing XML for
* @param locale the locale to add the XML for
*
* @return the given root element with the missing content added
*/
public Element addDefaultXml(CmsObject cms, I_CmsXmlDocument document, Element root, Locale locale) {
Iterator i = m_typeSequence.iterator();
int currentPos = 0;
List allElements = CmsXmlGenericWrapper.elements(root);
while (i.hasNext()) {
I_CmsXmlSchemaType type = i.next();
// check how many elements of this type already exist in the XML
String elementName = type.getName();
List elements = CmsXmlGenericWrapper.elements(root, elementName);
currentPos += elements.size();
for (int j = elements.size(); j < type.getMinOccurs(); j++) {
// append the missing elements
Element typeElement = type.generateXml(cms, document, root, locale);
// need to check for default value again because the of appinfo "mappings" node
I_CmsXmlContentValue value = type.createValue(document, typeElement, locale);
String defaultValue = document.getHandler().getDefault(cms, value, locale);
if (defaultValue != null) {
// only if there is a default value available use it to overwrite the initial default
value.setStringValue(cms, defaultValue);
}
// re-sort elements as they have been appended to the end of the XML root, not at the correct position
typeElement.detach();
allElements.add(currentPos, typeElement);
currentPos++;
}
}
return root;
}
/**
* Adds a nested (included) XML content definition.
*
* @param nestedSchema the nested (included) XML content definition to add
*/
public void addInclude(CmsXmlContentDefinition nestedSchema) {
m_includes.add(nestedSchema);
}
/**
* Adds the given content type.
*
* @param type the content type to add
*
* @throws CmsXmlException in case an unregistered type is added
*/
public void addType(I_CmsXmlSchemaType type) throws CmsXmlException {
// check if the type to add actually exists in the type manager
CmsXmlContentTypeManager typeManager = OpenCms.getXmlContentTypeManager();
if (type.isSimpleType() && (typeManager.getContentType(type.getTypeName()) == null)) {
throw new CmsXmlException(Messages.get().container(Messages.ERR_UNREGISTERED_TYPE_1, type.getTypeName()));
}
// add the type to the internal type sequence and lookup table
m_typeSequence.add(type);
m_types.put(type.getName(), type);
// store reference to the content definition in the type
type.setContentDefinition(this);
}
/**
* Creates a clone of this XML content definition.
*
* @return a clone of this XML content definition
*/
@Override
public Object clone() {
CmsXmlContentDefinition result = new CmsXmlContentDefinition();
result.m_innerName = m_innerName;
result.m_schemaLocation = m_schemaLocation;
result.m_typeSequence = m_typeSequence;
result.m_types = m_types;
result.m_contentHandler = m_contentHandler;
result.m_typeName = m_typeName;
result.m_includes = m_includes;
result.m_sequenceType = m_sequenceType;
result.m_choiceMaxOccurs = m_choiceMaxOccurs;
result.m_elementTypes = m_elementTypes;
return result;
}
/**
* Generates the default XML content for this content definition, and append it to the given root element.
*
* Please note: The default values for the annotations are read from the content definition of the given
* document. For a nested content definitions, this means that all defaults are set in the annotations of the
* "outer" or "main" content definition.
*
* @param cms the current users OpenCms context
* @param document the OpenCms XML document the XML is created for
* @param root the node of the document where to append the generated XML to
* @param locale the locale to create the default element in the document with
*
* @return the default XML content for this content definition, and append it to the given root element
*/
public Element createDefaultXml(CmsObject cms, I_CmsXmlDocument document, Element root, Locale locale) {
Iterator i = m_typeSequence.iterator();
while (i.hasNext()) {
I_CmsXmlSchemaType type = i.next();
for (int j = 0; j < type.getMinOccurs(); j++) {
Element typeElement = type.generateXml(cms, document, root, locale);
// need to check for default value again because of the appinfo "mappings" node
I_CmsXmlContentValue value = type.createValue(document, typeElement, locale);
String defaultValue = document.getHandler().getDefault(cms, value, locale);
if (defaultValue != null) {
// only if there is a default value available use it to overwrite the initial default
value.setStringValue(cms, defaultValue);
}
}
}
return root;
}
/**
* Generates a valid XML document according to the XML schema of this content definition.
*
* @param cms the current users OpenCms context
* @param document the OpenCms XML document the XML is created for
* @param locale the locale to create the default element in the document with
*
* @return a valid XML document according to the XML schema of this content definition
*/
public Document createDocument(CmsObject cms, I_CmsXmlDocument document, Locale locale) {
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement(getOuterName());
root.add(I_CmsXmlSchemaType.XSI_NAMESPACE);
root.addAttribute(I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION, getSchemaLocation());
int version = getVersion();
if (version != 0) {
root.addAttribute(CmsXmlContent.A_VERSION, "" + version);
}
createLocale(cms, document, root, locale);
return doc;
}
/**
* Generates a valid locale (language) element for the XML schema of this content definition.
*
* @param cms the current users OpenCms context
* @param document the OpenCms XML document the XML is created for
* @param root the root node of the document where to append the locale to
* @param locale the locale to create the default element in the document with
*
* @return a valid XML element for the locale according to the XML schema of this content definition
*/
public Element createLocale(CmsObject cms, I_CmsXmlDocument document, Element root, Locale locale) {
// add an element with a "locale" attribute to the given root node
Element element = root.addElement(getInnerName());
element.addAttribute(XSD_ATTRIBUTE_VALUE_LANGUAGE, locale.toString());
// now generate the default XML for the element
return createDefaultXml(cms, document, element, locale);
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CmsXmlContentDefinition)) {
return false;
}
CmsXmlContentDefinition other = (CmsXmlContentDefinition)obj;
if (!getInnerName().equals(other.getInnerName())) {
return false;
}
if (!getOuterName().equals(other.getOuterName())) {
return false;
}
return m_typeSequence.equals(other.m_typeSequence);
}
/**
* Iterates over all schema types along a given xpath, starting from a root content definition.
*
* @param path the path
* @param consumer a handler that consumes both the schema type and the remaining suffix of the path, relative to the schema type
*
* @return true if for all path components a schema type could be found
*/
public boolean findSchemaTypesForPath(String path, BiConsumer consumer) {
path = CmsXmlUtils.removeAllXpathIndices(path);
List pathComponents = CmsXmlUtils.splitXpath(path);
List result = new ArrayList<>();
CmsXmlContentDefinition currentContentDef = this;
for (int i = 0; i < pathComponents.size(); i++) {
String pathComponent = pathComponents.get(i);
if (currentContentDef == null) {
return false;
}
I_CmsXmlSchemaType schemaType = currentContentDef.getSchemaType(pathComponent);
if (schemaType == null) {
return false;
} else {
String remainingPath = CmsStringUtil.listAsString(
pathComponents.subList(i + 1, pathComponents.size()),
"/");
consumer.accept(schemaType, remainingPath);
result.add(schemaType);
if (schemaType instanceof CmsXmlNestedContentDefinition) {
currentContentDef = ((CmsXmlNestedContentDefinition)schemaType).getNestedContentDefinition();
} else {
currentContentDef = null; //
}
}
}
return true;
}
/**
* Freezes this content definition, making all internal data structures
* unmodifiable.
*
* This is required to prevent modification of a cached content definition.
*/
public void freeze() {
m_types = Collections.unmodifiableMap(m_types);
m_typeSequence = Collections.unmodifiableList(m_typeSequence);
}
/**
* Returns the maxOccurs value for the choice in case this is a xsd:choice
content definition.
*
* This content definition is a xsd:choice
sequence if the returned value is larger then 0.
*
* @return the maxOccurs value for the choice in case this is a xsd:choice
content definition
*/
public int getChoiceMaxOccurs() {
return m_choiceMaxOccurs;
}
/**
* Returns the selected XML content handler for this XML content definition.
*
* If no specific XML content handler was provided in the "appinfo" node of the
* XML schema, the default XML content handler {@link CmsDefaultXmlContentHandler}
is used.
*
* @return the contentHandler
*/
public I_CmsXmlContentHandler getContentHandler() {
return m_contentHandler;
}
/**
* Returns the set of nested (included) XML content definitions.
*
* @return the set of nested (included) XML content definitions
*/
public Set getIncludes() {
return m_includes;
}
/**
* Returns the inner element name of this content definition.
*
* @return the inner element name of this content definition
*/
public String getInnerName() {
return m_innerName;
}
/**
* Returns the outer element name of this content definition.
*
* @return the outer element name of this content definition
*/
public String getOuterName() {
return m_outerName;
}
/**
* Generates an XML schema for the content definition.
*
* @return the generated XML schema
*/
public Document getSchema() {
Document result;
if (m_schemaDocument == null) {
result = DocumentHelper.createDocument();
Element root = result.addElement(XSD_NODE_SCHEMA);
root.addAttribute(XSD_ATTRIBUTE_ELEMENT_FORM_DEFAULT, XSD_ATTRIBUTE_VALUE_QUALIFIED);
Element include = root.addElement(XSD_NODE_INCLUDE);
include.addAttribute(XSD_ATTRIBUTE_SCHEMA_LOCATION, XSD_INCLUDE_OPENCMS);
if (m_includes.size() > 0) {
Iterator i = m_includes.iterator();
while (i.hasNext()) {
CmsXmlContentDefinition definition = i.next();
root.addElement(XSD_NODE_INCLUDE).addAttribute(
XSD_ATTRIBUTE_SCHEMA_LOCATION,
definition.m_schemaLocation);
}
}
String outerTypeName = createTypeName(getOuterName());
String innerTypeName = createTypeName(getInnerName());
Element content = root.addElement(XSD_NODE_ELEMENT);
content.addAttribute(XSD_ATTRIBUTE_NAME, getOuterName());
content.addAttribute(XSD_ATTRIBUTE_TYPE, outerTypeName);
Element list = root.addElement(XSD_NODE_COMPLEXTYPE);
list.addAttribute(XSD_ATTRIBUTE_NAME, outerTypeName);
Element listSequence = list.addElement(XSD_NODE_SEQUENCE);
Element listElement = listSequence.addElement(XSD_NODE_ELEMENT);
listElement.addAttribute(XSD_ATTRIBUTE_NAME, getInnerName());
listElement.addAttribute(XSD_ATTRIBUTE_TYPE, innerTypeName);
listElement.addAttribute(XSD_ATTRIBUTE_MIN_OCCURS, XSD_ATTRIBUTE_VALUE_ZERO);
listElement.addAttribute(XSD_ATTRIBUTE_MAX_OCCURS, XSD_ATTRIBUTE_VALUE_UNBOUNDED);
Element main = root.addElement(XSD_NODE_COMPLEXTYPE);
main.addAttribute(XSD_ATTRIBUTE_NAME, innerTypeName);
Element mainSequence;
if (m_sequenceType == SequenceType.SEQUENCE) {
mainSequence = main.addElement(XSD_NODE_SEQUENCE);
} else {
mainSequence = main.addElement(XSD_NODE_CHOICE);
if (getChoiceMaxOccurs() > 1) {
mainSequence.addAttribute(XSD_ATTRIBUTE_MAX_OCCURS, String.valueOf(getChoiceMaxOccurs()));
} else {
mainSequence.addAttribute(XSD_ATTRIBUTE_MIN_OCCURS, XSD_ATTRIBUTE_VALUE_ZERO);
mainSequence.addAttribute(XSD_ATTRIBUTE_MAX_OCCURS, XSD_ATTRIBUTE_VALUE_ONE);
}
}
Iterator i = m_typeSequence.iterator();
while (i.hasNext()) {
I_CmsXmlSchemaType schemaType = i.next();
schemaType.appendXmlSchema(mainSequence);
}
Element language = main.addElement(XSD_NODE_ATTRIBUTE);
language.addAttribute(XSD_ATTRIBUTE_NAME, XSD_ATTRIBUTE_VALUE_LANGUAGE);
language.addAttribute(XSD_ATTRIBUTE_TYPE, CmsXmlLocaleValue.TYPE_NAME);
language.addAttribute(XSD_ATTRIBUTE_USE, XSD_ATTRIBUTE_VALUE_OPTIONAL);
} else {
result = (Document)m_schemaDocument.clone();
}
return result;
}
/**
* Returns the location from which the XML schema was read (XML system id).
*
* @return the location from which the XML schema was read (XML system id)
*/
public String getSchemaLocation() {
return m_schemaLocation;
}
/**
* Returns the schema type for the given element name, or null
if no
* node is defined with this name.
*
* @param elementPath the element xpath to look up the type for
* @return the type for the given element name, or null
if no
* node is defined with this name
*/
public I_CmsXmlSchemaType getSchemaType(String elementPath) {
String path = CmsXmlUtils.removeXpath(elementPath);
I_CmsXmlSchemaType result = m_elementTypes.get(path);
if (result == null) {
result = getSchemaTypeRecusive(path);
if (result != null) {
m_elementTypes.put(path, result);
} else {
m_elementTypes.put(path, NULL_SCHEMA_TYPE);
}
} else if (result == NULL_SCHEMA_TYPE) {
result = null;
}
return result;
}
/**
* Returns the internal set of schema type names.
*
* @return the internal set of schema type names
*/
public Set getSchemaTypes() {
return m_types.keySet();
}
/**
* Returns the sequence type of this content definition.
*
* @return the sequence type of this content definition
*/
public SequenceType getSequenceType() {
return m_sequenceType;
}
/**
* Returns the main type name of this XML content definition.
*
* @return the main type name of this XML content definition
*/
public String getTypeName() {
return m_typeName;
}
/**
* Returns the type sequence, contains instances of {@link I_CmsXmlSchemaType}.
*
* @return the type sequence, contains instances of {@link I_CmsXmlSchemaType}
*/
public List getTypeSequence() {
return m_typeSequence;
}
/**
* Gets the version.
*
* @return the version number
*/
public int getVersion() {
return CmsXmlUtils.getSchemaVersion(m_schemaDocument);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return getInnerName().hashCode();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return CmsXmlContentDefinition.class.getSimpleName() + " " + m_schemaLocation;
}
/**
* Sets the inner element name to use for the content definition.
*
* @param innerName the inner element name to set
*/
protected void setInnerName(String innerName) {
m_innerName = innerName;
if (m_innerName != null) {
m_typeName = createTypeName(innerName);
}
}
/**
* Sets the outer element name to use for the content definition.
*
* @param outerName the outer element name to set
*/
protected void setOuterName(String outerName) {
m_outerName = outerName;
}
/**
* Calculates the schema type for the given element name by recursing into the schema structure.
*
* @param elementPath the element xpath to look up the type for
* @return the type for the given element name, or null
if no
* node is defined with this name
*/
private I_CmsXmlSchemaType getSchemaTypeRecusive(String elementPath) {
String path = CmsXmlUtils.getFirstXpathElement(elementPath);
I_CmsXmlSchemaType type = m_types.get(path);
if (type == null) {
// no node with the given path defined in schema
return null;
}
// check if recursion is required to get value from a nested schema
if (type.isSimpleType() || !CmsXmlUtils.isDeepXpath(elementPath)) {
// no recursion required
return type;
}
// recursion required since the path is an xpath and the type must be a nested content definition
CmsXmlNestedContentDefinition nestedDefinition = (CmsXmlNestedContentDefinition)type;
path = CmsXmlUtils.removeFirstXpathElement(elementPath);
return nestedDefinition.getNestedContentDefinition().getSchemaType(path);
}
}