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