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 }