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