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;
018    
019    import java.net.InetSocketAddress;
020    import java.util.concurrent.ExecutorService;
021    import java.util.concurrent.RejectedExecutionException;
022    
023    import org.apache.camel.AsyncCallback;
024    import org.apache.camel.CamelContext;
025    import org.apache.camel.CamelException;
026    import org.apache.camel.Exchange;
027    import org.apache.camel.NoTypeConversionAvailableException;
028    import org.apache.camel.ServicePoolAware;
029    import org.apache.camel.converter.IOConverter;
030    import org.apache.camel.impl.DefaultAsyncProducer;
031    import org.apache.camel.impl.DefaultExchange;
032    import org.apache.camel.processor.Logger;
033    import org.apache.camel.util.ExchangeHelper;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.jboss.netty.bootstrap.ClientBootstrap;
037    import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
038    import org.jboss.netty.channel.Channel;
039    import org.jboss.netty.channel.ChannelFactory;
040    import org.jboss.netty.channel.ChannelFuture;
041    import org.jboss.netty.channel.ChannelFutureListener;
042    import org.jboss.netty.channel.ChannelPipeline;
043    import org.jboss.netty.channel.group.ChannelGroup;
044    import org.jboss.netty.channel.group.ChannelGroupFuture;
045    import org.jboss.netty.channel.group.DefaultChannelGroup;
046    import org.jboss.netty.channel.socket.DatagramChannelFactory;
047    import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
048    import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory;
049    
050    public class NettyProducer extends DefaultAsyncProducer implements ServicePoolAware {
051        private static final transient Log LOG = LogFactory.getLog(NettyProducer.class);
052        private static final ChannelGroup ALL_CHANNELS = new DefaultChannelGroup("NettyProducer");
053        private CamelContext context;
054        private NettyConfiguration configuration;
055        private ChannelFactory channelFactory;
056        private DatagramChannelFactory datagramChannelFactory;
057        private Logger noReplyLogger;
058    
059        public NettyProducer(NettyEndpoint nettyEndpoint, NettyConfiguration configuration) {
060            super(nettyEndpoint);
061            this.configuration = configuration;
062            this.context = this.getEndpoint().getCamelContext();
063            this.noReplyLogger = new Logger(LOG, configuration.getNoReplyLogLevel());
064        }
065    
066        @Override
067        public NettyEndpoint getEndpoint() {
068            return (NettyEndpoint) super.getEndpoint();
069        }
070    
071        @Override
072        public boolean isSingleton() {
073            // the producer should not be singleton otherwise cannot use concurrent producers and safely
074            // use request/reply with correct correlation
075            return false;
076        }
077    
078        public CamelContext getContext() {
079            return context;
080        }
081    
082        protected boolean isTcp() {
083            return configuration.getProtocol().equalsIgnoreCase("tcp");
084        }
085    
086        @Override
087        protected void doStart() throws Exception {
088            super.doStart();
089    
090            if (isTcp()) {
091                setupTCPCommunication();
092            } else {
093                setupUDPCommunication();
094            }
095    
096            if (!configuration.isLazyChannelCreation()) {
097                // ensure the connection can be established when we start up
098                openAndCloseConnection();
099            }
100        }
101    
102        @Override
103        protected void doStop() throws Exception {
104            if (LOG.isDebugEnabled()) {
105                LOG.debug("Stopping producer at address: " + configuration.getAddress());
106            }
107            // close all channels
108            ChannelGroupFuture future = ALL_CHANNELS.close();
109            future.awaitUninterruptibly();
110    
111            // and then release other resources
112            if (channelFactory != null) {
113                channelFactory.releaseExternalResources();
114            }
115            super.doStop();
116        }
117    
118        public boolean process(final Exchange exchange, final AsyncCallback callback) {
119            if (!isRunAllowed()) {
120                if (exchange.getException() == null) {
121                    exchange.setException(new RejectedExecutionException());
122                }
123                callback.done(true);
124                return true;
125            }
126    
127            Object body = NettyPayloadHelper.getIn(getEndpoint(), exchange);
128            if (body == null) {
129                noReplyLogger.log("No payload to send for exchange: " + exchange);
130                callback.done(true);
131                return true;
132            }
133    
134            // if textline enabled then covert to a String which must be used for textline
135            if (getConfiguration().isTextline()) {
136                try {
137                    body = NettyHelper.getTextlineBody(body, exchange, getConfiguration().getDelimiter(), getConfiguration().isAutoAppendDelimiter());
138                } catch (NoTypeConversionAvailableException e) {
139                    exchange.setException(e);
140                    callback.done(true);
141                    return true;
142                }
143            }
144    
145            // set the exchange encoding property
146            if (getConfiguration().getCharsetName() != null) {
147                exchange.setProperty(Exchange.CHARSET_NAME, IOConverter.normalizeCharset(getConfiguration().getCharsetName()));
148            }
149    
150            ChannelFuture channelFuture;
151            final Channel channel;
152            try {
153                channelFuture = openConnection(exchange, callback);
154                channel = openChannel(channelFuture);
155            } catch (Exception e) {
156                exchange.setException(e);
157                callback.done(true);
158                return true;
159            }
160    
161            // log what we are writing
162            if (LOG.isDebugEnabled()) {
163                LOG.debug("Writing body: " + body);
164            }
165            // write the body asynchronously
166            ChannelFuture future = channel.write(body);
167    
168            // add listener which handles the operation
169            future.addListener(new ChannelFutureListener() {
170                public void operationComplete(ChannelFuture channelFuture) throws Exception {
171                    if (LOG.isDebugEnabled()) {
172                        LOG.debug("Operation complete " + channelFuture);
173                    }
174                    if (!channelFuture.isSuccess()) {
175                        // no success the set the caused exception and signal callback and break
176                        exchange.setException(channelFuture.getCause());
177                        callback.done(false);
178                        return;
179                    }
180    
181                    // if we do not expect any reply then signal callback to continue routing
182                    if (!configuration.isSync()) {
183                        try {
184                            // should channel be closed after complete?
185                            Boolean close;
186                            if (ExchangeHelper.isOutCapable(exchange)) {
187                                close = exchange.getOut().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
188                            } else {
189                                close = exchange.getIn().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
190                            }
191    
192                            // should we disconnect, the header can override the configuration
193                            boolean disconnect = getConfiguration().isDisconnect();
194                            if (close != null) {
195                                disconnect = close;
196                            }
197                            if (disconnect) {
198                                if (LOG.isDebugEnabled()) {
199                                    LOG.debug("Closing channel when complete at address: " + getEndpoint().getConfiguration().getAddress());
200                                }
201                                NettyHelper.close(channel);
202                            }
203                        } finally {
204                            // signal callback to continue routing
205                            callback.done(false);
206                        }
207                    }
208                }
209            });
210    
211            // continue routing asynchronously
212            return false;
213        }
214    
215        protected void setupTCPCommunication() throws Exception {
216            if (channelFactory == null) {
217                ExecutorService bossExecutor = context.getExecutorServiceStrategy().newThreadPool(this, "NettyTCPBoss",
218                        configuration.getCorePoolSize(), configuration.getMaxPoolSize());
219                ExecutorService workerExecutor = context.getExecutorServiceStrategy().newThreadPool(this, "NettyTCPWorker",
220                        configuration.getCorePoolSize(), configuration.getMaxPoolSize());
221                channelFactory = new NioClientSocketChannelFactory(bossExecutor, workerExecutor);
222            }
223        }
224    
225        protected void setupUDPCommunication() throws Exception {
226            if (datagramChannelFactory == null) {
227                ExecutorService workerExecutor = context.getExecutorServiceStrategy().newThreadPool(this, "NettyUDPWorker",
228                        configuration.getCorePoolSize(), configuration.getMaxPoolSize());
229                datagramChannelFactory = new NioDatagramChannelFactory(workerExecutor);
230            }
231        }
232    
233        private ChannelFuture openConnection(Exchange exchange, AsyncCallback callback) throws Exception {
234            ChannelFuture answer;
235            ChannelPipeline clientPipeline;
236    
237            if (configuration.getClientPipelineFactory() != null) {
238                // initialize user defined client pipeline factory
239                configuration.getClientPipelineFactory().setProducer(this);
240                configuration.getClientPipelineFactory().setExchange(exchange);
241                configuration.getClientPipelineFactory().setCallback(callback);
242                clientPipeline = configuration.getClientPipelineFactory().getPipeline();
243            } else {
244                // initialize client pipeline factory
245                ClientPipelineFactory clientPipelineFactory = new DefaultClientPipelineFactory(this, exchange, callback);
246                // must get the pipeline from the factory when opening a new connection
247                clientPipeline = clientPipelineFactory.getPipeline();
248            }
249    
250            if (isTcp()) {
251                ClientBootstrap clientBootstrap = new ClientBootstrap(channelFactory);
252                clientBootstrap.setOption("child.keepAlive", configuration.isKeepAlive());
253                clientBootstrap.setOption("child.tcpNoDelay", configuration.isTcpNoDelay());
254                clientBootstrap.setOption("child.reuseAddress", configuration.isReuseAddress());
255                clientBootstrap.setOption("child.connectTimeoutMillis", configuration.getConnectTimeout());
256    
257                // set the pipeline on the bootstrap
258                clientBootstrap.setPipeline(clientPipeline);
259                answer = clientBootstrap.connect(new InetSocketAddress(configuration.getHost(), configuration.getPort()));
260                return answer;
261            } else {
262                ConnectionlessBootstrap connectionlessClientBootstrap = new ConnectionlessBootstrap(datagramChannelFactory);
263                connectionlessClientBootstrap.setOption("child.keepAlive", configuration.isKeepAlive());
264                connectionlessClientBootstrap.setOption("child.tcpNoDelay", configuration.isTcpNoDelay());
265                connectionlessClientBootstrap.setOption("child.reuseAddress", configuration.isReuseAddress());
266                connectionlessClientBootstrap.setOption("child.connectTimeoutMillis", configuration.getConnectTimeout());
267                connectionlessClientBootstrap.setOption("child.broadcast", configuration.isBroadcast());
268                connectionlessClientBootstrap.setOption("sendBufferSize", configuration.getSendBufferSize());
269                connectionlessClientBootstrap.setOption("receiveBufferSize", configuration.getReceiveBufferSize());
270    
271                // set the pipeline on the bootstrap
272                connectionlessClientBootstrap.setPipeline(clientPipeline);
273                connectionlessClientBootstrap.bind(new InetSocketAddress(0));
274                answer = connectionlessClientBootstrap.connect(new InetSocketAddress(configuration.getHost(), configuration.getPort()));
275                return answer;
276            }
277        }
278    
279        private Channel openChannel(ChannelFuture channelFuture) throws Exception {
280            // wait until we got connection
281            channelFuture.awaitUninterruptibly();
282            if (!channelFuture.isSuccess()) {
283                throw new CamelException("Cannot connect to " + configuration.getAddress(), channelFuture.getCause());
284            }
285            Channel channel = channelFuture.getChannel();
286            // to keep track of all channels in use
287            ALL_CHANNELS.add(channel);
288    
289            if (LOG.isDebugEnabled()) {
290                LOG.debug("Creating connector to address: " + configuration.getAddress());
291            }
292            return channel;
293        }
294    
295        private void openAndCloseConnection() throws Exception {
296            ChannelFuture future = openConnection(new DefaultExchange(context), new AsyncCallback() {
297                public void done(boolean doneSync) {
298                    // noop
299                }
300            });
301            Channel channel = openChannel(future);
302            NettyHelper.close(channel);
303        }
304    
305        public NettyConfiguration getConfiguration() {
306            return configuration;
307        }
308    
309        public void setConfiguration(NettyConfiguration configuration) {
310            this.configuration = configuration;
311        }
312    
313        public ChannelFactory getChannelFactory() {
314            return channelFactory;
315        }
316    
317        public void setChannelFactory(ChannelFactory channelFactory) {
318            this.channelFactory = channelFactory;
319        }
320    
321        public ChannelGroup getAllChannels() {
322            return ALL_CHANNELS;
323        }
324    }