KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opencms > xml > content > CmsDefaultXmlContentHandler


1 /*
2  * File : $Source: /usr/local/cvs/opencms/src/org/opencms/xml/content/CmsDefaultXmlContentHandler.java,v $
3  * Date : $Date: 2006/03/27 14:52:36 $
4  * Version: $Revision: 1.46 $
5  *
6  * This library is part of OpenCms -
7  * the Open Source Content Mananagement System
8  *
9  * Copyright (c) 2005 Alkacon Software GmbH (http://www.alkacon.com)
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * For further information about Alkacon Software GmbH, please see the
22  * company website: http://www.alkacon.com
23  *
24  * For further information about OpenCms, please see the
25  * project website: http://www.opencms.org
26  *
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with this library; if not, write to the Free Software
29  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30  */

31
32 package org.opencms.xml.content;
33
34 import org.opencms.configuration.CmsConfigurationManager;
35 import org.opencms.file.CmsFile;
36 import org.opencms.file.CmsObject;
37 import org.opencms.file.CmsProperty;
38 import org.opencms.file.CmsResource;
39 import org.opencms.file.CmsResourceFilter;
40 import org.opencms.i18n.CmsEncoder;
41 import org.opencms.i18n.CmsMessages;
42 import org.opencms.main.CmsException;
43 import org.opencms.main.CmsRuntimeException;
44 import org.opencms.main.OpenCms;
45 import org.opencms.util.CmsFileUtil;
46 import org.opencms.util.CmsHtmlConverter;
47 import org.opencms.util.CmsMacroResolver;
48 import org.opencms.util.CmsStringUtil;
49 import org.opencms.widgets.I_CmsWidget;
50 import org.opencms.xml.CmsXmlContentDefinition;
51 import org.opencms.xml.CmsXmlEntityResolver;
52 import org.opencms.xml.CmsXmlException;
53 import org.opencms.xml.CmsXmlUtils;
54 import org.opencms.xml.types.I_CmsXmlContentValue;
55 import org.opencms.xml.types.I_CmsXmlSchemaType;
56
57 import java.util.HashMap JavaDoc;
58 import java.util.Iterator JavaDoc;
59 import java.util.List JavaDoc;
60 import java.util.Locale JavaDoc;
61 import java.util.Map JavaDoc;
62 import java.util.regex.Pattern JavaDoc;
63
64 import org.dom4j.Document;
65 import org.dom4j.DocumentHelper;
66 import org.dom4j.Element;
67
68 /**
69  * Default implementation for the XML content handler, will be used by all XML contents that do not
70  * provide their own handler.<p>
71  *
72  * @author Alexander Kandzior
73  *
74  * @version $Revision: 1.46 $
75  *
76  * @since 6.0.0
77  */

78 public class CmsDefaultXmlContentHandler implements I_CmsXmlContentHandler {
79
80     /** Constant for the "appinfo" element name itself. */
81     public static final String JavaDoc APPINFO_APPINFO = "appinfo";
82
83     /** Constant for the "configuration" appinfo attribute name. */
84     public static final String JavaDoc APPINFO_ATTR_CONFIGURATION = "configuration";
85
86     /** Constant for the "element" appinfo attribute name. */
87     public static final String JavaDoc APPINFO_ATTR_ELEMENT = "element";
88
89     /** Constant for the "mapto" appinfo attribute name. */
90     public static final String JavaDoc APPINFO_ATTR_MAPTO = "mapto";
91
92     /** Constant for the "message" appinfo attribute name. */
93     public static final String JavaDoc APPINFO_ATTR_MESSAGE = "message";
94
95     /** Constant for the "name" appinfo attribute name. */
96     public static final String JavaDoc APPINFO_ATTR_NAME = "name";
97
98     /** Constant for the "regex" appinfo attribute name. */
99     public static final String JavaDoc APPINFO_ATTR_REGEX = "regex";
100
101     /** Constant for the "type" appinfo attribute name. */
102     public static final String JavaDoc APPINFO_ATTR_TYPE = "type";
103
104     /** Constant for the "warning" appinfo attribute value. */
105     public static final String JavaDoc APPINFO_ATTR_TYPE_WARNING = "warning";
106
107     /** Constant for the "uri" appinfo attribute name. */
108     public static final String JavaDoc APPINFO_ATTR_URI = "uri";
109
110     /** Constant for the "value" appinfo attribute name. */
111     public static final String JavaDoc APPINFO_ATTR_VALUE = "value";
112
113     /** Constant for the "widget" appinfo attribute name. */
114     public static final String JavaDoc APPINFO_ATTR_WIDGET = "widget";
115
116     /** Constant for the "default" appinfo element name. */
117     public static final String JavaDoc APPINFO_DEFAULT = "default";
118
119     /** Constant for the "defaults" appinfo element name. */
120     public static final String JavaDoc APPINFO_DEFAULTS = "defaults";
121
122     /** Constant for the "layout" appinfo element name. */
123     public static final String JavaDoc APPINFO_LAYOUT = "layout";
124
125     /** Constant for the "layouts" appinfo element name. */
126     public static final String JavaDoc APPINFO_LAYOUTS = "layouts";
127
128     /** Constant for the "mapping" appinfo element name. */
129     public static final String JavaDoc APPINFO_MAPPING = "mapping";
130
131     /** Constant for the "mappings" appinfo element name. */
132     public static final String JavaDoc APPINFO_MAPPINGS = "mappings";
133
134     /** Constant for the "preview" appinfo element name. */
135     public static final String JavaDoc APPINFO_PREVIEW = "preview";
136
137     /** Constant for the "resourcebundle" appinfo element name. */
138     public static final String JavaDoc APPINFO_RESOURCEBUNDLE = "resourcebundle";
139
140     /** Constant for the "rule" appinfo element name. */
141     public static final String JavaDoc APPINFO_RULE = "rule";
142
143     /** The file where the appinfo schema is located. */
144     public static final String JavaDoc APPINFO_SCHEMA_FILE = "org/opencms/xml/content/DefaultAppinfo.xsd";
145
146     /** The XML system is for the appinfo schema. */
147     public static final String JavaDoc APPINFO_SCHEMA_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX
148         + APPINFO_SCHEMA_FILE;
149
150     /** Constant for the "validationrules" appinfo element name. */
151     public static final String JavaDoc APPINFO_VALIDATIONRULES = "validationrules";
152
153     /** Macro for resolving the preview URI. */
154     public static final String JavaDoc MACRO_PREVIEW_TEMPFILE = "previewtempfile";
155
156     /** Default message for validation errors. */
157     protected static final String JavaDoc MESSAGE_VALIDATION_DEFAULT_ERROR = "${validation.path}: "
158         + "${key." + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_ERROR_2 + "|${validation.value}|[${validation.regex}]}";
159
160     /** Default message for validation warnings. */
161     protected static final String JavaDoc MESSAGE_VALIDATION_DEFAULT_WARNING = "${validation.path}: "
162         + "${key." + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_WARNING_2 + "|${validation.value}|[${validation.regex}]}";
163
164     /** The configuration values for the element widgets (as defined in the annotations). */
165     protected Map JavaDoc m_configurationValues;
166
167     /** The default values for the elements (as defined in the annotations). */
168     protected Map JavaDoc m_defaultValues;
169
170     /** The element mappings (as defined in the annotations). */
171     protected Map JavaDoc m_elementMappings;
172
173     /** The widgets used for the elements (as defined in the annotations). */
174     protected Map JavaDoc m_elementWidgets;
175
176     /** The resource bundle name to be used for localization of this content handler. */
177     protected String JavaDoc m_messageBundleName;
178
179     /** The preview location (as defined in the annotations). */
180     protected String JavaDoc m_previewLocation;
181
182     /** The messages for the error rules. */
183     protected Map JavaDoc m_validationErrorMessages;
184
185     /** The validation rules that cause an error (as defined in the annotations). */
186     protected Map JavaDoc m_validationErrorRules;
187
188     /** The messages for the warning rules. */
189     protected Map JavaDoc m_validationWarningMessages;
190
191     /** The validation rules that cause a warning (as defined in the annotations). */
192     protected Map JavaDoc m_validationWarningRules;
193
194     /**
195      * Creates a new instance of the default XML content handler.<p>
196      */

197     public CmsDefaultXmlContentHandler() {
198
199         init();
200     }
201
202     /**
203      * Static initializer for caching the default appinfo validation schema.<p>
204      */

205     static {
206
207         // the schema definition is located in a separate file for easier editing
208
byte[] appinfoSchema;
209         try {
210             appinfoSchema = CmsFileUtil.readFile(APPINFO_SCHEMA_FILE);
211         } catch (Exception JavaDoc e) {
212             throw new CmsRuntimeException(Messages.get().container(
213                 org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1,
214                 APPINFO_SCHEMA_FILE), e);
215         }
216         CmsXmlEntityResolver.cacheSystemId(APPINFO_SCHEMA_SYSTEM_ID, appinfoSchema);
217     }
218
219     /**
220      * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguration(org.opencms.xml.types.I_CmsXmlSchemaType)
221      */

222     public String JavaDoc getConfiguration(I_CmsXmlSchemaType type) {
223
224         String JavaDoc elementName = type.getName();
225         return (String JavaDoc)m_configurationValues.get(elementName);
226     }
227
228     /**
229      * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefault(org.opencms.file.CmsObject, I_CmsXmlContentValue, java.util.Locale)
230      */

231     public String JavaDoc getDefault(CmsObject cms, I_CmsXmlContentValue value, Locale JavaDoc locale) {
232
233         String JavaDoc defaultValue;
234         if (value.getElement() == null) {
235             // use the "getDefault" method of the given value, will use value from standard XML schema
236
defaultValue = value.getDefault(locale);
237         } else {
238             String JavaDoc xpath = value.getPath();
239             // look up the default from the configured mappings
240
defaultValue = (String JavaDoc)m_defaultValues.get(xpath);
241             if (defaultValue == null) {
242                 // no value found, try default xpath
243
xpath = CmsXmlUtils.removeXpath(xpath);
244                 xpath = CmsXmlUtils.createXpath(xpath, 1);
245                 // look up the default value again with default index of 1 in all path elements
246
defaultValue = (String JavaDoc)m_defaultValues.get(xpath);
247             }
248         }
249         if (defaultValue != null) {
250             // return the default value with processed macros
251
CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms).setMessages(
252                 getMessages(locale));
253             return resolver.resolveMacros(defaultValue);
254         }
255         // no default value is available
256
return null;
257     }
258
259     /**
260      * Returns the mapping defined for the given element xpath.<p>
261      *
262      * @param elementName the element xpath to look up the mapping for
263      * @return the mapping defined for the given element xpath
264      */

265     public String JavaDoc getMapping(String JavaDoc elementName) {
266
267         return (String JavaDoc)m_elementMappings.get(elementName);
268     }
269
270     /**
271      * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMessages(java.util.Locale)
272      */

273     public CmsMessages getMessages(Locale JavaDoc locale) {
274
275         if (m_messageBundleName == null) {
276             // no message bundle was initialized
277
return null;
278         }
279
280         return new CmsMessages(m_messageBundleName, locale);
281     }
282
283     /**
284      * @see org.opencms.xml.content.I_CmsXmlContentHandler#getPreview(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, java.lang.String)
285      */

286     public String JavaDoc getPreview(CmsObject cms, CmsXmlContent content, String JavaDoc resourcename) {
287
288         CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms);
289         resolver.addMacro(MACRO_PREVIEW_TEMPFILE, resourcename);
290
291         return resolver.resolveMacros(m_previewLocation);
292     }
293
294     /**
295      * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.xml.types.I_CmsXmlContentValue)
296      */

297     public I_CmsWidget getWidget(I_CmsXmlContentValue value) {
298
299         // try the specific widget settings first
300
I_CmsWidget result = (I_CmsWidget)m_elementWidgets.get(value.getName());
301         if (result == null) {
302             // use default widget mappings
303
result = OpenCms.getXmlContentTypeManager().getWidgetDefault(value.getTypeName());
304         } else {
305             result = result.newInstance();
306         }
307         // set the configuration value for this widget
308
result.setConfiguration(getConfiguration(value));
309
310         return result;
311     }
312
313     /**
314      * @see org.opencms.xml.content.I_CmsXmlContentHandler#initialize(org.dom4j.Element, org.opencms.xml.CmsXmlContentDefinition)
315      */

316     public synchronized void initialize(Element appInfoElement, CmsXmlContentDefinition contentDefinition)
317     throws CmsXmlException {
318
319         if (appInfoElement == null) {
320             // no appinfo provided, so no initialization is required
321
return;
322         }
323
324         // validate the appinfo element XML content with the default appinfo handler schema
325
validateAppinfoElement(appInfoElement);
326
327         // re-initialize the local variables
328
init();
329
330         Iterator JavaDoc i = appInfoElement.elements().iterator();
331         while (i.hasNext()) {
332             // iterate all elements in the appinfo node
333
Element element = (Element)i.next();
334             String JavaDoc nodeName = element.getName();
335             if (nodeName.equals(APPINFO_MAPPINGS)) {
336                 initMappings(element, contentDefinition);
337             } else if (nodeName.equals(APPINFO_LAYOUTS)) {
338                 initLayouts(element, contentDefinition);
339             } else if (nodeName.equals(APPINFO_VALIDATIONRULES)) {
340                 initValidationRules(element, contentDefinition);
341             } else if (nodeName.equals(APPINFO_DEFAULTS)) {
342                 initDefaultValues(element, contentDefinition);
343             } else if (nodeName.equals(APPINFO_PREVIEW)) {
344                 initPreview(element, contentDefinition);
345             } else if (nodeName.equals(APPINFO_RESOURCEBUNDLE)) {
346                 initResourceBundle(element, contentDefinition);
347             }
348         }
349     }
350
351     /**
352      * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForWrite(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.file.CmsFile)
353      */

354     public CmsFile prepareForWrite(CmsObject cms, CmsXmlContent content, CmsFile file) throws CmsException {
355
356         // validate the xml structure before writing the file
357
// an exception will be thrown if the structure is invalid
358
content.validateXmlStructure(new CmsXmlEntityResolver(cms));
359         // read the content-conversion property
360
String JavaDoc contentConversion = CmsHtmlConverter.getConversionSettings(cms, file);
361         if (CmsStringUtil.isEmptyOrWhitespaceOnly(contentConversion)) {
362             // enable pretty printing and XHTML conversion of XML content html fields by default
363
contentConversion = CmsHtmlConverter.PARAM_XHTML;
364         }
365         content.setConversion(contentConversion);
366         // correct the HTML structure
367
file = content.correctXmlStructure(cms);
368         content.setFile(file);
369         // resolve the file mappings
370
content.resolveMappings(cms);
371         // ensure all property mappings of deleted optional values are removed
372
removeEmptyMappings(cms, content);
373
374         return file;
375     }
376
377     /**
378      * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.types.I_CmsXmlContentValue)
379      */

380     public void resolveMapping(CmsObject cms, CmsXmlContent content, I_CmsXmlContentValue value) throws CmsException {
381
382         if (!value.isSimpleType()) {
383             // no mappings for a nested schema are possible
384
// note that the sub-elemenets of the nested schema ARE mapped by the node visitor,
385
// it's just the nested schema value itself that does not support mapping
386
return;
387         }
388
389         // get the original VFS file from the content
390
CmsFile file = content.getFile();
391         if (file == null) {
392             throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0));
393         }
394
395         // get the mapping for the element name
396
String JavaDoc mapping = getMapping(value.getPath());
397
398         if (CmsStringUtil.isNotEmpty(mapping)) {
399
400             // get root path of the file
401
String JavaDoc rootPath = content.getFile().getRootPath();
402
403             try {
404                 // try / catch to ensure site root is always restored
405
cms.getRequestContext().saveSiteRoot();
406                 cms.getRequestContext().setSiteRoot("/");
407
408                 // read all siblings of the file
409
List JavaDoc siblings = cms.readSiblings(rootPath, CmsResourceFilter.IGNORE_EXPIRATION);
410
411                 // for multilanguage mappings, we need to ensure
412
// a) all siblings are handled
413
// b) only the "right" locale is mapped to a sibling
414

415                 for (int i = (siblings.size() - 1); i >= 0; i--) {
416                     // get filename
417
String JavaDoc filename = ((CmsResource)siblings.get(i)).getRootPath();
418                     Locale JavaDoc locale = OpenCms.getLocaleManager().getDefaultLocale(cms, filename);
419
420                     if (!locale.equals(value.getLocale())) {
421                         // only map property if the locale fits
422
continue;
423                     }
424
425                     // get the string value of the current node
426
String JavaDoc stringValue = value.getStringValue(cms);
427                     if (mapping.startsWith(MAPTO_PROPERTY_LIST) && (value.getIndex() == 0)) {
428
429                         boolean mapToShared;
430                         int prefixLength;
431                         // check which mapping is used (shared or individual)
432
if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) {
433                             mapToShared = true;
434                             prefixLength = MAPTO_PROPERTY_LIST_SHARED.length();
435                         } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) {
436                             mapToShared = false;
437                             prefixLength = MAPTO_PROPERTY_LIST_INDIVIDUAL.length();
438                         } else {
439                             mapToShared = false;
440                             prefixLength = MAPTO_PROPERTY_LIST.length();
441                         }
442
443                         // this is a property list mapping
444
String JavaDoc property = mapping.substring(prefixLength);
445
446                         String JavaDoc path = CmsXmlUtils.removeXpathIndex(value.getPath());
447                         List JavaDoc values = content.getValues(path, locale);
448                         Iterator JavaDoc j = values.iterator();
449                         StringBuffer JavaDoc result = new StringBuffer JavaDoc(values.size() * 64);
450                         while (j.hasNext()) {
451                             I_CmsXmlContentValue val = (I_CmsXmlContentValue)j.next();
452                             result.append(val.getStringValue(cms));
453                             if (j.hasNext()) {
454                                 result.append(CmsProperty.VALUE_LIST_DELIMITER);
455                             }
456                         }
457
458                         CmsProperty p;
459                         if (mapToShared) {
460                             // map to shared value
461
p = new CmsProperty(property, null, result.toString());
462                         } else {
463                             // map to individual value
464
p = new CmsProperty(property, result.toString(), null);
465                         }
466                         // write the created list string value in the selected property
467
cms.writePropertyObject(filename, p);
468                         if (mapToShared) {
469                             // special case: shared mappings must be written only to one sibling, end loop
470
i = 0;
471                         }
472
473                     } else if (mapping.startsWith(MAPTO_PROPERTY)) {
474
475                         boolean mapToShared;
476                         int prefixLength;
477                         // check which mapping is used (shared or individual)
478
if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) {
479                             mapToShared = true;
480                             prefixLength = MAPTO_PROPERTY_SHARED.length();
481                         } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) {
482                             mapToShared = false;
483                             prefixLength = MAPTO_PROPERTY_INDIVIDUAL.length();
484                         } else {
485                             mapToShared = false;
486                             prefixLength = MAPTO_PROPERTY.length();
487                         }
488
489                         // this is a property mapping
490
String JavaDoc property = mapping.substring(prefixLength);
491
492                         CmsProperty p;
493                         if (mapToShared) {
494                             // map to shared value
495
p = new CmsProperty(property, null, stringValue);
496                         } else {
497                             // map to individual value
498
p = new CmsProperty(property, stringValue, null);
499                         }
500                         // just store the string value in the selected property
501
cms.writePropertyObject(filename, p);
502                         if (mapToShared) {
503                             // special case: shared mappings must be written only to one sibling, end loop
504
i = 0;
505                         }
506
507                     } else if (mapping.startsWith(MAPTO_ATTRIBUTE)) {
508
509                         // this is an attribute mapping
510
String JavaDoc attribute = mapping.substring(MAPTO_ATTRIBUTE.length());
511                         switch (ATTRIBUTES.indexOf(attribute)) {
512                             case 0: // datereleased
513
long date;
514                                 date = Long.valueOf(stringValue).longValue();
515                                 if (date == 0) {
516                                     date = CmsResource.DATE_RELEASED_DEFAULT;
517                                 }
518                                 file.setDateReleased(date);
519                                 break;
520                             case 1: // dateexpired
521
date = Long.valueOf(stringValue).longValue();
522                                 if (date == 0) {
523                                     date = CmsResource.DATE_EXPIRED_DEFAULT;
524                                 }
525                                 file.setDateExpired(date);
526                                 break;
527                             default:
528                         // ignore invalid / other mappings
529
}
530                     }
531                 }
532
533             } finally {
534                 // restore the saved site root
535
cms.getRequestContext().restoreSiteRoot();
536             }
537         }
538     }
539
540     /**
541      * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveValidation(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlContentValue, org.opencms.xml.content.CmsXmlContentErrorHandler)
542      */

543     public CmsXmlContentErrorHandler resolveValidation(
544         CmsObject cms,
545         I_CmsXmlContentValue value,
546         CmsXmlContentErrorHandler errorHandler) {
547
548         if (errorHandler == null) {
549             // init a new error handler if required
550
errorHandler = new CmsXmlContentErrorHandler();
551         }
552
553         if (!value.isSimpleType()) {
554             // no validation for a nested schema is possible
555
// note that the sub-elemenets of the nested schema ARE validated by the node visitor,
556
// it's just the nested schema value itself that does not support validation
557
return errorHandler;
558         }
559
560         // validate the error rules
561
errorHandler = validateValue(cms, value, errorHandler, m_validationErrorRules, false);
562         // validate the warning rules
563
errorHandler = validateValue(cms, value, errorHandler, m_validationWarningRules, true);
564
565         // return the result
566
return errorHandler;
567     }
568
569     /**
570      * Adds a configuration value for an element widget.<p>
571      *
572      * @param contentDefinition the XML content definition this XML content handler belongs to
573      * @param elementName the element name to map
574      * @param configurationValue the configuration value to use
575      *
576      * @throws CmsXmlException in case an unknown element name is used
577      */

578     protected void addConfiguration(
579         CmsXmlContentDefinition contentDefinition,
580         String JavaDoc elementName,
581         String JavaDoc configurationValue) throws CmsXmlException {
582
583         if (contentDefinition.getSchemaType(elementName) == null) {
584             throw new CmsXmlException(Messages.get().container(
585                 Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1,
586                 elementName));
587         }
588
589         m_configurationValues.put(elementName, configurationValue);
590     }
591
592     /**
593      * Adds a default value for an element.<p>
594      *
595      * @param contentDefinition the XML content definition this XML content handler belongs to
596      * @param elementName the element name to map
597      * @param defaultValue the default value to use
598      *
599      * @throws CmsXmlException in case an unknown element name is used
600      */

601     protected void addDefault(CmsXmlContentDefinition contentDefinition, String JavaDoc elementName, String JavaDoc defaultValue)
602     throws CmsXmlException {
603
604         if (contentDefinition.getSchemaType(elementName) == null) {
605             throw new CmsXmlException(org.opencms.xml.types.Messages.get().container(
606                 org.opencms.xml.types.Messages.ERR_XMLCONTENT_INVALID_ELEM_DEFAULT_1,
607                 elementName));
608         }
609         // store mappings as Xpath to allow better control about what is mapped
610
String JavaDoc xpath = CmsXmlUtils.createXpath(elementName, 1);
611         m_defaultValues.put(xpath, defaultValue);
612     }
613
614     /**
615      * Adds an element mapping.<p>
616      *
617      * @param contentDefinition the XML content definition this XML content handler belongs to
618      * @param elementName the element name to map
619      * @param mapping the mapping to use
620      *
621      * @throws CmsXmlException in case an unknown element name is used
622      */

623     protected void addMapping(CmsXmlContentDefinition contentDefinition, String JavaDoc elementName, String JavaDoc mapping)
624     throws CmsXmlException {
625
626         if (contentDefinition.getSchemaType(elementName) == null) {
627             throw new CmsXmlException(Messages.get().container(
628                 Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1,
629                 elementName));
630         }
631
632         // store mappings as Xpath to allow better control about what is mapped
633
String JavaDoc xpath = CmsXmlUtils.createXpath(elementName, 1);
634         m_elementMappings.put(xpath, mapping);
635     }
636
637     /**
638      * Adds a validation rule for a specified element.<p>
639      *
640      * @param contentDefinition the XML content definition this XML content handler belongs to
641      * @param elementName the element name to add the rule to
642      * @param regex the validation rule regular expression
643      * @param message the message in case validation fails (may be null)
644      * @param isWarning if true, this rule is used for warnings, otherwise it's an error
645      *
646      * @throws CmsXmlException in case an unknown element name is used
647      */

648     protected void addValidationRule(
649         CmsXmlContentDefinition contentDefinition,
650         String JavaDoc elementName,
651         String JavaDoc regex,
652         String JavaDoc message,
653         boolean isWarning) throws CmsXmlException {
654
655         if (contentDefinition.getSchemaType(elementName) == null) {
656             throw new CmsXmlException(Messages.get().container(
657                 Messages.ERR_XMLCONTENT_INVALID_ELEM_VALIDATION_1,
658                 elementName));
659         }
660
661         if (isWarning) {
662             m_validationWarningRules.put(elementName, regex);
663             if (message != null) {
664                 m_validationWarningMessages.put(elementName, message);
665             }
666         } else {
667             m_validationErrorRules.put(elementName, regex);
668             if (message != null) {
669                 m_validationErrorMessages.put(elementName, message);
670             }
671         }
672     }
673
674     /**
675      * Adds a GUI widget for a specified element.<p>
676      *
677      * @param contentDefinition the XML content definition this XML content handler belongs to
678      * @param elementName the element name to map
679      * @param widgetClassOrAlias the widget to use as GUI for the element (registered alias or class name)
680      *
681      * @throws CmsXmlException in case an unknown element name is used
682      */

683     protected void addWidget(CmsXmlContentDefinition contentDefinition, String JavaDoc elementName, String JavaDoc widgetClassOrAlias)
684     throws CmsXmlException {
685
686         if (contentDefinition.getSchemaType(elementName) == null) {
687             throw new CmsXmlException(Messages.get().container(
688                 Messages.ERR_XMLCONTENT_INVALID_ELEM_LAYOUTWIDGET_1,
689                 elementName));
690         }
691
692         // get the base widget from the XML content type manager
693
I_CmsWidget widget = OpenCms.getXmlContentTypeManager().getWidget(widgetClassOrAlias);
694
695         if (widget == null) {
696             // no registered widget class found
697
if (CmsStringUtil.isValidJavaClassName(widgetClassOrAlias)) {
698                 // java class name given, try to create new instance of the class and cast to widget
699
try {
700                     Class JavaDoc specialWidgetClass = Class.forName(widgetClassOrAlias);
701                     widget = (I_CmsWidget)specialWidgetClass.newInstance();
702                 } catch (Exception JavaDoc e) {
703                     throw new CmsXmlException(Messages.get().container(
704                         Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3,
705                         widgetClassOrAlias,
706                         elementName,
707                         contentDefinition.getSchemaLocation()), e);
708                 }
709             }
710             if (widget == null) {
711                 // no valid widget found
712
throw new CmsXmlException(Messages.get().container(
713                     Messages.ERR_XMLCONTENT_INVALID_WIDGET_3,
714                     widgetClassOrAlias,
715                     elementName,
716                     contentDefinition.getSchemaLocation()));
717             }
718         }
719         m_elementWidgets.put(elementName, widget);
720     }
721
722     /**
723      * Returns the validation message to be displayed if a certain rule was violated.<p>
724      *
725      * @param cms the current users OpenCms context
726      * @param value the value to validate
727      * @param regex the rule that was vialoted
728      * @param valueStr the string value of the given value
729      * @param matchResult if false, the rule was negated
730      * @param isWarning if true, this validation indicate a warning, otherwise an error
731      *
732      * @return the validation message to be displayed
733      */

734     protected String JavaDoc getValidationMessage(
735         CmsObject cms,
736         I_CmsXmlContentValue value,
737         String JavaDoc regex,
738         String JavaDoc valueStr,
739         boolean matchResult,
740         boolean isWarning) {
741
742         String JavaDoc message = null;
743         if (isWarning) {
744             message = (String JavaDoc)m_validationWarningMessages.get(value.getName());
745         } else {
746             message = (String JavaDoc)m_validationErrorMessages.get(value.getName());
747         }
748
749         if (message == null) {
750             if (isWarning) {
751                 message = MESSAGE_VALIDATION_DEFAULT_WARNING;
752             } else {
753                 message = MESSAGE_VALIDATION_DEFAULT_ERROR;
754             }
755         }
756
757         // create additional macro values
758
Map JavaDoc additionalValues = new HashMap JavaDoc();
759         additionalValues.put(CmsMacroResolver.KEY_VALIDATION_VALUE, valueStr);
760         additionalValues.put(CmsMacroResolver.KEY_VALIDATION_REGEX, ((!matchResult) ? "!" : "") + regex);
761         additionalValues.put(CmsMacroResolver.KEY_VALIDATION_PATH, value.getPath());
762
763         CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms).setMessages(
764             getMessages(cms.getRequestContext().getLocale())).setAdditionalMacros(additionalValues);
765
766         return resolver.resolveMacros(message);
767     }
768
769     /**
770      * Called when this content handler is initialized.<p>
771      */

772     protected void init() {
773
774         m_elementMappings = new HashMap JavaDoc();
775         m_elementWidgets = new HashMap JavaDoc();
776         m_validationErrorRules = new HashMap JavaDoc();
777         m_validationErrorMessages = new HashMap JavaDoc();
778         m_validationWarningRules = new HashMap JavaDoc();
779         m_validationWarningMessages = new HashMap JavaDoc();
780         m_defaultValues = new HashMap JavaDoc();
781         m_configurationValues = new HashMap JavaDoc();
782         m_previewLocation = null;
783     }
784
785     /**
786      * Initializes the default values for this content handler.<p>
787      *
788      * Using the default values from the appinfo node, it's possible to have more
789      * sophisticated logic for generating the defaults then just using the XML schema "default"
790      * attribute.<p>
791      *
792      * @param root the "defaults" element from the appinfo node of the XML content definition
793      * @param contentDefinition the content definition the default values belong to
794      * @throws CmsXmlException if something goes wrong
795      */

796     protected void initDefaultValues(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
797
798         Iterator JavaDoc i = root.elementIterator(APPINFO_DEFAULT);
799         while (i.hasNext()) {
800             // iterate all "default" elements in the "defaults" node
801
Element element = (Element)i.next();
802             String JavaDoc elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
803             String JavaDoc defaultValue = element.attributeValue(APPINFO_ATTR_VALUE);
804             if ((elementName != null) && (defaultValue != null)) {
805                 // add a default value mapping for the element
806
addDefault(contentDefinition, elementName, defaultValue);
807             }
808         }
809     }
810
811     /**
812      * Initializes the layout for this content handler.<p>
813      *
814      * Unless otherwise instructed, the editor uses one specific GUI widget for each
815      * XML value schema type. For example, for a {@link org.opencms.xml.types.CmsXmlStringValue}
816      * the default widget is the {@link org.opencms.widgets.CmsInputWidget}.
817      * However, certain values can also use more then one widget, for example you may
818      * also use a {@link org.opencms.widgets.CmsCheckboxWidget} for a String value,
819      * and as a result the Strings possible values would be eithe <code>"false"</code> or <code>"true"</code>,
820      * but nevertheless be a String.<p>
821      *
822      * The widget to use can further be controlled using the <code>widget</code> attribute.
823      * You can specifiy either a valid widget alias such as <code>StringWidget</code>,
824      * or the name of a Java class that implements <code>{@link I_CmsWidget}</code>.<p>
825      *
826      * Configuration options to the widget can be passed using the <code>configuration</code>
827      * attribute. You can specify any String as configuration. This String is then passed
828      * to the widget during initialization. It's up to the individual widget implementation
829      * to interpret this configuration String.<p>
830      *
831      * @param root the "layouts" element from the appinfo node of the XML content definition
832      * @param contentDefinition the content definition the layout belongs to
833      *
834      * @throws CmsXmlException if something goes wrong
835      */

836     protected void initLayouts(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
837
838         Iterator JavaDoc i = root.elementIterator(APPINFO_LAYOUT);
839         while (i.hasNext()) {
840             // iterate all "layout" elements in the "layouts" node
841
Element element = (Element)i.next();
842             String JavaDoc elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
843             String JavaDoc widgetClassOrAlias = element.attributeValue(APPINFO_ATTR_WIDGET);
844             String JavaDoc configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION);
845             if ((elementName != null) && (widgetClassOrAlias != null)) {
846                 // add a widget mapping for the element
847
addWidget(contentDefinition, elementName, widgetClassOrAlias);
848                 if (configuration != null) {
849                     addConfiguration(contentDefinition, elementName, configuration);
850                 }
851             }
852         }
853     }
854
855     /**
856      * Initializes the element mappings for this content handler.<p>
857      *
858      * Element mappings allow storing values from the XML content in other locations.
859      * For example, if you have an elemenet called "Title", it's likley a good idea to
860      * store the value of this element also in the "Title" property of a XML content resource.<p>
861      *
862      * @param root the "mappings" element from the appinfo node of the XML content definition
863      * @param contentDefinition the content definition the mappings belong to
864      * @throws CmsXmlException if something goes wrong
865      */

866     protected void initMappings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
867
868         Iterator JavaDoc i = root.elementIterator(APPINFO_MAPPING);
869         while (i.hasNext()) {
870             // iterate all "mapping" elements in the "mappings" node
871
Element element = (Element)i.next();
872             // this is a mapping node
873
String JavaDoc elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
874             String JavaDoc maptoName = element.attributeValue(APPINFO_ATTR_MAPTO);
875             if ((elementName != null) && (maptoName != null)) {
876                 // add the element mapping
877
addMapping(contentDefinition, elementName, maptoName);
878             }
879         }
880     }
881
882     /**
883      * Initializes the preview location for this content handler.<p>
884      *
885      * @param root the "preview" element from the appinfo node of the XML content definition
886      * @param contentDefinition the content definition the validation rules belong to
887      * @throws CmsXmlException if something goes wrong
888      */

889     protected void initPreview(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
890
891         String JavaDoc preview = root.attributeValue(APPINFO_ATTR_URI);
892         if (preview == null) {
893             throw new CmsXmlException(Messages.get().container(
894                 Messages.ERR_XMLCONTENT_MISSING_PREVIEW_URI_2,
895                 root.getName(),
896                 contentDefinition.getSchemaLocation()));
897         }
898         m_previewLocation = preview;
899     }
900
901     /**
902      * Initializes the resource bundle to use for localized messages in this content handler.<p>
903      *
904      * @param root the "resourcebundle" element from the appinfo node of the XML content definition
905      * @param contentDefinition the content definition the validation rules belong to
906      *
907      * @throws CmsXmlException if something goes wrong
908      */

909     protected void initResourceBundle(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
910
911         String JavaDoc name = root.attributeValue(APPINFO_ATTR_NAME);
912         if (name == null) {
913             throw new CmsXmlException(Messages.get().container(
914                 Messages.ERR_XMLCONTENT_MISSING_RESOURCE_BUNDLE_NAME_2,
915                 root.getName(),
916                 contentDefinition.getSchemaLocation()));
917         }
918         m_messageBundleName = name;
919     }
920
921     /**
922      * Initializes the validation rules this content handler.<p>
923      *
924      * OpenCms always performs XML schema validation for all XML contents. However,
925      * for most projects in the real world a more fine-grained control over the validation process is
926      * required. For these cases, individual validation rules can be defined for the appinfo node.<p>
927      *
928      * @param root the "validationrules" element from the appinfo node of the XML content definition
929      * @param contentDefinition the content definition the validation rules belong to
930      * @throws CmsXmlException if something goes wrong
931      */

932     protected void initValidationRules(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
933
934         Iterator JavaDoc i = root.elementIterator(APPINFO_RULE);
935         while (i.hasNext()) {
936             // iterate all "layout" elements in the "layouts" node
937
Element element = (Element)i.next();
938             String JavaDoc elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
939             String JavaDoc regex = element.attributeValue(APPINFO_ATTR_REGEX);
940             String JavaDoc type = element.attributeValue(APPINFO_ATTR_TYPE);
941             String JavaDoc message = element.attributeValue(APPINFO_ATTR_MESSAGE);
942             if ((elementName != null) && (regex != null)) {
943                 // add a validation ruls for the element
944
addValidationRule(
945                     contentDefinition,
946                     elementName,
947                     regex,
948                     message,
949                     APPINFO_ATTR_TYPE_WARNING.equals(type));
950             }
951         }
952     }
953
954     /**
955      * Returns the localized resource string for a given message key according to the configured resource bundle
956      * of this content handler.<p>
957      *
958      * If the key was not found in the configuredd bundle, or no bundle is configured for this
959      * content handler, the return value is
960      * <code>"??? " + keyName + " ???"</code>.<p>
961      *
962      * @param keyName the key for the desired string
963      * @param locale the locale to get the key from
964      *
965      * @return the resource string for the given key
966      *
967      * @see CmsMessages#formatUnknownKey(String)
968      * @see CmsMessages#isUnknownKey(String)
969      */

970     protected String JavaDoc key(String JavaDoc keyName, Locale JavaDoc locale) {
971
972         CmsMessages messages = getMessages(locale);
973         if (messages != null) {
974             return messages.key(keyName);
975         }
976         return CmsMessages.formatUnknownKey(keyName);
977     }
978
979     /**
980      * Removes property values on resources for non-existing, optional elements.<p>
981      *
982      * @param cms the current users OpenCms context
983      * @param content the XML content to remove the property values for
984      *
985      * @throws CmsException in case of read/write errors accessing the OpenCms VFS
986      */

987     protected void removeEmptyMappings(CmsObject cms, CmsXmlContent content) throws CmsException {
988
989         Iterator JavaDoc mappings = m_elementMappings.keySet().iterator();
990
991         String JavaDoc rootPath = null;
992         List JavaDoc siblings = null;
993
994         while (mappings.hasNext()) {
995             String JavaDoc path = (String JavaDoc)mappings.next();
996             String JavaDoc mapping = (String JavaDoc)m_elementMappings.get(path);
997
998             if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) {
999
1000                // get root path of the file
1001
if (rootPath == null) {
1002                    rootPath = content.getFile().getRootPath();
1003                }
1004
1005                try {
1006                    // try / catch to ensure site root is always restored
1007
cms.getRequestContext().saveSiteRoot();
1008                    cms.getRequestContext().setSiteRoot("/");
1009
1010                    // read all siblings of the file
1011
if (siblings == null) {
1012                        siblings = cms.readSiblings(rootPath, CmsResourceFilter.IGNORE_EXPIRATION);
1013                    }
1014
1015                    for (int i = 0; i < siblings.size(); i++) {
1016
1017                        // get sibline filename and locale
1018
String JavaDoc filename = ((CmsResource)siblings.get(i)).getRootPath();
1019                        Locale JavaDoc locale = OpenCms.getLocaleManager().getDefaultLocale(cms, filename);
1020
1021                        if (!content.hasLocale(locale)) {
1022                            // only remove property if the locale fits
1023
continue;
1024                        }
1025                        if (content.hasValue(path, locale)) {
1026                            // value is available, property must be kept
1027
continue;
1028                        }
1029
1030                        String JavaDoc property;
1031                        if (mapping.startsWith(MAPTO_PROPERTY_LIST)) {
1032                            // this is a property list mapping
1033
property = mapping.substring(MAPTO_PROPERTY_LIST.length());
1034                        } else {
1035                            // this is a property mapping
1036
property = mapping.substring(MAPTO_PROPERTY.length());
1037                        }
1038                        // delete the property value for the not existing node
1039
cms.writePropertyObject(filename, new CmsProperty(property, CmsProperty.DELETE_VALUE, null));
1040                    }
1041
1042                } finally {
1043                    // restore the saved site root
1044
cms.getRequestContext().restoreSiteRoot();
1045                }
1046            }
1047        }
1048    }
1049
1050    /**
1051     * Validates if the given <code>appinfo</code> element node from the XML content definition schema
1052     * is valid according the the capabilities of this content handler.<p>
1053     *
1054     * @param appinfoElement the <code>appinfo</code> element node to validate
1055     *
1056     * @throws CmsXmlException in case the element validation fails
1057     */

1058    protected void validateAppinfoElement(Element appinfoElement) throws CmsXmlException {
1059
1060        // create a document to validate
1061
Document doc = DocumentHelper.createDocument();
1062        Element root = doc.addElement(APPINFO_APPINFO);
1063        // attach the default appinfo schema
1064
root.add(I_CmsXmlSchemaType.XSI_NAMESPACE);
1065        root.addAttribute(I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION, APPINFO_SCHEMA_SYSTEM_ID);
1066        // append the content from the appinfo node in the content definition
1067
root.appendContent(appinfoElement);
1068        // now validate the document with the default appinfo schema
1069
CmsXmlUtils.validateXmlStructure(doc, CmsEncoder.ENCODING_UTF_8, new CmsXmlEntityResolver(null));
1070    }
1071
1072    /**
1073     * Validates the given rules against the given value.<p>
1074     *
1075     * @param cms the current users OpenCms context
1076     * @param value the value to validate
1077     * @param errorHandler the error handler to use in case errors or warnings are detected
1078     * @param rules the rules to validate the value against
1079     * @param isWarning if true, this validation should be stored as a warning, otherwise as an error
1080     *
1081     * @return the updated error handler
1082     */

1083    protected CmsXmlContentErrorHandler validateValue(
1084        CmsObject cms,
1085        I_CmsXmlContentValue value,
1086        CmsXmlContentErrorHandler errorHandler,
1087        Map JavaDoc rules,
1088        boolean isWarning) {
1089
1090        String JavaDoc valueStr;
1091        try {
1092            valueStr = value.getStringValue(cms);
1093        } catch (Exception JavaDoc e) {
1094            // if the value can not be accessed it's useless to continue
1095
errorHandler.addError(value, e.getMessage());
1096            return errorHandler;
1097        }
1098
1099        String JavaDoc regex = (String JavaDoc)rules.get(value.getName());
1100        if (regex == null) {
1101            // no customized rule, check default XML schema validation rules
1102
return validateValue(cms, value, valueStr, errorHandler, isWarning);
1103        }
1104
1105        boolean matchResult = true;
1106        if (regex.charAt(0) == '!') {
1107            // negate the pattern
1108
matchResult = false;
1109            regex = regex.substring(1);
1110        }
1111
1112        String JavaDoc matchValue = valueStr;
1113        if (matchValue == null) {
1114            // set match value to empty String to avoid exceptions in pattern matcher
1115
matchValue = "";
1116        }
1117
1118        // use the custom validation pattern
1119
if (matchResult != Pattern.matches(regex, matchValue)) {
1120            // generate the message
1121
String JavaDoc message = getValidationMessage(cms, value, regex, valueStr, matchResult, isWarning);
1122            if (isWarning) {
1123                errorHandler.addWarning(value, message);
1124            } else {
1125                errorHandler.addError(value, message);
1126                // if an error was found, the default XML schema validation is not applied
1127
return errorHandler;
1128            }
1129        }
1130
1131        // no error found, check default XML schema validation rules
1132
return validateValue(cms, value, valueStr, errorHandler, isWarning);
1133    }
1134
1135    /**
1136     * Checks the default XML schema vaildation rules.<p>
1137     *
1138     * These rules should only be tested if this is not a test for warnings.<p>
1139     *
1140     * @param cms the current users OpenCms context
1141     * @param value the value to validate
1142     * @param valueStr the string value of the given value
1143     * @param errorHandler the error handler to use in case errors or warnings are detected
1144     * @param isWarning if true, this validation should be stored as a warning, otherwise as an error
1145     *
1146     * @return the updated error handler
1147     */

1148    protected CmsXmlContentErrorHandler validateValue(
1149        CmsObject cms,
1150        I_CmsXmlContentValue value,
1151        String JavaDoc valueStr,
1152        CmsXmlContentErrorHandler errorHandler,
1153        boolean isWarning) {
1154
1155        if (isWarning) {
1156            // default schema validation only applies to errors
1157
return errorHandler;
1158        }
1159
1160        if (!value.validateValue(valueStr)) {
1161            // value is not valid, add an error to the handler
1162
String JavaDoc message = getValidationMessage(cms, value, value.getTypeName(), valueStr, true, false);
1163            errorHandler.addError(value, message);
1164        }
1165
1166        return errorHandler;
1167    }
1168}
Popular Tags