/* * Copyright (c) 2005, 2006, 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.EndpointAddress; import com.sun.xml.internal.ws.api.PropertySet; import com.sun.xml.internal.ws.api.message.Packet; import javax.xml.ws.BindingProvider; import javax.xml.ws.WebServiceException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Request context implementation. * * <h2>Why a custom map?</h2> * <p> * 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. * * <p> * 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. * * <p> * 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. * * <p> * These fields are used by {@link Stub#process} to populate a {@link Packet}. * * * * <h2>How it works?</h2> * <p> * 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. * * <p> * 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. * * <p> * If {@link RequestContext} is in this mode, its state can be copied * efficiently into {@link Packet}. * * <p> * 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. * * <p> * To be safe, once we fallback, we'll never come back to the efficient state. * * * * <h2>Caution</h2> * <p> * 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 PropertySet { /** * The default value to be use for {@link #contentNegotiation} obtained * from a system property. * <p> * 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<String,Object> others; /** * The endpoint address to which this message is sent to. * * <p> * 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.toString(); } 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. * * <p> * For outgoing messages, the transport may sends out this value. * If this field is null, the transport may choose to send <tt>""</tt> * (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. * * <p> * If the value is non-null, it must be always in the quoted form. * The value can be null. * * <p> * 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; } /** * {@link Map} exposed to the user application. */ private final MapView mapView = new MapView(); /** * Creates an empty {@link RequestContext}. */ /*package*/ RequestContext() { others = new HashMap<String, Object>(); } /** * Copy constructor. */ private RequestContext(RequestContext that) { others = new HashMap<String,Object>(that.others); endpointAddress = that.endpointAddress; soapAction = that.soapAction; contentNegotiation = that.contentNegotiation; // this is fragile, but it works faster } /** * 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<String,Object> getMapView() { return mapView; } /** * Fill a {@link Packet} with values of this {@link RequestContext}. */ public void fill(Packet packet) { if(mapView.fallbackMap==null) { if (endpointAddress != null) packet.endpointAddress = endpointAddress; packet.contentNegotiation = contentNegotiation; if (soapAction != null) { packet.soapAction = soapAction; } if(!others.isEmpty()) { packet.invocationProperties.putAll(others); //if it is not standard property it deafults to Scope.HANDLER packet.getHandlerScopePropertyNames(false).addAll(others.keySet()); } } else { Set<String> handlerScopePropertyNames = new HashSet<String>(); // fallback mode, simply copy map in a slow way for (Entry<String,Object> 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<String,Object> { private Map<String,Object> fallbackMap; private Map<String,Object> fallback() { if(fallbackMap==null) { // has to fall back. fill in fallbackMap fallbackMap = new HashMap<String,Object>(others); // then put all known properties for (Map.Entry<String,Accessor> prop : propMap.entrySet()) { fallbackMap.put(prop.getKey(),prop.getValue().get(RequestContext.this)); } } 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<? extends String, ? extends Object> t) { for (Entry<? extends String, ? extends Object> e : t.entrySet()) { put(e.getKey(),e.getValue()); } } public void clear() { fallback().clear(); } public Set<String> keySet() { return fallback().keySet(); } public Collection<Object> values() { return fallback().values(); } public Set<Entry<String, Object>> entrySet() { return fallback().entrySet(); } } protected PropertyMap getPropertyMap() { return propMap; } private static final PropertyMap propMap = parse(RequestContext.class); }