ohair@286: /* mkos@397: * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. ohair@286: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. ohair@286: * ohair@286: * This code is free software; you can redistribute it and/or modify it ohair@286: * under the terms of the GNU General Public License version 2 only, as ohair@286: * published by the Free Software Foundation. Oracle designates this ohair@286: * particular file as subject to the "Classpath" exception as provided ohair@286: * by Oracle in the LICENSE file that accompanied this code. ohair@286: * ohair@286: * This code is distributed in the hope that it will be useful, but WITHOUT ohair@286: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ohair@286: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ohair@286: * version 2 for more details (a copy is included in the LICENSE file that ohair@286: * accompanied this code). ohair@286: * ohair@286: * You should have received a copy of the GNU General Public License version ohair@286: * 2 along with this work; if not, write to the Free Software Foundation, ohair@286: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. ohair@286: * ohair@286: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@286: * or visit www.oracle.com if you need additional information or have any ohair@286: * questions. ohair@286: */ ohair@286: ohair@286: package com.sun.xml.internal.ws.util.pipe; ohair@286: ohair@286: import com.sun.istack.internal.NotNull; ohair@286: import com.sun.istack.internal.Nullable; ohair@286: import com.sun.xml.internal.stream.buffer.XMLStreamBufferResult; ohair@286: import com.sun.xml.internal.ws.api.WSBinding; ohair@286: import com.sun.xml.internal.ws.api.message.Message; ohair@286: import com.sun.xml.internal.ws.api.message.Packet; ohair@286: import com.sun.xml.internal.ws.api.pipe.Tube; ohair@286: import com.sun.xml.internal.ws.api.pipe.TubeCloner; ohair@286: import com.sun.xml.internal.ws.api.pipe.helper.AbstractFilterTubeImpl; ohair@286: import com.sun.xml.internal.ws.api.server.DocumentAddressResolver; ohair@286: import com.sun.xml.internal.ws.api.server.SDDocument; ohair@286: import com.sun.xml.internal.ws.api.server.SDDocumentSource; ohair@286: import com.sun.xml.internal.ws.developer.SchemaValidationFeature; ohair@286: import com.sun.xml.internal.ws.developer.ValidationErrorHandler; ohair@286: import com.sun.xml.internal.ws.server.SDDocumentImpl; ohair@286: import com.sun.xml.internal.ws.util.ByteArrayBuffer; ohair@286: import com.sun.xml.internal.ws.util.xml.XmlUtil; ohair@286: import com.sun.xml.internal.ws.wsdl.SDDocumentResolver; ohair@286: import com.sun.xml.internal.ws.wsdl.parser.WSDLConstants; ohair@286: import org.w3c.dom.*; ohair@286: import org.w3c.dom.ls.LSInput; ohair@286: import org.w3c.dom.ls.LSResourceResolver; ohair@286: import org.xml.sax.SAXException; ohair@286: import org.xml.sax.helpers.NamespaceSupport; ohair@286: ohair@286: import javax.xml.XMLConstants; ohair@286: import javax.xml.namespace.QName; ohair@286: import javax.xml.transform.Source; ohair@286: import javax.xml.transform.Transformer; ohair@286: import javax.xml.transform.TransformerException; ohair@286: import javax.xml.transform.dom.DOMResult; ohair@286: import javax.xml.transform.dom.DOMSource; ohair@286: import javax.xml.transform.stream.StreamSource; ohair@286: import javax.xml.validation.SchemaFactory; ohair@286: import javax.xml.validation.Validator; ohair@286: import javax.xml.ws.WebServiceException; ohair@286: import java.io.IOException; ohair@286: import java.io.InputStream; ohair@286: import java.io.Reader; ohair@286: import java.io.StringReader; ohair@286: import java.net.MalformedURLException; ohair@286: import java.net.URI; ohair@286: import java.net.URL; ohair@286: import java.util.*; ohair@286: import java.util.logging.Level; ohair@286: import java.util.logging.Logger; ohair@286: mkos@408: import static com.sun.xml.internal.ws.util.xml.XmlUtil.allowExternalAccess; mkos@397: ohair@286: /** ohair@286: * {@link Tube} that does the schema validation. ohair@286: * ohair@286: * @author Jitendra Kotamraju ohair@286: */ ohair@286: public abstract class AbstractSchemaValidationTube extends AbstractFilterTubeImpl { ohair@286: ohair@286: private static final Logger LOGGER = Logger.getLogger(AbstractSchemaValidationTube.class.getName()); ohair@286: ohair@286: protected final WSBinding binding; ohair@286: protected final SchemaValidationFeature feature; ohair@286: protected final DocumentAddressResolver resolver = new ValidationDocumentAddressResolver(); ohair@286: protected final SchemaFactory sf; ohair@286: ohair@286: public AbstractSchemaValidationTube(WSBinding binding, Tube next) { ohair@286: super(next); ohair@286: this.binding = binding; ohair@286: feature = binding.getFeature(SchemaValidationFeature.class); mkos@408: sf = allowExternalAccess(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI), "file", false); ohair@286: } ohair@286: ohair@286: protected AbstractSchemaValidationTube(AbstractSchemaValidationTube that, TubeCloner cloner) { ohair@286: super(that, cloner); ohair@286: this.binding = that.binding; ohair@286: this.feature = that.feature; ohair@286: this.sf = that.sf; ohair@286: } ohair@286: ohair@286: protected abstract Validator getValidator(); ohair@286: ohair@286: protected abstract boolean isNoValidation(); ohair@286: ohair@286: private static class ValidationDocumentAddressResolver implements DocumentAddressResolver { ohair@286: ohair@286: @Nullable alanb@368: @Override ohair@286: public String getRelativeAddressFor(@NotNull SDDocument current, @NotNull SDDocument referenced) { alanb@368: LOGGER.log(Level.FINE, "Current = {0} resolved relative={1}", new Object[]{current.getURL(), referenced.getURL()}); ohair@286: return referenced.getURL().toExternalForm(); ohair@286: } ohair@286: } ohair@286: ohair@286: private Document createDOM(SDDocument doc) { ohair@286: // Get infoset ohair@286: ByteArrayBuffer bab = new ByteArrayBuffer(); ohair@286: try { ohair@286: doc.writeTo(null, resolver, bab); ohair@286: } catch (IOException ioe) { ohair@286: throw new WebServiceException(ioe); ohair@286: } ohair@286: ohair@286: // Convert infoset to DOM ohair@286: Transformer trans = XmlUtil.newTransformer(); ohair@286: Source source = new StreamSource(bab.newInputStream(), null); //doc.getURL().toExternalForm()); ohair@286: DOMResult result = new DOMResult(); ohair@286: try { ohair@286: trans.transform(source, result); ohair@286: } catch(TransformerException te) { ohair@286: throw new WebServiceException(te); ohair@286: } ohair@286: return (Document)result.getNode(); ohair@286: } ohair@286: ohair@286: protected class MetadataResolverImpl implements SDDocumentResolver, LSResourceResolver { ohair@286: ohair@286: // systemID --> SDDocument ohair@286: final Map docs = new HashMap(); ohair@286: ohair@286: // targetnamespace --> SDDocument ohair@286: final Map nsMapping = new HashMap(); ohair@286: ohair@286: public MetadataResolverImpl() { ohair@286: } ohair@286: ohair@286: public MetadataResolverImpl(Iterable it) { ohair@286: for(SDDocument doc : it) { ohair@286: if (doc.isSchema()) { ohair@286: docs.put(doc.getURL().toExternalForm(), doc); ohair@286: nsMapping.put(((SDDocument.Schema)doc).getTargetNamespace(), doc); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: void addSchema(Source schema) { ohair@286: assert schema.getSystemId() != null; ohair@286: ohair@286: String systemId = schema.getSystemId(); ohair@286: try { ohair@286: XMLStreamBufferResult xsbr = XmlUtil.identityTransform(schema, new XMLStreamBufferResult()); ohair@286: SDDocumentSource sds = SDDocumentSource.create(new URL(systemId), xsbr.getXMLStreamBuffer()); ohair@286: SDDocument sdoc = SDDocumentImpl.create(sds, new QName(""), new QName("")); ohair@286: docs.put(systemId, sdoc); ohair@286: nsMapping.put(((SDDocument.Schema)sdoc).getTargetNamespace(), sdoc); ohair@286: } catch(Exception ex) { ohair@286: LOGGER.log(Level.WARNING, "Exception in adding schemas to resolver", ex); ohair@286: } ohair@286: } ohair@286: ohair@286: void addSchemas(Collection schemas) { ohair@286: for(Source src : schemas) { ohair@286: addSchema(src); ohair@286: } ohair@286: } ohair@286: alanb@368: @Override ohair@286: public SDDocument resolve(String systemId) { ohair@286: SDDocument sdi = docs.get(systemId); ohair@286: if (sdi == null) { ohair@286: SDDocumentSource sds; ohair@286: try { ohair@286: sds = SDDocumentSource.create(new URL(systemId)); ohair@286: } catch(MalformedURLException e) { ohair@286: throw new WebServiceException(e); ohair@286: } ohair@286: sdi = SDDocumentImpl.create(sds, new QName(""), new QName("")); ohair@286: docs.put(systemId, sdi); ohair@286: } ohair@286: return sdi; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public LSInput resolveResource(String type, String namespaceURI, String publicId, final String systemId, final String baseURI) { alanb@368: if (LOGGER.isLoggable(Level.FINE)) { alanb@368: LOGGER.log(Level.FINE, "type={0} namespaceURI={1} publicId={2} systemId={3} baseURI={4}", new Object[]{type, namespaceURI, publicId, systemId, baseURI}); alanb@368: } ohair@286: try { ohair@286: final SDDocument doc; ohair@286: if (systemId == null) { ohair@286: doc = nsMapping.get(namespaceURI); ohair@286: } else { ohair@286: URI rel = (baseURI != null) ohair@286: ? new URI(baseURI).resolve(systemId) ohair@286: : new URI(systemId); ohair@286: doc = docs.get(rel.toString()); ohair@286: } ohair@286: if (doc != null) { ohair@286: return new LSInput() { ohair@286: alanb@368: @Override ohair@286: public Reader getCharacterStream() { ohair@286: return null; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setCharacterStream(Reader characterStream) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public InputStream getByteStream() { ohair@286: ByteArrayBuffer bab = new ByteArrayBuffer(); ohair@286: try { ohair@286: doc.writeTo(null, resolver, bab); ohair@286: } catch (IOException ioe) { ohair@286: throw new WebServiceException(ioe); ohair@286: } ohair@286: return bab.newInputStream(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setByteStream(InputStream byteStream) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getStringData() { ohair@286: return null; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setStringData(String stringData) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getSystemId() { ohair@286: return doc.getURL().toExternalForm(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setSystemId(String systemId) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getPublicId() { ohair@286: return null; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setPublicId(String publicId) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getBaseURI() { ohair@286: return doc.getURL().toExternalForm(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setBaseURI(String baseURI) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getEncoding() { ohair@286: return null; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setEncoding(String encoding) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public boolean getCertifiedText() { ohair@286: return false; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void setCertifiedText(boolean certifiedText) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: }; ohair@286: } ohair@286: } catch(Exception e) { ohair@286: LOGGER.log(Level.WARNING, "Exception in LSResourceResolver impl", e); ohair@286: } alanb@368: if (LOGGER.isLoggable(Level.FINE)) { alanb@368: LOGGER.log(Level.FINE, "Don''t know about systemId={0} baseURI={1}", new Object[]{systemId, baseURI}); alanb@368: } ohair@286: return null; ohair@286: } ohair@286: ohair@286: } ohair@286: ohair@286: private void updateMultiSchemaForTns(String tns, String systemId, Map> schemas) { ohair@286: List docIdList = schemas.get(tns); ohair@286: if (docIdList == null) { ohair@286: docIdList = new ArrayList(); ohair@286: schemas.put(tns, docIdList); ohair@286: } ohair@286: docIdList.add(systemId); ohair@286: } ohair@286: ohair@286: /* ohair@286: * Using the following algorithm described in the xerces discussion thread: ohair@286: * ohair@286: * "If you're synthesizing schema documents to glue together the ones in ohair@286: * the WSDL then you may not even need to use "honour-all-schemaLocations". ohair@286: * Create a schema document for each namespace with s ohair@286: * (for each schema document in the WSDL with that target namespace) ohair@286: * and then combine those together with s for each of those ohair@286: * namespaces in a "master" schema document. ohair@286: * ohair@286: * That should work with any schema processor, not just those which ohair@286: * honour multiple imports for the same namespace." ohair@286: */ ohair@286: protected Source[] getSchemaSources(Iterable docs, MetadataResolverImpl mdresolver) { ohair@286: // All schema fragments in WSDLs are put inlinedSchemas ohair@286: // systemID --> DOMSource ohair@286: Map inlinedSchemas = new HashMap(); ohair@286: ohair@286: // Consolidates all the schemas(inlined and external) for a tns ohair@286: // tns --> list of systemId ohair@286: Map> multiSchemaForTns = new HashMap>(); ohair@286: ohair@286: for(SDDocument sdoc: docs) { ohair@286: if (sdoc.isWSDL()) { ohair@286: Document dom = createDOM(sdoc); ohair@286: // Get xsd:schema node from WSDL's DOM ohair@286: addSchemaFragmentSource(dom, sdoc.getURL().toExternalForm(), inlinedSchemas); ohair@286: } else if (sdoc.isSchema()) { ohair@286: updateMultiSchemaForTns(((SDDocument.Schema)sdoc).getTargetNamespace(), sdoc.getURL().toExternalForm(), multiSchemaForTns); ohair@286: } ohair@286: } alanb@368: if (LOGGER.isLoggable(Level.FINE)) { alanb@368: LOGGER.log(Level.FINE, "WSDL inlined schema fragment documents(these are used to create a pseudo schema) = {0}", inlinedSchemas.keySet()); alanb@368: } ohair@286: for(DOMSource src: inlinedSchemas.values()) { ohair@286: String tns = getTargetNamespace(src); ohair@286: updateMultiSchemaForTns(tns, src.getSystemId(), multiSchemaForTns); ohair@286: } ohair@286: ohair@286: if (multiSchemaForTns.isEmpty()) { ohair@286: return new Source[0]; // WSDL doesn't have any schema fragments ohair@286: } else if (multiSchemaForTns.size() == 1 && multiSchemaForTns.values().iterator().next().size() == 1) { ohair@286: // It must be a inlined schema, otherwise there would be at least two schemas ohair@286: String systemId = multiSchemaForTns.values().iterator().next().get(0); ohair@286: return new Source[] {inlinedSchemas.get(systemId)}; ohair@286: } ohair@286: ohair@286: // need to resolve these inlined schema fragments ohair@286: mdresolver.addSchemas(inlinedSchemas.values()); ohair@286: ohair@286: // If there are multiple schema fragments for the same tns, create a ohair@286: // pseudo schema for that tns by using of those. ohair@286: // tns --> systemId of a pseudo schema document (consolidated for that tns) ohair@286: Map oneSchemaForTns = new HashMap(); ohair@286: int i = 0; ohair@286: for(Map.Entry> e: multiSchemaForTns.entrySet()) { ohair@286: String systemId; ohair@286: List sameTnsSchemas = e.getValue(); ohair@286: if (sameTnsSchemas.size() > 1) { ohair@286: // SDDocumentSource should be changed to take String systemId ohair@286: // String pseudoSystemId = "urn:x-jax-ws-include-"+i++; ohair@286: systemId = "file:x-jax-ws-include-"+i++; ohair@286: Source src = createSameTnsPseudoSchema(e.getKey(), sameTnsSchemas, systemId); ohair@286: mdresolver.addSchema(src); ohair@286: } else { ohair@286: systemId = sameTnsSchemas.get(0); ohair@286: } ohair@286: oneSchemaForTns.put(e.getKey(), systemId); ohair@286: } ohair@286: ohair@286: // create a master pseudo schema with all the different tns ohair@286: Source pseudoSchema = createMasterPseudoSchema(oneSchemaForTns); ohair@286: return new Source[] { pseudoSchema }; ohair@286: } ohair@286: ohair@286: private @Nullable void addSchemaFragmentSource(Document doc, String systemId, Map map) { ohair@286: Element e = doc.getDocumentElement(); ohair@286: assert e.getNamespaceURI().equals(WSDLConstants.NS_WSDL); ohair@286: assert e.getLocalName().equals("definitions"); ohair@286: ohair@286: NodeList typesList = e.getElementsByTagNameNS(WSDLConstants.NS_WSDL, "types"); ohair@286: for(int i=0; i < typesList.getLength(); i++) { ohair@286: NodeList schemaList = ((Element)typesList.item(i)).getElementsByTagNameNS(WSDLConstants.NS_XMLNS, "schema"); ohair@286: for(int j=0; j < schemaList.getLength(); j++) { ohair@286: Element elem = (Element)schemaList.item(j); ohair@286: NamespaceSupport nss = new NamespaceSupport(); ohair@286: // Doing this because transformer is not picking up inscope namespaces ohair@286: // why doesn't transformer pickup the inscope namespaces ?? ohair@286: buildNamespaceSupport(nss, elem); ohair@286: patchDOMFragment(nss, elem); ohair@286: String docId = systemId+"#schema"+j; ohair@286: map.put(docId, new DOMSource(elem, docId)); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: ohair@286: /* ohair@286: * Recursively visit ancestors and build up {@link org.xml.sax.helpers.NamespaceSupport} object. ohair@286: */ ohair@286: private void buildNamespaceSupport(NamespaceSupport nss, Node node) { alanb@368: if (node==null || node.getNodeType()!=Node.ELEMENT_NODE) { ohair@286: return; alanb@368: } ohair@286: ohair@286: buildNamespaceSupport( nss, node.getParentNode() ); ohair@286: ohair@286: nss.pushContext(); ohair@286: NamedNodeMap atts = node.getAttributes(); ohair@286: for( int i=0; i fragment nodes. ohair@286: * ohair@286: * @param nss namespace context info ohair@286: * @param elem that is patched with inscope namespaces ohair@286: */ ohair@286: private @Nullable void patchDOMFragment(NamespaceSupport nss, Element elem) { ohair@286: NamedNodeMap atts = elem.getAttributes(); ohair@286: for( Enumeration en = nss.getPrefixes(); en.hasMoreElements(); ) { ohair@286: String prefix = (String)en.nextElement(); ohair@286: ohair@286: for( int i=0; i ohair@286: * ohair@286: * ohair@286: * ohair@286: * ohair@286: * @param tns targetNamespace of the the schema documents ohair@286: * @param docs collection of systemId for the schema documents that have the ohair@286: * same tns, the collection must have more than one document ohair@286: * @param psuedoSystemId for the created pseudo schema ohair@286: * @return Source of pseudo schema that can be used multiple times ohair@286: */ ohair@286: private @Nullable Source createSameTnsPseudoSchema(String tns, Collection docs, String pseudoSystemId) { ohair@286: assert docs.size() > 1; ohair@286: ohair@286: final StringBuilder sb = new StringBuilder("\n"); ohair@286: for(String systemId : docs) { ohair@286: sb.append("\n"); ohair@286: } ohair@286: sb.append("\n"); alanb@368: if (LOGGER.isLoggable(Level.FINE)) { alanb@368: LOGGER.log(Level.FINE, "Pseudo Schema for the same tns={0}is {1}", new Object[]{tns, sb}); alanb@368: } ohair@286: ohair@286: // override getReader() so that the same source can be used multiple times ohair@286: return new StreamSource(pseudoSystemId) { ohair@286: @Override ohair@286: public Reader getReader() { ohair@286: return new StringReader(sb.toString()); ohair@286: } ohair@286: }; ohair@286: } ohair@286: ohair@286: /* ohair@286: * Creates a master pseudo schema importing all WSDL schema fragments with ohair@286: * different tns+pseudo schema for same tns. ohair@286: * ohair@286: * ohair@286: * ohair@286: * ohair@286: * ohair@286: * @param pseudo a map(tns-->systemId) of schema documents ohair@286: * @return Source of pseudo schema that can be used multiple times ohair@286: */ ohair@286: private Source createMasterPseudoSchema(Map docs) { ohair@286: final StringBuilder sb = new StringBuilder("\n"); ohair@286: for(Map.Entry e : docs.entrySet()) { ohair@286: String systemId = e.getValue(); ohair@286: String ns = e.getKey(); ohair@286: sb.append("\n"); ohair@286: } ohair@286: sb.append(""); alanb@368: if (LOGGER.isLoggable(Level.FINE)) { alanb@368: LOGGER.log(Level.FINE, "Master Pseudo Schema = {0}", sb); alanb@368: } ohair@286: ohair@286: // override getReader() so that the same source can be used multiple times ohair@286: return new StreamSource("file:x-jax-ws-master-doc") { ohair@286: @Override ohair@286: public Reader getReader() { ohair@286: return new StringReader(sb.toString()); ohair@286: } ohair@286: }; ohair@286: } ohair@286: ohair@286: protected void doProcess(Packet packet) throws SAXException { ohair@286: getValidator().reset(); ohair@286: Class handlerClass = feature.getErrorHandler(); ohair@286: ValidationErrorHandler handler; ohair@286: try { ohair@286: handler = handlerClass.newInstance(); ohair@286: } catch(Exception e) { ohair@286: throw new WebServiceException(e); ohair@286: } ohair@286: handler.setPacket(packet); ohair@286: getValidator().setErrorHandler(handler); ohair@286: Message msg = packet.getMessage().copy(); ohair@286: Source source = msg.readPayloadAsSource(); ohair@286: try { ohair@286: // Validator javadoc allows ONLY SAX, and DOM Sources ohair@286: // But the impl seems to handle all kinds. ohair@286: getValidator().validate(source); ohair@286: } catch(IOException e) { ohair@286: throw new WebServiceException(e); ohair@286: } ohair@286: } ohair@286: ohair@286: private String getTargetNamespace(DOMSource src) { ohair@286: Element elem = (Element)src.getNode(); ohair@286: return elem.getAttribute("targetNamespace"); ohair@286: } ohair@286: ohair@286: // protected static void printSource(Source src) { ohair@286: // try { ohair@286: // ByteArrayBuffer bos = new ByteArrayBuffer(); ohair@286: // StreamResult sr = new StreamResult(bos ); ohair@286: // Transformer trans = TransformerFactory.newInstance().newTransformer(); ohair@286: // trans.transform(src, sr); ohair@286: // LOGGER.info("**** src ******"+bos.toString()); ohair@286: // bos.close(); ohair@286: // } catch(Exception e) { ohair@286: // e.printStackTrace(); ohair@286: // } ohair@286: // } ohair@286: ohair@286: }