All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.xml.content.CmsXmlContent Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 18.0
Show newest version
/*
 * 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.content;

import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.types.CmsResourceTypeLocaleIndependentXmlContent;
import org.opencms.file.types.CmsResourceTypeXmlAdeConfiguration;
import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.i18n.CmsEncoder;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.main.CmsException;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsLog;
import org.opencms.main.CmsRuntimeException;
import org.opencms.main.OpenCms;
import org.opencms.staticexport.CmsLinkProcessor;
import org.opencms.staticexport.CmsLinkTable;
import org.opencms.util.CmsMacroResolver;
import org.opencms.util.CmsStringUtil;
import org.opencms.xml.A_CmsXmlDocument;
import org.opencms.xml.CmsXmlContentDefinition;
import org.opencms.xml.CmsXmlException;
import org.opencms.xml.CmsXmlGenericWrapper;
import org.opencms.xml.CmsXmlUtils;
import org.opencms.xml.types.CmsXmlNestedContentDefinition;
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.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.apache.commons.logging.Log;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.xml.sax.EntityResolver;
import org.xml.sax.SAXException;

/**
 * Implementation of a XML content object,
 * used to access and manage structured content.

* * Use the {@link org.opencms.xml.content.CmsXmlContentFactory} to generate an * instance of this class.

* * @since 6.0.0 */ public class CmsXmlContent extends A_CmsXmlDocument { /** The name of the XML content auto correction runtime attribute, this must always be a Boolean. */ public static final String AUTO_CORRECTION_ATTRIBUTE = CmsXmlContent.class.getName() + ".autoCorrectionEnabled"; /** The property to set to enable xerces schema validation. */ public static final String XERCES_SCHEMA_PROPERTY = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation"; /** * Comparator to sort values according to the XML element position.

*/ private static final Comparator COMPARE_INDEX = new Comparator() { public int compare(I_CmsXmlContentValue v1, I_CmsXmlContentValue v2) { return v1.getIndex() - v2.getIndex(); } }; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsXmlContent.class); /** Flag to control if auto correction is enabled when saving this XML content. */ protected boolean m_autoCorrectionEnabled; /** The XML content definition object (i.e. XML schema) used by this content. */ protected CmsXmlContentDefinition m_contentDefinition; /** * Hides the public constructor.

*/ protected CmsXmlContent() { // noop } /** * Creates a new XML content based on the provided XML document.

* * The given encoding is used when marshalling the XML again later.

* * @param cms the cms context, if null no link validation is performed * @param document the document to create the xml content from * @param encoding the encoding of the xml content * @param resolver the XML entitiy resolver to use */ protected CmsXmlContent(CmsObject cms, Document document, String encoding, EntityResolver resolver) { // must set document first to be able to get the content definition m_document = document; // for the next line to work the document must already be available m_contentDefinition = getContentDefinition(resolver); // initialize the XML content structure initDocument(cms, m_document, encoding, m_contentDefinition); } /** * Create a new XML content based on the given default content, * that will have all language nodes of the default content and ensures the presence of the given locale.

* * The given encoding is used when marshalling the XML again later.

* * @param cms the current users OpenCms content * @param locale the locale to generate the default content for * @param modelUri the absolute path to the XML content file acting as model * * @throws CmsException in case the model file is not found or not valid */ protected CmsXmlContent(CmsObject cms, Locale locale, String modelUri) throws CmsException { // init model from given modelUri CmsFile modelFile = cms.readFile(modelUri, CmsResourceFilter.ONLY_VISIBLE_NO_DELETED); CmsXmlContent model = CmsXmlContentFactory.unmarshal(cms, modelFile); // initialize macro resolver to use on model file values CmsMacroResolver macroResolver = CmsMacroResolver.newInstance().setCmsObject(cms); // content defition must be set here since it's used during document creation m_contentDefinition = model.getContentDefinition(); // get the document from the default content Document document = (Document)model.m_document.clone(); // initialize the XML content structure initDocument(cms, document, model.getEncoding(), m_contentDefinition); // resolve eventual macros in the nodes visitAllValuesWith(new CmsXmlContentMacroVisitor(cms, macroResolver)); if (!hasLocale(locale)) { // required locale not present, add it try { addLocale(cms, locale); } catch (CmsXmlException e) { // this can not happen since the locale does not exist } } } /** * Create a new XML content based on the given content definiton, * that will have one language node for the given locale all initialized with default values.

* * The given encoding is used when marshalling the XML again later.

* * @param cms the current users OpenCms content * @param locale the locale to generate the default content for * @param encoding the encoding to use when marshalling the XML content later * @param contentDefinition the content definiton to create the content for */ protected CmsXmlContent(CmsObject cms, Locale locale, String encoding, CmsXmlContentDefinition contentDefinition) { // content defition must be set here since it's used during document creation m_contentDefinition = contentDefinition; // create the XML document according to the content definition Document document = m_contentDefinition.createDocument(cms, this, locale); // initialize the XML content structure initDocument(cms, document, encoding, m_contentDefinition); } /** * @see org.opencms.xml.I_CmsXmlDocument#addLocale(org.opencms.file.CmsObject, java.util.Locale) */ public void addLocale(CmsObject cms, Locale locale) throws CmsXmlException { if (hasLocale(locale)) { throw new CmsXmlException( org.opencms.xml.page.Messages.get().container( org.opencms.xml.page.Messages.ERR_XML_PAGE_LOCALE_EXISTS_1, locale)); } // add element node for Locale m_contentDefinition.createLocale(cms, this, m_document.getRootElement(), locale); // re-initialize the bookmarks initDocument(cms, m_document, m_encoding, m_contentDefinition); } /** * Adds a new XML content value for the given element name and locale at the given index position * to this XML content document.

* * @param cms the current users OpenCms context * @param path the path to the XML content value element * @param locale the locale where to add the new value * @param index the index where to add the value (relative to all other values of this type) * * @return the created XML content value * * @throws CmsIllegalArgumentException if the given path is invalid * @throws CmsRuntimeException if the element identified by the path already occurred {@link I_CmsXmlSchemaType#getMaxOccurs()} * or the given index is invalid (too high). */ public I_CmsXmlContentValue addValue(CmsObject cms, String path, Locale locale, int index) throws CmsIllegalArgumentException, CmsRuntimeException { // get the schema type of the requested path I_CmsXmlSchemaType type = m_contentDefinition.getSchemaType(path); if (type == null) { throw new CmsIllegalArgumentException( Messages.get().container(Messages.ERR_XMLCONTENT_UNKNOWN_ELEM_PATH_SCHEMA_1, path)); } Element parentElement; String elementName; CmsXmlContentDefinition contentDefinition; if (CmsXmlUtils.isDeepXpath(path)) { // this is a nested content definition, so the parent element must be in the bookmarks String parentPath = CmsXmlUtils.createXpath(CmsXmlUtils.removeLastXpathElement(path), 1); Object o = getBookmark(parentPath, locale); if (o == null) { throw new CmsIllegalArgumentException( Messages.get().container(Messages.ERR_XMLCONTENT_UNKNOWN_ELEM_PATH_1, path)); } CmsXmlNestedContentDefinition parentValue = (CmsXmlNestedContentDefinition)o; parentElement = parentValue.getElement(); elementName = CmsXmlUtils.getLastXpathElement(path); contentDefinition = parentValue.getNestedContentDefinition(); } else { // the parent element is the locale element parentElement = getLocaleNode(locale); elementName = CmsXmlUtils.removeXpathIndex(path); contentDefinition = m_contentDefinition; } int insertIndex; if (contentDefinition.getChoiceMaxOccurs() > 0) { // for a choice sequence with maxOccurs we do not check the index position, we rather check if maxOccurs has already been hit // additionally we ensure that the insert index is not too big List choiceSiblings = parentElement.content(); int numSiblings = choiceSiblings != null ? choiceSiblings.size() : 0; if ((numSiblings >= contentDefinition.getChoiceMaxOccurs()) || (index > numSiblings)) { throw new CmsRuntimeException( Messages.get().container( Messages.ERR_XMLCONTENT_ADD_ELEM_INVALID_IDX_CHOICE_3, new Integer(index), elementName, parentElement.getUniquePath())); } insertIndex = index; } else { // read the XML siblings from the parent node List siblings = CmsXmlGenericWrapper.elements(parentElement, elementName); if (siblings.size() > 0) { // we want to add an element to a sequence, and there are elements already of the same type if (siblings.size() >= type.getMaxOccurs()) { // must not allow adding an element if max occurs would be violated throw new CmsRuntimeException( Messages.get().container( Messages.ERR_XMLCONTENT_ELEM_MAXOCCURS_2, elementName, new Integer(type.getMaxOccurs()))); } if (index > siblings.size()) { // index position behind last element of the list throw new CmsRuntimeException( Messages.get().container( Messages.ERR_XMLCONTENT_ADD_ELEM_INVALID_IDX_3, new Integer(index), new Integer(siblings.size()))); } // check for offset required to append beyond last position int offset = (index == siblings.size()) ? 1 : 0; // get the element from the parent at the selected position Element sibling = siblings.get(index - offset); // check position of the node in the parent node content insertIndex = sibling.getParent().content().indexOf(sibling) + offset; } else { // we want to add an element to a sequence, but there are no elements of the same type yet if (index > 0) { // since the element does not occur, index must be 0 throw new CmsRuntimeException( Messages.get().container( Messages.ERR_XMLCONTENT_ADD_ELEM_INVALID_IDX_2, new Integer(index), elementName)); } // check where in the type sequence the type should appear int typeIndex = contentDefinition.getTypeSequence().indexOf(type); if (typeIndex == 0) { // this is the first type, so we just add at the very first position insertIndex = 0; } else { // create a list of all element names that should occur before the selected type List previousTypeNames = new ArrayList(); for (int i = 0; i < typeIndex; i++) { I_CmsXmlSchemaType t = contentDefinition.getTypeSequence().get(i); previousTypeNames.add(t.getName()); } // iterate all elements of the parent node Iterator i = CmsXmlGenericWrapper.content(parentElement).iterator(); int pos = 0; while (i.hasNext()) { Node node = i.next(); if (node instanceof Element) { if (!previousTypeNames.contains(node.getName())) { // the element name is NOT in the list of names that occurs before the selected type, // so it must be an element that occurs AFTER the type break; } } pos++; } insertIndex = pos; } } } // just append the new element at the calculated position I_CmsXmlContentValue newValue = addValue(cms, parentElement, type, locale, insertIndex); // re-initialize this XML content initDocument(m_document, m_encoding, m_contentDefinition); // return the value instance that was stored in the bookmarks // just returning "newValue" isn't enough since this instance is NOT stored in the bookmarks return getBookmark(getBookmarkName(newValue.getPath(), locale)); } /** * @see java.lang.Object#clone() */ @Override public CmsXmlContent clone() { CmsXmlContent clone = new CmsXmlContent(); clone.m_autoCorrectionEnabled = m_autoCorrectionEnabled; clone.m_contentDefinition = m_contentDefinition; clone.m_conversion = m_conversion; clone.m_document = (Document)(m_document.clone()); clone.m_encoding = m_encoding; clone.m_file = m_file; clone.initDocument(); return clone; } /** * Copies the content of the given source locale to the given destination locale in this XML document.

* * @param source the source locale * @param destination the destination loacle * @param elements the set of elements to copy * @throws CmsXmlException if something goes wrong */ public void copyLocale(Locale source, Locale destination, Set elements) throws CmsXmlException { if (!hasLocale(source)) { throw new CmsXmlException( Messages.get().container(org.opencms.xml.Messages.ERR_LOCALE_NOT_AVAILABLE_1, source)); } if (hasLocale(destination)) { throw new CmsXmlException( Messages.get().container(org.opencms.xml.Messages.ERR_LOCALE_ALREADY_EXISTS_1, destination)); } Element sourceElement = null; Element rootNode = m_document.getRootElement(); Iterator i = CmsXmlGenericWrapper.elementIterator(rootNode); String localeStr = source.toString(); while (i.hasNext()) { Element element = i.next(); String language = element.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE, null); if ((language != null) && (localeStr.equals(language))) { // detach node with the locale sourceElement = createDeepElementCopy(element, elements); // there can be only one node for the locale break; } } if (sourceElement == null) { // should not happen since this was checked already, just to make sure... throw new CmsXmlException( Messages.get().container(org.opencms.xml.Messages.ERR_LOCALE_NOT_AVAILABLE_1, source)); } // switch locale value in attribute of copied node sourceElement.addAttribute(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE, destination.toString()); // attach the copied node to the root node rootNode.add(sourceElement); // re-initialize the document bookmarks initDocument(m_document, m_encoding, getContentDefinition()); } /** * Returns all simple type sub values.

* * @param value the value * * @return the simple type sub values */ public List getAllSimpleSubValues(I_CmsXmlContentValue value) { List result = new ArrayList(); for (I_CmsXmlContentValue subValue : getSubValues(value.getPath(), value.getLocale())) { if (subValue.isSimpleType()) { result.add(subValue); } else { result.addAll(getAllSimpleSubValues(subValue)); } } return result; } /** * Returns the list of choice options for the given xpath in the selected locale.

* * In case the xpath does not select a nested choice content definition, * or in case the xpath does not exist at all, null is returned.

* * @param xpath the xpath to check the choice options for * @param locale the locale to check * * @return the list of choice options for the given xpath */ public List getChoiceOptions(String xpath, Locale locale) { I_CmsXmlSchemaType type = m_contentDefinition.getSchemaType(xpath); if (type == null) { // the xpath is not valid in the document return null; } if (!type.isChoiceType() && !type.isChoiceOption()) { // type is neither defining a choice nor part of a choice return null; } if (type.isChoiceType()) { // the type defines a choice sequence CmsXmlContentDefinition cd = ((CmsXmlNestedContentDefinition)type).getNestedContentDefinition(); return cd.getTypeSequence(); } // type must be a choice option I_CmsXmlContentValue value = getValue(xpath, locale); if ((value == null) || (value.getContentDefinition().getChoiceMaxOccurs() > 1)) { // value does not exist in the document or is a multiple choice value return type.getContentDefinition().getTypeSequence(); } // value must be a single choice that already exists in the document, so we must return null return null; } /** * @see org.opencms.xml.I_CmsXmlDocument#getContentDefinition() */ public CmsXmlContentDefinition getContentDefinition() { return m_contentDefinition; } /** * @see org.opencms.xml.I_CmsXmlDocument#getHandler() */ public I_CmsXmlContentHandler getHandler() { return getContentDefinition().getContentHandler(); } /** * @see org.opencms.xml.A_CmsXmlDocument#getLinkProcessor(org.opencms.file.CmsObject, org.opencms.staticexport.CmsLinkTable) */ public CmsLinkProcessor getLinkProcessor(CmsObject cms, CmsLinkTable linkTable) { // initialize link processor String relativeRoot = null; if (m_file != null) { relativeRoot = CmsResource.getParentFolder(cms.getSitePath(m_file)); } return new CmsLinkProcessor(cms, linkTable, getEncoding(), relativeRoot); } /** * Returns the XML root element node for the given locale.

* * @param locale the locale to get the root element for * * @return the XML root element node for the given locale * * @throws CmsRuntimeException if no language element is found in the document */ public Element getLocaleNode(Locale locale) throws CmsRuntimeException { String localeStr = locale.toString(); Iterator i = CmsXmlGenericWrapper.elementIterator(m_document.getRootElement()); while (i.hasNext()) { Element element = i.next(); if (localeStr.equals(element.attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE))) { // language element found, return it return element; } } // language element was not found throw new CmsRuntimeException(Messages.get().container(Messages.ERR_XMLCONTENT_MISSING_LOCALE_1, locale)); } /** * Returns all simple type values below a given path.

* * @param elementPath the element path * @param locale the content locale * * @return the simple type values */ public List getSimpleValuesBelowPath(String elementPath, Locale locale) { List result = new ArrayList(); for (I_CmsXmlContentValue value : getValuesByPath(elementPath, locale)) { if (value.isSimpleType()) { result.add(value); } else { result.addAll(getAllSimpleSubValues(value)); } } return result; } /** * Returns the list of sub-value for the given xpath in the selected locale.

* * @param path the xpath to look up the sub-value for * @param locale the locale to use * * @return the list of sub-value for the given xpath in the selected locale */ @Override public List getSubValues(String path, Locale locale) { List result = new ArrayList(); String bookmark = getBookmarkName(CmsXmlUtils.createXpath(path, 1), locale); int depth = CmsResource.getPathLevel(bookmark) + 1; Iterator i = getBookmarks().iterator(); while (i.hasNext()) { String bm = i.next(); if (bm.startsWith(bookmark) && (CmsResource.getPathLevel(bm) == depth)) { result.add(getBookmark(bm)); } } if (result.size() > 0) { Collections.sort(result, COMPARE_INDEX); } return result; } /** * Returns all values of the given element path.

* * @param elementPath the element path * @param locale the content locale * * @return the values */ public List getValuesByPath(String elementPath, Locale locale) { String[] pathElements = elementPath.split("/"); List values = getValues(pathElements[0], locale); for (int i = 1; i < pathElements.length; i++) { List subValues = new ArrayList(); for (I_CmsXmlContentValue value : values) { subValues.addAll(getValues(CmsXmlUtils.concatXpath(value.getPath(), pathElements[i]), locale)); } if (subValues.isEmpty()) { values = Collections.emptyList(); break; } values = subValues; } return values; } /** * Returns the value sequence for the selected element xpath in this XML content.

* * If the given element xpath is not valid according to the schema of this XML content, * null is returned.

* * @param xpath the element xpath to get the value sequence for * @param locale the locale to get the value sequence for * * @return the value sequence for the selected element name in this XML content */ public CmsXmlContentValueSequence getValueSequence(String xpath, Locale locale) { I_CmsXmlSchemaType type = m_contentDefinition.getSchemaType(xpath); if (type == null) { return null; } return new CmsXmlContentValueSequence(xpath, locale, this); } /** * Returns true if choice options exist for the given xpath in the selected locale.

* * In case the xpath does not select a nested choice content definition, * or in case the xpath does not exist at all, false is returned.

* * @param xpath the xpath to check the choice options for * @param locale the locale to check * * @return true if choice options exist for the given xpath in the selected locale */ public boolean hasChoiceOptions(String xpath, Locale locale) { List options = getChoiceOptions(xpath, locale); if ((options == null) || (options.size() <= 1)) { return false; } return true; } /** * @see org.opencms.xml.A_CmsXmlDocument#isAutoCorrectionEnabled() */ @Override public boolean isAutoCorrectionEnabled() { return m_autoCorrectionEnabled; } /** * Checks if the content is locale independent.

* * @return true if the content is locale independent */ public boolean isLocaleIndependent() { CmsFile file = getFile(); if (CmsResourceTypeXmlContainerPage.isContainerPage(file) || OpenCms.getResourceManager().matchResourceType( CmsResourceTypeXmlContainerPage.GROUP_CONTAINER_TYPE_NAME, file.getTypeId()) || OpenCms.getResourceManager().matchResourceType( CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME, file.getTypeId())) { return true; } try { I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(file); if ((resourceType instanceof CmsResourceTypeLocaleIndependentXmlContent) || (resourceType instanceof CmsResourceTypeXmlAdeConfiguration)) { return true; } } catch (Exception e) { // ignore } return false; } /** * Removes an existing XML content value of the given element name and locale at the given index position * from this XML content document.

* * @param name the name of the XML content value element * @param locale the locale where to remove the value * @param index the index where to remove the value (relative to all other values of this type) */ public void removeValue(String name, Locale locale, int index) { // first get the value from the selected locale and index I_CmsXmlContentValue value = getValue(name, locale, index); if (!value.isChoiceOption()) { // check for the min / max occurs constrains List values = getValues(name, locale); if (values.size() <= value.getMinOccurs()) { // must not allow removing an element if min occurs would be violated throw new CmsRuntimeException( Messages.get().container( Messages.ERR_XMLCONTENT_ELEM_MINOCCURS_2, name, new Integer(value.getMinOccurs()))); } } // detach the value node from the XML document value.getElement().detach(); // re-initialize this XML content initDocument(m_document, m_encoding, m_contentDefinition); } /** * Resolves the mappings for all values of this XML content.

* * @param cms the current users OpenCms context */ public void resolveMappings(CmsObject cms) { // iterate through all initialized value nodes in this XML content CmsXmlContentMappingVisitor visitor = new CmsXmlContentMappingVisitor(cms, this); visitAllValuesWith(visitor); } /** * Sets the flag to control if auto correction is enabled when saving this XML content.

* * @param value the flag to control if auto correction is enabled when saving this XML content */ public void setAutoCorrectionEnabled(boolean value) { m_autoCorrectionEnabled = value; } /** * Synchronizes the locale independent fields for the given locale.

* * @param cms the cms context * @param skipPaths the paths to skip * @param sourceLocale the source locale */ public void synchronizeLocaleIndependentValues(CmsObject cms, Collection skipPaths, Locale sourceLocale) { if (getContentDefinition().getContentHandler().hasSynchronizedElements() && (getLocales().size() > 1)) { for (String elementPath : getContentDefinition().getContentHandler().getSynchronizations()) { synchronizeElement(cms, elementPath, skipPaths, sourceLocale); } } } /** * @see org.opencms.xml.I_CmsXmlDocument#validate(org.opencms.file.CmsObject) */ public CmsXmlContentErrorHandler validate(CmsObject cms) { // iterate through all initialized value nodes in this XML content CmsXmlContentValidationVisitor visitor = new CmsXmlContentValidationVisitor(cms); visitAllValuesWith(visitor); return visitor.getErrorHandler(); } /** * Visits all values of this XML content with the given value visitor.

* * Please note that the order in which the values are visited may NOT be the * order they appear in the XML document. It is ensured that the the parent * of a nested value is visited before the element it contains.

* * @param visitor the value visitor implementation to visit the values with */ public void visitAllValuesWith(I_CmsXmlContentValueVisitor visitor) { List bookmarks = new ArrayList(getBookmarks()); Collections.sort(bookmarks); for (int i = 0; i < bookmarks.size(); i++) { String key = bookmarks.get(i); I_CmsXmlContentValue value = getBookmark(key); visitor.visit(value); } } /** * Creates a new bookmark for the given element.

* * @param element the element to create the bookmark for * @param locale the locale * @param parent the parent node of the element * @param parentPath the parent's path * @param parentDef the parent's content definition */ protected void addBookmarkForElement( Element element, Locale locale, Element parent, String parentPath, CmsXmlContentDefinition parentDef) { int elemIndex = CmsXmlUtils.getXpathIndexInt(element.getUniquePath(parent)); String elemPath = CmsXmlUtils.concatXpath( parentPath, CmsXmlUtils.createXpathElement(element.getName(), elemIndex)); I_CmsXmlSchemaType elemSchemaType = parentDef.getSchemaType(element.getName()); I_CmsXmlContentValue elemValue = elemSchemaType.createValue(this, element, locale); addBookmark(elemPath, locale, true, elemValue); } /** * Adds a bookmark for the given value.

* * @param value the value to bookmark * @param path the lookup path to use for the bookmark * @param locale the locale to use for the bookmark * @param enabled if true, the value is enabled, if false it is disabled */ protected void addBookmarkForValue(I_CmsXmlContentValue value, String path, Locale locale, boolean enabled) { addBookmark(path, locale, enabled, value); } /** * Adds a new XML schema type with the default value to the given parent node.

* * @param cms the cms context * @param parent the XML parent element to add the new value to * @param type the type of the value to add * @param locale the locale to add the new value for * @param insertIndex the index in the XML document where to add the XML node * * @return the created XML content value */ protected I_CmsXmlContentValue addValue( CmsObject cms, Element parent, I_CmsXmlSchemaType type, Locale locale, int insertIndex) { // first generate the XML element for the new value Element element = type.generateXml(cms, this, parent, locale); // detach the XML element from the appended position in order to insert it at the required position element.detach(); // add the XML element at the required position in the parent XML node CmsXmlGenericWrapper.content(parent).add(insertIndex, element); // create the type and return it I_CmsXmlContentValue value = type.createValue(this, element, locale); // generate the default value again - required for nested mappings because only now the full path is available String defaultValue = m_contentDefinition.getContentHandler().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); } // finally return the value return value; } /** * @see org.opencms.xml.A_CmsXmlDocument#getBookmark(java.lang.String) */ @Override protected I_CmsXmlContentValue getBookmark(String bookmark) { // allows package classes to directly access the bookmark information of the XML content return super.getBookmark(bookmark); } /** * @see org.opencms.xml.A_CmsXmlDocument#getBookmarks() */ @Override protected Set getBookmarks() { // allows package classes to directly access the bookmark information of the XML content return super.getBookmarks(); } /** * Returns the content definition object for this xml content object.

* * @param resolver the XML entity resolver to use, required for VFS access * * @return the content definition object for this xml content object * * @throws CmsRuntimeException if the schema location attribute (systemId)cannot be found, * parsing of the schema fails, an underlying IOException occurs or unmarshalling fails * */ protected CmsXmlContentDefinition getContentDefinition(EntityResolver resolver) throws CmsRuntimeException { String schemaLocation = m_document.getRootElement().attributeValue( I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION); // Note regarding exception handling: // Since this object already is a valid XML content object, // it must have a valid schema, otherwise it would not exist. // Therefore the exceptions should never be really thrown. if (schemaLocation == null) { throw new CmsRuntimeException(Messages.get().container(Messages.ERR_XMLCONTENT_MISSING_SCHEMA_0)); } try { return CmsXmlContentDefinition.unmarshal(schemaLocation, resolver); } catch (SAXException e) { throw new CmsRuntimeException(Messages.get().container(Messages.ERR_XML_SCHEMA_PARSE_1, schemaLocation), e); } catch (IOException e) { throw new CmsRuntimeException(Messages.get().container(Messages.ERR_XML_SCHEMA_IO_1, schemaLocation), e); } catch (CmsXmlException e) { throw new CmsRuntimeException( Messages.get().container(Messages.ERR_XMLCONTENT_UNMARSHAL_1, schemaLocation), e); } } /** * Initializes an XML document based on the provided document, encoding and content definition.

* * Checks the links and removes invalid ones in the initialized document.

* * @param cms the current users OpenCms content * @param document the base XML document to use for initializing * @param encoding the encoding to use when marshalling the document later * @param definition the content definition to use */ protected void initDocument(CmsObject cms, Document document, String encoding, CmsXmlContentDefinition definition) { initDocument(document, encoding, definition); // check invalid links if (cms != null) { // this will remove all invalid links getHandler().invalidateBrokenLinks(cms, this); } } /** * @see org.opencms.xml.A_CmsXmlDocument#initDocument(org.dom4j.Document, java.lang.String, org.opencms.xml.CmsXmlContentDefinition) */ @Override protected void initDocument(Document document, String encoding, CmsXmlContentDefinition definition) { m_document = document; m_contentDefinition = definition; m_encoding = CmsEncoder.lookupEncoding(encoding, encoding); m_elementLocales = new HashMap>(); m_elementNames = new HashMap>(); m_locales = new HashSet(); clearBookmarks(); // initialize the bookmarks for (Iterator i = CmsXmlGenericWrapper.elementIterator(m_document.getRootElement()); i.hasNext();) { Element node = i.next(); try { Locale locale = CmsLocaleManager.getLocale( node.attribute(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE).getValue()); addLocale(locale); processSchemaNode(node, null, locale, definition); } catch (NullPointerException e) { LOG.error(Messages.get().getBundle().key(Messages.LOG_XMLCONTENT_INIT_BOOKMARKS_0), e); } } } /** * Processes a document node and extracts the values of the node according to the provided XML * content definition.

* * @param root the root node element to process * @param rootPath the Xpath of the root node in the document * @param locale the locale * @param definition the XML content definition to use for processing the values */ protected void processSchemaNode(Element root, String rootPath, Locale locale, CmsXmlContentDefinition definition) { // iterate all XML nodes List content = CmsXmlGenericWrapper.content(root); for (int i = content.size() - 1; i >= 0; i--) { Node node = content.get(i); if (!(node instanceof Element)) { // this node is not an element, so it must be a white space text node, remove it node.detach(); } else { // node must be an element Element element = (Element)node; String name = element.getName(); int xpathIndex = CmsXmlUtils.getXpathIndexInt(element.getUniquePath(root)); // build the Xpath expression for the current node String path; if (rootPath != null) { StringBuffer b = new StringBuffer(rootPath.length() + name.length() + 6); b.append(rootPath); b.append('/'); b.append(CmsXmlUtils.createXpathElement(name, xpathIndex)); path = b.toString(); } else { path = CmsXmlUtils.createXpathElement(name, xpathIndex); } // create a XML content value element I_CmsXmlSchemaType schemaType = definition.getSchemaType(name); if (schemaType != null) { // directly add simple type to schema I_CmsXmlContentValue value = schemaType.createValue(this, element, locale); addBookmark(path, locale, true, value); if (!schemaType.isSimpleType()) { // recurse for nested schema CmsXmlNestedContentDefinition nestedSchema = (CmsXmlNestedContentDefinition)schemaType; processSchemaNode(element, path, locale, nestedSchema.getNestedContentDefinition()); } } else { // unknown XML node name according to schema if (LOG.isWarnEnabled()) { LOG.warn( Messages.get().getBundle().key( Messages.LOG_XMLCONTENT_INVALID_ELEM_2, name, definition.getSchemaLocation())); } } } } } /** * Sets the file this XML content is written to.

* * @param file the file this XML content content is written to */ protected void setFile(CmsFile file) { m_file = file; } /** * Ensures the parent values to the given path are created.

* * @param cms the cms context * @param valuePath the value path * @param locale the content locale */ private void ensureParentValues(CmsObject cms, String valuePath, Locale locale) { if (valuePath.contains("/")) { String parentPath = valuePath.substring(0, valuePath.lastIndexOf("/")); if (!hasValue(parentPath, locale)) { ensureParentValues(cms, parentPath, locale); int index = CmsXmlUtils.getXpathIndexInt(parentPath) - 1; addValue(cms, parentPath, locale, index); } } } /** * Removes all surplus values of locale independent fields in the other locales.

* * @param elementPath the element path * @param valueCount the value count * @param sourceLocale the source locale */ private void removeSurplusValuesInOtherLocales(String elementPath, int valueCount, Locale sourceLocale) { for (Locale locale : getLocales()) { if (locale.equals(sourceLocale)) { continue; } List localeValues = getValues(elementPath, locale); for (int i = valueCount; i < localeValues.size(); i++) { removeValue(elementPath, locale, 0); } } } /** * Removes all values of the given path in the other locales.

* * @param elementPath the element path * @param sourceLocale the source locale */ private void removeValuesInOtherLocales(String elementPath, Locale sourceLocale) { for (Locale locale : getLocales()) { if (locale.equals(sourceLocale)) { continue; } while (hasValue(elementPath, locale)) { removeValue(elementPath, locale, 0); } } } /** * Sets the value in all other locales.

* * @param cms the cms context * @param value the value * @param requiredParent the path to the required parent value */ private void setValueForOtherLocales(CmsObject cms, I_CmsXmlContentValue value, String requiredParent) { if (!value.isSimpleType()) { throw new IllegalArgumentException(); } for (Locale locale : getLocales()) { if (locale.equals(value.getLocale())) { continue; } String valuePath = value.getPath(); if (CmsStringUtil.isEmptyOrWhitespaceOnly(requiredParent) || hasValue(requiredParent, locale)) { ensureParentValues(cms, valuePath, locale); if (hasValue(valuePath, locale)) { I_CmsXmlContentValue localeValue = getValue(valuePath, locale); localeValue.setStringValue(cms, value.getStringValue(cms)); } else { int index = CmsXmlUtils.getXpathIndexInt(valuePath) - 1; I_CmsXmlContentValue localeValue = addValue(cms, valuePath, locale, index); localeValue.setStringValue(cms, value.getStringValue(cms)); } } } } /** * Synchronizes the values for the given element path.

* * @param cms the cms context * @param elementPath the element path * @param skipPaths the paths to skip * @param sourceLocale the source locale */ private void synchronizeElement( CmsObject cms, String elementPath, Collection skipPaths, Locale sourceLocale) { if (elementPath.contains("/")) { String parentPath = CmsXmlUtils.removeLastXpathElement(elementPath); List parentValues = getValuesByPath(parentPath, sourceLocale); String elementName = CmsXmlUtils.getLastXpathElement(elementPath); for (I_CmsXmlContentValue parentValue : parentValues) { String valuePath = CmsXmlUtils.concatXpath(parentValue.getPath(), elementName); boolean skip = false; for (String skipPath : skipPaths) { if (valuePath.startsWith(skipPath)) { skip = true; break; } } if (!skip) { if (hasValue(valuePath, sourceLocale)) { List subValues = getValues(valuePath, sourceLocale); removeSurplusValuesInOtherLocales(elementPath, subValues.size(), sourceLocale); for (I_CmsXmlContentValue value : subValues) { if (value.isSimpleType()) { setValueForOtherLocales(cms, value, CmsXmlUtils.removeLastXpathElement(valuePath)); } else { List simpleValues = getAllSimpleSubValues(value); for (I_CmsXmlContentValue simpleValue : simpleValues) { setValueForOtherLocales(cms, simpleValue, parentValue.getPath()); } } } } else { removeValuesInOtherLocales(valuePath, sourceLocale); } } } } else { if (hasValue(elementPath, sourceLocale)) { List subValues = getValues(elementPath, sourceLocale); removeSurplusValuesInOtherLocales(elementPath, subValues.size(), sourceLocale); for (I_CmsXmlContentValue value : subValues) { if (value.isSimpleType()) { setValueForOtherLocales(cms, value, null); } else { List simpleValues = getAllSimpleSubValues(value); for (I_CmsXmlContentValue simpleValue : simpleValues) { setValueForOtherLocales(cms, simpleValue, null); } } } } else { removeValuesInOtherLocales(elementPath, sourceLocale); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy