1 /*
   2  * Copyright (c) 1997, 2012, 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 
  26 package com.sun.xml.internal.ws.api;
  27 
  28 import com.sun.istack.internal.NotNull;
  29 import com.sun.xml.internal.ws.api.message.Message;
  30 import com.sun.xml.internal.ws.api.pipe.Codec;
  31 import com.sun.xml.internal.ws.api.pipe.Tube;
  32 import com.sun.xml.internal.ws.binding.BindingImpl;
  33 import com.sun.xml.internal.ws.binding.SOAPBindingImpl;
  34 import com.sun.xml.internal.ws.binding.WebServiceFeatureList;
  35 import com.sun.xml.internal.ws.encoding.SOAPBindingCodec;
  36 import com.sun.xml.internal.ws.encoding.XMLHTTPBindingCodec;
  37 import com.sun.xml.internal.ws.encoding.soap.streaming.SOAPNamespaceConstants;
  38 import com.sun.xml.internal.ws.util.ServiceFinder;
  39 import com.sun.xml.internal.ws.developer.JAXWSProperties;
  40 
  41 import javax.xml.ws.BindingType;
  42 import javax.xml.ws.WebServiceException;
  43 import javax.xml.ws.WebServiceFeature;
  44 import javax.xml.ws.handler.Handler;
  45 import javax.xml.ws.http.HTTPBinding;
  46 import javax.xml.ws.soap.MTOMFeature;
  47 import javax.xml.ws.soap.SOAPBinding;
  48 
  49 import java.io.UnsupportedEncodingException;
  50 import java.net.URL;
  51 import java.net.URLDecoder;
  52 import java.util.HashMap;
  53 import 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  */
  89 public 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      * <tt>"{@value SOAPBinding#SOAP11HTTP_MTOM_BINDING}"</tt>
 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 <tt>?mtom=true</tt>) 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 }