/*
* Copyright (c) 2010-2016 Evolveum
*
* 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 com.evolveum.midpoint.prism.schema;
import static com.evolveum.midpoint.prism.PrismConstants.*;
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import javax.xml.XMLConstants;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.namespace.QName;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import com.evolveum.midpoint.prism.*;
import com.sun.xml.xsom.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.sun.xml.xsom.parser.XSOMParser;
import com.sun.xml.xsom.util.DomAnnotationParserFactory;
/**
* Parser for DOM-represented XSD, creates midPoint Schema representation.
*
* It will parse schema in several passes. It has special handling if the schema
* is "resource schema", which will create ResourceObjectDefinition and
* ResourceObjectAttributeDefinition instead of PropertyContainer and Property.
*
* @author lazyman
* @author Radovan Semancik
*/
class DomToSchemaProcessor {
private static final Trace LOGGER = TraceManager.getTrace(DomToSchemaProcessor.class);
private PrismSchemaImpl schema;
private EntityResolver entityResolver;
private PrismContext prismContext;
private XSSchemaSet xsSchemaSet;
private String shortDescription;
private boolean isRuntime = false;
private boolean allowDelayedItemDefinitions;
public EntityResolver getEntityResolver() {
return entityResolver;
}
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
public PrismContext getPrismContext() {
return prismContext;
}
public void setPrismContext(PrismContext prismContext) {
this.prismContext = prismContext;
}
public SchemaRegistry getSchemaRegistry() {
return this.prismContext.getSchemaRegistry();
}
private SchemaDefinitionFactory getDefinitionFactory() {
return ((PrismContextImpl) prismContext).getDefinitionFactory();
}
private String getNamespace() {
return schema.getNamespace();
}
private boolean isMyNamespace(QName qname) {
return getNamespace().equals(qname.getNamespaceURI());
}
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
public boolean isRuntime() {
return isRuntime;
}
public void setRuntime(boolean isRuntime) {
this.isRuntime = isRuntime;
}
public boolean isAllowDelayedItemDefinitions() {
return allowDelayedItemDefinitions;
}
public void setAllowDelayedItemDefinitions(boolean allowDelayedItemDefinitions) {
this.allowDelayedItemDefinitions = allowDelayedItemDefinitions;
}
/**
* Main entry point.
*
*
* @param xsdSchema
* DOM-represented XSD schema
* @return parsed midPoint schema
* @throws SchemaException
* in case of any error
*/
void parseDom(PrismSchemaImpl prismSchema, Element xsdSchema) throws SchemaException {
Validate.notNull(xsdSchema, "XSD schema element must not be null.");
schema = prismSchema;
initSchema(xsdSchema);
xsSchemaSet = parseSchema(xsdSchema);
if (xsSchemaSet == null) {
return;
}
// Create ComplexTypeDefinitions from all top-level complexType
// definition in the XSD
processComplexTypeDefinitions(xsSchemaSet);
// Create SimpleTypeDefinitions from all top-level simpleType
// definition in the XSD
processSimpleTypeDefinitions(xsSchemaSet);
// Create PropertyContainer (and possibly also Property) definition from
// the top-level elements in XSD
// This also creates ResourceObjectDefinition in some cases
createDefinitionsFromElements(xsSchemaSet);
}
private void initSchema(Element xsdSchema) throws SchemaException {
String targetNamespace = DOMUtil.getAttribute(xsdSchema, DOMUtil.XSD_ATTR_TARGET_NAMESPACE);
if (StringUtils.isEmpty(targetNamespace)) {
throw new SchemaException("Schema does not have targetNamespace specification");
}
schema.setNamespace(targetNamespace);
}
private XSSchemaSet parseSchema(Element schema) throws SchemaException {
// Make sure that the schema parser sees all the namespace declarations
DOMUtil.fixNamespaceDeclarations(schema);
XSSchemaSet xss = null;
try {
TransformerFactory transfac = TransformerFactory.newInstance();
Transformer trans = transfac.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
trans.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(schema);
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamResult result = new StreamResult(out);
trans.transform(source, result);
XSOMParser parser = createSchemaParser();
InputSource inSource = new InputSource(new ByteArrayInputStream(out.toByteArray()));
// XXX: hack: it's here to make entity resolver work...
inSource.setSystemId("SystemId");
// XXX: end hack
inSource.setEncoding("utf-8");
parser.parse(inSource);
xss = parser.getResult();
} catch (SAXException e) {
throw new SchemaException("XML error during XSD schema parsing: " + e.getMessage()
+ "(embedded exception " + e.getException() + ") in " + shortDescription, e);
} catch (TransformerException e) {
throw new SchemaException("XML transformer error during XSD schema parsing: " + e.getMessage()
+ "(locator: " + e.getLocator() + ", embedded exception:" + e.getException() + ") in "
+ shortDescription, e);
} catch (RuntimeException e) {
// This sometimes happens, e.g. NPEs in Saxon
if (LOGGER.isErrorEnabled()) {
LOGGER.error("Unexpected error {} during parsing of schema:\n{}", e.getMessage(),
DOMUtil.serializeDOMToString(schema));
}
throw new SchemaException(
"XML error during XSD schema parsing: " + e.getMessage() + " in " + shortDescription, e);
}
return xss;
}
private XSOMParser createSchemaParser() {
XSOMParser parser = new XSOMParser();
if (entityResolver == null) {
entityResolver = prismContext.getEntityResolver();
if (entityResolver == null) {
throw new IllegalStateException(
"Entity resolver is not set (even tried to pull it from prism context)");
}
}
SchemaHandler errorHandler = new SchemaHandler(entityResolver);
parser.setErrorHandler(errorHandler);
parser.setAnnotationParser(new DomAnnotationParserFactory());
parser.setEntityResolver(errorHandler);
return parser;
}
/**
* Create ComplexTypeDefinitions from all top-level complexType definitions
* in the XSD.
*
* These definitions are the reused later to fit inside PropertyContainer
* definitions.
*
* @param set
* XS Schema Set
*/
private void processComplexTypeDefinitions(XSSchemaSet set) throws SchemaException {
Iterator<XSComplexType> iterator = set.iterateComplexTypes();
while (iterator.hasNext()) {
XSComplexType complexType = iterator.next();
if (complexType.getTargetNamespace().equals(schema.getNamespace())) {
processComplexTypeDefinition(complexType);
}
}
}
private ComplexTypeDefinition getOrProcessComplexType(QName typeName) throws SchemaException {
ComplexTypeDefinition complexTypeDefinition = schema.findComplexTypeDefinition(typeName);
if (complexTypeDefinition != null) {
return complexTypeDefinition;
}
// The definition is not yet processed (or does not exist). Let's try to
// process it.
XSComplexType complexType = xsSchemaSet.getComplexType(typeName.getNamespaceURI(),
typeName.getLocalPart());
return processComplexTypeDefinition(complexType);
}
/**
* Creates ComplexTypeDefinition object from a single XSD complexType
* definition.
*
* @param complexType
* XS complex type definition
*/
private ComplexTypeDefinition processComplexTypeDefinition(XSComplexType complexType)
throws SchemaException {
SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
ComplexTypeDefinitionImpl ctd = (ComplexTypeDefinitionImpl) definitionFactory.createComplexTypeDefinition(complexType, prismContext,
complexType.getAnnotation());
ComplexTypeDefinition existingComplexTypeDefinition = schema
.findComplexTypeDefinition(ctd.getTypeName());
if (existingComplexTypeDefinition != null) {
// We already have this in schema. So avoid redundant work and
// infinite loops;
return existingComplexTypeDefinition;
}
// Add to the schema right now to avoid loops - even if it is not
// complete yet
// The definition may reference itself
schema.add(ctd);
XSContentType content = complexType.getContentType();
XSContentType explicitContent = complexType.getExplicitContent();
if (content != null) {
XSParticle particle = content.asParticle();
if (particle != null) {
XSTerm term = particle.getTerm();
if (term.isModelGroup()) {
Boolean inherited = null;
if (explicitContent == null || content == explicitContent) {
inherited = false;
}
addPropertyDefinitionListFromGroup(term.asModelGroup(), ctd, inherited, explicitContent);
}
}
XSAnnotation annotation = complexType.getAnnotation();
Element extensionAnnotationElement = SchemaProcessorUtil.getAnnotationElement(annotation,
A_EXTENSION);
if (extensionAnnotationElement != null) {
QName extensionType = DOMUtil.getQNameAttribute(extensionAnnotationElement,
A_EXTENSION_REF.getLocalPart());
if (extensionType == null) {
throw new SchemaException("The " + A_EXTENSION + "annontation on " + ctd.getTypeName()
+ " complex type does not have " + A_EXTENSION_REF.getLocalPart() + " attribute",
A_EXTENSION_REF);
}
ctd.setExtensionForType(extensionType);
}
}
markRuntime(ctd);
if (complexType.isAbstract()) {
ctd.setAbstract(true);
}
QName superType = determineSupertype(complexType);
if (superType != null) {
ctd.setSuperType(superType);
}
if (isObjectDefinition(complexType)) {
ctd.setObjectMarker(true);
}
if (isPropertyContainer(complexType)) {
ctd.setContainerMarker(true);
}
ctd.setDefaultNamespace(getDefaultNamespace(complexType));
ctd.setIgnoredNamespaces(getIgnoredNamespaces(complexType));
if (isAny(complexType)) {
ctd.setXsdAnyMarker(true);
}
if (isList(complexType)) {
ctd.setListMarker(true);
}
extractDocumentation(ctd, complexType.getAnnotation());
if (getSchemaRegistry() != null) {
Class<?> compileTimeClass = getSchemaRegistry().determineCompileTimeClass(ctd.getTypeName());
ctd.setCompileTimeClass(compileTimeClass);
}
definitionFactory.finishComplexTypeDefinition(ctd, complexType, prismContext,
complexType.getAnnotation());
// Attempt to create object or container definition from this complex
// type
PrismContainerDefinition<?> defFromComplexType = getDefinitionFactory()
.createExtraDefinitionFromComplexType(complexType, ctd, prismContext,
complexType.getAnnotation());
if (defFromComplexType != null) {
markRuntime(defFromComplexType);
schema.add(defFromComplexType);
}
return ctd;
}
private void processSimpleTypeDefinitions(XSSchemaSet set) throws SchemaException {
Iterator<XSSimpleType> iterator = set.iterateSimpleTypes();
while (iterator.hasNext()) {
XSSimpleType simpleType = iterator.next();
if (simpleType.getTargetNamespace().equals(schema.getNamespace())) {
processSimpleTypeDefinition(simpleType);
}
}
}
private SimpleTypeDefinition processSimpleTypeDefinition(XSSimpleType simpleType)
throws SchemaException {
SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
SimpleTypeDefinitionImpl std = (SimpleTypeDefinitionImpl) definitionFactory.createSimpleTypeDefinition(simpleType, prismContext,
simpleType.getAnnotation());
SimpleTypeDefinition existingSimpleTypeDefinition = schema.findSimpleTypeDefinitionByType(std.getTypeName());
if (existingSimpleTypeDefinition != null) {
// We already have this in schema. So avoid redundant work
return existingSimpleTypeDefinition;
}
markRuntime(std);
QName superType = determineSupertype(simpleType);
if (superType != null) {
std.setSuperType(superType);
}
extractDocumentation(std, simpleType.getAnnotation());
if (getSchemaRegistry() != null) {
Class<?> compileTimeClass = getSchemaRegistry().determineCompileTimeClass(std.getTypeName());
std.setCompileTimeClass(compileTimeClass);
}
schema.add(std);
return std;
}
private void extractDocumentation(Definition definition, XSAnnotation annotation) {
if (annotation == null) {
return;
}
Element documentationElement = SchemaProcessorUtil.getAnnotationElement(annotation,
DOMUtil.XSD_DOCUMENTATION_ELEMENT);
if (documentationElement != null) {
// The documentation may be HTML-formatted. Therefore we want to
// keep the formatting and tag names
String documentationText = DOMUtil.serializeElementContent(documentationElement);
((DefinitionImpl) definition).setDocumentation(documentationText);
}
}
/**
* Creates ComplexTypeDefinition object from a XSModelGroup inside XSD
* complexType definition. This is a recursive method. It can create
* "anonymous" internal PropertyContainerDefinitions. The definitions will
* be added to the ComplexTypeDefinition provided as parameter.
*
* @param group
* XSD XSModelGroup
* @param ctd
* ComplexTypeDefinition that will hold the definitions
* @param inherited
* Are these properties inherited? (null means we don't know and
* we'll determine that from explicitContent)
* @param explicitContent
* Explicit (i.e. non-inherited) content of the type being parsed
* - filled-in only for subtypes!
*/
private void addPropertyDefinitionListFromGroup(XSModelGroup group, ComplexTypeDefinition ctd,
Boolean inherited, XSContentType explicitContent) throws SchemaException {
XSParticle[] particles = group.getChildren();
for (XSParticle p : particles) {
boolean particleInherited = inherited != null ? inherited : (p != explicitContent);
XSTerm pterm = p.getTerm();
if (pterm.isModelGroup()) {
addPropertyDefinitionListFromGroup(pterm.asModelGroup(), ctd, particleInherited,
explicitContent);
}
// xs:element inside complex type
if (pterm.isElementDecl()) {
XSAnnotation annotation = selectAnnotationToUse(p.getAnnotation(), pterm.getAnnotation());
XSElementDecl elementDecl = pterm.asElementDecl();
QName elementName = new QName(elementDecl.getTargetNamespace(), elementDecl.getName());
QName typeFromAnnotation = getTypeAnnotation(p.getAnnotation());
XSType xsType = elementDecl.getType();
if (isObjectReference(xsType, annotation)) {
processObjectReferenceDefinition(xsType, elementName, annotation, ctd, p,
particleInherited);
} else if (isObjectDefinition(xsType)) {
// This is object reference. It also has its *Ref equivalent
// which will get parsed.
// therefore it is safe to ignore
} else if (xsType.getName() == null && typeFromAnnotation == null) {
if (isAny(xsType)) {
if (isPropertyContainer(elementDecl)) {
XSAnnotation containerAnnotation = xsType.getAnnotation();
PrismContainerDefinition<?> containerDefinition = createPropertyContainerDefinition(
xsType, p, null, containerAnnotation, false);
((PrismContainerDefinitionImpl) containerDefinition).setInherited(particleInherited);
((ComplexTypeDefinitionImpl) ctd).add(containerDefinition);
} else {
PrismPropertyDefinitionImpl propDef = createPropertyDefinition(xsType, elementName,
DOMUtil.XSD_ANY, ctd, annotation, p);
propDef.setInherited(particleInherited);
((ComplexTypeDefinitionImpl) ctd).add(propDef);
}
}
} else if (isPropertyContainer(elementDecl)) {
// Create an inner PropertyContainer. It is assumed that
// this is a XSD complex type
XSComplexType complexType = (XSComplexType) xsType;
ComplexTypeDefinition complexTypeDefinition = null;
if (typeFromAnnotation != null && complexType != null
&& !typeFromAnnotation.equals(getType(xsType))) {
// There is a type override annotation. The type that
// the schema parser determined is useless
// We need to locate our own complex type definition
if (isMyNamespace(typeFromAnnotation)) {
complexTypeDefinition = getOrProcessComplexType(typeFromAnnotation);
} else {
complexTypeDefinition = getPrismContext().getSchemaRegistry()
.findComplexTypeDefinition(typeFromAnnotation);
}
if (complexTypeDefinition == null) {
throw new SchemaException(
"Cannot find definition of complex type " + typeFromAnnotation
+ " as specified in type override annotation at " + elementName);
}
} else {
complexTypeDefinition = processComplexTypeDefinition(complexType);
}
XSAnnotation containerAnnotation = complexType.getAnnotation();
PrismContainerDefinition<?> containerDefinition = createPropertyContainerDefinition(
xsType, p, complexTypeDefinition, containerAnnotation, false);
if (isAny(xsType)) {
((PrismContainerDefinitionImpl) containerDefinition).setRuntimeSchema(true);
((PrismContainerDefinitionImpl) containerDefinition).setDynamic(true);
}
((PrismContainerDefinitionImpl) containerDefinition).setInherited(particleInherited);
((ComplexTypeDefinitionImpl) ctd).add(containerDefinition);
} else {
// Create a property definition (even if this is a XSD
// complex type)
QName typeName = new QName(xsType.getTargetNamespace(), xsType.getName());
PrismPropertyDefinitionImpl propDef = createPropertyDefinition(xsType, elementName, typeName,
ctd, annotation, p);
propDef.setInherited(particleInherited);
((ComplexTypeDefinitionImpl) ctd).add(propDef);
}
}
}
}
private PrismReferenceDefinitionImpl processObjectReferenceDefinition(XSType xsType, QName elementName,
XSAnnotation annotation, ComplexTypeDefinition containingCtd, XSParticle elementParticle,
boolean inherited) throws SchemaException {
QName typeName = new QName(xsType.getTargetNamespace(), xsType.getName());
QName primaryElementName = elementName;
Element objRefAnnotationElement = SchemaProcessorUtil.getAnnotationElement(annotation,
A_OBJECT_REFERENCE);
boolean hasExplicitPrimaryElementName = (objRefAnnotationElement != null
&& !StringUtils.isEmpty(objRefAnnotationElement.getTextContent()));
if (hasExplicitPrimaryElementName) {
primaryElementName = DOMUtil.getQNameValue(objRefAnnotationElement);
}
PrismReferenceDefinitionImpl definition = null;
if (containingCtd != null) {
definition = (PrismReferenceDefinitionImpl) containingCtd.findItemDefinition(primaryElementName, PrismReferenceDefinition.class);
}
if (definition == null) {
SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
definition = (PrismReferenceDefinitionImpl) definitionFactory.createReferenceDefinition(primaryElementName, typeName,
containingCtd, prismContext, annotation, elementParticle);
definition.setInherited(inherited);
if (containingCtd != null) {
((ComplexTypeDefinitionImpl) containingCtd).add(definition);
}
}
if (hasExplicitPrimaryElementName) {
// The elements that have explicit type name determine the target
// type name (if not yet set)
if (definition.getTargetTypeName() == null) {
definition.setTargetTypeName(typeName);
}
if (definition.getCompositeObjectElementName() == null) {
definition.setCompositeObjectElementName(elementName);
}
} else {
// The elements that use default element names override type
// definition
// as there can be only one such definition, therefore the behavior
// is deterministic
definition.setTypeName(typeName);
}
Element targetTypeAnnotationElement = SchemaProcessorUtil.getAnnotationElement(annotation,
A_OBJECT_REFERENCE_TARGET_TYPE);
if (targetTypeAnnotationElement != null
&& !StringUtils.isEmpty(targetTypeAnnotationElement.getTextContent())) {
// Explicit definition of target type overrides previous logic
QName targetType = DOMUtil.getQNameValue(targetTypeAnnotationElement);
definition.setTargetTypeName(targetType);
}
setMultiplicity(definition, elementParticle, annotation, false);
Boolean composite = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_COMPOSITE);
if (composite != null) {
definition.setComposite(composite);
}
parseItemDefinitionAnnotations(definition, annotation);
// extractDocumentation(definition, annotation);
return definition;
}
private void setMultiplicity(ItemDefinition itemDef, XSParticle particle, XSAnnotation annotation,
boolean topLevel) {
if (topLevel || particle == null) {
((ItemDefinitionImpl) itemDef).setMinOccurs(0);
Element maxOccursAnnotation = SchemaProcessorUtil.getAnnotationElement(annotation, A_MAX_OCCURS);
if (maxOccursAnnotation != null) {
String maxOccursString = maxOccursAnnotation.getTextContent();
int maxOccurs = XsdTypeMapper.multiplicityToInteger(maxOccursString);
((ItemDefinitionImpl) itemDef).setMaxOccurs(maxOccurs);
} else {
((ItemDefinitionImpl) itemDef).setMaxOccurs(-1);
}
} else {
// itemDef.setMinOccurs(particle.getMinOccurs());
// itemDef.setMaxOccurs(particle.getMaxOccurs());
((ItemDefinitionImpl) itemDef).setMinOccurs(particle.getMinOccurs().intValue());
((ItemDefinitionImpl) itemDef).setMaxOccurs(particle.getMaxOccurs().intValue());
}
}
/**
* Create PropertyContainer (and possibly also Property) definition from the
* top-level elements in XSD. Each top-level element will be interpreted as
* a potential PropertyContainer. The element name will be set as name of
* the PropertyContainer, element type will become type (indirectly through
* ComplexTypeDefinition).
*
* No need to recurse here. All the work was already done while creating
* ComplexTypeDefinitions.
*
* @param set
* XS Schema Set
* @throws SchemaException
*/
private void createDefinitionsFromElements(XSSchemaSet set) throws SchemaException {
Iterator<XSElementDecl> iterator = set.iterateElementDecls();
while (iterator.hasNext()) {
XSElementDecl xsElementDecl = iterator.next();
if (isDeprecated(xsElementDecl)) {
// Safe to ignore. We want it in the XSD schema only. The real
// definition will be
// parsed from the non-deprecated variant
}
if (xsElementDecl.getTargetNamespace().equals(schema.getNamespace())) {
QName elementName = new QName(xsElementDecl.getTargetNamespace(), xsElementDecl.getName());
XSType xsType = xsElementDecl.getType();
if (xsType == null) {
throw new SchemaException("Found element " + elementName + " without type definition");
}
QName typeQName = determineType(xsElementDecl);
if (typeQName == null) {
// No type defined, safe to skip
continue;
// throw new SchemaException("Found element "+elementName+"
// with incomplete type name:
// {"+xsType.getTargetNamespace()+"}"+xsType.getName());
}
XSAnnotation annotation = xsElementDecl.getAnnotation();
ItemDefinitionImpl definition;
if (isPropertyContainer(xsElementDecl) || isObjectDefinition(xsType)) {
ComplexTypeDefinition complexTypeDefinition = findComplexTypeDefinition(typeQName);
if (complexTypeDefinition == null) {
if (!allowDelayedItemDefinitions) {
throw new SchemaException("Couldn't parse prism container " + elementName + " of type " + typeQName
+ " because complex type definition couldn't be found and delayed item definitions are not allowed.");
}
definition = null;
schema.addDelayedItemDefinition(() -> {
ComplexTypeDefinition ctd = findComplexTypeDefinition(typeQName);
// here we take the risk that ctd is null
return createPropertyContainerDefinition(xsType, xsElementDecl, ctd, annotation, null, true);
});
} else {
definition = createPropertyContainerDefinition(
xsType, xsElementDecl, complexTypeDefinition, annotation, null, true);
}
} else if (isObjectReference(xsElementDecl, xsType)) {
definition = processObjectReferenceDefinition(xsType, elementName,
annotation, null, null, false);
} else {
// Create a top-level property definition (even if this is a XSD complex type)
definition = createPropertyDefinition(xsType, elementName, typeQName,
null, annotation, null);
}
if (definition != null) {
definition.setSubstitutionHead(getSubstitutionHead(xsElementDecl));
schema.getDefinitions().add(definition);
}
} else { //if (xsElementDecl.getTargetNamespace().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
// This is OK to ignore. These are imported elements from other
// schemas
// } else {
// throw new SchemaException("Found element
// "+xsElementDecl.getName()+" with wrong namespace
// "+xsElementDecl.getTargetNamespace()+" while expecting
// "+schema.getNamespace());
}
}
}
// We first try to find the definition locally, because in schema registry we don't have the current schema yet.
private ComplexTypeDefinition findComplexTypeDefinition(QName typeQName) {
ComplexTypeDefinition complexTypeDefinition = schema.findComplexTypeDefinitionByType(typeQName);
if (complexTypeDefinition == null) {
complexTypeDefinition = getSchemaRegistry().findComplexTypeDefinitionByType(typeQName);
}
return complexTypeDefinition;
}
private QName getSubstitutionHead(XSElementDecl element) {
XSElementDecl head = element.getSubstAffiliation();
if (head == null) {
return null;
} else {
return new QName(head.getTargetNamespace(), head.getName());
}
}
private QName determineType(XSElementDecl xsElementDecl) {
// Check for a:type annotation. If present, this overrides the type
QName type = getTypeAnnotation(xsElementDecl);
if (type != null) {
return type;
}
XSType xsType = xsElementDecl.getType();
if (xsType == null) {
return null;
}
return getType(xsType);
}
private QName getType(XSType xsType) {
if (xsType.getName() == null) {
return null;
}
return new QName(xsType.getTargetNamespace(), xsType.getName());
}
private QName getTypeAnnotation(XSElementDecl xsElementDecl) {
XSAnnotation annotation = xsElementDecl.getAnnotation();
return getTypeAnnotation(annotation);
}
private QName getTypeAnnotation(XSAnnotation annotation) {
return SchemaProcessorUtil.getAnnotationQName(annotation, A_TYPE);
}
/**
* Determine whether the definition contains xsd:any (directly or indirectly)
*/
private boolean isAny(XSType xsType) {
if (xsType instanceof XSComplexType) {
XSComplexType complexType = (XSComplexType) xsType;
XSContentType contentType = complexType.getContentType();
if (contentType != null) {
XSParticle particle = contentType.asParticle();
if (particle != null) {
XSTerm term = particle.getTerm();
if (term != null) {
return isAny(term);
}
}
}
}
return false;
}
/**
* Determine whether the definition contains "list" attribute (directly or indirectly)
*/
private boolean isList(XSComplexType complexType) {
Collection<? extends XSAttributeUse> attributeUses = complexType.getAttributeUses();
return attributeUses != null && attributeUses.stream()
.anyMatch(au -> au.getDecl() != null && DOMUtil.IS_LIST_ATTRIBUTE_NAME.equals(au.getDecl().getName()));
}
// not much tested
private void applyToDeclarations(XSComponent component, Consumer<XSDeclaration> consumer) {
if (component == null) {
return;
}
if (component instanceof XSDeclaration) {
consumer.accept((XSDeclaration) component);
}
// recursion (if needed)
if (component instanceof XSParticle) {
applyToDeclarations(((XSParticle) component).getTerm(), consumer);
} else if (component instanceof XSModelGroup) {
for (XSParticle particle : ((XSModelGroup) component).getChildren()) {
applyToDeclarations(particle, consumer);
}
} else if (component instanceof XSModelGroupDecl) {
applyToDeclarations(((XSModelGroupDecl) component).getModelGroup(), consumer);
}
}
private QName determineSupertype(XSType type) {
XSType baseType = type.getBaseType();
if (baseType == null) {
return null;
}
if (baseType.getName().equals("anyType")) {
return null;
}
return new QName(baseType.getTargetNamespace(), baseType.getName());
}
/**
* Determine whether the definition contains xsd:any (directly or indirectly)
*/
private boolean isAny(XSTerm term) {
if (term.isWildcard()) {
return true;
}
if (term.isModelGroup()) {
XSParticle[] children = term.asModelGroup().getChildren();
if (children != null) {
for (XSParticle childParticle : children) {
XSTerm childTerm = childParticle.getTerm();
if (childTerm != null) {
if (isAny(childTerm)) {
return true;
}
}
}
}
}
return false;
}
private boolean isPropertyContainer(XSElementDecl xsElementDecl) {
Element annoElement = SchemaProcessorUtil.getAnnotationElement(xsElementDecl.getAnnotation(),
A_PROPERTY_CONTAINER);
if (annoElement != null) {
return true;
}
return isPropertyContainer(xsElementDecl.getType());
}
/**
* Returns true if provides XSD type is a property container. It looks for
* annotations.
*/
private boolean isPropertyContainer(XSType xsType) {
Element annoElement = SchemaProcessorUtil.getAnnotationElement(xsType.getAnnotation(),
A_PROPERTY_CONTAINER);
if (annoElement != null) {
return true;
}
if (xsType.getBaseType() != null && !xsType.getBaseType().equals(xsType)) {
return isPropertyContainer(xsType.getBaseType());
}
return false;
}
private String getDefaultNamespace(XSType xsType) {
Element annoElement = SchemaProcessorUtil.getAnnotationElement(xsType.getAnnotation(), A_DEFAULT_NAMESPACE);
if (annoElement != null) {
return annoElement.getTextContent();
}
if (xsType.getBaseType() != null && !xsType.getBaseType().equals(xsType)) {
return getDefaultNamespace(xsType.getBaseType());
}
return null;
}
@NotNull
private List<String> getIgnoredNamespaces(XSType xsType) {
List<String> rv = new ArrayList<>();
List<Element> annoElements = SchemaProcessorUtil.getAnnotationElements(xsType.getAnnotation(), A_IGNORED_NAMESPACE);
for (Element annoElement : annoElements) {
rv.add(annoElement.getTextContent());
}
if (xsType.getBaseType() != null && !xsType.getBaseType().equals(xsType)) {
rv.addAll(getIgnoredNamespaces(xsType.getBaseType()));
}
return rv;
}
private boolean isObjectReference(XSElementDecl xsElementDecl, XSType xsType) {
XSAnnotation annotation = xsType.getAnnotation();
return isObjectReference(xsType, annotation);
}
private boolean isObjectReference(XSType xsType, XSAnnotation annotation) {
if (isObjectReference(annotation)) {
return true;
}
return isObjectReference(xsType);
}
private boolean isObjectReference(XSAnnotation annotation) {
Element objRefAnnotationElement = SchemaProcessorUtil.getAnnotationElement(annotation,
A_OBJECT_REFERENCE);
return (objRefAnnotationElement != null);
}
private boolean isObjectReference(XSType xsType) {
return SchemaProcessorUtil.hasAnnotation(xsType, A_OBJECT_REFERENCE);
}
/**
* Returns true if provides XSD type is an object definition. It looks for a
* ObjectType supertype.
*/
private boolean isObjectDefinition(XSType xsType) {
return SchemaProcessorUtil.hasAnnotation(xsType, A_OBJECT);
}
/**
* Creates appropriate instance of PropertyContainerDefinition. It may be
* PropertyContainerDefinition itself or one of its subclasses
* (ResourceObjectDefinition). This method also takes care of parsing all
* the annotations and similar fancy stuff.
*
* We need to pass createResourceObject flag explicitly here. Because even
* if we are in resource schema, we want PropertyContainers inside
* ResourceObjects, not ResourceObjects inside ResouceObjects.
*/
private PrismContainerDefinition<?> createPropertyContainerDefinition(XSType xsType,
XSParticle elementParticle, ComplexTypeDefinition complexTypeDefinition, XSAnnotation annotation,
boolean topLevel) throws SchemaException {
XSTerm elementTerm = elementParticle.getTerm();
XSElementDecl elementDecl = elementTerm.asElementDecl();
PrismContainerDefinition<?> pcd = createPropertyContainerDefinition(xsType, elementDecl,
complexTypeDefinition, annotation, elementParticle, topLevel);
return pcd;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private PrismContainerDefinitionImpl<?> createPropertyContainerDefinition(XSType xsType,
XSElementDecl elementDecl, ComplexTypeDefinition complexTypeDefinition,
XSAnnotation annotation, XSParticle elementParticle, boolean topLevel)
throws SchemaException {
QName elementName = new QName(elementDecl.getTargetNamespace(), elementDecl.getName());
PrismContainerDefinitionImpl<?> pcd;
SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
Class compileTimeClass = null;
if (getSchemaRegistry() != null && complexTypeDefinition != null) {
compileTimeClass = getSchemaRegistry().determineCompileTimeClass(complexTypeDefinition.getTypeName());
}
if (isObjectDefinition(xsType)) {
pcd = definitionFactory.createObjectDefinition(elementName, complexTypeDefinition, prismContext, compileTimeClass);
// Multiplicity is fixed to a single-value here
pcd.setMinOccurs(1);
pcd.setMaxOccurs(1);
} else {
pcd = definitionFactory.createContainerDefinition(elementName, complexTypeDefinition, prismContext, compileTimeClass);
setMultiplicity(pcd, elementParticle, elementDecl.getAnnotation(), topLevel);
}
markRuntime(pcd);
parseItemDefinitionAnnotations(pcd, annotation);
parseItemDefinitionAnnotations(pcd, elementDecl.getAnnotation());
if (elementParticle != null) {
parseItemDefinitionAnnotations(pcd, elementParticle.getAnnotation());
}
return pcd;
}
/**
* Creates appropriate instance of PropertyDefinition. It creates either
* PropertyDefinition itself or one of its subclasses
* (ResourceObjectAttributeDefinition). The behavior depends of the "mode"
* of the schema. This method is also processing annotations and other fancy
* property-relates stuff.
*/
private <T> PrismPropertyDefinitionImpl<T> createPropertyDefinition(XSType xsType, QName elementName,
QName typeName, ComplexTypeDefinition ctd, XSAnnotation annotation, XSParticle elementParticle)
throws SchemaException {
PrismPropertyDefinitionImpl<T> propDef;
SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
Collection<? extends DisplayableValue<T>> allowedValues = parseEnumAllowedValues(typeName, ctd,
xsType);
Object defaultValue = parseDefaultValue(elementParticle, typeName);
propDef = (PrismPropertyDefinitionImpl) definitionFactory.createPropertyDefinition(elementName, typeName, ctd, prismContext,
annotation, elementParticle, allowedValues, null);
setMultiplicity(propDef, elementParticle, annotation, ctd == null);
// Process generic annotations
parseItemDefinitionAnnotations(propDef, annotation);
List<Element> accessElements = SchemaProcessorUtil.getAnnotationElements(annotation, A_ACCESS);
if (accessElements.isEmpty()) {
// Default access is read-write-create
propDef.setCanAdd(true);
propDef.setCanModify(true);
propDef.setCanRead(true);
} else {
propDef.setCanAdd(false);
propDef.setCanModify(false);
propDef.setCanRead(false);
for (Element e : accessElements) {
String access = e.getTextContent();
if (access.equals(A_ACCESS_CREATE)) {
propDef.setCanAdd(true);
}
if (access.equals(A_ACCESS_UPDATE)) {
propDef.setCanModify(true);
}
if (access.equals(A_ACCESS_READ)) {
propDef.setCanRead(true);
}
}
}
markRuntime(propDef);
Element indexableElement = SchemaProcessorUtil.getAnnotationElement(annotation, A_INDEXED);
if (indexableElement != null) {
Boolean indexable = XmlTypeConverter.toJavaValue(indexableElement, Boolean.class);
propDef.setIndexed(indexable);
}
Element matchingRuleElement = SchemaProcessorUtil.getAnnotationElement(annotation, A_MATCHING_RULE);
if (matchingRuleElement != null) {
QName matchingRule = XmlTypeConverter.toJavaValue(matchingRuleElement, QName.class);
propDef.setMatchingRuleQName(matchingRule);
}
Element valueEnumerationRefElement = SchemaProcessorUtil.getAnnotationElement(annotation, A_VALUE_ENUMERATION_REF);
if (valueEnumerationRefElement != null) {
String oid = valueEnumerationRefElement.getAttribute(PrismConstants.ATTRIBUTE_OID_LOCAL_NAME);
if (oid != null) {
QName targetType = DOMUtil.getQNameAttribute(valueEnumerationRefElement, PrismConstants.ATTRIBUTE_REF_TYPE_LOCAL_NAME);
PrismReferenceValue valueEnumerationRef = new PrismReferenceValue(oid, targetType);
propDef.setValueEnumerationRef(valueEnumerationRef);
}
}
return propDef;
}
private Object parseDefaultValue(XSParticle elementParticle, QName typeName) {
if (elementParticle == null) {
return null;
}
XSTerm term = elementParticle.getTerm();
if (term == null) {
return null;
}
XSElementDecl elementDecl = term.asElementDecl();
if (elementDecl == null) {
return null;
}
if (elementDecl.getDefaultValue() != null) {
if (XmlTypeConverter.canConvert(typeName)) {
return XmlTypeConverter.toJavaValue(elementDecl.getDefaultValue().value, typeName);
}
return elementDecl.getDefaultValue().value;
}
return null;
}
private <T> Collection<? extends DisplayableValue<T>> parseEnumAllowedValues(QName typeName,
ComplexTypeDefinition ctd, XSType xsType) {
if (xsType.isSimpleType()) {
if (xsType.asSimpleType().isRestriction()) {
XSRestrictionSimpleType restriction = xsType.asSimpleType().asRestriction();
List<XSFacet> enumerations = restriction.getDeclaredFacets(XSFacet.FACET_ENUMERATION);
List<DisplayableValueImpl<T>> enumValues = new ArrayList<DisplayableValueImpl<T>>(
enumerations.size());
for (XSFacet facet : enumerations) {
String value = facet.getValue().value;
Element descriptionE = SchemaProcessorUtil.getAnnotationElement(facet.getAnnotation(),
SCHEMA_DOCUMENTATION);
Element appInfo = SchemaProcessorUtil.getAnnotationElement(facet.getAnnotation(),
SCHEMA_APP_INFO);
Element valueE = null;
if (appInfo != null) {
NodeList list = appInfo.getElementsByTagNameNS(
PrismConstants.A_LABEL.getNamespaceURI(),
PrismConstants.A_LABEL.getLocalPart());
if (list.getLength() != 0) {
valueE = (Element) list.item(0);
}
}
String label = null;
if (valueE != null) {
label = valueE.getTextContent();
} else {
label = value;
}
DisplayableValueImpl<T> edv = null;
Class compileTimeClass = prismContext.getSchemaRegistry().getCompileTimeClass(typeName);
if (ctd != null && !ctd.isRuntimeSchema() && compileTimeClass != null) {
String fieldName = null;
for (Field field : compileTimeClass.getDeclaredFields()) {
XmlEnumValue xmlEnumValue = field.getAnnotation(XmlEnumValue.class);
if (xmlEnumValue != null && xmlEnumValue.value() != null
&& xmlEnumValue.value().equals(value)) {
fieldName = field.getName();
}
}
if (fieldName != null) {
T enumValue = (T) Enum.valueOf((Class<Enum>) compileTimeClass, fieldName);
edv = new DisplayableValueImpl(enumValue, label,
descriptionE != null ? descriptionE.getTextContent() : null);
} else {
edv = new DisplayableValueImpl(value, label,
descriptionE != null ? descriptionE.getTextContent() : null);
}
} else {
edv = new DisplayableValueImpl(value, label,
descriptionE != null ? descriptionE.getTextContent() : null);
}
enumValues.add(edv);
}
if (enumValues != null && !enumValues.isEmpty()) {
return enumValues;
}
}
}
return null;
}
private void parseItemDefinitionAnnotations(ItemDefinitionImpl itemDef, XSAnnotation annotation)
throws SchemaException {
if (annotation == null || annotation.getAnnotation() == null) {
return;
}
// ignore
Boolean ignore = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_IGNORE);
if (ignore != null) {
itemDef.setIgnored(ignore);
}
// deprecated
Boolean deprecated = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_DEPRECATED);
if (deprecated != null) {
itemDef.setDeprecated(deprecated);
}
// operational
Boolean operational = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_OPERATIONAL);
if (operational != null) {
itemDef.setOperational(operational);
}
// displayName
Element attributeDisplayName = SchemaProcessorUtil.getAnnotationElement(annotation, A_DISPLAY_NAME);
if (attributeDisplayName != null) {
itemDef.setDisplayName(attributeDisplayName.getTextContent());
}
// displayOrder
Element displayOrderElement = SchemaProcessorUtil.getAnnotationElement(annotation, A_DISPLAY_ORDER);
if (displayOrderElement != null) {
Integer displayOrder = DOMUtil.getIntegerValue(displayOrderElement);
itemDef.setDisplayOrder(displayOrder);
}
// help
Element help = SchemaProcessorUtil.getAnnotationElement(annotation, A_HELP);
if (help != null) {
itemDef.setHelp(help.getTextContent());
}
// emphasized
Boolean emphasized = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_EMPHASIZED);
if (emphasized != null) {
itemDef.setEmphasized(emphasized);
}
// documentation
extractDocumentation(itemDef, annotation);
Boolean heterogeneousListItem = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_HETEROGENEOUS_LIST_ITEM);
if (heterogeneousListItem != null) {
itemDef.setHeterogeneousListItem(heterogeneousListItem);
}
}
private boolean isDeprecated(XSElementDecl xsElementDecl) throws SchemaException {
XSAnnotation annotation = xsElementDecl.getAnnotation();
Boolean deprecated = SchemaProcessorUtil.getAnnotationBooleanMarker(annotation, A_DEPRECATED);
return (deprecated != null && deprecated);
}
private boolean containsAccessFlag(String flag, List<Element> accessList) {
for (Element element : accessList) {
if (flag.equals(element.getTextContent())) {
return true;
}
}
return false;
}
private XSAnnotation selectAnnotationToUse(XSAnnotation particleAnnotation, XSAnnotation termAnnotation) {
boolean useParticleAnnotation = false;
if (particleAnnotation != null && particleAnnotation.getAnnotation() != null) {
if (testAnnotationAppinfo(particleAnnotation)) {
useParticleAnnotation = true;
}
}
boolean useTermAnnotation = false;
if (termAnnotation != null && termAnnotation.getAnnotation() != null) {
if (testAnnotationAppinfo(termAnnotation)) {
useTermAnnotation = true;
}
}
if (useParticleAnnotation) {
return particleAnnotation;
}
if (useTermAnnotation) {
return termAnnotation;
}
return null;
}
private boolean testAnnotationAppinfo(XSAnnotation annotation) {
Element appinfo = SchemaProcessorUtil.getAnnotationElement(annotation,
new QName(W3C_XML_SCHEMA_NS_URI, "appinfo"));
if (appinfo != null) {
return true;
}
return false;
}
private void markRuntime(Definition def) {
if (isRuntime) {
((DefinitionImpl) def).setRuntimeSchema(true);
}
}
}