0001: /*
0002: * File : $Source: /usr/local/cvs/opencms/src/org/opencms/xml/content/CmsDefaultXmlContentHandler.java,v $
0003: * Date : $Date: 2008-02-27 12:05:36 $
0004: * Version: $Revision: 1.58 $
0005: *
0006: * This library is part of OpenCms -
0007: * the Open Source Content Management System
0008: *
0009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
0010: *
0011: * This library is free software; you can redistribute it and/or
0012: * modify it under the terms of the GNU Lesser General Public
0013: * License as published by the Free Software Foundation; either
0014: * version 2.1 of the License, or (at your option) any later version.
0015: *
0016: * This library is distributed in the hope that it will be useful,
0017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: * Lesser General Public License for more details.
0020: *
0021: * For further information about Alkacon Software GmbH, please see the
0022: * company website: http://www.alkacon.com
0023: *
0024: * For further information about OpenCms, please see the
0025: * project website: http://www.opencms.org
0026: *
0027: * You should have received a copy of the GNU Lesser General Public
0028: * License along with this library; if not, write to the Free Software
0029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0030: */
0031:
0032: package org.opencms.xml.content;
0033:
0034: import org.opencms.configuration.CmsConfigurationManager;
0035: import org.opencms.file.CmsFile;
0036: import org.opencms.file.CmsObject;
0037: import org.opencms.file.CmsProperty;
0038: import org.opencms.file.CmsResource;
0039: import org.opencms.file.CmsResourceFilter;
0040: import org.opencms.i18n.CmsEncoder;
0041: import org.opencms.i18n.CmsMessages;
0042: import org.opencms.lock.CmsLock;
0043: import org.opencms.main.CmsException;
0044: import org.opencms.main.CmsLog;
0045: import org.opencms.main.CmsRuntimeException;
0046: import org.opencms.main.OpenCms;
0047: import org.opencms.relations.CmsLink;
0048: import org.opencms.relations.CmsRelationType;
0049: import org.opencms.site.CmsSite;
0050: import org.opencms.util.CmsFileUtil;
0051: import org.opencms.util.CmsHtmlConverter;
0052: import org.opencms.util.CmsMacroResolver;
0053: import org.opencms.util.CmsStringUtil;
0054: import org.opencms.widgets.CmsDisplayWidget;
0055: import org.opencms.widgets.I_CmsWidget;
0056: import org.opencms.xml.CmsXmlContentDefinition;
0057: import org.opencms.xml.CmsXmlEntityResolver;
0058: import org.opencms.xml.CmsXmlException;
0059: import org.opencms.xml.CmsXmlUtils;
0060: import org.opencms.xml.types.CmsXmlNestedContentDefinition;
0061: import org.opencms.xml.types.CmsXmlVarLinkValue;
0062: import org.opencms.xml.types.CmsXmlVfsFileValue;
0063: import org.opencms.xml.types.I_CmsXmlContentValue;
0064: import org.opencms.xml.types.I_CmsXmlSchemaType;
0065:
0066: import java.util.ArrayList;
0067: import java.util.HashMap;
0068: import java.util.Iterator;
0069: import java.util.List;
0070: import java.util.Locale;
0071: import java.util.Map;
0072: import java.util.regex.Pattern;
0073:
0074: import org.apache.commons.logging.Log;
0075:
0076: import org.dom4j.Document;
0077: import org.dom4j.DocumentHelper;
0078: import org.dom4j.Element;
0079:
0080: /**
0081: * Default implementation for the XML content handler, will be used by all XML contents that do not
0082: * provide their own handler.<p>
0083: *
0084: * @author Alexander Kandzior
0085: * @author Michael Moossen
0086: *
0087: * @version $Revision: 1.58 $
0088: *
0089: * @since 6.0.0
0090: */
0091: public class CmsDefaultXmlContentHandler implements
0092: I_CmsXmlContentHandler {
0093:
0094: /** Constant for the "appinfo" element name itself. */
0095: public static final String APPINFO_APPINFO = "appinfo";
0096:
0097: /** Constant for the "configuration" appinfo attribute name. */
0098: public static final String APPINFO_ATTR_CONFIGURATION = "configuration";
0099:
0100: /** Constant for the "element" appinfo attribute name. */
0101: public static final String APPINFO_ATTR_ELEMENT = "element";
0102:
0103: /** Constant for the "invalidate" appinfo attribute name. */
0104: public static final String APPINFO_ATTR_INVALIDATE = "invalidate";
0105:
0106: /** Constant for the "mapto" appinfo attribute name. */
0107: public static final String APPINFO_ATTR_MAPTO = "mapto";
0108:
0109: /** Constant for the "message" appinfo attribute name. */
0110: public static final String APPINFO_ATTR_MESSAGE = "message";
0111:
0112: /** Constant for the "name" appinfo attribute name. */
0113: public static final String APPINFO_ATTR_NAME = "name";
0114:
0115: /** Constant for the "regex" appinfo attribute name. */
0116: public static final String APPINFO_ATTR_REGEX = "regex";
0117:
0118: /** Constant for the "searchcontent" appinfo attribute name. */
0119: public static final String APPINFO_ATTR_SEARCHCONTENT = "searchcontent";
0120:
0121: /** Constant for the "type" appinfo attribute name. */
0122: public static final String APPINFO_ATTR_TYPE = "type";
0123:
0124: /** Constant for the "node" appinfo attribute value. */
0125: public static final String APPINFO_ATTR_TYPE_NODE = "node";
0126:
0127: /** Constant for the "parent" appinfo attribute value. */
0128: public static final String APPINFO_ATTR_TYPE_PARENT = "parent";
0129:
0130: /** Constant for the "warning" appinfo attribute value. */
0131: public static final String APPINFO_ATTR_TYPE_WARNING = "warning";
0132:
0133: /** Constant for the "uri" appinfo attribute name. */
0134: public static final String APPINFO_ATTR_URI = "uri";
0135:
0136: /** Constant for the "value" appinfo attribute name. */
0137: public static final String APPINFO_ATTR_VALUE = "value";
0138:
0139: /** Constant for the "widget" appinfo attribute name. */
0140: public static final String APPINFO_ATTR_WIDGET = "widget";
0141:
0142: /** Constant for the "default" appinfo element name. */
0143: public static final String APPINFO_DEFAULT = "default";
0144:
0145: /** Constant for the "defaults" appinfo element name. */
0146: public static final String APPINFO_DEFAULTS = "defaults";
0147:
0148: /** Constant for the "layout" appinfo element name. */
0149: public static final String APPINFO_LAYOUT = "layout";
0150:
0151: /** Constant for the "layouts" appinfo element name. */
0152: public static final String APPINFO_LAYOUTS = "layouts";
0153:
0154: /** Constant for the "mapping" appinfo element name. */
0155: public static final String APPINFO_MAPPING = "mapping";
0156:
0157: /** Constant for the "mappings" appinfo element name. */
0158: public static final String APPINFO_MAPPINGS = "mappings";
0159:
0160: /** Constant for the "modelfolder" appinfo element name. */
0161: public static final String APPINFO_MODELFOLDER = "modelfolder";
0162:
0163: /** Constant for the "preview" appinfo element name. */
0164: public static final String APPINFO_PREVIEW = "preview";
0165:
0166: /** Constant for the "relation" appinfo element name. */
0167: public static final String APPINFO_RELATION = "relation";
0168:
0169: /** Constant for the "relations" appinfo element name. */
0170: public static final String APPINFO_RELATIONS = "relations";
0171:
0172: /** Constant for the "searchexclusions" appinfo element name. */
0173: public static final String APPINFO_RESOURCEBUNDLE = "resourcebundle";
0174:
0175: /** Constant for the "rule" appinfo element name. */
0176: public static final String APPINFO_RULE = "rule";
0177:
0178: /** The file where the default appinfo schema is located. */
0179: public static final String APPINFO_SCHEMA_FILE = "org/opencms/xml/content/DefaultAppinfo.xsd";
0180:
0181: /** The file where the default appinfo schema types are located. */
0182: public static final String APPINFO_SCHEMA_FILE_TYPES = "org/opencms/xml/content/DefaultAppinfoTypes.xsd";
0183:
0184: /** The XML system id for the default appinfo schema types. */
0185: public static final String APPINFO_SCHEMA_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX
0186: + APPINFO_SCHEMA_FILE;
0187:
0188: /** The XML system id for the default appinfo schema types. */
0189: public static final String APPINFO_SCHEMA_TYPES_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX
0190: + APPINFO_SCHEMA_FILE_TYPES;
0191:
0192: /** Constant for the "searchsetting" appinfo element name. */
0193: public static final String APPINFO_SEARCHSETTING = "searchsetting";
0194:
0195: /** Constant for the "searchsettings" appinfo element name. */
0196: public static final String APPINFO_SEARCHSETTINGS = "searchsettings";
0197:
0198: /** Constant for the "validationrule" appinfo element name. */
0199: public static final String APPINFO_VALIDATIONRULE = "validationrule";
0200:
0201: /** Constant for the "validationrules" appinfo element name. */
0202: public static final String APPINFO_VALIDATIONRULES = "validationrules";
0203:
0204: /** Macro for resolving the preview URI. */
0205: public static final String MACRO_PREVIEW_TEMPFILE = "previewtempfile";
0206:
0207: /** Default message for validation errors. */
0208: protected static final String MESSAGE_VALIDATION_DEFAULT_ERROR = "${validation.path}: "
0209: + "${key."
0210: + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_ERROR_2
0211: + "|${validation.value}|[${validation.regex}]}";
0212:
0213: /** Default message for validation warnings. */
0214: protected static final String MESSAGE_VALIDATION_DEFAULT_WARNING = "${validation.path}: "
0215: + "${key."
0216: + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_WARNING_2
0217: + "|${validation.value}|[${validation.regex}]}";
0218:
0219: /** The log object for this class. */
0220: private static final Log LOG = CmsLog
0221: .getLog(CmsDefaultXmlContentHandler.class);
0222:
0223: /** Prefix to cache the checkrule relation types. */
0224: private static final String RELATION_TYPE_PREFIX = "rt_";
0225:
0226: /** The configuration values for the element widgets (as defined in the annotations). */
0227: protected Map m_configurationValues;
0228:
0229: /** The default values for the elements (as defined in the annotations). */
0230: protected Map m_defaultValues;
0231:
0232: /** The element mappings (as defined in the annotations). */
0233: protected Map m_elementMappings;
0234:
0235: /** The widgets used for the elements (as defined in the annotations). */
0236: protected Map m_elementWidgets;
0237:
0238: /** The resource bundle name to be used for localization of this content handler. */
0239: protected String m_messageBundleName;
0240:
0241: /** The folder containing the model file(s) for the content. */
0242: protected String m_modelFolder;
0243:
0244: /** The preview location (as defined in the annotations). */
0245: protected String m_previewLocation;
0246:
0247: /** The relation check rules. */
0248: protected Map m_relations;
0249:
0250: /** The search settings. */
0251: protected Map m_searchSettings;
0252:
0253: /** The messages for the error validation rules. */
0254: protected Map m_validationErrorMessages;
0255:
0256: /** The validation rules that cause an error (as defined in the annotations). */
0257: protected Map m_validationErrorRules;
0258:
0259: /** The messages for the warning validation rules. */
0260: protected Map m_validationWarningMessages;
0261:
0262: /** The validation rules that cause a warning (as defined in the annotations). */
0263: protected Map m_validationWarningRules;
0264:
0265: /**
0266: * Creates a new instance of the default XML content handler.<p>
0267: */
0268: public CmsDefaultXmlContentHandler() {
0269:
0270: init();
0271: }
0272:
0273: /**
0274: * Static initializer for caching the default appinfo validation schema.<p>
0275: */
0276: static {
0277:
0278: // the schema definition is located in 2 separates file for easier editing
0279: // 2 files are required in case an extended schema want to use the default definitions,
0280: // but with an extended "appinfo" node
0281: byte[] appinfoSchemaTypes;
0282: try {
0283: // first read the default types
0284: appinfoSchemaTypes = CmsFileUtil
0285: .readFile(APPINFO_SCHEMA_FILE_TYPES);
0286: } catch (Exception e) {
0287: throw new CmsRuntimeException(
0288: Messages
0289: .get()
0290: .container(
0291: org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1,
0292: APPINFO_SCHEMA_FILE_TYPES), e);
0293: }
0294: CmsXmlEntityResolver.cacheSystemId(
0295: APPINFO_SCHEMA_TYPES_SYSTEM_ID, appinfoSchemaTypes);
0296: byte[] appinfoSchema;
0297: try {
0298: // now read the default base schema
0299: appinfoSchema = CmsFileUtil.readFile(APPINFO_SCHEMA_FILE);
0300: } catch (Exception e) {
0301: throw new CmsRuntimeException(
0302: Messages
0303: .get()
0304: .container(
0305: org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1,
0306: APPINFO_SCHEMA_FILE), e);
0307: }
0308: CmsXmlEntityResolver.cacheSystemId(APPINFO_SCHEMA_SYSTEM_ID,
0309: appinfoSchema);
0310: }
0311:
0312: /**
0313: * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguration(org.opencms.xml.types.I_CmsXmlSchemaType)
0314: */
0315: public String getConfiguration(I_CmsXmlSchemaType type) {
0316:
0317: String elementName = type.getName();
0318: return (String) m_configurationValues.get(elementName);
0319: }
0320:
0321: /**
0322: * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefault(org.opencms.file.CmsObject, I_CmsXmlContentValue, java.util.Locale)
0323: */
0324: public String getDefault(CmsObject cms, I_CmsXmlContentValue value,
0325: Locale locale) {
0326:
0327: String defaultValue;
0328: if (value.getElement() == null) {
0329: // use the "getDefault" method of the given value, will use value from standard XML schema
0330: defaultValue = value.getDefault(locale);
0331: } else {
0332: String xpath = value.getPath();
0333: // look up the default from the configured mappings
0334: defaultValue = (String) m_defaultValues.get(xpath);
0335: if (defaultValue == null) {
0336: // no value found, try default xpath
0337: xpath = CmsXmlUtils.removeXpath(xpath);
0338: xpath = CmsXmlUtils.createXpath(xpath, 1);
0339: // look up the default value again with default index of 1 in all path elements
0340: defaultValue = (String) m_defaultValues.get(xpath);
0341: }
0342: }
0343: if (defaultValue != null) {
0344: CmsObject newCms = cms;
0345: try {
0346: // switch the current URI to the XML document resource so that properties can be read
0347: CmsResource file = value.getDocument().getFile();
0348: CmsSite site = OpenCms.getSiteManager()
0349: .getSiteForRootPath(file.getRootPath());
0350: if (site != null) {
0351: newCms = OpenCms.initCmsObject(cms);
0352: newCms.getRequestContext().setSiteRoot(
0353: site.getSiteRoot());
0354: newCms.getRequestContext().setUri(
0355: newCms.getSitePath(file));
0356: }
0357: } catch (Exception e) {
0358: // on any error just use the default input OpenCms context
0359: }
0360: // return the default value with processed macros
0361: CmsMacroResolver resolver = CmsMacroResolver.newInstance()
0362: .setCmsObject(newCms).setMessages(
0363: getMessages(locale));
0364: return resolver.resolveMacros(defaultValue);
0365: }
0366: // no default value is available
0367: return null;
0368: }
0369:
0370: /**
0371: * Returns the first mapping defined for the given element xpath.<p>
0372: *
0373: * Since OpenCms version 7.0.2, multiple mappings for an element are possible, so
0374: * use {@link #getMapping(String)} instead.<p>
0375: *
0376: * @param elementName the element xpath to look up the mapping for
0377: *
0378: * @return the mapping defined for the given element xpath
0379: *
0380: * @deprecated use {@link #getMappings(String)} instead to recieve all mappings
0381: */
0382: public String getMapping(String elementName) {
0383:
0384: String[] mappings = getMappings(elementName);
0385: return (mappings == null) ? null : mappings[0];
0386: }
0387:
0388: /**
0389: * Returns the all mappings defined for the given element xpath.<p>
0390: *
0391: * @since 7.0.2
0392: *
0393: * @param elementName the element xpath to look up the mapping for
0394: *
0395: * @return the mapping defined for the given element xpath
0396: */
0397: public String[] getMappings(String elementName) {
0398:
0399: return (String[]) m_elementMappings.get(elementName);
0400: }
0401:
0402: /**
0403: * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMessages(java.util.Locale)
0404: */
0405: public CmsMessages getMessages(Locale locale) {
0406:
0407: if (m_messageBundleName == null) {
0408: // no message bundle was initialized
0409: return null;
0410: }
0411:
0412: return new CmsMessages(m_messageBundleName, locale);
0413: }
0414:
0415: /**
0416: * @see org.opencms.xml.content.I_CmsXmlContentHandler#getModelFolder(org.opencms.file.CmsObject, java.lang.String)
0417: */
0418: public String getModelFolder(CmsObject cms, String currentFolder) {
0419:
0420: String result = m_modelFolder;
0421: // store the original uri
0422: String uri = cms.getRequestContext().getUri();
0423: try {
0424: // set uri to current folder
0425: cms.getRequestContext().setUri(currentFolder);
0426: CmsMacroResolver resolver = CmsMacroResolver.newInstance()
0427: .setCmsObject(cms);
0428: // resolve eventual macros
0429: result = resolver.resolveMacros(m_modelFolder);
0430: } finally {
0431: // switch back to stored uri
0432: cms.getRequestContext().setUri(uri);
0433: }
0434: return result;
0435: }
0436:
0437: /**
0438: * @see org.opencms.xml.content.I_CmsXmlContentHandler#getPreview(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, java.lang.String)
0439: */
0440: public String getPreview(CmsObject cms, CmsXmlContent content,
0441: String resourcename) {
0442:
0443: CmsMacroResolver resolver = CmsMacroResolver.newInstance()
0444: .setCmsObject(cms);
0445: resolver.addMacro(MACRO_PREVIEW_TEMPFILE, resourcename);
0446:
0447: return resolver.resolveMacros(m_previewLocation);
0448: }
0449:
0450: /**
0451: * @see I_CmsXmlContentHandler#getRelationType(I_CmsXmlContentValue)
0452: */
0453: public CmsRelationType getRelationType(I_CmsXmlContentValue value) {
0454:
0455: if (value == null) {
0456: return null;
0457: }
0458: String xpath = value.getPath();
0459: CmsRelationType relationType = null;
0460: // look up the default from the configured mappings
0461: relationType = (CmsRelationType) m_relations
0462: .get(RELATION_TYPE_PREFIX + xpath);
0463: if (relationType == null) {
0464: // no value found, try default xpath
0465: String path = CmsXmlUtils.removeXpathIndex(xpath);
0466: // look up the default value again without indexes
0467: relationType = (CmsRelationType) m_relations
0468: .get(RELATION_TYPE_PREFIX + path);
0469: }
0470: if (relationType == null) {
0471: // no value found, try the last simple type path
0472: String path = CmsXmlUtils.getLastXpathElement(xpath);
0473: // look up the default value again for the last simple type
0474: relationType = (CmsRelationType) m_relations
0475: .get(RELATION_TYPE_PREFIX + path);
0476: }
0477: if (relationType == null) {
0478: return CmsRelationType.XML_WEAK;
0479: }
0480: return relationType;
0481: }
0482:
0483: /**
0484: * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.xml.types.I_CmsXmlContentValue)
0485: */
0486: public I_CmsWidget getWidget(I_CmsXmlContentValue value) {
0487:
0488: // try the specific widget settings first
0489: I_CmsWidget result = (I_CmsWidget) m_elementWidgets.get(value
0490: .getName());
0491: if (result == null) {
0492: // use default widget mappings
0493: result = OpenCms.getXmlContentTypeManager()
0494: .getWidgetDefault(value.getTypeName());
0495: } else {
0496: result = result.newInstance();
0497: }
0498: // set the configuration value for this widget
0499: result.setConfiguration(getConfiguration(value));
0500:
0501: return result;
0502: }
0503:
0504: /**
0505: * @see org.opencms.xml.content.I_CmsXmlContentHandler#initialize(org.dom4j.Element, org.opencms.xml.CmsXmlContentDefinition)
0506: */
0507: public synchronized void initialize(Element appInfoElement,
0508: CmsXmlContentDefinition contentDefinition)
0509: throws CmsXmlException {
0510:
0511: if (appInfoElement != null) {
0512: // validate the appinfo element XML content with the default appinfo handler schema
0513: validateAppinfoElement(appInfoElement);
0514:
0515: // re-initialize the local variables
0516: init();
0517:
0518: Iterator i = appInfoElement.elements().iterator();
0519: while (i.hasNext()) {
0520: // iterate all elements in the appinfo node
0521: Element element = (Element) i.next();
0522: String nodeName = element.getName();
0523: if (nodeName.equals(APPINFO_MAPPINGS)) {
0524: initMappings(element, contentDefinition);
0525: } else if (nodeName.equals(APPINFO_LAYOUTS)) {
0526: initLayouts(element, contentDefinition);
0527: } else if (nodeName.equals(APPINFO_VALIDATIONRULES)) {
0528: initValidationRules(element, contentDefinition);
0529: } else if (nodeName.equals(APPINFO_RELATIONS)) {
0530: initRelations(element, contentDefinition);
0531: } else if (nodeName.equals(APPINFO_DEFAULTS)) {
0532: initDefaultValues(element, contentDefinition);
0533: } else if (nodeName.equals(APPINFO_MODELFOLDER)) {
0534: initModelFolder(element, contentDefinition);
0535: } else if (nodeName.equals(APPINFO_PREVIEW)) {
0536: initPreview(element, contentDefinition);
0537: } else if (nodeName.equals(APPINFO_RESOURCEBUNDLE)) {
0538: initResourceBundle(element, contentDefinition);
0539: } else if (nodeName.equals(APPINFO_SEARCHSETTINGS)) {
0540: initSearchSettings(element, contentDefinition);
0541: }
0542: }
0543: }
0544:
0545: // at the end, add default check rules for optional file references
0546: addDefaultCheckRules(contentDefinition, null, null);
0547: }
0548:
0549: /**
0550: * @see org.opencms.xml.content.I_CmsXmlContentHandler#invalidateBrokenLinks(CmsObject, CmsXmlContent)
0551: */
0552: public void invalidateBrokenLinks(CmsObject cms,
0553: CmsXmlContent document) {
0554:
0555: if ((cms == null)
0556: || (cms.getRequestContext().getRequestTime() == CmsResource.DATE_RELEASED_EXPIRED_IGNORE)) {
0557: // do not check if the request comes the editor
0558: return;
0559: }
0560: boolean needReinitialization = false;
0561: // iterate the locales
0562: Iterator itLocales = document.getLocales().iterator();
0563: while (itLocales.hasNext()) {
0564: Locale locale = (Locale) itLocales.next();
0565: List removedNodes = new ArrayList();
0566: // iterate the values
0567: Iterator itValues = document.getValues(locale).iterator();
0568: while (itValues.hasNext()) {
0569: I_CmsXmlContentValue value = (I_CmsXmlContentValue) itValues
0570: .next();
0571: String path = value.getPath();
0572: // check if this value has already been deleted by parent rules
0573: boolean alreadyRemoved = false;
0574: Iterator itRemNodes = removedNodes.iterator();
0575: while (itRemNodes.hasNext()) {
0576: String remNode = (String) itRemNodes.next();
0577: if (path.startsWith(remNode)) {
0578: alreadyRemoved = true;
0579: break;
0580: }
0581: }
0582: // only continue if not already removed and if a rule match
0583: if (alreadyRemoved
0584: || ((m_relations.get(path) == null) && (m_relations
0585: .get(CmsXmlUtils.removeXpath(path)) == null))) {
0586: continue;
0587: }
0588:
0589: // check rule matched
0590: if (LOG.isDebugEnabled()) {
0591: LOG.debug(Messages.get().getBundle().key(
0592: Messages.LOG_XMLCONTENT_CHECK_RULE_MATCH_1,
0593: path));
0594: }
0595: if (validateLink(cms, value, null)) {
0596: // invalid link
0597: if (LOG.isDebugEnabled()) {
0598: LOG
0599: .debug(Messages
0600: .get()
0601: .getBundle()
0602: .key(
0603: Messages.LOG_XMLCONTENT_CHECK_WARNING_2,
0604: path,
0605: value
0606: .getStringValue(cms)));
0607: }
0608: // find the node to remove
0609: String parentPath = path;
0610: while (isInvalidateParent(parentPath)) {
0611: // check parent
0612: parentPath = CmsXmlUtils
0613: .removeLastXpathElement(parentPath);
0614: // log info
0615: if (LOG.isDebugEnabled()) {
0616: LOG
0617: .debug(Messages
0618: .get()
0619: .getBundle()
0620: .key(
0621: Messages.LOG_XMLCONTENT_CHECK_PARENT_2,
0622: path, parentPath));
0623: }
0624: }
0625: value = document.getValue(parentPath, locale);
0626: // detach the value node from the XML document
0627: value.getElement().detach();
0628: // mark node as deleted
0629: removedNodes.add(parentPath);
0630: }
0631: }
0632: if (!removedNodes.isEmpty()) {
0633: needReinitialization = true;
0634: }
0635: }
0636: if (needReinitialization) {
0637: // re-initialize the XML content
0638: document.initDocument();
0639: }
0640: }
0641:
0642: /**
0643: * @see org.opencms.xml.content.I_CmsXmlContentHandler#isSearchable(org.opencms.xml.types.I_CmsXmlContentValue)
0644: */
0645: public boolean isSearchable(I_CmsXmlContentValue value) {
0646:
0647: // check for name configured in the annotations
0648: Boolean anno = (Boolean) m_searchSettings.get(value.getName());
0649: // if no annotation has been found, use default for value
0650: return (anno == null) ? value.isSearchable() : anno
0651: .booleanValue();
0652: }
0653:
0654: /**
0655: * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForUse(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent)
0656: */
0657: public CmsXmlContent prepareForUse(CmsObject cms,
0658: CmsXmlContent content) {
0659:
0660: // NOOP, just return the unmodified content
0661: return content;
0662: }
0663:
0664: /**
0665: * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForWrite(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.file.CmsFile)
0666: */
0667: public CmsFile prepareForWrite(CmsObject cms,
0668: CmsXmlContent content, CmsFile file) throws CmsException {
0669:
0670: if (!content.isAutoCorrectionEnabled()) {
0671: // check if the XML should be corrected automatically (if not already set)
0672: Object attribute = cms.getRequestContext().getAttribute(
0673: CmsXmlContent.AUTO_CORRECTION_ATTRIBUTE);
0674: // set the auto correction mode as required
0675: boolean autoCorrectionEnabled = (attribute != null)
0676: && ((Boolean) attribute).booleanValue();
0677: content.setAutoCorrectionEnabled(autoCorrectionEnabled);
0678: }
0679: // validate the XML structure before writing the file if required
0680: if (!content.isAutoCorrectionEnabled()) {
0681: // an exception will be thrown if the structure is invalid
0682: content.validateXmlStructure(new CmsXmlEntityResolver(cms));
0683: }
0684: // read the content-conversion property
0685: String contentConversion = CmsHtmlConverter
0686: .getConversionSettings(cms, file);
0687: if (CmsStringUtil.isEmptyOrWhitespaceOnly(contentConversion)) {
0688: // enable pretty printing and XHTML conversion of XML content html fields by default
0689: contentConversion = CmsHtmlConverter.PARAM_XHTML;
0690: }
0691: content.setConversion(contentConversion);
0692: // correct the HTML structure
0693: file = content.correctXmlStructure(cms);
0694: content.setFile(file);
0695: // resolve the file mappings
0696: content.resolveMappings(cms);
0697: // ensure all property mappings of deleted optional values are removed
0698: removeEmptyMappings(cms, content);
0699: // return the result
0700: return file;
0701: }
0702:
0703: /**
0704: * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.types.I_CmsXmlContentValue)
0705: */
0706: public void resolveMapping(CmsObject cms, CmsXmlContent content,
0707: I_CmsXmlContentValue value) throws CmsException {
0708:
0709: if (!value.isSimpleType()) {
0710: // no mappings for a nested schema are possible
0711: // note that the sub-elements of the nested schema ARE mapped by the node visitor,
0712: // it's just the nested schema value itself that does not support mapping
0713: return;
0714: }
0715:
0716: // get the original VFS file from the content
0717: CmsFile file = content.getFile();
0718: if (file == null) {
0719: throw new CmsXmlException(Messages.get().container(
0720: Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0));
0721: }
0722:
0723: // get the mappings for the element name
0724: String[] mappings = getMappings(value.getPath());
0725: if (mappings == null) {
0726: // nothing to do if we have no mappings at all
0727: return;
0728: }
0729: // create OpenCms user context initialized with "/" as site root to read all siblings
0730: CmsObject rootCms = OpenCms.initCmsObject(cms);
0731: rootCms.getRequestContext().setSiteRoot("/");
0732: // read all siblings of the file
0733: List siblings = rootCms.readSiblings(content.getFile()
0734: .getRootPath(), CmsResourceFilter.IGNORE_EXPIRATION);
0735:
0736: // since 7.0.2 multiple mappings are possible
0737: for (int m = mappings.length - 1; m >= 0; m--) {
0738:
0739: // for multiple language mappings, we need to ensure
0740: // a) all siblings are handled
0741: // b) only the "right" locale is mapped to a sibling
0742: String mapping = mappings[m];
0743: if (CmsStringUtil.isNotEmpty(mapping)) {
0744: for (int i = (siblings.size() - 1); i >= 0; i--) {
0745: // get filename
0746: String filename = ((CmsResource) siblings.get(i))
0747: .getRootPath();
0748: Locale locale = OpenCms.getLocaleManager()
0749: .getDefaultLocale(rootCms, filename);
0750:
0751: if (!locale.equals(value.getLocale())) {
0752: // only map property if the locale fits
0753: continue;
0754: }
0755:
0756: // make sure the file is locked
0757: CmsLock lock = rootCms.getLock(filename);
0758: if (lock.isUnlocked()) {
0759: rootCms.lockResource(filename);
0760: } else if (!lock.isExclusiveOwnedBy(rootCms
0761: .getRequestContext().currentUser())) {
0762: rootCms.changeLock(filename);
0763: }
0764:
0765: // get the string value of the current node
0766: String stringValue = value.getStringValue(rootCms);
0767: if (mapping.startsWith(MAPTO_PROPERTY_LIST)
0768: && (value.getIndex() == 0)) {
0769:
0770: boolean mapToShared;
0771: int prefixLength;
0772: // check which mapping is used (shared or individual)
0773: if (mapping
0774: .startsWith(MAPTO_PROPERTY_LIST_SHARED)) {
0775: mapToShared = true;
0776: prefixLength = MAPTO_PROPERTY_LIST_SHARED
0777: .length();
0778: } else if (mapping
0779: .startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) {
0780: mapToShared = false;
0781: prefixLength = MAPTO_PROPERTY_LIST_INDIVIDUAL
0782: .length();
0783: } else {
0784: mapToShared = false;
0785: prefixLength = MAPTO_PROPERTY_LIST.length();
0786: }
0787:
0788: // this is a property list mapping
0789: String property = mapping
0790: .substring(prefixLength);
0791:
0792: String path = CmsXmlUtils
0793: .removeXpathIndex(value.getPath());
0794: List values = content.getValues(path, locale);
0795: Iterator j = values.iterator();
0796: StringBuffer result = new StringBuffer(values
0797: .size() * 64);
0798: while (j.hasNext()) {
0799: I_CmsXmlContentValue val = (I_CmsXmlContentValue) j
0800: .next();
0801: result.append(val.getStringValue(rootCms));
0802: if (j.hasNext()) {
0803: result
0804: .append(CmsProperty.VALUE_LIST_DELIMITER);
0805: }
0806: }
0807:
0808: CmsProperty p;
0809: if (mapToShared) {
0810: // map to shared value
0811: p = new CmsProperty(property, null, result
0812: .toString());
0813: } else {
0814: // map to individual value
0815: p = new CmsProperty(property, result
0816: .toString(), null);
0817: }
0818: // write the created list string value in the selected property
0819: rootCms.writePropertyObject(filename, p);
0820: if (mapToShared) {
0821: // special case: shared mappings must be written only to one sibling, end loop
0822: i = 0;
0823: }
0824:
0825: } else if (mapping.startsWith(MAPTO_PROPERTY)) {
0826:
0827: boolean mapToShared;
0828: int prefixLength;
0829: // check which mapping is used (shared or individual)
0830: if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) {
0831: mapToShared = true;
0832: prefixLength = MAPTO_PROPERTY_SHARED
0833: .length();
0834: } else if (mapping
0835: .startsWith(MAPTO_PROPERTY_INDIVIDUAL)) {
0836: mapToShared = false;
0837: prefixLength = MAPTO_PROPERTY_INDIVIDUAL
0838: .length();
0839: } else {
0840: mapToShared = false;
0841: prefixLength = MAPTO_PROPERTY.length();
0842: }
0843:
0844: // this is a property mapping
0845: String property = mapping
0846: .substring(prefixLength);
0847:
0848: CmsProperty p;
0849: if (mapToShared) {
0850: // map to shared value
0851: p = new CmsProperty(property, null,
0852: stringValue);
0853: } else {
0854: // map to individual value
0855: p = new CmsProperty(property, stringValue,
0856: null);
0857: }
0858: // just store the string value in the selected property
0859: rootCms.writePropertyObject(filename, p);
0860: if (mapToShared) {
0861: // special case: shared mappings must be written only to one sibling, end loop
0862: i = 0;
0863: }
0864:
0865: } else if (mapping.startsWith(MAPTO_ATTRIBUTE)) {
0866:
0867: // this is an attribute mapping
0868: String attribute = mapping
0869: .substring(MAPTO_ATTRIBUTE.length());
0870: switch (ATTRIBUTES.indexOf(attribute)) {
0871: case 0: // date released
0872: long date = 0;
0873: try {
0874: date = Long.valueOf(stringValue)
0875: .longValue();
0876: } catch (NumberFormatException e) {
0877: // ignore, value can be a macro
0878: }
0879: if (date == 0) {
0880: date = CmsResource.DATE_RELEASED_DEFAULT;
0881: }
0882: // set the sibling release date
0883: rootCms.setDateReleased(filename, date,
0884: false);
0885: // set current file release date
0886: if (filename.equals(rootCms
0887: .getSitePath(file))) {
0888: file.setDateReleased(date);
0889: }
0890: break;
0891: case 1: // date expired
0892: date = 0;
0893: try {
0894: date = Long.valueOf(stringValue)
0895: .longValue();
0896: } catch (NumberFormatException e) {
0897: // ignore, value can be a macro
0898: }
0899: if (date == 0) {
0900: date = CmsResource.DATE_EXPIRED_DEFAULT;
0901: }
0902: // set the sibling expired date
0903: rootCms.setDateExpired(filename, date,
0904: false);
0905: // set current file expired date
0906: if (filename.equals(rootCms
0907: .getSitePath(file))) {
0908: file.setDateExpired(date);
0909: }
0910: break;
0911: default:
0912: // ignore invalid / other mappings
0913: }
0914: }
0915: }
0916: }
0917: }
0918: // make sure the original is locked
0919: CmsLock lock = rootCms.getLock(file);
0920: if (lock.isUnlocked()) {
0921: rootCms.lockResource(file.getRootPath());
0922: } else if (!lock.isExclusiveOwnedBy(rootCms.getRequestContext()
0923: .currentUser())) {
0924: rootCms.changeLock(file.getRootPath());
0925: }
0926: }
0927:
0928: /**
0929: * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveValidation(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlContentValue, org.opencms.xml.content.CmsXmlContentErrorHandler)
0930: */
0931: public CmsXmlContentErrorHandler resolveValidation(CmsObject cms,
0932: I_CmsXmlContentValue value,
0933: CmsXmlContentErrorHandler errorHandler) {
0934:
0935: if (errorHandler == null) {
0936: // init a new error handler if required
0937: errorHandler = new CmsXmlContentErrorHandler();
0938: }
0939:
0940: if (!value.isSimpleType()) {
0941: // no validation for a nested schema is possible
0942: // note that the sub-elements of the nested schema ARE validated by the node visitor,
0943: // it's just the nested schema value itself that does not support validation
0944: return errorHandler;
0945: }
0946:
0947: // validate the error rules
0948: errorHandler = validateValue(cms, value, errorHandler,
0949: m_validationErrorRules, false);
0950: // validate the warning rules
0951: errorHandler = validateValue(cms, value, errorHandler,
0952: m_validationWarningRules, true);
0953:
0954: // return the result
0955: return errorHandler;
0956: }
0957:
0958: /**
0959: * Adds a check rule for a specified element.<p>
0960: *
0961: * @param contentDefinition the XML content definition this XML content handler belongs to
0962: * @param elementName the element name to add the rule to
0963: * @param invalidate
0964: * <code>false</code>, to disable link check
0965: * <code>true</code> or <code>node</code>, to invalidate just the single node if the link is broken
0966: * <code>parent</code>, if this rule will invalidate the whole parent node in nested content
0967: * @param type the relation type
0968: *
0969: * @throws CmsXmlException in case an unknown element name is used
0970: */
0971: protected void addCheckRule(
0972: CmsXmlContentDefinition contentDefinition,
0973: String elementName, String invalidate, String type)
0974: throws CmsXmlException {
0975:
0976: I_CmsXmlSchemaType schemaType = contentDefinition
0977: .getSchemaType(elementName);
0978: if (schemaType == null) {
0979: // no element with the given name
0980: throw new CmsXmlException(Messages.get().container(
0981: Messages.ERR_XMLCONTENT_CHECK_INVALID_ELEM_1,
0982: elementName));
0983: }
0984: if (!CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType
0985: .getTypeName())
0986: && !CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType
0987: .getTypeName())) {
0988: // element is not a OpenCmsVfsFile
0989: throw new CmsXmlException(Messages.get().container(
0990: Messages.ERR_XMLCONTENT_CHECK_INVALID_TYPE_1,
0991: elementName));
0992: }
0993:
0994: // cache the check rule data
0995: Boolean invalidateParent = null;
0996: if ((invalidate == null)
0997: || invalidate.equalsIgnoreCase(Boolean.TRUE.toString())
0998: || invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_NODE)) {
0999: invalidateParent = Boolean.FALSE;
1000: } else if (invalidate
1001: .equalsIgnoreCase(APPINFO_ATTR_TYPE_PARENT)) {
1002: invalidateParent = Boolean.TRUE;
1003: }
1004: if (invalidateParent != null) {
1005: m_relations.put(elementName, invalidateParent);
1006: }
1007: CmsRelationType relationType = (type == null ? CmsRelationType.XML_WEAK
1008: : CmsRelationType.valueOfXml(type));
1009: m_relations.put(RELATION_TYPE_PREFIX + elementName,
1010: relationType);
1011:
1012: if (invalidateParent != null) {
1013: // check the whole xpath hierarchy
1014: String path = elementName;
1015: while (CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)) {
1016: if (!isInvalidateParent(path)) {
1017: // if invalidate type = node, then the node needs to be optional
1018: if (contentDefinition.getSchemaType(path)
1019: .getMinOccurs() > 0) {
1020: // element is not optional
1021: throw new CmsXmlException(
1022: Messages
1023: .get()
1024: .container(
1025: Messages.ERR_XMLCONTENT_CHECK_NOT_OPTIONAL_1,
1026: path));
1027: }
1028: // no need to further check
1029: break;
1030: } else if (!CmsXmlUtils.isDeepXpath(path)) {
1031: // if invalidate type = parent, then the node needs to be nested
1032: // document root can not be invalidated
1033: throw new CmsXmlException(
1034: Messages
1035: .get()
1036: .container(
1037: Messages.ERR_XMLCONTENT_CHECK_NOT_EMPTY_DOC_0));
1038: }
1039: path = CmsXmlUtils.removeLastXpathElement(path);
1040: }
1041: }
1042: }
1043:
1044: /**
1045: * Adds a configuration value for an element widget.<p>
1046: *
1047: * @param contentDefinition the XML content definition this XML content handler belongs to
1048: * @param elementName the element name to map
1049: * @param configurationValue the configuration value to use
1050: *
1051: * @throws CmsXmlException in case an unknown element name is used
1052: */
1053: protected void addConfiguration(
1054: CmsXmlContentDefinition contentDefinition,
1055: String elementName, String configurationValue)
1056: throws CmsXmlException {
1057:
1058: if (contentDefinition.getSchemaType(elementName) == null) {
1059: throw new CmsXmlException(Messages.get().container(
1060: Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1,
1061: elementName));
1062: }
1063:
1064: m_configurationValues.put(elementName, configurationValue);
1065: }
1066:
1067: /**
1068: * Adds a default value for an element.<p>
1069: *
1070: * @param contentDefinition the XML content definition this XML content handler belongs to
1071: * @param elementName the element name to map
1072: * @param defaultValue the default value to use
1073: *
1074: * @throws CmsXmlException in case an unknown element name is used
1075: */
1076: protected void addDefault(
1077: CmsXmlContentDefinition contentDefinition,
1078: String elementName, String defaultValue)
1079: throws CmsXmlException {
1080:
1081: if (contentDefinition.getSchemaType(elementName) == null) {
1082: throw new CmsXmlException(
1083: org.opencms.xml.types.Messages
1084: .get()
1085: .container(
1086: Messages.ERR_XMLCONTENT_INVALID_ELEM_DEFAULT_1,
1087: elementName));
1088: }
1089: // store mappings as xpath to allow better control about what is mapped
1090: String xpath = CmsXmlUtils.createXpath(elementName, 1);
1091: m_defaultValues.put(xpath, defaultValue);
1092: }
1093:
1094: /**
1095: * Adds all needed default check rules recursively for the given schema type.<p>
1096: *
1097: * @param rootContentDefinition the root content definition
1098: * @param schemaType the schema type to check
1099: * @param elementPath the current element path
1100: *
1101: * @throws CmsXmlException if something goes wrong
1102: */
1103: protected void addDefaultCheckRules(
1104: CmsXmlContentDefinition rootContentDefinition,
1105: I_CmsXmlSchemaType schemaType, String elementPath)
1106: throws CmsXmlException {
1107:
1108: if ((schemaType != null) && schemaType.isSimpleType()) {
1109: if ((schemaType.getMinOccurs() == 0)
1110: && (CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType
1111: .getTypeName()) || CmsXmlVarLinkValue.TYPE_NAME
1112: .equals(schemaType.getTypeName()))
1113: && !m_relations.containsKey(elementPath)
1114: && !m_relations.containsKey(RELATION_TYPE_PREFIX
1115: + elementPath)) {
1116: // add default check rule for the element
1117: addCheckRule(rootContentDefinition, elementPath, null,
1118: null);
1119: }
1120: } else {
1121: // recursion required
1122: CmsXmlContentDefinition nestedContentDefinition = rootContentDefinition;
1123: if (schemaType != null) {
1124: CmsXmlNestedContentDefinition nestedDefinition = (CmsXmlNestedContentDefinition) schemaType;
1125: nestedContentDefinition = nestedDefinition
1126: .getNestedContentDefinition();
1127: }
1128: Iterator itElems = nestedContentDefinition.getSchemaTypes()
1129: .iterator();
1130: while (itElems.hasNext()) {
1131: String element = (String) itElems.next();
1132: String path = (schemaType != null) ? CmsXmlUtils
1133: .concatXpath(elementPath, element) : element;
1134: addDefaultCheckRules(rootContentDefinition,
1135: nestedContentDefinition.getSchemaType(element),
1136: path);
1137: }
1138: }
1139: }
1140:
1141: /**
1142: * Adds an element mapping.<p>
1143: *
1144: * @param contentDefinition the XML content definition this XML content handler belongs to
1145: * @param elementName the element name to map
1146: * @param mapping the mapping to use
1147: *
1148: * @throws CmsXmlException in case an unknown element name is used
1149: */
1150: protected void addMapping(
1151: CmsXmlContentDefinition contentDefinition,
1152: String elementName, String mapping) throws CmsXmlException {
1153:
1154: if (contentDefinition.getSchemaType(elementName) == null) {
1155: throw new CmsXmlException(Messages.get().container(
1156: Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1,
1157: elementName));
1158: }
1159:
1160: // store mappings as xpath to allow better control about what is mapped
1161: String xpath = CmsXmlUtils.createXpath(elementName, 1);
1162: // since 7.0.2 multiple mappings are possible, so the mappings are stored in an array
1163: String[] values = (String[]) m_elementMappings.get(xpath);
1164: if (values == null) {
1165: values = new String[] { mapping };
1166: } else {
1167: String[] newValues = new String[values.length + 1];
1168: System.arraycopy(values, 0, newValues, 0, values.length);
1169: newValues[values.length] = mapping;
1170: values = newValues;
1171: }
1172: m_elementMappings.put(xpath, values);
1173: }
1174:
1175: /**
1176: * Adds a search setting for an element.<p>
1177: *
1178: * @param contentDefinition the XML content definition this XML content handler belongs to
1179: * @param elementName the element name to map
1180: * @param value the search setting value to store
1181: *
1182: * @throws CmsXmlException in case an unknown element name is used
1183: */
1184: protected void addSearchSetting(
1185: CmsXmlContentDefinition contentDefinition,
1186: String elementName, Boolean value) throws CmsXmlException {
1187:
1188: if (contentDefinition.getSchemaType(elementName) == null) {
1189: throw new CmsXmlException(
1190: org.opencms.xml.types.Messages
1191: .get()
1192: .container(
1193: Messages.ERR_XMLCONTENT_INVALID_ELEM_SEARCHSETTINGS_1,
1194: elementName));
1195: }
1196: // store the search exclusion as defined
1197: m_searchSettings.put(elementName, value);
1198: }
1199:
1200: /**
1201: * Adds a validation rule for a specified element.<p>
1202: *
1203: * @param contentDefinition the XML content definition this XML content handler belongs to
1204: * @param elementName the element name to add the rule to
1205: * @param regex the validation rule regular expression
1206: * @param message the message in case validation fails (may be null)
1207: * @param isWarning if true, this rule is used for warnings, otherwise it's an error
1208: *
1209: * @throws CmsXmlException in case an unknown element name is used
1210: */
1211: protected void addValidationRule(
1212: CmsXmlContentDefinition contentDefinition,
1213: String elementName, String regex, String message,
1214: boolean isWarning) throws CmsXmlException {
1215:
1216: if (contentDefinition.getSchemaType(elementName) == null) {
1217: throw new CmsXmlException(Messages.get().container(
1218: Messages.ERR_XMLCONTENT_INVALID_ELEM_VALIDATION_1,
1219: elementName));
1220: }
1221:
1222: if (isWarning) {
1223: m_validationWarningRules.put(elementName, regex);
1224: if (message != null) {
1225: m_validationWarningMessages.put(elementName, message);
1226: }
1227: } else {
1228: m_validationErrorRules.put(elementName, regex);
1229: if (message != null) {
1230: m_validationErrorMessages.put(elementName, message);
1231: }
1232: }
1233: }
1234:
1235: /**
1236: * Adds a GUI widget for a specified element.<p>
1237: *
1238: * @param contentDefinition the XML content definition this XML content handler belongs to
1239: * @param elementName the element name to map
1240: * @param widgetClassOrAlias the widget to use as GUI for the element (registered alias or class name)
1241: *
1242: * @throws CmsXmlException in case an unknown element name is used
1243: */
1244: protected void addWidget(CmsXmlContentDefinition contentDefinition,
1245: String elementName, String widgetClassOrAlias)
1246: throws CmsXmlException {
1247:
1248: if (contentDefinition.getSchemaType(elementName) == null) {
1249: throw new CmsXmlException(
1250: Messages
1251: .get()
1252: .container(
1253: Messages.ERR_XMLCONTENT_INVALID_ELEM_LAYOUTWIDGET_1,
1254: elementName));
1255: }
1256:
1257: // get the base widget from the XML content type manager
1258: I_CmsWidget widget = OpenCms.getXmlContentTypeManager()
1259: .getWidget(widgetClassOrAlias);
1260:
1261: if (widget == null) {
1262: // no registered widget class found
1263: if (CmsStringUtil.isValidJavaClassName(widgetClassOrAlias)) {
1264: // java class name given, try to create new instance of the class and cast to widget
1265: try {
1266: Class specialWidgetClass = Class
1267: .forName(widgetClassOrAlias);
1268: widget = (I_CmsWidget) specialWidgetClass
1269: .newInstance();
1270: } catch (Exception e) {
1271: throw new CmsXmlException(
1272: Messages
1273: .get()
1274: .container(
1275: Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3,
1276: widgetClassOrAlias,
1277: elementName,
1278: contentDefinition
1279: .getSchemaLocation()),
1280: e);
1281: }
1282: }
1283: if (widget == null) {
1284: // no valid widget found
1285: throw new CmsXmlException(Messages.get().container(
1286: Messages.ERR_XMLCONTENT_INVALID_WIDGET_3,
1287: widgetClassOrAlias, elementName,
1288: contentDefinition.getSchemaLocation()));
1289: }
1290: }
1291: m_elementWidgets.put(elementName, widget);
1292: }
1293:
1294: /**
1295: * Returns the validation message to be displayed if a certain rule was violated.<p>
1296: *
1297: * @param cms the current users OpenCms context
1298: * @param value the value to validate
1299: * @param regex the rule that was violated
1300: * @param valueStr the string value of the given value
1301: * @param matchResult if false, the rule was negated
1302: * @param isWarning if true, this validation indicate a warning, otherwise an error
1303: *
1304: * @return the validation message to be displayed
1305: */
1306: protected String getValidationMessage(CmsObject cms,
1307: I_CmsXmlContentValue value, String regex, String valueStr,
1308: boolean matchResult, boolean isWarning) {
1309:
1310: String message = null;
1311: if (isWarning) {
1312: message = (String) m_validationWarningMessages.get(value
1313: .getName());
1314: } else {
1315: message = (String) m_validationErrorMessages.get(value
1316: .getName());
1317: }
1318:
1319: if (message == null) {
1320: if (isWarning) {
1321: message = MESSAGE_VALIDATION_DEFAULT_WARNING;
1322: } else {
1323: message = MESSAGE_VALIDATION_DEFAULT_ERROR;
1324: }
1325: }
1326:
1327: // create additional macro values
1328: Map additionalValues = new HashMap();
1329: additionalValues.put(CmsMacroResolver.KEY_VALIDATION_VALUE,
1330: valueStr);
1331: additionalValues.put(CmsMacroResolver.KEY_VALIDATION_REGEX,
1332: ((!matchResult) ? "!" : "") + regex);
1333: additionalValues.put(CmsMacroResolver.KEY_VALIDATION_PATH,
1334: value.getPath());
1335:
1336: CmsMacroResolver resolver = CmsMacroResolver
1337: .newInstance()
1338: .setCmsObject(cms)
1339: .setMessages(
1340: getMessages(cms.getRequestContext().getLocale()))
1341: .setAdditionalMacros(additionalValues);
1342:
1343: return resolver.resolveMacros(message);
1344: }
1345:
1346: /**
1347: * Called when this content handler is initialized.<p>
1348: */
1349: protected void init() {
1350:
1351: m_elementMappings = new HashMap();
1352: m_elementWidgets = new HashMap();
1353: m_validationErrorRules = new HashMap();
1354: m_validationErrorMessages = new HashMap();
1355: m_validationWarningRules = new HashMap();
1356: m_validationWarningMessages = new HashMap();
1357: m_defaultValues = new HashMap();
1358: m_configurationValues = new HashMap();
1359: m_searchSettings = new HashMap();
1360: m_relations = new HashMap();
1361: m_previewLocation = null;
1362: m_modelFolder = null;
1363: }
1364:
1365: /**
1366: * Initializes the default values for this content handler.<p>
1367: *
1368: * Using the default values from the appinfo node, it's possible to have more
1369: * sophisticated logic for generating the defaults then just using the XML schema "default"
1370: * attribute.<p>
1371: *
1372: * @param root the "defaults" element from the appinfo node of the XML content definition
1373: * @param contentDefinition the content definition the default values belong to
1374: * @throws CmsXmlException if something goes wrong
1375: */
1376: protected void initDefaultValues(Element root,
1377: CmsXmlContentDefinition contentDefinition)
1378: throws CmsXmlException {
1379:
1380: Iterator i = root.elementIterator(APPINFO_DEFAULT);
1381: while (i.hasNext()) {
1382: // iterate all "default" elements in the "defaults" node
1383: Element element = (Element) i.next();
1384: String elementName = element
1385: .attributeValue(APPINFO_ATTR_ELEMENT);
1386: String defaultValue = element
1387: .attributeValue(APPINFO_ATTR_VALUE);
1388: if ((elementName != null) && (defaultValue != null)) {
1389: // add a default value mapping for the element
1390: addDefault(contentDefinition, elementName, defaultValue);
1391: }
1392: }
1393: }
1394:
1395: /**
1396: * Initializes the layout for this content handler.<p>
1397: *
1398: * Unless otherwise instructed, the editor uses one specific GUI widget for each
1399: * XML value schema type. For example, for a {@link org.opencms.xml.types.CmsXmlStringValue}
1400: * the default widget is the {@link org.opencms.widgets.CmsInputWidget}.
1401: * However, certain values can also use more then one widget, for example you may
1402: * also use a {@link org.opencms.widgets.CmsCheckboxWidget} for a String value,
1403: * and as a result the Strings possible values would be eithe <code>"false"</code> or <code>"true"</code>,
1404: * but nevertheless be a String.<p>
1405: *
1406: * The widget to use can further be controlled using the <code>widget</code> attribute.
1407: * You can specify either a valid widget alias such as <code>StringWidget</code>,
1408: * or the name of a Java class that implements <code>{@link I_CmsWidget}</code>.<p>
1409: *
1410: * Configuration options to the widget can be passed using the <code>configuration</code>
1411: * attribute. You can specify any String as configuration. This String is then passed
1412: * to the widget during initialization. It's up to the individual widget implementation
1413: * to interpret this configuration String.<p>
1414: *
1415: * @param root the "layouts" element from the appinfo node of the XML content definition
1416: * @param contentDefinition the content definition the layout belongs to
1417: *
1418: * @throws CmsXmlException if something goes wrong
1419: */
1420: protected void initLayouts(Element root,
1421: CmsXmlContentDefinition contentDefinition)
1422: throws CmsXmlException {
1423:
1424: Iterator i = root.elementIterator(APPINFO_LAYOUT);
1425: while (i.hasNext()) {
1426: // iterate all "layout" elements in the "layouts" node
1427: Element element = (Element) i.next();
1428: String elementName = element
1429: .attributeValue(APPINFO_ATTR_ELEMENT);
1430: String widgetClassOrAlias = element
1431: .attributeValue(APPINFO_ATTR_WIDGET);
1432: String configuration = element
1433: .attributeValue(APPINFO_ATTR_CONFIGURATION);
1434: if ((elementName != null) && (widgetClassOrAlias != null)) {
1435: // add a widget mapping for the element
1436: addWidget(contentDefinition, elementName,
1437: widgetClassOrAlias);
1438: if (configuration != null) {
1439: addConfiguration(contentDefinition, elementName,
1440: configuration);
1441: }
1442: }
1443: }
1444: }
1445:
1446: /**
1447: * Initializes the element mappings for this content handler.<p>
1448: *
1449: * Element mappings allow storing values from the XML content in other locations.
1450: * For example, if you have an element called "Title", it's likely a good idea to
1451: * store the value of this element also in the "Title" property of a XML content resource.<p>
1452: *
1453: * @param root the "mappings" element from the appinfo node of the XML content definition
1454: * @param contentDefinition the content definition the mappings belong to
1455: * @throws CmsXmlException if something goes wrong
1456: */
1457: protected void initMappings(Element root,
1458: CmsXmlContentDefinition contentDefinition)
1459: throws CmsXmlException {
1460:
1461: Iterator i = root.elementIterator(APPINFO_MAPPING);
1462: while (i.hasNext()) {
1463: // iterate all "mapping" elements in the "mappings" node
1464: Element element = (Element) i.next();
1465: // this is a mapping node
1466: String elementName = element
1467: .attributeValue(APPINFO_ATTR_ELEMENT);
1468: String maptoName = element
1469: .attributeValue(APPINFO_ATTR_MAPTO);
1470: if ((elementName != null) && (maptoName != null)) {
1471: // add the element mapping
1472: addMapping(contentDefinition, elementName, maptoName);
1473: }
1474: }
1475: }
1476:
1477: /**
1478: * Initializes the folder containing the model file(s) for this content handler.<p>
1479: *
1480: * @param root the "modelfolder" element from the appinfo node of the XML content definition
1481: * @param contentDefinition the content definition the model folder belongs to
1482: * @throws CmsXmlException if something goes wrong
1483: */
1484: protected void initModelFolder(Element root,
1485: CmsXmlContentDefinition contentDefinition)
1486: throws CmsXmlException {
1487:
1488: String master = root.attributeValue(APPINFO_ATTR_URI);
1489: if (master == null) {
1490: throw new CmsXmlException(Messages.get().container(
1491: Messages.ERR_XMLCONTENT_MISSING_MODELFOLDER_URI_2,
1492: root.getName(),
1493: contentDefinition.getSchemaLocation()));
1494: }
1495: m_modelFolder = master;
1496: }
1497:
1498: /**
1499: * Initializes the preview location for this content handler.<p>
1500: *
1501: * @param root the "preview" element from the appinfo node of the XML content definition
1502: * @param contentDefinition the content definition the validation rules belong to
1503: * @throws CmsXmlException if something goes wrong
1504: */
1505: protected void initPreview(Element root,
1506: CmsXmlContentDefinition contentDefinition)
1507: throws CmsXmlException {
1508:
1509: String preview = root.attributeValue(APPINFO_ATTR_URI);
1510: if (preview == null) {
1511: throw new CmsXmlException(Messages.get().container(
1512: Messages.ERR_XMLCONTENT_MISSING_PREVIEW_URI_2,
1513: root.getName(),
1514: contentDefinition.getSchemaLocation()));
1515: }
1516: m_previewLocation = preview;
1517: }
1518:
1519: /**
1520: * Initializes the relation configuration for this content handler.<p>
1521: *
1522: * OpenCms performs link checks for all OPTIONAL links defined in XML content values of type
1523: * OpenCmsVfsFile. However, for most projects in the real world a more fine-grained control
1524: * over the link check process is required. For these cases, individual relation behavior can
1525: * be defined for the appinfo node.<p>
1526: *
1527: * Additional here can be defined an optional type for the relations, for instance.<p>
1528: *
1529: * @param root the "relations" element from the appinfo node of the XML content definition
1530: * @param contentDefinition the content definition the check rules belong to
1531: *
1532: * @throws CmsXmlException if something goes wrong
1533: */
1534: protected void initRelations(Element root,
1535: CmsXmlContentDefinition contentDefinition)
1536: throws CmsXmlException {
1537:
1538: Iterator i = root.elementIterator(APPINFO_RELATION);
1539: while (i.hasNext()) {
1540: // iterate all "checkrule" elements in the "checkrule" node
1541: Element element = (Element) i.next();
1542: String elementName = element
1543: .attributeValue(APPINFO_ATTR_ELEMENT);
1544: String invalidate = element
1545: .attributeValue(APPINFO_ATTR_INVALIDATE);
1546: if (invalidate != null) {
1547: invalidate = invalidate.toUpperCase();
1548: }
1549: String type = element.attributeValue(APPINFO_ATTR_TYPE);
1550: if (type != null) {
1551: type = type.toLowerCase();
1552: }
1553: if (elementName != null) {
1554: // add a check rule for the element
1555: addCheckRule(contentDefinition, elementName,
1556: invalidate, type);
1557: }
1558: }
1559: }
1560:
1561: /**
1562: * Initializes the resource bundle to use for localized messages in this content handler.<p>
1563: *
1564: * @param root the "resourcebundle" element from the appinfo node of the XML content definition
1565: * @param contentDefinition the content definition the validation rules belong to
1566: *
1567: * @throws CmsXmlException if something goes wrong
1568: */
1569: protected void initResourceBundle(Element root,
1570: CmsXmlContentDefinition contentDefinition)
1571: throws CmsXmlException {
1572:
1573: String name = root.attributeValue(APPINFO_ATTR_NAME);
1574: if (name == null) {
1575: throw new CmsXmlException(
1576: Messages
1577: .get()
1578: .container(
1579: Messages.ERR_XMLCONTENT_MISSING_RESOURCE_BUNDLE_NAME_2,
1580: root.getName(),
1581: contentDefinition
1582: .getSchemaLocation()));
1583: }
1584: m_messageBundleName = name;
1585: }
1586:
1587: /**
1588: * Initializes the search exclusions values for this content handler.<p>
1589: *
1590: * For the full text search, the value of all elements in one locale of the XML content are combined
1591: * to one big text, which is referred to as the "content" in the context of the full text search.
1592: * With this option, it is possible to hide certain elements from this "content" that does not make sense
1593: * to include in the full text search.<p>
1594: *
1595: * @param root the "searchsettings" element from the appinfo node of the XML content definition
1596: * @param contentDefinition the content definition the default values belong to
1597: *
1598: * @throws CmsXmlException if something goes wrong
1599: */
1600: protected void initSearchSettings(Element root,
1601: CmsXmlContentDefinition contentDefinition)
1602: throws CmsXmlException {
1603:
1604: Iterator i = root.elementIterator(APPINFO_SEARCHSETTING);
1605: while (i.hasNext()) {
1606: // iterate all "searchsetting" elements in the "searchsettings" node
1607: Element element = (Element) i.next();
1608: String elementName = element
1609: .attributeValue(APPINFO_ATTR_ELEMENT);
1610: String searchContent = element
1611: .attributeValue(APPINFO_ATTR_SEARCHCONTENT);
1612: boolean include = CmsStringUtil.isEmpty(searchContent)
1613: || Boolean.valueOf(searchContent).booleanValue();
1614: if (elementName != null) {
1615: // add search exclusion for the element
1616: // this may also be "false" in case a default of "true" is to be overwritten
1617: addSearchSetting(contentDefinition, elementName,
1618: Boolean.valueOf(include));
1619: }
1620: }
1621: }
1622:
1623: /**
1624: * Initializes the validation rules this content handler.<p>
1625: *
1626: * OpenCms always performs XML schema validation for all XML contents. However,
1627: * for most projects in the real world a more fine-grained control over the validation process is
1628: * required. For these cases, individual validation rules can be defined for the appinfo node.<p>
1629: *
1630: * @param root the "validationrules" element from the appinfo node of the XML content definition
1631: * @param contentDefinition the content definition the validation rules belong to
1632: *
1633: * @throws CmsXmlException if something goes wrong
1634: */
1635: protected void initValidationRules(Element root,
1636: CmsXmlContentDefinition contentDefinition)
1637: throws CmsXmlException {
1638:
1639: List elements = new ArrayList(root.elements(APPINFO_RULE));
1640: elements.addAll(root.elements(APPINFO_VALIDATIONRULE));
1641: Iterator i = elements.iterator();
1642: while (i.hasNext()) {
1643: // iterate all "rule" or "validationrule" elements in the "validationrules" node
1644: Element element = (Element) i.next();
1645: String elementName = element
1646: .attributeValue(APPINFO_ATTR_ELEMENT);
1647: String regex = element.attributeValue(APPINFO_ATTR_REGEX);
1648: String type = element.attributeValue(APPINFO_ATTR_TYPE);
1649: if (type != null) {
1650: type = type.toLowerCase();
1651: }
1652: String message = element
1653: .attributeValue(APPINFO_ATTR_MESSAGE);
1654: if ((elementName != null) && (regex != null)) {
1655: // add a validation rule for the element
1656: addValidationRule(contentDefinition, elementName,
1657: regex, message, APPINFO_ATTR_TYPE_WARNING
1658: .equals(type));
1659: }
1660: }
1661: }
1662:
1663: /**
1664: * Returns the is-invalidate-parent flag for the given xpath.<p>
1665: *
1666: * @param xpath the path to get the check rule for
1667: *
1668: * @return the configured is-invalidate-parent flag for the given xpath
1669: */
1670: protected boolean isInvalidateParent(String xpath) {
1671:
1672: if (!CmsXmlUtils.isDeepXpath(xpath)) {
1673: return false;
1674: }
1675: Boolean isInvalidateParent = null;
1676: // look up the default from the configured mappings
1677: isInvalidateParent = (Boolean) m_relations.get(xpath);
1678: if (isInvalidateParent == null) {
1679: // no value found, try default xpath
1680: String path = CmsXmlUtils.removeXpath(xpath);
1681: // look up the default value again without indexes
1682: isInvalidateParent = (Boolean) m_relations.get(path);
1683: }
1684: if (isInvalidateParent == null) {
1685: return false;
1686: }
1687: return isInvalidateParent.booleanValue();
1688: }
1689:
1690: /**
1691: * Returns the localized resource string for a given message key according to the configured resource bundle
1692: * of this content handler.<p>
1693: *
1694: * If the key was not found in the configured bundle, or no bundle is configured for this
1695: * content handler, the return value is
1696: * <code>"??? " + keyName + " ???"</code>.<p>
1697: *
1698: * @param keyName the key for the desired string
1699: * @param locale the locale to get the key from
1700: *
1701: * @return the resource string for the given key
1702: *
1703: * @see CmsMessages#formatUnknownKey(String)
1704: * @see CmsMessages#isUnknownKey(String)
1705: */
1706: protected String key(String keyName, Locale locale) {
1707:
1708: CmsMessages messages = getMessages(locale);
1709: if (messages != null) {
1710: return messages.key(keyName);
1711: }
1712: return CmsMessages.formatUnknownKey(keyName);
1713: }
1714:
1715: /**
1716: * Removes property values on resources for non-existing, optional elements.<p>
1717: *
1718: * @param cms the current users OpenCms context
1719: * @param content the XML content to remove the property values for
1720: *
1721: * @throws CmsException in case of read/write errors accessing the OpenCms VFS
1722: */
1723: protected void removeEmptyMappings(CmsObject cms,
1724: CmsXmlContent content) throws CmsException {
1725:
1726: List siblings = null;
1727: CmsObject rootCms = null;
1728:
1729: Iterator mappings = m_elementMappings.entrySet().iterator();
1730: while (mappings.hasNext()) {
1731: Map.Entry e = (Map.Entry) mappings.next();
1732: String path = String.valueOf(e.getKey());
1733: String[] values = (String[]) e.getValue();
1734: if (values == null) {
1735: // nothing to do if we have no mappings at all
1736: continue;
1737: }
1738: if ((siblings == null) || (rootCms == null)) {
1739: // create OpenCms user context initialized with "/" as site root to read all siblings
1740: rootCms = OpenCms.initCmsObject(cms);
1741: rootCms.getRequestContext().setSiteRoot("/");
1742: siblings = rootCms.readSiblings(content.getFile()
1743: .getRootPath(),
1744: CmsResourceFilter.IGNORE_EXPIRATION);
1745: }
1746: for (int v = values.length - 1; v >= 0; v--) {
1747: String mapping = values[v];
1748: if (mapping.startsWith(MAPTO_PROPERTY_LIST)
1749: || mapping.startsWith(MAPTO_PROPERTY)) {
1750:
1751: for (int i = 0; i < siblings.size(); i++) {
1752:
1753: // get siblings filename and locale
1754: String filename = ((CmsResource) siblings
1755: .get(i)).getRootPath();
1756: Locale locale = OpenCms.getLocaleManager()
1757: .getDefaultLocale(rootCms, filename);
1758:
1759: if (!content.hasLocale(locale)) {
1760: // only remove property if the locale fits
1761: continue;
1762: }
1763: if (content.hasValue(path, locale)) {
1764: // value is available, property must be kept
1765: continue;
1766: }
1767:
1768: String property;
1769: if (mapping.startsWith(MAPTO_PROPERTY_LIST)) {
1770: // this is a property list mapping
1771: property = mapping
1772: .substring(MAPTO_PROPERTY_LIST
1773: .length());
1774: } else {
1775: // this is a property mapping
1776: property = mapping.substring(MAPTO_PROPERTY
1777: .length());
1778: }
1779: // delete the property value for the not existing node
1780: rootCms
1781: .writePropertyObject(
1782: filename,
1783: new CmsProperty(
1784: property,
1785: CmsProperty.DELETE_VALUE,
1786: null));
1787: }
1788: }
1789: }
1790: }
1791: }
1792:
1793: /**
1794: * Validates if the given <code>appinfo</code> element node from the XML content definition schema
1795: * is valid according the the capabilities of this content handler.<p>
1796: *
1797: * @param appinfoElement the <code>appinfo</code> element node to validate
1798: *
1799: * @throws CmsXmlException in case the element validation fails
1800: */
1801: protected void validateAppinfoElement(Element appinfoElement)
1802: throws CmsXmlException {
1803:
1804: // create a document to validate
1805: Document doc = DocumentHelper.createDocument();
1806: Element root = doc.addElement(APPINFO_APPINFO);
1807: // attach the default appinfo schema
1808: root.add(I_CmsXmlSchemaType.XSI_NAMESPACE);
1809: root
1810: .addAttribute(
1811: I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION,
1812: APPINFO_SCHEMA_SYSTEM_ID);
1813: // append the content from the appinfo node in the content definition
1814: root.appendContent(appinfoElement);
1815: // now validate the document with the default appinfo schema
1816: CmsXmlUtils.validateXmlStructure(doc,
1817: CmsEncoder.ENCODING_UTF_8, new CmsXmlEntityResolver(
1818: null));
1819: }
1820:
1821: /**
1822: * Validates the given rules against the given value.<p>
1823: *
1824: * @param cms the current users OpenCms context
1825: * @param value the value to validate
1826: * @param errorHandler the error handler to use in case errors or warnings are detected
1827: *
1828: * @return if a broken link has been found
1829: */
1830: protected boolean validateLink(CmsObject cms,
1831: I_CmsXmlContentValue value,
1832: CmsXmlContentErrorHandler errorHandler) {
1833:
1834: // if there is a value of type file reference
1835: if ((value == null)
1836: || (!(value instanceof CmsXmlVfsFileValue) && !(value instanceof CmsXmlVarLinkValue))) {
1837: return false;
1838: }
1839: // if the value has a link (this will automatically fix, for instance, the path of moved resources)
1840: CmsLink link = null;
1841: if (value instanceof CmsXmlVfsFileValue) {
1842: link = ((CmsXmlVfsFileValue) value).getLink(cms);
1843: } else if (value instanceof CmsXmlVarLinkValue) {
1844: link = ((CmsXmlVarLinkValue) value).getLink(cms);
1845: }
1846: if ((link == null) || !link.isInternal()) {
1847: return false;
1848: }
1849: try {
1850: // validate the link for error
1851: CmsResource res = cms.readResource(link.getStructureId(),
1852: CmsResourceFilter.IGNORE_EXPIRATION);
1853:
1854: // check the time range
1855: if (res != null) {
1856: long time = System.currentTimeMillis();
1857: if (!res.isReleased(time)) {
1858: if (errorHandler != null) {
1859: // generate warning message
1860: errorHandler
1861: .addWarning(
1862: value,
1863: Messages
1864: .get()
1865: .getBundle(
1866: value
1867: .getLocale())
1868: .key(
1869: Messages.GUI_XMLCONTENT_CHECK_WARNING_NOT_RELEASED_0));
1870: }
1871: return true;
1872: } else if (res.isExpired(time)) {
1873: if (errorHandler != null) {
1874: // generate warning message
1875: errorHandler
1876: .addWarning(
1877: value,
1878: Messages
1879: .get()
1880: .getBundle(
1881: value
1882: .getLocale())
1883: .key(
1884: Messages.GUI_XMLCONTENT_CHECK_WARNING_EXPIRED_0));
1885: }
1886: return true;
1887: }
1888: }
1889: } catch (CmsException e) {
1890: if (errorHandler != null) {
1891: // generate error message
1892: errorHandler.addError(value, Messages.get().getBundle(
1893: value.getLocale()).key(
1894: Messages.GUI_XMLCONTENT_CHECK_ERROR_0));
1895: }
1896: return true;
1897: }
1898: return false;
1899: }
1900:
1901: /**
1902: * Validates the given rules against the given value.<p>
1903: *
1904: * @param cms the current users OpenCms context
1905: * @param value the value to validate
1906: * @param errorHandler the error handler to use in case errors or warnings are detected
1907: * @param rules the rules to validate the value against
1908: * @param isWarning if true, this validation should be stored as a warning, otherwise as an error
1909: *
1910: * @return the updated error handler
1911: */
1912: protected CmsXmlContentErrorHandler validateValue(CmsObject cms,
1913: I_CmsXmlContentValue value,
1914: CmsXmlContentErrorHandler errorHandler, Map rules,
1915: boolean isWarning) {
1916:
1917: if (validateLink(cms, value, errorHandler)) {
1918: return errorHandler;
1919: }
1920: try {
1921: if (value.getContentDefinition().getContentHandler()
1922: .getWidget(value) instanceof CmsDisplayWidget) {
1923: // display widgets should not be validated
1924: return errorHandler;
1925: }
1926: } catch (CmsXmlException e) {
1927: errorHandler.addError(value, e.getMessage());
1928: return errorHandler;
1929: }
1930:
1931: String valueStr;
1932: try {
1933: valueStr = value.getStringValue(cms);
1934: } catch (Exception e) {
1935: // if the value can not be accessed it's useless to continue
1936: errorHandler.addError(value, e.getMessage());
1937: return errorHandler;
1938: }
1939:
1940: String regex = (String) rules.get(value.getName());
1941: if (regex == null) {
1942: // no customized rule, check default XML schema validation rules
1943: return validateValue(cms, value, valueStr, errorHandler,
1944: isWarning);
1945: }
1946:
1947: boolean matchResult = true;
1948: if (regex.charAt(0) == '!') {
1949: // negate the pattern
1950: matchResult = false;
1951: regex = regex.substring(1);
1952: }
1953:
1954: String matchValue = valueStr;
1955: if (matchValue == null) {
1956: // set match value to empty String to avoid exceptions in pattern matcher
1957: matchValue = "";
1958: }
1959:
1960: // use the custom validation pattern
1961: if (matchResult != Pattern.matches(regex, matchValue)) {
1962: // generate the message
1963: String message = getValidationMessage(cms, value, regex,
1964: valueStr, matchResult, isWarning);
1965: if (isWarning) {
1966: errorHandler.addWarning(value, message);
1967: } else {
1968: errorHandler.addError(value, message);
1969: // if an error was found, the default XML schema validation is not applied
1970: return errorHandler;
1971: }
1972: }
1973:
1974: // no error found, check default XML schema validation rules
1975: return validateValue(cms, value, valueStr, errorHandler,
1976: isWarning);
1977: }
1978:
1979: /**
1980: * Checks the default XML schema validation rules.<p>
1981: *
1982: * These rules should only be tested if this is not a test for warnings.<p>
1983: *
1984: * @param cms the current users OpenCms context
1985: * @param value the value to validate
1986: * @param valueStr the string value of the given value
1987: * @param errorHandler the error handler to use in case errors or warnings are detected
1988: * @param isWarning if true, this validation should be stored as a warning, otherwise as an error
1989: *
1990: * @return the updated error handler
1991: */
1992: protected CmsXmlContentErrorHandler validateValue(CmsObject cms,
1993: I_CmsXmlContentValue value, String valueStr,
1994: CmsXmlContentErrorHandler errorHandler, boolean isWarning) {
1995:
1996: if (isWarning) {
1997: // default schema validation only applies to errors
1998: return errorHandler;
1999: }
2000:
2001: if (!value.validateValue(valueStr)) {
2002: // value is not valid, add an error to the handler
2003: String message = getValidationMessage(cms, value, value
2004: .getTypeName(), valueStr, true, false);
2005: errorHandler.addError(value, message);
2006: }
2007:
2008: return errorHandler;
2009: }
2010: }
|