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.NoTypeConversionAvailableException;
023    import org.apache.camel.component.netty.NettyConstants;
024    import org.apache.camel.component.netty.NettyHelper;
025    import org.apache.camel.component.netty.NettyPayloadHelper;
026    import org.apache.camel.component.netty.NettyProducer;
027    import org.apache.camel.util.ExchangeHelper;
028    import org.jboss.netty.channel.ChannelHandlerContext;
029    import org.jboss.netty.channel.ChannelStateEvent;
030    import org.jboss.netty.channel.ExceptionEvent;
031    import org.jboss.netty.channel.MessageEvent;
032    import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
033    import org.slf4j.Logger;
034    import org.slf4j.LoggerFactory;
035    
036    /**
037     * Client handler which cannot be shared
038     */
039    public class ClientChannelHandler extends SimpleChannelUpstreamHandler {
040        private static final transient Logger LOG = LoggerFactory.getLogger(ClientChannelHandler.class);
041        private final NettyProducer producer;
042        private final Exchange exchange;
043        private final AsyncCallback callback;
044        private boolean messageReceived;
045        private boolean exceptionHandled;
046    
047        public ClientChannelHandler(NettyProducer producer, Exchange exchange, AsyncCallback callback) {
048            super();
049            this.producer = producer;
050            this.exchange = exchange;
051            this.callback = callback;
052        }
053    
054        @Override
055        public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent channelStateEvent) throws Exception {
056            // to keep track of open sockets
057            producer.getAllChannels().add(channelStateEvent.getChannel());
058        }
059    
060        @Override
061        public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent exceptionEvent) throws Exception {
062            if (LOG.isTraceEnabled()) {
063                LOG.trace("Exception caught at Channel: " + ctx.getChannel(), exceptionEvent.getCause());
064    
065            }
066            if (exceptionHandled) {
067                // ignore subsequent exceptions being thrown
068                return;
069            }
070    
071            exceptionHandled = true;
072            Throwable cause = exceptionEvent.getCause();
073    
074            if (LOG.isDebugEnabled()) {
075                LOG.debug("Closing channel as an exception was thrown from Netty", cause);
076            }
077            // set the cause on the exchange
078            exchange.setException(cause);
079    
080            // close channel in case an exception was thrown
081            NettyHelper.close(exceptionEvent.getChannel());
082    
083            // signal callback
084            callback.done(false);
085        }
086    
087        @Override
088        public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
089            if (LOG.isTraceEnabled()) {
090                LOG.trace("Channel closed: " + ctx.getChannel());
091            }
092    
093            if (producer.getConfiguration().isSync() && !messageReceived && !exceptionHandled) {
094                // session was closed but no message received. This could be because the remote server had an internal error
095                // and could not return a response. We should count down to stop waiting for a response
096                if (LOG.isDebugEnabled()) {
097                    LOG.debug("Channel closed but no message received from address: " + producer.getConfiguration().getAddress());
098                }
099                exchange.setException(new CamelExchangeException("No response received from remote server: " + producer.getConfiguration().getAddress(), exchange));
100                // signal callback
101                callback.done(false);
102            }
103        }
104    
105        @Override
106        public void messageReceived(ChannelHandlerContext ctx, MessageEvent messageEvent) throws Exception {
107            messageReceived = true;
108    
109            Object body = messageEvent.getMessage();
110            if (LOG.isDebugEnabled()) {
111                LOG.debug("Message received: " + body);
112            }
113    
114            // if textline enabled then covert to a String which must be used for textline
115            if (producer.getConfiguration().isTextline()) {
116                try {
117                    body = producer.getContext().getTypeConverter().mandatoryConvertTo(String.class, exchange, body);
118                } catch (NoTypeConversionAvailableException e) {
119                    exchange.setException(e);
120                    callback.done(false);
121                }
122            }
123    
124    
125            // set the result on either IN or OUT on the original exchange depending on its pattern
126            if (ExchangeHelper.isOutCapable(exchange)) {
127                NettyPayloadHelper.setOut(exchange, body);
128            } else {
129                NettyPayloadHelper.setIn(exchange, body);
130            }
131    
132            try {
133                // should channel be closed after complete?
134                Boolean close;
135                if (ExchangeHelper.isOutCapable(exchange)) {
136                    close = exchange.getOut().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
137                } else {
138                    close = exchange.getIn().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
139                }
140    
141                // should we disconnect, the header can override the configuration
142                boolean disconnect = producer.getConfiguration().isDisconnect();
143                if (close != null) {
144                    disconnect = close;
145                }
146                if (disconnect) {
147                    if (LOG.isDebugEnabled()) {
148                        LOG.debug("Closing channel when complete at address: " + producer.getConfiguration().getAddress());
149                    }
150                    NettyHelper.close(ctx.getChannel());
151                }
152            } finally {
153                // signal callback
154                callback.done(false);
155            }
156        }
157    
158    }