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