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
26package com.sun.xml.internal.ws.transport.http;
27
28import java.io.ByteArrayOutputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.io.OutputStreamWriter;
33import java.io.PrintWriter;
34import java.net.HttpURLConnection;
35import java.util.AbstractMap;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.HashMap;
39import java.util.List;
40import java.util.Map;
41import java.util.Map.Entry;
42import java.util.Set;
43import java.util.TreeMap;
44import java.util.logging.Level;
45import java.util.logging.Logger;
46
47import javax.xml.ws.Binding;
48import javax.xml.ws.WebServiceException;
49import javax.xml.ws.http.HTTPBinding;
50
51import com.oracle.webservices.internal.api.message.PropertySet;
52import com.sun.istack.internal.NotNull;
53import com.sun.istack.internal.Nullable;
54import com.sun.xml.internal.ws.api.Component;
55import com.sun.xml.internal.ws.api.EndpointAddress;
56import com.sun.xml.internal.ws.api.SOAPVersion;
57import com.sun.xml.internal.ws.api.addressing.AddressingVersion;
58import com.sun.xml.internal.ws.api.addressing.NonAnonymousResponseProcessor;
59import com.sun.xml.internal.ws.api.ha.HaInfo;
60import com.sun.xml.internal.ws.api.message.ExceptionHasMessage;
61import com.sun.xml.internal.ws.api.message.Message;
62import com.sun.xml.internal.ws.api.message.Packet;
63import com.sun.xml.internal.ws.api.pipe.Codec;
64import com.sun.xml.internal.ws.api.pipe.ContentType;
65import com.sun.xml.internal.ws.api.server.AbstractServerAsyncTransport;
66import com.sun.xml.internal.ws.api.server.Adapter;
67import com.sun.xml.internal.ws.api.server.BoundEndpoint;
68import com.sun.xml.internal.ws.api.server.DocumentAddressResolver;
69import com.sun.xml.internal.ws.api.server.Module;
70import com.sun.xml.internal.ws.api.server.PortAddressResolver;
71import com.sun.xml.internal.ws.api.server.SDDocument;
72import com.sun.xml.internal.ws.api.server.ServiceDefinition;
73import com.sun.xml.internal.ws.api.server.TransportBackChannel;
74import com.sun.xml.internal.ws.api.server.WSEndpoint;
75import com.sun.xml.internal.ws.api.server.WebServiceContextDelegate;
76import com.sun.xml.internal.ws.fault.SOAPFaultBuilder;
77import com.sun.xml.internal.ws.resources.WsservletMessages;
78import com.sun.xml.internal.ws.server.UnsupportedMediaException;
79import com.sun.xml.internal.ws.util.ByteArrayBuffer;
80import 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 */
93public 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}
1146