/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.sequencer.xsd;
import static org.modeshape.sequencer.sramp.SrampLexicon.DESCRIPTION;
import static org.modeshape.sequencer.xsd.XsdLexicon.IMPORT;
import java.io.InputStream;
import java.io.Reader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.jcr.NamespaceRegistry;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import org.eclipse.emf.common.util.AbstractEnumerator;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xsd.XSDAnnotation;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDAttributeGroupContent;
import org.eclipse.xsd.XSDAttributeGroupDefinition;
import org.eclipse.xsd.XSDAttributeUse;
import org.eclipse.xsd.XSDComplexTypeContent;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDCompositor;
import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDDerivationMethod;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDEnumerationFacet;
import org.eclipse.xsd.XSDFacet;
import org.eclipse.xsd.XSDImport;
import org.eclipse.xsd.XSDInclude;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDModelGroupDefinition;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDParticleContent;
import org.eclipse.xsd.XSDPatternFacet;
import org.eclipse.xsd.XSDProcessContents;
import org.eclipse.xsd.XSDProhibitedSubstitutions;
import org.eclipse.xsd.XSDRedefine;
import org.eclipse.xsd.XSDRepeatableFacet;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSimpleFinal;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.XSDWildcard;
import org.eclipse.xsd.util.XSDParser;
import org.modeshape.common.annotation.NotThreadSafe;
import org.modeshape.common.util.SizeMeasuringInputStream;
import org.modeshape.common.util.SizeMeasuringReader;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.api.sequencer.Sequencer;
import org.modeshape.sequencer.sramp.AbstractResolvingReader;
import org.modeshape.sequencer.sramp.SrampLexicon;
import org.modeshape.sequencer.sramp.SymbolSpace;
import org.modeshape.sequencer.xsd.XsdSequencer.MimeTypeConstants;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.InputSource;
/**
* A class that can parse XML Schema Documents and create a node structure based on the schema information.
* <p>
* This class can be subclassed and any of the 'process' methods overridden to customize the derived graph structure.
* </p>
*/
@NotThreadSafe
public class XsdReader extends AbstractResolvingReader {
/**
* In XML Schema, there is a distinct symbol space within each target namespace for each kind of <a
* href="http://www.w3.org/TR/xmlschema-1/#concepts-data-model">declaration and definition component</a>, except that within a
* target namespace the simple type definitions and complex type definitions share a single symbol space. See the <a
* href="http://www.w3.org/TR/xmlschema-1/#concepts-nameSymbolSpaces">specification</a> for details.
*/
public static final SymbolSpace ATTRIBUTE_DECLARATIONS = new SymbolSpace("AttributeDeclarations");
public static final SymbolSpace ELEMENT_DECLARATION = new SymbolSpace("ElementDeclarations");
public static final SymbolSpace TYPE_DEFINITIONS = new SymbolSpace("TypeDeclarations");
public static final SymbolSpace ATTRIBUTE_GROUP_DEFINITIONS = new SymbolSpace("AttributeGroupDeclarations");
public static final SymbolSpace MODEL_GROUP_DEFINITIONS = new SymbolSpace("ModelGroupDeclarations");
public static final SymbolSpace IDENTITY_CONSTRAINT_DEFINITIONS = new SymbolSpace("IdentityConstraintDeclarations");
/**
* A set of attribute names which should be ignored when/if they appear without a schema
*/
private static final Set<String> IGNORED_ATTRIBUTES_IMPORT = removePrefix(XsdLexicon.NAMESPACE, XsdLexicon.SCHEMA_LOCATION);
private static final Set<String> IGNORED_ATTRIBUTES_INCLUDE = removePrefix(XsdLexicon.SCHEMA_LOCATION);
private static final Set<String> IGNORED_ATTRIBUTES_REDEFINE = removePrefix(XsdLexicon.SCHEMA_LOCATION);
private static final Set<String> IGNORED_ATTRIBUTES_SIMPLE_TYPE_DEF = removePrefix(XsdLexicon.NAMESPACE, XsdLexicon.NC_NAME);
private static final Set<String> IGNORED_ATTRIBUTES_COMPLEX_TYPE_DEF = removePrefix(XsdLexicon.NAMESPACE,
XsdLexicon.BASE_TYPE_NAME,
XsdLexicon.BASE_TYPE_NAMESPACE,
XsdLexicon.BLOCK,
XsdLexicon.FINAL,
XsdLexicon.ABSTRACT, XsdLexicon.MIXED);
private static final Set<String> IGNORED_ATTRIBUTES_ELEMENT_DECL = removePrefix(XsdLexicon.TYPE_NAME, XsdLexicon.TYPE_NAMESPACE,
XsdLexicon.TYPE_REFERENCE, XsdLexicon.FORM,
XsdLexicon.FINAL, XsdLexicon.BLOCK);
private static final Set<String> IGNORED_ATTRIBUTES_ATTR_DECL = removePrefix(XsdLexicon.TYPE_NAME, XsdLexicon.TYPE_NAMESPACE);
private static final Set<String> IGNORED_ATTRIBUTES_COMPLEX_TYPE_CONTENT = removePrefix(XsdLexicon.METHOD);
private static final Set<String> IGNORED_ATTRIBUTES_ATTR_GROUP_DEF = removePrefix(XsdLexicon.REF, XsdLexicon.NC_NAME,
XsdLexicon.NAMESPACE);
private static final Set<String> IGNORED_ATTRIBUTES_WILDCARD = removePrefix(XsdLexicon.NAMESPACE, XsdLexicon.PROCESS_CONTENTS);
private static final Set<String> IGNORED_ATTRIBUTES_ATTR_USE = removePrefix(XsdLexicon.USE);
public XsdReader( Sequencer.Context context ) {
super(context);
}
@Override
public void read( InputSource source,
Node outputNode ) throws Exception {
logger.debug("Processing XSD '{0}'", outputNode);
Reader reader = null;
InputStream stream = null;
try {
// Parse the XSD, measuring the number of bytes as we read ...
Map<?, ?> options = new HashMap<Object, Object>();
XSDParser parser = new XSDParser(options);
AtomicLong contentSize = new AtomicLong();
if (source.getCharacterStream() != null) {
reader = new SizeMeasuringReader(source.getCharacterStream(), contentSize);
source = new InputSource(reader);
} else {
stream = new SizeMeasuringInputStream(source.getByteStream(), contentSize);
source = new InputSource(stream);
}
parser.parse(source);
// Get some metadata about the XSD ...
String encoding = parser.getEncoding();
// Convert the XSD to content ...
XSDSchema schema = parser.getSchema();
process(schema, encoding, contentSize.get(), outputNode);
} finally {
try {
if (reader != null) reader.close();
} catch (Exception e) {
logger.debug(e, "Cannot close reader stream ");
} finally {
try {
if (stream != null) stream.close();
} catch (Exception e) {
logger.debug(e, "Cannot close reader stream ");
}
}
}
}
/**
* Read an XSDSchema instance and create the node hierarchy under the given root node.
*
* @param schema the schema object; may not be null
* @param encoding the encoding for the XSD; may be null if the encoding is not specified
* @param contentSize the size of the XML Schema Document content; may not be negative
* @param rootNode the root node that will be populated with the XML Schema Document information
* @throws Exception if there is a probelm reading the XSD content
*/
protected void process( XSDSchema schema,
String encoding,
long contentSize,
Node rootNode ) throws Exception {
assert schema != null;
logger.debug("Target namespace: '{0}'", schema.getTargetNamespace());
rootNode.setProperty(SrampLexicon.CONTENT_TYPE, MimeTypeConstants.APPLICATION_XML);
if (encoding != null) {
rootNode.setProperty(SrampLexicon.CONTENT_ENCODING, encoding);
}
rootNode.setProperty(SrampLexicon.CONTENT_SIZE, contentSize);
// Parse the annotations first to aggregate them all into a single 'sramp:description' property ...
@SuppressWarnings( "unchecked" )
List<XSDAnnotation> annotations = schema.getAnnotations();
processAnnotations(annotations, rootNode);
processNonSchemaAttributes(schema, rootNode, Collections.<String>emptySet());
// Parse the objects ...
for (EObject obj : schema.eContents()) {
if (obj instanceof XSDSimpleTypeDefinition) {
processSimpleTypeDefinition((XSDSimpleTypeDefinition)obj, rootNode);
} else if (obj instanceof XSDComplexTypeDefinition) {
processComplexTypeDefinition((XSDComplexTypeDefinition)obj, rootNode);
} else if (obj instanceof XSDElementDeclaration) {
processElementDeclaration((XSDElementDeclaration)obj, rootNode);
} else if (obj instanceof XSDAttributeDeclaration) {
processAttributeDeclaration((XSDAttributeDeclaration)obj, rootNode, false);
} else if (obj instanceof XSDImport) {
processImport((XSDImport)obj, rootNode);
} else if (obj instanceof XSDInclude) {
processInclude((XSDInclude)obj, rootNode);
} else if (obj instanceof XSDRedefine) {
processRedefine((XSDRedefine)obj, rootNode);
} else if (obj instanceof XSDAttributeGroupDefinition) {
processAttributeGroupDefinition((XSDAttributeGroupDefinition)obj, rootNode);
} else if (obj instanceof XSDAnnotation) {
// already processed above ...
}
}
// Resolve any outstanding, unresolved references ...
resolveReferences();
}
protected void processImport( XSDImport xsdImport,
Node parentNode ) throws RepositoryException {
logger.debug("Import: '{0}' with location '{1}' ", xsdImport.getNamespace(), xsdImport.getSchemaLocation());
Node importNode = parentNode.addNode(IMPORT, IMPORT);
importNode.setProperty(XsdLexicon.NAMESPACE, xsdImport.getNamespace());
importNode.setProperty(XsdLexicon.SCHEMA_LOCATION, xsdImport.getSchemaLocation());
processNonSchemaAttributes(xsdImport, importNode, IGNORED_ATTRIBUTES_IMPORT);
}
protected void processInclude( XSDInclude xsdInclude,
Node parentNode ) throws RepositoryException {
logger.debug("Include: '{0}' ", xsdInclude.getSchemaLocation());
Node includeNode = parentNode.addNode(XsdLexicon.INCLUDE, XsdLexicon.INCLUDE);
includeNode.setProperty(XsdLexicon.SCHEMA_LOCATION, xsdInclude.getSchemaLocation());
processNonSchemaAttributes(xsdInclude, includeNode, IGNORED_ATTRIBUTES_INCLUDE);
}
protected void processRedefine( XSDRedefine redefine,
Node parentNode ) throws RepositoryException {
logger.debug("Include: '{0}' ", redefine.getSchemaLocation());
Node redefineNode = parentNode.addNode(XsdLexicon.REDEFINE, XsdLexicon.REDEFINE);
redefineNode.setProperty(XsdLexicon.SCHEMA_LOCATION, redefine.getSchemaLocation());
processNonSchemaAttributes(redefine, redefineNode, IGNORED_ATTRIBUTES_REDEFINE);
}
protected void processSimpleTypeDefinition( XSDSimpleTypeDefinition type,
Node node ) throws RepositoryException {
boolean isAnonymous = type.getName() == null;
String nodeName = isAnonymous ? XsdLexicon.SIMPLE_TYPE : type.getName();
// This is a normal simple type definition ...
logger.debug("Simple type: '{0}' in ns '{1}' ", type.getName(), type.getTargetNamespace());
Node typeNode = node.addNode(nodeName, XsdLexicon.SIMPLE_TYPE_DEFINITION);
typeNode.setProperty(XsdLexicon.NAMESPACE, type.getTargetNamespace());
if (!isAnonymous) {
typeNode.setProperty(XsdLexicon.NC_NAME, type.getName());
registerForSymbolSpace(TYPE_DEFINITIONS, type.getTargetNamespace(), type.getName(), typeNode.getIdentifier());
}
processTypeFacets(type, typeNode, type.getBaseType());
processNonSchemaAttributes(type, typeNode, IGNORED_ATTRIBUTES_SIMPLE_TYPE_DEF);
}
protected void processTypeFacets( XSDSimpleTypeDefinition type,
Node typeNode,
XSDTypeDefinition baseType ) throws RepositoryException {
if (baseType == null) {
baseType = type.getBaseType();
}
if (baseType == type) {
// The base type is the anytype ...
baseType = type.getSchema()
.getSchemaForSchema()
.resolveSimpleTypeDefinition("http://www.w3.org/2001/XMLSchema", "anyType");
}
if (baseType != null) {
typeNode.setProperty(XsdLexicon.BASE_TYPE_NAME, baseType.getName());
typeNode.setProperty(XsdLexicon.BASE_TYPE_NAMESPACE, baseType.getTargetNamespace());
setReference(typeNode,
XsdLexicon.BASE_TYPE_REFERENCE,
TYPE_DEFINITIONS,
baseType.getTargetNamespace(),
baseType.getName());
}
processFacet(type.getEffectiveMaxLengthFacet(), typeNode, XsdLexicon.MAX_LENGTH, PropertyType.LONG);
processFacet(type.getMaxLengthFacet(), typeNode, XsdLexicon.MAX_LENGTH, PropertyType.LONG);
processFacet(type.getEffectiveMinLengthFacet(), typeNode, XsdLexicon.MIN_LENGTH, PropertyType.LONG);
processFacet(type.getMinLengthFacet(), typeNode, XsdLexicon.MIN_LENGTH, PropertyType.LONG);
processFacet(type.getEffectiveMaxFacet(), typeNode, XsdLexicon.MAX_VALUE_EXCLUSIVE, PropertyType.LONG);
processFacet(type.getMaxExclusiveFacet(), typeNode, XsdLexicon.MAX_VALUE_EXCLUSIVE, PropertyType.LONG);
processFacet(type.getEffectiveMinFacet(), typeNode, XsdLexicon.MIN_VALUE_EXCLUSIVE, PropertyType.LONG);
processFacet(type.getMinExclusiveFacet(), typeNode, XsdLexicon.MIN_VALUE_EXCLUSIVE, PropertyType.LONG);
processFacet(type.getMaxInclusiveFacet(), typeNode, XsdLexicon.MAX_VALUE_INCLUSIVE, PropertyType.LONG);
processFacet(type.getMinInclusiveFacet(), typeNode, XsdLexicon.MIN_VALUE_INCLUSIVE, PropertyType.LONG);
processFacet(type.getEffectiveTotalDigitsFacet(), typeNode, XsdLexicon.TOTAL_DIGITS, PropertyType.LONG);
processFacet(type.getTotalDigitsFacet(), typeNode, XsdLexicon.TOTAL_DIGITS, PropertyType.LONG);
processFacet(type.getEffectiveFractionDigitsFacet(), typeNode, XsdLexicon.FRACTION_DIGITS, PropertyType.LONG);
processFacet(type.getFractionDigitsFacet(), typeNode, XsdLexicon.FRACTION_DIGITS, PropertyType.LONG);
processFacet(type.getEffectiveWhiteSpaceFacet(), typeNode, XsdLexicon.WHITESPACE, PropertyType.STRING);
processFacet(type.getWhiteSpaceFacet(), typeNode, XsdLexicon.WHITESPACE, PropertyType.STRING);
processFacet(type.getEffectivePatternFacet(), typeNode, XsdLexicon.PATTERN, PropertyType.STRING);
@SuppressWarnings( "unchecked" )
List<XSDPatternFacet> patternFacets = type.getPatternFacets();
processFacetsList(patternFacets, typeNode, XsdLexicon.PATTERN);
processFacet(type.getEffectiveEnumerationFacet(), typeNode, XsdLexicon.ENUMERATED_VALUES, PropertyType.STRING);
@SuppressWarnings( "unchecked" )
List<XSDEnumerationFacet> enumFacets = type.getEnumerationFacets();
processFacetsList(enumFacets, typeNode, XsdLexicon.ENUMERATED_VALUES);
@SuppressWarnings( "unchecked" )
List<XSDSimpleFinal> finalFacets2 = type.getFinal();
processEnumerators(finalFacets2, typeNode, XsdLexicon.FINAL);
processAnnotation(type.getAnnotation(), typeNode);
}
protected void processComplexTypeDefinition( XSDComplexTypeDefinition type,
Node parentNode ) throws RepositoryException {
logger.debug("Complex type: '{0}' in ns '{1}' ", type.getName(), type.getTargetNamespace());
boolean isAnonymous = type.getName() == null;
String nodeName = isAnonymous ? XsdLexicon.COMPLEX_TYPE : type.getName();
Node typeNode = parentNode.addNode(nodeName, XsdLexicon.COMPLEX_TYPE_DEFINITION);
typeNode.setProperty(XsdLexicon.NAMESPACE, type.getTargetNamespace());
if (!isAnonymous) {
typeNode.setProperty(XsdLexicon.NC_NAME, type.getName());
registerForSymbolSpace(TYPE_DEFINITIONS, type.getTargetNamespace(), type.getName(), typeNode.getIdentifier());
}
XSDTypeDefinition baseType = type.getBaseType();
if (baseType == type) {
// The base type is the anytype ...
baseType = type.getSchema()
.getSchemaForSchema()
.resolveComplexTypeDefinition("http://www.w3.org/2001/XMLSchema", "anyType");
}
if (baseType != null) {
typeNode.setProperty(XsdLexicon.BASE_TYPE_NAME, baseType.getName());
typeNode.setProperty(XsdLexicon.BASE_TYPE_NAMESPACE, baseType.getTargetNamespace());
}
typeNode.setProperty(XsdLexicon.ABSTRACT, type.isAbstract());
typeNode.setProperty(XsdLexicon.MIXED, type.isMixed());
@SuppressWarnings( "unchecked" )
List<XSDProhibitedSubstitutions> blocks = type.getBlock();
processEnumerators(blocks, typeNode, XsdLexicon.BLOCK);
@SuppressWarnings( "unchecked" )
List<XSDSimpleFinal> finalFacets = type.getFinal();
processEnumerators(finalFacets, typeNode, XsdLexicon.FINAL);
processComplexTypeContent(type.getContent(), typeNode);
processAnnotation(type.getAnnotation(), typeNode);
processNonSchemaAttributes(type, typeNode, IGNORED_ATTRIBUTES_COMPLEX_TYPE_DEF);
}
protected Node processElementDeclaration( XSDElementDeclaration decl,
Node parentNode ) throws RepositoryException {
if (decl == null) {
return null;
}
logger.debug("Element declaration: '{0}' in ns '{1}' ", decl.getName(), decl.getTargetNamespace());
Node declarationNode;
if (decl.getName() != null) {
// Normal element declaration ...
declarationNode = parentNode.addNode(decl.getName(), XsdLexicon.ELEMENT_DECLARATION);
declarationNode.setProperty(XsdLexicon.NC_NAME, decl.getName());
declarationNode.setProperty(XsdLexicon.NAMESPACE, decl.getTargetNamespace());
} else {
assert decl.isFeatureReference() : "expected element reference";
XSDElementDeclaration resolved = decl.getResolvedElementDeclaration();
declarationNode = parentNode.addNode(resolved.getName(), XsdLexicon.ELEMENT_DECLARATION);
declarationNode.setProperty(XsdLexicon.REF_NAME, resolved.getName());
declarationNode.setProperty(XsdLexicon.REF_NAMESPACE, resolved.getTargetNamespace());
setReference(declarationNode, XsdLexicon.REF, ELEMENT_DECLARATION, resolved.getTargetNamespace(), resolved.getName());
}
if (decl.isGlobal()) {
registerForSymbolSpace(ELEMENT_DECLARATION,
decl.getTargetNamespace(),
decl.getName(),
declarationNode.getIdentifier());
}
declarationNode.setProperty(XsdLexicon.ABSTRACT, decl.isAbstract());
declarationNode.setProperty(XsdLexicon.NILLABLE, decl.isNillable());
XSDTypeDefinition type = decl.getType();
if (type != null) {
declarationNode.setProperty(XsdLexicon.TYPE_NAME, type.getName());
declarationNode.setProperty(XsdLexicon.TYPE_NAMESPACE, type.getTargetNamespace());
setReference(declarationNode, XsdLexicon.TYPE_REFERENCE, TYPE_DEFINITIONS, type.getTargetNamespace(), type.getName());
}
if (decl.getAnonymousTypeDefinition() == type) {
// It's anonymous, so we need to process the definition here ...
if (type instanceof XSDComplexTypeDefinition) {
processComplexTypeDefinition((XSDComplexTypeDefinition)type, declarationNode);
} else if (type instanceof XSDSimpleTypeDefinition) {
processSimpleTypeDefinition((XSDSimpleTypeDefinition)type, declarationNode);
}
}
processEnumerator(decl.getForm(), declarationNode, XsdLexicon.FORM);
@SuppressWarnings( "unchecked" )
List<XSDProhibitedSubstitutions> finals = decl.getLexicalFinal();
processEnumerators(finals, declarationNode, XsdLexicon.FINAL);
@SuppressWarnings( "unchecked" )
List<XSDProhibitedSubstitutions> blocks = decl.getBlock();
processEnumerators(blocks, declarationNode, XsdLexicon.BLOCK);
processAnnotation(decl.getAnnotation(), declarationNode);
processNonSchemaAttributes(type, declarationNode, IGNORED_ATTRIBUTES_ELEMENT_DECL);
return declarationNode;
}
protected Node processAttributeDeclaration( XSDAttributeDeclaration decl,
Node parentNode,
boolean isUse ) throws RepositoryException {
if (decl == null) {
return null;
}
logger.debug("Attribute declaration: '{0}' in ns '{1}' ", decl.getName(), decl.getTargetNamespace());
Node attributeDeclarationNode = parentNode.addNode(decl.getName(), XsdLexicon.ATTRIBUTE_DECLARATION);
attributeDeclarationNode.setProperty(XsdLexicon.NC_NAME, decl.getName());
attributeDeclarationNode.setProperty(XsdLexicon.NAMESPACE, decl.getTargetNamespace());
if (decl.isGlobal() && !isUse) {
registerForSymbolSpace(ATTRIBUTE_DECLARATIONS,
decl.getTargetNamespace(),
decl.getName(),
attributeDeclarationNode.getIdentifier());
}
XSDTypeDefinition type = decl.getType();
if (type != null) {
attributeDeclarationNode.setProperty(XsdLexicon.TYPE_NAME, type.getName());
attributeDeclarationNode.setProperty(XsdLexicon.TYPE_NAMESPACE, type.getTargetNamespace());
}
processAnnotation(decl.getAnnotation(), attributeDeclarationNode);
processNonSchemaAttributes(type, attributeDeclarationNode, IGNORED_ATTRIBUTES_ATTR_DECL);
return attributeDeclarationNode;
}
protected void processComplexTypeContent( XSDComplexTypeContent content,
Node parentNode ) throws RepositoryException {
if (content == null) {
return;
}
XSDComplexTypeDefinition owner = (XSDComplexTypeDefinition)content.eContainer();
if (content instanceof XSDParticle) {
processParticle((XSDParticle)content, parentNode);
} else if (content instanceof XSDSimpleTypeDefinition) {
Node contentNode = parentNode.addNode(XsdLexicon.SIMPLE_CONTENT, XsdLexicon.SIMPLE_CONTENT);
processTypeFacets((XSDSimpleTypeDefinition)content, contentNode, owner.getBaseTypeDefinition());
}
XSDDerivationMethod method = owner.getDerivationMethod();
if (method != null) {
parentNode.setProperty(XsdLexicon.METHOD, method.getLiteral());
}
@SuppressWarnings( "unchecked" )
List<XSDAttributeGroupContent> attributeGroupContents = owner.getAttributeContents();
if (attributeGroupContents != null) {
for (XSDAttributeGroupContent attributeGroup : attributeGroupContents) {
processAttributeGroupContent(attributeGroup, parentNode);
}
}
@SuppressWarnings( "unchecked" )
List<XSDAttributeUse> attributeUses = owner.getAttributeUses();
if (attributeUses != null) {
for (XSDAttributeUse attributeUse : attributeUses) {
processAttributeUse(attributeUse, parentNode);
}
}
XSDWildcard wildcard = owner.getAttributeWildcard();
processWildcard(wildcard, parentNode);
processNonSchemaAttributes(owner, parentNode, IGNORED_ATTRIBUTES_COMPLEX_TYPE_CONTENT);
}
protected void processParticle( XSDParticle content,
Node node ) throws RepositoryException {
if (content == null) {
return;
}
XSDParticleContent particle = content.getContent();
Node particleNode = null;
if (particle instanceof XSDModelGroupDefinition) {
particleNode = processModelGroupDefinition((XSDModelGroupDefinition)particle, node);
} else if (particle instanceof XSDElementDeclaration) {
particleNode = processElementDeclaration((XSDElementDeclaration)particle, node);
} else if (particle instanceof XSDModelGroup) {
particleNode = processModelGroup((XSDModelGroup)particle, node);
} else if (particle instanceof XSDWildcard) {
particleNode = processWildcard((XSDWildcard)particle, node);
}
if (particleNode != null) {
long minOccurs = content.getMinOccurs();
long maxOccurs = content.getMaxOccurs();
particleNode.setProperty(XsdLexicon.MIN_OCCURS, minOccurs);
if (maxOccurs >= 0) {
particleNode.setProperty(XsdLexicon.MAX_OCCURS, maxOccurs);
} else {
// unbounded ...
}
}
}
protected Node processModelGroupDefinition( XSDModelGroupDefinition defn,
Node parentNode ) throws RepositoryException {
if (defn == null) {
return null;
}
XSDModelGroup group = defn.getModelGroup();
processNonSchemaAttributes(defn, parentNode, Collections.<String>emptySet());
return processModelGroup(group, parentNode);
}
protected Node processModelGroup( XSDModelGroup group,
Node parentNode ) throws RepositoryException {
if (group == null) {
return null;
}
XSDCompositor compositor = group.getCompositor();
String primaryTypeName = getPrimaryTypeFromCompositor(compositor);
Node childNode = parentNode.addNode(primaryTypeName, primaryTypeName);
@SuppressWarnings( "unchecked" )
List<XSDParticle> particles = group.getParticles();
for (XSDParticle particle : particles) {
processParticle(particle, childNode);
}
processNonSchemaAttributes(group, childNode, Collections.<String>emptySet());
return childNode;
}
private String getPrimaryTypeFromCompositor( XSDCompositor compositor ) {
String primaryTypeName = null;
switch (compositor.getValue()) {
case XSDCompositor.ALL:
primaryTypeName = XsdLexicon.ALL;
break;
case XSDCompositor.CHOICE:
primaryTypeName = XsdLexicon.CHOICE;
break;
case XSDCompositor.SEQUENCE:
primaryTypeName = XsdLexicon.SEQUENCE;
break;
default:
assert false : "should not get here";
}
return primaryTypeName;
}
protected void processAttributeGroupContent( XSDAttributeGroupContent content,
Node parentNode ) throws RepositoryException {
if (content == null) {
return;
}
if (content instanceof XSDAttributeGroupDefinition) {
processAttributeGroupDefinition((XSDAttributeGroupDefinition)content, parentNode);
return;
}
if (content instanceof XSDAttributeUse) {
processAttributeUse((XSDAttributeUse)content, parentNode);
return;
}
assert false : "Invalid attribute group content type";
}
protected void processAttributeGroupDefinition( XSDAttributeGroupDefinition defn,
Node parentNode ) throws RepositoryException {
if (defn == null) {
return;
}
Node attributeGroupNode = null;
if (defn.isAttributeGroupDefinitionReference()) {
XSDAttributeGroupDefinition resolved = defn.getResolvedAttributeGroupDefinition();
logger.debug("Attribute Group definition (ref): '{0}' in ns '{1}' ",
resolved.getName(),
resolved.getTargetNamespace());
attributeGroupNode = parentNode.addNode(resolved.getName(), XsdLexicon.ATTRIBUTE_GROUP);
setReference(attributeGroupNode,
XsdLexicon.REF,
ATTRIBUTE_GROUP_DEFINITIONS,
resolved.getTargetNamespace(),
resolved.getName());
} else {
logger.debug("Attribute Group definition: '{0}' in ns '{1}' ", defn.getName(), defn.getTargetNamespace());
attributeGroupNode = parentNode.addNode(defn.getName(), XsdLexicon.ATTRIBUTE_GROUP);
registerForSymbolSpace(ATTRIBUTE_GROUP_DEFINITIONS,
defn.getTargetNamespace(),
defn.getName(),
attributeGroupNode.getIdentifier());
attributeGroupNode.setProperty(XsdLexicon.NC_NAME, defn.getName());
attributeGroupNode.setProperty(XsdLexicon.NAMESPACE, defn.getTargetNamespace());
for (Object child : defn.getContents()) {
if (child instanceof XSDAttributeUse) {
processAttributeUse((XSDAttributeUse)child, attributeGroupNode);
} else if (child instanceof XSDWildcard) {
processWildcard((XSDWildcard)child, attributeGroupNode);
}
}
}
processAnnotation(defn.getAnnotation(), attributeGroupNode);
processNonSchemaAttributes(defn, attributeGroupNode, IGNORED_ATTRIBUTES_ATTR_GROUP_DEF);
}
protected Node processWildcard( XSDWildcard wildcard,
Node parentNode ) throws RepositoryException {
if (wildcard == null) {
return null;
}
logger.debug("Any Attribute");
Node anyAttributeNode = parentNode.addNode(XsdLexicon.ANY_ATTRIBUTE, XsdLexicon.ANY_ATTRIBUTE);
@SuppressWarnings( "unchecked" )
EList<String> nsConstraints = wildcard.getNamespaceConstraint();
if (nsConstraints != null && !nsConstraints.isEmpty()) {
Set<String> values = new HashSet<String>();
for (String nsConstraint : nsConstraints) {
if (nsConstraint == null) continue;
nsConstraint = nsConstraint.trim();
if (nsConstraint.length() == 0) continue;
values.add(nsConstraint);
}
if (!values.isEmpty()) {
anyAttributeNode.setProperty(XsdLexicon.NAMESPACE, values.toArray(new String[values.size()]));
}
}
if (wildcard.getProcessContents() != null) {
XSDProcessContents processContents = wildcard.getProcessContents();
anyAttributeNode.setProperty(XsdLexicon.PROCESS_CONTENTS, processContents.getLiteral());
}
processAnnotation(wildcard.getAnnotation(), anyAttributeNode);
processNonSchemaAttributes(wildcard, anyAttributeNode, IGNORED_ATTRIBUTES_WILDCARD);
return anyAttributeNode;
}
protected void processAttributeUse( XSDAttributeUse use,
Node parentNode ) throws RepositoryException {
// Process the attribute declaration ...
Node attributeDeclaration = processAttributeDeclaration(use.getAttributeDeclaration(), parentNode, true);
if (use.getUse() != null) {
attributeDeclaration.setProperty(XsdLexicon.USE, use.getUse().getLiteral());
}
processNonSchemaAttributes(use, attributeDeclaration, IGNORED_ATTRIBUTES_ATTR_USE);
}
protected void processNonSchemaAttributes( XSDConcreteComponent component,
Node node,
Set<String> excludeAttributes) throws RepositoryException {
if (component == null) {
return;
}
Element element = component.getElement();
if (element == null) {
return;
}
NamedNodeMap attributes = element.getAttributes();
if (attributes == null) {
return;
}
for (int i = 0, len = attributes.getLength(); i != len; ++i) {
org.w3c.dom.Node attribute = attributes.item(i);
if (attribute.getNodeType() != org.w3c.dom.Node.ATTRIBUTE_NODE) {
continue;
}
String namespaceUri = attribute.getNamespaceURI();
if (!XsdLexicon.Namespace.URI.equals(namespaceUri)) {
// Record any attribute that is not in the XSD namespace ...
String localName = attribute.getLocalName();
if (excludeAttributes.contains(localName)) {
continue;
}
String value = attribute.getNodeValue();
if (value == null) continue;
if (namespaceUri != null) {
NamespaceRegistry namespaceRegistry = node.getSession().getWorkspace().getNamespaceRegistry();
String prefix = registerNamespace(namespaceRegistry, namespaceUri, attribute.getPrefix());
String propertyName = prefix + ":" + localName;
node.setProperty(propertyName, value);
} else {
node.setProperty(localName, value);
}
}
}
}
protected void processAnnotation( XSDAnnotation annotation,
Node node ) throws RepositoryException {
if (annotation == null) {
return;
}
StringBuilder sb = new StringBuilder();
for (Object obj : annotation.getUserInformation()) {
Element element = (Element)obj;
if (element.getLocalName().equals("documentation")) {
String content = element.getTextContent();
if (content != null) sb.append(content);
}
}
if (sb.length() != 0) {
String content = sb.toString();
content = content.trim();
if (content.length() != 0) {
node.setProperty(DESCRIPTION, content);
}
}
}
protected void processAnnotations( List<XSDAnnotation> annotations,
Node parentNode ) throws RepositoryException {
assert annotations != null;
StringBuilder sb = new StringBuilder();
for (XSDAnnotation annotation : annotations) {
for (Object obj : annotation.getUserInformation()) {
Element element = (Element)obj;
if (element.getLocalName().equals("documentation")) {
String content = element.getTextContent();
if (content != null) sb.append(content);
}
}
sb.append(System.getProperty("line.separator"));
}
if (sb.length() != 0) {
String content = sb.toString();
content = content.trim();
if (content.length() != 0) {
parentNode.setProperty(DESCRIPTION, content);
}
}
}
/**
* Given an {@link XSDFacet}, determines the JCR property type based on the value of the facet.
*
* @param facetValue a String representing the lexical value of the facet, which can be null.
* @param defaultPropertyType a given property type, of which we expected the string value to be convertible to.
* @return a property type to which the string value can be converted
*/
private int determineJCRPropertyTypeForFacet( String facetValue,
int defaultPropertyType ) {
switch (defaultPropertyType) {
case PropertyType.LONG: {
try {
Long.valueOf(facetValue);
return PropertyType.LONG;
} catch (NumberFormatException e) {
return PropertyType.DECIMAL;
}
}
default: {
return defaultPropertyType;
}
}
}
protected void processFacet( XSDFacet facet,
Node node,
String propertyName,
int propertyType ) throws RepositoryException {
if (facet == null) {
return;
}
String lexicalValue = facet.getLexicalValue();
if (lexicalValue != null) {
int actualPropertyType = determineJCRPropertyTypeForFacet(lexicalValue, propertyType);
Value value = context.valueFactory().createValue(facet.getLexicalValue(), actualPropertyType);
node.setProperty(propertyName, value);
} else if (facet instanceof XSDRepeatableFacet) {
Set<String> values = getRepeatableFacetValues((XSDRepeatableFacet)facet);
if (!values.isEmpty()) {
node.setProperty(propertyName, values.toArray(new String[values.size()]));
}
}
}
private Set<String> getRepeatableFacetValues( XSDRepeatableFacet facet ) {
EList<?> facetValues = null;
if (facet instanceof XSDPatternFacet) {
facetValues = ((XSDPatternFacet)facet).getValue();
} else if (facet instanceof XSDEnumerationFacet) {
facetValues = ((XSDEnumerationFacet)facet).getValue();
}
Set<String> values = new HashSet<String>();
if (facetValues != null && !facetValues.isEmpty()) {
for (Object enumValue : facetValues) {
values.add(enumValue.toString());
}
}
return values;
}
protected <Facet extends XSDFacet> void processFacetsList( List<Facet> facets,
Node node,
String propertyName ) throws RepositoryException {
if (facets == null) {
return;
}
Set<String> values = new HashSet<String>();
for (XSDFacet facet : facets) {
String lexicalValue = facet.getLexicalValue();
if (lexicalValue != null) {
values.add(facet.getLexicalValue());
} else if (facet instanceof XSDRepeatableFacet) {
values.addAll(getRepeatableFacetValues((XSDRepeatableFacet)facet));
}
}
if (!values.isEmpty()) {
node.setProperty(propertyName, values.toArray(new String[values.size()]));
}
}
protected <Enumerator extends AbstractEnumerator> void processEnumerators( List<Enumerator> enumerators,
Node node,
String propertyName ) throws RepositoryException {
if (enumerators == null) {
return;
}
Set<String> values = new HashSet<String>();
for (Enumerator enumValue : enumerators) {
String value = enumValue.getLiteral();
if (value != null) {
values.add(value);
}
}
if (!values.isEmpty()) {
node.setProperty(propertyName, values.toArray(new String[values.size()]));
}
}
protected <Enumerator extends AbstractEnumerator> void processEnumerator( Enumerator enumerator,
Node node,
String propertyName ) throws RepositoryException {
if (enumerator != null && enumerator.getLiteral() != null) {
node.setProperty(propertyName, enumerator.getLiteral());
}
}
protected static Set<String> removePrefix( String... attributeNames ) {
if (attributeNames.length == 0) {
return Collections.emptySet();
}
Set<String> result = new HashSet<>(attributeNames.length);
for (String attributeName : attributeNames) {
if (!StringUtil.isBlank(attributeName) && attributeName.contains(":")) {
attributeName = attributeName.substring(attributeName.indexOf(":") + 1);
}
result.add(attributeName);
}
return result;
}
}