/******************************************************************************* * Copyright (c) 2006-2010 eBay Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 *******************************************************************************/ package org.ebayopensource.turmeric.runtime.spf.impl.transport.local; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import org.ebayopensource.turmeric.runtime.common.binding.DataBindingDesc; import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException; import org.ebayopensource.turmeric.runtime.common.impl.attachment.InboundMessageAttachments; import org.ebayopensource.turmeric.runtime.common.impl.internal.config.MetadataPropertyConfigHolder; import org.ebayopensource.turmeric.runtime.common.impl.internal.pipeline.BaseMessageContextImpl; import org.ebayopensource.turmeric.runtime.common.impl.internal.service.ServiceDesc; import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.AsyncCallBack; import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.IAsyncResponsePoller; import org.ebayopensource.turmeric.runtime.common.impl.utils.HTTPCommonUtils; import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager; import org.ebayopensource.turmeric.runtime.common.pipeline.InboundMessage; import org.ebayopensource.turmeric.runtime.common.pipeline.Message; import org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext; import org.ebayopensource.turmeric.runtime.common.pipeline.OutboundMessage; import org.ebayopensource.turmeric.runtime.common.pipeline.Transport; import org.ebayopensource.turmeric.runtime.common.pipeline.TransportOptions; import org.ebayopensource.turmeric.runtime.common.types.Cookie; import org.ebayopensource.turmeric.runtime.common.types.SOAConstants; import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants; import org.ebayopensource.turmeric.runtime.sif.impl.internal.pipeline.ClientMessageContextImpl; import org.ebayopensource.turmeric.runtime.sif.impl.internal.pipeline.LocalBindingThreadPool; import org.ebayopensource.turmeric.runtime.sif.pipeline.ClientMessageContext; import org.ebayopensource.turmeric.runtime.spf.impl.pipeline.ServerMessageContextBuilder; import org.ebayopensource.turmeric.runtime.spf.impl.transport.http.HTTPServerUtils; import org.ebayopensource.turmeric.runtime.spf.impl.transport.http.ISOATransportRequest; import org.ebayopensource.turmeric.runtime.spf.impl.transport.http.SOALocalTransportRequest; import org.ebayopensource.turmeric.runtime.spf.pipeline.ServerMessageContext; /** * This class is a null transport, i.e, there is no physical transport. The * message is handed off directly to the next step. That next step is reall the * message processor on the side. * * @author ichernyshev */ public class LocalTransport implements Transport { private final static int DFLT_REQUEST_TIMEOUT_MS = 1000; private final static Logger s_logger = LogManager.getInstance(LocalTransport.class); // SOA2.4, changing default skip serialization to true private boolean m_configSkipSerialization; private boolean m_configUseDetachedLocalBinding; private int m_configInvocationTimeoutMs = DFLT_REQUEST_TIMEOUT_MS; // Changes for supporting relative mapping in Local transport. private String requestUri; public static final String REQUEST_URI = "request-uri"; // private String tName = "LocalTransport"; public void checkTransportPoller(IAsyncResponsePoller holder) { if (holder.getTransportPoller() == null || !(holder.getTransportPoller() instanceof LocalTransportPoller)) { holder.setTransportPoller(new LocalTransportPoller()); } } public boolean supportsPoll() { return true; } public void init(InitContext ctx) throws ServiceException { ServerMessageContextBuilder.init(); Boolean configSkipSer = ctx.getOptions().getSkipSerialization(); if (configSkipSer != null && configSkipSer.booleanValue()) { m_configSkipSerialization = true; } else { m_configSkipSerialization = false; } // TODO - skip-serialization is not compatible with using protocol // processor. We should catch this, maybe // in Service.getProtocolProcessor(). Boolean configUseDetached = ctx.getOptions() .isUseDetachedLocalBinding(); // By default, detached local binding is used if not specified in // configuration m_configUseDetachedLocalBinding = false; if (configUseDetached != null) { m_configUseDetachedLocalBinding = configUseDetached.booleanValue(); } Integer configInvTimeoutMs = ctx.getOptions().getInvocationTimeout(); if (configInvTimeoutMs != null) { m_configInvocationTimeoutMs = configInvTimeoutMs.intValue(); } requestUri = ctx.getOptions().getProperty(REQUEST_URI); } public Object preInvoke(MessageContext ctx) throws ServiceException { OutboundMessage clientRequestMsg = (OutboundMessage) ctx .getRequestMessage(); // Set the content-type of response only if it has not been set (in // soap1.2 case, // this gets over-written at the Server Protocol processor. // So make sure we are only setting here if it has not already been set) if (clientRequestMsg .getTransportHeader(SOAConstants.HTTP_HEADER_CONTENT_TYPE) == null) { DataBindingDesc binding = clientRequestMsg.getDataBindingDesc(); String mimeType = binding.getMimeType(); Charset charset = clientRequestMsg.getG11nOptions().getCharset(); String contentType = HTTPCommonUtils.formatContentType(mimeType, charset); clientRequestMsg.setTransportHeader( SOAConstants.HTTP_HEADER_CONTENT_TYPE, contentType); } return null; } public void invoke(Message msg, TransportOptions invokerOptions) throws ServiceException { validate((OutboundMessage) msg); ClientMessageContext clientCtx = (ClientMessageContext) msg .getContext(); ServerMessageContextBuilder builder = createServerContext(clientCtx, invokerOptions); invokeInternal(builder, clientCtx, invokerOptions); } public Future<?> invokeAsync(Message msg, TransportOptions invokerOptions) throws ServiceException { validate((OutboundMessage) msg); ClientMessageContext clientCtx = (ClientMessageContext) msg .getContext(); ServerMessageContextBuilder builder = createServerContext(clientCtx, invokerOptions); @SuppressWarnings("unused") AsyncCallBack callback = ((BaseMessageContextImpl) clientCtx) .getServiceAsyncCallback(); Future<MessageContext> pullFuture = getPullFuture(clientCtx, builder); return callback == null ? pullFuture : getPushFuture(clientCtx, builder, callback, pullFuture); } public void retrieve(MessageContext context, Future<?> futureResp) throws ServiceException { if (((BaseMessageContextImpl) context).getServiceAsyncCallback() != null) { return; } try { futureResp.get(); } catch (Throwable t) { handleLocalBindingError(t, context, 0); } } private Future<MessageContext> getPushFuture( ClientMessageContext clientCtx, ServerMessageContextBuilder builder, AsyncCallBack callback, Future<MessageContext> pullFuture) throws ServiceException { Future<MessageContext> pushFuture = null; try { pushFuture = LocalBindingThreadPool.getInstance().execute( new CallBackWorker(pullFuture, callback)); } catch (Throwable t) { handleLocalBindingError(t, clientCtx, 0); } return pushFuture; } private Future<MessageContext> getPullFuture( ClientMessageContext clientCtx, ServerMessageContextBuilder builder) throws ServiceException { Future<MessageContext> future = null; try { future = new WorkerFutureTask(new LocalBindingWorker(builder, clientCtx)); LocalBindingThreadPool.getInstance().execute( (FutureTask<MessageContext>) future); IAsyncResponsePoller poller = ((BaseMessageContextImpl) clientCtx) .getServicePoller(); LocalTransportPoller transpPoller = null; if (poller != null) { checkTransportPoller(poller); transpPoller = (LocalTransportPoller) poller .getTransportPoller(); } if (transpPoller != null) { transpPoller.getBlockingQueue().add(future); } } catch (Throwable t) { handleLocalBindingError(t, clientCtx, 0); } return future; } private void invokeInternal(final ServerMessageContextBuilder builder, final MessageContext msgContext, final TransportOptions invokerTransportOptions) throws ServiceException { if (!isDetachedLocalBinding(invokerTransportOptions)) { builder.processCall(); return; } // Detached local binding long timeout = getRequestTimeoutMs(invokerTransportOptions); try { Future<MessageContext> f = LocalBindingThreadPool.getInstance() .execute(new LocalBindingWorker(builder, msgContext)); String testlog = System.getProperty("test.log.out"); if(testlog!=null && testlog.equals("true")) System.out.println("<><><> TIMEOUT: "+ timeout); f.get(timeout, TimeUnit.MILLISECONDS); } catch (Throwable t) { handleLocalBindingError(t, msgContext, timeout); } } private void handleLocalBindingError(final Throwable t, final MessageContext msgContext, final long timeout) throws ServiceException { Throwable cause = t; StringBuilder sb = new StringBuilder(); ServiceException exception = null; if (t instanceof TimeoutException) { sb.append("Request timed out after ").append(timeout); sb.append(" ms in local transport for '"); exception = new ServiceException( ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_LOCAL_BINDING_TIMEOUT, ErrorConstants.ERRORDOMAIN, new Object[] { String.valueOf(timeout), msgContext.getAdminName(), msgContext.getOperationName() })); } else { cause = t.getCause(); sb.append("Unexpected error in LocalTransport.invoke() for '"); exception = new ServiceException( ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_INVOCATION_FAILED_APP, ErrorConstants.ERRORDOMAIN), cause); } sb.append(msgContext.getAdminName()).append("."); sb.append(msgContext.getOperationName()).append("': "); sb.append(cause.toString()); String errorMsg = sb.toString(); // log it LogManager.getInstance(this.getClass()).log(Level.SEVERE, errorMsg, t); // throw the composed exception throw exception; } private void validate(OutboundMessage msg) throws ServiceException { ClientMessageContext clientCtx = (ClientMessageContext) msg .getContext(); if (msg.isREST()) { throw new ServiceException( ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_NO_GET_WITH_LOCAL, ErrorConstants.ERRORDOMAIN, new Object[] { clientCtx.getAdminName() })); } if (m_configSkipSerialization) { String messageProtocol = clientCtx.getMessageProtocol(); if (!messageProtocol.equals(SOAConstants.MSG_PROTOCOL_NONE)) { throw new ServiceException( ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_NO_SOAP_WITH_LOCAL, ErrorConstants.ERRORDOMAIN, new Object[] { clientCtx.getAdminName() })); } } } private ServerMessageContextBuilder createServerContext( ClientMessageContext clientCtx, TransportOptions options) throws ServiceException { OutboundMessage clientRequestMsg = (OutboundMessage) clientCtx .getRequestMessage(); if (clientRequestMsg.isUnserializable()) { throw new ServiceException( ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_UNSERIALIZABLE_MESSAGE, ErrorConstants.ERRORDOMAIN, new Object[] { clientRequestMsg.getUnserializableReason()})); } /* * Get admin name from metadata holder. It will be null for pre-ServiceUID services. * Othierwise initialize the requestMetacontext with the admin name. */ ServiceDesc serviceDesc = ((ClientMessageContextImpl)clientCtx).getServiceDesc(); MetadataPropertyConfigHolder metadatsHolder = serviceDesc.getConfig().getMetaData(); String adminName = metadatsHolder.getAdminName(); if (adminName==null || adminName.trim().length() <1) adminName = clientCtx.getAdminName(); options.getProperties().put(REQUEST_URI, requestUri); ISOATransportRequest soaRequest = SOALocalTransportRequest.createRequest(clientCtx,options); HTTPServerUtils serverUtils = new HTTPServerUtils(soaRequest, adminName, null); boolean skipSerialization; if (options.getSkipSerialization() != null) { // ServiceInvokerOptions override of config skipSerialization = options.getSkipSerialization().booleanValue(); } else { // Use configured skip-serialization value skipSerialization = m_configSkipSerialization; } Transport responseTransport = new LocalServerResponseTransport( clientCtx, skipSerialization); ServerMessageContextBuilder builder = serverUtils.createMessageContext(responseTransport); // TODO: config it boolean hasInboundAttachments = false; if (hasInboundAttachments) { builder.setRequestAttachments(new InboundMessageAttachments()); } if (skipSerialization) { // Directly set the outbound (client) request params, to the inbound // (server) request params. int paramCount = clientRequestMsg.getParamCount(); Object[] serverParams = new Object[paramCount]; for (int i = 0; i < paramCount; i++) { Object param = clientRequestMsg.getParam(i); serverParams[i] = param; } builder.setParamReferences(serverParams); } else { byte[] body = serializeData(clientRequestMsg); ByteArrayInputStream bis = new ByteArrayInputStream(body); builder.setInputStream(bis); } return builder; } private Cookie[] cloneCookies(Cookie[] cookies) { if (cookies == null) { return null; } Cookie[] result = new Cookie[cookies.length]; for (int i = 0; i < cookies.length; i++) { Cookie oldCookie = cookies[i]; Cookie newCookie = new Cookie(oldCookie.getName(), oldCookie .getValue()); result[i] = newCookie; } return result; } private byte[] serializeData(OutboundMessage outboundMsg) throws ServiceException { ByteArrayOutputStream bos = new ByteArrayOutputStream(8192); outboundMsg.serialize(bos); if (s_logger.isLoggable(Level.FINEST)) { s_logger.log(Level.FINEST, "Response msg: " + bos.toString()); } return bos.toByteArray(); } private void transferParamReferences(OutboundMessage outboundMsg, InboundMessage inboundMsg) throws ServiceException { if (outboundMsg.isErrorMessage()) { inboundMsg .setErrorResponseReference(outboundMsg.getErrorResponse()); } else { int paramCount = outboundMsg.getParamCount(); Object[] serverParams = new Object[paramCount]; for (int i = 0; i < paramCount; i++) { Object param = outboundMsg.getParam(i); serverParams[i] = param; } inboundMsg.setParamReferences(serverParams); } } void populateClientResponse(ServerMessageContext serverCtx, ClientMessageContext clientCtx, boolean skipSerialization) throws ServiceException { InboundMessage clientResponse = (InboundMessage) clientCtx .getResponseMessage(); OutboundMessage serverResponse = (OutboundMessage) serverCtx .getResponseMessage(); Map<String, String> transportHeaders = serverResponse .buildOutputHeaders(); if (transportHeaders != null) { for (Iterator<Map.Entry<String, String>> it = transportHeaders .entrySet().iterator(); it.hasNext();) { Map.Entry<String, String> e = it.next(); clientResponse.setTransportHeader(e.getKey(), e.getValue()); } } Cookie[] cookies = cloneCookies(serverResponse.getCookies()); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { clientResponse.setCookie(cookies[i]); } } if (serverResponse.isUnserializable()) { clientResponse.unableToProvideStream(); ServiceException e = new ServiceException( ErrorDataFactory.createErrorData(ErrorConstants.SVC_TRANSPORT_UNSERIALIZABLE_MESSAGE, ErrorConstants.ERRORDOMAIN, new Object[] { "Server returned: " + serverResponse.getUnserializableReason() })); clientCtx.addError(e); return; } if (skipSerialization) { // Directly set the outbound (client) request params, to the inbound // (server) request params. transferParamReferences(serverResponse, clientResponse); } else { boolean gotBytes = false; byte[] body = null; try { body = serializeData(serverResponse); gotBytes = true; } catch (Throwable e) { clientCtx.addError(e); clientResponse.unableToProvideStream(); } if (gotBytes) { if (clientCtx.isOutboundRawMode()) { clientResponse.setByteBuffer(ByteBuffer.wrap(body)); } ByteArrayInputStream bis = new ByteArrayInputStream(body); clientResponse.setInputStream(bis); } } } /** * CONFIGURATION MERGERS * * The following methods are responsible for merging the configuration * between the static configuration and the invoker options. The static * configuration includes the defaults, the overrides and the dynamic * changes from the config beans. * * In a nutshell, if there's no override from the invoker option, use the * one from the static configuration. The invoker options essentially * supercede the static configuration. */ private boolean isDetachedLocalBinding(final TransportOptions invokerOptions) { Boolean isInvokerDetached = invokerOptions.isUseDetachedLocalBinding(); if (isInvokerDetached == null) { return m_configUseDetachedLocalBinding; } return isInvokerDetached.booleanValue(); } private int getRequestTimeoutMs(final TransportOptions invokerOptions) { Integer requestTimeoutMs = invokerOptions.getInvocationTimeout(); if (requestTimeoutMs == null) { return m_configInvocationTimeoutMs; } return requestTimeoutMs.intValue(); } /** * end of CONFIGURATION MERGERS */ // ------------------- Inner class ----------------------- private class LocalBindingWorker implements Callable<MessageContext> { private ServerMessageContextBuilder m_msgContextBuilder; private MessageContext m_msgContext; private Future<?> m_future; LocalBindingWorker(final ServerMessageContextBuilder builder, final MessageContext msgContext) { m_msgContextBuilder = builder; m_msgContext = msgContext; } @SuppressWarnings( { "unchecked", "synthetic-access" }) public MessageContext call() throws Exception { m_msgContextBuilder.processCall(); BaseMessageContextImpl clientCtx = (BaseMessageContextImpl) m_msgContext; if (clientCtx.getServicePoller() != null && clientCtx.getServicePoller().getTransportPoller() != null && m_future != null) { ((LocalTransportPoller) clientCtx.getServicePoller() .getTransportPoller()).getBlockingQueue().add(m_future); } return m_msgContext; } public void setFuture(Future<?> future) { m_future = future; } } private static class WorkerFutureTask extends FutureTask<MessageContext> { public WorkerFutureTask(LocalBindingWorker worker) { super(worker); worker.setFuture(this); } } private class LocalServerResponseTransport implements Transport { private ClientMessageContext m_clientCtx; private boolean m_skipSerialization; public LocalServerResponseTransport(ClientMessageContext clientCtx, boolean skipSerialization) { this.m_clientCtx = clientCtx; this.m_skipSerialization = skipSerialization; } public void init(InitContext ctx) throws ServiceException { // noop } public Object preInvoke(MessageContext ctx) throws ServiceException { OutboundMessage serverResponse = (OutboundMessage) ctx .getResponseMessage(); DataBindingDesc binding = serverResponse.getDataBindingDesc(); String mimeType = binding.getMimeType(); Charset charset = serverResponse.getG11nOptions().getCharset(); String contentType = HTTPCommonUtils.formatContentType(mimeType, charset); serverResponse.setTransportHeader( SOAConstants.HTTP_HEADER_CONTENT_TYPE, contentType); return null; } public void invoke(Message msg, TransportOptions options) throws ServiceException { ServerMessageContext serverCtx = (ServerMessageContext) msg .getContext(); populateClientResponse(serverCtx, m_clientCtx, m_skipSerialization); } public Future<?> invokeAsync(Message msg, TransportOptions transportOptions) throws ServiceException { throw new UnsupportedOperationException( "Async invoke is not supported on " + "LocalTransport.LocalServerResponseTransport"); } public void retrieve(MessageContext context, Future<?> futureResp) throws ServiceException { throw new UnsupportedOperationException( "Async retrieve is not supported on " + "LocalTransport.LocalServerResponseTransport"); } public boolean supportsPoll() { throw new UnsupportedOperationException( "supportsPoll is not supported on " + "LocalTransport.LocalServerResponseTransport"); } } private static class CallBackWorker implements Callable<MessageContext> { private final Future<MessageContext> m_asyncFuture; private final AsyncCallBack m_callback; CallBackWorker(final Future<MessageContext> asyncFuture, final AsyncCallBack callback) { m_asyncFuture = asyncFuture; m_callback = callback; } public MessageContext call() throws Exception { MessageContext msgCtx = null; try { msgCtx = m_asyncFuture.get(); m_callback.onResponseInContext(); } catch (Throwable e) { m_callback.onException(e); } return msgCtx; } } }