/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.xml.ws.transport.local;
import com.sun.istack.NotNull;
import com.sun.xml.ws.api.WSBinding;
import com.sun.xml.ws.api.message.Packet;
import com.sun.xml.ws.api.pipe.Codec;
import com.sun.xml.ws.api.pipe.ContentType;
import com.sun.xml.ws.api.pipe.NextAction;
import com.sun.xml.ws.api.pipe.Tube;
import com.sun.xml.ws.api.pipe.TubeCloner;
import com.sun.xml.ws.api.pipe.helper.AbstractTubeImpl;
import com.sun.xml.ws.api.server.Adapter;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.client.ContentNegotiation;
import com.sun.xml.ws.transport.http.HttpAdapter;
import com.sun.xml.ws.transport.http.WSHTTPConnection;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.MessageContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Transport {@link Tube} that routes a message to a service that runs within it.
*
* <p>
* This is useful to test the whole client-server in a single VM.
*
* @author Jitendra Kotamraju
*/
final class LocalTransportTube extends AbstractTubeImpl {
/**
* Represents the service running inside the local transport.
*
* We use {@link HttpAdapter}, so that the local transport
* excercise as much server code as possible. If this were
* to be done "correctly" we should write our own {@link Adapter}
* for the local transport.
*/
private final HttpAdapter adapter;
private final Codec codec;
/**
* The address of the endpoint deployed in this tube.
*/
private final URI baseURI;
// per-pipe reusable resources.
// we don't really have to reuse anything since this isn't designed for performance,
// but nevertheless we do it as an experiement.
private final Map<String, List<String>> reqHeaders = new HashMap<String, List<String>>();
public LocalTransportTube(URI baseURI, WSEndpoint endpoint, Codec codec) {
this(baseURI,HttpAdapter.createAlone(endpoint),codec);
}
private LocalTransportTube(URI baseURI,HttpAdapter adapter, Codec codec) {
this.adapter = adapter;
this.codec = codec;
this.baseURI = baseURI;
assert codec !=null && adapter!=null;
}
/**
* Copy constructor for {@link Tube#copy(TubeCloner)}.
*/
private LocalTransportTube(LocalTransportTube that, TubeCloner cloner) {
this(that.baseURI, that.adapter, that.codec.copy());
cloner.add(that,this);
}
public @NotNull NextAction processException(@NotNull Throwable t) {
return doThrow(t);
}
public Packet process(Packet request) {
try {
// Set up WSConnection with tranport headers, request content
// get transport headers from message
reqHeaders.clear();
Map<String, List<String>> rh = (Map<String, List<String>>) request.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS);
//assign empty map if its null
if(rh != null){
reqHeaders.putAll(rh);
}
LocalConnectionImpl con = new LocalConnectionImpl(baseURI,reqHeaders);
// Calling getStaticContentType sets some internal state in the codec
// TODO : need to fix this properly in Codec
ContentType contentType = codec.getStaticContentType(request);
String requestContentType;
if (contentType != null) {
requestContentType = contentType.getContentType();
codec.encode(request, con.getOutput());
} else {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
contentType = codec.encode(request, baos);
requestContentType = contentType.getContentType();
baos.writeTo(con.getOutput());
}
reqHeaders.put("Content-Type", Collections.singletonList(requestContentType));
String requestAccept = contentType.getAcceptHeader();
if (contentType.getAcceptHeader() != null) {
reqHeaders.put("Accept", Collections.singletonList(requestAccept));
}
writeSOAPAction(reqHeaders, contentType.getSOAPActionHeader(), request);
if(dump)
dump(con,"request",reqHeaders);
adapter.handle(con);
if(dump)
dump(con,"response",con.getResponseHeaders());
String responseContentType = getResponseContentType(con);
if (con.getStatus() == WSHTTPConnection.ONEWAY) {
return request.createClientResponse(null); // one way. no response given.
}
// TODO: check if returned MIME type is the same as that which was sent
// or is acceptable if an Accept header was used
checkFIConnegIntegrity(request.contentNegotiation, requestContentType, requestAccept, responseContentType);
Packet reply = request.createClientResponse(null);
codec.decode(con.getInput(), responseContentType, reply);
return reply;
} catch (WebServiceException wex) {
throw wex;
} catch (IOException ex) {
throw new WebServiceException(ex);
}
}
/**
* write SOAPAction header if the soapAction parameter is non-null or BindingProvider properties set.
* BindingProvider properties take precedence.
*/
private void writeSOAPAction(Map<String, List<String>> reqHeaders, String soapAction, Packet packet) {
//request Property soapAction overrides wsdl
if (soapAction != null)
reqHeaders.put("SOAPAction", Collections.singletonList(soapAction));
else
reqHeaders.put("SOAPAction", Collections.singletonList("\"\""));
}
private void checkFIConnegIntegrity(ContentNegotiation conneg,
String requestContentType, String requestAccept, String responseContentType) {
requestAccept = (requestAccept == null) ? "" : requestAccept;
if (requestContentType.contains("fastinfoset")) {
if (!responseContentType.contains("fastinfoset")) {
throw new RuntimeException(
"Request is encoded using Fast Infoset but response (" +
responseContentType +
") is not");
} else if (conneg == ContentNegotiation.none) {
throw new RuntimeException(
"Request is encoded but Fast Infoset content negotiation is set to none");
}
} else if (requestAccept.contains("fastinfoset")) {
if (!responseContentType.contains("fastinfoset")) {
throw new RuntimeException(
"Fast Infoset is acceptable but response is not encoded in Fast Infoset");
} else if (conneg == ContentNegotiation.none) {
throw new RuntimeException(
"Fast Infoset is acceptable but Fast Infoset content negotiation is set to none");
}
} else if (conneg == ContentNegotiation.pessimistic) {
throw new RuntimeException(
"Content negotitaion is set to pessimistic but Fast Infoset is not acceptable");
} else if (conneg == ContentNegotiation.optimistic) {
throw new RuntimeException(
"Content negotitaion is set to optimistic but the request (" +
requestContentType +
") is not encoded using Fast Infoset");
}
}
private String getResponseContentType(LocalConnectionImpl con) {
Map<String, List<String>> rsph = con.getResponseHeaders();
if(rsph!=null) {
List<String> c = rsph.get("Content-Type");
if(c!=null && !c.isEmpty())
return c.get(0);
}
return null;
}
@NotNull
public NextAction processRequest(@NotNull Packet request) {
return doReturnWith(process(request));
}
@NotNull
public NextAction processResponse(@NotNull Packet response) {
throw new IllegalStateException("LocalTransportPipe's processResponse shouldn't be called.");
}
public void preDestroy() {
// Nothing to do here. Intenionally left empty
}
public LocalTransportTube copy(TubeCloner cloner) {
return new LocalTransportTube(this, cloner);
}
private void dump(LocalConnectionImpl con, String caption, Map<String,List<String>> headers) {
System.out.println("---["+caption +"]---");
if(headers!=null) {
for (Entry<String,List<String>> header : headers.entrySet()) {
if(header.getValue().isEmpty()) {
// I don't think this is legal, but let's just dump it,
// as the point of the dump is to uncover problems.
System.out.println(header.getValue());
} else {
for (String value : header.getValue()) {
System.out.println(header.getKey()+": "+value);
}
}
}
}
System.out.println(con.toString());
System.out.println("--------------------");
}
/**
* Dumps what goes across HTTP transport.
*/
private static final boolean dump;
static {
boolean b;
try {
b = Boolean.getBoolean(LocalTransportTube.class.getName()+".dump");
} catch( Throwable t ) {
b = false;
}
dump = b;
}
}