/* * Copyright (c) 2005-2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.synapse.transport.passthru; import org.apache.axis2.AxisFault; import org.apache.axis2.context.MessageContext; import org.apache.axis2.addressing.EndpointReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.nio.NHttpClientConnection; import org.apache.synapse.transport.http.conn.ProxyConfig; import org.apache.synapse.transport.http.conn.SynapseDebugInfoHolder; import org.apache.synapse.transport.passthru.config.TargetConfiguration; import org.apache.synapse.transport.passthru.connections.TargetConnections; import org.apache.synapse.transport.passthru.util.TargetRequestFactory; import java.io.OutputStream; import java.util.Queue; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.net.URL; import java.net.MalformedURLException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * This class acts as a gateway for differed delivery of the messages. When a message is to be * delivered it is submitted to this class. If a connection is available to the target this * class will try to deliver the message immediately over that connection. If a connection is * not available it will queue the message and request a connection from the pool. When a new * connection is available a queued message will be sent through it. */ public class DeliveryAgent { private static final Log log = LogFactory.getLog(DeliveryAgent.class); /** * This Map holds the messages that need to be delivered. But at the moment maximum * number of connections to the host:pair is being used. So these messages has to wait * until a new connection is available. */ private Map<HttpRoute, Queue<MessageContext>> waitingMessages = new ConcurrentHashMap<HttpRoute, Queue<MessageContext>>(); /** The connection management */ private TargetConnections targetConnections; /** Configuration of the sender */ private TargetConfiguration targetConfiguration; /** Proxy config */ private ProxyConfig proxyConfig; /** The maximum number of messages that can wait for a connection */ private int maxWaitingMessages = Integer.MAX_VALUE; private TargetErrorHandler targetErrorHandler; /** Lock for synchronizing access */ private Lock lock = new ReentrantLock(); /** * Create a delivery agent with the target configuration and connection management. * * @param targetConfiguration configuration of the sender * @param targetConnections connection management */ public DeliveryAgent(TargetConfiguration targetConfiguration, TargetConnections targetConnections, ProxyConfig proxyConfig) { this.targetConfiguration = targetConfiguration; this.targetConnections = targetConnections; this.proxyConfig = proxyConfig; this.targetErrorHandler = new TargetErrorHandler(targetConfiguration); } /** * This method queues the message for delivery. If a connection is already existing for * the destination epr, the message will be delivered immediately. Otherwise message has * to wait until a connection is established. In this case this method will inform the * system about the need for a connection. * * @param msgContext the message context to be sent * @param epr the endpoint to which the message should be sent * @throws AxisFault if an error occurs */ public void submit(MessageContext msgContext, EndpointReference epr) throws AxisFault { try { URL url = new URL(epr.getAddress()); String scheme = url.getProtocol() != null ? url.getProtocol() : "http"; String hostname = url.getHost(); int port = url.getPort(); if (port == -1) { // use default if ("http".equals(scheme)) { port = 80; } else if ("https".equals(scheme)) { port = 443; } } HttpHost target = new HttpHost(hostname, port, scheme); boolean secure = "https".equalsIgnoreCase(target.getSchemeName()); HttpHost proxy = proxyConfig.selectProxy(target); msgContext.setProperty(PassThroughConstants.PROXY_PROFILE_TARGET_HOST, target.getHostName()); HttpRoute route; if (proxy != null) { route = new HttpRoute(target, null, proxy, secure); } else { route = new HttpRoute(target, null, secure); } // first we queue the message Queue<MessageContext> queue = null; lock.lock(); try { queue = waitingMessages.get(route); if (queue == null) { queue = new ConcurrentLinkedQueue<MessageContext>(); waitingMessages.put(route, queue); } if (queue.size() == maxWaitingMessages) { MessageContext msgCtx = queue.poll(); targetErrorHandler.handleError(msgCtx, ErrorCodes.CONNECTION_TIMEOUT, "Error connecting to the back end", null, ProtocolState.REQUEST_READY); } queue.add(msgContext); } finally { lock.unlock(); } NHttpClientConnection conn = targetConnections.getConnection(route); if (conn != null) { conn.resetInput(); conn.resetOutput(); MessageContext messageContext = queue.poll(); if (messageContext != null) { tryNextMessage(messageContext, route, conn); } } } catch (MalformedURLException e) { handleException("Malformed URL in the target EPR", e); } } public void errorConnecting(HttpRoute route, int errorCode, String message) { Queue<MessageContext> queue = waitingMessages.get(route); if (queue != null) { MessageContext msgCtx = queue.poll(); if (msgCtx != null) { targetErrorHandler.handleError(msgCtx, errorCode, "Error connecting to the back end", null, ProtocolState.REQUEST_READY); synchronized (msgCtx) { msgCtx.setProperty(PassThroughConstants.WAIT_BUILDER_IN_STREAM_COMPLETE, Boolean.TRUE); msgCtx.notifyAll(); } } } else { throw new IllegalStateException("Queue cannot be null for: " + route); } } /** * Notification for a connection availability. When this occurs a message in the * queue for delivery will be tried. * * @param host name of the remote host * @param port remote port number */ public void connected(HttpRoute route, NHttpClientConnection conn) { Queue<MessageContext> queue = null; lock.lock(); try { queue = waitingMessages.get(route); } finally { lock.unlock(); } while (queue.size() > 0) { if(conn == null) { conn = targetConnections.getExistingConnection(route); } if (conn != null) { MessageContext messageContext = queue.poll(); if (messageContext != null) { tryNextMessage(messageContext, route, conn); conn = null; } } else { break; } } //releasing the connection to pool when connection is not null and message queue is empty, //otherwise connection remains in busyConnections pool till it gets timeout and It is not used. if(conn != null && TargetContext.getState(conn) == ProtocolState.REQUEST_READY) { targetConfiguration.getConnections().releaseConnection(conn); } } private void tryNextMessage(MessageContext messageContext, HttpRoute route, NHttpClientConnection conn) { if (conn != null) { try { TargetContext.updateState(conn, ProtocolState.REQUEST_READY); TargetContext.get(conn).setRequestMsgCtx(messageContext); submitRequest(conn, route, messageContext); } catch (AxisFault e) { log.error("IO error while sending the request out", e); } } } private void submitRequest(NHttpClientConnection conn, HttpRoute route, MessageContext msgContext) throws AxisFault { if (log.isDebugEnabled()) { log.debug("Submitting new request to the connection: " + conn); } if (SynapseDebugInfoHolder.getInstance().isDebuggerEnabled()) { conn.getContext().setAttribute(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_HOLDER_PROPERTY, msgContext.getProperty(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_HOLDER_PROPERTY)); conn.getContext().setAttribute(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_MEDIATOR_ID_PROPERTY, msgContext.getProperty(SynapseDebugInfoHolder.SYNAPSE_WIRE_LOG_MEDIATOR_ID_PROPERTY)); } TargetRequest request = TargetRequestFactory.create(msgContext, route, targetConfiguration); TargetContext.setRequest(conn, request); Pipe pipe = (Pipe) msgContext.getProperty(PassThroughConstants.PASS_THROUGH_PIPE); if (pipe != null) { pipe.attachConsumer(conn); request.connect(pipe); if (Boolean.TRUE.equals(msgContext.getProperty(PassThroughConstants.MESSAGE_BUILDER_INVOKED))) { synchronized (msgContext) { OutputStream out = pipe.getOutputStream(); msgContext.setProperty(PassThroughConstants.BUILDER_OUTPUT_STREAM, out); msgContext.setProperty(PassThroughConstants.WAIT_BUILDER_IN_STREAM_COMPLETE, Boolean.TRUE); msgContext.notifyAll(); } return; } } conn.requestOutput(); } /** * Throws an AxisFault if an error occurs at this level * @param s a message describing the error * @param e original exception leads to the error condition * @throws AxisFault wrapping the original exception */ private void handleException(String s, Exception e) throws AxisFault { log.error(s, e); throw new AxisFault(s, e); } }