/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.xml; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.xsd.XSDAttributeDeclaration; import org.eclipse.xsd.XSDAttributeGroupContent; import org.eclipse.xsd.XSDAttributeGroupDefinition; import org.eclipse.xsd.XSDAttributeUse; import org.eclipse.xsd.XSDComplexTypeDefinition; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDFactory; import org.eclipse.xsd.XSDImport; import org.eclipse.xsd.XSDInclude; import org.eclipse.xsd.XSDModelGroup; import org.eclipse.xsd.XSDModelGroupDefinition; import org.eclipse.xsd.XSDNamedComponent; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.XSDSchemaContent; import org.eclipse.xsd.XSDSchemaDirective; import org.eclipse.xsd.XSDSimpleTypeDefinition; import org.eclipse.xsd.XSDTypeDefinition; import org.eclipse.xsd.util.XSDConstants; import org.eclipse.xsd.util.XSDResourceFactoryImpl; import org.eclipse.xsd.util.XSDResourceImpl; import org.eclipse.xsd.util.XSDSchemaLocationResolver; import org.eclipse.xsd.util.XSDSchemaLocator; import org.eclipse.xsd.util.XSDUtil; import org.geotools.data.DataUtilities; import org.geotools.util.Utilities; import org.geotools.xml.impl.SchemaIndexImpl; import org.geotools.xml.impl.TypeWalker; import org.picocontainer.ComponentAdapter; import org.picocontainer.MutablePicoContainer; import org.picocontainer.Parameter; import org.picocontainer.PicoContainer; import org.picocontainer.PicoVisitor; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.eclipse.xsd.XSDWildcard; /** * Utility class for performing various operations. * * @author Justin Deoliveira, The Open Planning Project * * * * @source $URL$ */ public class Schemas { private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(Schemas.class.getPackage() .getName()); static { //need to register custom factory to load schema resources Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap() .put("xsd", new XSDResourceFactoryImpl()); } /** * Finds all the XSDSchemas used by the {@link Configuration configuration} * by looking at the configuration's schema locator and its dependencies. * * @param configuration the {@link Configuration} for which to find all its * related schemas * * @return a {@link SchemaIndex} holding the schemas related to * <code>configuration</code> */ public static final SchemaIndex findSchemas(Configuration configuration) { Set configurations = new HashSet(configuration.allDependencies()); configurations.add(configuration); List resolvedSchemas = new ArrayList(configurations.size()); for (Iterator it = configurations.iterator(); it.hasNext();) { Configuration conf = (Configuration) it.next(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("looking up schema for " + conf.getNamespaceURI()); } XSDSchemaLocator locator = conf.getSchemaLocator(); if (locator == null) { LOGGER.fine("No schema locator for " + conf.getNamespaceURI()); continue; } String namespaceURI = conf.getNamespaceURI(); String schemaLocation = null; try { URL location = new URL(conf.getSchemaFileURL()); schemaLocation = location.toExternalForm(); } catch (MalformedURLException e) { throw new RuntimeException(e); } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("schema location: " + schemaLocation); } XSDSchema schema = locator.locateSchema(null, namespaceURI, schemaLocation, null); if (schema != null) { resolvedSchemas.add(schema); } } XSDSchema[] schemas = (XSDSchema[]) resolvedSchemas.toArray(new XSDSchema[resolvedSchemas .size()]); SchemaIndex index = new SchemaIndexImpl(schemas); return index; } /** * Finds all {@link XSDSchemaLocationResolver}'s used by the configuration. * * @param configuration The parser configuration. * * @return A list of location resolvers, empty if none found. */ public static List findSchemaLocationResolvers(Configuration configuration) { List all = configuration.allDependencies(); List resolvers = new ArrayList(); for (Iterator c = all.iterator(); c.hasNext();) { configuration = (Configuration) c.next(); XSDSchemaLocationResolver resolver = configuration.getSchemaLocationResolver(); if (resolver != null) { resolvers.add(resolver); } } return resolvers; } /** * Parses a schema at the specified location. * * @param location A uri pointing to the location of the schema. * * @return The parsed schema, or null if the schema could not be parsed. * * @throws IOException In the event of a schema parsing error. */ public static final XSDSchema parse(String location) throws IOException { return parse(location, Collections.EMPTY_LIST, Collections.EMPTY_LIST); } /** * Parses a schema at the specified location. * * @param location A uri pointing to the location of the schema. * @param locators An array of schema locator objects to be used when * parsing imports/includes of the main schema. * @param resolvers An array of schema location resolvers used to override * schema locations encountered in an instance document or an imported * schema. * * @return The parsed schema, or null if the schema could not be parsed. * * @throws IOException In the event of a schema parsing error. */ public static final XSDSchema parse(String location, XSDSchemaLocator[] locators, XSDSchemaLocationResolver[] resolvers) throws IOException { return parse(location, (locators != null) ? Arrays.asList(locators) : Collections.EMPTY_LIST, (resolvers != null) ? Arrays.asList(resolvers) : Collections.EMPTY_LIST); } public static final XSDSchema parse(String location, List locators, List resolvers) throws IOException { ResourceSet resourceSet = new ResourceSetImpl(); //add the specialized schema location resolvers if ((resolvers != null) && !resolvers.isEmpty()) { AdapterFactory adapterFactory = new SchemaLocationResolverAdapterFactory(resolvers); resourceSet.getAdapterFactories().add(adapterFactory); } //add the specialized schema locators as adapters if ((locators != null) && !locators.isEmpty()) { AdapterFactory adapterFactory = new SchemaLocatorAdapterFactory(locators); resourceSet.getAdapterFactories().add(adapterFactory); } return parse(location, resourceSet); } public static final XSDSchema parse(String location, ResourceSet resourceSet) throws IOException { //check for case of file url, make sure it is an absolute reference File locationFile = null; try { locationFile = DataUtilities.urlToFile(new URL(location)); } catch (MalformedURLException e) { // Some tests use relative file URLs, which Schemas.parse cannot // support as it does not permit a context URL. Treat them // as local file paths. Naughty, naughty. locationFile = new File(location); } if (locationFile != null && locationFile.exists()) { location = locationFile.getCanonicalFile().toURI().toString(); } URI uri = URI.createURI(location); XSDResourceImpl xsdMainResource = (XSDResourceImpl) resourceSet.createResource(URI.createURI( ".xsd")); xsdMainResource.setURI(uri); xsdMainResource.load(resourceSet.getLoadOptions()); return xsdMainResource.getSchema(); } /** * Imports one schema into another. * * @param schema The schema being imported into. * @param importee The schema being imported. * * @return The import object. */ public static final XSDImport importSchema( XSDSchema schema, final XSDSchema importee ) throws IOException { Resource resource = schema.eResource(); if ( resource == null ) { final ResourceSet resourceSet = new ResourceSetImpl(); resource = (XSDResourceImpl) resourceSet.createResource(URI.createURI(".xsd")); resource.getContents().add( schema ); } XSDImport imprt = XSDFactory.eINSTANCE.createXSDImport(); imprt.setNamespace( importee.getTargetNamespace() ); schema.getContents().add( imprt ); List<XSDSchemaLocator> locators = new ArrayList<XSDSchemaLocator>(); locators.add( new XSDSchemaLocator() { public XSDSchema locateSchema(XSDSchema xsdSchema, String namespaceURI, String rawSchemaLocationURI, String resolvedSchemaLocationURI) { if ( importee.getTargetNamespace().equals( namespaceURI ) ) { return importee; } return null; } }); AdapterFactory adapterFactory = new SchemaLocatorAdapterFactory(locators); resource.getResourceSet().getAdapterFactories().add( adapterFactory ); return imprt; } /** * Remove all references to a schema * It is important to call this method for every dynamic schema created that is not needed * anymore, because references in the static schema's will otherwise keep it alive forever * * @param schema to be flushed */ public static final void dispose(XSDSchema schema) { for (XSDSchemaContent content : schema.getContents()) { if (content instanceof XSDSchemaDirective) { XSDSchemaDirective directive = (XSDSchemaDirective) content; XSDSchema resolvedSchema = directive.getResolvedSchema(); if (resolvedSchema != null) { synchronized(resolvedSchema) { resolvedSchema.getReferencingDirectives().remove(directive); for (XSDElementDeclaration dec : resolvedSchema.getElementDeclarations()) { List<XSDElementDeclaration> toRemove = new ArrayList<XSDElementDeclaration>(); for (XSDElementDeclaration subs : dec.getSubstitutionGroup()) { if (subs.getContainer().equals(schema)) { toRemove.add(subs); } } dec.getSubstitutionGroup().removeAll(toRemove); } } } } } } public static final List validateImportsIncludes(String location) throws IOException { return validateImportsIncludes(location,Collections.EMPTY_LIST,Collections.EMPTY_LIST); } public static final List validateImportsIncludes(String location, XSDSchemaLocator[] locators, XSDSchemaLocationResolver[] resolvers ) throws IOException { return validateImportsIncludes(location, (locators != null) ? Arrays.asList(locators) : Collections.EMPTY_LIST, (resolvers != null) ? Arrays.asList(resolvers) : Collections.EMPTY_LIST); } public static final List validateImportsIncludes(String location, List locators, List resolvers) throws IOException { //create a parser SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(false); SAXParser parser = null; try { parser = factory.newSAXParser(); } catch( Exception e ) { throw (IOException) new IOException("could not create parser").initCause( e ); } SchemaImportIncludeValidator validator = new SchemaImportIncludeValidator( locators, resolvers ); //queue of files to parse LinkedList q = new LinkedList(); q.add( location ); while( !q.isEmpty() ) { location = (String) q.removeFirst(); validator.setBaseLocation( location ); try { parser.parse( location, validator ); } catch (SAXException e) { throw (IOException) new IOException( "parse error").initCause( e ); } //check for errors if ( !validator.errors.isEmpty() ) { return validator.errors; } if ( !validator.next.isEmpty() ) { q.addAll( validator.next ); } } return Collections.EMPTY_LIST; } static final class SchemaImportIncludeValidator extends DefaultHandler { /** base location */ String baseLocation; /** locators for resolving to schemas directely */ List locators; /** locators for resolving to absolute schema locations */ List resolvers; /** tracking seen namespaces and schema locations */ Set seen; /** list of errors encountered */ List errors; /** next set of locations to process */ List next; SchemaImportIncludeValidator( List locators, List resolvers ) { this.locators = locators; this.resolvers = resolvers; seen = new HashSet(); errors = new ArrayList(); next = new ArrayList(); } public void setBaseLocation(String baseLocation) { this.baseLocation = baseLocation; } public void startDocument() throws SAXException { next.clear(); } public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { //process import if ( "import".equals( localName ) ) { //get the namespace + location String namespace = attributes.getValue( "namespace" ); String schemaLocation = attributes.getValue( "schemaLocation" ); //do not validate if we have already seen this import if ( seen.contains( namespace) ) { return; } seen.add( namespace ); if ( schemaLocation != null ) { //look for a locator or resolver that can handle it for ( Iterator l = locators.iterator(); l.hasNext(); ) { XSDSchemaLocator locator = (XSDSchemaLocator) l.next(); //check for schema locator which canHandle if ( locator instanceof SchemaLocator ) { if ( ((SchemaLocator)locator).canHandle(null, namespace, schemaLocation, null)) { //cool, return here and do not recurse return; } } } //resolve String resolvedSchemaLocation = resolve( namespace, schemaLocation ); if ( resolvedSchemaLocation != null ) { recurse( resolvedSchemaLocation ); } else { errors.add( "Could not resolve import: " + namespace + "," + schemaLocation); } } else { errors.add( "No schemaLocation attribute for namespace import: " + namespace ); } } else if ( "include".equals( localName ) ) { //process include String location = attributes.getValue( "location" ); String resolvedLocation = resolve( null, location ); if ( resolvedLocation != null ) { recurse( resolvedLocation ); } else { errors.add( "Could not resolve include: " + location); } } } String resolve( String namespace, String location ) { //look for a location resolver capable of handling it for ( Iterator r = resolvers.iterator(); r.hasNext(); ) { XSDSchemaLocationResolver resolver = (XSDSchemaLocationResolver) r.next(); if ( resolver instanceof SchemaLocationResolver ) { if ( ((SchemaLocationResolver) resolver ).canHandle(null, namespace,location)) { //can handle, actually resolve and recurse String resolvedSchemaLocation = resolver.resolveSchemaLocation(null, namespace, location ); if ( resolvedSchemaLocation != null ) { return resolvedSchemaLocation; } else { //should not happen, but just continue } } } } //attempt tp resolve manuualy File file = new File( location ); if ( file.exists() ) { return file.getAbsolutePath(); } else { //try relative to base location if ( !file.isAbsolute() ) { File dir = new File( baseLocation ).getParentFile(); if ( dir != null ) { file = new File( dir, location ); if ( file.exists() ) { return file.getAbsolutePath(); } } } } return null; } void recurse( String location ) { //do not recurse if we have already processed this one if ( seen.contains( location ) ) { return; } //mark it as seen seen.add( location ); //add to queue for processing of in next round next.add( location ); } } /** * Returns a list of all child element declarations of the specified * element, no order is guaranteed. * * @param element The parent element. * * @return A list of @link XSDElementDeclaration objects, one for each * child element. * * @deprecated use {@link #getChildElementDeclarations(XSDTypeDefinition)} */ public static final List getChildElementDeclarations(XSDElementDeclaration element) { return getChildElementDeclarations(element.getType()); } /** * Returns a list of all child element declarations of the specified * type, no order is guaranteed. * * @param type The type. * * @return A list of @link XSDElementDeclaration objects, one for each * child element. */ public static final List getChildElementDeclarations(XSDTypeDefinition type) { return getChildElementDeclarations(type, true); } /** * Returns a list of all child element declarations of the specified * element. * <p> * The <code>includeParents</code> flag controls if this method should * returns those elements defined on parent types. * </p> * @param element The parent element. * @param includeParents Flag indicating if parent types should be processed. * @return A list of @link XSDElementDeclaration objects, one for each * child element. * * @deprecated use {@link #getChildElementDeclarations(XSDTypeDefinition, boolean)} */ public static final List getChildElementDeclarations(XSDElementDeclaration element, boolean includeParents) { return getChildElementDeclarations(element.getType(), includeParents); } /** * Returns the particle for an element declaration that is part of a type. * * @param type The type definition. * @param name The naem of the child element declaration. * * @param includeParents Flag to control wether parent types are included. * * @return The particle representing the element declaration, or <code>null</code> if it could * not be found. */ public static final XSDParticle getChildElementParticle(XSDTypeDefinition type, String name, boolean includeParents) { List particles = getChildElementParticles(type, includeParents); for (Iterator p = particles.iterator(); p.hasNext();) { XSDParticle particle = (XSDParticle) p.next(); XSDElementDeclaration element = (XSDElementDeclaration) particle.getContent(); if (element.isElementDeclarationReference()) { element.getResolvedElementDeclaration(); } if (name.equals(element.getName())) { return particle; } } return null; } /** * Returns a list of all child element particles that corresponde to element declarations of * the specified type, no order is guaranteed. * <p> * The <code>includeParents</code> flag controls if this method should * returns those elements defined on parent types. * </p> * * @param type THe type. * @param includeParents flag indicating if parent types should be processed * * @return A list of {@link XSDParticle}. * */ public static final List getChildElementParticles(XSDTypeDefinition type, boolean includeParents) { final HashSet contents = new HashSet(); final ArrayList particles = new ArrayList(); TypeWalker.Visitor visitor = new TypeWalker.Visitor() { public boolean visit(XSDTypeDefinition type) { //simple types dont have children if (type instanceof XSDSimpleTypeDefinition) { return true; } XSDComplexTypeDefinition cType = (XSDComplexTypeDefinition) type; ElementVisitor visitor = new ElementVisitor() { public void visit(XSDParticle particle) { XSDElementDeclaration element = (XSDElementDeclaration) particle .getContent(); if (element.isElementDeclarationReference()) { element = element.getResolvedElementDeclaration(); } //make sure unique if (!contents.contains(element)) { contents.add(element); particles.add(particle); } } }; visitElements(cType, visitor); return true; } }; if (includeParents) { //walk up the type hierarchy of the element to generate a list of // possible elements new TypeWalker().rwalk(type, visitor); } else { //just visit this type visitor.visit(type); } return new ArrayList(particles); } /** * Returns a list of all xs:any element particles that correspond to element declarations of the * specified type. * * @param type * The type. * * @return A list of {@link XSDParticle}. * */ public static final List getAnyElementParticles(XSDTypeDefinition type) { final HashSet contents = new HashSet(); final ArrayList particles = new ArrayList(); TypeWalker.Visitor visitor = new TypeWalker.Visitor() { public boolean visit(XSDTypeDefinition type) { // simple types doesn't have children if (type instanceof XSDSimpleTypeDefinition) { return true; } XSDComplexTypeDefinition cType = (XSDComplexTypeDefinition) type; ElementVisitor visitor = new ElementVisitor() { public void visit(XSDParticle particle) { XSDWildcard element = (XSDWildcard) particle.getContent(); // make sure unique if (!contents.contains(element)) { contents.add(element); particles.add(particle); } } }; visitAnyElements(cType, visitor); return true; } }; // just visit this type visitor.visit(type); return new ArrayList(particles); } /** * Method to visit xs:any element. The method differs from visitElements only by the fact that * it visits xs:any rather than <element> * * @param cType * XSDComplexTypeDefinition * @param visitor * ElementVisitor */ private static void visitAnyElements(XSDComplexTypeDefinition cType, ElementVisitor visitor) { // simple content cant define children if ((cType.getContent() == null) || (cType.getContent() instanceof XSDSimpleTypeDefinition)) { return; } // use a queue to simulate the recursion LinkedList queue = new LinkedList(); queue.addLast(cType.getContent()); while (!queue.isEmpty()) { XSDParticle particle = (XSDParticle) queue.removeFirst(); // analyze type of particle content int pType = XSDUtil.nodeType(particle.getElement()); if (pType == XSDConstants.ANY_ELEMENT) { visitor.visit(particle); } else { // model group XSDModelGroup grp = null; switch (pType) { case XSDConstants.GROUP_ELEMENT: XSDModelGroupDefinition grpDef = (XSDModelGroupDefinition) particle .getContent(); if (grpDef.isModelGroupDefinitionReference()) { grpDef = grpDef.getResolvedModelGroupDefinition(); } grp = grpDef.getModelGroup(); break; case XSDConstants.CHOICE_ELEMENT: case XSDConstants.ALL_ELEMENT: case XSDConstants.SEQUENCE_ELEMENT: grp = (XSDModelGroup) particle.getContent(); break; } if (grp != null) { // enque all particles in the group List parts = grp.getParticles(); // TODO: this check isa bit hacky.. .figure out why this is the case if (parts.isEmpty()) { parts = grp.getContents(); } // add in reverse order to front of queue to maintain order for (int i = parts.size() - 1; i >= 0; i--) { queue.addFirst(parts.get(i)); } } } } // while } /** * Returns a list of all child element declarations of the specified * type, no order is guaranteed. * <p> * The <code>includeParents</code> flag controls if this method should * returns those elements defined on parent types. * </p> * @param type The type * @param includeParents flag indicating if parent types should be processed * * @return A list of @link XSDElementDeclaration objects, one for each * child element. */ public static final List getChildElementDeclarations(XSDTypeDefinition type, boolean includeParents) { List particles = getChildElementParticles(type, includeParents); List elements = new ArrayList(); for (Iterator p = particles.iterator(); p.hasNext();) { XSDParticle particle = (XSDParticle) p.next(); XSDElementDeclaration decl = (XSDElementDeclaration) particle.getContent(); if (decl.isElementDeclarationReference()) { decl = decl.getResolvedElementDeclaration(); } elements.add(decl); } return elements; } /** * Returns the base type defintion of <code>type</code> named <code>parentTypeName<code>. * <p> * This method will handle the case in which the <code>parentTypeName == type.getTypeName()</code>. * If no such parent type is found this method will return <code>null</code>. * </p> * @param type The type. * @param parentTypeName The name of the base type to return. * * @return The base type, or null if it could not be found. */ public static final XSDTypeDefinition getBaseTypeDefinition(XSDTypeDefinition type, final QName parentTypeName) { final List found = new ArrayList(); TypeWalker.Visitor visitor = new TypeWalker.Visitor() { public boolean visit(XSDTypeDefinition type) { if (nameMatches(type, parentTypeName)) { found.add(type); return false; } return true; } }; new TypeWalker().walk(type, visitor); return found.isEmpty() ? null : (XSDTypeDefinition) found.get(0); } /** * Determines if the type of an element is a sub-type of another element. * * @param e1 The element. * @param e2 The element to be tested as a base type. * * @since 2.5 */ public static final boolean isBaseType(XSDElementDeclaration e1, XSDElementDeclaration e2 ) { XSDTypeDefinition type = e1.getType(); while( type != null ) { if ( type.equals( e2.getType() ) ) { return true; } if ( type.equals( type.getBaseType() ) ) { break; } type = type.getBaseType(); } return false; } /** * Returns the minimum number of occurences of an element within a complex * type. * * @param type The type definition containg the declaration <code>element</code> * @param element The declaration of the element. * * @return The minimum number of occurences. * * @throws IllegalArgumentException If the element declaration cannot be * locaated withing the type definition. */ public static final int getMinOccurs(XSDComplexTypeDefinition type, XSDElementDeclaration element) { final XSDElementDeclaration fElement = element; final ArrayList minOccurs = new ArrayList(); ElementVisitor visitor = new ElementVisitor() { public void visit(XSDParticle particle) { XSDElementDeclaration decl = (XSDElementDeclaration) particle.getContent(); if (decl.isElementDeclarationReference()) { decl = decl.getResolvedElementDeclaration(); } if (decl == fElement) { minOccurs.add(new Integer(particle.getMinOccurs())); } } }; visitElements(type, visitor, true); if (minOccurs.isEmpty()) { throw new IllegalArgumentException("Element: " + element + " not found in type: " + type); } return ((Integer) minOccurs.get(0)).intValue(); } /** * Returns the minimum number of occurences of an element within a complex * type. * * @param type The type definition containg the declaration <code>element</code> * @param element The declaration of the element. * * @return The minimum number of occurences. * * @throws IllegalArgumentException If the element declaration cannot be * locaated withing the type definition. */ public static final int getMaxOccurs(XSDComplexTypeDefinition type, XSDElementDeclaration element) { final XSDElementDeclaration fElement = element; final ArrayList maxOccurs = new ArrayList(); ElementVisitor visitor = new ElementVisitor() { public void visit(XSDParticle particle) { XSDElementDeclaration decl = (XSDElementDeclaration) particle.getContent(); if (decl.isElementDeclarationReference()) { decl = decl.getResolvedElementDeclaration(); } if (decl == fElement) { maxOccurs.add(new Integer(particle.getMaxOccurs())); } } }; visitElements(type, visitor, true); if (maxOccurs.isEmpty()) { throw new IllegalArgumentException("Element: " + element + " not found in type: " + type); } return ((Integer) maxOccurs.get(0)).intValue(); } private static void visitElements(XSDComplexTypeDefinition cType, ElementVisitor visitor, boolean includeParents) { if (includeParents) { LinkedList baseTypes = new LinkedList(); XSDTypeDefinition baseType = cType.getBaseType(); while ((baseType != null) && (baseType != baseType.getBaseType())) { if (baseType instanceof XSDComplexTypeDefinition) { baseTypes.addLast(baseType); } baseType = baseType.getBaseType(); } for (Iterator it = baseTypes.iterator(); it.hasNext();) { baseType = (XSDTypeDefinition) it.next(); visitElements((XSDComplexTypeDefinition) baseType, visitor); } } visitElements(cType, visitor); } private static void visitElements(XSDComplexTypeDefinition cType, ElementVisitor visitor) { //simple content cant define children if ((cType.getContent() == null) || (cType.getContent() instanceof XSDSimpleTypeDefinition)) { return; } //use a queue to simulate the recursion LinkedList queue = new LinkedList(); queue.addLast(cType.getContent()); while (!queue.isEmpty()) { XSDParticle particle = (XSDParticle) queue.removeFirst(); //analyze type of particle content int pType = XSDUtil.nodeType(particle.getElement()); if (pType == XSDConstants.ELEMENT_ELEMENT) { visitor.visit(particle); } else { //model group XSDModelGroup grp = null; switch (pType) { case XSDConstants.GROUP_ELEMENT: XSDModelGroupDefinition grpDef = (XSDModelGroupDefinition) particle.getContent(); if (grpDef.isModelGroupDefinitionReference()) { grpDef = grpDef.getResolvedModelGroupDefinition(); } grp = grpDef.getModelGroup(); break; case XSDConstants.CHOICE_ELEMENT: case XSDConstants.ALL_ELEMENT: case XSDConstants.SEQUENCE_ELEMENT: grp = (XSDModelGroup) particle.getContent(); break; } if (grp != null) { //enque all particles in the group List parts = grp.getParticles(); //TODO: this check isa bit hacky.. .figure out why this is the case if ( parts.isEmpty() ) { parts = grp.getContents(); } //add in reverse order to front of queue to maintain order for (int i = parts.size() - 1; i >= 0; i--) { queue.addFirst(parts.get(i)); } } } } //while } /** * Returns an element declaration that is contained in the type of another * element declaration. The following strategy is used to locate the child * element declaration. * * <ol> * <li>The immediate children of the specified element are examined, if a * match is found, it is returned. * </li>If 1. does not match, global elements that derive from the * immediate children are examined. * </ol> * * @param parent the containing element declaration * @param qName the qualified name of the contained element * * @return The contained element declaration, or false if containment is * not satisfied. */ public static final XSDElementDeclaration getChildElementDeclaration( XSDElementDeclaration parent, QName qName) { //look for a match in a direct child List children = getChildElementDeclarations(parent); for (Iterator itr = children.iterator(); itr.hasNext();) { XSDElementDeclaration element = (XSDElementDeclaration) itr.next(); if (nameMatches(element, qName)) { return element; } } //couldn't find one, look for match in derived elements ArrayList derived = new ArrayList(); for (Iterator itr = children.iterator(); itr.hasNext();) { XSDElementDeclaration child = (XSDElementDeclaration) itr.next(); derived.addAll(getDerivedElementDeclarations(child)); } for (Iterator itr = derived.iterator(); itr.hasNext();) { XSDElementDeclaration child = (XSDElementDeclaration) itr.next(); if (nameMatches(child, qName)) { return child; } } return null; } /** * Returns a list of all top level elements that are of a type derived * from the type of the specified element. * * @param element The element. * * @return All elements which are of a type derived from the type of the * specified element. */ public static final List getDerivedElementDeclarations(XSDElementDeclaration element) { List elements = element.getSchema().getElementDeclarations(); List derived = new ArrayList(); for (Iterator itr = elements.iterator(); itr.hasNext();) { XSDElementDeclaration derivee = (XSDElementDeclaration) itr.next(); if (derivee.equals(element)) { continue; //same element } XSDTypeDefinition type = derivee.getType(); while (true) { if (type.equals(element.getType())) { derived.add(derivee); break; } if (type.equals(type.getBaseType())) { break; } type = type.getBaseType(); } } return derived; } /** * Returns a list of all attribute declarations declared in the type (or * any base type) of the specified element. * * <p> * This method is just a shortcut for {@link #getAttributeDeclarations(XSDTypeDefinition) getAttributeDeclarations(element.getType()} * </p> * * @param element The element. * * @return A list of @link XSDAttributeDeclaration objects, one for each * attribute of the element. */ public static final List getAttributeDeclarations(XSDElementDeclaration element) { return getAttributeDeclarations(element.getType()); } /** * Returns a list of all attribute declarations declared in the type (or * any base type) of the specified element. * * @param element The element. * * @return A list of @link XSDAttributeDeclaration objects, one for each * attribute of the element. */ public static final List getAttributeDeclarations(XSDTypeDefinition type) { return getAttributeDeclarations(type,true); } /** * Returns a list of all attribute declarations declared in the type (and optionally * any base type) of the specified element. * * @param element The element. * @param includeParents Wether to include parent types. * * @return A list of @link XSDAttributeDeclaration objects, one for each * attribute of the element. */ public static final List getAttributeDeclarations(XSDTypeDefinition type, boolean includeParents) { final ArrayList attributes = new ArrayList(); //walk up the type hierarchy of the element to generate a list of atts TypeWalker.Visitor visitor = new TypeWalker.Visitor() { public boolean visit(XSDTypeDefinition type) { //simple types dont have attributes if (type instanceof XSDSimpleTypeDefinition) { return true; } XSDComplexTypeDefinition cType = (XSDComplexTypeDefinition) type; //get all the attribute content (groups,or uses) and add to q List attContent = cType.getAttributeContents(); for (Iterator itr = attContent.iterator(); itr.hasNext();) { XSDAttributeGroupContent content = (XSDAttributeGroupContent) itr.next(); if (content instanceof XSDAttributeUse) { //an attribute, add it to the list XSDAttributeUse use = (XSDAttributeUse) content; attributes.add(use.getAttributeDeclaration()); } else if (content instanceof XSDAttributeGroupDefinition) { //attribute group, add all atts in group to list XSDAttributeGroupDefinition attGrp = (XSDAttributeGroupDefinition) content; if (attGrp.isAttributeGroupDefinitionReference()) { attGrp = attGrp.getResolvedAttributeGroupDefinition(); } List uses = attGrp.getAttributeUses(); for (Iterator aitr = uses.iterator(); aitr.hasNext();) { XSDAttributeUse use = (XSDAttributeUse) aitr.next(); attributes.add(use.getAttributeDeclaration()); } } } return true; } }; if (includeParents) { //walk up the type hierarchy of the element to generate a list of // possible elements new TypeWalker().walk(type, visitor); } else { //just visit this type visitor.visit(type); } return attributes; } /** * Returns an attribute declaration that is contained in the type of another * element declaration. * * @param element The containing element declaration. * @param qName The qualified name of the contained attribute * * @return The contained attribute declaration, or false if containment is * not satisfied. */ public static final XSDAttributeDeclaration getAttributeDeclaration( XSDElementDeclaration element, QName qName) { List atts = getAttributeDeclarations(element); for (Iterator itr = atts.iterator(); itr.hasNext();) { XSDAttributeDeclaration att = (XSDAttributeDeclaration) itr.next(); if (nameMatches(att, qName)) { return att; } } return null; } /** * Returns a flat list of imports from the specified schema. * <p> * The method recurses into imported schemas. The list returned is filtered so that * duplicate includes are removed. Two includes are considered equal if they have the same * target namespace. * </p> * * @param schema The top-level schema. * * @return A list containing objects of type {@link XSDImport}. */ public static final List getImports(XSDSchema schema) { LinkedList queue = new LinkedList(); ArrayList imports = new ArrayList(); HashSet added = new HashSet(); queue.addLast(schema); while (!queue.isEmpty()) { schema = (XSDSchema) queue.removeFirst(); List contents = schema.getContents(); for (Iterator itr = contents.iterator(); itr.hasNext();) { XSDSchemaContent content = (XSDSchemaContent) itr.next(); if (content instanceof XSDImport) { XSDImport imprt = (XSDImport) content; if (!added.contains(imprt.getNamespace())) { imports.add(imprt); added.add(imprt.getNamespace()); XSDSchema resolvedSchema = imprt.getResolvedSchema(); if(resolvedSchema == null){ LOGGER.info("Schema import wasn't resolved: " + imprt.getNamespace() + " declared location: " + imprt.getSchemaLocation()); }else{ queue.addLast(resolvedSchema); } } } } } return imports; } /** * Returns a flat list of includes from the specified schema. * <p> * The method recurses into included schemas. The list returned is filtered so that * duplicate includes are removed. Two includes are considered equal if they have the same * uri location * </p> * * @param schema The top-level schema. * * @return A list containing objects of type {@link XSDInclude}. */ public static final List getIncludes(XSDSchema schema) { LinkedList queue = new LinkedList(); ArrayList includes = new ArrayList(); HashSet added = new HashSet(); queue.addLast(schema); while (!queue.isEmpty()) { schema = (XSDSchema) queue.removeFirst(); List contents = schema.getContents(); for (Iterator itr = contents.iterator(); itr.hasNext();) { XSDSchemaContent content = (XSDSchemaContent) itr.next(); if (content instanceof XSDInclude) { XSDInclude include = (XSDInclude) content; if (!added.contains(include.getSchemaLocation())) { includes.add(include); added.add(include.getSchemaLocation()); if (include.getIncorporatedSchema() != null) { queue.addLast(include.getIncorporatedSchema()); } else { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("include: " + include + " resulted in null schema"); } } } } } } return includes; } /** * Searches <code>schema</code> for an element which matches <code>name</code>. * * @param schema The schema * @param name The element to search for * * @return The element declaration, or null if it could not be found. */ public static XSDElementDeclaration getElementDeclaration(XSDSchema schema, QName name) { for (Iterator e = schema.getElementDeclarations().iterator(); e.hasNext();) { XSDElementDeclaration element = (XSDElementDeclaration) e.next(); if (element.getTargetNamespace().equals(name.getNamespaceURI())) { if (element.getName().equals(name.getLocalPart())) { return element; } } } return null; } /** * Method for comparing the name of a schema component to a qualified name. * The component name and the qualified name match if both the namespaces * match, and the local parts match. Prefixes are ignored. Two strings will * match if one of the following conditions hold. * * <ul> * <li>Both strings are null. * <li>Both strings are the empty string. * <li>One string is null, and the other is the empty string. * <li>Both strings are non-null and non-empty and equals() return true. * </ul> * * @param component The component in question. * @param qName The qualifined name. * */ public static final boolean nameMatches(XSDNamedComponent component, QName qName) { String ns1 = component.getTargetNamespace(); String ns2 = qName.getNamespaceURI(); String n1 = component.getName(); String n2 = qName.getLocalPart(); ns1 = "".equals(ns1) ? null : ns1; ns2 = "".equals(ns2) ? null : ns2; n1 = "".equals(n1) ? null : n1; n2 = "".equals(n2) ? null : n2; if ((ns1 == null) && (ns2 != null)) { //try the default namespace if (component.getSchema() != null) { ns1 = component.getSchema().getTargetNamespace(); if ("".equals(ns1)) { ns1 = null; } } } return Utilities.equals(ns1, ns2) && Utilities.equals(n1, n2); // // //is this the element we are looking for // if ((component.getTargetNamespace() == null) // || "".equals(component.getTargetNamespace())) { // if ((qName.getNamespaceURI() == null) // || "".equals(qName.getNamespaceURI())) { // //do a local name match // String n1 = component.getName(); // if ( "".equals( n1 ) ) { // n1 = null; // } // String n2 = qName.getLocalPart(); // if ( "".equals( n2 ) ) { // n2 = null; // } // return (n1 == null && n2 == null) || n1.equals( n2 ); // } // // //assume default namespace // if (component.getSchema().getTargetNamespace() // .equals(qName.getNamespaceURI()) // && component.getName().equals(qName.getLocalPart())) { // return true; // } // } else if (component.getTargetNamespace().equals(qName.getNamespaceURI()) // && component.getName().equals(qName.getLocalPart())) { // return true; // } } /** * Returns the namespace prefix mapped to the targetNamespace of the schema. * * @param schema The schema in question. * * @return The namesapce prefix, or <code>null</code> if not found. */ public static String getTargetPrefix(XSDSchema schema) { String ns = schema.getTargetNamespace(); Map pre2ns = schema.getQNamePrefixToNamespaceMap(); for (Iterator itr = pre2ns.entrySet().iterator(); itr.hasNext();) { Map.Entry entry = (Map.Entry) itr.next(); if (entry.getKey() == null) { continue; //default prefix } if (entry.getValue().equals(ns)) { return (String) entry.getKey(); } } return null; } /** * Obtains all instances of a particular class from a container by navigating * up the container hierachy. * * @param container The container. * @param clazz The class. * * @return A list of all instances of <code>clazz</code>, or the empty list if none found. */ public static List getComponentInstancesOfType(PicoContainer container, Class clazz) { List instances = new ArrayList(); while (container != null) { List l = container.getComponentInstancesOfType(clazz); instances.addAll(l); container = container.getParent(); } return instances; } /** * Unregisters a component in the container and all parent containers. * * @param container The container. * @param key The key of the component. * */ public static void unregisterComponent(PicoContainer container, final Object key) { //go to the top of the hierachy while (container.getParent() != null) { container = container.getParent(); } container.accept(new PicoVisitor() { public Object traverse(Object node) { return null; } public void visitContainer(PicoContainer container) { if (container instanceof MutablePicoContainer) { ((MutablePicoContainer) container).unregisterComponent(key); } } public void visitComponentAdapter(ComponentAdapter adapter) { } public void visitParameter(Parameter parameter) { } }); } /** * Returns the name of the element represented by the particle as a QName. */ public static QName getParticleName(XSDParticle particle) { XSDElementDeclaration content = (XSDElementDeclaration) particle.getContent(); if ( content.isElementDeclarationReference() ) { content = content.getResolvedElementDeclaration(); } return new QName( content.getTargetNamespace(),content.getName() ); } /** * Simple visitor interface for visiting elements which are part of * complex types. This interface is private api because there is probably * a better way of visiting the contents of a type. * * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org * */ private static interface ElementVisitor { /** * The particle containing the element. * @param element */ void visit(XSDParticle element); } static class SchemaLocatorAdapterFactory extends AdapterFactoryImpl { SchemaLocatorAdapter adapter; public SchemaLocatorAdapterFactory(List /*<XSDSchemaLocator>*/ locators) { adapter = new SchemaLocatorAdapter(locators); } public boolean isFactoryForType(Object type) { return type == XSDSchemaLocator.class; } public Adapter adaptNew(Notifier notifier, Object type) { return adapter; } } static class SchemaLocatorAdapter extends AdapterImpl implements XSDSchemaLocator { List /*<XSDSchemaLocator>*/ locators; public SchemaLocatorAdapter(List /*<XSDSchemaLocator>*/ locators) { this.locators = new ArrayList(locators); } public boolean isAdapterForType(Object type) { return type == XSDSchemaLocator.class; } public XSDSchema locateSchema(XSDSchema xsdSchema, String namespaceURI, String rawSchemaLocationURI, String resolvedSchemaLocationURI) { for (int i = 0; i < locators.size(); i++) { XSDSchemaLocator locator = (XSDSchemaLocator) locators.get(i); XSDSchema schema = locator.locateSchema(xsdSchema, namespaceURI, rawSchemaLocationURI, resolvedSchemaLocationURI); if (schema != null) { return schema; } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Could not locate schema for: " + rawSchemaLocationURI + "."); } return null; } } static class SchemaLocationResolverAdapterFactory extends AdapterFactoryImpl { SchemaLocationResolverAdapter adapter; public SchemaLocationResolverAdapterFactory(List /*<XSDSchemaLocationResolver>*/ resolvers) { adapter = new SchemaLocationResolverAdapter(resolvers); } public boolean isFactoryForType(Object type) { return type == XSDSchemaLocationResolver.class; } public Adapter adaptNew(Notifier notifier, Object type) { return adapter; } } static class SchemaLocationResolverAdapter extends AdapterImpl implements XSDSchemaLocationResolver { List /*<XSDSchemaLocationResolver>*/ resolvers; public SchemaLocationResolverAdapter(List /*<XSDSchemaLocationResolver>*/ resolvers) { this.resolvers = new ArrayList(resolvers); } public boolean isAdapterForType(Object type) { return type == XSDSchemaLocationResolver.class; } public String resolveSchemaLocation(XSDSchema schema, String namespaceURI, String rawSchemaLocationURI) { for (int i = 0; i < resolvers.size(); i++) { XSDSchemaLocationResolver resolver = (XSDSchemaLocationResolver) resolvers.get(i); String resolved = resolver.resolveSchemaLocation(schema, namespaceURI, rawSchemaLocationURI); if (resolved != null) { return resolved; } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Could not resolve schema location: " + rawSchemaLocationURI + " to physical location."); } return null; } } }