KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opencms > xml > CmsXmlContentDefinition


1 /*
2  * File : $Source: /usr/local/cvs/opencms/src/org/opencms/xml/CmsXmlContentDefinition.java,v $
3  * Date : $Date: 2006/07/19 12:38:16 $
4  * Version: $Revision: 1.37 $
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;
33
34 import org.opencms.file.CmsObject;
35 import org.opencms.main.OpenCms;
36 import org.opencms.util.CmsStringUtil;
37 import org.opencms.xml.content.CmsDefaultXmlContentHandler;
38 import org.opencms.xml.content.I_CmsXmlContentHandler;
39 import org.opencms.xml.types.CmsXmlLocaleValue;
40 import org.opencms.xml.types.CmsXmlNestedContentDefinition;
41 import org.opencms.xml.types.I_CmsXmlContentValue;
42 import org.opencms.xml.types.I_CmsXmlSchemaType;
43
44 import java.io.IOException JavaDoc;
45 import java.util.ArrayList JavaDoc;
46 import java.util.Arrays JavaDoc;
47 import java.util.Collections JavaDoc;
48 import java.util.HashMap JavaDoc;
49 import java.util.HashSet JavaDoc;
50 import java.util.Iterator JavaDoc;
51 import java.util.List JavaDoc;
52 import java.util.Locale JavaDoc;
53 import java.util.Map JavaDoc;
54 import java.util.Set JavaDoc;
55
56 import org.dom4j.Attribute;
57 import org.dom4j.Document;
58 import org.dom4j.DocumentHelper;
59 import org.dom4j.Element;
60 import org.dom4j.Namespace;
61 import org.dom4j.QName;
62 import org.xml.sax.EntityResolver JavaDoc;
63 import org.xml.sax.InputSource JavaDoc;
64 import org.xml.sax.SAXException JavaDoc;
65
66 /**
67  * Describes the structure definition of an XML content object.<p>
68  *
69  * @author Alexander Kandzior
70  *
71  * @version $Revision: 1.37 $
72  *
73  * @since 6.0.0
74  */

75 public class CmsXmlContentDefinition implements Cloneable JavaDoc {
76
77     /**
78      * Simple data structure to describe a type seqnence in an XML schema.<p>
79      */

80     private final class CmsXmlComplexTypeSequence {
81
82         /** Indicates if this type sequence has a language attribute. */
83         protected boolean m_hasLanguageAttribute;
84
85         /** The name of the complex type seqnence. */
86         protected String JavaDoc m_name;
87
88         /** The type sequence elements. */
89         protected List JavaDoc m_sequence;
90
91         /**
92          * Creates a new complex type sequence data structure.<p>
93          *
94          * @param name the name of the sequence
95          * @param sequence the type sequence element list
96          * @param hasLanguageAttribute indicates if a "language" attribute is present
97          */

98         protected CmsXmlComplexTypeSequence(String JavaDoc name, List JavaDoc sequence, boolean hasLanguageAttribute) {
99
100             m_name = name;
101             m_sequence = sequence;
102             m_hasLanguageAttribute = hasLanguageAttribute;
103         }
104     }
105
106     /** Constant for the XML schema attribute "mapto". */
107     public static final String JavaDoc XSD_ATTRIBUTE_DEFAULT = "default";
108
109     /** Constant for the XML schema attribute "elementFormDefault". */
110     public static final String JavaDoc XSD_ATTRIBUTE_ELEMENT_FORM_DEFAULT = "elementFormDefault";
111
112     /** Constant for the XML schema attribute "maxOccurs". */
113     public static final String JavaDoc XSD_ATTRIBUTE_MAX_OCCURS = "maxOccurs";
114
115     /** Constant for the XML schema attribute "minOccurs". */
116     public static final String JavaDoc XSD_ATTRIBUTE_MIN_OCCURS = "minOccurs";
117
118     /** Constant for the XML schema attribute "name". */
119     public static final String JavaDoc XSD_ATTRIBUTE_NAME = "name";
120
121     /** Constant for the XML schema attribute "schemaLocation". */
122     public static final String JavaDoc XSD_ATTRIBUTE_SCHEMA_LOCATION = "schemaLocation";
123
124     /** Constant for the XML schema attribute "type". */
125     public static final String JavaDoc XSD_ATTRIBUTE_TYPE = "type";
126
127     /** Constant for the XML schema attribute "use". */
128     public static final String JavaDoc XSD_ATTRIBUTE_USE = "use";
129
130     /** Constant for the XML schema attribute value "language". */
131     public static final String JavaDoc XSD_ATTRIBUTE_VALUE_LANGUAGE = "language";
132
133     /** Constant for the XML schema attribute value "optional". */
134     public static final String JavaDoc XSD_ATTRIBUTE_VALUE_OPTIONAL = "optional";
135
136     /** Constant for the XML schema attribute value "qualified". */
137     public static final String JavaDoc XSD_ATTRIBUTE_VALUE_QUALIFIED = "qualified";
138
139     /** Constant for the XML schema attribute value "required". */
140     public static final String JavaDoc XSD_ATTRIBUTE_VALUE_REQUIRED = "required";
141
142     /** Constant for the XML schema attribute value "unbounded". */
143     public static final String JavaDoc XSD_ATTRIBUTE_VALUE_UNBOUNDED = "unbounded";
144
145     /** Constant for the XML schema attribute value "0". */
146     public static final String JavaDoc XSD_ATTRIBUTE_VALUE_ZERO = "0";
147
148     /** The opencms default type definition include. */
149     public static final String JavaDoc XSD_INCLUDE_OPENCMS = CmsXmlEntityResolver.OPENCMS_SCHEME + "opencms-xmlcontent.xsd";
150
151     /** The schema definition namespace. */
152     public static final Namespace XSD_NAMESPACE = Namespace.get("xsd", "http://www.w3.org/2001/XMLSchema");
153
154     /** Constant for the "annotation" node in the XML schema namespace. */
155     public static final QName XSD_NODE_ANNOTATION = QName.get("annotation", XSD_NAMESPACE);
156
157     /** Constant for the "appinfo" node in the XML schema namespace. */
158     public static final QName XSD_NODE_APPINFO = QName.get("appinfo", XSD_NAMESPACE);
159
160     /** Constant for the "attribute" node in the XML schema namespace. */
161     public static final QName XSD_NODE_ATTRIBUTE = QName.get("attribute", XSD_NAMESPACE);
162
163     /** Constant for the "complexType" node in the XML schema namespace. */
164     public static final QName XSD_NODE_COMPLEXTYPE = QName.get("complexType", XSD_NAMESPACE);
165
166     /** Constant for the "element" node in the XML schema namespace. */
167     public static final QName XSD_NODE_ELEMENT = QName.get("element", XSD_NAMESPACE);
168
169     /** Constant for the "include" node in the XML schema namespace. */
170     public static final QName XSD_NODE_INCLUDE = QName.get("include", XSD_NAMESPACE);
171
172     /** Constant for the "schema" node in the XML schema namespace. */
173     public static final QName XSD_NODE_SCHEMA = QName.get("schema", XSD_NAMESPACE);
174
175     /** Constant for the "sequence" node in the XML schema namespace. */
176     public static final QName XSD_NODE_SEQUENCE = QName.get("sequence", XSD_NAMESPACE);
177
178     /** The XML content handler. */
179     private I_CmsXmlContentHandler m_contentHandler;
180
181     /** The set of included additional XML content definitions. */
182     private Set JavaDoc m_includes;
183
184     /** The inner element name of the content definition (type sequence). */
185     private String JavaDoc m_innerName;
186
187     /** The outer element name of the content definition (languange sequence). */
188     private String JavaDoc m_outerName;
189
190     /** The location from which the XML schema was read (XML system id). */
191     private String JavaDoc m_schemaLocation;
192
193     /** The main type name of this XML content definition. */
194     private String JavaDoc m_typeName;
195
196     /** The Map of configured types. */
197     private Map JavaDoc m_types;
198
199     /** The type sequence. */
200     private List JavaDoc m_typeSequence;
201
202     /**
203      * Creates a new XML content definition.<p>
204      *
205      * @param innerName the inner element name to use for the content definiton
206      * @param schemaLocation the location from which the XML schema was read (system id)
207      */

208     public CmsXmlContentDefinition(String JavaDoc innerName, String JavaDoc schemaLocation) {
209
210         this(innerName + "s", innerName, schemaLocation);
211     }
212
213     /**
214      * Creates a new XML content definition.<p>
215      *
216      * @param outerName the outer element name to use for the content definiton
217      * @param innerName the inner element name to use for the content definiton
218      * @param schemaLocation the location from which the XML schema was read (system id)
219      */

220     public CmsXmlContentDefinition(String JavaDoc outerName, String JavaDoc innerName, String JavaDoc schemaLocation) {
221
222         m_outerName = outerName;
223         m_innerName = innerName;
224         setInnerName(innerName);
225         m_typeSequence = new ArrayList JavaDoc();
226         m_types = new HashMap JavaDoc();
227         m_includes = new HashSet JavaDoc();
228         m_schemaLocation = schemaLocation;
229         m_contentHandler = new CmsDefaultXmlContentHandler();
230     }
231
232     /**
233      * Required empty constructor for clone operation.<p>
234      */

235     protected CmsXmlContentDefinition() {
236
237         // noop, required for clone operation
238
}
239
240     /**
241      * Factory method to unmarshal (read) a XML content definition instance from a byte array
242      * that contains XML data.<p>
243      *
244      * @param xmlData the XML data in a byte array
245      * @param schemaLocation the location from which the XML schema was read (system id)
246      * @param resolver the XML entitiy resolver to use
247      *
248      * @return a XML content definition instance unmarshalled from the byte array
249      *
250      * @throws CmsXmlException if something goes wrong
251      */

252     public static CmsXmlContentDefinition unmarshal(byte[] xmlData, String JavaDoc schemaLocation, EntityResolver JavaDoc resolver)
253     throws CmsXmlException {
254
255         CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
256         if (result == null) {
257             // content definition was not found in the cache, unmarshal the XML document
258
result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(xmlData, resolver), schemaLocation, resolver);
259         }
260         return result;
261     }
262
263     /**
264      * Factory method to unmarshal (read) a XML content definition instance from the OpenCms VFS resource name.<p>
265      *
266      * @param cms the current users CmsObject
267      * @param resourcename the resource name to unmarshal the XML content definition from
268      *
269      * @return a XML content definition instance unmarshalled from the VFS resource
270      *
271      * @throws CmsXmlException if something goes wrong
272      */

273     public static CmsXmlContentDefinition unmarshal(CmsObject cms, String JavaDoc resourcename) throws CmsXmlException {
274
275         CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);
276         String JavaDoc schemaLocation = CmsXmlEntityResolver.OPENCMS_SCHEME.concat(resourcename.substring(1));
277         CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
278         if (result == null) {
279             // content definition was not found in the cache, unmarshal the XML document
280
InputSource JavaDoc source = resolver.resolveEntity(null, schemaLocation);
281             result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(source, resolver), schemaLocation, resolver);
282         }
283         return result;
284     }
285
286     /**
287      * Factory method to unmarshal (read) a XML content definition instance from a XML document.<p>
288      *
289      * This method does additional validation to ensure the document has the required
290      * XML structure for a OpenCms content definition schema.<p>
291      *
292      * @param document the XML document to generate a XML content definition from
293      * @param schemaLocation the location from which the XML schema was read (system id)
294      *
295      * @return a XML content definition instance unmarshalled from the XML document
296      *
297      * @throws CmsXmlException if something goes wrong
298      */

299     public static CmsXmlContentDefinition unmarshal(Document document, String JavaDoc schemaLocation) throws CmsXmlException {
300
301         EntityResolver JavaDoc resolver = document.getEntityResolver();
302         CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
303         if (result == null) {
304             // content definition was not found in the cache, unmarshal the XML document
305
result = unmarshalInternal(document, schemaLocation, resolver);
306         }
307         return result;
308     }
309
310     /**
311      * Factory method to unmarshal (read) a XML content definition instance from a XML InputSource.<p>
312      *
313      * @param source the XML InputSource to use
314      * @param schemaLocation the location from which the XML schema was read (system id)
315      * @param resolver the XML entitiy resolver to use
316      *
317      * @return a XML content definition instance unmarshalled from the InputSource
318      *
319      * @throws CmsXmlException if something goes wrong
320      */

321     public static CmsXmlContentDefinition unmarshal(InputSource JavaDoc source, String JavaDoc schemaLocation, EntityResolver JavaDoc resolver)
322     throws CmsXmlException {
323
324         CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
325         if (result == null) {
326             // content definition was not found in the cache, unmarshal the XML document
327
result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(source, resolver), schemaLocation, resolver);
328         }
329         return result;
330     }
331
332     /**
333      * Factory method to unmarshal (read) a XML content definition instance from a given XML schema location.<p>
334      *
335      * The XML content definiton data to unmarshal will be read from the provided schema location using
336      * an XML InputSource.<p>
337      *
338      * @param schemaLocation the location from which to read the XML schema (system id)
339      * @param resolver the XML entitiy resolver to use
340      *
341      * @return a XML content definition instance unmarshalled from the InputSource
342      *
343      * @throws CmsXmlException if something goes wrong
344      * @throws SAXException if the XML schema location could not be converted to an XML InputSource
345      * @throws IOException if the XML schema location could not be converted to an XML InputSource
346      */

347     public static CmsXmlContentDefinition unmarshal(String JavaDoc schemaLocation, EntityResolver JavaDoc resolver)
348     throws CmsXmlException, SAXException JavaDoc, IOException JavaDoc {
349
350         CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
351         if (result == null) {
352             // content definition was not found in the cache, unmarshal the XML document
353
InputSource JavaDoc source = resolver.resolveEntity(null, schemaLocation);
354             result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(source, resolver), schemaLocation, resolver);
355         }
356         return result;
357     }
358
359     /**
360      * Factory method to unmarshal (read) a XML content definition instance from a String
361      * that contains XML data.<p>
362      *
363      * @param xmlData the XML data in a String
364      * @param schemaLocation the location from which the XML schema was read (system id)
365      * @param resolver the XML entitiy resolver to use
366      *
367      * @return a XML content definition instance unmarshalled from the byte array
368      *
369      * @throws CmsXmlException if something goes wrong
370      */

371     public static CmsXmlContentDefinition unmarshal(String JavaDoc xmlData, String JavaDoc schemaLocation, EntityResolver JavaDoc resolver)
372     throws CmsXmlException {
373
374         CmsXmlContentDefinition result = getCachedContentDefinition(schemaLocation, resolver);
375         if (result == null) {
376             // content definition was not found in the cache, unmarshal the XML document
377
result = unmarshalInternal(CmsXmlUtils.unmarshalHelper(xmlData, resolver), schemaLocation, resolver);
378         }
379         return result;
380     }
381
382     /**
383      * Creates the name of the type attribute from the given content name.<p>
384      *
385      * @param name the name to use
386      *
387      * @return the name of the type attribute
388      */

389     protected static String JavaDoc createTypeName(String JavaDoc name) {
390
391         StringBuffer JavaDoc result = new StringBuffer JavaDoc(32);
392         result.append("OpenCms");
393         result.append(name.substring(0, 1).toUpperCase());
394         if (name.length() > 1) {
395             result.append(name.substring(1));
396         }
397         return result.toString();
398     }
399
400     /**
401      * Validates if a given attribute exists at the given element with an (optional) specified value.<p>
402      *
403      * If the required value is not <code>null</code>, the attribute must have excatly this
404      * value set.<p>
405      *
406      * If no value is required, some simple validation is performed on the attribute value,
407      * like a check that the value does not have leading or trainling white spaces.<p>
408      *
409      * @param element the element to validate
410      * @param attributeName the attribute to check for
411      * @param requiredValue the required value of the attribute, or <code>null</code> if any value is allowed
412      *
413      * @return the value of the attribute
414      *
415      * @throws CmsXmlException if the element does not have the required attribute set, or if the validation fails
416      */

417     protected static String JavaDoc validateAttribute(Element element, String JavaDoc attributeName, String JavaDoc requiredValue)
418     throws CmsXmlException {
419
420         Attribute attribute = element.attribute(attributeName);
421         if (attribute == null) {
422             throw new CmsXmlException(Messages.get().container(
423                 Messages.ERR_EL_MISSING_ATTRIBUTE_2,
424                 element.getUniquePath(),
425                 attributeName));
426         }
427         String JavaDoc value = attribute.getValue();
428
429         if (requiredValue == null) {
430             if (CmsStringUtil.isEmptyOrWhitespaceOnly(value) || !value.equals(value.trim())) {
431                 throw new CmsXmlException(Messages.get().container(
432                     Messages.ERR_EL_BAD_ATTRIBUTE_WS_3,
433                     element.getUniquePath(),
434                     attributeName,
435                     value));
436             }
437         } else {
438             if (!requiredValue.equals(value)) {
439                 throw new CmsXmlException(Messages.get().container(
440                     Messages.ERR_EL_BAD_ATTRIBUTE_VALUE_4,
441                     new Object JavaDoc[] {element.getUniquePath(), attributeName, requiredValue, value}));
442             }
443         }
444         return value;
445     }
446
447     /**
448      * Validates if a gicen element has exactly the required attributes set.<p>
449      *
450      * @param element the element to validate
451      * @param requiredAttributes the list of required attributes
452      * @param optionalAttributes the list of optional attributes
453      *
454      * @throws CmsXmlException if the validation fails
455      */

456     protected static void validateAttributesExists(
457         Element element,
458         String JavaDoc[] requiredAttributes,
459         String JavaDoc[] optionalAttributes) throws CmsXmlException {
460
461         if (element.attributeCount() < requiredAttributes.length) {
462             throw new CmsXmlException(Messages.get().container(
463                 Messages.ERR_EL_ATTRIBUTE_TOOFEW_3,
464                 element.getUniquePath(),
465                 new Integer JavaDoc(requiredAttributes.length),
466                 new Integer JavaDoc(element.attributeCount())));
467         }
468
469         if (element.attributeCount() > (requiredAttributes.length + optionalAttributes.length)) {
470             throw new CmsXmlException(Messages.get().container(
471                 Messages.ERR_EL_ATTRIBUTE_TOOMANY_3,
472                 element.getUniquePath(),
473                 new Integer JavaDoc(requiredAttributes.length + optionalAttributes.length),
474                 new Integer JavaDoc(element.attributeCount())));
475         }
476
477         List JavaDoc attributes = element.attributes();
478
479         for (int i = 0; i < requiredAttributes.length; i++) {
480             String JavaDoc attributeName = requiredAttributes[i];
481             if (element.attribute(attributeName) == null) {
482                 throw new CmsXmlException(Messages.get().container(
483                     Messages.ERR_EL_MISSING_ATTRIBUTE_2,
484                     element.getUniquePath(),
485                     attributeName));
486             }
487         }
488
489         List JavaDoc rA = Arrays.asList(requiredAttributes);
490         List JavaDoc oA = Arrays.asList(optionalAttributes);
491
492         for (int i = 0; i < attributes.size(); i++) {
493             String JavaDoc attributeName = element.attribute(i).getName();
494             if (!rA.contains(attributeName) && !oA.contains(attributeName)) {
495                 throw new CmsXmlException(Messages.get().container(
496                     Messages.ERR_EL_INVALID_ATTRIBUTE_2,
497                     element.getUniquePath(),
498                     attributeName));
499             }
500         }
501     }
502
503     /**
504      * Validates the given element as a complex type sequence.<p>
505      *
506      * @param element the element to validate
507      * @param includes the XML schema includes
508      * @param definition the content definition the complex type seqnence belongs to
509      *
510      * @return a data structure containing the validated complex type seqnence data
511      *
512      * @throws CmsXmlException if the validation fails
513      */

514     protected static CmsXmlComplexTypeSequence validateComplexTypeSequence(
515         Element element,
516         Set JavaDoc includes,
517         CmsXmlContentDefinition definition) throws CmsXmlException {
518
519         validateAttributesExists(element, new String JavaDoc[] {XSD_ATTRIBUTE_NAME}, new String JavaDoc[0]);
520
521         String JavaDoc name = validateAttribute(element, XSD_ATTRIBUTE_NAME, null);
522
523         // now check the type definition list
524
List JavaDoc mainElements = element.elements();
525         if ((mainElements.size() != 1) && (mainElements.size() != 2)) {
526             throw new CmsXmlException(Messages.get().container(
527                 Messages.ERR_TS_SUBELEMENT_COUNT_2,
528                 element.getUniquePath(),
529                 new Integer JavaDoc(mainElements.size())));
530         }
531
532         boolean hasLanguageAttribute = false;
533         if (mainElements.size() == 2) {
534             // two elements in the master list: the second must be the "language" attribute definition
535

536             Element typeAttribute = (Element)mainElements.get(1);
537             if (!XSD_NODE_ATTRIBUTE.equals(typeAttribute.getQName())) {
538                 throw new CmsXmlException(Messages.get().container(
539                     Messages.ERR_CD_ELEMENT_NAME_3,
540                     typeAttribute.getUniquePath(),
541                     XSD_NODE_ATTRIBUTE.getQualifiedName(),
542                     typeAttribute.getQName().getQualifiedName()));
543             }
544             validateAttribute(typeAttribute, XSD_ATTRIBUTE_NAME, XSD_ATTRIBUTE_VALUE_LANGUAGE);
545             validateAttribute(typeAttribute, XSD_ATTRIBUTE_TYPE, CmsXmlLocaleValue.TYPE_NAME);
546             try {
547                 validateAttribute(typeAttribute, XSD_ATTRIBUTE_USE, XSD_ATTRIBUTE_VALUE_REQUIRED);
548             } catch (CmsXmlException e) {
549                 validateAttribute(typeAttribute, XSD_ATTRIBUTE_USE, XSD_ATTRIBUTE_VALUE_OPTIONAL);
550             }
551             // no error: then the language attribute is valid
552
hasLanguageAttribute = true;
553         }
554
555         // check the main element type sequence
556
Element typeSequence = (Element)mainElements.get(0);
557         if (!XSD_NODE_SEQUENCE.equals(typeSequence.getQName())) {
558             throw new CmsXmlException(Messages.get().container(
559                 Messages.ERR_CD_ELEMENT_NAME_3,
560                 typeSequence.getUniquePath(),
561                 XSD_NODE_SEQUENCE.getQualifiedName(),
562                 typeSequence.getQName().getQualifiedName()));
563         }
564
565         // check the type definition sequence
566
List JavaDoc typeSequenceElements = typeSequence.elements();
567         if (typeSequenceElements.size() < 1) {
568             throw new CmsXmlException(Messages.get().container(
569                 Messages.ERR_TS_SUBELEMENT_TOOFEW_3,
570                 typeSequence.getUniquePath(),
571                 new Integer JavaDoc(1),
572                 new Integer JavaDoc(typeSequenceElements.size())));
573         }
574
575         // now add all type definitions from the schema
576
List JavaDoc sequence = new ArrayList JavaDoc();
577
578         if (hasLanguageAttribute) {
579             // only generate types for sequence node with language attribute
580

581             CmsXmlContentTypeManager typeManager = OpenCms.getXmlContentTypeManager();
582             Iterator JavaDoc i = typeSequenceElements.iterator();
583             while (i.hasNext()) {
584                 sequence.add(typeManager.getContentType((Element)i.next(), includes));
585             }
586         } else {
587             // generate a nested content definition for the main type sequence
588

589             Element e = (Element)typeSequenceElements.get(0);
590             String JavaDoc typeName = validateAttribute(e, XSD_ATTRIBUTE_NAME, null);
591             String JavaDoc minOccurs = validateAttribute(e, XSD_ATTRIBUTE_MIN_OCCURS, XSD_ATTRIBUTE_VALUE_ZERO);
592             String JavaDoc maxOccurs = validateAttribute(e, XSD_ATTRIBUTE_MAX_OCCURS, XSD_ATTRIBUTE_VALUE_UNBOUNDED);
593             validateAttribute(e, XSD_ATTRIBUTE_TYPE, createTypeName(typeName));
594
595             CmsXmlNestedContentDefinition cd = new CmsXmlNestedContentDefinition(null, typeName, minOccurs, maxOccurs);
596             sequence.add(cd);
597         }
598
599         // return a data structure with the collected values
600
return definition.new CmsXmlComplexTypeSequence(name, sequence, hasLanguageAttribute);
601     }
602
603     /**
604      * Looks up the given XML content definition system id in the internal content definition cache.<p>
605      *
606      * @param schemaLocation the system id of the XML content definition to look up
607      * @param resolver the XML entitiy resolver to use (contains the cache)
608      *
609      * @return the XML content definition found, or null if no definition is cached for the given system id
610      */

611     private static CmsXmlContentDefinition getCachedContentDefinition(String JavaDoc schemaLocation, EntityResolver JavaDoc resolver) {
612
613         if (resolver instanceof CmsXmlEntityResolver) {
614             // check for a cached version of this content definition
615
CmsXmlEntityResolver cmsResolver = (CmsXmlEntityResolver)resolver;
616             return cmsResolver.getCachedContentDefinition(schemaLocation);
617         }
618         return null;
619     }
620
621     /**
622      * Internal method to unmarshal (read) a XML content definition instance from a XML document.<p>
623      *
624      * It is assumed that the XML content definition cache has already been tested and the document
625      * has not been found in the cache. After the XML content definition has been successfully created,
626      * it is placed in the cache.<p>
627      *
628      * @param document the XML document to generate a XML content definition from
629      * @param schemaLocation the location from which the XML schema was read (system id)
630      * @param resolver the XML entitiy resolver used by the given XML document
631      *
632      * @return a XML content definition instance unmarshalled from the XML document
633      *
634      * @throws CmsXmlException if something goes wrong
635      */

636     private static CmsXmlContentDefinition unmarshalInternal(
637         Document document,
638         String JavaDoc schemaLocation,
639         EntityResolver JavaDoc resolver) throws CmsXmlException {
640
641         // analyze the document and generate the XML content type definition
642
Element root = document.getRootElement();
643         if (!XSD_NODE_SCHEMA.equals(root.getQName())) {
644             // schema node is required
645
throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_NO_SCHEMA_NODE_0));
646         }
647
648         List JavaDoc includes = root.elements(XSD_NODE_INCLUDE);
649         if (includes.size() < 1) {
650             // one include is required
651
throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_ONE_INCLUDE_REQUIRED_0));
652         }
653
654         Element include = (Element)includes.get(0);
655         String JavaDoc target = validateAttribute(include, XSD_ATTRIBUTE_SCHEMA_LOCATION, null);
656         if (!XSD_INCLUDE_OPENCMS.equals(target)) {
657             // the first include must point to the default OpenCms standard schema include
658
throw new CmsXmlException(Messages.get().container(
659                 Messages.ERR_CD_FIRST_INCLUDE_2,
660                 XSD_INCLUDE_OPENCMS,
661                 target));
662         }
663
664         Set JavaDoc nestedDefinitions = new HashSet JavaDoc();
665         if (includes.size() > 1) {
666             // resolve additional, nested include calls
667
for (int i = 1; i < includes.size(); i++) {
668
669                 Element inc = (Element)includes.get(i);
670                 String JavaDoc schemaLoc = validateAttribute(inc, XSD_ATTRIBUTE_SCHEMA_LOCATION, null);
671                 InputSource JavaDoc source = null;
672                 try {
673                     source = resolver.resolveEntity(null, schemaLoc);
674                 } catch (Exception JavaDoc e) {
675                     throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_BAD_INCLUDE_1, schemaLoc));
676                 }
677                 CmsXmlContentDefinition xmlContentDefinition = unmarshal(source, schemaLoc, resolver);
678                 nestedDefinitions.add(xmlContentDefinition);
679             }
680         }
681
682         List JavaDoc elements = root.elements(XSD_NODE_ELEMENT);
683         if (elements.size() != 1) {
684             // only one root element is allowed
685
throw new CmsXmlException(Messages.get().container(
686                 Messages.ERR_CD_ROOT_ELEMENT_COUNT_1,
687                 XSD_INCLUDE_OPENCMS,
688                 new Integer JavaDoc(elements.size())));
689         }
690
691         // collect the data from the root element node
692
Element main = (Element)elements.get(0);
693         String JavaDoc name = validateAttribute(main, XSD_ATTRIBUTE_NAME, null);
694
695         // now process the complex types
696
List JavaDoc complexTypes = root.elements(XSD_NODE_COMPLEXTYPE);
697         if (complexTypes.size() != 2) {
698             // exactly two complex types are required
699
throw new CmsXmlException(Messages.get().container(
700                 Messages.ERR_CD_COMPLEX_TYPE_COUNT_1,
701                 new Integer JavaDoc(complexTypes.size())));
702         }
703
704         // generate the result XML content definition
705
CmsXmlContentDefinition result = new CmsXmlContentDefinition(name, null, schemaLocation);
706
707         // set the nested definitions
708
result.m_includes = nestedDefinitions;
709
710         List JavaDoc complexTypeData = new ArrayList JavaDoc();
711         Iterator JavaDoc ct = complexTypes.iterator();
712         while (ct.hasNext()) {
713             Element e = (Element)ct.next();
714             CmsXmlComplexTypeSequence sequence = validateComplexTypeSequence(e, nestedDefinitions, result);
715             complexTypeData.add(sequence);
716         }
717
718         // get the outer element sequence, this must be the first element
719
CmsXmlComplexTypeSequence outerSequence = (CmsXmlComplexTypeSequence)complexTypeData.get(0);
720         CmsXmlNestedContentDefinition outer = (CmsXmlNestedContentDefinition)outerSequence.m_sequence.get(0);
721
722         // make sure the inner and outer element names are as required
723
String JavaDoc outerTypeName = createTypeName(name);
724         String JavaDoc innerTypeName = createTypeName(outer.getName());
725         validateAttribute((Element)complexTypes.get(0), XSD_ATTRIBUTE_NAME, outerTypeName);
726         validateAttribute((Element)complexTypes.get(1), XSD_ATTRIBUTE_NAME, innerTypeName);
727         validateAttribute(main, XSD_ATTRIBUTE_TYPE, outerTypeName);
728
729         // the inner name is the element name set in the outer sequence
730
result.setInnerName(outer.getName());
731
732         // get the inner element sequence, this must be the second element
733
CmsXmlComplexTypeSequence innerSequence = (CmsXmlComplexTypeSequence)complexTypeData.get(1);
734
735         // add the types from the main sequence node
736
Iterator JavaDoc it = innerSequence.m_sequence.iterator();
737         while (it.hasNext()) {
738             result.addType((I_CmsXmlSchemaType)it.next());
739         }
740
741         // resolve the XML content handler information
742
List JavaDoc annotations = root.elements(XSD_NODE_ANNOTATION);
743         I_CmsXmlContentHandler contentHandler = null;
744         Element appInfoElement = null;
745
746         if (annotations.size() > 0) {
747             List JavaDoc appinfos = ((Element)annotations.get(0)).elements(XSD_NODE_APPINFO);
748
749             if (appinfos.size() > 0) {
750                 // the first appinfo node contains the specific XML content data
751
appInfoElement = (Element)appinfos.get(0);
752
753                 // check for a special content handler in the appinfo node
754
Element handlerElement = appInfoElement.element("handler");
755                 if (handlerElement != null) {
756                     String JavaDoc className = handlerElement.attributeValue("class");
757                     if (className != null) {
758                         contentHandler = OpenCms.getXmlContentTypeManager().getContentHandler(className, schemaLocation);
759                     }
760                 }
761             }
762         }
763
764         if (contentHandler == null) {
765             // if no content handler is defined, the default handler is used
766
contentHandler = OpenCms.getXmlContentTypeManager().getContentHandler(
767                 CmsDefaultXmlContentHandler.class.getName(),
768                 name);
769         }
770
771         // analyze the app info node with the selected XML content handler
772
contentHandler.initialize(appInfoElement, result);
773         result.m_contentHandler = contentHandler;
774
775         result.freeze();
776
777         if (resolver instanceof CmsXmlEntityResolver) {
778             // put the generated content definition in the cache
779
((CmsXmlEntityResolver)resolver).cacheContentDefinition(schemaLocation, result);
780         }
781
782         return result;
783     }
784
785     /**
786      * Adds the missing default XML according to this content definition to the given document element.<p>
787      *
788      * In case the root element already contains subnodes, only missing subnodes are added.<p>
789      *
790      * @param cms the current users OpenCms context
791      * @param document the document where the XML is added in (required for default XML generation)
792      * @param root the root node to add the missing XML for
793      * @param locale the locale to add the XML for
794      *
795      * @return the given root element with the missing content added
796      */

797     public Element addDefaultXml(CmsObject cms, I_CmsXmlDocument document, Element root, Locale JavaDoc locale) {
798
799         Iterator JavaDoc i = m_typeSequence.iterator();
800         int currentPos = 0;
801         List JavaDoc allElements = root.elements();
802
803         while (i.hasNext()) {
804             I_CmsXmlSchemaType type = (I_CmsXmlSchemaType)i.next();
805
806             // check how many elements of this type already exist in the XML
807
String JavaDoc elementName = type.getName();
808             List JavaDoc elements = root.elements(elementName);
809
810             currentPos += elements.size();
811             for (int j = elements.size(); j < type.getMinOccurs(); j++) {
812                 // append the missing elements
813
Element typeElement = type.generateXml(cms, document, root, locale);
814                 // need to check for default value again because the of appinfo "mappings" node
815
I_CmsXmlContentValue value = type.createValue(document, typeElement, locale);
816                 String JavaDoc defaultValue = document.getContentDefinition().getContentHandler().getDefault(cms, value, locale);
817                 if (defaultValue != null) {
818                     // only if there is a default value available use it to overwrite the initial default
819
value.setStringValue(cms, defaultValue);
820                 }
821
822                 // re-sort elements as they have been appended to the end of the XML root, not at the correct position
823
typeElement.detach();
824                 allElements.add(currentPos, typeElement);
825                 currentPos++;
826             }
827         }
828
829         return root;
830     }
831
832     /**
833      * Adds a nested (included) XML content definition.<p>
834      *
835      * @param nestedSchema the nested (included) XML content definition to add
836      */

837     public void addInclude(CmsXmlContentDefinition nestedSchema) {
838
839         m_includes.add(nestedSchema);
840     }
841
842     /**
843      * Adds the given content type.<p>
844      *
845      * @param type the content type to add
846      *
847      * @throws CmsXmlException in case an unregisterd type is added
848      */

849     public void addType(I_CmsXmlSchemaType type) throws CmsXmlException {
850
851         // check if the type to add actually exists in the type manager
852
CmsXmlContentTypeManager typeManager = OpenCms.getXmlContentTypeManager();
853         if (type.isSimpleType() && (typeManager.getContentType(type.getTypeName()) == null)) {
854             throw new CmsXmlException(Messages.get().container(Messages.ERR_UNREGISTERED_TYPE_1, type.getTypeName()));
855         }
856
857         // add the type to the internal type sequence and lookup table
858
m_typeSequence.add(type);
859         m_types.put(type.getName(), type);
860
861         // store reference to the content definition in the type
862
type.setContentDefinition(this);
863     }
864
865     /**
866      * Creates a clone of this XML content definition.<p>
867      *
868      * @return a clone of this XML content definition
869      */

870     public Object JavaDoc clone() {
871
872         CmsXmlContentDefinition result = new CmsXmlContentDefinition();
873         result.m_innerName = m_innerName;
874         result.m_schemaLocation = m_schemaLocation;
875         result.m_typeSequence = m_typeSequence;
876         result.m_types = m_types;
877         result.m_contentHandler = m_contentHandler;
878         result.m_typeName = m_typeName;
879         result.m_includes = m_includes;
880         return result;
881     }
882
883     /**
884      * Generates the default XML content for this content definition, and append it to the given root element.<p>
885      *
886      * Please note: The default values for the annotations are read from the content definition of the given
887      * document. For a nested content definitions, this means that all defaults are set in the annotations of the
888      * "outer" or "main" content defintition.<p>
889      *
890      * @param cms the current users OpenCms context
891      * @param document the OpenCms XML document the XML is created for
892      * @param root the node of the document where to append the generated XML to
893      * @param locale the locale to create the default element in the document with
894      *
895      * @return the default XML content for this content definition, and append it to the given root element
896      */

897     public Element createDefaultXml(CmsObject cms, I_CmsXmlDocument document, Element root, Locale JavaDoc locale) {
898
899         Iterator JavaDoc i = m_typeSequence.iterator();
900         while (i.hasNext()) {
901             I_CmsXmlSchemaType type = (I_CmsXmlSchemaType)i.next();
902             for (int j = 0; j < type.getMinOccurs(); j++) {
903                 Element typeElement = type.generateXml(cms, document, root, locale);
904                 // need to check for default value again because the of appinfo "mappings" node
905
I_CmsXmlContentValue value = type.createValue(document, typeElement, locale);
906                 String JavaDoc defaultValue = document.getContentDefinition().getContentHandler().getDefault(cms, value, locale);
907                 if (defaultValue != null) {
908                     // only if there is a default value available use it to overwrite the initial default
909
value.setStringValue(cms, defaultValue);
910                 }
911             }
912         }
913
914         return root;
915     }
916
917     /**
918      * Generates a valid XML document according to the XML schema of this content definition.<p>
919      *
920      * @param cms the current users OpenCms context
921      * @param document the OpenCms XML document the XML is created for
922      * @param locale the locale to create the default element in the document with
923      *
924      * @return a valid XML document according to the XML schema of this content definition
925      */

926     public Document createDocument(CmsObject cms, I_CmsXmlDocument document, Locale JavaDoc locale) {
927
928         Document doc = DocumentHelper.createDocument();
929
930         Element root = doc.addElement(getOuterName());
931
932         root.add(I_CmsXmlSchemaType.XSI_NAMESPACE);
933         root.addAttribute(I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION, getSchemaLocation());
934
935         createLocale(cms, document, root, locale);
936         return doc;
937     }
938
939     /**
940      * Generates a valid locale (language) element for the XML schema of this content definition.<p>
941      *
942      * @param cms the current users OpenCms context
943      * @param document the OpenCms XML document the XML is created for
944      * @param root the root node of the document where to append the locale to
945      * @param locale the locale to create the default element in the document with
946      *
947      * @return a valid XML element for the locale according to the XML schema of this content definition
948      */

949     public Element createLocale(CmsObject cms, I_CmsXmlDocument document, Element root, Locale JavaDoc locale) {
950
951         // add an element with a "locale" attribute to the given root node
952
Element element = root.addElement(getInnerName());
953         element.addAttribute(XSD_ATTRIBUTE_VALUE_LANGUAGE, locale.toString());
954
955         // now generate the default XML for the element
956
return createDefaultXml(cms, document, element, locale);
957     }
958
959     /**
960      * @see java.lang.Object#equals(java.lang.Object)
961      */

962     public boolean equals(Object JavaDoc obj) {
963
964         if (obj == this) {
965             return true;
966         }
967         if (!(obj instanceof CmsXmlContentDefinition)) {
968             return false;
969         }
970         CmsXmlContentDefinition other = (CmsXmlContentDefinition)obj;
971         if (!getInnerName().equals(other.getInnerName())) {
972             return false;
973         }
974         if (!getOuterName().equals(other.getOuterName())) {
975             return false;
976         }
977         return m_typeSequence.equals(other.m_typeSequence);
978     }
979
980     /**
981      * Freezes this content definition, making all internal data structures
982      * unmodifiable.<p>
983      *
984      * This is required to prevent modification of a cached content definition.<p>
985      */

986     public void freeze() {
987
988         m_types = Collections.unmodifiableMap(m_types);
989         m_typeSequence = Collections.unmodifiableList(m_typeSequence);
990     }
991
992     /**
993      * Returns the selected XML content handler for this XML content definition.<p>
994      *
995      * If no specific XML content handler was provided in the "appinfo" node of the
996      * XML schema, the default XML content handler <code>{@link CmsDefaultXmlContentHandler}</code> is used.<p>
997      *
998      * @return the contentHandler
999      */

1000    public I_CmsXmlContentHandler getContentHandler() {
1001
1002        return m_contentHandler;
1003    }
1004
1005    /**
1006     * Returns the set of nested (included) XML content definitions.<p>
1007     *
1008     * @return the set of nested (included) XML content definitions
1009     */

1010    public Set JavaDoc getIncludes() {
1011
1012        return m_includes;
1013    }
1014
1015    /**
1016     * Returns the inner element name of this content definiton.<p>
1017     *
1018     * @return the inner element name of this content definiton
1019     */

1020    public String JavaDoc getInnerName() {
1021
1022        return m_innerName;
1023    }
1024
1025    /**
1026     * Returns the outer element name of this content definiton.<p>
1027     *
1028     * @return the outer element name of this content definiton
1029     */

1030    public String JavaDoc getOuterName() {
1031
1032        return m_outerName;
1033    }
1034
1035    /**
1036     * Generates an XML schema for the content definition.<p>
1037     *
1038     * @return the generated XML schema
1039     */

1040    public Document getSchema() {
1041
1042        Document schema = DocumentHelper.createDocument();
1043
1044        Element root = schema.addElement(XSD_NODE_SCHEMA);
1045        root.addAttribute(XSD_ATTRIBUTE_ELEMENT_FORM_DEFAULT, XSD_ATTRIBUTE_VALUE_QUALIFIED);
1046
1047        Element include = root.addElement(XSD_NODE_INCLUDE);
1048        include.addAttribute(XSD_ATTRIBUTE_SCHEMA_LOCATION, XSD_INCLUDE_OPENCMS);
1049
1050        if (m_includes.size() > 0) {
1051            Iterator JavaDoc i = m_includes.iterator();
1052            while (i.hasNext()) {
1053                CmsXmlContentDefinition definition = (CmsXmlContentDefinition)i.next();
1054                root.addElement(XSD_NODE_INCLUDE).addAttribute(
1055                    XSD_ATTRIBUTE_SCHEMA_LOCATION,
1056                    definition.m_schemaLocation);
1057            }
1058        }
1059
1060        String JavaDoc outerTypeName = createTypeName(getOuterName());
1061        String JavaDoc innerTypeName = createTypeName(getInnerName());
1062
1063        Element content = root.addElement(XSD_NODE_ELEMENT);
1064        content.addAttribute(XSD_ATTRIBUTE_NAME, getOuterName());
1065        content.addAttribute(XSD_ATTRIBUTE_TYPE, outerTypeName);
1066
1067        Element list = root.addElement(XSD_NODE_COMPLEXTYPE);
1068        list.addAttribute(XSD_ATTRIBUTE_NAME, outerTypeName);
1069
1070        Element listSequence = list.addElement(XSD_NODE_SEQUENCE);
1071        Element listElement = listSequence.addElement(XSD_NODE_ELEMENT);
1072        listElement.addAttribute(XSD_ATTRIBUTE_NAME, getInnerName());
1073        listElement.addAttribute(XSD_ATTRIBUTE_TYPE, innerTypeName);
1074        listElement.addAttribute(XSD_ATTRIBUTE_MIN_OCCURS, XSD_ATTRIBUTE_VALUE_ZERO);
1075        listElement.addAttribute(XSD_ATTRIBUTE_MAX_OCCURS, XSD_ATTRIBUTE_VALUE_UNBOUNDED);
1076
1077        Element main = root.addElement(XSD_NODE_COMPLEXTYPE);
1078        main.addAttribute(XSD_ATTRIBUTE_NAME, innerTypeName);
1079
1080        Element mainSequence = main.addElement(XSD_NODE_SEQUENCE);
1081
1082        Iterator JavaDoc i = m_typeSequence.iterator();
1083        while (i.hasNext()) {
1084            I_CmsXmlSchemaType schemaType = (I_CmsXmlSchemaType)i.next();
1085            schemaType.appendXmlSchema(mainSequence);
1086        }
1087
1088        Element language = main.addElement(XSD_NODE_ATTRIBUTE);
1089        language.addAttribute(XSD_ATTRIBUTE_NAME, XSD_ATTRIBUTE_VALUE_LANGUAGE);
1090        language.addAttribute(XSD_ATTRIBUTE_TYPE, CmsXmlLocaleValue.TYPE_NAME);
1091        language.addAttribute(XSD_ATTRIBUTE_USE, XSD_ATTRIBUTE_VALUE_REQUIRED);
1092
1093        return schema;
1094    }
1095
1096    /**
1097     * Returns the location from which the XML schema was read (XML system id).<p>
1098     *
1099     * @return the location from which the XML schema was read (XML system id)
1100     */

1101    public String JavaDoc getSchemaLocation() {
1102
1103        return m_schemaLocation;
1104    }
1105
1106    /**
1107     * Returns the scheme type for the given element name, or <code>null</code> if no
1108     * node is defined with this name.<p>
1109     *
1110     * @param elementPath the element path to look up the type for
1111     * @return the type for the given element name, or <code>null</code> if no
1112     * node is defined with this name
1113     */

1114    public I_CmsXmlSchemaType getSchemaType(String JavaDoc elementPath) {
1115
1116        String JavaDoc path = CmsXmlUtils.getFirstXpathElement(elementPath);
1117
1118        I_CmsXmlSchemaType type = (I_CmsXmlSchemaType)m_types.get(path);
1119        if (type == null) {
1120            // no node with the given path defined in schema
1121
return null;
1122        }
1123
1124        // check if recursion is required to get value from a nested schema
1125
if (type.isSimpleType() || !CmsXmlUtils.isDeepXpath(elementPath)) {
1126            // no recusion required
1127
return type;
1128        }
1129
1130        // recusion required since the path is an Xpath
1131
CmsXmlNestedContentDefinition nestedDefinition = (CmsXmlNestedContentDefinition)type;
1132        path = CmsXmlUtils.removeFirstXpathElement(elementPath);
1133        return nestedDefinition.getNestedContentDefinition().getSchemaType(path);
1134    }
1135
1136    /**
1137     * Returns the main type name of this XML content definition.<p>
1138     *
1139     * @return the main type name of this XML content definition
1140     */

1141    public String JavaDoc getTypeName() {
1142
1143        return m_typeName;
1144    }
1145
1146    /**
1147     * Returns the type sequence.<p>
1148     *
1149     * @return the type sequence
1150     */

1151    public List JavaDoc getTypeSequence() {
1152
1153        return m_typeSequence;
1154    }
1155
1156    /**
1157     * @see java.lang.Object#hashCode()
1158     */

1159    public int hashCode() {
1160
1161        return getInnerName().hashCode();
1162    }
1163
1164    /**
1165     * Sets the inner element name to use for the content definiton.<p>
1166     *
1167     * @param innerName the inner element name to set
1168     */

1169    protected void setInnerName(String JavaDoc innerName) {
1170
1171        m_innerName = innerName;
1172        if (m_innerName != null) {
1173            m_typeName = createTypeName(innerName);
1174        }
1175    }
1176
1177    /**
1178     * Sets the outer element name to use for the content definiton.<p>
1179     *
1180     * @param outerName the outer element name to set
1181     */

1182    protected void setOuterName(String JavaDoc outerName) {
1183
1184        m_outerName = outerName;
1185    }
1186}
Popular Tags