001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.sequencer.xsd;
017
018import static org.modeshape.sequencer.sramp.SrampLexicon.DESCRIPTION;
019import static org.modeshape.sequencer.xsd.XsdLexicon.IMPORT;
020import java.io.InputStream;
021import java.io.Reader;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.atomic.AtomicLong;
029import javax.jcr.NamespaceRegistry;
030import javax.jcr.Node;
031import javax.jcr.PropertyType;
032import javax.jcr.RepositoryException;
033import javax.jcr.Value;
034import org.eclipse.emf.common.util.AbstractEnumerator;
035import org.eclipse.emf.common.util.EList;
036import org.eclipse.emf.ecore.EObject;
037import org.eclipse.xsd.XSDAnnotation;
038import org.eclipse.xsd.XSDAttributeDeclaration;
039import org.eclipse.xsd.XSDAttributeGroupContent;
040import org.eclipse.xsd.XSDAttributeGroupDefinition;
041import org.eclipse.xsd.XSDAttributeUse;
042import org.eclipse.xsd.XSDComplexTypeContent;
043import org.eclipse.xsd.XSDComplexTypeDefinition;
044import org.eclipse.xsd.XSDCompositor;
045import org.eclipse.xsd.XSDConcreteComponent;
046import org.eclipse.xsd.XSDDerivationMethod;
047import org.eclipse.xsd.XSDElementDeclaration;
048import org.eclipse.xsd.XSDEnumerationFacet;
049import org.eclipse.xsd.XSDFacet;
050import org.eclipse.xsd.XSDImport;
051import org.eclipse.xsd.XSDInclude;
052import org.eclipse.xsd.XSDModelGroup;
053import org.eclipse.xsd.XSDModelGroupDefinition;
054import org.eclipse.xsd.XSDParticle;
055import org.eclipse.xsd.XSDParticleContent;
056import org.eclipse.xsd.XSDPatternFacet;
057import org.eclipse.xsd.XSDProcessContents;
058import org.eclipse.xsd.XSDProhibitedSubstitutions;
059import org.eclipse.xsd.XSDRedefine;
060import org.eclipse.xsd.XSDRepeatableFacet;
061import org.eclipse.xsd.XSDSchema;
062import org.eclipse.xsd.XSDSimpleFinal;
063import org.eclipse.xsd.XSDSimpleTypeDefinition;
064import org.eclipse.xsd.XSDTypeDefinition;
065import org.eclipse.xsd.XSDWildcard;
066import org.eclipse.xsd.util.XSDParser;
067import org.modeshape.common.annotation.NotThreadSafe;
068import org.modeshape.common.util.SizeMeasuringInputStream;
069import org.modeshape.common.util.SizeMeasuringReader;
070import org.modeshape.common.util.StringUtil;
071import org.modeshape.jcr.api.sequencer.Sequencer;
072import org.modeshape.sequencer.sramp.AbstractResolvingReader;
073import org.modeshape.sequencer.sramp.SrampLexicon;
074import org.modeshape.sequencer.sramp.SymbolSpace;
075import org.modeshape.sequencer.xsd.XsdSequencer.MimeTypeConstants;
076import org.w3c.dom.Element;
077import org.w3c.dom.NamedNodeMap;
078import org.xml.sax.InputSource;
079
080/**
081 * A class that can parse XML Schema Documents and create a node structure based on the schema information.
082 * <p>
083 * This class can be subclassed and any of the 'process' methods overridden to customize the derived graph structure.
084 * </p>
085 */
086@NotThreadSafe
087public class XsdReader extends AbstractResolvingReader {
088
089    /**
090     * In XML Schema, there is a distinct symbol space within each target namespace for each kind of <a
091     * href="http://www.w3.org/TR/xmlschema-1/#concepts-data-model">declaration and definition component</a>, except that within a
092     * target namespace the simple type definitions and complex type definitions share a single symbol space. See the <a
093     * href="http://www.w3.org/TR/xmlschema-1/#concepts-nameSymbolSpaces">specification</a> for details.
094     */
095    public static final SymbolSpace ATTRIBUTE_DECLARATIONS = new SymbolSpace("AttributeDeclarations");
096    public static final SymbolSpace ELEMENT_DECLARATION = new SymbolSpace("ElementDeclarations");
097    public static final SymbolSpace TYPE_DEFINITIONS = new SymbolSpace("TypeDeclarations");
098    public static final SymbolSpace ATTRIBUTE_GROUP_DEFINITIONS = new SymbolSpace("AttributeGroupDeclarations");
099    public static final SymbolSpace MODEL_GROUP_DEFINITIONS = new SymbolSpace("ModelGroupDeclarations");
100    public static final SymbolSpace IDENTITY_CONSTRAINT_DEFINITIONS = new SymbolSpace("IdentityConstraintDeclarations");
101
102    /**
103     * A set of attribute names which should be ignored when/if they appear without a schema
104     */
105    private static final Set<String> IGNORED_ATTRIBUTES_IMPORT = removePrefix(XsdLexicon.NAMESPACE, XsdLexicon.SCHEMA_LOCATION);
106    private static final Set<String> IGNORED_ATTRIBUTES_INCLUDE = removePrefix(XsdLexicon.SCHEMA_LOCATION);
107    private static final Set<String> IGNORED_ATTRIBUTES_REDEFINE = removePrefix(XsdLexicon.SCHEMA_LOCATION);
108    private static final Set<String> IGNORED_ATTRIBUTES_SIMPLE_TYPE_DEF = removePrefix(XsdLexicon.NAMESPACE, XsdLexicon.NC_NAME);
109    private static final Set<String> IGNORED_ATTRIBUTES_COMPLEX_TYPE_DEF = removePrefix(XsdLexicon.NAMESPACE,
110                                                                                     XsdLexicon.BASE_TYPE_NAME,
111                                                                                     XsdLexicon.BASE_TYPE_NAMESPACE,
112                                                                                     XsdLexicon.BLOCK,
113                                                                                     XsdLexicon.FINAL,
114                                                                                     XsdLexicon.ABSTRACT, XsdLexicon.MIXED);
115    private static final Set<String> IGNORED_ATTRIBUTES_ELEMENT_DECL = removePrefix(XsdLexicon.TYPE_NAME, XsdLexicon.TYPE_NAMESPACE,
116                                                                                XsdLexicon.TYPE_REFERENCE, XsdLexicon.FORM,
117                                                                                XsdLexicon.FINAL, XsdLexicon.BLOCK);
118    private static final Set<String> IGNORED_ATTRIBUTES_ATTR_DECL = removePrefix(XsdLexicon.TYPE_NAME, XsdLexicon.TYPE_NAMESPACE);
119    private static final Set<String> IGNORED_ATTRIBUTES_COMPLEX_TYPE_CONTENT = removePrefix(XsdLexicon.METHOD);
120    private static final Set<String> IGNORED_ATTRIBUTES_ATTR_GROUP_DEF = removePrefix(XsdLexicon.REF, XsdLexicon.NC_NAME,
121                                                                                   XsdLexicon.NAMESPACE);
122    private static final Set<String> IGNORED_ATTRIBUTES_WILDCARD = removePrefix(XsdLexicon.NAMESPACE, XsdLexicon.PROCESS_CONTENTS);
123    private static final Set<String> IGNORED_ATTRIBUTES_ATTR_USE = removePrefix(XsdLexicon.USE);
124
125    public XsdReader( Sequencer.Context context ) {
126        super(context);
127    }
128
129    @Override
130    public void read( InputSource source,
131                      Node outputNode ) throws Exception {
132        logger.debug("Processing XSD '{0}'", outputNode);
133        Reader reader = null;
134        InputStream stream = null;
135        try {
136            // Parse the XSD, measuring the number of bytes as we read ...
137            Map<?, ?> options = new HashMap<Object, Object>();
138            XSDParser parser = new XSDParser(options);
139            AtomicLong contentSize = new AtomicLong();
140            if (source.getCharacterStream() != null) {
141                reader = new SizeMeasuringReader(source.getCharacterStream(), contentSize);
142                source = new InputSource(reader);
143            } else {
144                stream = new SizeMeasuringInputStream(source.getByteStream(), contentSize);
145                source = new InputSource(stream);
146            }
147            parser.parse(source);
148
149            // Get some metadata about the XSD ...
150            String encoding = parser.getEncoding();
151
152            // Convert the XSD to content ...
153            XSDSchema schema = parser.getSchema();
154            process(schema, encoding, contentSize.get(), outputNode);
155
156        } finally {
157            try {
158                if (reader != null) reader.close();
159            } catch (Exception e) {
160                logger.debug(e, "Cannot close reader stream ");
161            } finally {
162                try {
163                    if (stream != null) stream.close();
164                } catch (Exception e) {
165                    logger.debug(e, "Cannot close reader stream ");
166                }
167            }
168        }
169    }
170
171    /**
172     * Read an XSDSchema instance and create the node hierarchy under the given root node.
173     * 
174     * @param schema the schema object; may not be null
175     * @param encoding the encoding for the XSD; may be null if the encoding is not specified
176     * @param contentSize the size of the XML Schema Document content; may not be negative
177     * @param rootNode the root node that will be populated with the XML Schema Document information
178     * @throws Exception if there is a probelm reading the XSD content
179     */
180    protected void process( XSDSchema schema,
181                            String encoding,
182                            long contentSize,
183                            Node rootNode ) throws Exception {
184        assert schema != null;
185
186        logger.debug("Target namespace: '{0}'", schema.getTargetNamespace());
187        rootNode.setProperty(SrampLexicon.CONTENT_TYPE, MimeTypeConstants.APPLICATION_XML);
188        if (encoding != null) {
189            rootNode.setProperty(SrampLexicon.CONTENT_ENCODING, encoding);
190        }
191        rootNode.setProperty(SrampLexicon.CONTENT_SIZE, contentSize);
192
193        // Parse the annotations first to aggregate them all into a single 'sramp:description' property ...
194        @SuppressWarnings( "unchecked" )
195        List<XSDAnnotation> annotations = schema.getAnnotations();
196        processAnnotations(annotations, rootNode);
197        processNonSchemaAttributes(schema, rootNode, Collections.<String>emptySet());
198
199        // Parse the objects ...
200        for (EObject obj : schema.eContents()) {
201            if (obj instanceof XSDSimpleTypeDefinition) {
202                processSimpleTypeDefinition((XSDSimpleTypeDefinition)obj, rootNode);
203            } else if (obj instanceof XSDComplexTypeDefinition) {
204                processComplexTypeDefinition((XSDComplexTypeDefinition)obj, rootNode);
205            } else if (obj instanceof XSDElementDeclaration) {
206                processElementDeclaration((XSDElementDeclaration)obj, rootNode);
207            } else if (obj instanceof XSDAttributeDeclaration) {
208                processAttributeDeclaration((XSDAttributeDeclaration)obj, rootNode, false);
209            } else if (obj instanceof XSDImport) {
210                processImport((XSDImport)obj, rootNode);
211            } else if (obj instanceof XSDInclude) {
212                processInclude((XSDInclude)obj, rootNode);
213            } else if (obj instanceof XSDRedefine) {
214                processRedefine((XSDRedefine)obj, rootNode);
215            } else if (obj instanceof XSDAttributeGroupDefinition) {
216                processAttributeGroupDefinition((XSDAttributeGroupDefinition)obj, rootNode);
217            } else if (obj instanceof XSDAnnotation) {
218                // already processed above ...
219            }
220        }
221
222        // Resolve any outstanding, unresolved references ...
223        resolveReferences();
224    }
225
226    protected void processImport( XSDImport xsdImport,
227                                  Node parentNode ) throws RepositoryException {
228        logger.debug("Import: '{0}' with location '{1}' ", xsdImport.getNamespace(), xsdImport.getSchemaLocation());
229        Node importNode = parentNode.addNode(IMPORT, IMPORT);
230        importNode.setProperty(XsdLexicon.NAMESPACE, xsdImport.getNamespace());
231        importNode.setProperty(XsdLexicon.SCHEMA_LOCATION, xsdImport.getSchemaLocation());
232        processNonSchemaAttributes(xsdImport, importNode, IGNORED_ATTRIBUTES_IMPORT);
233    }
234
235    protected void processInclude( XSDInclude xsdInclude,
236                                   Node parentNode ) throws RepositoryException {
237        logger.debug("Include: '{0}' ", xsdInclude.getSchemaLocation());
238        Node includeNode = parentNode.addNode(XsdLexicon.INCLUDE, XsdLexicon.INCLUDE);
239        includeNode.setProperty(XsdLexicon.SCHEMA_LOCATION, xsdInclude.getSchemaLocation());
240        processNonSchemaAttributes(xsdInclude, includeNode, IGNORED_ATTRIBUTES_INCLUDE);
241    }
242
243    protected void processRedefine( XSDRedefine redefine,
244                                    Node parentNode ) throws RepositoryException {
245        logger.debug("Include: '{0}' ", redefine.getSchemaLocation());
246        Node redefineNode = parentNode.addNode(XsdLexicon.REDEFINE, XsdLexicon.REDEFINE);
247        redefineNode.setProperty(XsdLexicon.SCHEMA_LOCATION, redefine.getSchemaLocation());
248        processNonSchemaAttributes(redefine, redefineNode, IGNORED_ATTRIBUTES_REDEFINE);
249    }
250
251    protected void processSimpleTypeDefinition( XSDSimpleTypeDefinition type,
252                                                Node node ) throws RepositoryException {
253        boolean isAnonymous = type.getName() == null;
254        String nodeName = isAnonymous ? XsdLexicon.SIMPLE_TYPE : type.getName();
255        // This is a normal simple type definition ...
256        logger.debug("Simple type: '{0}' in ns '{1}' ", type.getName(), type.getTargetNamespace());
257
258        Node typeNode = node.addNode(nodeName, XsdLexicon.SIMPLE_TYPE_DEFINITION);
259        typeNode.setProperty(XsdLexicon.NAMESPACE, type.getTargetNamespace());
260        if (!isAnonymous) {
261            typeNode.setProperty(XsdLexicon.NC_NAME, type.getName());
262            registerForSymbolSpace(TYPE_DEFINITIONS, type.getTargetNamespace(), type.getName(), typeNode.getIdentifier());
263        }
264        processTypeFacets(type, typeNode, type.getBaseType());
265        processNonSchemaAttributes(type, typeNode, IGNORED_ATTRIBUTES_SIMPLE_TYPE_DEF);
266    }
267
268    protected void processTypeFacets( XSDSimpleTypeDefinition type,
269                                      Node typeNode,
270                                      XSDTypeDefinition baseType ) throws RepositoryException {
271        if (baseType == null) {
272            baseType = type.getBaseType();
273        }
274        if (baseType == type) {
275            // The base type is the anytype ...
276            baseType = type.getSchema()
277                           .getSchemaForSchema()
278                           .resolveSimpleTypeDefinition("http://www.w3.org/2001/XMLSchema", "anyType");
279        }
280        if (baseType != null) {
281            typeNode.setProperty(XsdLexicon.BASE_TYPE_NAME, baseType.getName());
282            typeNode.setProperty(XsdLexicon.BASE_TYPE_NAMESPACE, baseType.getTargetNamespace());
283            setReference(typeNode,
284                         XsdLexicon.BASE_TYPE_REFERENCE,
285                         TYPE_DEFINITIONS,
286                         baseType.getTargetNamespace(),
287                         baseType.getName());
288        }
289
290        processFacet(type.getEffectiveMaxLengthFacet(), typeNode, XsdLexicon.MAX_LENGTH, PropertyType.LONG);
291        processFacet(type.getMaxLengthFacet(), typeNode, XsdLexicon.MAX_LENGTH, PropertyType.LONG);
292        processFacet(type.getEffectiveMinLengthFacet(), typeNode, XsdLexicon.MIN_LENGTH, PropertyType.LONG);
293        processFacet(type.getMinLengthFacet(), typeNode, XsdLexicon.MIN_LENGTH, PropertyType.LONG);
294        processFacet(type.getEffectiveMaxFacet(), typeNode, XsdLexicon.MAX_VALUE_EXCLUSIVE, PropertyType.LONG);
295        processFacet(type.getMaxExclusiveFacet(), typeNode, XsdLexicon.MAX_VALUE_EXCLUSIVE, PropertyType.LONG);
296        processFacet(type.getEffectiveMinFacet(), typeNode, XsdLexicon.MIN_VALUE_EXCLUSIVE, PropertyType.LONG);
297        processFacet(type.getMinExclusiveFacet(), typeNode, XsdLexicon.MIN_VALUE_EXCLUSIVE, PropertyType.LONG);
298        processFacet(type.getMaxInclusiveFacet(), typeNode, XsdLexicon.MAX_VALUE_INCLUSIVE, PropertyType.LONG);
299        processFacet(type.getMinInclusiveFacet(), typeNode, XsdLexicon.MIN_VALUE_INCLUSIVE, PropertyType.LONG);
300        processFacet(type.getEffectiveTotalDigitsFacet(), typeNode, XsdLexicon.TOTAL_DIGITS, PropertyType.LONG);
301        processFacet(type.getTotalDigitsFacet(), typeNode, XsdLexicon.TOTAL_DIGITS, PropertyType.LONG);
302        processFacet(type.getEffectiveFractionDigitsFacet(), typeNode, XsdLexicon.FRACTION_DIGITS, PropertyType.LONG);
303        processFacet(type.getFractionDigitsFacet(), typeNode, XsdLexicon.FRACTION_DIGITS, PropertyType.LONG);
304
305        processFacet(type.getEffectiveWhiteSpaceFacet(), typeNode, XsdLexicon.WHITESPACE, PropertyType.STRING);
306        processFacet(type.getWhiteSpaceFacet(), typeNode, XsdLexicon.WHITESPACE, PropertyType.STRING);
307
308        processFacet(type.getEffectivePatternFacet(), typeNode, XsdLexicon.PATTERN, PropertyType.STRING);
309        @SuppressWarnings( "unchecked" )
310        List<XSDPatternFacet> patternFacets = type.getPatternFacets();
311        processFacetsList(patternFacets, typeNode, XsdLexicon.PATTERN);
312
313        processFacet(type.getEffectiveEnumerationFacet(), typeNode, XsdLexicon.ENUMERATED_VALUES, PropertyType.STRING);
314        @SuppressWarnings( "unchecked" )
315        List<XSDEnumerationFacet> enumFacets = type.getEnumerationFacets();
316        processFacetsList(enumFacets, typeNode, XsdLexicon.ENUMERATED_VALUES);
317
318        @SuppressWarnings( "unchecked" )
319        List<XSDSimpleFinal> finalFacets2 = type.getFinal();
320        processEnumerators(finalFacets2, typeNode, XsdLexicon.FINAL);
321
322        processAnnotation(type.getAnnotation(), typeNode);
323    }
324
325    protected void processComplexTypeDefinition( XSDComplexTypeDefinition type,
326                                                 Node parentNode ) throws RepositoryException {
327        logger.debug("Complex type: '{0}' in ns '{1}' ", type.getName(), type.getTargetNamespace());
328        boolean isAnonymous = type.getName() == null;
329
330        String nodeName = isAnonymous ? XsdLexicon.COMPLEX_TYPE : type.getName();
331        Node typeNode = parentNode.addNode(nodeName, XsdLexicon.COMPLEX_TYPE_DEFINITION);
332        typeNode.setProperty(XsdLexicon.NAMESPACE, type.getTargetNamespace());
333        if (!isAnonymous) {
334            typeNode.setProperty(XsdLexicon.NC_NAME, type.getName());
335            registerForSymbolSpace(TYPE_DEFINITIONS, type.getTargetNamespace(), type.getName(), typeNode.getIdentifier());
336        }
337        XSDTypeDefinition baseType = type.getBaseType();
338        if (baseType == type) {
339            // The base type is the anytype ...
340            baseType = type.getSchema()
341                           .getSchemaForSchema()
342                           .resolveComplexTypeDefinition("http://www.w3.org/2001/XMLSchema", "anyType");
343        }
344        if (baseType != null) {
345            typeNode.setProperty(XsdLexicon.BASE_TYPE_NAME, baseType.getName());
346            typeNode.setProperty(XsdLexicon.BASE_TYPE_NAMESPACE, baseType.getTargetNamespace());
347        }
348        typeNode.setProperty(XsdLexicon.ABSTRACT, type.isAbstract());
349        typeNode.setProperty(XsdLexicon.MIXED, type.isMixed());
350
351        @SuppressWarnings( "unchecked" )
352        List<XSDProhibitedSubstitutions> blocks = type.getBlock();
353        processEnumerators(blocks, typeNode, XsdLexicon.BLOCK);
354
355        @SuppressWarnings( "unchecked" )
356        List<XSDSimpleFinal> finalFacets = type.getFinal();
357        processEnumerators(finalFacets, typeNode, XsdLexicon.FINAL);
358
359        processComplexTypeContent(type.getContent(), typeNode);
360
361        processAnnotation(type.getAnnotation(), typeNode);
362        processNonSchemaAttributes(type, typeNode, IGNORED_ATTRIBUTES_COMPLEX_TYPE_DEF);
363    }
364
365    protected Node processElementDeclaration( XSDElementDeclaration decl,
366                                              Node parentNode ) throws RepositoryException {
367        if (decl == null) {
368            return null;
369        }
370        logger.debug("Element declaration: '{0}' in ns '{1}' ", decl.getName(), decl.getTargetNamespace());
371        Node declarationNode;
372        if (decl.getName() != null) {
373            // Normal element declaration ...
374            declarationNode = parentNode.addNode(decl.getName(), XsdLexicon.ELEMENT_DECLARATION);
375            declarationNode.setProperty(XsdLexicon.NC_NAME, decl.getName());
376            declarationNode.setProperty(XsdLexicon.NAMESPACE, decl.getTargetNamespace());
377        } else {
378            assert decl.isFeatureReference() : "expected element reference";
379            XSDElementDeclaration resolved = decl.getResolvedElementDeclaration();
380            declarationNode = parentNode.addNode(resolved.getName(), XsdLexicon.ELEMENT_DECLARATION);
381            declarationNode.setProperty(XsdLexicon.REF_NAME, resolved.getName());
382            declarationNode.setProperty(XsdLexicon.REF_NAMESPACE, resolved.getTargetNamespace());
383            setReference(declarationNode, XsdLexicon.REF, ELEMENT_DECLARATION, resolved.getTargetNamespace(), resolved.getName());
384        }
385        if (decl.isGlobal()) {
386            registerForSymbolSpace(ELEMENT_DECLARATION,
387                                   decl.getTargetNamespace(),
388                                   decl.getName(),
389                                   declarationNode.getIdentifier());
390        }
391
392        declarationNode.setProperty(XsdLexicon.ABSTRACT, decl.isAbstract());
393        declarationNode.setProperty(XsdLexicon.NILLABLE, decl.isNillable());
394
395        XSDTypeDefinition type = decl.getType();
396        if (type != null) {
397            declarationNode.setProperty(XsdLexicon.TYPE_NAME, type.getName());
398            declarationNode.setProperty(XsdLexicon.TYPE_NAMESPACE, type.getTargetNamespace());
399            setReference(declarationNode, XsdLexicon.TYPE_REFERENCE, TYPE_DEFINITIONS, type.getTargetNamespace(), type.getName());
400        }
401
402        if (decl.getAnonymousTypeDefinition() == type) {
403            // It's anonymous, so we need to process the definition here ...
404            if (type instanceof XSDComplexTypeDefinition) {
405                processComplexTypeDefinition((XSDComplexTypeDefinition)type, declarationNode);
406            } else if (type instanceof XSDSimpleTypeDefinition) {
407                processSimpleTypeDefinition((XSDSimpleTypeDefinition)type, declarationNode);
408            }
409        }
410        processEnumerator(decl.getForm(), declarationNode, XsdLexicon.FORM);
411
412        @SuppressWarnings( "unchecked" )
413        List<XSDProhibitedSubstitutions> finals = decl.getLexicalFinal();
414        processEnumerators(finals, declarationNode, XsdLexicon.FINAL);
415
416        @SuppressWarnings( "unchecked" )
417        List<XSDProhibitedSubstitutions> blocks = decl.getBlock();
418        processEnumerators(blocks, declarationNode, XsdLexicon.BLOCK);
419
420        processAnnotation(decl.getAnnotation(), declarationNode);
421        processNonSchemaAttributes(type, declarationNode, IGNORED_ATTRIBUTES_ELEMENT_DECL);
422        return declarationNode;
423    }
424
425    protected Node processAttributeDeclaration( XSDAttributeDeclaration decl,
426                                                Node parentNode,
427                                                boolean isUse ) throws RepositoryException {
428        if (decl == null) {
429            return null;
430        }
431        logger.debug("Attribute declaration: '{0}' in ns '{1}' ", decl.getName(), decl.getTargetNamespace());
432
433        Node attributeDeclarationNode = parentNode.addNode(decl.getName(), XsdLexicon.ATTRIBUTE_DECLARATION);
434        attributeDeclarationNode.setProperty(XsdLexicon.NC_NAME, decl.getName());
435        attributeDeclarationNode.setProperty(XsdLexicon.NAMESPACE, decl.getTargetNamespace());
436        if (decl.isGlobal() && !isUse) {
437            registerForSymbolSpace(ATTRIBUTE_DECLARATIONS,
438                                   decl.getTargetNamespace(),
439                                   decl.getName(),
440                                   attributeDeclarationNode.getIdentifier());
441        }
442        XSDTypeDefinition type = decl.getType();
443        if (type != null) {
444            attributeDeclarationNode.setProperty(XsdLexicon.TYPE_NAME, type.getName());
445            attributeDeclarationNode.setProperty(XsdLexicon.TYPE_NAMESPACE, type.getTargetNamespace());
446        }
447        processAnnotation(decl.getAnnotation(), attributeDeclarationNode);
448        processNonSchemaAttributes(type, attributeDeclarationNode, IGNORED_ATTRIBUTES_ATTR_DECL);
449        return attributeDeclarationNode;
450    }
451
452    protected void processComplexTypeContent( XSDComplexTypeContent content,
453                                              Node parentNode ) throws RepositoryException {
454        if (content == null) {
455            return;
456        }
457
458        XSDComplexTypeDefinition owner = (XSDComplexTypeDefinition)content.eContainer();
459
460        if (content instanceof XSDParticle) {
461            processParticle((XSDParticle)content, parentNode);
462        } else if (content instanceof XSDSimpleTypeDefinition) {
463            Node contentNode = parentNode.addNode(XsdLexicon.SIMPLE_CONTENT, XsdLexicon.SIMPLE_CONTENT);
464            processTypeFacets((XSDSimpleTypeDefinition)content, contentNode, owner.getBaseTypeDefinition());
465        }
466
467        XSDDerivationMethod method = owner.getDerivationMethod();
468        if (method != null) {
469            parentNode.setProperty(XsdLexicon.METHOD, method.getLiteral());
470        }
471
472        @SuppressWarnings( "unchecked" )
473        List<XSDAttributeGroupContent> attributeGroupContents = owner.getAttributeContents();
474        if (attributeGroupContents != null) {
475            for (XSDAttributeGroupContent attributeGroup : attributeGroupContents) {
476                processAttributeGroupContent(attributeGroup, parentNode);
477            }
478        }
479        @SuppressWarnings( "unchecked" )
480        List<XSDAttributeUse> attributeUses = owner.getAttributeUses();
481        if (attributeUses != null) {
482            for (XSDAttributeUse attributeUse : attributeUses) {
483                processAttributeUse(attributeUse, parentNode);
484            }
485        }
486        XSDWildcard wildcard = owner.getAttributeWildcard();
487        processWildcard(wildcard, parentNode);
488        processNonSchemaAttributes(owner, parentNode, IGNORED_ATTRIBUTES_COMPLEX_TYPE_CONTENT);
489    }
490
491    protected void processParticle( XSDParticle content,
492                                    Node node ) throws RepositoryException {
493        if (content == null) {
494            return;
495        }
496        XSDParticleContent particle = content.getContent();
497        Node particleNode = null;
498        if (particle instanceof XSDModelGroupDefinition) {
499            particleNode = processModelGroupDefinition((XSDModelGroupDefinition)particle, node);
500        } else if (particle instanceof XSDElementDeclaration) {
501            particleNode = processElementDeclaration((XSDElementDeclaration)particle, node);
502        } else if (particle instanceof XSDModelGroup) {
503            particleNode = processModelGroup((XSDModelGroup)particle, node);
504        } else if (particle instanceof XSDWildcard) {
505            particleNode = processWildcard((XSDWildcard)particle, node);
506        }
507        if (particleNode != null) {
508            long minOccurs = content.getMinOccurs();
509            long maxOccurs = content.getMaxOccurs();
510            particleNode.setProperty(XsdLexicon.MIN_OCCURS, minOccurs);
511            if (maxOccurs >= 0) {
512                particleNode.setProperty(XsdLexicon.MAX_OCCURS, maxOccurs);
513            } else {
514                // unbounded ...
515            }
516        }
517    }
518
519    protected Node processModelGroupDefinition( XSDModelGroupDefinition defn,
520                                                Node parentNode ) throws RepositoryException {
521        if (defn == null) {
522            return null;
523        }
524        XSDModelGroup group = defn.getModelGroup();
525        processNonSchemaAttributes(defn, parentNode, Collections.<String>emptySet());
526        return processModelGroup(group, parentNode);
527    }
528
529    protected Node processModelGroup( XSDModelGroup group,
530                                      Node parentNode ) throws RepositoryException {
531        if (group == null) {
532            return null;
533        }
534        XSDCompositor compositor = group.getCompositor();
535        String primaryTypeName = getPrimaryTypeFromCompositor(compositor);
536
537        Node childNode = parentNode.addNode(primaryTypeName, primaryTypeName);
538        @SuppressWarnings( "unchecked" )
539        List<XSDParticle> particles = group.getParticles();
540        for (XSDParticle particle : particles) {
541            processParticle(particle, childNode);
542        }
543        processNonSchemaAttributes(group, childNode, Collections.<String>emptySet());
544        return childNode;
545    }
546
547    private String getPrimaryTypeFromCompositor( XSDCompositor compositor ) {
548        String primaryTypeName = null;
549        switch (compositor.getValue()) {
550            case XSDCompositor.ALL:
551                primaryTypeName = XsdLexicon.ALL;
552                break;
553            case XSDCompositor.CHOICE:
554                primaryTypeName = XsdLexicon.CHOICE;
555                break;
556            case XSDCompositor.SEQUENCE:
557                primaryTypeName = XsdLexicon.SEQUENCE;
558                break;
559            default:
560                assert false : "should not get here";
561        }
562        return primaryTypeName;
563    }
564
565    protected void processAttributeGroupContent( XSDAttributeGroupContent content,
566                                                 Node parentNode ) throws RepositoryException {
567        if (content == null) {
568            return;
569        }
570        if (content instanceof XSDAttributeGroupDefinition) {
571            processAttributeGroupDefinition((XSDAttributeGroupDefinition)content, parentNode);
572            return;
573        }
574        if (content instanceof XSDAttributeUse) {
575            processAttributeUse((XSDAttributeUse)content, parentNode);
576            return;
577        }
578        assert false : "Invalid attribute group content type";
579    }
580
581    protected void processAttributeGroupDefinition( XSDAttributeGroupDefinition defn,
582                                                    Node parentNode ) throws RepositoryException {
583        if (defn == null) {
584            return;
585        }
586        Node attributeGroupNode = null;
587        if (defn.isAttributeGroupDefinitionReference()) {
588            XSDAttributeGroupDefinition resolved = defn.getResolvedAttributeGroupDefinition();
589            logger.debug("Attribute Group definition (ref): '{0}' in ns '{1}' ",
590                         resolved.getName(),
591                         resolved.getTargetNamespace());
592            attributeGroupNode = parentNode.addNode(resolved.getName(), XsdLexicon.ATTRIBUTE_GROUP);
593            setReference(attributeGroupNode,
594                         XsdLexicon.REF,
595                         ATTRIBUTE_GROUP_DEFINITIONS,
596                         resolved.getTargetNamespace(),
597                         resolved.getName());
598        } else {
599            logger.debug("Attribute Group definition: '{0}' in ns '{1}' ", defn.getName(), defn.getTargetNamespace());
600            attributeGroupNode = parentNode.addNode(defn.getName(), XsdLexicon.ATTRIBUTE_GROUP);
601            registerForSymbolSpace(ATTRIBUTE_GROUP_DEFINITIONS,
602                                   defn.getTargetNamespace(),
603                                   defn.getName(),
604                                   attributeGroupNode.getIdentifier());
605            attributeGroupNode.setProperty(XsdLexicon.NC_NAME, defn.getName());
606            attributeGroupNode.setProperty(XsdLexicon.NAMESPACE, defn.getTargetNamespace());
607
608            for (Object child : defn.getContents()) {
609                if (child instanceof XSDAttributeUse) {
610                    processAttributeUse((XSDAttributeUse)child, attributeGroupNode);
611                } else if (child instanceof XSDWildcard) {
612                    processWildcard((XSDWildcard)child, attributeGroupNode);
613                }
614            }
615        }
616        processAnnotation(defn.getAnnotation(), attributeGroupNode);
617        processNonSchemaAttributes(defn, attributeGroupNode, IGNORED_ATTRIBUTES_ATTR_GROUP_DEF);
618    }
619
620    protected Node processWildcard( XSDWildcard wildcard,
621                                    Node parentNode ) throws RepositoryException {
622        if (wildcard == null) {
623            return null;
624        }
625        logger.debug("Any Attribute");
626
627        Node anyAttributeNode = parentNode.addNode(XsdLexicon.ANY_ATTRIBUTE, XsdLexicon.ANY_ATTRIBUTE);
628
629        @SuppressWarnings( "unchecked" )
630        EList<String> nsConstraints = wildcard.getNamespaceConstraint();
631        if (nsConstraints != null && !nsConstraints.isEmpty()) {
632            Set<String> values = new HashSet<String>();
633            for (String nsConstraint : nsConstraints) {
634                if (nsConstraint == null) continue;
635                nsConstraint = nsConstraint.trim();
636                if (nsConstraint.length() == 0) continue;
637                values.add(nsConstraint);
638            }
639            if (!values.isEmpty()) {
640                anyAttributeNode.setProperty(XsdLexicon.NAMESPACE, values.toArray(new String[values.size()]));
641            }
642        }
643        if (wildcard.getProcessContents() != null) {
644            XSDProcessContents processContents = wildcard.getProcessContents();
645            anyAttributeNode.setProperty(XsdLexicon.PROCESS_CONTENTS, processContents.getLiteral());
646        }
647        processAnnotation(wildcard.getAnnotation(), anyAttributeNode);
648        processNonSchemaAttributes(wildcard, anyAttributeNode, IGNORED_ATTRIBUTES_WILDCARD);
649        return anyAttributeNode;
650    }
651
652    protected void processAttributeUse( XSDAttributeUse use,
653                                        Node parentNode ) throws RepositoryException {
654        // Process the attribute declaration ...
655        Node attributeDeclaration = processAttributeDeclaration(use.getAttributeDeclaration(), parentNode, true);
656        if (use.getUse() != null) {
657            attributeDeclaration.setProperty(XsdLexicon.USE, use.getUse().getLiteral());
658        }
659        processNonSchemaAttributes(use, attributeDeclaration, IGNORED_ATTRIBUTES_ATTR_USE);
660    }
661
662    protected void processNonSchemaAttributes( XSDConcreteComponent component,
663                                               Node node,
664                                               Set<String> excludeAttributes) throws RepositoryException {
665        if (component == null) {
666            return;
667        }
668        Element element = component.getElement();
669        if (element == null) {
670            return;
671        }
672
673        NamedNodeMap attributes = element.getAttributes();
674        if (attributes == null) {
675            return;
676        }
677
678        for (int i = 0, len = attributes.getLength(); i != len; ++i) {
679            org.w3c.dom.Node attribute = attributes.item(i);
680            if (attribute.getNodeType() != org.w3c.dom.Node.ATTRIBUTE_NODE) {
681                continue;
682            }
683            String namespaceUri = attribute.getNamespaceURI();
684            if (!XsdLexicon.Namespace.URI.equals(namespaceUri)) {
685                // Record any attribute that is not in the XSD namespace ...
686                String localName = attribute.getLocalName();
687                if (excludeAttributes.contains(localName)) {
688                    continue;
689                }
690                String value = attribute.getNodeValue();
691                if (value == null) continue;
692                if (namespaceUri != null) {
693                    NamespaceRegistry namespaceRegistry = node.getSession().getWorkspace().getNamespaceRegistry();
694                    String prefix = registerNamespace(namespaceRegistry, namespaceUri, attribute.getPrefix());
695                    String propertyName = prefix + ":" + localName;
696                    node.setProperty(propertyName, value);
697                } else {
698                    node.setProperty(localName, value);
699                }
700            }
701        }
702    }
703
704    protected void processAnnotation( XSDAnnotation annotation,
705                                      Node node ) throws RepositoryException {
706        if (annotation == null) {
707            return;
708        }
709        StringBuilder sb = new StringBuilder();
710        for (Object obj : annotation.getUserInformation()) {
711            Element element = (Element)obj;
712            if (element.getLocalName().equals("documentation")) {
713                String content = element.getTextContent();
714                if (content != null) sb.append(content);
715            }
716        }
717        if (sb.length() != 0) {
718            String content = sb.toString();
719            content = content.trim();
720            if (content.length() != 0) {
721                node.setProperty(DESCRIPTION, content);
722            }
723        }
724    }
725
726    protected void processAnnotations( List<XSDAnnotation> annotations,
727                                       Node parentNode ) throws RepositoryException {
728        assert annotations != null;
729        StringBuilder sb = new StringBuilder();
730        for (XSDAnnotation annotation : annotations) {
731            for (Object obj : annotation.getUserInformation()) {
732                Element element = (Element)obj;
733                if (element.getLocalName().equals("documentation")) {
734                    String content = element.getTextContent();
735                    if (content != null) sb.append(content);
736                }
737            }
738            sb.append(System.getProperty("line.separator"));
739        }
740        if (sb.length() != 0) {
741            String content = sb.toString();
742            content = content.trim();
743            if (content.length() != 0) {
744                parentNode.setProperty(DESCRIPTION, content);
745            }
746        }
747    }
748
749    /**
750     * Given an {@link XSDFacet}, determines the JCR property type based on the value of the facet.
751     * 
752     * @param facetValue a String representing the lexical value of the facet, which can be null.
753     * @param defaultPropertyType a given property type, of which we expected the string value to be convertible to.
754     * @return a property type to which the string value can be converted
755     */
756    private int determineJCRPropertyTypeForFacet( String facetValue,
757                                                  int defaultPropertyType ) {
758        switch (defaultPropertyType) {
759            case PropertyType.LONG: {
760                try {
761                    Long.valueOf(facetValue);
762                    return PropertyType.LONG;
763                } catch (NumberFormatException e) {
764                    return PropertyType.DECIMAL;
765                }
766            }
767            default: {
768                return defaultPropertyType;
769            }
770        }
771    }
772
773    protected void processFacet( XSDFacet facet,
774                                 Node node,
775                                 String propertyName,
776                                 int propertyType ) throws RepositoryException {
777        if (facet == null) {
778            return;
779        }
780        String lexicalValue = facet.getLexicalValue();
781        if (lexicalValue != null) {
782            int actualPropertyType = determineJCRPropertyTypeForFacet(lexicalValue, propertyType);
783            Value value = context.valueFactory().createValue(facet.getLexicalValue(), actualPropertyType);
784            node.setProperty(propertyName, value);
785        } else if (facet instanceof XSDRepeatableFacet) {
786            Set<String> values = getRepeatableFacetValues((XSDRepeatableFacet)facet);
787            if (!values.isEmpty()) {
788                node.setProperty(propertyName, values.toArray(new String[values.size()]));
789            }
790        }
791    }
792
793    private Set<String> getRepeatableFacetValues( XSDRepeatableFacet facet ) {
794        EList<?> facetValues = null;
795        if (facet instanceof XSDPatternFacet) {
796            facetValues = ((XSDPatternFacet)facet).getValue();
797        } else if (facet instanceof XSDEnumerationFacet) {
798            facetValues = ((XSDEnumerationFacet)facet).getValue();
799        }
800
801        Set<String> values = new HashSet<String>();
802        if (facetValues != null && !facetValues.isEmpty()) {
803            for (Object enumValue : facetValues) {
804                values.add(enumValue.toString());
805            }
806        }
807        return values;
808    }
809
810    protected <Facet extends XSDFacet> void processFacetsList( List<Facet> facets,
811                                                               Node node,
812                                                               String propertyName ) throws RepositoryException {
813        if (facets == null) {
814            return;
815        }
816
817        Set<String> values = new HashSet<String>();
818        for (XSDFacet facet : facets) {
819            String lexicalValue = facet.getLexicalValue();
820            if (lexicalValue != null) {
821                values.add(facet.getLexicalValue());
822            } else if (facet instanceof XSDRepeatableFacet) {
823                values.addAll(getRepeatableFacetValues((XSDRepeatableFacet)facet));
824            }
825        }
826
827        if (!values.isEmpty()) {
828            node.setProperty(propertyName, values.toArray(new String[values.size()]));
829        }
830    }
831
832    protected <Enumerator extends AbstractEnumerator> void processEnumerators( List<Enumerator> enumerators,
833                                                                               Node node,
834                                                                               String propertyName ) throws RepositoryException {
835        if (enumerators == null) {
836            return;
837        }
838        Set<String> values = new HashSet<String>();
839        for (Enumerator enumValue : enumerators) {
840            String value = enumValue.getLiteral();
841            if (value != null) {
842                values.add(value);
843            }
844        }
845        if (!values.isEmpty()) {
846            node.setProperty(propertyName, values.toArray(new String[values.size()]));
847        }
848    }
849
850    protected <Enumerator extends AbstractEnumerator> void processEnumerator( Enumerator enumerator,
851                                                                              Node node,
852                                                                              String propertyName ) throws RepositoryException {
853        if (enumerator != null && enumerator.getLiteral() != null) {
854            node.setProperty(propertyName, enumerator.getLiteral());
855        }
856    }
857
858    protected static Set<String> removePrefix( String... attributeNames ) {
859        if (attributeNames.length == 0) {
860            return Collections.emptySet();
861        }
862        Set<String> result = new HashSet<>(attributeNames.length);
863        for (String attributeName : attributeNames) {
864            if (!StringUtil.isBlank(attributeName) && attributeName.contains(":")) {
865                attributeName = attributeName.substring(attributeName.indexOf(":") + 1);
866            }
867            result.add(attributeName);
868        }
869        return result;
870    }
871}