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 }