| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.tuscany.sca.xsd.xml; |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.tuscany.sca.assembly.xsd.Constants; |
| import org.apache.tuscany.sca.common.xml.XMLDocumentHelper; |
| import org.apache.tuscany.sca.contribution.Artifact; |
| import org.apache.tuscany.sca.contribution.Contribution; |
| import org.apache.tuscany.sca.contribution.DefaultImport; |
| import org.apache.tuscany.sca.contribution.Import; |
| import org.apache.tuscany.sca.contribution.namespace.NamespaceImport; |
| import org.apache.tuscany.sca.contribution.processor.ContributionRuntimeException; |
| import org.apache.tuscany.sca.contribution.processor.ProcessorContext; |
| import org.apache.tuscany.sca.contribution.resolver.ModelResolver; |
| import org.apache.tuscany.sca.core.FactoryExtensionPoint; |
| import org.apache.tuscany.sca.xsd.DefaultXSDFactory; |
| import org.apache.tuscany.sca.xsd.XSDFactory; |
| import org.apache.tuscany.sca.xsd.XSDefinition; |
| import org.apache.tuscany.sca.xsd.impl.XSDefinitionImpl; |
| import org.apache.ws.commons.schema.XmlSchema; |
| import org.apache.ws.commons.schema.XmlSchemaCollection; |
| import org.apache.ws.commons.schema.XmlSchemaInclude; |
| import org.apache.ws.commons.schema.resolver.URIResolver; |
| import org.xml.sax.InputSource; |
| |
| /** |
| * A Model Resolver for XSD models. |
| * |
| * @version $Rev$ $Date$ |
| */ |
| public class XSDModelResolver implements ModelResolver { |
| private static final String AGGREGATED_XSD = "http://tuscany.apache.org/aggregated.xsd"; |
| private XSDFactory factory; |
| private Contribution contribution; |
| private Map<String, List<XSDefinition>> map = new HashMap<String, List<XSDefinition>>(); |
| private XmlSchemaCollection schemaCollection; |
| |
| public XSDModelResolver(Contribution contribution, FactoryExtensionPoint modelFactories) { |
| this.contribution = contribution; |
| this.schemaCollection = new XmlSchemaCollection(); |
| this.factory = new DefaultXSDFactory(); |
| } |
| |
| public void addModel(Object resolved, ProcessorContext context) { |
| XSDefinition definition = (XSDefinition)resolved; |
| List<XSDefinition> list = map.get(definition.getNamespace()); |
| if (list == null) { |
| list = new ArrayList<XSDefinition>(); |
| map.put(definition.getNamespace(), list); |
| } |
| list.add(definition); |
| } |
| |
| public Object removeModel(Object resolved, ProcessorContext context) { |
| XSDefinition definition = (XSDefinition)resolved; |
| List<XSDefinition> list = map.get(definition.getNamespace()); |
| if (list == null) { |
| return null; |
| } else { |
| return list.remove(definition); |
| } |
| } |
| |
| public <T> T resolveModel(Class<T> modelClass, T unresolved, ProcessorContext context) { |
| schemaCollection.setSchemaResolver(new URIResolverImpl(contribution, context)); |
| XSDefinition definition = (XSDefinition)unresolved; |
| String namespace = definition.getNamespace(); |
| XSDefinition resolved = null; |
| |
| // Lookup a definition for the given namespace, within the contribution |
| List<XSDefinition> list = map.get(namespace); |
| |
| if (list == null || |
| (list != null && list.size() == 0)){ |
| // if no schema is found locally delegate to other |
| // contributions via the imports |
| resolved = resolutionDelegation(namespace, context); |
| return modelClass.cast(resolved); |
| } |
| |
| XSDefinition modelXSD = null; |
| if (list != null && definition.getDocument() != null) { |
| // Set the document for the inline schema |
| int index = list.indexOf(definition); |
| if (index != -1) { // a matching (not identical) document was found |
| modelXSD = list.get(index); |
| modelXSD.setDocument(definition.getDocument()); |
| } |
| } |
| if (list == null && definition.getDocument() != null) { |
| // Hit for the 1st time |
| list = new ArrayList<XSDefinition>(); |
| list.add(definition); |
| map.put(namespace, list); |
| } |
| try { |
| resolved = aggregate(list); |
| } catch (IOException e) { |
| throw new ContributionRuntimeException(e); |
| } |
| if (resolved != null && !resolved.isUnresolved()) { |
| if (definition.isUnresolved() && definition.getSchema() == null && modelXSD != null) { |
| // Update the unresolved model with schema information and mark it |
| // resolved. This information in the unresolved model is needed when |
| // this method is called by WSDLModelResolver.readInlineSchemas(). |
| definition.setSchema(modelXSD.getSchema()); |
| definition.setSchemaCollection(modelXSD.getSchemaCollection()); |
| definition.setUnresolved(false); |
| } |
| return modelClass.cast(resolved); |
| } |
| |
| return modelClass.cast(unresolved); |
| } |
| |
| private void loadOnDemand(XSDefinition definition) throws IOException { |
| if (definition.getSchema() != null) { |
| return; |
| } |
| if (definition.getDocument() != null) { |
| String uri = null; |
| if (definition.getLocation() != null) { |
| uri = definition.getLocation().toString(); |
| } |
| XmlSchema schema = null; |
| try { |
| schema = schemaCollection.read(definition.getDocument(), uri, null); |
| } catch (RuntimeException e) { |
| // find original cause of the problem |
| Throwable cause = e; |
| while (cause.getCause() != null && cause != cause.getCause()) { |
| cause = cause.getCause(); |
| } |
| throw new ContributionRuntimeException(cause); |
| } |
| definition.setSchemaCollection(schemaCollection); |
| definition.setSchema(schema); |
| definition.setUnresolved(false); |
| } else if (definition.getLocation() != null) { |
| if (definition.getLocation().getFragment() != null) { |
| // It's an inline schema |
| return; |
| } |
| // Read an XSD document |
| XmlSchema schema = null; |
| for (XmlSchema d : schemaCollection.getXmlSchemas()) { |
| if (isSameNamespace(d.getTargetNamespace(), definition.getNamespace())) { |
| if (d.getSourceURI().equals(definition.getLocation().toString())) { |
| schema = d; |
| break; |
| } |
| } |
| } |
| if (schema == null) { |
| InputSource xsd = null; |
| try { |
| xsd = XMLDocumentHelper.getInputSource(definition.getLocation().toURL()); |
| } catch (IOException e) { |
| throw new ContributionRuntimeException(e); |
| } |
| |
| try { |
| schema = schemaCollection.read(xsd, null); |
| } catch (RuntimeException e) { |
| // find original cause of the problem |
| Throwable cause = e; |
| while (cause.getCause() != null && cause != cause.getCause()) { |
| cause = cause.getCause(); |
| } |
| throw new ContributionRuntimeException(cause); |
| } |
| } |
| definition.setSchemaCollection(schemaCollection); |
| definition.setSchema(schema); |
| } |
| } |
| |
| private boolean isSameNamespace(String ns1, String ns2) { |
| if (ns1 == null) { |
| return ns2 == null; |
| } else { |
| return ns1.equals(ns2); |
| } |
| } |
| /** |
| * Create a facade XmlSchema which includes all the definitions |
| * |
| * @param definitions A list of the XmlSchema under the same target |
| * namespace |
| * @return The aggregated XmlSchema |
| */ |
| private XSDefinition aggregate(List<XSDefinition> definitions) throws IOException { |
| if (definitions == null || definitions.size() == 0) { |
| return null; |
| } |
| if (definitions.size() == 1) { |
| XSDefinition d = definitions.get(0); |
| loadOnDemand(d); |
| return d; |
| } |
| XSDefinition aggregated = factory.createXSDefinition(); |
| for (XSDefinition d : definitions) { |
| loadOnDemand(d); |
| } |
| String ns = definitions.get(0).getNamespace(); |
| |
| XmlSchema facade = null; |
| // Check if the facade XSD is already in the collection |
| for (XmlSchema s : schemaCollection.getXmlSchema(AGGREGATED_XSD)) { |
| if (ns.equals(s.getTargetNamespace())) { |
| facade = s; |
| break; |
| } |
| } |
| if (facade == null) { |
| // This will add the facade into the collection |
| facade = new XmlSchema(ns, AGGREGATED_XSD, schemaCollection); |
| } |
| |
| for (XmlSchema d : schemaCollection.getXmlSchemas()) { |
| if (ns.equals(d.getTargetNamespace())) { |
| if (d == facade) { |
| continue; |
| } |
| XmlSchemaInclude include = new XmlSchemaInclude(); |
| include.setSchema(d); |
| include.setSourceURI(d.getSourceURI()); |
| include.setSchemaLocation(d.getSourceURI()); |
| facade.getIncludes().add(include); |
| facade.getItems().add(include); |
| } |
| } |
| aggregated.setUnresolved(true); |
| aggregated.setSchema(facade); |
| aggregated.setNamespace(ns); |
| aggregated.setAggregatedDefinitions(definitions); |
| aggregated.setUnresolved(false); |
| |
| // FIXME: [rfeng] This is hacky |
| //definitions.clear(); |
| //definitions.add(aggregated); |
| return aggregated; |
| } |
| |
| private XSDefinition resolutionDelegation(String namespace, ProcessorContext context){ |
| // Delegate the resolution to namespace imports |
| XSDefinition resolved = null; |
| XSDefinition unresolved = new XSDefinitionImpl(); |
| unresolved.setUnresolved(true); |
| unresolved.setNamespace(namespace); |
| |
| for (Import import_ : this.contribution.getImports()) { |
| if (import_ instanceof NamespaceImport) { |
| NamespaceImport namespaceImport = (NamespaceImport)import_; |
| if (namespaceImport.getNamespace().equals(namespace)) { |
| // Delegate the resolution to the namespace import resolver |
| resolved = |
| namespaceImport.getModelResolver().resolveModel(XSDefinition.class, (XSDefinition)unresolved, context); |
| if (!resolved.isUnresolved()) { |
| return resolved; |
| } |
| } |
| } else if (import_ instanceof DefaultImport) { |
| // Delegate the resolution to the default import resolver |
| resolved = |
| import_.getModelResolver().resolveModel(XSDefinition.class, (XSDefinition)unresolved, context); |
| if (!resolved.isUnresolved()) { |
| return resolved; |
| } |
| } |
| } |
| |
| return resolved; |
| } |
| |
| /** |
| * URI resolver implementation for XML schema |
| */ |
| public static class URIResolverImpl implements URIResolver { |
| private Contribution contribution; |
| private ProcessorContext context; |
| |
| public URIResolverImpl(Contribution contribution, ProcessorContext context) { |
| this.contribution = contribution; |
| this.context = context; |
| } |
| |
| public org.xml.sax.InputSource resolveEntity(java.lang.String targetNamespace, |
| java.lang.String schemaLocation, |
| java.lang.String baseUri) { |
| try { |
| if (schemaLocation == null) { |
| return null; |
| } |
| URL url = null; |
| |
| // Delegate the resolution to namespace imports |
| XSDefinition resolved = null; |
| XSDefinition unresolved = new XSDefinitionImpl(); |
| unresolved.setUnresolved(true); |
| unresolved.setLocation(new URI(schemaLocation)); |
| unresolved.setNamespace(targetNamespace); |
| |
| for (Import import_ : this.contribution.getImports()) { |
| if (import_ instanceof NamespaceImport) { |
| NamespaceImport namespaceImport = (NamespaceImport)import_; |
| if (namespaceImport.getNamespace().equals(targetNamespace)) { |
| // Delegate the resolution to the namespace import resolver |
| resolved = |
| namespaceImport.getModelResolver().resolveModel(XSDefinition.class, (XSDefinition)unresolved, context); |
| if (!resolved.isUnresolved()) { |
| return XMLDocumentHelper.getInputSource(resolved.getLocation().toURL()); |
| } |
| } |
| } else if (import_ instanceof DefaultImport) { |
| // Delegate the resolution to the default import resolver |
| resolved = |
| import_.getModelResolver().resolveModel(XSDefinition.class, (XSDefinition)unresolved, context); |
| if (!resolved.isUnresolved()) { |
| return XMLDocumentHelper.getInputSource(resolved.getLocation().toURL()); |
| } |
| } |
| } |
| |
| // Not found, lookup a definition for the given namespace |
| // within the current contribution. |
| if (schemaLocation.startsWith("/")) { |
| // The URI is relative to the contribution |
| String uri = schemaLocation.substring(1); |
| for (Artifact a : contribution.getArtifacts()) { |
| if (a.getURI().equals(uri)) { |
| url = new URL(a.getLocation()); |
| break; |
| } |
| } |
| if (url == null) { |
| // URI not found in the contribution; return a default InputSource |
| // so that the XmlSchema code will produce a useful diagnostic |
| return new InputSource(schemaLocation); |
| } |
| } else { |
| url = new URL(new URL(baseUri), schemaLocation); |
| String scheme = url.getProtocol(); |
| if ("file".equalsIgnoreCase(scheme) || "jar".equalsIgnoreCase(scheme) |
| || "zip".equalsIgnoreCase(scheme) |
| || "wsjar".equalsIgnoreCase(scheme)) { |
| // For local URLs, use as-is |
| } else { |
| // look to see whether Tuscany has a local version of the |
| // required schema. It can load the local version rather |
| // than going out to the network in order to improve performance |
| URL cached = Constants.CACHED_XSDS.get(targetNamespace); |
| if (cached != null) { |
| url = cached; |
| } |
| } |
| } |
| return XMLDocumentHelper.getInputSource(url); |
| } catch (IOException e) { |
| // Invalid URI; return a default InputSource so that the |
| // XmlSchema code will produce a useful diagnostic |
| return new InputSource(schemaLocation); |
| } catch (URISyntaxException e) { |
| // Invalid URI; return a default InputSource so that the |
| // XmlSchema code will produce a useful diagnostic |
| return new InputSource(schemaLocation); |
| } |
| } |
| } |
| |
| } |