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.InetSocketAddress;
020    import java.net.SocketAddress;
021    import java.net.URI;
022    import java.nio.charset.Charset;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.concurrent.ExecutorService;
026    import java.util.concurrent.Executors;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.Endpoint;
030    import org.apache.camel.ExchangePattern;
031    import org.apache.camel.impl.DefaultComponent;
032    import org.apache.camel.util.ObjectHelper;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    import org.apache.mina.common.DefaultIoFilterChainBuilder;
036    import org.apache.mina.common.IoAcceptor;
037    import org.apache.mina.common.IoConnector;
038    import org.apache.mina.common.IoFilter;
039    import org.apache.mina.common.IoServiceConfig;
040    import org.apache.mina.common.ThreadModel;
041    import org.apache.mina.filter.LoggingFilter;
042    import org.apache.mina.filter.codec.ProtocolCodecFactory;
043    import org.apache.mina.filter.codec.ProtocolCodecFilter;
044    import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
045    import org.apache.mina.filter.codec.textline.LineDelimiter;
046    import org.apache.mina.filter.executor.ExecutorFilter;
047    import org.apache.mina.transport.socket.nio.DatagramAcceptor;
048    import org.apache.mina.transport.socket.nio.DatagramAcceptorConfig;
049    import org.apache.mina.transport.socket.nio.DatagramConnector;
050    import org.apache.mina.transport.socket.nio.DatagramConnectorConfig;
051    import org.apache.mina.transport.socket.nio.SocketAcceptor;
052    import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
053    import org.apache.mina.transport.socket.nio.SocketConnector;
054    import org.apache.mina.transport.socket.nio.SocketConnectorConfig;
055    import org.apache.mina.transport.vmpipe.VmPipeAcceptor;
056    import org.apache.mina.transport.vmpipe.VmPipeAddress;
057    import org.apache.mina.transport.vmpipe.VmPipeConnector;
058    
059    /**
060     * Component for Apache MINA.
061     *
062     * @version $Revision: 18438 $
063     */
064    public class MinaComponent extends DefaultComponent {
065        private static final transient Log LOG = LogFactory.getLog(MinaComponent.class);
066        private MinaConfiguration configuration;
067    
068        public MinaComponent() {
069        }
070    
071        public MinaComponent(CamelContext context) {
072            super(context);
073        }
074    
075        @Override
076        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
077            // Using the configuration which set by the component as a default one
078            // Since the configuration's properties will be set by the URI
079            // we need to copy or create a new MinaConfiguration here
080            MinaConfiguration config;
081            if (configuration != null) {        
082                config = configuration.copy();
083            } else {
084                config = new MinaConfiguration();
085            }
086    
087            URI u = new URI(remaining);
088            config.setHost(u.getHost());
089            config.setPort(u.getPort());
090            config.setProtocol(u.getScheme());
091            config.setFilters(resolveAndRemoveReferenceListParameter(parameters, "filters", IoFilter.class));
092            setProperties(config, parameters);
093    
094            return createEndpoint(uri, config);
095        }
096    
097        public Endpoint createEndpoint(MinaConfiguration config) throws Exception {
098            return createEndpoint(null, config);
099        }
100    
101        private Endpoint createEndpoint(String uri, MinaConfiguration config) throws Exception {
102            ObjectHelper.notNull(getCamelContext(), "camelContext");
103    
104            String protocol = config.getProtocol();
105            // if mistyped uri then protocol can be null
106            if (protocol != null) {
107                if (protocol.equals("tcp")) {
108                    return createSocketEndpoint(uri, config);
109                } else if (config.isDatagramProtocol()) {
110                    return createDatagramEndpoint(uri, config);
111                } else if (protocol.equals("vm")) {
112                    return createVmEndpoint(uri, config);
113                }
114            }
115            // protocol not resolved so error
116            throw new IllegalArgumentException("Unrecognised MINA protocol: " + protocol + " for uri: " + uri);
117    
118        }
119    
120        // Implementation methods
121        //-------------------------------------------------------------------------
122    
123        protected MinaEndpoint createVmEndpoint(String uri, MinaConfiguration configuration) {
124            boolean minaLogger = configuration.isMinaLogger();
125            boolean sync = configuration.isSync();
126            List<IoFilter> filters = configuration.getFilters();
127    
128            IoAcceptor acceptor = new VmPipeAcceptor();
129            SocketAddress address = new VmPipeAddress(configuration.getPort());
130            IoConnector connector = new VmPipeConnector();
131    
132            // connector config
133            configureCodecFactory("MinaProducer", connector.getDefaultConfig(), configuration);
134            if (minaLogger) {
135                connector.getFilterChain().addLast("logger", new LoggingFilter());
136            }
137            appendIoFiltersToChain(filters, connector.getFilterChain());
138    
139            // acceptor connectorConfig
140            configureCodecFactory("MinaConsumer", acceptor.getDefaultConfig(), configuration);
141            if (minaLogger) {
142                acceptor.getFilterChain().addLast("logger", new LoggingFilter());
143            }
144            appendIoFiltersToChain(filters, acceptor.getFilterChain());
145    
146            MinaEndpoint endpoint = new MinaEndpoint(uri, this);
147            endpoint.setAddress(address);
148            endpoint.setAcceptor(acceptor);
149            endpoint.setConnector(connector);
150            endpoint.setConfiguration(configuration);
151    
152            // set sync or async mode after endpoint is created
153            if (sync) {
154                endpoint.setExchangePattern(ExchangePattern.InOut);
155            } else {
156                endpoint.setExchangePattern(ExchangePattern.InOnly);
157            }
158    
159            return endpoint;
160        }
161    
162        protected MinaEndpoint createSocketEndpoint(String uri, MinaConfiguration configuration) {
163            boolean minaLogger = configuration.isMinaLogger();
164            long timeout = configuration.getTimeout();
165            boolean sync = configuration.isSync();
166            List<IoFilter> filters = configuration.getFilters();
167            final int processorCount = Runtime.getRuntime().availableProcessors() + 1;
168    
169            IoAcceptor acceptor = new SocketAcceptor(processorCount,
170                    getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaSocketAcceptor"));
171            IoConnector connector = new SocketConnector(processorCount,
172                    getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaSocketConnector"));
173            SocketAddress address = new InetSocketAddress(configuration.getHost(), configuration.getPort());
174    
175            // connector config
176            SocketConnectorConfig connectorConfig = new SocketConnectorConfig();
177            // must use manual thread model according to Mina documentation
178            connectorConfig.setThreadModel(ThreadModel.MANUAL);
179            configureCodecFactory("MinaProducer", connectorConfig, configuration);
180            connectorConfig.getFilterChain().addLast("threadPool",
181                    new ExecutorFilter(getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaThreadPool")));
182            if (minaLogger) {
183                connectorConfig.getFilterChain().addLast("logger", new LoggingFilter());
184            }
185            appendIoFiltersToChain(filters, connectorConfig.getFilterChain());
186    
187            // set connect timeout to mina in seconds
188            connectorConfig.setConnectTimeout((int) (timeout / 1000));
189    
190            // acceptor connectorConfig
191            SocketAcceptorConfig acceptorConfig = new SocketAcceptorConfig();
192            // must use manual thread model according to Mina documentation
193            acceptorConfig.setThreadModel(ThreadModel.MANUAL);
194            configureCodecFactory("MinaConsumer", acceptorConfig, configuration);
195            acceptorConfig.setReuseAddress(true);
196            acceptorConfig.setDisconnectOnUnbind(true);
197            acceptorConfig.getFilterChain().addLast("threadPool",
198                    new ExecutorFilter(getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaThreadPool")));
199            if (minaLogger) {
200                acceptorConfig.getFilterChain().addLast("logger", new LoggingFilter());
201            }
202            appendIoFiltersToChain(filters, acceptorConfig.getFilterChain());
203    
204            MinaEndpoint endpoint = new MinaEndpoint(uri, this);
205            endpoint.setAddress(address);
206            endpoint.setAcceptor(acceptor);
207            endpoint.setAcceptorConfig(acceptorConfig);
208            endpoint.setConnector(connector);
209            endpoint.setConnectorConfig(connectorConfig);
210            endpoint.setConfiguration(configuration);
211    
212            // set sync or async mode after endpoint is created
213            if (sync) {
214                endpoint.setExchangePattern(ExchangePattern.InOut);
215            } else {
216                endpoint.setExchangePattern(ExchangePattern.InOnly);
217            }
218    
219            return endpoint;
220        }
221    
222        protected void configureCodecFactory(String type, IoServiceConfig config, MinaConfiguration configuration) {
223            if (configuration.getCodec() != null) {
224                addCodecFactory(config, configuration.getCodec());
225            } else if (configuration.isAllowDefaultCodec()) {
226                configureDefaultCodecFactory(type, config, configuration);
227            }
228        }
229    
230        protected void configureDefaultCodecFactory(String type, IoServiceConfig config, MinaConfiguration configuration) {
231            if (configuration.isTextline()) {
232                Charset charset = getEncodingParameter(type, configuration);
233                LineDelimiter delimiter = getLineDelimiterParameter(configuration.getTextlineDelimiter());
234                TextLineCodecFactory codecFactory = new TextLineCodecFactory(charset, delimiter);
235                if (configuration.getEncoderMaxLineLength() > 0) {
236                    codecFactory.setEncoderMaxLineLength(configuration.getEncoderMaxLineLength());
237                }
238                if (configuration.getDecoderMaxLineLength() > 0) {
239                    codecFactory.setDecoderMaxLineLength(configuration.getDecoderMaxLineLength());
240                }
241                addCodecFactory(config, codecFactory);
242                if (LOG.isDebugEnabled()) {
243                    LOG.debug(type + ": Using TextLineCodecFactory: " + codecFactory + " using encoding: "
244                            + charset + " line delimiter: " + configuration.getTextlineDelimiter()
245                            + "(" + delimiter + ")");
246                    LOG.debug("Encoder maximum line length: " + codecFactory.getEncoderMaxLineLength()
247                            + ". Decoder maximum line length: " + codecFactory.getDecoderMaxLineLength());
248                }
249            } else {
250                ObjectSerializationCodecFactory codecFactory = new ObjectSerializationCodecFactory();
251                addCodecFactory(config, codecFactory);
252                if (LOG.isDebugEnabled()) {
253                    LOG.debug(type + ": Using ObjectSerializationCodecFactory: " + codecFactory);
254                }
255            }
256            
257        }
258        
259        protected MinaEndpoint createDatagramEndpoint(String uri, MinaConfiguration configuration) {
260            boolean minaLogger = configuration.isMinaLogger();
261            long timeout = configuration.getTimeout();
262            boolean transferExchange = configuration.isTransferExchange();
263            boolean sync = configuration.isSync();
264            List<IoFilter> filters = configuration.getFilters();
265    
266            IoAcceptor acceptor = new DatagramAcceptor(getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaDatagramAcceptor"));
267            IoConnector connector = new DatagramConnector(getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaDatagramConnector"));
268            SocketAddress address = new InetSocketAddress(configuration.getHost(), configuration.getPort());
269    
270            if (transferExchange) {
271                throw new IllegalArgumentException("transferExchange=true is not supported for datagram protocol");
272            }
273    
274            DatagramConnectorConfig connectorConfig = new DatagramConnectorConfig();
275            // must use manual thread model according to Mina documentation
276            connectorConfig.setThreadModel(ThreadModel.MANUAL);
277            configureDataGramCodecFactory("MinaProducer", connectorConfig, configuration);
278            connectorConfig.getFilterChain().addLast("threadPool",
279                    new ExecutorFilter(getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaThreadPool")));
280            if (minaLogger) {
281                connectorConfig.getFilterChain().addLast("logger", new LoggingFilter());
282            }
283            appendIoFiltersToChain(filters, connectorConfig.getFilterChain());
284            // set connect timeout to mina in seconds
285            connectorConfig.setConnectTimeout((int) (timeout / 1000));
286    
287            DatagramAcceptorConfig acceptorConfig = new DatagramAcceptorConfig();
288            // must use manual thread model according to Mina documentation
289            acceptorConfig.setThreadModel(ThreadModel.MANUAL);
290            configureDataGramCodecFactory("MinaConsumer", acceptorConfig, configuration);
291            acceptorConfig.setDisconnectOnUnbind(true);
292            // reuse address is default true for datagram
293            acceptorConfig.getFilterChain().addLast("threadPool",
294                    new ExecutorFilter(getCamelContext().getExecutorServiceStrategy().newCachedThreadPool(this, "MinaThreadPool")));
295            if (minaLogger) {
296                acceptorConfig.getFilterChain().addLast("logger", new LoggingFilter());
297            }
298            appendIoFiltersToChain(filters, acceptorConfig.getFilterChain());
299    
300            MinaEndpoint endpoint = new MinaEndpoint(uri, this);
301            endpoint.setAddress(address);
302            endpoint.setAcceptor(acceptor);
303            endpoint.setAcceptorConfig(acceptorConfig);
304            endpoint.setConnector(connector);
305            endpoint.setConnectorConfig(connectorConfig);
306            endpoint.setConfiguration(configuration);
307            // set sync or async mode after endpoint is created
308            if (sync) {
309                endpoint.setExchangePattern(ExchangePattern.InOut);
310            } else {
311                endpoint.setExchangePattern(ExchangePattern.InOnly);
312            }
313    
314            return endpoint;
315        }
316    
317        /**
318         * For datagrams the entire message is available as a single ByteBuffer so lets just pass those around by default
319         * and try converting whatever they payload is into ByteBuffers unless some custom converter is specified
320         */
321        protected void configureDataGramCodecFactory(final String type, final IoServiceConfig config, final MinaConfiguration configuration) {
322            ProtocolCodecFactory codecFactory = configuration.getCodec();
323            if (codecFactory == null) {
324                final Charset charset = getEncodingParameter(type, configuration);
325                
326                codecFactory = new MinaUdpProtocolCodecFactory(getCamelContext(), charset);
327    
328                if (LOG.isDebugEnabled()) {
329                    LOG.debug(type + ": Using CodecFactory: " + codecFactory + " using encoding: " + charset);
330                }
331            }
332    
333            addCodecFactory(config, codecFactory);
334        }
335    
336        private void addCodecFactory(IoServiceConfig config, ProtocolCodecFactory codecFactory) {
337            config.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecFactory));
338        }
339    
340        private static LineDelimiter getLineDelimiterParameter(TextLineDelimiter delimiter) {
341            if (delimiter == null) {
342                return LineDelimiter.DEFAULT;
343            }
344    
345            switch (delimiter) {
346            case DEFAULT:
347                return LineDelimiter.DEFAULT;
348            case AUTO:
349                return LineDelimiter.AUTO;
350            case UNIX:
351                return LineDelimiter.UNIX;
352            case WINDOWS:
353                return LineDelimiter.WINDOWS;
354            case MAC:
355                return LineDelimiter.MAC;
356            default:
357                throw new IllegalArgumentException("Unknown textline delimiter: " + delimiter);
358            }
359        }
360    
361        private static Charset getEncodingParameter(String type, MinaConfiguration configuration) {
362            String encoding = configuration.getEncoding();
363            if (encoding == null) {
364                encoding = Charset.defaultCharset().name();
365                // set in on configuration so its updated
366                configuration.setEncoding(encoding);
367                if (LOG.isDebugEnabled()) {
368                    LOG.debug(type + ": No encoding parameter using default charset: " + encoding);
369                }
370            }
371            if (!Charset.isSupported(encoding)) {
372                throw new IllegalArgumentException("The encoding: " + encoding + " is not supported");
373            }
374    
375            return Charset.forName(encoding);
376        }
377    
378        private void appendIoFiltersToChain(List<IoFilter> filters, DefaultIoFilterChainBuilder filterChain) {
379            if (filters != null && filters.size() > 0) {
380                for (IoFilter ioFilter : filters) {
381                    filterChain.addLast(ioFilter.getClass().getCanonicalName(), ioFilter);
382                }
383            }
384        }
385    
386        // Properties
387        //-------------------------------------------------------------------------
388    
389        public MinaConfiguration getConfiguration() {        
390            return configuration;
391        }
392    
393        public void setConfiguration(MinaConfiguration configuration) {
394            this.configuration = configuration;
395        }
396    }