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