diff -r 88b85470e72c -r f50545b5e2f1 src/share/jaxws_classes/com/sun/xml/internal/ws/client/RequestContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/jaxws_classes/com/sun/xml/internal/ws/client/RequestContext.java Tue Mar 06 16:09:35 2012 -0800 @@ -0,0 +1,453 @@ +/* + * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.xml.internal.ws.client; + +import com.sun.istack.internal.NotNull; +import com.sun.xml.internal.ws.api.DistributedPropertySet; +import com.sun.xml.internal.ws.api.EndpointAddress; +import com.sun.xml.internal.ws.api.PropertySet; +import com.sun.xml.internal.ws.api.message.Packet; +import com.sun.xml.internal.ws.transport.Headers; + +import javax.xml.ws.BindingProvider; +import javax.xml.ws.handler.MessageContext; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Logger; + +/** + * Request context implementation. + * + *

Why a custom map?

+ *

+ * The JAX-WS spec exposes properties as a {@link Map}, but if we just use + * an ordinary {@link HashMap} for this, it doesn't work as fast as we'd like + * it to be. Hence we have this class. + * + *

+ * We expect the user to set a few properties and then use that same + * setting to make a bunch of invocations. So we'd like to take some hit + * when the user actually sets a property to do some computation, + * then use that computed value during a method invocation again and again. + * + *

+ * For this goal, we use {@link PropertySet} and implement some properties + * as virtual properties backed by methods. This allows us to do the computation + * in the setter, and store it in a field. + * + *

+ * These fields are used by {@link Stub#process} to populate a {@link Packet}. + * + * + * + *

How it works?

+ *

+ * We make an assumption that a request context is mostly used to just + * get and put values, not really for things like enumerating or size. + * + *

+ * So we start by maintaining state as a combination of {@link #others} + * bag and strongly-typed fields. As long as the application uses + * just {@link Map#put}, {@link Map#get}, and {@link Map#putAll}, we can + * do things in this way. In this mode a {@link Map} we return works as + * a view into {@link RequestContext}, and by itself it maintains no state. + * + *

+ * If {@link RequestContext} is in this mode, its state can be copied + * efficiently into {@link Packet}. + * + *

+ * Once the application uses any other {@link Map} method, we move to + * the "fallback" mode, where the data is actually stored in a {@link HashMap}, + * this is necessary for implementing the map interface contract correctly. + * + *

+ * To be safe, once we fallback, we'll never come back to the efficient state. + * + * + * + *

Caution

+ *

+ * Once we are in the fallback mode, none of the strongly typed field will + * be used, and they may contain stale values. So the only method + * the code outside this class can safely use is {@link #copy()}, + * {@link #fill(Packet)}, and constructors. Do not access the strongly + * typed fields nor {@link #others} directly. + * + * @author Kohsuke Kawaguchi + */ +@SuppressWarnings({"SuspiciousMethodCalls"}) +public final class RequestContext extends DistributedPropertySet { + private static final Logger LOGGER = Logger.getLogger(RequestContext.class.getName()); + + /** + * The default value to be use for {@link #contentNegotiation} obtained + * from a system property. + *

+ * This enables content negotiation to be easily switched on by setting + * a system property on the command line for testing purposes tests. + */ + private static ContentNegotiation defaultContentNegotiation = + ContentNegotiation.obtainFromSystemProperty(); + + /** + * Stores properties that don't fit the strongly-typed fields. + */ + private final Map others; + + /** + * The endpoint address to which this message is sent to. + * + *

+ * This is the actual data store for {@link BindingProvider#ENDPOINT_ADDRESS_PROPERTY}. + */ + private @NotNull EndpointAddress endpointAddress; + + /** + * Creates {@link BindingProvider#ENDPOINT_ADDRESS_PROPERTY} view + * on top of {@link #endpointAddress}. + * + * @deprecated + * always access {@link #endpointAddress}. + */ + @Property(BindingProvider.ENDPOINT_ADDRESS_PROPERTY) + public String getEndPointAddressString() { + return endpointAddress != null ? endpointAddress.toString() : null; + } + + public void setEndPointAddressString(String s) { + if(s==null) + throw new IllegalArgumentException(); + else + this.endpointAddress = EndpointAddress.create(s); + } + + public void setEndpointAddress(@NotNull EndpointAddress epa) { + this.endpointAddress = epa; + } + + public @NotNull EndpointAddress getEndpointAddress() { + return endpointAddress; + } + + /** + * The value of {@link ContentNegotiation#PROPERTY} + * property. + */ + public ContentNegotiation contentNegotiation = defaultContentNegotiation; + + @Property(ContentNegotiation.PROPERTY) + public String getContentNegotiationString() { + return contentNegotiation.toString(); + } + + public void setContentNegotiationString(String s) { + if(s==null) + contentNegotiation = ContentNegotiation.none; + else { + try { + contentNegotiation = ContentNegotiation.valueOf(s); + } catch (IllegalArgumentException e) { + // If the value is not recognized default to none + contentNegotiation = ContentNegotiation.none; + } + } + } + /** + * The value of the SOAPAction header associated with the message. + * + *

+ * For outgoing messages, the transport may sends out this value. + * If this field is null, the transport may choose to send "" + * (quoted empty string.) + * + * For incoming messages, the transport will set this field. + * If the incoming message did not contain the SOAPAction header, + * the transport sets this field to null. + * + *

+ * If the value is non-null, it must be always in the quoted form. + * The value can be null. + * + *

+ * Note that the way the transport sends this value out depends on + * transport and SOAP version. + * + * For HTTP transport and SOAP 1.1, BP requires that SOAPAction + * header is present (See {@BP R2744} and {@BP R2745}.) For SOAP 1.2, + * this is moved to the parameter of the "application/soap+xml". + */ + + private String soapAction; + + @Property(BindingProvider.SOAPACTION_URI_PROPERTY) + public String getSoapAction(){ + return soapAction; + } + public void setSoapAction(String sAction){ + if(sAction == null) { + throw new IllegalArgumentException("SOAPAction value cannot be null"); + } + soapAction = sAction; + } + + /** + * This controls whether BindingProvider.SOAPACTION_URI_PROPERTY is used. + * See BindingProvider.SOAPACTION_USE_PROPERTY for details. + * + * This only control whether value of BindingProvider.SOAPACTION_URI_PROPERTY is used or not and not + * if it can be sent if it can be obtained by other means such as WSDL binding + */ + private Boolean soapActionUse; + @Property(BindingProvider.SOAPACTION_USE_PROPERTY) + public Boolean getSoapActionUse(){ + return soapActionUse; + } + public void setSoapActionUse(Boolean sActionUse){ + soapActionUse = sActionUse; + } + + /** + * {@link Map} exposed to the user application. + */ + private final MapView mapView = new MapView(); + + /** + * Creates an empty {@link RequestContext}. + */ + /*package*/ RequestContext() { + others = new HashMap(); + } + + /** + * Copy constructor. + */ + private RequestContext(RequestContext that) { + others = new HashMap(that.others); + mapView.fallbackMap = that.mapView.fallbackMap != null ? + new HashMap(that.mapView.fallback()) : null; + endpointAddress = that.endpointAddress; + soapAction = that.soapAction; + contentNegotiation = that.contentNegotiation; + that.copySatelliteInto(this); + } + + /** + * The efficient get method that reads from {@link RequestContext}. + */ + public Object get(Object key) { + if(super.supports(key)) + return super.get(key); + else + return others.get(key); + } + + /** + * The efficient put method that updates {@link RequestContext}. + */ + public Object put(String key, Object value) { + if(super.supports(key)) + return super.put(key,value); + else + return others.put(key,value); + } + + /** + * Gets the {@link Map} view of this request context. + * + * @return + * Always same object. Returned map is live. + */ + public Map getMapView() { + return mapView; + } + + /** + * Fill a {@link Packet} with values of this {@link RequestContext}. + */ + public void fill(Packet packet, boolean isAddressingEnabled) { + if(mapView.fallbackMap==null) { + if (endpointAddress != null) + packet.endpointAddress = endpointAddress; + packet.contentNegotiation = contentNegotiation; + + //JAX-WS-596: Check the semantics of SOAPACTION_USE_PROPERTY before using the SOAPACTION_URI_PROPERTY for + // SoapAction as specified in the javadoc of BindingProvider. The spec seems to be little contradicting with + // javadoc and says that the use property effects the sending of SOAPAction property. + // Since the user has the capability to set the value as "" if needed, implement the javadoc behavior. + + if ((soapActionUse != null && soapActionUse) || (soapActionUse == null && isAddressingEnabled)) { + if (soapAction != null) { + packet.soapAction = soapAction; + } + } + + if((!isAddressingEnabled && (soapActionUse == null || !soapActionUse)) && soapAction != null) { + LOGGER.warning("BindingProvider.SOAPACTION_URI_PROPERTY is set in the RequestContext but is ineffective," + + " Either set BindingProvider.SOAPACTION_USE_PROPERTY to true or enable AddressingFeature"); + } + + copySatelliteInto((DistributedPropertySet)packet); + + if(!others.isEmpty()) { + //for bug 12883765 + //retrieve headers which is set in soap message + Headers headerFromPacketProperty = (Headers)packet.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS); + //retrieve headers from request context + Map> headerFromOthers =(Map>) others.get(MessageContext.HTTP_REQUEST_HEADERS); + if((headerFromPacketProperty != null) && (headerFromOthers != null) ) { + //update the headers set in soap message with those in request context + for(String key: headerFromOthers.keySet()) { + if(key!=null && key.trim().length()!=0) { + List valueFromPacketProperty = headerFromPacketProperty.get(key); + //if the two headers contain the same key, combine the value + if(valueFromPacketProperty!=null) { + valueFromPacketProperty.addAll(headerFromOthers.get(key)); + }else{ + //add the headers in request context to those set in soap message + headerFromPacketProperty.put(key, headerFromOthers.get(key)); + } + } + } + // update headers in request context with those set in soap message since 'others' may contain other properties.. + others.put(MessageContext.HTTP_REQUEST_HEADERS, headerFromPacketProperty); + } + packet.invocationProperties.putAll(others); + //if it is not standard property it deafults to Scope.HANDLER + packet.getHandlerScopePropertyNames(false).addAll(others.keySet()); + } + } else { + Set handlerScopePropertyNames = new HashSet(); + // fallback mode, simply copy map in a slow way + for (Entry entry : mapView.fallbackMap.entrySet()) { + String key = entry.getKey(); + if(packet.supports(key)) + packet.put(key,entry.getValue()); + else + packet.invocationProperties.put(key,entry.getValue()); + + //if it is not standard property it deafults to Scope.HANDLER + if(!super.supports(key)) { + handlerScopePropertyNames.add(key); + } + } + + if(!handlerScopePropertyNames.isEmpty()) + packet.getHandlerScopePropertyNames(false).addAll(handlerScopePropertyNames); + } + } + + public RequestContext copy() { + return new RequestContext(this); + } + + private final class MapView implements Map { + private Map fallbackMap; + + private Map fallback() { + if(fallbackMap==null) { + // has to fall back. fill in fallbackMap + fallbackMap = new HashMap(others); + // then put all known properties + fallbackMap.putAll(createMapView()); + } + return fallbackMap; + } + + public int size() { + return fallback().size(); + } + + public boolean isEmpty() { + return fallback().isEmpty(); + } + + public boolean containsKey(Object key) { + return fallback().containsKey(key); + } + + public boolean containsValue(Object value) { + return fallback().containsValue(value); + } + + public Object get(Object key) { + if (fallbackMap ==null) { + return RequestContext.this.get(key); + } else { + return fallback().get(key); + } + } + + public Object put(String key, Object value) { + if(fallbackMap ==null) + return RequestContext.this.put(key,value); + else + return fallback().put(key, value); + } + + public Object remove(Object key) { + if (fallbackMap ==null) { + return RequestContext.this.remove(key); + } else { + return fallback().remove(key); + } + } + + public void putAll(Map t) { + for (Entry e : t.entrySet()) { + put(e.getKey(),e.getValue()); + } + } + + public void clear() { + fallback().clear(); + } + + public Set keySet() { + return fallback().keySet(); + } + + public Collection values() { + return fallback().values(); + } + + public Set> entrySet() { + return fallback().entrySet(); + } + } + + protected PropertyMap getPropertyMap() { + return propMap; + } + + private static final PropertyMap propMap = parse(RequestContext.class); +}