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.message.Packet;
040: import com.sun.xml.ws.api.pipe.Codec;
041: import com.sun.xml.ws.api.pipe.ContentType;
042: import com.sun.xml.ws.api.pipe.Fiber;
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.handler.MessageContext;
055: import java.io.ByteArrayOutputStream;
056: import java.io.IOException;
057: import java.net.URI;
058: import java.util.Collections;
059: import java.util.HashMap;
060: import java.util.List;
061: import java.util.Map;
062: import java.util.Map.Entry;
063:
064: /**
065: * Transport {@link Tube} that routes a message to a service that runs within it.
066: *
067: * <p>
068: * This is useful to test the whole client-server in a single VM.
069: *
070: * @author Jitendra Kotamraju
071: */
072: final class LocalAsyncTransportTube extends AbstractTubeImpl {
073:
074: /**
075: * Represents the service running inside the local transport.
076: *
077: * We use {@link HttpAdapter}, so that the local transport
078: * excercise as much server code as possible. If this were
079: * to be done "correctly" we should write our own {@link Adapter}
080: * for the local transport.
081: */
082: private final HttpAdapter adapter;
083:
084: private final Codec codec;
085:
086: /**
087: * The address of the endpoint deployed in this tube.
088: */
089: private final URI baseURI;
090:
091: // per-pipe reusable resources.
092: // we don't really have to reuse anything since this isn't designed for performance,
093: // but nevertheless we do it as an experiement.
094: private final Map<String, List<String>> reqHeaders = new HashMap<String, List<String>>();
095:
096: public LocalAsyncTransportTube(URI baseURI, WSEndpoint endpoint,
097: Codec codec) {
098: this (baseURI, HttpAdapter.createAlone(endpoint), codec);
099: }
100:
101: private LocalAsyncTransportTube(URI baseURI, HttpAdapter adapter,
102: Codec codec) {
103: this .adapter = adapter;
104: this .codec = codec;
105: this .baseURI = baseURI;
106: assert codec != null && adapter != null;
107: }
108:
109: /**
110: * Copy constructor for {@link Tube#copy(TubeCloner)}.
111: */
112: private LocalAsyncTransportTube(LocalAsyncTransportTube that,
113: TubeCloner cloner) {
114: this (that.baseURI, that.adapter, that.codec.copy());
115: cloner.add(that, this );
116: }
117:
118: public @NotNull
119: NextAction processException(@NotNull
120: Throwable t) {
121: return doThrow(t);
122: }
123:
124: private class MyClosedCallback implements ClosedCallback {
125: final Fiber fiber;
126: LocalConnectionImpl con;
127: String requestContentType;
128: String requestAccept;
129: final Packet request;
130:
131: MyClosedCallback(Packet request) {
132:
133: this .request = request;
134: this .requestContentType = requestContentType;
135: this .requestAccept = requestAccept;
136: fiber = Fiber.current();
137: }
138:
139: void setConnection(LocalConnectionImpl con) {
140: this .con = con;
141: }
142:
143: void setContentType(ContentType ct) {
144: requestContentType = ct.getContentType();
145: requestAccept = ct.getAcceptHeader();
146: }
147:
148: public void onClosed() {
149: String responseContentType = getResponseContentType(con);
150:
151: if (con.getStatus() == WSHTTPConnection.ONEWAY) {
152: Packet reply = request.createClientResponse(null); // one way. no response given.
153: fiber.resume(reply);
154: return;
155: }
156:
157: // TODO: check if returned MIME type is the same as that which was sent
158: // or is acceptable if an Accept header was used
159:
160: checkFIConnegIntegrity(request.contentNegotiation,
161: requestContentType, requestAccept,
162: responseContentType);
163:
164: Packet reply = request.createClientResponse(null);
165: try {
166: codec
167: .decode(con.getInput(), responseContentType,
168: reply);
169: } catch (Exception e) {
170: e.printStackTrace();
171: }
172:
173: fiber.resume(reply);
174: }
175: }
176:
177: private void checkFIConnegIntegrity(ContentNegotiation conneg,
178: String requestContentType, String requestAccept,
179: String responseContentType) {
180: requestAccept = (requestAccept == null) ? "" : requestAccept;
181: if (requestContentType.contains("fastinfoset")) {
182: if (!responseContentType.contains("fastinfoset")) {
183: throw new RuntimeException(
184: "Request is encoded using Fast Infoset but response ("
185: + responseContentType + ") is not");
186: } else if (conneg == ContentNegotiation.none) {
187: throw new RuntimeException(
188: "Request is encoded but Fast Infoset content negotiation is set to none");
189: }
190: } else if (requestAccept.contains("fastinfoset")) {
191: if (!responseContentType.contains("fastinfoset")) {
192: throw new RuntimeException(
193: "Fast Infoset is acceptable but response is not encoded in Fast Infoset");
194: } else if (conneg == ContentNegotiation.none) {
195: throw new RuntimeException(
196: "Fast Infoset is acceptable but Fast Infoset content negotiation is set to none");
197: }
198: } else if (conneg == ContentNegotiation.pessimistic) {
199: throw new RuntimeException(
200: "Content negotitaion is set to pessimistic but Fast Infoset is not acceptable");
201: } else if (conneg == ContentNegotiation.optimistic) {
202: throw new RuntimeException(
203: "Content negotitaion is set to optimistic but the request ("
204: + requestContentType
205: + ") is not encoded using Fast Infoset");
206: }
207: }
208:
209: private String getResponseContentType(LocalConnectionImpl con) {
210: Map<String, List<String>> rsph = con.getResponseHeaders();
211: if (rsph != null) {
212: List<String> c = rsph.get("Content-Type");
213: if (c != null && !c.isEmpty())
214: return c.get(0);
215: }
216: return null;
217: }
218:
219: @NotNull
220: public NextAction processRequest(@NotNull
221: Packet request) {
222: try {
223: // Set up WSConnection with tranport headers, request content
224:
225: // get transport headers from message
226: reqHeaders.clear();
227: Map<String, List<String>> rh = (Map<String, List<String>>) request.invocationProperties
228: .get(MessageContext.HTTP_REQUEST_HEADERS);
229: //assign empty map if its null
230: if (rh != null) {
231: reqHeaders.putAll(rh);
232: }
233:
234: MyClosedCallback callback = new MyClosedCallback(request);
235: LocalConnectionImpl con = new LocalConnectionImpl(baseURI,
236: reqHeaders, callback);
237: callback.setConnection(con);
238: // Calling getStaticContentType sets some internal state in the codec
239: // TODO : need to fix this properly in Codec
240: ContentType contentType = codec
241: .getStaticContentType(request);
242: String requestContentType;
243: if (contentType != null) {
244: requestContentType = contentType.getContentType();
245: codec.encode(request, con.getOutput());
246: } else {
247: ByteArrayOutputStream baos = new ByteArrayOutputStream();
248: contentType = codec.encode(request, baos);
249: requestContentType = contentType.getContentType();
250: baos.writeTo(con.getOutput());
251: }
252: callback.setContentType(contentType);
253: reqHeaders.put("Content-Type", Collections
254: .singletonList(requestContentType));
255:
256: String requestAccept = contentType.getAcceptHeader();
257: if (contentType.getAcceptHeader() != null) {
258: reqHeaders.put("Accept", Collections
259: .singletonList(requestAccept));
260: }
261:
262: if (dump)
263: dump(con, "request", reqHeaders);
264:
265: //Packet serverReq = adapter.decodePacket(con, codec);
266: adapter.invokeAsync(con);
267: return doSuspend();
268: } catch (IOException ioe) {
269: throw new WebServiceException(ioe);
270: }
271: }
272:
273: @NotNull
274: public NextAction processResponse(@NotNull
275: Packet response) {
276: return doReturnWith(response);
277: }
278:
279: public void preDestroy() {
280: // Nothing to do here. Intenionally left empty
281: }
282:
283: public LocalAsyncTransportTube copy(TubeCloner cloner) {
284: return new LocalAsyncTransportTube(this , cloner);
285: }
286:
287: private void dump(LocalConnectionImpl con, String caption,
288: Map<String, List<String>> headers) {
289: System.out.println("---[" + caption + "]---");
290: if (headers != null) {
291: for (Entry<String, List<String>> header : headers
292: .entrySet()) {
293: if (header.getValue().isEmpty()) {
294: // I don't think this is legal, but let's just dump it,
295: // as the point of the dump is to uncover problems.
296: System.out.println(header.getValue());
297: } else {
298: for (String value : header.getValue()) {
299: System.out.println(header.getKey() + ": "
300: + value);
301: }
302: }
303: }
304: }
305: System.out.println(con.toString());
306: System.out.println("--------------------");
307: }
308:
309: /**
310: * Dumps what goes across HTTP transport.
311: */
312: private static final boolean dump;
313:
314: static {
315: boolean b;
316: try {
317: b = Boolean.getBoolean(LocalTransportTube.class.getName()
318: + ".dump");
319: } catch (Throwable t) {
320: b = false;
321: }
322: dump = b;
323: }
324:
325: private static final boolean async;
326: static {
327: boolean b;
328: try {
329: b = Boolean.getBoolean(LocalTransportTube.class.getName()
330: + ".async");
331: } catch (Throwable t) {
332: b = false;
333: }
334: async = b;
335: }
336: }
|