/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.schema.tools.processing.internal; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.xsd.XSDAttributeDeclaration; import org.eclipse.xsd.XSDAttributeUse; import org.eclipse.xsd.XSDComplexTypeContent; import org.eclipse.xsd.XSDCompositor; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDModelGroup; import org.eclipse.xsd.XSDModelGroupDefinition; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDParticleContent; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.XSDSimpleTypeDefinition; import org.eclipse.xsd.XSDTerm; import org.eclipse.xsd.XSDTypeDefinition; import org.eclipse.xsd.XSDWildcard; import org.eclipse.xsd.util.XSDParser; import org.teiid.designer.schema.tools.ToolsPlugin; import org.teiid.designer.schema.tools.model.schema.Column; import org.teiid.designer.schema.tools.model.schema.QName; import org.teiid.designer.schema.tools.model.schema.SchemaModel; import org.teiid.designer.schema.tools.model.schema.SchemaObject; import org.teiid.designer.schema.tools.model.schema.impl.AttributeColumn; import org.teiid.designer.schema.tools.model.schema.impl.ElementImpl; import org.teiid.designer.schema.tools.model.schema.impl.SchemaModelImpl; import org.teiid.designer.schema.tools.model.schema.impl.TextColumn; import org.teiid.designer.schema.tools.model.schema.impl.TypeDefinition; import org.teiid.designer.schema.tools.processing.SchemaProcessingException; import org.teiid.designer.schema.tools.processing.SchemaProcessor; import org.teiid.designer.schema.tools.processing.SchemaUtil; /** * @since 8.0 */ public class SchemaProcessorImpl implements SchemaProcessor { private Map namespaces; private HashMap duplicateNamespaceFilter; private ArrayList elements; public ElementContentTraversalContext traverseCtx; private boolean representTypes = false; private String separator; public SchemaProcessorImpl( String separator ) { this.separator = separator; clear(); } /** * @See org.teiid.designer.schema.tools.processing.internal.SchemaProcessor#clear() */ @Override public void clear() { namespaces = new HashMap(); duplicateNamespaceFilter = new HashMap(); elements = new ArrayList(); traverseCtx = new ElementContentTraversalContext(null, null); } /** * @See org.teiid.designer.schema.tools.processing.internal.SchemaProcessor#processSchemas(org.eclipse.xsd.XSDSchema[]) */ @Override public void processSchemas( XSDSchema[] schemas ) throws SchemaProcessingException { for (int i = 0; i < schemas.length; ++i) { XSDSchema schema = schemas[i]; processSchemaRootElements(schema, traverseCtx); } for (int i = 0; i < schemas.length; ++i) { XSDSchema schema = schemas[i]; processSchema(schema, traverseCtx); } } /** * @See org.teiid.designer.schema.tools.processing.internal.SchemaProcessor#processSchemaURIs(java.util.List) */ @Override public void processSchemaURIs( List schemaURIs ) throws SchemaProcessingException { List schemas = new ArrayList(); for (Iterator resourceIter = schemaURIs.iterator(); resourceIter.hasNext();) { Object o = resourceIter.next(); URI uri = (URI)o; XSDSchema schema = getSchemaFromURI(uri); schemas.add(schema); } XSDSchema[] xsdSchemas = new XSDSchema[schemas.size()]; schemas.toArray(xsdSchemas); processSchemas(xsdSchemas); } public static XSDSchema getSchemaFromURI( URI uri ) { XSDParser parser = new XSDParser(null); String path = null; // In non-ACS scenarios, the URI will be resolvable to a file on the local filesystem. // In that case, convert URI to path and parse from there. // In the ACS case, the schema is embedded in a custom URI as a string. It // must be extracted and parsed as a string. if (uri.scheme() == "ACSResponse" || uri.scheme() == "ACSRequest") { //$NON-NLS-1$ //$NON-NLS-2$ String schemaString = uri.fragment(); parser.parseString(schemaString); } else { path = uri.toFileString(); parser.parse(path); } XSDSchema schema = parser.getSchema(); if (schema.getSchemaLocation() == null) { schema.setSchemaLocation(path); } return schema; } private void processSchemaRootElements( XSDSchema schema, ElementContentTraversalContext traverseCtx ) { // Eclipse does not resolve references across schema files (i.e. imports) // unless the schemaLocation attribute is provided (in the schema file). // To work around this, we loop through the schemas making a list of the // top level elements and types, so that we can help with the cross // referencing. for (Iterator elemIter = schema.getElementDeclarations().iterator(); elemIter.hasNext();) { Object oelem = elemIter.next(); XSDElementDeclaration elem = (XSDElementDeclaration)oelem; addRootElement(elem, traverseCtx); } for (Iterator typeIter = schema.getTypeDefinitions().iterator(); typeIter.hasNext();) { Object otype = typeIter.next(); XSDTypeDefinition type = (XSDTypeDefinition)otype; addRootType(type, traverseCtx); } } private void processSchema( XSDSchema schema, ElementContentTraversalContext traverseCtx ) throws SchemaProcessingException { processNamespaces(schema); for (Iterator elemiter = schema.getElementDeclarations().iterator(); elemiter.hasNext();) { Object oelem = elemiter.next(); XSDElementDeclaration elem = (XSDElementDeclaration)oelem; processElement(elem, traverseCtx, schema); } if (representTypes) { for (Iterator typeiter = schema.getTypeDefinitions().iterator(); typeiter.hasNext();) { Object otype = typeiter.next(); XSDTypeDefinition type = (XSDTypeDefinition)otype; processType(type, traverseCtx, schema); } } } private void processNamespaces( XSDSchema schema ) { Map schemaNamespaces = schema.getQNamePrefixToNamespaceMap(); Iterator iter = schemaNamespaces.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); if (key == null || key.trim().equals("")) key = "mmn0"; //$NON-NLS-1$ //$NON-NLS-2$ String value = (String)schemaNamespaces.get(key); if (null == value) continue; if (value.equals("http://www.w3.org/XML/1998/namespace")) { //$NON-NLS-1$ key = "xml"; //$NON-NLS-1$ } // ensure unique int i = 1; if ((namespaces.get(key) != null) && (value.equals(namespaces.get(key)))) { continue; } if (null != duplicateNamespaceFilter.get(value)) { String prefix = (String)duplicateNamespaceFilter.get(value); if (prefix.startsWith("mmn")) { //$NON-NLS-1$ prefix = key; } continue; } while (namespaces.get(key) != null) { key = key + i++; } namespaces.put(key, value); duplicateNamespaceFilter.put(value, key); } String namespace = schema.getTargetNamespace(); if ((null != namespace) && (null == duplicateNamespaceFilter.get(namespace))) { if (namespace.equals("http://www.w3.org/XML/1998/namespace")) { //$NON-NLS-1$ namespaces.put("xml", namespace); //$NON-NLS-1$ } else { final String tns = "mmn"; //$NON-NLS-1$ int ctr = 0; if (namespaces.get(tns + ctr) != null) ++ctr; namespaces.put(tns + ctr, namespace); } } } private void addRootElement( XSDElementDeclaration elem, ElementContentTraversalContext traverseCtx ) { String name = elem.getName(); String namespace = elem.getTargetNamespace(); if (name == null) { return; } QName qname = SchemaUtil.getQName(namespace, name); if (traverseCtx.getGlobalElement(qname) != null) { return; } traverseCtx.putGlobalElement(qname, elem); } private void addRootType( XSDTypeDefinition type, ElementContentTraversalContext traverseCtx ) { String name = type.getName(); String namespace = type.getTargetNamespace(); if (name == null) { return; } QName qname = SchemaUtil.getQName(namespace, name); if (traverseCtx.getGlobalType(qname) != null) { return; } traverseCtx.putGlobalType(qname, type); } @Override public void processType( XSDTypeDefinition type, ElementContentTraversalContext traverseCtx2, XSDSchema schema ) throws SchemaProcessingException { String namespacePrefix = getNameSpacePrefix(type.getTargetNamespace()); SchemaObject typeDecl = new TypeDefinition(type, namespacePrefix, schema); String fileName = SchemaUtil.shortenFileName(schema.getSchemaLocation()); typeDecl.setFileName(fileName); addElement(typeDecl); processAttributes(typeDecl, traverseCtx); processElementText(typeDecl); processElementContents(typeDecl, traverseCtx); } @Override public void processElement( XSDElementDeclaration elem, ElementContentTraversalContext traverseCtx, XSDSchema schema ) throws SchemaProcessingException { String name = elem.getName(); String namespace = elem.getTargetNamespace(); String fileName = SchemaUtil.shortenFileName(schema.getSchemaLocation()); XSDElementDeclaration refElem = resolveElementRef(elem, traverseCtx); if (name == null) { if (refElem != elem) { processElement(refElem, traverseCtx, schema); return; } // How can the ref be null, or equal to the referencer if the name is null? ToolsPlugin.Util.log(IStatus.WARNING, ToolsPlugin.Util.getString("SchemaProcessorImpl.nullElement")); //$NON-NLS-1$ return; } if (refElem != null && refElem != elem) { // This case represents an anonymous use of a known type. } XSDTypeDefinition type = resolveElementType(elem, traverseCtx); // Elements can have the same name and the same type and be different elements // (e.g. <xsd:element name="foo" type="bar"/> appearing in multiple places) // We consider these the same table SchemaObject element; QName qname = SchemaUtil.getQName(namespace, name); Map tablesForName = traverseCtx.getElementsByNameThenType(qname); element = (SchemaObject)tablesForName.get(type); if (element == null) { element = new ElementImpl(elem, getNameSpacePrefix(elem.getTargetNamespace()), type, schema); element.setFileName(fileName); // It's important to put the table in the map before we recurse down // the element's contents, to prevent infinite recursion of circular // references tablesForName.put(type, element); addElement(element); processAttributes(element, traverseCtx); processElementText(element); processElementContents(element, traverseCtx); } int minOccurs = traverseCtx.calculateMinOccurs(1); int maxOccurs = traverseCtx.calculateMaxOccurs(1); element.addParent(traverseCtx.getParentTable(), minOccurs, maxOccurs); } public void addElement( SchemaObject element ) { elements.add(element); } public XSDElementDeclaration resolveElementRef( XSDElementDeclaration ref, ElementContentTraversalContext traverseCtx ) { XSDElementDeclaration elem = ref.getResolvedElementDeclaration(); String name = elem.getName(); String namespace = elem.getTargetNamespace(); QName qname = SchemaUtil.getQName(namespace, name); Object oGlobal = traverseCtx.getGlobalElement(qname); XSDElementDeclaration retval; if (oGlobal != null) { retval = (XSDElementDeclaration)oGlobal; } else { retval = elem; } return retval; } public XSDTypeDefinition resolveElementType( XSDElementDeclaration elem, ElementContentTraversalContext traverseCtx ) throws SchemaProcessingException { XSDTypeDefinition type = elem.getType(); //Type is an optional attribute, so we will return null if it is not there. if (null == type) { return null; } return resolveType(type, traverseCtx); } private XSDTypeDefinition resolveType( XSDTypeDefinition type, ElementContentTraversalContext traverseCtx ) { String name = type.getName(); String namespace = type.getTargetNamespace(); QName qname = SchemaUtil.getQName(namespace, name); Object oGlobal = traverseCtx.getGlobalType(qname); XSDTypeDefinition retval; if (oGlobal != null) { retval = (XSDTypeDefinition)oGlobal; } else { retval = type; } return retval; } public void processAttributes( SchemaObject element, ElementContentTraversalContext traverseCtx ) throws SchemaProcessingException { List attrs = element.getAttributeList(); for (Iterator iter = attrs.iterator(); iter.hasNext();) { Object o = iter.next(); XSDAttributeUse attrUse = (XSDAttributeUse)o; XSDAttributeDeclaration attrDecl = attrUse.getAttributeDeclaration(); Column col = new AttributeColumn(attrDecl, getNameSpacePrefix(attrDecl.getTargetNamespace()), false); element.addAttribute(col); } } public String getNameSpacePrefix( String targetNamespace ) { Map namespacePrefixes = getNamespacePrefixes(); return (String)namespacePrefixes.get(targetNamespace); } public Map getNamespacePrefixes() { // reverse the m_namespaces map Map nsMap = getNamespaces(); HashMap returnMap = new HashMap(); Iterator nsIter = nsMap.keySet().iterator(); while (nsIter.hasNext()) { String key = (String)nsIter.next(); returnMap.put(nsMap.get(key), key); } return returnMap; } @Override public void processElementText( SchemaObject element ) { XSDSimpleTypeDefinition textType = element.getTextType(); Column col; if (textType != null) { col = new TextColumn(false, textType); element.addAttribute(col); } } public void processElementContents( SchemaObject element, ElementContentTraversalContext traverseCtx ) throws SchemaProcessingException { XSDComplexTypeContent content = element.getContent(); if (null == content || !(content instanceof XSDParticle)) { return; } XSDParticle particle = (XSDParticle)content; ElementContentTraversalContext childTraverseCtx = new ElementContentTraversalContext(element, traverseCtx); processParticle(particle, childTraverseCtx, element.getSchema()); } private void processParticle( XSDParticle particle, ElementContentTraversalContext traverseCtx, XSDSchema schema ) throws SchemaProcessingException { XSDParticleContent content = particle.getContent(); int min = particle.getMinOccurs(); int max = particle.getMaxOccurs(); traverseCtx.addMinOccurs(new Integer(min)); traverseCtx.addMaxOccurs(new Integer(max)); if (content instanceof XSDElementDeclaration) { XSDElementDeclaration elem = (XSDElementDeclaration)content; processElement(elem, traverseCtx, schema); } else if (content instanceof XSDModelGroup) { XSDModelGroup group = (XSDModelGroup)content; processGroup(group, traverseCtx, schema); } else if (content instanceof XSDModelGroupDefinition) { XSDModelGroupDefinition groupDef = (XSDModelGroupDefinition)content; XSDModelGroupDefinition resolvedGroup = groupDef.getResolvedModelGroupDefinition(); XSDModelGroup group = resolvedGroup == null ? null : resolvedGroup.getModelGroup(); if (group != null) { processGroup(group, traverseCtx, schema); } } else if (content instanceof XSDTerm) { // XSDTerm term = (XSDTerm)content; // TODO: what are these? } else if (content instanceof XSDWildcard) { // XSDWildcard wildcard = (XSDWildcard)content; // I don't think we need to do anything with these } traverseCtx.removeMinOccurs(traverseCtx.minOccurs.size() - 1); traverseCtx.removeMaxOccurs(traverseCtx.maxOccurs.size() - 1); } private void processGroup( XSDModelGroup group, ElementContentTraversalContext traverseCtx, XSDSchema schema ) throws SchemaProcessingException { int min; int compositor = group.getCompositor().getValue(); switch (compositor) { case XSDCompositor.ALL: min = 0; break; case XSDCompositor.CHOICE: min = 0; // technically if there was only one child this shoud be // 1 // but I'm not sure if we can determine that here break; case XSDCompositor.SEQUENCE: min = 1; break; default: // error min = 1; break; } traverseCtx.addMinOccurs(new Integer(min)); traverseCtx.addMaxOccurs(new Integer(1)); EList particles = group.getParticles(); for (Iterator iter = particles.iterator(); iter.hasNext();) { Object o = iter.next(); XSDParticle particle = (XSDParticle)o; processParticle(particle, traverseCtx, schema); } traverseCtx.removeMinOccurs(traverseCtx.minOccurs.size() - 1); traverseCtx.removeMaxOccurs(traverseCtx.maxOccurs.size() - 1); } /** * @See org.teiid.designer.schema.tools.processing.internal.SchemaProcessor#getNamespaces() */ @Override public Map getNamespaces() { return namespaces; } /* (non-Javadoc) * @See org.teiid.designer.schema.tools.processing.internal.SchemaProcessor#getSchemaModel() */ @Override public SchemaModel getSchemaModel() { SchemaModelImpl model = new SchemaModelImpl(elements, namespaces, separator); model.setTypeAware(representTypes); return model; } @Override public void representTypes( boolean representTypes ) { this.representTypes = representTypes; } @Override public void setNamespaces( Map namespaces ) { this.namespaces = namespaces; } }