1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.ws.util.pipe; 27 28 import com.sun.istack.internal.NotNull; 29 import com.sun.istack.internal.Nullable; 30 import com.sun.xml.internal.stream.buffer.XMLStreamBufferResult; 31 import com.sun.xml.internal.ws.api.WSBinding; 32 import com.sun.xml.internal.ws.api.message.Message; 33 import com.sun.xml.internal.ws.api.message.Packet; 34 import com.sun.xml.internal.ws.api.pipe.Tube; 35 import com.sun.xml.internal.ws.api.pipe.TubeCloner; 36 import com.sun.xml.internal.ws.api.pipe.helper.AbstractFilterTubeImpl; 37 import com.sun.xml.internal.ws.api.server.DocumentAddressResolver; 38 import com.sun.xml.internal.ws.api.server.SDDocument; 39 import com.sun.xml.internal.ws.api.server.SDDocumentSource; 40 import com.sun.xml.internal.ws.developer.SchemaValidationFeature; 41 import com.sun.xml.internal.ws.developer.ValidationErrorHandler; 42 import com.sun.xml.internal.ws.server.SDDocumentImpl; 43 import com.sun.xml.internal.ws.util.ByteArrayBuffer; 44 import com.sun.xml.internal.ws.util.xml.XmlUtil; 45 import com.sun.xml.internal.ws.wsdl.SDDocumentResolver; 46 import com.sun.xml.internal.ws.wsdl.parser.WSDLConstants; 47 import org.w3c.dom.*; 48 import org.w3c.dom.ls.LSInput; 49 import org.w3c.dom.ls.LSResourceResolver; 50 import org.xml.sax.SAXException; 51 import org.xml.sax.helpers.NamespaceSupport; 52 53 import javax.xml.XMLConstants; 54 import javax.xml.namespace.QName; 55 import javax.xml.transform.Source; 56 import javax.xml.transform.Transformer; 57 import javax.xml.transform.TransformerException; 58 import javax.xml.transform.dom.DOMResult; 59 import javax.xml.transform.dom.DOMSource; 60 import javax.xml.transform.stream.StreamSource; 61 import javax.xml.validation.SchemaFactory; 62 import javax.xml.validation.Validator; 63 import javax.xml.ws.WebServiceException; 64 import java.io.IOException; 65 import java.io.InputStream; 66 import java.io.Reader; 67 import java.io.StringReader; 68 import java.net.MalformedURLException; 69 import java.net.URI; 70 import java.net.URL; 71 import java.util.*; 72 import java.util.logging.Level; 73 import java.util.logging.Logger; 74 75 import static com.sun.xml.internal.ws.util.xml.XmlUtil.allowExternalAccess; 76 77 /** 78 * {@link Tube} that does the schema validation. 79 * 80 * @author Jitendra Kotamraju 81 */ 82 public abstract class AbstractSchemaValidationTube extends AbstractFilterTubeImpl { 83 84 private static final Logger LOGGER = Logger.getLogger(AbstractSchemaValidationTube.class.getName()); 85 86 protected final WSBinding binding; 87 protected final SchemaValidationFeature feature; 88 protected final DocumentAddressResolver resolver = new ValidationDocumentAddressResolver(); 89 protected final SchemaFactory sf; 90 91 public AbstractSchemaValidationTube(WSBinding binding, Tube next) { 92 super(next); 93 this.binding = binding; 94 feature = binding.getFeature(SchemaValidationFeature.class); 95 sf = allowExternalAccess(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI), "all", false); 96 } 97 98 protected AbstractSchemaValidationTube(AbstractSchemaValidationTube that, TubeCloner cloner) { 99 super(that, cloner); 100 this.binding = that.binding; 101 this.feature = that.feature; 102 this.sf = that.sf; 103 } 104 105 protected abstract Validator getValidator(); 106 107 protected abstract boolean isNoValidation(); 108 109 private static class ValidationDocumentAddressResolver implements DocumentAddressResolver { 110 111 @Nullable 112 @Override 113 public String getRelativeAddressFor(@NotNull SDDocument current, @NotNull SDDocument referenced) { 114 LOGGER.log(Level.FINE, "Current = {0} resolved relative={1}", new Object[]{current.getURL(), referenced.getURL()}); 115 return referenced.getURL().toExternalForm(); 116 } 117 } 118 119 private Document createDOM(SDDocument doc) { 120 // Get infoset 121 ByteArrayBuffer bab = new ByteArrayBuffer(); 122 try { 123 doc.writeTo(null, resolver, bab); 124 } catch (IOException ioe) { 125 throw new WebServiceException(ioe); 126 } 127 128 // Convert infoset to DOM 129 Transformer trans = XmlUtil.newTransformer(); 130 Source source = new StreamSource(bab.newInputStream(), null); //doc.getURL().toExternalForm()); 131 DOMResult result = new DOMResult(); 132 try { 133 trans.transform(source, result); 134 } catch(TransformerException te) { 135 throw new WebServiceException(te); 136 } 137 return (Document)result.getNode(); 138 } 139 140 protected class MetadataResolverImpl implements SDDocumentResolver, LSResourceResolver { 141 142 // systemID --> SDDocument 143 final Map<String, SDDocument> docs = new HashMap<String, SDDocument>(); 144 145 // targetnamespace --> SDDocument 146 final Map<String, SDDocument> nsMapping = new HashMap<String, SDDocument>(); 147 148 public MetadataResolverImpl() { 149 } 150 151 public MetadataResolverImpl(Iterable<SDDocument> it) { 152 for(SDDocument doc : it) { 153 if (doc.isSchema()) { 154 docs.put(doc.getURL().toExternalForm(), doc); 155 nsMapping.put(((SDDocument.Schema)doc).getTargetNamespace(), doc); 156 } 157 } 158 } 159 160 void addSchema(Source schema) { 161 assert schema.getSystemId() != null; 162 163 String systemId = schema.getSystemId(); 164 try { 165 XMLStreamBufferResult xsbr = XmlUtil.identityTransform(schema, new XMLStreamBufferResult()); 166 SDDocumentSource sds = SDDocumentSource.create(new URL(systemId), xsbr.getXMLStreamBuffer()); 167 SDDocument sdoc = SDDocumentImpl.create(sds, new QName(""), new QName("")); 168 docs.put(systemId, sdoc); 169 nsMapping.put(((SDDocument.Schema)sdoc).getTargetNamespace(), sdoc); 170 } catch(Exception ex) { 171 LOGGER.log(Level.WARNING, "Exception in adding schemas to resolver", ex); 172 } 173 } 174 175 void addSchemas(Collection<? extends Source> schemas) { 176 for(Source src : schemas) { 177 addSchema(src); 178 } 179 } 180 181 @Override 182 public SDDocument resolve(String systemId) { 183 SDDocument sdi = docs.get(systemId); 184 if (sdi == null) { 185 SDDocumentSource sds; 186 try { 187 sds = SDDocumentSource.create(new URL(systemId)); 188 } catch(MalformedURLException e) { 189 throw new WebServiceException(e); 190 } 191 sdi = SDDocumentImpl.create(sds, new QName(""), new QName("")); 192 docs.put(systemId, sdi); 193 } 194 return sdi; 195 } 196 197 @Override 198 public LSInput resolveResource(String type, String namespaceURI, String publicId, final String systemId, final String baseURI) { 199 if (LOGGER.isLoggable(Level.FINE)) { 200 LOGGER.log(Level.FINE, "type={0} namespaceURI={1} publicId={2} systemId={3} baseURI={4}", new Object[]{type, namespaceURI, publicId, systemId, baseURI}); 201 } 202 try { 203 final SDDocument doc; 204 if (systemId == null) { 205 doc = nsMapping.get(namespaceURI); 206 } else { 207 URI rel = (baseURI != null) 208 ? new URI(baseURI).resolve(systemId) 209 : new URI(systemId); 210 doc = docs.get(rel.toString()); 211 } 212 if (doc != null) { 213 return new LSInput() { 214 215 @Override 216 public Reader getCharacterStream() { 217 return null; 218 } 219 220 @Override 221 public void setCharacterStream(Reader characterStream) { 222 throw new UnsupportedOperationException(); 223 } 224 225 @Override 226 public InputStream getByteStream() { 227 ByteArrayBuffer bab = new ByteArrayBuffer(); 228 try { 229 doc.writeTo(null, resolver, bab); 230 } catch (IOException ioe) { 231 throw new WebServiceException(ioe); 232 } 233 return bab.newInputStream(); 234 } 235 236 @Override 237 public void setByteStream(InputStream byteStream) { 238 throw new UnsupportedOperationException(); 239 } 240 241 @Override 242 public String getStringData() { 243 return null; 244 } 245 246 @Override 247 public void setStringData(String stringData) { 248 throw new UnsupportedOperationException(); 249 } 250 251 @Override 252 public String getSystemId() { 253 return doc.getURL().toExternalForm(); 254 } 255 256 @Override 257 public void setSystemId(String systemId) { 258 throw new UnsupportedOperationException(); 259 } 260 261 @Override 262 public String getPublicId() { 263 return null; 264 } 265 266 @Override 267 public void setPublicId(String publicId) { 268 throw new UnsupportedOperationException(); 269 } 270 271 @Override 272 public String getBaseURI() { 273 return doc.getURL().toExternalForm(); 274 } 275 276 @Override 277 public void setBaseURI(String baseURI) { 278 throw new UnsupportedOperationException(); 279 } 280 281 @Override 282 public String getEncoding() { 283 return null; 284 } 285 286 @Override 287 public void setEncoding(String encoding) { 288 throw new UnsupportedOperationException(); 289 } 290 291 @Override 292 public boolean getCertifiedText() { 293 return false; 294 } 295 296 @Override 297 public void setCertifiedText(boolean certifiedText) { 298 throw new UnsupportedOperationException(); 299 } 300 }; 301 } 302 } catch(Exception e) { 303 LOGGER.log(Level.WARNING, "Exception in LSResourceResolver impl", e); 304 } 305 if (LOGGER.isLoggable(Level.FINE)) { 306 LOGGER.log(Level.FINE, "Don''t know about systemId={0} baseURI={1}", new Object[]{systemId, baseURI}); 307 } 308 return null; 309 } 310 311 } 312 313 private void updateMultiSchemaForTns(String tns, String systemId, Map<String, List<String>> schemas) { 314 List<String> docIdList = schemas.get(tns); 315 if (docIdList == null) { 316 docIdList = new ArrayList<String>(); 317 schemas.put(tns, docIdList); 318 } 319 docIdList.add(systemId); 320 } 321 322 /* 323 * Using the following algorithm described in the xerces discussion thread: 324 * 325 * "If you're synthesizing schema documents to glue together the ones in 326 * the WSDL then you may not even need to use "honour-all-schemaLocations". 327 * Create a schema document for each namespace with <xs:include>s 328 * (for each schema document in the WSDL with that target namespace) 329 * and then combine those together with <xs:import>s for each of those 330 * namespaces in a "master" schema document. 331 * 332 * That should work with any schema processor, not just those which 333 * honour multiple imports for the same namespace." 334 */ 335 protected Source[] getSchemaSources(Iterable<SDDocument> docs, MetadataResolverImpl mdresolver) { 336 // All schema fragments in WSDLs are put inlinedSchemas 337 // systemID --> DOMSource 338 Map<String, DOMSource> inlinedSchemas = new HashMap<String, DOMSource>(); 339 340 // Consolidates all the schemas(inlined and external) for a tns 341 // tns --> list of systemId 342 Map<String, List<String>> multiSchemaForTns = new HashMap<String, List<String>>(); 343 344 for(SDDocument sdoc: docs) { 345 if (sdoc.isWSDL()) { 346 Document dom = createDOM(sdoc); 347 // Get xsd:schema node from WSDL's DOM 348 addSchemaFragmentSource(dom, sdoc.getURL().toExternalForm(), inlinedSchemas); 349 } else if (sdoc.isSchema()) { 350 updateMultiSchemaForTns(((SDDocument.Schema)sdoc).getTargetNamespace(), sdoc.getURL().toExternalForm(), multiSchemaForTns); 351 } 352 } 353 if (LOGGER.isLoggable(Level.FINE)) { 354 LOGGER.log(Level.FINE, "WSDL inlined schema fragment documents(these are used to create a pseudo schema) = {0}", inlinedSchemas.keySet()); 355 } 356 for(DOMSource src: inlinedSchemas.values()) { 357 String tns = getTargetNamespace(src); 358 updateMultiSchemaForTns(tns, src.getSystemId(), multiSchemaForTns); 359 } 360 361 if (multiSchemaForTns.isEmpty()) { 362 return new Source[0]; // WSDL doesn't have any schema fragments 363 } else if (multiSchemaForTns.size() == 1 && multiSchemaForTns.values().iterator().next().size() == 1) { 364 // It must be a inlined schema, otherwise there would be at least two schemas 365 String systemId = multiSchemaForTns.values().iterator().next().get(0); 366 return new Source[] {inlinedSchemas.get(systemId)}; 367 } 368 369 // need to resolve these inlined schema fragments 370 mdresolver.addSchemas(inlinedSchemas.values()); 371 372 // If there are multiple schema fragments for the same tns, create a 373 // pseudo schema for that tns by using <xsd:include> of those. 374 // tns --> systemId of a pseudo schema document (consolidated for that tns) 375 Map<String, String> oneSchemaForTns = new HashMap<String, String>(); 376 int i = 0; 377 for(Map.Entry<String, List<String>> e: multiSchemaForTns.entrySet()) { 378 String systemId; 379 List<String> sameTnsSchemas = e.getValue(); 380 if (sameTnsSchemas.size() > 1) { 381 // SDDocumentSource should be changed to take String systemId 382 // String pseudoSystemId = "urn:x-jax-ws-include-"+i++; 383 systemId = "file:x-jax-ws-include-"+i++; 384 Source src = createSameTnsPseudoSchema(e.getKey(), sameTnsSchemas, systemId); 385 mdresolver.addSchema(src); 386 } else { 387 systemId = sameTnsSchemas.get(0); 388 } 389 oneSchemaForTns.put(e.getKey(), systemId); 390 } 391 392 // create a master pseudo schema with all the different tns 393 Source pseudoSchema = createMasterPseudoSchema(oneSchemaForTns); 394 return new Source[] { pseudoSchema }; 395 } 396 397 private @Nullable void addSchemaFragmentSource(Document doc, String systemId, Map<String, DOMSource> map) { 398 Element e = doc.getDocumentElement(); 399 assert e.getNamespaceURI().equals(WSDLConstants.NS_WSDL); 400 assert e.getLocalName().equals("definitions"); 401 402 NodeList typesList = e.getElementsByTagNameNS(WSDLConstants.NS_WSDL, "types"); 403 for(int i=0; i < typesList.getLength(); i++) { 404 NodeList schemaList = ((Element)typesList.item(i)).getElementsByTagNameNS(WSDLConstants.NS_XMLNS, "schema"); 405 for(int j=0; j < schemaList.getLength(); j++) { 406 Element elem = (Element)schemaList.item(j); 407 NamespaceSupport nss = new NamespaceSupport(); 408 // Doing this because transformer is not picking up inscope namespaces 409 // why doesn't transformer pickup the inscope namespaces ?? 410 buildNamespaceSupport(nss, elem); 411 patchDOMFragment(nss, elem); 412 String docId = systemId+"#schema"+j; 413 map.put(docId, new DOMSource(elem, docId)); 414 } 415 } 416 } 417 418 419 /* 420 * Recursively visit ancestors and build up {@link org.xml.sax.helpers.NamespaceSupport} object. 421 */ 422 private void buildNamespaceSupport(NamespaceSupport nss, Node node) { 423 if (node==null || node.getNodeType()!=Node.ELEMENT_NODE) { 424 return; 425 } 426 427 buildNamespaceSupport( nss, node.getParentNode() ); 428 429 nss.pushContext(); 430 NamedNodeMap atts = node.getAttributes(); 431 for( int i=0; i<atts.getLength(); i++ ) { 432 Attr a = (Attr)atts.item(i); 433 if( "xmlns".equals(a.getPrefix()) ) { 434 nss.declarePrefix( a.getLocalName(), a.getValue() ); 435 continue; 436 } 437 if( "xmlns".equals(a.getName()) ) { 438 nss.declarePrefix( "", a.getValue() ); 439 //continue; 440 } 441 } 442 } 443 444 /** 445 * Adds inscope namespaces as attributes to <xsd:schema> fragment nodes. 446 * 447 * @param nss namespace context info 448 * @param elem that is patched with inscope namespaces 449 */ 450 private @Nullable void patchDOMFragment(NamespaceSupport nss, Element elem) { 451 NamedNodeMap atts = elem.getAttributes(); 452 for( Enumeration en = nss.getPrefixes(); en.hasMoreElements(); ) { 453 String prefix = (String)en.nextElement(); 454 455 for( int i=0; i<atts.getLength(); i++ ) { 456 Attr a = (Attr)atts.item(i); 457 if (!"xmlns".equals(a.getPrefix()) || !a.getLocalName().equals(prefix)) { 458 if (LOGGER.isLoggable(Level.FINE)) { 459 LOGGER.log(Level.FINE, "Patching with xmlns:{0}={1}", new Object[]{prefix, nss.getURI(prefix)}); 460 } 461 elem.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:"+prefix, nss.getURI(prefix)); 462 } 463 } 464 } 465 } 466 467 /* 468 * Creates a pseudo schema for the WSDL schema fragments that have the same 469 * targetNamespace. 470 * 471 * <xsd:schema targetNamespace="X"> 472 * <xsd:include schemaLocation="Y1"/> 473 * <xsd:include schemaLocation="Y2"/> 474 * </xsd:schema> 475 * 476 * @param tns targetNamespace of the the schema documents 477 * @param docs collection of systemId for the schema documents that have the 478 * same tns, the collection must have more than one document 479 * @param psuedoSystemId for the created pseudo schema 480 * @return Source of pseudo schema that can be used multiple times 481 */ 482 private @Nullable Source createSameTnsPseudoSchema(String tns, Collection<String> docs, String pseudoSystemId) { 483 assert docs.size() > 1; 484 485 final StringBuilder sb = new StringBuilder("<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'"); 486 if (tns != null && !("".equals(tns)) && !("null".equals(tns))) { 487 sb.append(" targetNamespace='").append(tns).append("'"); 488 } 489 sb.append(">\n"); 490 for(String systemId : docs) { 491 sb.append("<xsd:include schemaLocation='").append(systemId).append("'/>\n"); 492 } 493 sb.append("</xsd:schema>\n"); 494 if (LOGGER.isLoggable(Level.FINE)) { 495 LOGGER.log(Level.FINE, "Pseudo Schema for the same tns={0}is {1}", new Object[]{tns, sb}); 496 } 497 498 // override getReader() so that the same source can be used multiple times 499 return new StreamSource(pseudoSystemId) { 500 @Override 501 public Reader getReader() { 502 return new StringReader(sb.toString()); 503 } 504 }; 505 } 506 507 /* 508 * Creates a master pseudo schema importing all WSDL schema fragments with 509 * different tns+pseudo schema for same tns. 510 * <xsd:schema targetNamespace="urn:x-jax-ws-master"> 511 * <xsd:import schemaLocation="Y1" namespace="X1"/> 512 * <xsd:import schemaLocation="Y2" namespace="X2"/> 513 * </xsd:schema> 514 * 515 * @param pseudo a map(tns-->systemId) of schema documents 516 * @return Source of pseudo schema that can be used multiple times 517 */ 518 private Source createMasterPseudoSchema(Map<String, String> docs) { 519 final StringBuilder sb = new StringBuilder("<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema' targetNamespace='urn:x-jax-ws-master'>\n"); 520 for(Map.Entry<String, String> e : docs.entrySet()) { 521 String systemId = e.getValue(); 522 String ns = e.getKey(); 523 sb.append("<xsd:import schemaLocation='").append(systemId).append("'"); 524 if (ns != null && !("".equals(ns))) { 525 sb.append(" namespace='").append(ns).append("'"); 526 } 527 sb.append("/>\n"); 528 } 529 sb.append("</xsd:schema>"); 530 if (LOGGER.isLoggable(Level.FINE)) { 531 LOGGER.log(Level.FINE, "Master Pseudo Schema = {0}", sb); 532 } 533 534 // override getReader() so that the same source can be used multiple times 535 return new StreamSource("file:x-jax-ws-master-doc") { 536 @Override 537 public Reader getReader() { 538 return new StringReader(sb.toString()); 539 } 540 }; 541 } 542 543 protected void doProcess(Packet packet) throws SAXException { 544 getValidator().reset(); 545 Class<? extends ValidationErrorHandler> handlerClass = feature.getErrorHandler(); 546 ValidationErrorHandler handler; 547 try { 548 handler = handlerClass.newInstance(); 549 } catch(Exception e) { 550 throw new WebServiceException(e); 551 } 552 handler.setPacket(packet); 553 getValidator().setErrorHandler(handler); 554 Message msg = packet.getMessage().copy(); 555 Source source = msg.readPayloadAsSource(); 556 try { 557 // Validator javadoc allows ONLY SAX, and DOM Sources 558 // But the impl seems to handle all kinds. 559 getValidator().validate(source); 560 } catch(IOException e) { 561 throw new WebServiceException(e); 562 } 563 } 564 565 private String getTargetNamespace(DOMSource src) { 566 Element elem = (Element)src.getNode(); 567 return elem.getAttribute("targetNamespace"); 568 } 569 570 // protected static void printSource(Source src) { 571 // try { 572 // ByteArrayBuffer bos = new ByteArrayBuffer(); 573 // StreamResult sr = new StreamResult(bos ); 574 // Transformer trans = TransformerFactory.newInstance().newTransformer(); 575 // trans.transform(src, sr); 576 // LOGGER.info("**** src ******"+bos.toString()); 577 // bos.close(); 578 // } catch(Exception e) { 579 // e.printStackTrace(); 580 // } 581 // } 582 583 }