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 }