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.CountDownLatch;
021    import java.util.concurrent.ExecutorService;
022    import java.util.concurrent.TimeUnit;
023    
024    import org.apache.camel.CamelContext;
025    import org.apache.camel.CamelException;
026    import org.apache.camel.CamelExchangeException;
027    import org.apache.camel.Exchange;
028    import org.apache.camel.ExchangeTimedOutException;
029    import org.apache.camel.ServicePoolAware;
030    import org.apache.camel.component.netty.handlers.ClientChannelHandler;
031    import org.apache.camel.impl.DefaultProducer;
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.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 DefaultProducer implements ServicePoolAware {
050        private static final transient Log LOG = LogFactory.getLog(NettyProducer.class);
051        private final ChannelGroup allChannels;
052        private CamelContext context;
053        private NettyConfiguration configuration;
054        private CountDownLatch countdownLatch;
055        private ChannelFactory channelFactory;
056        private DatagramChannelFactory datagramChannelFactory;
057        private Channel channel;
058        private ClientBootstrap clientBootstrap;
059        private ConnectionlessBootstrap connectionlessClientBootstrap;
060        private ClientPipelineFactory clientPipelineFactory;
061        private ChannelPipeline clientPipeline;
062        private Logger noReplyLogger;
063    
064        public NettyProducer(NettyEndpoint nettyEndpoint, NettyConfiguration configuration) {
065            super(nettyEndpoint);
066            this.configuration = configuration;
067            this.context = this.getEndpoint().getCamelContext();
068            this.allChannels = new DefaultChannelGroup("NettyProducer-" + nettyEndpoint.getEndpointUri());
069            this.noReplyLogger = new Logger(LOG, configuration.getNoReplyLogLevel());
070        }
071    
072        @Override
073        public NettyEndpoint getEndpoint() {
074            return (NettyEndpoint) super.getEndpoint();
075        }
076    
077        @Override
078        public boolean isSingleton() {
079            // the producer should not be singleton otherwise cannot use concurrent producers and safely
080            // use request/reply with correct correlation
081            return false;
082        }
083    
084        @Override
085        protected void doStart() throws Exception {
086            super.doStart();
087    
088            if (configuration.getProtocol().equalsIgnoreCase("udp")) {
089                setupUDPCommunication();
090            } else {
091                setupTCPCommunication();
092            }
093            if (!configuration.isLazyChannelCreation()) {
094                openConnection();
095            }
096        }
097    
098        @Override
099        protected void doStop() throws Exception {
100            if (LOG.isDebugEnabled()) {
101                LOG.debug("Stopping producer at address: " + configuration.getAddress());
102            }
103            closeConnection();
104            super.doStop();
105        }
106    
107        public void process(Exchange exchange) throws Exception {
108            if (channel == null && !configuration.isLazyChannelCreation()) {
109                throw new IllegalStateException("Not started yet!");
110            }
111            if (channel == null || !channel.isConnected()) {
112                openConnection();
113            }
114    
115            Object body = NettyPayloadHelper.getIn(getEndpoint(), exchange);
116            if (body == null) {
117                noReplyLogger.log("No payload to send for exchange: " + exchange);
118                return; // exit early since nothing to write
119            }
120    
121            if (configuration.isSync()) {
122                // only initialize latch if we should get a response
123                countdownLatch = new CountDownLatch(1);
124            }
125    
126            // log what we are writing
127            if (LOG.isDebugEnabled()) {
128                Object out = body;
129                if (body instanceof byte[]) {
130                    // byte arrays is not readable so convert to string
131                    out = exchange.getContext().getTypeConverter().convertTo(String.class, body);
132                }
133                LOG.debug("Writing body : " + out);
134            }
135    
136            // write the body
137            NettyHelper.writeBody(channel, null, body, exchange);
138    
139            if (configuration.isSync()) {
140                boolean success = countdownLatch.await(configuration.getTimeout(), TimeUnit.MILLISECONDS);
141                if (!success) {
142                    throw new ExchangeTimedOutException(exchange, configuration.getTimeout());
143                }
144    
145                ClientChannelHandler handler = (ClientChannelHandler) clientPipeline.get("handler");
146                if (handler.getCause() != null) {
147                    throw new CamelExchangeException("Error occurred in ClientChannelHandler", exchange, handler.getCause());
148                } else if (!handler.isMessageReceived()) {
149                    // no message received
150                    throw new CamelExchangeException("No response received from remote server: " + configuration.getAddress(), exchange);
151                } else {
152                    // set the result on either IN or OUT on the original exchange depending on its pattern
153                    if (ExchangeHelper.isOutCapable(exchange)) {
154                        NettyPayloadHelper.setOut(exchange, handler.getMessage());
155                    } else {
156                        NettyPayloadHelper.setIn(exchange, handler.getMessage());
157                    }
158                }
159            }
160    
161            // should channel be closed after complete?
162            Boolean close;
163            if (ExchangeHelper.isOutCapable(exchange)) {
164                close = exchange.getOut().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
165            } else {
166                close = exchange.getIn().getHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, Boolean.class);
167            }
168    
169            // should we disconnect, the header can override the configuration
170            boolean disconnect = getConfiguration().isDisconnect();
171            if (close != null) {
172                disconnect = close;
173            }
174            if (disconnect) {
175                if (LOG.isDebugEnabled()) {
176                    LOG.debug("Closing channel when complete at address: " + getEndpoint().getConfiguration().getAddress());
177                }
178                NettyHelper.close(channel);
179            }
180        }
181    
182        protected void setupTCPCommunication() throws Exception {
183            if (channelFactory == null) {
184                ExecutorService bossExecutor = context.getExecutorServiceStrategy().newThreadPool(this, "NettyTCPBoss",
185                        configuration.getCorePoolSize(), configuration.getMaxPoolSize());
186                ExecutorService workerExecutor = context.getExecutorServiceStrategy().newThreadPool(this, "NettyTCPWorker",
187                        configuration.getCorePoolSize(), configuration.getMaxPoolSize());
188                channelFactory = new NioClientSocketChannelFactory(bossExecutor, workerExecutor);
189            }
190            if (clientBootstrap == null) {
191                clientBootstrap = new ClientBootstrap(channelFactory);
192                clientBootstrap.setOption("child.keepAlive", configuration.isKeepAlive());
193                clientBootstrap.setOption("child.tcpNoDelay", configuration.isTcpNoDelay());
194                clientBootstrap.setOption("child.reuseAddress", configuration.isReuseAddress());
195                clientBootstrap.setOption("child.connectTimeoutMillis", configuration.getConnectTimeout());
196            }
197        }
198    
199        protected void setupUDPCommunication() throws Exception {
200            if (datagramChannelFactory == null) {
201                ExecutorService workerExecutor = context.getExecutorServiceStrategy().newThreadPool(this, "NettyUDPWorker",
202                        configuration.getCorePoolSize(), configuration.getMaxPoolSize());
203                datagramChannelFactory = new NioDatagramChannelFactory(workerExecutor);
204            }
205            if (connectionlessClientBootstrap == null) {
206                connectionlessClientBootstrap = new ConnectionlessBootstrap(datagramChannelFactory);
207                connectionlessClientBootstrap.setOption("child.keepAlive", configuration.isKeepAlive());
208                connectionlessClientBootstrap.setOption("child.tcpNoDelay", configuration.isTcpNoDelay());
209                connectionlessClientBootstrap.setOption("child.reuseAddress", configuration.isReuseAddress());
210                connectionlessClientBootstrap.setOption("child.connectTimeoutMillis", configuration.getConnectTimeout());
211                connectionlessClientBootstrap.setOption("child.broadcast", configuration.isBroadcast());
212                connectionlessClientBootstrap.setOption("sendBufferSize", configuration.getSendBufferSize());
213                connectionlessClientBootstrap.setOption("receiveBufferSize", configuration.getReceiveBufferSize());
214    
215            }
216        }
217    
218        private void openConnection() throws Exception {
219            ChannelFuture channelFuture;
220    
221            // initialize client pipeline factory
222            if (clientPipelineFactory == null) {
223                clientPipelineFactory = new ClientPipelineFactory(this);
224            }
225            // must get the pipeline from the factory when opening a new connection
226            clientPipeline = clientPipelineFactory.getPipeline();
227    
228            if (clientBootstrap != null) {
229                clientBootstrap.setPipeline(clientPipeline);
230                channelFuture = clientBootstrap.connect(new InetSocketAddress(configuration.getHost(), configuration.getPort()));
231            } else if (connectionlessClientBootstrap != null) {
232                connectionlessClientBootstrap.setPipeline(clientPipeline);
233                connectionlessClientBootstrap.bind(new InetSocketAddress(0));
234                channelFuture = connectionlessClientBootstrap.connect(new InetSocketAddress(configuration.getHost(), configuration.getPort()));
235            } else {
236                throw new IllegalStateException("Should either be TCP or UDP");
237            }
238    
239            channelFuture.awaitUninterruptibly();
240            if (!channelFuture.isSuccess()) {
241                throw new CamelException("Cannot connect to " + configuration.getAddress(), channelFuture.getCause());
242            }
243            channel = channelFuture.getChannel();
244            // to keep track of all channels in use
245            allChannels.add(channel);
246    
247            if (LOG.isDebugEnabled()) {
248                LOG.debug("Creating connector to address: " + configuration.getAddress());
249            }
250        }
251    
252        private void closeConnection() throws Exception {
253            // close all channels
254            ChannelGroupFuture future = allChannels.close();
255            future.awaitUninterruptibly();
256    
257            // and then release other resources
258            if (channelFactory != null) {
259                channelFactory.releaseExternalResources();
260            }
261        }
262    
263        public NettyConfiguration getConfiguration() {
264            return configuration;
265        }
266    
267        public void setConfiguration(NettyConfiguration configuration) {
268            this.configuration = configuration;
269        }
270    
271        public CountDownLatch getCountdownLatch() {
272            return countdownLatch;
273        }
274    
275        public ChannelFactory getChannelFactory() {
276            return channelFactory;
277        }
278    
279        public void setChannelFactory(ChannelFactory channelFactory) {
280            this.channelFactory = channelFactory;
281        }
282    
283        public ClientBootstrap getClientBootstrap() {
284            return clientBootstrap;
285        }
286    
287        public void setClientBootstrap(ClientBootstrap clientBootstrap) {
288            this.clientBootstrap = clientBootstrap;
289        }
290    
291        public ClientPipelineFactory getClientPipelineFactory() {
292            return clientPipelineFactory;
293        }
294    
295        public void setClientPipelineFactory(ClientPipelineFactory clientPipelineFactory) {
296            this.clientPipelineFactory = clientPipelineFactory;
297        }
298    
299        public ChannelGroup getAllChannels() {
300            return allChannels;
301        }
302    }