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 }