/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.event.xml; import com.espertech.esper.client.ConfigurationException; import com.espertech.esper.epl.core.EngineImportService; import com.espertech.esper.util.FileUtil; import com.espertech.esper.util.JavaClassHelper; import com.espertech.esper.util.ResourceLoader; import com.sun.org.apache.xerces.internal.dom.DOMXSImplementationSourceImpl; import com.sun.org.apache.xerces.internal.impl.dv.XSSimpleType; import com.sun.org.apache.xerces.internal.impl.dv.xs.XSSimpleTypeDecl; import com.sun.org.apache.xerces.internal.xs.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.DOMError; import org.w3c.dom.DOMErrorHandler; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.LSInput; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; /** * Helper class for mapping a XSD schema model to an internal representation. */ public class XSDSchemaMapper { private static final Logger log = LoggerFactory.getLogger(XSDSchemaMapper.class); private static final int JAVA5_COMPLEX_TYPE = 13; private static final int JAVA5_SIMPLE_TYPE = 14; private static final int JAVA6_COMPLEX_TYPE = 15; private static final int JAVA6_SIMPLE_TYPE = 16; /** * Loading and mapping of the schema to the internal representation. * * @param schemaResource schema to load and map. * @param schemaText schema * @param engineImportService engine imports * @return model */ public static SchemaModel loadAndMap(String schemaResource, String schemaText, EngineImportService engineImportService) { // Load schema XSModel model; try { model = readSchemaInternal(schemaResource, schemaText, engineImportService); } catch (ConfigurationException ex) { throw ex; } catch (Exception ex) { throw new ConfigurationException("Failed to read schema '" + schemaResource + "' : " + ex.getMessage(), ex); } // Map schema to internal representation return map(model); } private static XSModel readSchemaInternal(String schemaResource, String schemaText, EngineImportService engineImportService) throws IllegalAccessException, InstantiationException, ClassNotFoundException, ConfigurationException, URISyntaxException { LSInputImpl input = null; String baseURI = null; URL url = null; if (schemaResource != null) { url = ResourceLoader.resolveClassPathOrURLResource("schema", schemaResource, engineImportService.getClassLoader()); baseURI = url.toURI().toString(); } else { input = new LSInputImpl(schemaText); } // Uses Xerxes internal classes DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); registry.addSource(new DOMXSImplementationSourceImpl()); Object xsImplementation = registry.getDOMImplementation("XS-Loader"); if (xsImplementation == null) { throw new ConfigurationException("Failed to retrieve XS-Loader implementation from registry obtained via DOMImplementationRegistry.newInstance, please check that registry.getDOMImplementation(\"XS-Loader\") returns an instance"); } if (!JavaClassHelper.isImplementsInterface(xsImplementation.getClass(), XSImplementation.class)) { String message = "The XS-Loader instance returned by the DOM registry class '" + xsImplementation.getClass().getName() + "' does not implement the interface '" + XSImplementation.class.getName() + "'; If you have a another Xerces distribution in your classpath please ensure the classpath order loads the JRE Xerces distribution or set the DOMImplementationRegistry.PROPERTY system property"; throw new ConfigurationException(message); } XSImplementation impl = (XSImplementation) xsImplementation; XSLoader schemaLoader = impl.createXSLoader(null); schemaLoader.getConfig().setParameter("error-handler", new XSDSchemaMapperErrorHandler(schemaResource)); XSModel xsModel; if (input != null) { xsModel = schemaLoader.load(input); } else { xsModel = schemaLoader.loadURI(baseURI); // If having trouble loading from the uri, try to attempt from file system. if (xsModel == null) { String schema; try { schema = FileUtil.readTextFile(new File(url.toURI())); } catch (IOException e) { throw new ConfigurationException("Failed to read file '" + url.toURI() + "':" + e.getMessage(), e); } log.debug("Found and obtained schema: " + schema); xsModel = schemaLoader.load(new LSInputImpl(schema)); log.debug("Model for schema: " + xsModel); } } if (xsModel == null) { throw new ConfigurationException("Failed to read schema via URL '" + schemaResource + '\''); } return xsModel; } private static SchemaModel map(XSModel xsModel) { // get namespaces StringList namespaces = xsModel.getNamespaces(); List<String> namesspaceList = new ArrayList<String>(); for (int i = 0; i < namespaces.getLength(); i++) { namesspaceList.add(namespaces.item(i)); } // get top-level complex elements XSNamedMap elements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION); List<SchemaElementComplex> components = new ArrayList<SchemaElementComplex>(); for (int i = 0; i < elements.getLength(); i++) { XSObject object = elements.item(i); if (!(object instanceof XSElementDeclaration)) { continue; } XSElementDeclaration decl = (XSElementDeclaration) elements.item(i); if (!isComplexTypeCategory(decl.getTypeDefinition().getTypeCategory())) { continue; } XSComplexTypeDefinition complexActualElement = (XSComplexTypeDefinition) decl.getTypeDefinition(); String name = object.getName(); String namespace = object.getNamespace(); ElementPathNode rootNode = new ElementPathNode(null, name); if (log.isDebugEnabled()) { log.debug("Processing component " + namespace + " " + name); } SchemaElementComplex complexElement = process(name, namespace, complexActualElement, false, rootNode); if (log.isDebugEnabled()) { log.debug("Adding component " + namespace + " " + name); } components.add(complexElement); } return new SchemaModel(components, namesspaceList); } private static boolean isComplexTypeCategory(short typeCategory) { return (typeCategory == XSTypeDefinition.COMPLEX_TYPE) || (typeCategory == JAVA5_COMPLEX_TYPE) || (typeCategory == JAVA6_COMPLEX_TYPE); } private static boolean isSimpleTypeCategory(short typeCategory) { return (typeCategory == XSTypeDefinition.SIMPLE_TYPE) || (typeCategory == JAVA5_SIMPLE_TYPE) || (typeCategory == JAVA6_SIMPLE_TYPE); } private static SchemaElementComplex process(String complexElementName, String complexElementNamespace, XSComplexTypeDefinition complexActualElement, boolean isArray, ElementPathNode node) { if (log.isDebugEnabled()) { log.debug("Processing complex " + complexElementNamespace + " " + complexElementName + " stack " + node.toString()); } List<SchemaItemAttribute> attributes = new ArrayList<SchemaItemAttribute>(); List<SchemaElementSimple> simpleElements = new ArrayList<SchemaElementSimple>(); List<SchemaElementComplex> complexElements = new ArrayList<SchemaElementComplex>(); Short optionalSimplyType = null; String optionalSimplyTypeName = null; if (complexActualElement.getSimpleType() != null) { XSSimpleTypeDecl simpleType = (XSSimpleTypeDecl) complexActualElement.getSimpleType(); optionalSimplyType = simpleType.getPrimitiveKind(); optionalSimplyTypeName = simpleType.getName(); } SchemaElementComplex complexElement = new SchemaElementComplex(complexElementName, complexElementNamespace, attributes, complexElements, simpleElements, isArray, optionalSimplyType, optionalSimplyTypeName); // add attributes XSObjectList attrs = complexActualElement.getAttributeUses(); for (int i = 0; i < attrs.getLength(); i++) { XSAttributeUse attr = (XSAttributeUse) attrs.item(i); String namespace = attr.getAttrDeclaration().getNamespace(); String name = attr.getAttrDeclaration().getName(); XSSimpleTypeDecl simpleType = (XSSimpleTypeDecl) attr.getAttrDeclaration().getTypeDefinition(); attributes.add(new SchemaItemAttribute(namespace, name, simpleType.getPrimitiveKind(), simpleType.getName())); } if ((complexActualElement.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_ELEMENT) || (complexActualElement.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED)) { // has children XSParticle particle = complexActualElement.getParticle(); if (particle.getTerm() instanceof XSModelGroup) { return processModelGroup(particle, simpleElements, complexElements, node, complexActualElement, complexElement); } } return complexElement; } private static SchemaElementComplex processModelGroup(XSObject xsObject, List<SchemaElementSimple> simpleElements, List<SchemaElementComplex> complexElements, ElementPathNode node, XSComplexTypeDefinition complexActualElement, SchemaElementComplex complexElement) { XSTerm term = null; if (xsObject instanceof XSParticle) { term = ((XSParticle) xsObject).getTerm(); } else { term = (XSTerm) xsObject; } if (term instanceof XSModelGroup) { XSModelGroup group = (XSModelGroup) term; XSObjectList particles = group.getParticles(); for (int i = 0; i < particles.getLength(); i++) { XSParticle childParticle = (XSParticle) particles.item(i); if (childParticle.getTerm() instanceof XSElementDeclaration) { XSElementDeclaration decl = (XSElementDeclaration) childParticle.getTerm(); boolean isArrayFlag = isArray(childParticle); if (isSimpleTypeCategory(decl.getTypeDefinition().getTypeCategory())) { XSSimpleTypeDecl simpleType = (XSSimpleTypeDecl) decl.getTypeDefinition(); Integer fractionDigits = getFractionRestriction(simpleType); simpleElements.add(new SchemaElementSimple(decl.getName(), decl.getNamespace(), simpleType.getPrimitiveKind(), simpleType.getName(), isArrayFlag, fractionDigits)); } if (isComplexTypeCategory(decl.getTypeDefinition().getTypeCategory())) { String name = decl.getName(); String namespace = decl.getNamespace(); ElementPathNode newChild = node.addChild(name); if (newChild.doesNameAlreadyExistInHierarchy()) { continue; } complexActualElement = (XSComplexTypeDefinition) decl.getTypeDefinition(); SchemaElementComplex innerComplex = process(name, namespace, complexActualElement, isArrayFlag, newChild); if (log.isDebugEnabled()) { log.debug("Adding complex " + complexElement); } complexElements.add(innerComplex); } } processModelGroup(childParticle.getTerm(), simpleElements, complexElements, node, complexActualElement, complexElement); } } return complexElement; } private static Integer getFractionRestriction(XSSimpleTypeDecl simpleType) { if ((simpleType.getDefinedFacets() & XSSimpleType.FACET_FRACTIONDIGITS) != 0) { XSObjectList facets = simpleType.getFacets(); Integer digits = null; for (int f = 0; f < facets.getLength(); f++) { XSObject item = facets.item(f); if (item instanceof XSFacet) { XSFacet facet = (XSFacet) item; if (facet.getFacetKind() == XSSimpleType.FACET_FRACTIONDIGITS) { try { digits = Integer.parseInt(facet.getLexicalFacetValue()); } catch (RuntimeException ex) { log.warn("Error parsing fraction facet value '" + facet.getLexicalFacetValue() + "' : " + ex.getMessage(), ex); } } } } return digits; } return null; } private static boolean isArray(XSParticle particle) { return particle.getMaxOccursUnbounded() || (particle.getMaxOccurs() > 1); } public static class LSInputImpl implements LSInput { private String stringData; public LSInputImpl(String stringData) { this.stringData = stringData; } @Override public Reader getCharacterStream() { return null; } @Override public void setCharacterStream(Reader characterStream) { } @Override public InputStream getByteStream() { return null; } @Override public void setByteStream(InputStream byteStream) { } @Override public String getStringData() { return stringData; } @Override public void setStringData(String stringData) { this.stringData = stringData; } @Override public String getSystemId() { return null; } @Override public void setSystemId(String systemId) { } @Override public String getPublicId() { return null; } @Override public void setPublicId(String publicId) { } @Override public String getBaseURI() { return null; } @Override public void setBaseURI(String baseURI) { } @Override public String getEncoding() { return null; } @Override public void setEncoding(String encoding) { } @Override public boolean getCertifiedText() { return false; } @Override public void setCertifiedText(boolean certifiedText) { } } public static class XSDSchemaMapperErrorHandler implements DOMErrorHandler { private final String schemaResource; public XSDSchemaMapperErrorHandler(String schemaResource) { this.schemaResource = schemaResource; } public boolean handleError(DOMError error) { String from = schemaResource != null ? schemaResource : "string"; log.warn("DOM error reported loading schema from " + from + ":\n" + " message: " + error.getMessage() + "\n" + " type: " + error.getType() + "\n" + " related data: " + error.getRelatedData() + "\n" + " related exception: " + error.getRelatedException() + "\n" + " severity: " + error.getSeverity() + "\n" + " location: " + error.getLocation()); if (error.getRelatedException() instanceof Throwable) { Throwable t = (Throwable) error.getRelatedException(); log.warn("DOM error related exception: " + t.getMessage(), t); } return false; } } }