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.mina;
018
019 import java.net.SocketAddress;
020 import java.util.concurrent.CountDownLatch;
021 import java.util.concurrent.TimeUnit;
022
023 import org.apache.camel.CamelExchangeException;
024 import org.apache.camel.Exchange;
025 import org.apache.camel.ExchangeTimedOutException;
026 import org.apache.camel.ServicePoolAware;
027 import org.apache.camel.impl.DefaultProducer;
028 import org.apache.camel.processor.Logger;
029 import org.apache.camel.util.ExchangeHelper;
030 import org.apache.commons.logging.Log;
031 import org.apache.commons.logging.LogFactory;
032 import org.apache.mina.common.ConnectFuture;
033 import org.apache.mina.common.IoConnector;
034 import org.apache.mina.common.IoHandler;
035 import org.apache.mina.common.IoHandlerAdapter;
036 import org.apache.mina.common.IoSession;
037 import org.apache.mina.transport.socket.nio.SocketConnector;
038
039 /**
040 * A {@link org.apache.camel.Producer} implementation for MINA
041 *
042 * @version $Revision: 20195 $
043 */
044 public class MinaProducer extends DefaultProducer implements ServicePoolAware {
045 private static final transient Log LOG = LogFactory.getLog(MinaProducer.class);
046 private IoSession session;
047 private MinaEndpoint endpoint;
048 private CountDownLatch latch;
049 private boolean lazySessionCreation;
050 private long timeout;
051 private IoConnector connector;
052 private boolean sync;
053 private Logger noReplyLogger;
054
055 public MinaProducer(MinaEndpoint endpoint) {
056 super(endpoint);
057 this.endpoint = endpoint;
058 this.lazySessionCreation = endpoint.getConfiguration().isLazySessionCreation();
059 this.timeout = endpoint.getConfiguration().getTimeout();
060 this.sync = endpoint.getConfiguration().isSync();
061 this.noReplyLogger = new Logger(LOG, endpoint.getConfiguration().getNoReplyLogLevel());
062 }
063
064 @Override
065 public boolean isSingleton() {
066 // the producer should not be singleton otherwise cannot use concurrent producers and safely
067 // use request/reply with correct correlation
068 return false;
069 }
070
071 public void process(Exchange exchange) throws Exception {
072 if (session == null && !lazySessionCreation) {
073 throw new IllegalStateException("Not started yet!");
074 }
075 if (session == null || !session.isConnected()) {
076 openConnection();
077 }
078
079 // set the exchange encoding property
080 if (endpoint.getConfiguration().getCharsetName() != null) {
081 exchange.setProperty(Exchange.CHARSET_NAME, endpoint.getConfiguration().getCharsetName());
082 }
083
084 Object body = MinaPayloadHelper.getIn(endpoint, exchange);
085 if (body == null) {
086 noReplyLogger.log("No payload to send for exchange: " + exchange);
087 return; // exit early since nothing to write
088 }
089
090 // if textline enabled then covert to a String which must be used for textline
091 if (endpoint.getConfiguration().isTextline()) {
092 body = endpoint.getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, exchange, body);
093 }
094
095 // if sync is true then we should also wait for a response (synchronous mode)
096 if (sync) {
097 // only initialize latch if we should get a response
098 latch = new CountDownLatch(1);
099 // reset handler if we expect a response
100 ResponseHandler handler = (ResponseHandler) session.getHandler();
101 handler.reset();
102 }
103
104 // log what we are writing
105 if (LOG.isDebugEnabled()) {
106 Object out = body;
107 if (body instanceof byte[]) {
108 // byte arrays is not readable so convert to string
109 out = exchange.getContext().getTypeConverter().convertTo(String.class, body);
110 }
111 LOG.debug("Writing body : " + out);
112 }
113 // write the body
114 MinaHelper.writeBody(session, body, exchange);
115
116 if (sync) {
117 // wait for response, consider timeout
118 if (LOG.isDebugEnabled()) {
119 LOG.debug("Waiting for response using timeout " + timeout + " millis.");
120 }
121 boolean done = latch.await(timeout, TimeUnit.MILLISECONDS);
122 if (!done) {
123 throw new ExchangeTimedOutException(exchange, timeout);
124 }
125
126 // did we get a response
127 ResponseHandler handler = (ResponseHandler) session.getHandler();
128 if (handler.getCause() != null) {
129 throw new CamelExchangeException("Error occurred in ResponseHandler", exchange, handler.getCause());
130 } else if (!handler.isMessageReceived()) {
131 // no message received
132 throw new CamelExchangeException("No response received from remote server: " + endpoint.getEndpointUri(), exchange);
133 } else {
134 // set the result on either IN or OUT on the original exchange depending on its pattern
135 if (ExchangeHelper.isOutCapable(exchange)) {
136 MinaPayloadHelper.setOut(exchange, handler.getMessage());
137 } else {
138 MinaPayloadHelper.setIn(exchange, handler.getMessage());
139 }
140 }
141 }
142
143 // should session be closed after complete?
144 Boolean close;
145 if (ExchangeHelper.isOutCapable(exchange)) {
146 close = exchange.getOut().getHeader(MinaConstants.MINA_CLOSE_SESSION_WHEN_COMPLETE, Boolean.class);
147 } else {
148 close = exchange.getIn().getHeader(MinaConstants.MINA_CLOSE_SESSION_WHEN_COMPLETE, Boolean.class);
149 }
150
151 // should we disconnect, the header can override the configuration
152 boolean disconnect = endpoint.getConfiguration().isDisconnect();
153 if (close != null) {
154 disconnect = close;
155 }
156 if (disconnect) {
157 if (LOG.isDebugEnabled()) {
158 LOG.debug("Closing session when complete at address: " + endpoint.getAddress());
159 }
160 session.close();
161 }
162 }
163
164 @Override
165 protected void doStart() throws Exception {
166 super.doStart();
167 if (!lazySessionCreation) {
168 openConnection();
169 }
170 }
171
172 @Override
173 protected void doStop() throws Exception {
174 if (LOG.isDebugEnabled()) {
175 LOG.debug("Stopping connector: " + connector + " at address: " + endpoint.getAddress());
176 }
177 closeConnection();
178 super.doStop();
179 }
180
181 private void closeConnection() {
182 if (connector instanceof SocketConnector) {
183 // Change the worker timeout to 0 second to make the I/O thread quit soon when there's no connection to manage.
184 // Default worker timeout is 60 sec and therefore the client using MinaProducer cannot terminate the JVM
185 // asap but must wait for the timeout to happen, so to speed this up we set the timeout to 0.
186 if (LOG.isTraceEnabled()) {
187 LOG.trace("Setting SocketConnector WorkerTimeout=0 to force MINA stopping its resources faster");
188 }
189 ((SocketConnector) connector).setWorkerTimeout(0);
190 }
191
192 if (session != null) {
193 session.close();
194 }
195 }
196
197 private void openConnection() {
198 SocketAddress address = endpoint.getAddress();
199 connector = endpoint.getConnector();
200 if (LOG.isDebugEnabled()) {
201 LOG.debug("Creating connector to address: " + address + " using connector: " + connector + " timeout: " + timeout + " millis.");
202 }
203 IoHandler ioHandler = new ResponseHandler(endpoint);
204 // connect and wait until the connection is established
205 ConnectFuture future = connector.connect(address, ioHandler, endpoint.getConnectorConfig());
206 future.join();
207 session = future.getSession();
208 }
209
210 /**
211 * Handles response from session writes
212 */
213 private final class ResponseHandler extends IoHandlerAdapter {
214 private MinaEndpoint endpoint;
215 private Object message;
216 private Throwable cause;
217 private boolean messageReceived;
218
219 private ResponseHandler(MinaEndpoint endpoint) {
220 this.endpoint = endpoint;
221 }
222
223 public void reset() {
224 this.message = null;
225 this.cause = null;
226 this.messageReceived = false;
227 }
228
229 @Override
230 public void messageReceived(IoSession ioSession, Object message) throws Exception {
231 if (LOG.isDebugEnabled()) {
232 LOG.debug("Message received: " + message);
233 }
234 this.message = message;
235 messageReceived = true;
236 cause = null;
237 countDown();
238 }
239
240 protected void countDown() {
241 CountDownLatch downLatch = latch;
242 if (downLatch != null) {
243 downLatch.countDown();
244 }
245 }
246
247 @Override
248 public void sessionClosed(IoSession session) throws Exception {
249 if (sync && !messageReceived) {
250 // sync=true (InOut mode) so we expected a message as reply but did not get one before the session is closed
251 if (LOG.isDebugEnabled()) {
252 LOG.debug("Session closed but no message received from address: " + this.endpoint.getAddress());
253 }
254 // session was closed but no message received. This could be because the remote server had an internal error
255 // and could not return a response. We should count down to stop waiting for a response
256 countDown();
257 }
258 }
259
260 @Override
261 public void exceptionCaught(IoSession ioSession, Throwable cause) {
262 LOG.error("Exception on receiving message from address: " + this.endpoint.getAddress()
263 + " using connector: " + this.endpoint.getConnector(), cause);
264 this.message = null;
265 this.messageReceived = false;
266 this.cause = cause;
267 if (ioSession != null) {
268 ioSession.close();
269 }
270 }
271
272 public Throwable getCause() {
273 return this.cause;
274 }
275
276 public Object getMessage() {
277 return this.message;
278 }
279
280 public boolean isMessageReceived() {
281 return messageReceived;
282 }
283 }
284
285 }