001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/framework/xml/schema/XSDocument.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstraße 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.framework.xml.schema;
044    
045    import java.net.URI;
046    import java.util.List;
047    
048    import org.deegree.datatypes.QualifiedName;
049    import org.deegree.framework.log.ILogger;
050    import org.deegree.framework.log.LoggerFactory;
051    import org.deegree.framework.xml.XMLFragment;
052    import org.deegree.framework.xml.XMLParsingException;
053    import org.deegree.framework.xml.XMLTools;
054    import org.w3c.dom.Element;
055    import org.w3c.dom.Node;
056    
057    /**
058     * Parser for XML schema documents.
059     * 
060     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
061     * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh </a>
062     * @author last edited by: $Author: apoth $
063     * 
064     * @version $Revision: 9339 $, $Date: 2007-12-27 13:31:52 +0100 (Do, 27 Dez 2007) $
065     */
066    public class XSDocument extends XMLFragment {
067    
068        private static final long serialVersionUID = 4371672452129797159L;
069    
070        private URI targetNamespace;
071    
072        private final ILogger LOG = LoggerFactory.getLogger( XSDocument.class );
073    
074        /**
075         * Returns the class representation of the underlying schema document.
076         * 
077         * @return class representation of the underlying schema document
078         * @throws XMLParsingException
079         * @throws XMLSchemaException
080         */
081        public XMLSchema parseXMLSchema()
082                                throws XMLParsingException, XMLSchemaException {
083            SimpleTypeDeclaration[] simpleTypes = extractSimpleTypeDeclarations();
084            ComplexTypeDeclaration[] complexTypes = extractComplexTypeDeclarations();
085            ElementDeclaration[] elementDeclarations = extractElementDeclarations();
086            return new XMLSchema( getTargetNamespace(), simpleTypes, complexTypes, elementDeclarations );
087        }
088    
089        /**
090         * Returns the target namespace of the underlying schema document.
091         * 
092         * @return target namespace of the underlying schema document
093         * @throws XMLParsingException
094         */
095        public synchronized URI getTargetNamespace()
096                                throws XMLParsingException {
097            if ( this.targetNamespace == null ) {
098                this.targetNamespace = XMLTools.getRequiredNodeAsURI( this.getRootElement(), "@targetNamespace", nsContext );
099            }
100            return this.targetNamespace;
101        }
102    
103        /**
104         * Extracts all global (top-level) simple type declarations from the underlying schema document.
105         * 
106         * @return all global (top-level) simple type declarations
107         * @throws XMLParsingException
108         *             if the document is not a valid XML Schema document or does not match the
109         *             limitations of this class
110         */
111        public SimpleTypeDeclaration[] extractSimpleTypeDeclarations()
112                                throws XMLParsingException {
113            List simpleTypeElements = XMLTools.getNodes( this.getRootElement(), getFullName( "simpleType" ), nsContext );
114            LOG.logDebug( "Found " + simpleTypeElements.size() + " simple type declarations." );
115            SimpleTypeDeclaration[] simpleTypeDeclarations = new SimpleTypeDeclaration[simpleTypeElements.size()];
116            for ( int i = 0; i < simpleTypeDeclarations.length; i++ ) {
117                simpleTypeDeclarations[i] = parseSimpleTypeDeclaration( (Element) simpleTypeElements.get( i ) );
118            }
119            return simpleTypeDeclarations;
120        }
121    
122        /**
123         * Extracts all global (top-level) complex type declarations from the underlying schema
124         * document.
125         * 
126         * @return all global (top-level) complex type declarations
127         * @throws XMLParsingException
128         *             if the document is not a valid XML Schema document or does not match the
129         *             limitations of this class
130         */
131        public ComplexTypeDeclaration[] extractComplexTypeDeclarations()
132                                throws XMLParsingException {
133            List complexTypeElements = XMLTools.getNodes( this.getRootElement(), getFullName( "complexType" ), nsContext );
134            LOG.logDebug( "Found " + complexTypeElements.size() + " complex type declarations." );
135            ComplexTypeDeclaration[] complexTypeDeclarations = new ComplexTypeDeclaration[complexTypeElements.size()];
136            for ( int i = 0; i < complexTypeDeclarations.length; i++ ) {
137                complexTypeDeclarations[i] = parseComplexTypeDeclaration( (Element) complexTypeElements.get( i ) );
138            }
139            return complexTypeDeclarations;
140        }
141    
142        /**
143         * Extracts all global (top-level) element declarations from the underlying schema document.
144         * 
145         * @return all global (top-level) element declarations
146         * @throws XMLParsingException
147         *             if the document is not a valid XML Schema document or does not match the
148         *             limitations of this class
149         */
150        public ElementDeclaration[] extractElementDeclarations()
151                                throws XMLParsingException {
152            List complexTypeElements = XMLTools.getNodes( this.getRootElement(), getFullName( "element" ), nsContext );
153            LOG.logDebug( "Found " + complexTypeElements.size() + " element declarations." );
154            ElementDeclaration[] elementDeclarations = new ElementDeclaration[complexTypeElements.size()];
155            for ( int i = 0; i < elementDeclarations.length; i++ ) {
156                elementDeclarations[i] = parseElementDeclaration( (Element) complexTypeElements.get( i ) );
157            }
158            return elementDeclarations;
159        }
160    
161        /**
162         * Returns the root element of the complex type declaration for the given name.
163         * 
164         * @param name
165         *            the name of the complex type declaration to look up (w/o namespace)
166         * @return the root element of the complex type declaration or null, if the requested complex
167         *         type is not declared
168         */
169        public Element getComplexTypeDeclaration( String name ) {
170            String xPath = getFullName( "complexType[name=\"]" ) + name + "\"]";
171            Element element = null;
172            try {
173                element = (Element) XMLTools.getNode( getRootElement(), xPath, nsContext );
174            } catch ( XMLParsingException e ) {
175                // happens if requested complex type is not declared
176            }
177            return element;
178        }
179    
180        /**
181         * Returns the root element of the element declaration for the given name.
182         * 
183         * @param name
184         *            the name of the element declaration to look up (w/o namespace)
185         * @return the root element of the element declaration or null, if the requested element is not
186         *         declared
187         */
188        public Element getElementDeclaration( String name ) {
189            String xPath = getFullName( "element[name=\"]" ) + name + "\"]";
190            Element element = null;
191            try {
192                element = (Element) XMLTools.getNode( getRootElement(), xPath, nsContext );
193            } catch ( XMLParsingException e ) {
194                // happens if requested element is not declared
195            }
196            return element;
197        }
198    
199        /**
200         * Parses the given <code>Element</code> as an 'xs:element' declaration.
201         * 
202         * @param element
203         *            'xs:element' declaration to be parsed
204         * @return object representation of the declaration
205         * @throws XMLParsingException
206         *             if the document is not a valid XML Schema document or does not match the
207         *             limitations of this class
208         */
209        protected ElementDeclaration parseElementDeclaration( Element element )
210                                throws XMLParsingException {
211    
212            QualifiedName name = new QualifiedName( XMLTools.getRequiredNodeAsString( element, "@name", nsContext ),
213                                                    getTargetNamespace() );
214    
215            if ( name.getLocalName().length() == 0 ) {
216                String msg = "Error in schema document. Empty name (\"\") in element declaration " + "found.";
217                throw new XMLSchemaException( msg );
218            }
219    
220            LOG.logDebug( "Parsing element declaration '" + name + "'." );
221    
222            boolean isAbstract = XMLTools.getNodeAsBoolean( element, "@abstract", nsContext, false );
223    
224            TypeReference typeReference = null;
225            Node typeNode = XMLTools.getNode( element,
226                                              "@type|xs:simpleType/xs:restriction/@base|xs:simpleType/xs:extension/@base",
227                                              nsContext );
228            if ( typeNode != null ) {
229                typeReference = new TypeReference( parseQualifiedName( typeNode ) );
230            } else {
231                // inline type declaration
232                Element elem = (Element) XMLTools.getRequiredNode( element, getFullName( "complexType" ), nsContext );
233                TypeDeclaration type = parseComplexTypeDeclaration( elem );
234                typeReference = new TypeReference( type );
235            }
236    
237            int minOccurs = XMLTools.getNodeAsInt( element, "@minOccurs", nsContext, 1 );
238            int maxOccurs = -1;
239            String maxOccursString = XMLTools.getNodeAsString( element, "@maxOccurs", nsContext, "1" );
240            if ( !"unbounded".equals( maxOccursString ) ) {
241                try {
242                    maxOccurs = Integer.parseInt( maxOccursString );
243                } catch ( NumberFormatException e ) {
244                    throw new XMLParsingException( "Invalid value ('" + maxOccursString + "') in 'maxOccurs' attribute. "
245                                                   + "Must be a valid integer value or 'unbounded'." );
246                }
247            }
248    
249            QualifiedName substitutionGroup = null;
250            Node substitutionGroupNode = XMLTools.getNode( element, "@substitutionGroup", nsContext );
251            if ( substitutionGroupNode != null ) {
252                substitutionGroup = parseQualifiedName( substitutionGroupNode );
253            }
254    
255            return new ElementDeclaration( name, isAbstract, typeReference, minOccurs, maxOccurs, substitutionGroup );
256        }
257    
258        /**
259         * Parses the given <code>Element</code> as an 'xs:simpleType' declaration.
260         * <p>
261         * The following limitations apply:
262         * <ul>
263         * <li>the type must be defined using 'restriction' (of a basic xsd type)</li>
264         * <li>the content model (enumeration, ...) is not evaluated</li>
265         * </ul>
266         * </p>
267         * 
268         * @param element
269         *            'xs:simpleType' declaration to be parsed
270         * @return object representation of the declaration
271         * @throws XMLParsingException
272         *             if the document is not a valid XML Schema document or does not match the
273         *             limitations of this class
274         */
275        protected SimpleTypeDeclaration parseSimpleTypeDeclaration( Element element )
276                                throws XMLParsingException {
277    
278            QualifiedName name = null;
279            String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null );
280            if ( localName != null ) {
281                name = new QualifiedName( localName, getTargetNamespace() );
282                if ( localName.length() == 0 ) {
283                    String msg = "Error in schema document. Empty name (\"\") in simpleType " + "declaration found.";
284                    throw new XMLSchemaException( msg );
285                }
286            }
287    
288            LOG.logDebug( "Parsing simple type declaration '" + name + "'." );
289    
290            Node restrictionBaseNode = XMLTools.getRequiredNode( element, getFullName( "restriction/@base" ), nsContext );
291            TypeReference restrictionBase = new TypeReference( parseQualifiedName( restrictionBaseNode ) );
292    
293            return new SimpleTypeDeclaration( name, restrictionBase );
294        }
295    
296        /**
297         * Parses the given <code>Element</code> as an 'xs:complexType' declaration.
298         * 
299         * @param element
300         *            'xs:complexType' declaration to be parsed
301         * @return object representation of the declaration
302         * @throws XMLParsingException
303         *             if the document is not a valid XML Schema document or does not match the
304         *             limitations of this class
305         */
306        protected ComplexTypeDeclaration parseComplexTypeDeclaration( Element element )
307                                throws XMLParsingException {
308    
309            QualifiedName name = null;
310            String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null );
311            if ( localName != null ) {
312                name = new QualifiedName( localName, getTargetNamespace() );
313                if ( localName.length() == 0 ) {
314                    String msg = "Error in schema document. Empty name (\"\") for complexType " + "declaration found.";
315                    throw new XMLSchemaException( msg );
316                }
317            }
318            LOG.logDebug( "Parsing complex type declaration '" + name + "'." );
319    
320            List subElementList = null;
321            TypeReference extensionBase = null;
322            Node extensionBaseNode = XMLTools.getNode( element, getFullName( "complexContent/" )
323                                                                + getFullName( "extension/@base" ), nsContext );
324            if ( extensionBaseNode != null ) {
325                extensionBase = new TypeReference( parseQualifiedName( extensionBaseNode ) );
326                subElementList = XMLTools.getNodes( element, getFullName( "complexContent/" ) + getFullName( "extension/" )
327                                                             + getFullName( "sequence/" ) + getFullName( "element" ),
328                                                    nsContext );
329            } else {
330                subElementList = XMLTools.getRequiredNodes( element, getFullName( "sequence/" ) + getFullName( "element" ),
331                                                            nsContext );
332            }
333    
334            ElementDeclaration[] subElements = new ElementDeclaration[subElementList.size()];
335            for ( int i = 0; i < subElements.length; i++ ) {
336                Element subElement = (Element) subElementList.get( i );
337                subElements[i] = parseElementDeclaration( subElement );
338            }
339    
340            return new ComplexTypeDeclaration( name, extensionBase, subElements );
341        }
342    
343        /**
344         * Prepends the prefix of the RootElement to the given local name.
345         * <p>
346         * If the prefix of the RootElement is empty, "xs:" is prepended.
347         * 
348         * @param localName
349         *            to this the prefix will be prepended
350         * @return prefix + localName
351         */
352        protected String getFullName( String localName ) {
353            String ret;
354            Element root = this.getRootElement();
355            String prefix = root.getPrefix();
356    
357            if ( prefix != null && prefix.length() > 0 ) {
358                URI uri = nsContext.getURI( prefix );
359                if ( null == uri ) {
360                    String nsUri = root.lookupNamespaceURI( prefix );
361                    try {
362                        nsContext.addNamespace( prefix, new URI( nsUri ) ); // synchronized ???
363                    } catch ( Exception exc ) {
364                        LOG.logError( "failed to add namespace: " + nsUri, exc );
365                    }
366                }
367                ret = prefix + ':' + localName;
368            } else {
369                // fallback
370                ret = "xs:" + localName;
371            }
372            return ret;
373        }
374    }