1 /*
   2  * Copyright (c) 1997, 2014, 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.transport.http;
  27 
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.OutputStream;
  32 import java.io.OutputStreamWriter;
  33 import java.io.PrintWriter;
  34 import java.net.HttpURLConnection;
  35 import java.util.AbstractMap;
  36 import java.util.Collection;
  37 import java.util.Collections;
  38 import java.util.HashMap;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.Map.Entry;
  42 import java.util.Set;
  43 import java.util.TreeMap;
  44 import java.util.logging.Level;
  45 import java.util.logging.Logger;
  46 
  47 import javax.xml.ws.Binding;
  48 import javax.xml.ws.WebServiceException;
  49 import javax.xml.ws.http.HTTPBinding;
  50 
  51 import com.oracle.webservices.internal.api.message.PropertySet;
  52 import com.sun.istack.internal.NotNull;
  53 import com.sun.istack.internal.Nullable;
  54 import com.sun.xml.internal.ws.api.Component;
  55 import com.sun.xml.internal.ws.api.EndpointAddress;
  56 import com.sun.xml.internal.ws.api.SOAPVersion;
  57 import com.sun.xml.internal.ws.api.addressing.AddressingVersion;
  58 import com.sun.xml.internal.ws.api.addressing.NonAnonymousResponseProcessor;
  59 import com.sun.xml.internal.ws.api.ha.HaInfo;
  60 import com.sun.xml.internal.ws.api.message.ExceptionHasMessage;
  61 import com.sun.xml.internal.ws.api.message.Message;
  62 import com.sun.xml.internal.ws.api.message.Packet;
  63 import com.sun.xml.internal.ws.api.pipe.Codec;
  64 import com.sun.xml.internal.ws.api.pipe.ContentType;
  65 import com.sun.xml.internal.ws.api.server.AbstractServerAsyncTransport;
  66 import com.sun.xml.internal.ws.api.server.Adapter;
  67 import com.sun.xml.internal.ws.api.server.BoundEndpoint;
  68 import com.sun.xml.internal.ws.api.server.DocumentAddressResolver;
  69 import com.sun.xml.internal.ws.api.server.Module;
  70 import com.sun.xml.internal.ws.api.server.PortAddressResolver;
  71 import com.sun.xml.internal.ws.api.server.SDDocument;
  72 import com.sun.xml.internal.ws.api.server.ServiceDefinition;
  73 import com.sun.xml.internal.ws.api.server.TransportBackChannel;
  74 import com.sun.xml.internal.ws.api.server.WSEndpoint;
  75 import com.sun.xml.internal.ws.api.server.WebServiceContextDelegate;
  76 import com.sun.xml.internal.ws.fault.SOAPFaultBuilder;
  77 import com.sun.xml.internal.ws.resources.WsservletMessages;
  78 import com.sun.xml.internal.ws.server.UnsupportedMediaException;
  79 import com.sun.xml.internal.ws.util.ByteArrayBuffer;
  80 import com.sun.xml.internal.ws.util.Pool;
  81 
  82 
  83 /**
  84  * {@link com.sun.xml.internal.ws.api.server.Adapter} that receives messages in HTTP.
  85  *
  86  * <p>
  87  * This object also assigns unique query string (such as "xsd=1") to
  88  * each {@link com.sun.xml.internal.ws.api.server.SDDocument} so that they can be served by HTTP GET requests.
  89  *
  90  * @author Kohsuke Kawaguchi
  91  * @author Jitendra Kotamraju
  92  */
  93 public class HttpAdapter extends Adapter<HttpAdapter.HttpToolkit> {
  94 
  95     private static final Logger LOGGER = Logger.getLogger(HttpAdapter.class.getName());
  96 
  97     /**
  98      * {@link com.sun.xml.internal.ws.api.server.SDDocument}s keyed by the query string like "?abc".
  99      * Used for serving documents via HTTP GET.
 100      *
 101      * Empty if the endpoint doesn't have {@link com.sun.xml.internal.ws.api.server.ServiceDefinition}.
 102      * Read-only.
 103      */
 104     protected Map<String,SDDocument> wsdls;
 105 
 106     /**
 107      * Reverse map of {@link #wsdls}. Read-only.
 108      */
 109     private Map<SDDocument,String> revWsdls;
 110 
 111     /**
 112      * A reference to the service definition from which the map of wsdls/revWsdls
 113      * was created. This allows us to establish if the service definition documents
 114      * have changed in the meantime.
 115      */
 116     private ServiceDefinition serviceDefinition = null;
 117 
 118     public final HttpAdapterList<? extends HttpAdapter> owner;
 119 
 120     /**
 121      * Servlet URL pattern with which this {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} is associated.
 122      */
 123     public final String urlPattern;
 124 
 125     protected boolean stickyCookie;
 126 
 127     protected boolean disableJreplicaCookie = false;
 128 
 129     /**
 130      * Creates a lone {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} that does not know of any other
 131      * {@link com.sun.xml.internal.ws.transport.http.HttpAdapter}s.
 132      *
 133      * This is convenient for creating an {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} for an environment
 134      * where they don't know each other (such as JavaSE deployment.)
 135      *
 136      * @param endpoint web service endpoint
 137      * @return singe adapter to process HTTP messages
 138      */
 139     public static HttpAdapter createAlone(WSEndpoint endpoint) {
 140         return new DummyList().createAdapter("","",endpoint);
 141     }
 142 
 143     /**
 144      * @deprecated
 145      *      remove as soon as we can update the test util.
 146      * @param endpoint web service endpoint
 147      * @param owner list of related adapters
 148      */
 149     protected HttpAdapter(WSEndpoint endpoint,
 150                           HttpAdapterList<? extends HttpAdapter> owner) {
 151         this(endpoint,owner,null);
 152     }
 153 
 154     protected HttpAdapter(WSEndpoint endpoint,
 155                           HttpAdapterList<? extends HttpAdapter> owner,
 156                           String urlPattern) {
 157         super(endpoint);
 158         this.owner = owner;
 159         this.urlPattern = urlPattern;
 160 
 161         initWSDLMap(endpoint.getServiceDefinition());
 162     }
 163 
 164     /**
 165      * Return the last known service definition of the endpoint.
 166      *
 167      * @return The service definition of the endpoint
 168      */
 169     public ServiceDefinition getServiceDefinition() {
 170         return this.serviceDefinition;
 171     }
 172 
 173     /**
 174      * Fill in WSDL map.
 175      *
 176      * @param sdef service definition
 177      */
 178     public final void initWSDLMap(final ServiceDefinition serviceDefinition) {
 179         this.serviceDefinition = serviceDefinition;
 180         if(serviceDefinition==null) {
 181             wsdls = Collections.emptyMap();
 182             revWsdls = Collections.emptyMap();
 183         } else {
 184             wsdls = new AbstractMap<String, SDDocument>() {
 185                 private Map<String, SDDocument> delegate = null;
 186 
 187                 private synchronized Map<String, SDDocument> delegate() {
 188                     if (delegate != null)
 189                         return delegate;
 190 
 191                     delegate = new HashMap<String, SDDocument>();  // wsdl=1 --> Doc
 192                     // Sort WSDL, Schema documents based on SystemId so that the same
 193                     // document gets wsdl=x mapping
 194                     Map<String, SDDocument> systemIds = new TreeMap<String, SDDocument>();
 195                     for (SDDocument sdd : serviceDefinition) {
 196                         if (sdd == serviceDefinition.getPrimary()) { // No sorting for Primary WSDL
 197                             delegate.put("wsdl", sdd);
 198                             delegate.put("WSDL", sdd);
 199                         } else {
 200                             systemIds.put(sdd.getURL().toString(), sdd);
 201                         }
 202                     }
 203 
 204                     int wsdlnum = 1;
 205                     int xsdnum = 1;
 206                     for (Entry<String, SDDocument> e : systemIds.entrySet()) {
 207                         SDDocument sdd = e.getValue();
 208                         if (sdd.isWSDL()) {
 209                             delegate.put("wsdl="+(wsdlnum++),sdd);
 210                         }
 211                         if (sdd.isSchema()) {
 212                             delegate.put("xsd="+(xsdnum++),sdd);
 213                         }
 214                     }
 215 
 216                     return delegate;
 217                 }
 218 
 219                 @Override
 220                 public void clear() {
 221                     delegate().clear();
 222                 }
 223 
 224                 @Override
 225                 public boolean containsKey(Object arg0) {
 226                     return delegate().containsKey(arg0);
 227                 }
 228 
 229                 @Override
 230                 public boolean containsValue(Object arg0) {
 231                     return delegate.containsValue(arg0);
 232                 }
 233 
 234                 @Override
 235                 public SDDocument get(Object arg0) {
 236                     return delegate().get(arg0);
 237                 }
 238 
 239                 @Override
 240                 public boolean isEmpty() {
 241                     return delegate().isEmpty();
 242                 }
 243 
 244                 @Override
 245                 public Set<String> keySet() {
 246                     return delegate().keySet();
 247                 }
 248 
 249                 @Override
 250                 public SDDocument put(String arg0, SDDocument arg1) {
 251                     return delegate().put(arg0, arg1);
 252                 }
 253 
 254                 @Override
 255                 public void putAll(
 256                         Map<? extends String, ? extends SDDocument> arg0) {
 257                     delegate().putAll(arg0);
 258                 }
 259 
 260                 @Override
 261                 public SDDocument remove(Object arg0) {
 262                     return delegate().remove(arg0);
 263                 }
 264 
 265                 @Override
 266                 public int size() {
 267                     return delegate().size();
 268                 }
 269 
 270                 @Override
 271                 public Collection<SDDocument> values() {
 272                     return delegate().values();
 273                 }
 274 
 275                 @Override
 276                 public Set<java.util.Map.Entry<String, SDDocument>> entrySet() {
 277                     return delegate().entrySet();
 278                 }
 279             };
 280 
 281             revWsdls = new AbstractMap<SDDocument, String>() {
 282                 private Map<SDDocument, String> delegate = null;
 283 
 284                 private synchronized Map<SDDocument, String> delegate() {
 285                     if (delegate != null)
 286                         return delegate;
 287 
 288                     delegate = new HashMap<SDDocument,String>();    // Doc --> wsdl=1
 289                     for (Entry<String,SDDocument> e : wsdls.entrySet()) {
 290                         if (!e.getKey().equals("WSDL")) {           // map Doc --> wsdl, not WSDL
 291                             delegate.put(e.getValue(),e.getKey());
 292                         }
 293                     }
 294 
 295                     return delegate;
 296                 }
 297 
 298                 @Override
 299                 public void clear() {
 300                     delegate().clear();
 301                 }
 302 
 303                 @Override
 304                 public boolean containsKey(Object key) {
 305                     return delegate().containsKey(key);
 306                 }
 307 
 308                 @Override
 309                 public boolean containsValue(Object value) {
 310                     return delegate().containsValue(value);
 311                 }
 312 
 313                 @Override
 314                 public Set<Entry<SDDocument, String>> entrySet() {
 315                     return delegate().entrySet();
 316                 }
 317 
 318                 @Override
 319                 public String get(Object key) {
 320                     return delegate().get(key);
 321                 }
 322 
 323                 @Override
 324                 public boolean isEmpty() {
 325                     // TODO Auto-generated method stub
 326                     return super.isEmpty();
 327                 }
 328 
 329                 @Override
 330                 public Set<SDDocument> keySet() {
 331                     return delegate().keySet();
 332                 }
 333 
 334                 @Override
 335                 public String put(SDDocument key, String value) {
 336                     return delegate().put(key, value);
 337                 }
 338 
 339                 @Override
 340                 public void putAll(Map<? extends SDDocument, ? extends String> m) {
 341                     delegate().putAll(m);
 342                 }
 343 
 344                 @Override
 345                 public String remove(Object key) {
 346                     return delegate().remove(key);
 347                 }
 348 
 349                 @Override
 350                 public int size() {
 351                     return delegate().size();
 352                 }
 353 
 354                 @Override
 355                 public Collection<String> values() {
 356                     return delegate().values();
 357                 }
 358             };
 359         }
 360     }
 361 
 362     /**
 363      * Returns the "/abc/def/ghi" portion if
 364      * the URL pattern is "/abc/def/ghi/*".
 365      */
 366     public String getValidPath() {
 367         if (urlPattern.endsWith("/*")) {
 368             return urlPattern.substring(0, urlPattern.length() - 2);
 369         } else {
 370             return urlPattern;
 371         }
 372     }
 373 
 374     @Override
 375     protected HttpToolkit createToolkit() {
 376         return new HttpToolkit();
 377     }
 378 
 379     /**
 380      * Receives the incoming HTTP connection and dispatches
 381      * it to JAX-WS. This method returns when JAX-WS completes
 382      * processing the request and the whole reply is written
 383      * to {@link WSHTTPConnection}.
 384      *
 385      * <p>
 386      * This method is invoked by the lower-level HTTP stack,
 387      * and "connection" here is an HTTP connection.
 388      *
 389      * <p>
 390      * To populate a request {@link com.sun.xml.internal.ws.api.message.Packet} with more info,
 391      * define {@link com.oracle.webservices.internal.api.message.PropertySet.Property properties} on
 392      * {@link WSHTTPConnection}.
 393      *
 394      * @param connection to receive/send HTTP messages for web service endpoints
 395      * @throws java.io.IOException when I/O errors happen
 396      */
 397     public void handle(@NotNull WSHTTPConnection connection) throws IOException {
 398         if (handleGet(connection)) {
 399             return;
 400         }
 401 
 402         // Make sure the Toolkit is recycled by the same pool instance from which it was taken
 403         final Pool<HttpToolkit> currentPool = getPool();
 404         // normal request handling
 405         final HttpToolkit tk = currentPool.take();
 406         try {
 407             tk.handle(connection);
 408         } finally {
 409             currentPool.recycle(tk);
 410         }
 411     }
 412 
 413     public boolean handleGet(@NotNull WSHTTPConnection connection) throws IOException {
 414         if (connection.getRequestMethod().equals("GET")) {
 415             // metadata query. let the interceptor run
 416             for (Component c : endpoint.getComponents()) {
 417                 HttpMetadataPublisher spi = c.getSPI(HttpMetadataPublisher.class);
 418                 if (spi != null && spi.handleMetadataRequest(this, connection)) {
 419                     return true;
 420                 } // handled
 421             }
 422 
 423             if (isMetadataQuery(connection.getQueryString())) {
 424                 // Sends published WSDL and schema documents as the default action.
 425                 publishWSDL(connection);
 426                 return true;
 427             }
 428 
 429             Binding binding = getEndpoint().getBinding();
 430             if (!(binding instanceof HTTPBinding)) {
 431                 // Writes HTML page with all the endpoint descriptions
 432                 writeWebServicesHtmlPage(connection);
 433                 return true;
 434             }
 435         } else if (connection.getRequestMethod().equals("HEAD")) {
 436             connection.getInput().close();
 437             Binding binding = getEndpoint().getBinding();
 438             if (isMetadataQuery(connection.getQueryString())) {
 439                 SDDocument doc = wsdls.get(connection.getQueryString());
 440                 connection.setStatus(doc != null
 441                         ? HttpURLConnection.HTTP_OK
 442                         : HttpURLConnection.HTTP_NOT_FOUND);
 443                 connection.getOutput().close();
 444                 connection.close();
 445                 return true;
 446             } else if (!(binding instanceof HTTPBinding)) {
 447                 connection.setStatus(HttpURLConnection.HTTP_NOT_FOUND);
 448                 connection.getOutput().close();
 449                 connection.close();
 450                 return true;
 451             }
 452             // Let the endpoint handle for HTTPBinding
 453         }
 454 
 455         return false;
 456 
 457     }
 458     /*
 459      *
 460      * @param con
 461      * @param codec
 462      * @return
 463      * @throws IOException
 464      *         ExceptionHasMessage exception that contains particular fault message
 465      *         UnsupportedMediaException to indicate to send 415 error code
 466      */
 467     private Packet decodePacket(@NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException {
 468         String ct = con.getRequestHeader("Content-Type");
 469         InputStream in = con.getInput();
 470         Packet packet = new Packet();
 471         packet.soapAction = fixQuotesAroundSoapAction(con.getRequestHeader("SOAPAction"));
 472         packet.wasTransportSecure = con.isSecure();
 473         packet.acceptableMimeTypes = con.getRequestHeader("Accept");
 474         packet.addSatellite(con);
 475         addSatellites(packet);
 476         packet.isAdapterDeliversNonAnonymousResponse = true;
 477         packet.component = this;
 478         packet.transportBackChannel = new Oneway(con);
 479         packet.webServiceContextDelegate = con.getWebServiceContextDelegate();
 480         packet.setState(Packet.State.ServerRequest);
 481         if (dump || LOGGER.isLoggable(Level.FINER)) {
 482             ByteArrayBuffer buf = new ByteArrayBuffer();
 483             buf.write(in);
 484             in.close();
 485             dump(buf, "HTTP request", con.getRequestHeaders());
 486             in = buf.newInputStream();
 487         }
 488         codec.decode(in, ct, packet);
 489         return packet;
 490     }
 491 
 492     protected void addSatellites(Packet packet) {
 493     }
 494 
 495     /**
 496      * Some stacks may send non WS-I BP 1.2 conforming SoapAction.
 497      * Make sure SOAPAction is quoted as {@link com.sun.xml.internal.ws.api.message.Packet#soapAction} expects quoted soapAction value.
 498      *
 499      * @param soapAction SoapAction HTTP Header
 500      * @return quoted SOAPAction value
 501      */
 502     static public String fixQuotesAroundSoapAction(String soapAction) {
 503         if(soapAction != null && (!soapAction.startsWith("\"") || !soapAction.endsWith("\"")) ) {
 504             if (LOGGER.isLoggable(Level.INFO)) {
 505                 LOGGER.log(Level.INFO, "Received WS-I BP non-conformant Unquoted SoapAction HTTP header: {0}", soapAction);
 506             }
 507             String fixedSoapAction = soapAction;
 508             if(!soapAction.startsWith("\"")) {
 509                 fixedSoapAction = "\"" + fixedSoapAction;
 510             }
 511             if(!soapAction.endsWith("\"")) {
 512                 fixedSoapAction = fixedSoapAction + "\"";
 513             }
 514             return fixedSoapAction;
 515         }
 516         return soapAction;
 517     }
 518 
 519     protected NonAnonymousResponseProcessor getNonAnonymousResponseProcessor() {
 520         return NonAnonymousResponseProcessor.getDefault();
 521     }
 522 
 523     /**
 524      * This method is added for the case of the sub-class wants to override the method to
 525      * print details. E.g. convert soapfault as HTML msg for 403 error connstatus.
 526      * @param os
 527      */
 528     protected void writeClientError(int connStatus, @NotNull OutputStream os, @NotNull Packet packet) throws IOException {
 529         //do nothing
 530     }
 531 
 532     private boolean isClientErrorStatus(int connStatus)
 533     {
 534         return (connStatus == HttpURLConnection.HTTP_FORBIDDEN); // add more for future.
 535     }
 536 
 537     private boolean isNonAnonymousUri(EndpointAddress addr){
 538         return (addr != null) && !addr.toString().equals(AddressingVersion.W3C.anonymousUri) &&
 539                         !addr.toString().equals(AddressingVersion.MEMBER.anonymousUri);
 540     }
 541 
 542     private void encodePacket(@NotNull Packet packet, @NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException {
 543         if (isNonAnonymousUri(packet.endpointAddress) && packet.getMessage() != null) {
 544            try {
 545                 // Message is targeted to non-anonymous response endpoint.
 546                 // After call to non-anonymous processor, typically, packet.getMessage() will be null
 547                 // however, processors could use this pattern to modify the response sent on the back-channel,
 548                 // e.g. send custom HTTP headers with the HTTP 202
 549                     packet = getNonAnonymousResponseProcessor().process(packet);
 550             } catch (RuntimeException re) {
 551                 // if processing by NonAnonymousResponseProcessor fails, new SOAPFaultMessage is created to be sent
 552                 // to back-channel client
 553                 SOAPVersion soapVersion = packet.getBinding().getSOAPVersion();
 554                 Message faultMsg = SOAPFaultBuilder.createSOAPFaultMessage(soapVersion, null, re);
 555                 packet = packet.createServerResponse(faultMsg, packet.endpoint.getPort(), null, packet.endpoint.getBinding());
 556             }
 557         }
 558 
 559         if (con.isClosed()) {
 560             return;                 // Connection is already closed
 561         }
 562         Message responseMessage = packet.getMessage();
 563         addStickyCookie(con);
 564         addReplicaCookie(con, packet);
 565         if (responseMessage == null) {
 566             if (!con.isClosed()) {
 567                 // set the response code if not already set
 568                 // for example, 415 may have been set earlier for Unsupported Content-Type
 569                 if (con.getStatus() == 0) {
 570                     con.setStatus(WSHTTPConnection.ONEWAY);
 571                 }
 572                 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
 573                 if (dump || LOGGER.isLoggable(Level.FINER)) {
 574                     ByteArrayBuffer buf = new ByteArrayBuffer();
 575                     codec.encode(packet, buf);
 576                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
 577                     buf.writeTo(os);
 578                 } else {
 579                     codec.encode(packet, os);
 580                 }
 581                 // close the response channel now
 582                 try {
 583                     os.close(); // no payload
 584                 } catch (IOException e) {
 585                     throw new WebServiceException(e);
 586                 }
 587             }
 588         } else {
 589             if (con.getStatus() == 0) {
 590                 // if the appliation didn't set the status code,
 591                 // set the default one.
 592                 con.setStatus(responseMessage.isFault()
 593                         ? HttpURLConnection.HTTP_INTERNAL_ERROR
 594                         : HttpURLConnection.HTTP_OK);
 595             }
 596 
 597             if (isClientErrorStatus(con.getStatus())) {
 598                 OutputStream os = con.getOutput();
 599                 if (dump || LOGGER.isLoggable(Level.FINER)) {
 600                     ByteArrayBuffer buf = new ByteArrayBuffer();
 601                     writeClientError(con.getStatus(), buf, packet);
 602                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
 603                     buf.writeTo(os);
 604                 } else {
 605                     writeClientError(con.getStatus(), os, packet);
 606                 }
 607                 os.close();
 608                   return;
 609             }
 610 
 611             ContentType contentType = codec.getStaticContentType(packet);
 612             if (contentType != null) {
 613                 con.setContentTypeResponseHeader(contentType.getContentType());
 614                 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
 615                 if (dump || LOGGER.isLoggable(Level.FINER)) {
 616                     ByteArrayBuffer buf = new ByteArrayBuffer();
 617                     codec.encode(packet, buf);
 618                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
 619                     buf.writeTo(os);
 620                 } else {
 621                     codec.encode(packet, os);
 622                 }
 623                 os.close();
 624             } else {
 625 
 626                 ByteArrayBuffer buf = new ByteArrayBuffer();
 627                 contentType = codec.encode(packet, buf);
 628                 con.setContentTypeResponseHeader(contentType.getContentType());
 629                 if (dump || LOGGER.isLoggable(Level.FINER)) {
 630                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
 631                 }
 632                 OutputStream os = con.getOutput();
 633                 buf.writeTo(os);
 634                 os.close();
 635             }
 636         }
 637     }
 638 
 639     /*
 640      * GlassFish Load-balancer plugin always add a header proxy-jroute on
 641      * request being send from load-balancer plugin to server
 642      *
 643      * JROUTE cookie need to be stamped in two cases
 644      * 1 : At the time of session creation. In this case, request will not have
 645      * any JROUTE cookie.
 646      * 2 : At the time of fail-over. In this case, value of proxy-jroute
 647      * header(will point to current instance) and JROUTE cookie(will point to
 648      * previous failed instance) will be different. This logic can be used
 649      * to determine fail-over scenario.
 650      */
 651     private void addStickyCookie(WSHTTPConnection con) {
 652         if (stickyCookie) {
 653             String proxyJroute = con.getRequestHeader("proxy-jroute");
 654             if (proxyJroute == null) {
 655                 // Load-balancer plugin is not front-ending this instance
 656                 return;
 657             }
 658 
 659             String jrouteId = con.getCookie("JROUTE");
 660             if (jrouteId == null || !jrouteId.equals(proxyJroute)) {
 661                 // Initial request or failover
 662                 con.setCookie("JROUTE", proxyJroute);
 663             }
 664         }
 665     }
 666 
 667     private void addReplicaCookie(WSHTTPConnection con, Packet packet) {
 668         if (stickyCookie) {
 669             HaInfo haInfo = null;
 670             if (packet.supports(Packet.HA_INFO)) {
 671                 haInfo = (HaInfo)packet.get(Packet.HA_INFO);
 672             }
 673             if (haInfo != null) {
 674                 con.setCookie("METRO_KEY", haInfo.getKey());
 675                 if (!disableJreplicaCookie) {
 676                     con.setCookie("JREPLICA", haInfo.getReplicaInstance());
 677                 }
 678             }
 679         }
 680     }
 681 
 682     public void invokeAsync(final WSHTTPConnection con) throws IOException {
 683         invokeAsync(con, NO_OP_COMPLETION_CALLBACK);
 684     }
 685 
 686     public void invokeAsync(final WSHTTPConnection con, final CompletionCallback callback) throws IOException {
 687 
 688             if (handleGet(con)) {
 689                 callback.onCompletion();
 690                 return;
 691             }
 692             final Pool<HttpToolkit> currentPool = getPool();
 693             final HttpToolkit tk = currentPool.take();
 694             final Packet request;
 695 
 696             try {
 697 
 698                 request = decodePacket(con, tk.codec);
 699             } catch (ExceptionHasMessage e) {
 700                 LOGGER.log(Level.SEVERE, e.getMessage(), e);
 701                 Packet response = new Packet();
 702                 response.setMessage(e.getFaultMessage());
 703                 encodePacket(response, con, tk.codec);
 704                 currentPool.recycle(tk);
 705                 con.close();
 706                 callback.onCompletion();
 707                 return;
 708             } catch (UnsupportedMediaException e) {
 709                 LOGGER.log(Level.SEVERE, e.getMessage(), e);
 710                 Packet response = new Packet();
 711                 con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);
 712                 encodePacket(response, con, tk.codec);
 713                 currentPool.recycle(tk);
 714                 con.close();
 715                 callback.onCompletion();
 716                 return;
 717             }
 718 
 719             endpoint.process(request, new WSEndpoint.CompletionCallback() {
 720                 @Override
 721                 public void onCompletion(@NotNull Packet response) {
 722                     try {
 723                         try {
 724                             encodePacket(response, con, tk.codec);
 725                         } catch (IOException ioe) {
 726                             LOGGER.log(Level.SEVERE, ioe.getMessage(), ioe);
 727                         }
 728                         currentPool.recycle(tk);
 729                     } finally {
 730                         con.close();
 731                         callback.onCompletion();
 732 
 733                     }
 734                 }
 735             },null);
 736 
 737     }
 738 
 739     public static  final CompletionCallback NO_OP_COMPLETION_CALLBACK = new CompletionCallback() {
 740 
 741         @Override
 742         public void onCompletion() {
 743             //NO-OP
 744         }
 745     };
 746 
 747     public interface CompletionCallback{
 748         void onCompletion();
 749     }
 750 
 751     final class AsyncTransport extends AbstractServerAsyncTransport<WSHTTPConnection> {
 752 
 753         public AsyncTransport() {
 754             super(endpoint);
 755         }
 756 
 757         public void handleAsync(WSHTTPConnection con) throws IOException {
 758             super.handle(con);
 759         }
 760 
 761         @Override
 762         protected void encodePacket(WSHTTPConnection con, @NotNull Packet packet, @NotNull Codec codec) throws IOException {
 763             HttpAdapter.this.encodePacket(packet, con, codec);
 764         }
 765 
 766         protected @Override @Nullable String getAcceptableMimeTypes(WSHTTPConnection con) {
 767             return null;
 768         }
 769 
 770         protected @Override @Nullable TransportBackChannel getTransportBackChannel(WSHTTPConnection con) {
 771             return new Oneway(con);
 772         }
 773 
 774         protected @Override @NotNull
 775         PropertySet getPropertySet(WSHTTPConnection con) {
 776             return con;
 777         }
 778 
 779         protected @Override @NotNull WebServiceContextDelegate getWebServiceContextDelegate(WSHTTPConnection con) {
 780             return con.getWebServiceContextDelegate();
 781         }
 782     }
 783 
 784     static final class Oneway implements TransportBackChannel {
 785         WSHTTPConnection con;
 786         boolean closed;
 787 
 788         Oneway(WSHTTPConnection con) {
 789             this.con = con;
 790         }
 791         @Override
 792         public void close() {
 793             if (!closed) {
 794                 closed = true;
 795                 // close the response channel now
 796                 if (con.getStatus() == 0) {
 797                     // if the appliation didn't set the status code,
 798                     // set the default one.
 799                     con.setStatus(WSHTTPConnection.ONEWAY);
 800                 }
 801 
 802                 OutputStream output = null;
 803                 try {
 804                     output = con.getOutput();
 805                 } catch (IOException e) {
 806                     // no-op
 807                 }
 808 
 809                 if (dump || LOGGER.isLoggable(Level.FINER)) {
 810                     try {
 811                         ByteArrayBuffer buf = new ByteArrayBuffer();
 812                         dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
 813                     } catch (Exception e) {
 814                         throw new WebServiceException(e.toString(), e);
 815                     }
 816                 }
 817 
 818                 if (output != null) {
 819                         try {
 820                                 output.close(); // no payload
 821                         } catch (IOException e) {
 822                                 throw new WebServiceException(e);
 823                         }
 824                 }
 825                 con.close();
 826             }
 827         }
 828     }
 829 
 830     final class HttpToolkit extends Adapter.Toolkit {
 831         public void handle(WSHTTPConnection con) throws IOException {
 832             try {
 833                 boolean invoke = false;
 834                 Packet packet;
 835                 try {
 836                     packet = decodePacket(con, codec);
 837                     invoke = true;
 838                 } catch(Exception e) {
 839                     packet = new Packet();
 840                     if (e instanceof ExceptionHasMessage) {
 841                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
 842                         packet.setMessage(((ExceptionHasMessage)e).getFaultMessage());
 843                     } else if (e instanceof UnsupportedMediaException) {
 844                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
 845                         con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);
 846                     } else {
 847                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
 848                         con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
 849                     }
 850                 }
 851                 if (invoke) {
 852                     try {
 853                         packet = head.process(packet, con.getWebServiceContextDelegate(),
 854                                 packet.transportBackChannel);
 855                     } catch(Throwable e) {
 856                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
 857                         if (!con.isClosed()) {
 858                             writeInternalServerError(con);
 859                         }
 860                         return;
 861                     }
 862                 }
 863                 encodePacket(packet, con, codec);
 864             } finally {
 865                 if (!con.isClosed()) {
 866                     if (LOGGER.isLoggable(Level.FINE)) {
 867                         LOGGER.log(Level.FINE, "Closing HTTP Connection with status: {0}", con.getStatus());
 868                     }
 869                     con.close();
 870                 }
 871             }
 872         }
 873     }
 874 
 875     /**
 876      * Returns true if the given query string is for metadata request.
 877      *
 878      * @param query
 879      *      String like "xsd=1" or "perhaps=some&amp;unrelated=query".
 880      *      Can be null.
 881      * @return true for metadata requests
 882      *         false for web service requests
 883      */
 884     private boolean isMetadataQuery(String query) {
 885         // we intentionally return true even if documents don't exist,
 886         // so that they get 404.
 887         return query != null && (query.equals("WSDL") || query.startsWith("wsdl") || query.startsWith("xsd="));
 888     }
 889 
 890     /**
 891      * Sends out the WSDL (and other referenced documents)
 892      * in response to the GET requests to URLs like "?wsdl" or "?xsd=2".
 893      *
 894      * @param con
 895      *      The connection to which the data will be sent.
 896      *
 897      * @throws java.io.IOException when I/O errors happen
 898      */
 899     public void publishWSDL(@NotNull WSHTTPConnection con) throws IOException {
 900         con.getInput().close();
 901 
 902         SDDocument doc = wsdls.get(con.getQueryString());
 903         if (doc == null) {
 904             writeNotFoundErrorPage(con,"Invalid Request");
 905             return;
 906         }
 907 
 908         con.setStatus(HttpURLConnection.HTTP_OK);
 909         con.setContentTypeResponseHeader("text/xml;charset=utf-8");
 910 
 911         OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
 912 
 913         PortAddressResolver portAddressResolver = getPortAddressResolver(con.getBaseAddress());
 914         DocumentAddressResolver resolver = getDocumentAddressResolver(portAddressResolver);
 915 
 916         doc.writeTo(portAddressResolver, resolver, os);
 917         os.close();
 918     }
 919 
 920     public PortAddressResolver getPortAddressResolver(String baseAddress) {
 921         return owner.createPortAddressResolver(baseAddress, endpoint.getImplementationClass());
 922     }
 923 
 924     public DocumentAddressResolver getDocumentAddressResolver(
 925                         PortAddressResolver portAddressResolver) {
 926         final String address = portAddressResolver.getAddressFor(endpoint.getServiceName(), endpoint.getPortName().getLocalPart());
 927         assert address != null;
 928         return new DocumentAddressResolver() {
 929             @Override
 930             public String getRelativeAddressFor(@NotNull SDDocument current, @NotNull SDDocument referenced) {
 931                 // the map on endpoint should account for all SDDocument
 932                 assert revWsdls.containsKey(referenced);
 933                 return address+'?'+ revWsdls.get(referenced);
 934             }
 935         };
 936     }
 937 
 938     /**
 939      * HTTP/1.0 connections require Content-Length. So just buffer to find out
 940      * the length.
 941      */
 942     private final static class Http10OutputStream extends ByteArrayBuffer {
 943         private final WSHTTPConnection con;
 944 
 945         Http10OutputStream(WSHTTPConnection con) {
 946             this.con = con;
 947         }
 948 
 949         @Override
 950         public void close() throws IOException {
 951             super.close();
 952             con.setContentLengthResponseHeader(size());
 953             OutputStream os = con.getOutput();
 954             writeTo(os);
 955             os.close();
 956         }
 957     }
 958 
 959     private void writeNotFoundErrorPage(WSHTTPConnection con, String message) throws IOException {
 960         con.setStatus(HttpURLConnection.HTTP_NOT_FOUND);
 961         con.setContentTypeResponseHeader("text/html; charset=utf-8");
 962 
 963         PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8"));
 964         out.println("<html>");
 965         out.println("<head><title>");
 966         out.println(WsservletMessages.SERVLET_HTML_TITLE());
 967         out.println("</title></head>");
 968         out.println("<body>");
 969         out.println(WsservletMessages.SERVLET_HTML_NOT_FOUND(message));
 970         out.println("</body>");
 971         out.println("</html>");
 972         out.close();
 973     }
 974 
 975     private void writeInternalServerError(WSHTTPConnection con) throws IOException {
 976         con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
 977         con.getOutput().close();        // Sets the status code
 978     }
 979 
 980     private static final class DummyList extends HttpAdapterList<HttpAdapter> {
 981         @Override
 982         protected HttpAdapter createHttpAdapter(String name, String urlPattern, WSEndpoint<?> endpoint) {
 983             return new HttpAdapter(endpoint,this,urlPattern);
 984         }
 985     }
 986 
 987     private static void dump(ByteArrayBuffer buf, String caption, Map<String, List<String>> headers) throws IOException {
 988         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 989         PrintWriter pw = new PrintWriter(baos, true);
 990         pw.println("---["+caption +"]---");
 991         if (headers != null) {
 992             for (Entry<String, List<String>> header : headers.entrySet()) {
 993                 if (header.getValue().isEmpty()) {
 994                     // I don't think this is legal, but let's just dump it,
 995                     // as the point of the dump is to uncover problems.
 996                     pw.println(header.getValue());
 997                 } else {
 998                     for (String value : header.getValue()) {
 999                         pw.println(header.getKey() + ": " + value);
1000                     }
1001                 }
1002             }
1003         }
1004         if (buf.size() > dump_threshold) {
1005             byte[] b = buf.getRawData();
1006             baos.write(b, 0, dump_threshold);
1007             pw.println();
1008             pw.println(WsservletMessages.MESSAGE_TOO_LONG(HttpAdapter.class.getName() + ".dumpTreshold"));
1009         } else {
1010             buf.writeTo(baos);
1011         }
1012         pw.println("--------------------");
1013 
1014         String msg = baos.toString();
1015         if (dump) {
1016           System.out.println(msg);
1017         }
1018         if (LOGGER.isLoggable(Level.FINER)) {
1019           LOGGER.log(Level.FINER, msg);
1020         }
1021     }
1022 
1023     /*
1024      * Generates the listing of all services.
1025      */
1026     private void writeWebServicesHtmlPage(WSHTTPConnection con) throws IOException {
1027         if (!publishStatusPage) {
1028             return;
1029         }
1030 
1031         // TODO: resurrect the ability to localize according to the current request.
1032 
1033         con.getInput().close();
1034 
1035         // standard browsable page
1036         con.setStatus(WSHTTPConnection.OK);
1037         con.setContentTypeResponseHeader("text/html; charset=utf-8");
1038 
1039         PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8"));
1040         out.println("<html>");
1041         out.println("<head><title>");
1042         // out.println("Web Services");
1043         out.println(WsservletMessages.SERVLET_HTML_TITLE());
1044         out.println("</title></head>");
1045         out.println("<body>");
1046         // out.println("<h1>Web Services</h1>");
1047         out.println(WsservletMessages.SERVLET_HTML_TITLE_2());
1048 
1049         // what endpoints do we have in this system?
1050         Module module = getEndpoint().getContainer().getSPI(Module.class);
1051         List<BoundEndpoint> endpoints = Collections.emptyList();
1052         if(module!=null) {
1053             endpoints = module.getBoundEndpoints();
1054         }
1055 
1056         if (endpoints.isEmpty()) {
1057             // out.println("<p>No JAX-WS context information available.</p>");
1058             out.println(WsservletMessages.SERVLET_HTML_NO_INFO_AVAILABLE());
1059         } else {
1060             out.println("<table width='100%' border='1'>");
1061             out.println("<tr>");
1062             out.println("<td>");
1063             // out.println("Endpoint");
1064             out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_PORT_NAME());
1065             out.println("</td>");
1066 
1067             out.println("<td>");
1068             // out.println("Information");
1069             out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_INFORMATION());
1070             out.println("</td>");
1071             out.println("</tr>");
1072 
1073             for (BoundEndpoint a : endpoints) {
1074                 String endpointAddress = a.getAddress(con.getBaseAddress()).toString();
1075                 out.println("<tr>");
1076 
1077                 out.println("<td>");
1078                 out.println(WsservletMessages.SERVLET_HTML_ENDPOINT_TABLE(
1079                     a.getEndpoint().getServiceName(),
1080                     a.getEndpoint().getPortName()
1081                 ));
1082                 out.println("</td>");
1083 
1084                 out.println("<td>");
1085                 out.println(WsservletMessages.SERVLET_HTML_INFORMATION_TABLE(
1086                     endpointAddress,
1087                     a.getEndpoint().getImplementationClass().getName()
1088                 ));
1089                 out.println("</td>");
1090 
1091                 out.println("</tr>");
1092             }
1093             out.println("</table>");
1094         }
1095         out.println("</body>");
1096         out.println("</html>");
1097         out.close();
1098     }
1099 
1100     /**
1101      * Dumps what goes across HTTP transport.
1102      */
1103     public static volatile boolean dump = false;
1104 
1105     public static volatile int dump_threshold = 4096;
1106 
1107     public static volatile boolean publishStatusPage = true;
1108 
1109     public static synchronized void setPublishStatus(boolean publish) {
1110         publishStatusPage = publish;
1111     }
1112 
1113     static {
1114         try {
1115             dump = Boolean.getBoolean(HttpAdapter.class.getName() + ".dump");
1116         } catch (SecurityException se) {
1117             if (LOGGER.isLoggable(Level.CONFIG)) {
1118                 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
1119                         new Object[] {HttpAdapter.class.getName() + ".dump"});
1120             }
1121         }
1122         try {
1123             dump_threshold = Integer.getInteger(HttpAdapter.class.getName() + ".dumpTreshold", 4096);
1124         } catch (SecurityException se) {
1125             if (LOGGER.isLoggable(Level.CONFIG)) {
1126                 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
1127                         new Object[] {HttpAdapter.class.getName() + ".dumpTreshold"});
1128             }
1129         }
1130         try {
1131             if (System.getProperty(HttpAdapter.class.getName() + ".publishStatusPage") != null) {
1132                 setPublishStatus(Boolean.getBoolean(HttpAdapter.class.getName() + ".publishStatusPage"));
1133             }
1134         } catch (SecurityException se) {
1135             if (LOGGER.isLoggable(Level.CONFIG)) {
1136                 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
1137                         new Object[] {HttpAdapter.class.getName() + ".publishStatusPage"});
1138             }
1139         }
1140     }
1141 
1142     public static void setDump(boolean dumpMessages) {
1143         HttpAdapter.dump = dumpMessages;
1144     }
1145 }