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