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 }