001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036: package com.sun.xml.ws.transport.local;
037:
038: import com.sun.istack.NotNull;
039: import com.sun.xml.ws.api.WSBinding;
040: import com.sun.xml.ws.api.message.Packet;
041: import com.sun.xml.ws.api.pipe.Codec;
042: import com.sun.xml.ws.api.pipe.ContentType;
043: import com.sun.xml.ws.api.pipe.NextAction;
044: import com.sun.xml.ws.api.pipe.Tube;
045: import com.sun.xml.ws.api.pipe.TubeCloner;
046: import com.sun.xml.ws.api.pipe.helper.AbstractTubeImpl;
047: import com.sun.xml.ws.api.server.Adapter;
048: import com.sun.xml.ws.api.server.WSEndpoint;
049: import com.sun.xml.ws.client.ContentNegotiation;
050: import com.sun.xml.ws.transport.http.HttpAdapter;
051: import com.sun.xml.ws.transport.http.WSHTTPConnection;
052:
053: import javax.xml.ws.WebServiceException;
054: import javax.xml.ws.BindingProvider;
055: import javax.xml.ws.handler.MessageContext;
056: import java.io.ByteArrayOutputStream;
057: import java.io.IOException;
058: import java.net.URI;
059: import java.util.Collections;
060: import java.util.HashMap;
061: import java.util.List;
062: import java.util.Map;
063: import java.util.Map.Entry;
064:
065: /**
066: * Transport {@link Tube} that routes a message to a service that runs within it.
067: *
068: * <p>
069: * This is useful to test the whole client-server in a single VM.
070: *
071: * @author Jitendra Kotamraju
072: */
073: final class LocalTransportTube extends AbstractTubeImpl {
074:
075: /**
076: * Represents the service running inside the local transport.
077: *
078: * We use {@link HttpAdapter}, so that the local transport
079: * excercise as much server code as possible. If this were
080: * to be done "correctly" we should write our own {@link Adapter}
081: * for the local transport.
082: */
083: private final HttpAdapter adapter;
084:
085: private final Codec codec;
086:
087: /**
088: * The address of the endpoint deployed in this tube.
089: */
090: private final URI baseURI;
091:
092: // per-pipe reusable resources.
093: // we don't really have to reuse anything since this isn't designed for performance,
094: // but nevertheless we do it as an experiement.
095: private final Map<String, List<String>> reqHeaders = new HashMap<String, List<String>>();
096:
097: public LocalTransportTube(URI baseURI, WSEndpoint endpoint,
098: Codec codec) {
099: this (baseURI, HttpAdapter.createAlone(endpoint), codec);
100: }
101:
102: private LocalTransportTube(URI baseURI, HttpAdapter adapter,
103: Codec codec) {
104: this .adapter = adapter;
105: this .codec = codec;
106: this .baseURI = baseURI;
107: assert codec != null && adapter != null;
108: }
109:
110: /**
111: * Copy constructor for {@link Tube#copy(TubeCloner)}.
112: */
113: private LocalTransportTube(LocalTransportTube that,
114: TubeCloner cloner) {
115: this (that.baseURI, that.adapter, that.codec.copy());
116: cloner.add(that, this );
117: }
118:
119: public @NotNull
120: NextAction processException(@NotNull
121: Throwable t) {
122: return doThrow(t);
123: }
124:
125: public Packet process(Packet request) {
126:
127: try {
128: // Set up WSConnection with tranport headers, request content
129:
130: // get transport headers from message
131: reqHeaders.clear();
132: Map<String, List<String>> rh = (Map<String, List<String>>) request.invocationProperties
133: .get(MessageContext.HTTP_REQUEST_HEADERS);
134: //assign empty map if its null
135: if (rh != null) {
136: reqHeaders.putAll(rh);
137: }
138:
139: LocalConnectionImpl con = new LocalConnectionImpl(baseURI,
140: reqHeaders);
141: // Calling getStaticContentType sets some internal state in the codec
142: // TODO : need to fix this properly in Codec
143: ContentType contentType = codec
144: .getStaticContentType(request);
145: String requestContentType;
146: if (contentType != null) {
147: requestContentType = contentType.getContentType();
148: codec.encode(request, con.getOutput());
149: } else {
150: ByteArrayOutputStream baos = new ByteArrayOutputStream();
151: contentType = codec.encode(request, baos);
152: requestContentType = contentType.getContentType();
153: baos.writeTo(con.getOutput());
154: }
155: reqHeaders.put("Content-Type", Collections
156: .singletonList(requestContentType));
157:
158: String requestAccept = contentType.getAcceptHeader();
159: if (contentType.getAcceptHeader() != null) {
160: reqHeaders.put("Accept", Collections
161: .singletonList(requestAccept));
162: }
163:
164: writeSOAPAction(reqHeaders, contentType
165: .getSOAPActionHeader(), request);
166:
167: if (dump)
168: dump(con, "request", reqHeaders);
169:
170: adapter.handle(con);
171:
172: if (dump)
173: dump(con, "response", con.getResponseHeaders());
174:
175: String responseContentType = getResponseContentType(con);
176:
177: if (con.getStatus() == WSHTTPConnection.ONEWAY) {
178: return request.createClientResponse(null); // one way. no response given.
179: }
180:
181: // TODO: check if returned MIME type is the same as that which was sent
182: // or is acceptable if an Accept header was used
183:
184: checkFIConnegIntegrity(request.contentNegotiation,
185: requestContentType, requestAccept,
186: responseContentType);
187:
188: Packet reply = request.createClientResponse(null);
189: codec.decode(con.getInput(), responseContentType, reply);
190: return reply;
191: } catch (WebServiceException wex) {
192: throw wex;
193: } catch (IOException ex) {
194: throw new WebServiceException(ex);
195: }
196: }
197:
198: /**
199: * write SOAPAction header if the soapAction parameter is non-null or BindingProvider properties set.
200: * BindingProvider properties take precedence.
201: */
202: private void writeSOAPAction(Map<String, List<String>> reqHeaders,
203: String soapAction, Packet packet) {
204: //request Property soapAction overrides wsdl
205: if (soapAction != null)
206: reqHeaders.put("SOAPAction", Collections
207: .singletonList(soapAction));
208: else
209: reqHeaders.put("SOAPAction", Collections
210: .singletonList("\"\""));
211: }
212:
213: private void checkFIConnegIntegrity(ContentNegotiation conneg,
214: String requestContentType, String requestAccept,
215: String responseContentType) {
216: requestAccept = (requestAccept == null) ? "" : requestAccept;
217: if (requestContentType.contains("fastinfoset")) {
218: if (!responseContentType.contains("fastinfoset")) {
219: throw new RuntimeException(
220: "Request is encoded using Fast Infoset but response ("
221: + responseContentType + ") is not");
222: } else if (conneg == ContentNegotiation.none) {
223: throw new RuntimeException(
224: "Request is encoded but Fast Infoset content negotiation is set to none");
225: }
226: } else if (requestAccept.contains("fastinfoset")) {
227: if (!responseContentType.contains("fastinfoset")) {
228: throw new RuntimeException(
229: "Fast Infoset is acceptable but response is not encoded in Fast Infoset");
230: } else if (conneg == ContentNegotiation.none) {
231: throw new RuntimeException(
232: "Fast Infoset is acceptable but Fast Infoset content negotiation is set to none");
233: }
234: } else if (conneg == ContentNegotiation.pessimistic) {
235: throw new RuntimeException(
236: "Content negotitaion is set to pessimistic but Fast Infoset is not acceptable");
237: } else if (conneg == ContentNegotiation.optimistic) {
238: throw new RuntimeException(
239: "Content negotitaion is set to optimistic but the request ("
240: + requestContentType
241: + ") is not encoded using Fast Infoset");
242: }
243: }
244:
245: private String getResponseContentType(LocalConnectionImpl con) {
246: Map<String, List<String>> rsph = con.getResponseHeaders();
247: if (rsph != null) {
248: List<String> c = rsph.get("Content-Type");
249: if (c != null && !c.isEmpty())
250: return c.get(0);
251: }
252: return null;
253: }
254:
255: @NotNull
256: public NextAction processRequest(@NotNull
257: Packet request) {
258: return doReturnWith(process(request));
259: }
260:
261: @NotNull
262: public NextAction processResponse(@NotNull
263: Packet response) {
264: throw new IllegalStateException(
265: "LocalTransportPipe's processResponse shouldn't be called.");
266: }
267:
268: public void preDestroy() {
269: // Nothing to do here. Intenionally left empty
270: }
271:
272: public LocalTransportTube copy(TubeCloner cloner) {
273: return new LocalTransportTube(this , cloner);
274: }
275:
276: private void dump(LocalConnectionImpl con, String caption,
277: Map<String, List<String>> headers) {
278: System.out.println("---[" + caption + "]---");
279: if (headers != null) {
280: for (Entry<String, List<String>> header : headers
281: .entrySet()) {
282: if (header.getValue().isEmpty()) {
283: // I don't think this is legal, but let's just dump it,
284: // as the point of the dump is to uncover problems.
285: System.out.println(header.getValue());
286: } else {
287: for (String value : header.getValue()) {
288: System.out.println(header.getKey() + ": "
289: + value);
290: }
291: }
292: }
293: }
294: System.out.println(con.toString());
295: System.out.println("--------------------");
296: }
297:
298: /**
299: * Dumps what goes across HTTP transport.
300: */
301: private static final boolean dump;
302:
303: static {
304: boolean b;
305: try {
306: b = Boolean.getBoolean(LocalTransportTube.class.getName()
307: + ".dump");
308: } catch (Throwable t) {
309: b = false;
310: }
311: dump = b;
312: }
313: }
|