1/* 2 * Copyright (c) 1997, 2013, 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 26package com.sun.xml.internal.ws.addressing; 27 28import com.sun.istack.internal.NotNull; 29import com.sun.xml.internal.ws.addressing.model.InvalidAddressingHeaderException; 30import com.sun.xml.internal.ws.addressing.model.MissingAddressingHeaderException; 31import com.sun.xml.internal.ws.api.SOAPVersion; 32import com.sun.xml.internal.ws.api.WSBinding; 33import com.sun.xml.internal.ws.api.addressing.AddressingVersion; 34import com.sun.xml.internal.ws.api.message.AddressingUtils; 35import com.sun.xml.internal.ws.api.message.Header; 36import com.sun.xml.internal.ws.api.message.Message; 37import com.sun.xml.internal.ws.api.message.Messages; 38import com.sun.xml.internal.ws.api.message.Packet; 39import com.sun.xml.internal.ws.api.model.wsdl.WSDLBoundOperation; 40import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort; 41import com.sun.xml.internal.ws.api.pipe.NextAction; 42import com.sun.xml.internal.ws.api.pipe.Tube; 43import com.sun.xml.internal.ws.api.pipe.TubeCloner; 44import com.sun.xml.internal.ws.api.pipe.helper.AbstractFilterTubeImpl; 45import com.sun.xml.internal.ws.developer.MemberSubmissionAddressingFeature; 46import com.sun.xml.internal.ws.message.FaultDetailHeader; 47import com.sun.xml.internal.ws.resources.AddressingMessages; 48 49import javax.xml.namespace.QName; 50import javax.xml.soap.SOAPFault; 51import javax.xml.stream.XMLStreamException; 52import javax.xml.ws.WebServiceException; 53import javax.xml.ws.soap.AddressingFeature; 54import javax.xml.ws.soap.SOAPBinding; 55import java.util.Iterator; 56import java.util.logging.Logger; 57import java.util.logging.Level; 58 59/** 60 * WS-Addressing processing code shared between client and server. 61 * 62 * <p> 63 * This tube is used only when WS-Addressing is enabled. 64 * 65 * @author Rama Pulavarthi 66 * @author Arun Gupta 67 */ 68abstract class WsaTube extends AbstractFilterTubeImpl { 69 /** 70 * Port that we are processing. 71 */ 72 protected final @NotNull WSDLPort wsdlPort; 73 protected final WSBinding binding; 74 final WsaTubeHelper helper; 75 protected final @NotNull AddressingVersion addressingVersion; 76 protected final SOAPVersion soapVersion; 77 78 /** 79 * True if the addressing headers are mandatory. 80 */ 81 private final boolean addressingRequired; 82 83 public WsaTube(WSDLPort wsdlPort, WSBinding binding, Tube next) { 84 super(next); 85 this.wsdlPort = wsdlPort; 86 this.binding = binding; 87 addKnownHeadersToBinding(binding); 88 addressingVersion = binding.getAddressingVersion(); 89 soapVersion = binding.getSOAPVersion(); 90 helper = getTubeHelper(); 91 addressingRequired = AddressingVersion.isRequired(binding); 92 } 93 94 public WsaTube(WsaTube that, TubeCloner cloner) { 95 super(that, cloner); 96 this.wsdlPort = that.wsdlPort; 97 this.binding = that.binding; 98 this.helper = that.helper; 99 addressingVersion = that.addressingVersion; 100 soapVersion = that.soapVersion; 101 addressingRequired = that.addressingRequired; 102 } 103 104 private void addKnownHeadersToBinding(WSBinding binding) { 105 for (AddressingVersion addrVersion: AddressingVersion.values()) { 106 binding.addKnownHeader(addrVersion.actionTag); 107 binding.addKnownHeader(addrVersion.faultDetailTag); 108 binding.addKnownHeader(addrVersion.faultToTag); 109 binding.addKnownHeader(addrVersion.fromTag); 110 binding.addKnownHeader(addrVersion.messageIDTag); 111 binding.addKnownHeader(addrVersion.relatesToTag); 112 binding.addKnownHeader(addrVersion.replyToTag); 113 binding.addKnownHeader(addrVersion.toTag); 114 } 115 } 116 117 @Override 118 public @NotNull NextAction processException(Throwable t) { 119 return super.processException(t); 120 } 121 122 protected WsaTubeHelper getTubeHelper() { 123 if(binding.isFeatureEnabled(AddressingFeature.class)) { 124 return new WsaTubeHelperImpl(wsdlPort, null, binding); 125 } else if(binding.isFeatureEnabled(MemberSubmissionAddressingFeature.class)) { 126 //seiModel is null as it is not needed. 127 return new com.sun.xml.internal.ws.addressing.v200408.WsaTubeHelperImpl(wsdlPort, null, binding); 128 } else { 129 // Addressing is not enabled, WsaTube should not be included in the pipeline 130 throw new WebServiceException(AddressingMessages.ADDRESSING_NOT_ENABLED(this.getClass().getSimpleName())); 131 } 132 } 133 134 /** 135 * Validates the inbound message. If an error is found, create 136 * a fault message and returns that. Otherwise 137 * it will pass through the parameter 'packet' object to the return value. 138 */ 139 protected Packet validateInboundHeaders(Packet packet) { 140 SOAPFault soapFault; 141 FaultDetailHeader s11FaultDetailHeader; 142 143 try { 144 checkMessageAddressingProperties(packet); 145 return packet; 146 } catch (InvalidAddressingHeaderException e) { 147 LOGGER.log(Level.WARNING, 148 addressingVersion.getInvalidMapText()+", Problem header:" + e.getProblemHeader()+ ", Reason: "+ e.getSubsubcode(),e); 149 soapFault = helper.createInvalidAddressingHeaderFault(e, addressingVersion); 150 s11FaultDetailHeader = new FaultDetailHeader(addressingVersion, addressingVersion.problemHeaderQNameTag.getLocalPart(), e.getProblemHeader()); 151 } catch (MissingAddressingHeaderException e) { 152 LOGGER.log(Level.WARNING,addressingVersion.getMapRequiredText()+", Problem header:"+ e.getMissingHeaderQName(),e); 153 soapFault = helper.newMapRequiredFault(e); 154 s11FaultDetailHeader = new FaultDetailHeader(addressingVersion, addressingVersion.problemHeaderQNameTag.getLocalPart(), e.getMissingHeaderQName()); 155 } 156 157 if (soapFault != null) { 158 // WS-A fault processing for one-way methods 159 if ((wsdlPort !=null) && packet.getMessage().isOneWay(wsdlPort)) { 160 return packet.createServerResponse(null, wsdlPort, null, binding); 161 } 162 163 Message m = Messages.create(soapFault); 164 if (soapVersion == SOAPVersion.SOAP_11) { 165 m.getHeaders().add(s11FaultDetailHeader); 166 } 167 168 return packet.createServerResponse(m, wsdlPort, null, binding); 169 } 170 171 return packet; 172 } 173 174 /** 175 * This method checks all the WS-Addressing headers are valid and as per the spec definded rules. 176 * Mainly it checks the cardinality of the WSA headers and checks that mandatory headers exist. 177 * It also checks if the SOAPAction is equal to wsa:Action value when non-empty. 178 * 179 * Override this method if you need to additional checking of headers other than just existence of the headers. 180 * For ex: On server-side, check Anonymous and Non-Anonymous semantics in addition to checking cardinality. 181 * 182 * Override checkMandatoryHeaders(Packet p) to have different validation rules for different versions 183 * 184 * @param packet 185 */ 186 protected void checkMessageAddressingProperties(Packet packet) { 187 checkCardinality(packet); 188 } 189 190 final boolean isAddressingEngagedOrRequired(Packet packet, WSBinding binding) { 191 if (AddressingVersion.isRequired(binding)) 192 return true; 193 194 if (packet == null) 195 return false; 196 197 if (packet.getMessage() == null) 198 return false; 199 200 if (packet.getMessage().getHeaders() != null) 201 return false; 202 203 String action = AddressingUtils.getAction( 204 packet.getMessage().getHeaders(), 205 addressingVersion, soapVersion); 206 if (action == null) 207 return true; 208 209 return true; 210 } 211 212 /** 213 * Checks the cardinality of WS-Addressing headers on an inbound {@link Packet}. This method 214 * checks for the cardinality if WS-Addressing is engaged (detected by the presence of wsa:Action 215 * header) or wsdl:required=true. 216 * 217 * @param packet The inbound packet. 218 * @throws WebServiceException if: 219 * <ul> 220 * <li>there is an error reading ReplyTo or FaultTo</li> 221 * <li>WS-Addressing is required and {@link Message} within <code>packet</code> is null</li> 222 * <li>WS-Addressing is required and no headers are found in the {@link Message}</li> 223 * <li>an uknown WS-Addressing header is present</li> 224 * </ul> 225 */ 226 protected void checkCardinality(Packet packet) { 227 Message message = packet.getMessage(); 228 if (message == null) { 229 if (addressingRequired) 230 throw new WebServiceException(AddressingMessages.NULL_MESSAGE()); 231 else 232 return; 233 } 234 235 Iterator<Header> hIter = message.getHeaders().getHeaders(addressingVersion.nsUri, true); 236 237 if (!hIter.hasNext()) { 238 // no WS-A headers are found 239 if (addressingRequired) 240 // if WS-A is required, then throw an exception looking for wsa:Action header 241 throw new MissingAddressingHeaderException(addressingVersion.actionTag,packet); 242 else 243 // else no need to process 244 return; 245 } 246 247 boolean foundFrom = false; 248 boolean foundTo = false; 249 boolean foundReplyTo = false; 250 boolean foundFaultTo = false; 251 boolean foundAction = false; 252 boolean foundMessageId = false; 253 boolean foundRelatesTo = false; 254 QName duplicateHeader = null; 255 256 while (hIter.hasNext()) { 257 Header h = hIter.next(); 258 259 // check if the Header is in current role 260 if (!isInCurrentRole(h, binding)) { 261 continue; 262 } 263 264 String local = h.getLocalPart(); 265 if (local.equals(addressingVersion.fromTag.getLocalPart())) { 266 if (foundFrom) { 267 duplicateHeader = addressingVersion.fromTag; 268 break; 269 } 270 foundFrom = true; 271 } else if (local.equals(addressingVersion.toTag.getLocalPart())) { 272 if (foundTo) { 273 duplicateHeader = addressingVersion.toTag; 274 break; 275 } 276 foundTo = true; 277 } else if (local.equals(addressingVersion.replyToTag.getLocalPart())) { 278 if (foundReplyTo) { 279 duplicateHeader = addressingVersion.replyToTag; 280 break; 281 } 282 foundReplyTo = true; 283 try { // verify that the header is in a good shape 284 h.readAsEPR(addressingVersion); 285 } catch (XMLStreamException e) { 286 throw new WebServiceException(AddressingMessages.REPLY_TO_CANNOT_PARSE(), e); 287 } 288 } else if (local.equals(addressingVersion.faultToTag.getLocalPart())) { 289 if (foundFaultTo) { 290 duplicateHeader = addressingVersion.faultToTag; 291 break; 292 } 293 foundFaultTo = true; 294 try { // verify that the header is in a good shape 295 h.readAsEPR(addressingVersion); 296 } catch (XMLStreamException e) { 297 throw new WebServiceException(AddressingMessages.FAULT_TO_CANNOT_PARSE(), e); 298 } 299 } else if (local.equals(addressingVersion.actionTag.getLocalPart())) { 300 if (foundAction) { 301 duplicateHeader = addressingVersion.actionTag; 302 break; 303 } 304 foundAction = true; 305 } else if (local.equals(addressingVersion.messageIDTag.getLocalPart())) { 306 if (foundMessageId) { 307 duplicateHeader = addressingVersion.messageIDTag; 308 break; 309 } 310 foundMessageId = true; 311 } else if (local.equals(addressingVersion.relatesToTag.getLocalPart())) { 312 foundRelatesTo = true; 313 } else if (local.equals(addressingVersion.faultDetailTag.getLocalPart())) { 314 // TODO: should anything be done here ? 315 // TODO: fault detail element - only for SOAP 1.1 316 } else { 317 System.err.println(AddressingMessages.UNKNOWN_WSA_HEADER()); 318 } 319 } 320 321 // check for invalid cardinality first before checking for mandatory headers 322 if (duplicateHeader != null) { 323 throw new InvalidAddressingHeaderException(duplicateHeader, addressingVersion.invalidCardinalityTag); 324 } 325 326 // WS-A is engaged if wsa:Action header is found 327 boolean engaged = foundAction; 328 329 // check for mandatory set of headers only if: 330 // 1. WS-A is engaged or 331 // 2. wsdl:required=true 332 // Both wsa:Action and wsa:To MUST be present on request (for oneway MEP) and 333 // response messages (for oneway and request/response MEP only) 334 if (engaged || addressingRequired) { 335 // Check for mandatory headers always (even for Protocol messages). 336 // If it breaks any interop scenarios, Remove the comments. 337 /* 338 WSDLBoundOperation wbo = getWSDLBoundOperation(packet); 339 // no need to check for for non-application messages 340 if (wbo == null) 341 return; 342 */ 343 checkMandatoryHeaders(packet, foundAction, foundTo, foundReplyTo, 344 foundFaultTo, foundMessageId, foundRelatesTo); 345 } 346 } 347 348 final boolean isInCurrentRole(Header header, WSBinding binding) { 349 // TODO: binding will be null for protocol messages 350 // TODO: returning true assumes that protocol messages are 351 // TODO: always in current role, this may not to be fixed. 352 if (binding == null) 353 return true; 354 return ((SOAPBinding)binding).getRoles().contains(header.getRole(soapVersion)); 355 356 } 357 358 protected final WSDLBoundOperation getWSDLBoundOperation(Packet packet) { 359 //we can find Req/Response or Oneway only with WSDLModel 360 if(wsdlPort == null) 361 return null; 362 QName opName = packet.getWSDLOperation(); 363 if(opName != null) 364 return wsdlPort.getBinding().get(opName); 365 return null; 366 } 367 368 protected void validateSOAPAction(Packet packet) { 369 String gotA = AddressingUtils.getAction( 370 packet.getMessage().getHeaders(), 371 addressingVersion, soapVersion); 372 if (gotA == null) 373 throw new WebServiceException(AddressingMessages.VALIDATION_SERVER_NULL_ACTION()); 374 if(packet.soapAction != null && !packet.soapAction.equals("\"\"") && !packet.soapAction.equals("\""+gotA+"\"")) { 375 throw new InvalidAddressingHeaderException(addressingVersion.actionTag, addressingVersion.actionMismatchTag); 376 } 377 } 378 379 protected abstract void validateAction(Packet packet); 380 381 /** 382 * This should be called only when Addressing is engaged. 383 * 384 * Checks only for presence of wsa:Action and validates that wsa:Action 385 * equals SOAPAction header when non-empty 386 * Should be overridden if other wsa headers need to be checked based on version. 387 * 388 * @param packet 389 * @param foundAction 390 * @param foundTo 391 * @param foundReplyTo 392 * @param foundFaultTo 393 * @param foundMessageId 394 * @param foundRelatesTo 395 */ 396 protected void checkMandatoryHeaders( 397 Packet packet, boolean foundAction, boolean foundTo, boolean foundReplyTo, 398 boolean foundFaultTo, boolean foundMessageId, boolean foundRelatesTo) { 399 // if no wsa:Action header is found 400 if (!foundAction) 401 throw new MissingAddressingHeaderException(addressingVersion.actionTag,packet); 402 validateSOAPAction(packet); 403 } 404 private static final Logger LOGGER = Logger.getLogger(WsaTube.class.getName()); 405} 406