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.client; 27 28import com.oracle.webservices.internal.api.message.BaseDistributedPropertySet; 29import com.sun.istack.internal.NotNull; 30import com.sun.xml.internal.ws.api.EndpointAddress; 31import com.sun.xml.internal.ws.api.message.Packet; 32import com.sun.xml.internal.ws.transport.Headers; 33 34import javax.xml.ws.BindingProvider; 35import java.util.HashMap; 36import java.util.HashSet; 37import java.util.List; 38import java.util.Map; 39import java.util.Map.Entry; 40import java.util.Set; 41import java.util.logging.Logger; 42 43 44import static javax.xml.ws.BindingProvider.*; 45import static javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS; 46 47/** 48 * Request context implementation. 49 * 50 * <h2>Why a custom map?</h2> 51 * <p> 52 * The JAX-WS spec exposes properties as a {@link Map}, but if we just use 53 * an ordinary {@link HashMap} for this, it doesn't work as fast as we'd like 54 * it to be. Hence we have this class. 55 * 56 * <p> 57 * We expect the user to set a few properties and then use that same 58 * setting to make a bunch of invocations. So we'd like to take some hit 59 * when the user actually sets a property to do some computation, 60 * then use that computed value during a method invocation again and again. 61 * 62 * <p> 63 * For this goal, we use {@link com.sun.xml.internal.ws.api.PropertySet} and implement some properties 64 * as virtual properties backed by methods. This allows us to do the computation 65 * in the setter, and store it in a field. 66 * 67 * <p> 68 * These fields are used by {@link Stub#process} to populate a {@link Packet}. 69 * 70 * <h2>How it works?</h2> 71 * <p> 72 * For better performance, we wan't use strongly typed field as much as possible 73 * to avoid reflection and unnecessary collection iterations; 74 * 75 * Using {@link com.oracle.webservices.internal.api.message.BasePropertySet.MapView} implementation allows client to use {@link Map} interface 76 * in a way that all the strongly typed properties are reflected to the fields 77 * right away. Any additional (extending) properties can be added by client as well; 78 * those would be processed using iterating the {@link MapView} and their processing, 79 * of course, would be slower. 80 * <p> 81 * The previous implementation with fallback mode has been removed to simplify 82 * the code and remove the bugs. 83 * 84 * @author Kohsuke Kawaguchi 85 */ 86@SuppressWarnings({"SuspiciousMethodCalls"}) 87public final class RequestContext extends BaseDistributedPropertySet { 88 private static final Logger LOGGER = Logger.getLogger(RequestContext.class.getName()); 89 90 /** 91 * The default value to be use for {@link #contentNegotiation} obtained 92 * from a system property. 93 * <p> 94 * This enables content negotiation to be easily switched on by setting 95 * a system property on the command line for testing purposes tests. 96 */ 97 private static ContentNegotiation defaultContentNegotiation = 98 ContentNegotiation.obtainFromSystemProperty(); 99 100 /** 101 * @deprecated 102 */ 103 public void addSatellite(@NotNull com.sun.xml.internal.ws.api.PropertySet satellite) { 104 super.addSatellite(satellite); 105 } 106 107 /** 108 * The endpoint address to which this message is sent to. 109 * 110 * <p> 111 * This is the actual data store for {@link BindingProvider#ENDPOINT_ADDRESS_PROPERTY}. 112 */ 113 private @NotNull EndpointAddress endpointAddress; 114 115 /** 116 * Creates {@link BindingProvider#ENDPOINT_ADDRESS_PROPERTY} view 117 * on top of {@link #endpointAddress}. 118 * 119 * @deprecated 120 * always access {@link #endpointAddress}. 121 */ 122 @Property(ENDPOINT_ADDRESS_PROPERTY) 123 public String getEndPointAddressString() { 124 return endpointAddress != null ? endpointAddress.toString() : null; 125 } 126 127 public void setEndPointAddressString(String s) { 128 if (s == null) { 129 throw new IllegalArgumentException(); 130 } else { 131 this.endpointAddress = EndpointAddress.create(s); 132 } 133 } 134 135 public void setEndpointAddress(@NotNull EndpointAddress epa) { 136 this.endpointAddress = epa; 137 } 138 139 public @NotNull EndpointAddress getEndpointAddress() { 140 return endpointAddress; 141 } 142 143 /** 144 * The value of {@link ContentNegotiation#PROPERTY} 145 * property. 146 */ 147 public ContentNegotiation contentNegotiation = defaultContentNegotiation; 148 149 @Property(ContentNegotiation.PROPERTY) 150 public String getContentNegotiationString() { 151 return contentNegotiation.toString(); 152 } 153 154 public void setContentNegotiationString(String s) { 155 if (s == null) { 156 contentNegotiation = ContentNegotiation.none; 157 } else { 158 try { 159 contentNegotiation = ContentNegotiation.valueOf(s); 160 } catch (IllegalArgumentException e) { 161 // If the value is not recognized default to none 162 contentNegotiation = ContentNegotiation.none; 163 } 164 } 165 } 166 167 /** 168 * The value of the SOAPAction header associated with the message. 169 * 170 * <p> 171 * For outgoing messages, the transport may sends out this value. 172 * If this field is null, the transport may choose to send {@code ""} 173 * (quoted empty string.) 174 * 175 * For incoming messages, the transport will set this field. 176 * If the incoming message did not contain the SOAPAction header, 177 * the transport sets this field to null. 178 * 179 * <p> 180 * If the value is non-null, it must be always in the quoted form. 181 * The value can be null. 182 * 183 * <p> 184 * Note that the way the transport sends this value out depends on 185 * transport and SOAP version. 186 * 187 * For HTTP transport and SOAP 1.1, BP requires that SOAPAction 188 * header is present (See {@BP R2744} and {@BP R2745}.) For SOAP 1.2, 189 * this is moved to the parameter of the "application/soap+xml". 190 */ 191 192 private String soapAction; 193 194 @Property(SOAPACTION_URI_PROPERTY) 195 public String getSoapAction() { 196 return soapAction; 197 } 198 199 public void setSoapAction(String sAction) { 200 soapAction = sAction; 201 } 202 203 /** 204 * This controls whether BindingProvider.SOAPACTION_URI_PROPERTY is used. 205 * See BindingProvider.SOAPACTION_USE_PROPERTY for details. 206 * 207 * This only control whether value of BindingProvider.SOAPACTION_URI_PROPERTY is used or not and not 208 * if it can be sent if it can be obtained by other means such as WSDL binding 209 */ 210 private Boolean soapActionUse; 211 212 @Property(SOAPACTION_USE_PROPERTY) 213 public Boolean getSoapActionUse() { 214 return soapActionUse; 215 } 216 217 public void setSoapActionUse(Boolean sActionUse) { 218 soapActionUse = sActionUse; 219 } 220 221 /** 222 * Creates an empty {@link RequestContext}. 223 */ 224 RequestContext() { 225 } 226 227 /** 228 * Copy constructor. 229 */ 230 private RequestContext(RequestContext that) { 231 for (Map.Entry<String, Object> entry : that.asMapLocal().entrySet()) { 232 if (!propMap.containsKey(entry.getKey())) { 233 asMap().put(entry.getKey(), entry.getValue()); 234 } 235 } 236 endpointAddress = that.endpointAddress; 237 soapAction = that.soapAction; 238 soapActionUse = that.soapActionUse; 239 contentNegotiation = that.contentNegotiation; 240 that.copySatelliteInto(this); 241 } 242 243 /** 244 * The efficient get method that reads from {@link RequestContext}. 245 */ 246 @Override 247 public Object get(Object key) { 248 if(supports(key)) { 249 return super.get(key); 250 } else { 251 // use mapView to get extending property 252 return asMap().get(key); 253 } 254 } 255 256 /** 257 * The efficient put method that updates {@link RequestContext}. 258 */ 259 @Override 260 public Object put(String key, Object value) { 261 262 if(supports(key)) { 263 return super.put(key,value); 264 } else { 265 // use mapView to put extending property (if the map allows that) 266 return asMap().put(key, value); 267 } 268 } 269 270 /** 271 * Fill a {@link Packet} with values of this {@link RequestContext}. 272 * 273 * @param packet to be filled with context values 274 * @param isAddressingEnabled flag if addressing enabled (to provide warning if necessary) 275 */ 276 @SuppressWarnings("unchecked") 277 public void fill(Packet packet, boolean isAddressingEnabled) { 278 279 // handling as many properties as possible (all in propMap.keySet()) 280 // to avoid slow Packet.put() 281 if (endpointAddress != null) { 282 packet.endpointAddress = endpointAddress; 283 } 284 packet.contentNegotiation = contentNegotiation; 285 fillSOAPAction(packet, isAddressingEnabled); 286 mergeRequestHeaders(packet); 287 288 Set<String> handlerScopeNames = new HashSet<String>(); 289 290 copySatelliteInto(packet); 291 292 // extending properties ... 293 for (String key : asMapLocal().keySet()) { 294 295 //if it is not standard property it defaults to Scope.HANDLER 296 if (!supportsLocal(key)) { 297 handlerScopeNames.add(key); 298 } 299 300 // to avoid slow Packet.put(), handle as small number of props as possible 301 // => only properties not from RequestContext object 302 if (!propMap.containsKey(key)) { 303 Object value = asMapLocal().get(key); 304 if (packet.supports(key)) { 305 // very slow operation - try to avoid it! 306 packet.put(key, value); 307 } else { 308 packet.invocationProperties.put(key, value); 309 } 310 } 311 } 312 313 if (!handlerScopeNames.isEmpty()) { 314 packet.getHandlerScopePropertyNames(false).addAll(handlerScopeNames); 315 } 316 } 317 318 @SuppressWarnings("unchecked") 319 private void mergeRequestHeaders(Packet packet) { 320 //for bug 12883765 321 //retrieve headers which is set in soap message 322 Headers packetHeaders = (Headers) packet.invocationProperties.get(HTTP_REQUEST_HEADERS); 323 //retrieve headers from request context 324 Map<String, List<String>> myHeaders = (Map<String, List<String>>) asMap().get(HTTP_REQUEST_HEADERS); 325 if ((packetHeaders != null) && (myHeaders != null)) { 326 //update the headers set in soap message with those in request context 327 for (Entry<String, List<String>> entry : myHeaders.entrySet()) { 328 String key = entry.getKey(); 329 if (key != null && key.trim().length() != 0) { 330 List<String> listFromPacket = packetHeaders.get(key); 331 //if the two headers contain the same key, combine the value 332 if (listFromPacket != null) { 333 listFromPacket.addAll(entry.getValue()); 334 } else { 335 //add the headers in request context to those set in soap message 336 packetHeaders.put(key, myHeaders.get(key)); 337 } 338 } 339 } 340 // update headers in request context with those set in soap message since it may contain other properties.. 341 asMap().put(HTTP_REQUEST_HEADERS, packetHeaders); 342 } 343 } 344 345 private void fillSOAPAction(Packet packet, boolean isAddressingEnabled) { 346 final boolean p = packet.packetTakesPriorityOverRequestContext; 347 final String localSoapAction = p ? packet.soapAction : soapAction; 348 final Boolean localSoapActionUse = p ? (Boolean) packet.invocationProperties.get(BindingProvider.SOAPACTION_USE_PROPERTY) 349 : soapActionUse; 350 351 //JAX-WS-596: Check the semantics of SOAPACTION_USE_PROPERTY before using the SOAPACTION_URI_PROPERTY for 352 // SoapAction as specified in the javadoc of BindingProvider. The spec seems to be little contradicting with 353 // javadoc and says that the use property effects the sending of SOAPAction property. 354 // Since the user has the capability to set the value as "" if needed, implement the javadoc behavior. 355 if ((localSoapActionUse != null && localSoapActionUse) || (localSoapActionUse == null && isAddressingEnabled)) { 356 if (localSoapAction != null) { 357 packet.soapAction = localSoapAction; 358 } 359 } 360 361 if ((!isAddressingEnabled && (localSoapActionUse == null || !localSoapActionUse)) && localSoapAction != null) { 362 LOGGER.warning("BindingProvider.SOAPACTION_URI_PROPERTY is set in the RequestContext but is ineffective," + 363 " Either set BindingProvider.SOAPACTION_USE_PROPERTY to true or enable AddressingFeature"); 364 } 365 } 366 367 public RequestContext copy() { 368 return new RequestContext(this); 369 } 370 371 @Override 372 protected PropertyMap getPropertyMap() { 373 return propMap; 374 } 375 376 private static final PropertyMap propMap = parse(RequestContext.class); 377 378 @Override 379 protected boolean mapAllowsAdditionalProperties() { 380 return true; 381 } 382} 383