001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.netty.handlers;
018    
019    import org.apache.camel.AsyncCallback;
020    import org.apache.camel.CamelExchangeException;
021    import org.apache.camel.Exchange;
022    import org.apache.camel.ExchangeTimedOutException;
023    import org.apache.camel.NoTypeConversionAvailableException;
024    import org.apache.camel.component.netty.NettyConstants;
025    import org.apache.camel.component.netty.NettyHelper;
026    import org.apache.camel.component.netty.NettyPayloadHelper;
027    import org.apache.camel.component.netty.NettyProducer;
028    import org.apache.camel.util.ExchangeHelper;
029    import org.apache.commons.logging.Log;
030    import org.apache.commons.logging.LogFactory;
031    import org.jboss.netty.channel.ChannelHandlerContext;
032    import org.jboss.netty.channel.ChannelStateEvent;
033    import org.jboss.netty.channel.ExceptionEvent;
034    import org.jboss.netty.channel.MessageEvent;
035    import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
036    import org.jboss.netty.handler.timeout.TimeoutException;
037    
038    /**
039     * Client handler which cannot be shared
040     */
041    public class ClientChannelHandler extends SimpleChannelUpstreamHandler {
042        private static final transient Log LOG = LogFactory.getLog(ClientChannelHandler.class);
043        private final NettyProducer producer;
044        private final Exchange exchange;
045        private final AsyncCallback callback;
046        private boolean messageReceived;
047        private boolean exceptionHandled;
048    
049        public ClientChannelHandler(NettyProducer producer, Exchange exchange, AsyncCallback callback) {
050            super();
051            this.producer = producer;
052            this.exchange = exchange;
053            this.callback = callback;
054        }
055    
056        @Override
057        public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent channelStateEvent) throws Exception {
058            // to keep track of open sockets
059            producer.getAllChannels().add(channelStateEvent.getChannel());
060        }
061    
062        @Override
063        public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent exceptionEvent) throws Exception {
064            if (LOG.isTraceEnabled()) {
065                LOG.trace("Exception caught at Channel: " + ctx.getChannel(), exceptionEvent.getCause());
066    
067            }
068            if (exceptionHandled) {
069                // ignore subsequent exceptions being thrown
070                return;
071            }
072    
073            exceptionHandled = true;
074            Throwable cause = exceptionEvent.getCause();
075    
076            // was it the timeout
077            if (cause instanceof TimeoutException) {
078                // timeout occurred
079                exchange.setException(new ExchangeTimedOutException(exchange, producer.getConfiguration().getTimeout()));
080    
081                // signal callback
082                callback.done(false);
083            } else {
084                if (LOG.isDebugEnabled()) {
085                    LOG.debug("Closing channel as an exception was thrown from Netty", cause);
086                }
087                // set the cause on the exchange
088                exchange.setException(cause);
089    
090                // close channel in case an exception was thrown
091                NettyHelper.close(exceptionEvent.getChannel());
092    
093                // signal callback
094                callback.done(false);
095            }
096        }
097    
098        @Override
099        public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
100            if (LOG.isTraceEnabled()) {
101                LOG.trace("Channel closed: " + ctx.getChannel());
102            }
103    
104            if (producer.getConfiguration().isSync() && !messageReceived && !exceptionHandled) {
105                // session was closed but no message received. This could be because the remote server had an internal error
106                // and could not return a response. We should count down to stop waiting for a response
107                if (LOG.isDebugEnabled()) {
108                    LOG.debug("Channel closed but no message received from address: " + producer.getConfiguration().getAddress());
109                }
110                exchange.setException(new CamelExchangeException("No response received from remote server: " + producer.getConfiguration().getAddress(), exchange));
111                // signal callback
112                callback.done(false);
113            }
114        }
115    
116        @Override
117        public void messageReceived(ChannelHandlerContext ctx, MessageEvent messageEvent) throws Exception {
118            messageReceived = true;
119    
120            Object body = messageEvent.getMessage();
121            if (LOG.isDebugEnabled()) {
122                LOG.debug("Message received: " + body);
123            }
124    
125            // if textline enabled then covert to a String which must be used for textline
126            if (producer.getConfiguration().isTextline()) {
127                try {
128                    body = producer.getContext().getTypeConverter().mandatoryConvertTo(String.class, exchange, body);
129                } catch (NoTypeConversionAvailableException e) {
130                    exchange.setException(e);
131                    callback.done(false);
132                }
133            }
134    
135    
136            // set the result on either IN or OUT on the original exchange depending on its pattern
137            if (ExchangeHelper.isOutCapable(exchange)) {
138                NettyPayloadHelper.setOut(exchange, body);
139            } else {
140                NettyPayloadHelper.setIn(exchange, body);
141            }
142    
143            try {
144                // should channel be closed after complete?
145                Boolean close;
146                if (ExchangeHelper.isOutCapable(exchange)) {
147                    close = exchange.getOut().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
148                } else {
149                    close = exchange.getIn().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
150                }
151    
152                // should we disconnect, the header can override the configuration
153                boolean disconnect = producer.getConfiguration().isDisconnect();
154                if (close != null) {
155                    disconnect = close;
156                }
157                if (disconnect) {
158                    if (LOG.isDebugEnabled()) {
159                        LOG.debug("Closing channel when complete at address: " + producer.getConfiguration().getAddress());
160                    }
161                    NettyHelper.close(ctx.getChannel());
162                }
163            } finally {
164                // signal callback
165                callback.done(false);
166            }
167        }
168    
169    }