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.api;
27
28import com.sun.istack.internal.NotNull;
29import com.sun.xml.internal.ws.api.message.Message;
30import com.sun.xml.internal.ws.api.pipe.Codec;
31import com.sun.xml.internal.ws.api.pipe.Tube;
32import com.sun.xml.internal.ws.binding.BindingImpl;
33import com.sun.xml.internal.ws.binding.SOAPBindingImpl;
34import com.sun.xml.internal.ws.binding.WebServiceFeatureList;
35import com.sun.xml.internal.ws.encoding.SOAPBindingCodec;
36import com.sun.xml.internal.ws.encoding.XMLHTTPBindingCodec;
37import com.sun.xml.internal.ws.encoding.soap.streaming.SOAPNamespaceConstants;
38import com.sun.xml.internal.ws.util.ServiceFinder;
39import com.sun.xml.internal.ws.developer.JAXWSProperties;
40
41import javax.xml.ws.BindingType;
42import javax.xml.ws.WebServiceException;
43import javax.xml.ws.WebServiceFeature;
44import javax.xml.ws.handler.Handler;
45import javax.xml.ws.http.HTTPBinding;
46import javax.xml.ws.soap.MTOMFeature;
47import javax.xml.ws.soap.SOAPBinding;
48
49import java.io.UnsupportedEncodingException;
50import java.net.URL;
51import java.net.URLDecoder;
52import java.util.HashMap;
53import java.util.Map;
54
55/**
56 * Parsed binding ID string.
57 *
58 * <p>
59 * {@link BindingID} is an immutable object that represents a binding ID,
60 * much like how {@link URL} is a representation of an URL.
61 * Like {@link URL}, this class offers a bunch of methods that let you
62 * query various traits/properties of a binding ID.
63 *
64 * <p>
65 * {@link BindingID} is extensible; one can plug in a parser from
66 * {@link String} to {@link BindingID} to interpret binding IDs that
67 * the JAX-WS RI does no a-priori knowledge of.
68 * Technologies such as Tango uses this to make the JAX-WS RI understand
69 * binding IDs defined in their world.
70 *
71 * Such technologies are free to extend this class and expose more characterstics.
72 *
73 * <p>
74 * Even though this class defines a few well known constants, {@link BindingID}
75 * instances do not necessarily have singleton semantics. Use {@link #equals(Object)}
76 * for the comparison.
77 *
78 * <h3>{@link BindingID} and {@link WSBinding}</h3>
79 * <p>
80 * {@link WSBinding} is mutable and represents a particular "use" of a {@link BindingID}.
81 * As such, it has state like a list of {@link Handler}s, which are inherently local
82 * to a particular usage. For example, if you have two proxies, you need two instances.
83 *
84 * {@link BindingID}, OTOH, is immutable and thus the single instance
85 * that represents "SOAP1.2/HTTP" can be shared and reused by all proxies in the same VM.
86 *
87 * @author Kohsuke Kawaguchi
88 */
89public abstract class BindingID {
90
91    /**
92     * Creates an instance of {@link WSBinding} (which is conceptually an "use"
93     * of {@link BindingID}) from a {@link BindingID}.
94     *
95     * @return
96     *      Always a new instance.
97     */
98    public final @NotNull WSBinding createBinding() {
99        return BindingImpl.create(this);
100    }
101
102    /**
103     * Returns wsdl:binding@transport attribute. Sub classes
104     * are expected to override this method to provide their transport
105     * attribute.
106     *
107     * @return wsdl:binding@transport attribute
108     * @since JAX-WS RI 2.1.6
109     */
110    public @NotNull String getTransport() {
111        return SOAPNamespaceConstants.TRANSPORT_HTTP;
112    }
113
114    public final @NotNull WSBinding createBinding(WebServiceFeature... features) {
115        return BindingImpl.create(this, features);
116    }
117
118    public final @NotNull WSBinding createBinding(WSFeatureList features) {
119        return createBinding(features.toArray());
120    }
121
122    /**
123     * Gets the SOAP version of this binding.
124     *
125     * TODO: clarify what to do with XML/HTTP binding
126     *
127     * @return
128     *      If the binding is using SOAP, this method returns
129     *      a {@link SOAPVersion} constant.
130     *
131     *      If the binding is not based on SOAP, this method
132     *      returns null. See {@link Message} for how a non-SOAP
133     *      binding shall be handled by {@link Tube}s.
134     */
135    public abstract SOAPVersion getSOAPVersion();
136
137    /**
138     * Creates a new {@link Codec} for this binding.
139     *
140     * @param binding
141     *      Ocassionally some aspects of binding can be overridden by
142     *      {@link WSBinding} at runtime by users, so some {@link Codec}s
143     *      need to have access to {@link WSBinding} that it's working for.
144     */
145    public abstract @NotNull Codec createEncoder(@NotNull WSBinding binding);
146
147    /**
148     * Gets the binding ID, which uniquely identifies the binding.
149     *
150     * <p>
151     * The relevant specs define the binding IDs and what they mean.
152     * The ID is used in many places to identify the kind of binding
153     * (such as SOAP1.1, SOAP1.2, REST, ...)
154     *
155     * @return
156     *      Always non-null same value.
157     */
158    @Override
159    public abstract String toString();
160
161    /**
162     * Returna a new {@link WebServiceFeatureList} instance
163     * that represents the features that are built into this binding ID.
164     *
165     * <p>
166     * For example, {@link BindingID} for
167     * {@code "{@value SOAPBinding#SOAP11HTTP_MTOM_BINDING}"}
168     * would always return a list that has {@link MTOMFeature} enabled.
169     */
170    public WebServiceFeatureList createBuiltinFeatureList() {
171        return new WebServiceFeatureList();
172    }
173
174    /**
175     * Returns true if this binding can generate WSDL.
176     *
177     * <p>
178     * For e.g.: SOAP 1.1 and "XSOAP 1.2" is supposed to return true
179     * from this method. For SOAP1.2, there is no standard WSDL, so the
180     * runtime is not generating one and it expects the WSDL is packaged.
181     *
182     */
183    public boolean canGenerateWSDL() {
184        return false;
185    }
186
187    /**
188     * Returns a parameter of this binding ID.
189     *
190     * <p>
191     * Some binding ID, such as those for SOAP/HTTP, uses the URL
192     * query syntax (like {@code ?mtom=true}) to control
193     * the optional part of the binding. This method obtains
194     * the value for such optional parts.
195     *
196     * <p>
197     * For implementors of the derived classes, if your binding ID
198     * does not define such optional parts (such as the XML/HTTP binding ID),
199     * then you should simply return the specified default value
200     * (which is what this implementation does.)
201     *
202     * @param parameterName
203     *      The parameter name, such as "mtom" in the above example.
204     * @param defaultValue
205     *      If this binding ID doesn't have the specified parameter explicitly,
206     *      this value will be returned.
207     *
208     * @return
209     *      the value of the parameter, if it's present (such as "true"
210     *      in the above example.) If not present, this method returns
211     *      the {@code defaultValue}.
212     */
213    public String getParameter(String parameterName, String defaultValue) {
214        return defaultValue;
215    }
216
217    /**
218     * Compares the equality based on {@link #toString()}.
219     */
220    @Override
221    public boolean equals(Object obj) {
222        if(!(obj instanceof BindingID))
223            return false;
224        return toString().equals(obj.toString());
225    }
226
227    @Override
228    public int hashCode() {
229        return toString().hashCode();
230    }
231
232    /**
233     * Parses a binding ID string into a {@link BindingID} object.
234     *
235     * <p>
236     * This method first checks for a few known values and then delegate
237     * the parsing to {@link BindingIDFactory}.
238     *
239     * <p>
240     * If parsing succeeds this method returns a value. Otherwise
241     * throws {@link WebServiceException}.
242     *
243     * @throws WebServiceException
244     *      If the binding ID is not understood.
245     */
246    public static @NotNull BindingID parse(String lexical) {
247        if(lexical.equals(XML_HTTP.toString()))
248            return XML_HTTP;
249        if(lexical.equals(REST_HTTP.toString()))
250            return REST_HTTP;
251        if(belongsTo(lexical,SOAP11_HTTP.toString()))
252            return customize(lexical,SOAP11_HTTP);
253        if(belongsTo(lexical,SOAP12_HTTP.toString()))
254            return customize(lexical,SOAP12_HTTP);
255        if(belongsTo(lexical,SOAPBindingImpl.X_SOAP12HTTP_BINDING))
256            return customize(lexical,X_SOAP12_HTTP);
257
258        // OK, it's none of the values JAX-WS understands.
259        for( BindingIDFactory f : ServiceFinder.find(BindingIDFactory.class) ) {
260            BindingID r = f.parse(lexical);
261            if(r!=null)
262                return r;
263        }
264
265        // nobody understood this value
266        throw new WebServiceException("Wrong binding ID: "+lexical);
267    }
268
269    private static boolean belongsTo(String lexical, String id) {
270        return lexical.equals(id) || lexical.startsWith(id+'?');
271    }
272
273    /**
274     * Parses parameter portion and returns appropriately populated {@link SOAPHTTPImpl}
275     */
276    private static SOAPHTTPImpl customize(String lexical, SOAPHTTPImpl base) {
277        if(lexical.equals(base.toString()))
278            return base;
279
280        // otherwise we must have query parameter
281        // we assume the spec won't define any tricky parameters that require
282        // complicated handling (such as %HH or non-ASCII char), so this parser
283        // is quite simple-minded.
284        SOAPHTTPImpl r = new SOAPHTTPImpl(base.getSOAPVersion(), lexical, base.canGenerateWSDL());
285        try {
286            // With X_SOAP12_HTTP, base != lexical and lexical does n't have any query string
287            if(lexical.indexOf('?') == -1) {
288                return r;
289            }
290            String query = URLDecoder.decode(lexical.substring(lexical.indexOf('?')+1),"UTF-8");
291            for( String token : query.split("&") ) {
292                int idx = token.indexOf('=');
293                if(idx<0)
294                    throw new WebServiceException("Malformed binding ID (no '=' in "+token+")");
295                r.parameters.put(token.substring(0,idx),token.substring(idx+1));
296            }
297        } catch (UnsupportedEncodingException e) {
298            throw new AssertionError(e);    // UTF-8 is supported everywhere
299        }
300
301        return r;
302    }
303
304
305    /**
306     * Figures out the binding from {@link BindingType} annotation.
307     *
308     * @return
309     *      default to {@link BindingID#SOAP11_HTTP}, if no such annotation is present.
310     * @see #parse(String)
311     */
312    public static @NotNull BindingID parse(Class<?> implClass) {
313        BindingType bindingType = implClass.getAnnotation(BindingType.class);
314        if (bindingType != null) {
315            String bindingId = bindingType.value();
316            if (bindingId.length() > 0) {
317                return BindingID.parse(bindingId);
318            }
319        }
320        return SOAP11_HTTP;
321    }
322
323    /**
324     * Constant that represents implementation specific SOAP1.2/HTTP which is
325     * used to generate non-standard WSDLs
326     */
327    public static final SOAPHTTPImpl X_SOAP12_HTTP = new SOAPHTTPImpl(
328        SOAPVersion.SOAP_12, SOAPBindingImpl.X_SOAP12HTTP_BINDING, true);
329
330    /**
331     * Constant that represents SOAP1.2/HTTP.
332     */
333    public static final SOAPHTTPImpl SOAP12_HTTP = new SOAPHTTPImpl(
334        SOAPVersion.SOAP_12, SOAPBinding.SOAP12HTTP_BINDING, true);
335    /**
336     * Constant that represents SOAP1.1/HTTP.
337     */
338    public static final SOAPHTTPImpl SOAP11_HTTP = new SOAPHTTPImpl(
339        SOAPVersion.SOAP_11, SOAPBinding.SOAP11HTTP_BINDING, true);
340
341    /**
342     * Constant that represents SOAP1.2/HTTP.
343     */
344    public static final SOAPHTTPImpl SOAP12_HTTP_MTOM = new SOAPHTTPImpl(
345        SOAPVersion.SOAP_12, SOAPBinding.SOAP12HTTP_MTOM_BINDING, true, true);
346    /**
347     * Constant that represents SOAP1.1/HTTP.
348     */
349    public static final SOAPHTTPImpl SOAP11_HTTP_MTOM = new SOAPHTTPImpl(
350        SOAPVersion.SOAP_11, SOAPBinding.SOAP11HTTP_MTOM_BINDING, true, true);
351
352
353    /**
354     * Constant that represents REST.
355     */
356    public static final BindingID XML_HTTP = new Impl(SOAPVersion.SOAP_11, HTTPBinding.HTTP_BINDING,false) {
357        @Override
358        public Codec createEncoder(WSBinding binding) {
359            return new XMLHTTPBindingCodec(binding.getFeatures());
360        }
361    };
362
363    /**
364     * Constant that represents REST.
365     */
366    private static final BindingID REST_HTTP = new Impl(SOAPVersion.SOAP_11, JAXWSProperties.REST_BINDING,true) {
367        @Override
368        public Codec createEncoder(WSBinding binding) {
369            return new XMLHTTPBindingCodec(binding.getFeatures());
370        }
371    };
372
373    private static abstract class Impl extends BindingID {
374        final SOAPVersion version;
375        private final String lexical;
376        private final boolean canGenerateWSDL;
377
378        public Impl(SOAPVersion version, String lexical, boolean canGenerateWSDL) {
379            this.version = version;
380            this.lexical = lexical;
381            this.canGenerateWSDL = canGenerateWSDL;
382        }
383
384        @Override
385        public SOAPVersion getSOAPVersion() {
386            return version;
387        }
388
389        @Override
390        public String toString() {
391            return lexical;
392        }
393
394        @Deprecated
395        @Override
396        public boolean canGenerateWSDL() {
397            return canGenerateWSDL;
398        }
399    }
400
401    /**
402     * Internal implementation for SOAP/HTTP.
403     */
404    private static final class SOAPHTTPImpl extends Impl implements Cloneable {
405        /*final*/ Map<String,String> parameters = new HashMap<String,String>();
406
407        static final String MTOM_PARAM = "mtom";
408
409        public SOAPHTTPImpl(SOAPVersion version, String lexical, boolean canGenerateWSDL) {
410            super(version, lexical, canGenerateWSDL);
411        }
412
413        public SOAPHTTPImpl(SOAPVersion version, String lexical, boolean canGenerateWSDL,
414                           boolean mtomEnabled) {
415            this(version, lexical, canGenerateWSDL);
416            String mtomStr = mtomEnabled ? "true" : "false";
417            parameters.put(MTOM_PARAM, mtomStr);
418        }
419
420        public @NotNull @Override Codec createEncoder(WSBinding binding) {
421            return new SOAPBindingCodec(binding.getFeatures());
422        }
423
424        private Boolean isMTOMEnabled() {
425            String mtom = parameters.get(MTOM_PARAM);
426            return mtom==null?null:Boolean.valueOf(mtom);
427        }
428
429        @Override
430        public WebServiceFeatureList createBuiltinFeatureList() {
431            WebServiceFeatureList r=super.createBuiltinFeatureList();
432            Boolean mtom = isMTOMEnabled();
433            if(mtom != null)
434                r.add(new MTOMFeature(mtom));
435            return r;
436        }
437
438        @Override
439        public String getParameter(String parameterName, String defaultValue) {
440            if (parameters.get(parameterName) == null)
441                return super.getParameter(parameterName, defaultValue);
442            return parameters.get(parameterName);
443        }
444
445        @Override
446        public SOAPHTTPImpl clone() throws CloneNotSupportedException {
447            return (SOAPHTTPImpl) super.clone();
448        }
449    }
450}
451