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