/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.mina2; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.Charset; import java.util.List; import java.util.concurrent.ExecutorService; import org.apache.camel.CamelException; import org.apache.camel.Exchange; import org.apache.camel.ExchangePattern; import org.apache.camel.Processor; import org.apache.camel.impl.DefaultConsumer; import org.apache.camel.util.ExchangeHelper; import org.apache.camel.util.IOHelper; import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.future.CloseFuture; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.service.IoService; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFactory; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.filter.codec.textline.LineDelimiter; import org.apache.mina.filter.executor.ExecutorFilter; import org.apache.mina.filter.executor.OrderedThreadPoolExecutor; import org.apache.mina.filter.executor.UnorderedThreadPoolExecutor; import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.transport.socket.nio.NioDatagramAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.apache.mina.transport.vmpipe.VmPipeAcceptor; import org.apache.mina.transport.vmpipe.VmPipeAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link org.apache.camel.Consumer Consumer} implementation for Apache MINA. * * @version */ public class Mina2Consumer extends DefaultConsumer { private static final Logger LOG = LoggerFactory.getLogger(Mina2Consumer.class); private IoSession session; private IoConnector connector; private SocketAddress address; private IoAcceptor acceptor; private final Mina2Configuration configuration; private ExecutorService workerPool; public Mina2Consumer(final Mina2Endpoint endpoint, Processor processor) throws Exception { super(endpoint, processor); this.configuration = endpoint.getConfiguration(); // // All mina2 endpoints are InOut. The endpoints are asynchronous. // Endpoints can send "n" messages and receive "m" messages. // this.getEndpoint().setExchangePattern(ExchangePattern.InOut); String protocol = configuration.getProtocol(); if (protocol.equals("tcp")) { if (configuration.isClientMode()) { setupClientSocketProtocol(protocol, configuration); } else { setupSocketProtocol(protocol, configuration); } } else if (configuration.isDatagramProtocol()) { setupDatagramProtocol(protocol, configuration); } else if (protocol.equals("vm")) { setupVmProtocol(protocol, configuration); } } @Override protected void doStart() throws Exception { super.doStart(); if (configuration.isClientMode() && configuration.getProtocol().equals("tcp")) { connector.setHandler(new ReceiveHandler()); ConnectFuture future = connector.connect(address); future.awaitUninterruptibly(); session = future.getSession(); LOG.info("Connected to server address: {} using connector: {} timeout: {} millis.", new Object[]{address, connector, configuration.getTimeout()}); } else { acceptor.setHandler(new ReceiveHandler()); acceptor.bind(address); LOG.info("Bound to server address: {} using acceptor: {}", address, acceptor); } } @Override protected void doStop() throws Exception { if (configuration.isClientMode() && configuration.getProtocol().equals("tcp")) { LOG.info("Disconnect from server address: {} using connector: {}", address, connector); if (session != null) { CloseFuture closeFuture = session.closeNow(); closeFuture.awaitUninterruptibly(); } connector.dispose(true); } else { LOG.info("Unbinding from server address: {} using acceptor: {}", address, acceptor); if (address instanceof InetSocketAddress) { // need to check if the address is IPV4 all network address if ("0.0.0.0".equals(((InetSocketAddress)address).getAddress().getHostAddress())) { LOG.info("Unbind the server address {}", acceptor.getLocalAddresses()); acceptor.unbind(acceptor.getLocalAddresses()); } else { acceptor.unbind(address); } } else { acceptor.unbind(address); } } super.doStop(); } @Override protected void doShutdown() throws Exception { if (workerPool != null) { workerPool.shutdown(); } if (acceptor != null) { acceptor.dispose(true); } super.doShutdown(); } // Implementation methods //------------------------------------------------------------------------- protected void setupVmProtocol(String uri, Mina2Configuration configuration) { boolean minaLogger = configuration.isMinaLogger(); List<IoFilter> filters = configuration.getFilters(); address = new VmPipeAddress(configuration.getPort()); acceptor = new VmPipeAcceptor(); // acceptor connectorConfig configureCodecFactory("Mina2Consumer", acceptor, configuration); if (minaLogger) { acceptor.getFilterChain().addLast("logger", new LoggingFilter()); } appendIoFiltersToChain(filters, acceptor.getFilterChain()); if (configuration.getSslContextParameters() != null) { LOG.warn("Using vm protocol" + ", but an SSLContextParameters instance was provided. SSLContextParameters is only supported on the TCP protocol."); } } protected void setupSocketProtocol(String uri, Mina2Configuration configuration) throws Exception { LOG.debug("createSocketEndpoint"); boolean minaLogger = configuration.isMinaLogger(); List<IoFilter> filters = configuration.getFilters(); address = new InetSocketAddress(configuration.getHost(), configuration.getPort()); final int processorCount = Runtime.getRuntime().availableProcessors() + 1; acceptor = new NioSocketAcceptor(processorCount); // acceptor connectorConfig configureCodecFactory("Mina2Consumer", acceptor, configuration); ((NioSocketAcceptor) acceptor).setReuseAddress(true); acceptor.setCloseOnDeactivation(true); if (configuration.isOrderedThreadPoolExecutor()) { workerPool = new OrderedThreadPoolExecutor(configuration.getMaximumPoolSize()); } else { workerPool = new UnorderedThreadPoolExecutor(configuration.getMaximumPoolSize()); } acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(workerPool)); if (minaLogger) { acceptor.getFilterChain().addLast("logger", new LoggingFilter()); } appendIoFiltersToChain(filters, acceptor.getFilterChain()); if (configuration.getSslContextParameters() != null) { SslFilter filter = new SslFilter(configuration.getSslContextParameters().createSSLContext(getEndpoint().getCamelContext()), configuration.isAutoStartTls()); filter.setUseClientMode(false); acceptor.getFilterChain().addFirst("sslFilter", filter); } } protected void setupClientSocketProtocol(String uri, Mina2Configuration configuration) throws Exception { boolean minaLogger = configuration.isMinaLogger(); long timeout = configuration.getTimeout(); List<IoFilter> filters = configuration.getFilters(); address = new InetSocketAddress(configuration.getHost(), configuration.getPort()); final int processorCount = Runtime.getRuntime().availableProcessors() + 1; connector = new NioSocketConnector(processorCount); if (configuration.isOrderedThreadPoolExecutor()) { workerPool = new OrderedThreadPoolExecutor(configuration.getMaximumPoolSize()); } else { workerPool = new UnorderedThreadPoolExecutor(configuration.getMaximumPoolSize()); } connector.getFilterChain().addLast("threadPool", new ExecutorFilter(workerPool)); if (minaLogger) { connector.getFilterChain().addLast("logger", new LoggingFilter()); } appendIoFiltersToChain(filters, connector.getFilterChain()); if (configuration.getSslContextParameters() != null) { SslFilter filter = new SslFilter(configuration.getSslContextParameters().createSSLContext(getEndpoint().getCamelContext()), configuration.isAutoStartTls()); filter.setUseClientMode(true); connector.getFilterChain().addFirst("sslFilter", filter); } configureCodecFactory("Mina2Consumer", connector, configuration); connector.setConnectTimeoutMillis(timeout); } protected void configureCodecFactory(String type, IoService service, Mina2Configuration configuration) { if (configuration.getCodec() != null) { addCodecFactory(service, configuration.getCodec()); } else if (configuration.isAllowDefaultCodec()) { configureDefaultCodecFactory(type, service, configuration); } } protected void configureDefaultCodecFactory(String type, IoService service, Mina2Configuration configuration) { if (configuration.isTextline()) { Charset charset = getEncodingParameter(type, configuration); LineDelimiter delimiter = getLineDelimiterParameter(configuration.getTextlineDelimiter()); Mina2TextLineCodecFactory codecFactory = new Mina2TextLineCodecFactory(charset, delimiter); if (configuration.getEncoderMaxLineLength() > 0) { codecFactory.setEncoderMaxLineLength(configuration.getEncoderMaxLineLength()); } if (configuration.getDecoderMaxLineLength() > 0) { codecFactory.setDecoderMaxLineLength(configuration.getDecoderMaxLineLength()); } addCodecFactory(service, codecFactory); if (LOG.isDebugEnabled()) { LOG.debug("{}: Using TextLineCodecFactory: {} using encoding: {} line delimiter: {}({})", new Object[]{type, codecFactory, charset, configuration.getTextlineDelimiter(), delimiter}); LOG.debug("Encoder maximum line length: {}. Decoder maximum line length: {}", codecFactory.getEncoderMaxLineLength(), codecFactory.getDecoderMaxLineLength()); } } else { ObjectSerializationCodecFactory codecFactory = new ObjectSerializationCodecFactory(); addCodecFactory(service, codecFactory); LOG.debug("{}: Using ObjectSerializationCodecFactory: {}", type, codecFactory); } } protected void setupDatagramProtocol(String uri, Mina2Configuration configuration) { boolean minaLogger = configuration.isMinaLogger(); List<IoFilter> filters = configuration.getFilters(); address = new InetSocketAddress(configuration.getHost(), configuration.getPort()); acceptor = new NioDatagramAcceptor(); // acceptor connectorConfig configureDataGramCodecFactory("Mina2Consumer", acceptor, configuration); acceptor.setCloseOnDeactivation(true); // reuse address is default true for datagram if (configuration.isOrderedThreadPoolExecutor()) { workerPool = new OrderedThreadPoolExecutor(configuration.getMaximumPoolSize()); } else { workerPool = new UnorderedThreadPoolExecutor(configuration.getMaximumPoolSize()); } acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(workerPool)); if (minaLogger) { acceptor.getFilterChain().addLast("logger", new LoggingFilter()); } appendIoFiltersToChain(filters, acceptor.getFilterChain()); if (configuration.getSslContextParameters() != null) { LOG.warn("Using datagram protocol, " + configuration.getProtocol() + ", but an SSLContextParameters instance was provided. SSLContextParameters is only supported on the TCP protocol."); } } /** * For datagrams the entire message is available as a single IoBuffer so lets just pass those around by default * and try converting whatever they payload is into IoBuffer unless some custom converter is specified */ protected void configureDataGramCodecFactory(final String type, final IoService service, final Mina2Configuration configuration) { ProtocolCodecFactory codecFactory = configuration.getCodec(); if (codecFactory == null) { codecFactory = new Mina2UdpProtocolCodecFactory(this.getEndpoint().getCamelContext()); if (LOG.isDebugEnabled()) { LOG.debug("{}: Using CodecFactory: {}", new Object[]{type, codecFactory}); } } addCodecFactory(service, codecFactory); } private void addCodecFactory(IoService service, ProtocolCodecFactory codecFactory) { service.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecFactory)); } private static LineDelimiter getLineDelimiterParameter(Mina2TextLineDelimiter delimiter) { if (delimiter == null) { return LineDelimiter.DEFAULT; } switch (delimiter) { case DEFAULT: return LineDelimiter.DEFAULT; case AUTO: return LineDelimiter.AUTO; case UNIX: return LineDelimiter.UNIX; case WINDOWS: return LineDelimiter.WINDOWS; case MAC: return LineDelimiter.MAC; default: throw new IllegalArgumentException("Unknown textline delimiter: " + delimiter); } } private Charset getEncodingParameter(String type, Mina2Configuration configuration) { String encoding = configuration.getEncoding(); if (encoding == null) { encoding = Charset.defaultCharset().name(); // set in on configuration so its updated configuration.setEncoding(encoding); LOG.debug("{}: No encoding parameter using default charset: {}", type, encoding); } if (!Charset.isSupported(encoding)) { throw new IllegalArgumentException("The encoding: " + encoding + " is not supported"); } return Charset.forName(encoding); } private void appendIoFiltersToChain(List<IoFilter> filters, DefaultIoFilterChainBuilder filterChain) { if (filters != null && filters.size() > 0) { for (IoFilter ioFilter : filters) { filterChain.addLast(ioFilter.getClass().getCanonicalName(), ioFilter); } } } @Override public Mina2Endpoint getEndpoint() { return (Mina2Endpoint) super.getEndpoint(); } public IoAcceptor getAcceptor() { return acceptor; } public void setAcceptor(IoAcceptor acceptor) { this.acceptor = acceptor; } /** * Handles consuming messages and replying if the exchange is out capable. */ private final class ReceiveHandler extends IoHandlerAdapter { @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { if (cause instanceof IOException) { LOG.debug("IOExceptions are automatically handled by MINA"); return; } // close invalid session if (session != null) { LOG.warn("Closing session as an exception was thrown from MINA"); session.closeNow(); } // must wrap and rethrow since cause can be of Throwable and we must only throw Exception throw new CamelException(cause); } @Override public void messageReceived(IoSession session, Object object) throws Exception { // log what we received if (LOG.isDebugEnabled()) { Object in = object; if (in instanceof byte[]) { // byte arrays is not readable so convert to string in = getEndpoint().getCamelContext().getTypeConverter().convertTo(String.class, in); } LOG.debug("Received body: {}", in); } Exchange exchange = getEndpoint().createExchange(session, object); //Set the exchange charset property for converting if (getEndpoint().getConfiguration().getCharsetName() != null) { exchange.setProperty(Exchange.CHARSET_NAME, IOHelper.normalizeCharset(getEndpoint().getConfiguration().getCharsetName())); } try { getProcessor().process(exchange); } catch (Throwable e) { getExceptionHandler().handleException(e); } // // If there's a response to send, send it. // boolean disconnect = getEndpoint().getConfiguration().isDisconnect(); Object response = null; if (exchange.hasOut()) { response = Mina2PayloadHelper.getOut(getEndpoint(), exchange); } else { response = Mina2PayloadHelper.getIn(getEndpoint(), exchange); } boolean failed = exchange.isFailed(); if (failed && !getEndpoint().getConfiguration().isTransferExchange()) { if (exchange.getException() != null) { response = exchange.getException(); } else { // failed and no exception, must be a fault response = exchange.getOut().getBody(); } } if (response != null) { LOG.debug("Writing body: {}", response); Mina2Helper.writeBody(session, response, exchange); } else { LOG.debug("Writing no response"); disconnect = Boolean.TRUE; } // should session be closed after complete? Boolean close; if (ExchangeHelper.isOutCapable(exchange)) { close = exchange.getOut().getHeader(Mina2Constants.MINA_CLOSE_SESSION_WHEN_COMPLETE, Boolean.class); } else { close = exchange.getIn().getHeader(Mina2Constants.MINA_CLOSE_SESSION_WHEN_COMPLETE, Boolean.class); } // should we disconnect, the header can override the configuration if (close != null) { disconnect = close; } if (disconnect) { LOG.debug("Closing session when complete at address: {}", address); session.closeNow(); } } } }