Merge branch 'master' of git.springsource.org:spring-integration/spring-integration

This commit is contained in:
Chris Beams
2010-10-28 15:12:32 -04:00
135 changed files with 2387 additions and 2076 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package org.springframework.integration;
/**
* Exception that indicates an error during message delivery.
* Exception that indicates an error occurred during message delivery.
*
* @author Mark Fisher
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package org.springframework.integration;
/**
* Exception that indicates an error during message handling.
* Exception that indicates an error occurred during message handling.
*
* @author Mark Fisher
*/

View File

@@ -44,7 +44,7 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
private static final Log logger = LogFactory.getLog(MessageHeaders.class);
public static final String PREFIX = "$";
public static final String PREFIX = "";
/**
* The key for the Message ID. This is an automatically generated UUID and

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -16,8 +16,9 @@
package org.springframework.integration;
/**
* Exception that indicates a timeout elapsed prior to successful message delivery.
*
* @author Mark Fisher
*/
@SuppressWarnings("serial")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,8 @@
package org.springframework.integration;
/**
* The base exception for any failures within the messaging system.
* The base exception for any failures related to messaging.
*
* @author Mark Fisher
* @author Gary Russell
@@ -26,7 +25,7 @@ package org.springframework.integration;
@SuppressWarnings("serial")
public class MessagingException extends RuntimeException {
private Message<?> failedMessage;
private volatile Message<?> failedMessage;
public MessagingException(Message<?> message) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@ package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
@@ -33,13 +33,13 @@ public abstract class AbstractPollingInboundChannelAdapterParser extends Abstrac
@Override
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
String source = this.parseSource(element, parserContext);
if (!StringUtils.hasText(source)) {
BeanMetadataElement source = this.parseSource(element, parserContext);
if (source == null) {
parserContext.getReaderContext().error("failed to parse source", element);
}
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SourcePollingChannelAdapterFactoryBean");
adapterBuilder.addPropertyReference("source", source);
adapterBuilder.addPropertyValue("source", source);
adapterBuilder.addPropertyReference("outputChannel", channelName);
Element pollerElement = DomUtils.getChildElementByTagName(element, "poller");
if (pollerElement != null) {
@@ -53,6 +53,6 @@ public abstract class AbstractPollingInboundChannelAdapterParser extends Abstrac
* Subclasses must implement this method to parse the PollableSource instance
* which the created Channel Adapter will poll.
*/
protected abstract String parseSource(Element element, ParserContext parserContext);
protected abstract BeanMetadataElement parseSource(Element element, ParserContext parserContext);
}

View File

@@ -191,8 +191,7 @@ public abstract class IntegrationNamespaceUtils {
}
public static BeanComponentDefinition parseInnerHandlerDefinition(Element element, ParserContext parserContext) {
// parses out inner bean definition for concrete implementation if
// defined
// parses out the inner bean definition for concrete implementation if defined
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "bean");
BeanComponentDefinition innerComponentDefinition = null;
if (childElements != null && childElements.size() == 1) {
@@ -202,15 +201,13 @@ public abstract class IntegrationNamespaceUtils {
bdHolder = delegate.decorateBeanDefinitionIfRequired(beanElement, bdHolder);
BeanDefinition inDef = bdHolder.getBeanDefinition();
innerComponentDefinition = new BeanComponentDefinition(inDef, bdHolder.getBeanName());
parserContext.registerBeanComponent(innerComponentDefinition);
}
String ref = element.getAttribute(REF_ATTRIBUTE);
Assert.isTrue(!(StringUtils.hasText(ref) && innerComponentDefinition != null),
"Ambiguous definition. Inner bean "
+ (innerComponentDefinition == null ? innerComponentDefinition : innerComponentDefinition
.getBeanDefinition().getBeanClassName()) + " declaration and \"ref\" " + ref
+ " are not allowed together.");
"Ambiguous definition. Inner bean " + (innerComponentDefinition == null ? innerComponentDefinition
: innerComponentDefinition.getBeanDefinition().getBeanClassName())
+ " declaration and \"ref\" " + ref + " are not allowed together.");
return innerComponentDefinition;
}
}

View File

@@ -20,6 +20,8 @@ import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
@@ -38,39 +40,57 @@ import org.springframework.util.xml.DomUtils;
public class MethodInvokingInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanMetadataElement result = null;
BeanComponentDefinition innnerBeanDef = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
String sourceRef = element.getAttribute("ref");
String methodName = element.getAttribute("method");
String expressionString = element.getAttribute("expression");
if (innnerBeanDef != null) {
if (StringUtils.hasText(sourceRef)) {
parserContext.getReaderContext().error(
"inner bean and a 'ref' attribute are mutually exclusive options", element);
}
sourceRef = innnerBeanDef.getBeanName();
if (StringUtils.hasText(methodName)) {
result = this.parseMethodInvokingSource(innnerBeanDef, methodName, element, parserContext);
}
else {
result = innnerBeanDef;
}
}
else if (StringUtils.hasText(expressionString)) {
if (StringUtils.hasText(sourceRef)) {
parserContext.getReaderContext().error(
"the 'expression' and 'ref' attributes are mutually exclusive options", element);
}
sourceRef = this.parseExpression(expressionString, element, parserContext);
String expressionBeanName = this.parseExpression(expressionString, element, parserContext);
result = new RuntimeBeanReference(expressionBeanName);
}
if (!StringUtils.hasText(sourceRef)) {
else if (StringUtils.hasText(sourceRef)) {
BeanMetadataElement sourceValue = new RuntimeBeanReference(sourceRef);
if (StringUtils.hasText(methodName)) {
result = this.parseMethodInvokingSource(sourceValue, methodName, element, parserContext);
}
else {
result = sourceValue;
}
}
else {
parserContext.getReaderContext().error("One of the following is required: " +
"'ref' attribute, 'expression' attribute, or an inner-bean definition.", element);
}
String methodName = element.getAttribute("method");
if (StringUtils.hasText(methodName)) {
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".endpoint.MethodInvokingMessageSource");
sourceBuilder.addPropertyReference("object", sourceRef);
sourceBuilder.addPropertyValue("methodName", methodName);
this.parseHeaderExpressions(sourceBuilder, element, parserContext);
sourceRef = BeanDefinitionReaderUtils.registerWithGeneratedName(
sourceBuilder.getBeanDefinition(), parserContext.getRegistry());
}
return sourceRef;
return result;
}
private BeanMetadataElement parseMethodInvokingSource(BeanMetadataElement targetObject, String methodName, Element element, ParserContext parserContext) {
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".endpoint.MethodInvokingMessageSource");
sourceBuilder.addPropertyValue("object", targetObject);
sourceBuilder.addPropertyValue("methodName", methodName);
this.parseHeaderExpressions(sourceBuilder, element, parserContext);
String sourceRef = BeanDefinitionReaderUtils.registerWithGeneratedName(
sourceBuilder.getBeanDefinition(), parserContext.getRegistry());
return new RuntimeBeanReference(sourceRef);
}
private String parseExpression(String expressionString, Element element, ParserContext parserContext) {

View File

@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.context;
import org.springframework.beans.BeansException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -26,14 +29,25 @@ import org.springframework.context.support.ConversionServiceFactoryBean;
/**
* @author Oleg Zhurakousky
* @sini 2.0
* @author Mark Fisher
* @since 2.0
*/
class ConversionServiceCreator implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (!beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME)){
BeanDefinitionBuilder conversionServiceBuilder = BeanDefinitionBuilder.rootBeanDefinition(ConversionServiceFactoryBean.class);
BeanDefinitionHolder csHolder = new BeanDefinitionHolder(conversionServiceBuilder.getBeanDefinition(), IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(csHolder, (BeanDefinitionRegistry) beanFactory);
private final Log logger = LogFactory.getLog(this.getClass());
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
if (!beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME)) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionBuilder conversionServiceBuilder = BeanDefinitionBuilder.rootBeanDefinition(ConversionServiceFactoryBean.class);
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(
conversionServiceBuilder.getBeanDefinition(), IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, (BeanDefinitionRegistry) beanFactory);
}
else if (logger.isWarnEnabled()) {
logger.warn("BeanFactory is not a BeanDefinitionRegistry implementation. Cannot register a default ConversionService.");
}
}
}
}

View File

@@ -13,39 +13,53 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.context;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.Assert;
/**
* Utility class that keeps track of a set of Converters in order to register
* them with the "integrationConversionService" upon initialization.
*
* @author Oleg Zhurakousky
* @author Mark Fisher
* @since 2.0
*/
class ConverterRegistrar implements InitializingBean, BeanFactoryAware {
private final Set<Converter<?, ?>> converters;
private BeanFactory beanFactory;
public ConverterRegistrar(Set<Converter<?, ?>> converters){
public ConverterRegistrar(Set<Converter<?, ?>> converters) {
this.converters = converters;
}
public void afterPropertiesSet() throws Exception {
GenericConversionService conversionService = beanFactory.getBean(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, GenericConversionService.class);
Assert.notNull(conversionService, "can not locate '" + IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME + "' ");
ConversionServiceFactory.registerConverters(converters, conversionService);
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(beanFactory, "BeanFactory is required");
ConversionService conversionService = IntegrationContextUtils.getConversionService(beanFactory);
if (conversionService instanceof GenericConversionService) {
ConversionServiceFactory.registerConverters(converters, (GenericConversionService) conversionService);
}
else {
Assert.notNull(conversionService, "Failed to locate '" + IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME + "'");
}
}
}

View File

@@ -21,8 +21,8 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.context.metadata.MetadataStore;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.store.MetadataStore;
import org.springframework.scheduling.TaskScheduler;

View File

@@ -13,21 +13,33 @@
package org.springframework.integration.control;
import org.springframework.expression.Expression;
import org.springframework.integration.Message;
import org.springframework.integration.handler.AbstractMessageProcessor;
import org.springframework.util.Assert;
/**
* A MessageProcessor implementation that expects an Expression or expressionString
* as the Message payload. When processing, it simply evaluates that expression.
*
* @author Dave Syer
* @author Mark Fisher
* @since 2.0
*
*/
public class ExpressionPayloadMessageProcessor extends AbstractMessageProcessor<Object> {
/**
* Evaluates the Message payload expression.
* @throws IllegalArgumentException if the payload is not an Exception or String
*/
public Object processMessage(Message<?> message) {
Assert.state(message.getPayload() instanceof String, "Message payload must be a String expression");
String expression = (String) message.getPayload();
return evaluateExpression(expression, message);
Object expression = message.getPayload();
if (expression instanceof Expression) {
return evaluateExpression((Expression) expression, message);
}
if (expression instanceof String) {
return evaluateExpression((String) expression, message);
}
throw new IllegalArgumentException("Message payload must be an Expression instance or an expression String.");
}
}

View File

@@ -25,6 +25,7 @@ import java.util.Map;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
@@ -39,7 +40,6 @@ import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.util.SimpleBeanResolver;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -106,7 +106,7 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
public void setBeanFactory(final BeanFactory beanFactory) {
if (beanFactory != null) {
this.beanResolver = new SimpleBeanResolver(beanFactory);
this.beanResolver = new BeanFactoryResolver(beanFactory);
this.evaluationContext.setBeanResolver(beanResolver);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.context.metadata;
package org.springframework.integration.store;
/**
* Strategy interface for storing metadata from certain adapters

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.context.metadata;
package org.springframework.integration.store;
import java.io.File;
import java.io.FileInputStream;

View File

@@ -11,7 +11,7 @@
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.context.metadata;
package org.springframework.integration.store;
import java.util.HashMap;
import java.util.Map;

View File

@@ -15,6 +15,7 @@ package org.springframework.integration.util;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.EvaluationException;
@@ -50,7 +51,7 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware {
public void setBeanFactory(final BeanFactory beanFactory) {
if (beanFactory != null) {
this.typeConverter.setBeanFactory(beanFactory);
this.evaluationContext.setBeanResolver(new SimpleBeanResolver(beanFactory));
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.util;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
/**
* @author Mark Fisher
* @since 2.0
*/
public class SimpleBeanResolver implements BeanResolver {
private final BeanFactory beanFactory;
public SimpleBeanResolver(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "beanFactory must not be null");
this.beanFactory = beanFactory;
}
public final Object resolve(EvaluationContext context, String beanName) throws AccessException {
return this.beanFactory.getBean(beanName);
}
}

View File

@@ -2622,10 +2622,9 @@ The list of component name patterns you want to track (e.g., tracked-components
<xsd:element name="control-bus">
<xsd:annotation>
<xsd:documentation>
Control bus that accepts messages in the form of Groovy scripts. The scripts should be provided as
String payloads
in incoming messages. Scripts can refer to beans in the context using the standard @beanName
convention.
Control bus that accepts messages in the form of SpEL expressions. The expressions should be provided
either as Expression or String payloads (that can be parsed into valid Expressions). in incoming messages.
The expressions can refer to beans in the context using the standard @beanName convention.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- see INT-1528 -->
<service-activator id="testEndpoint" input-channel="input">
<beans:bean class="org.springframework.integration.config.xml.InnerBeanConfigTests$TestBean"/>
</service-activator>
</beans:beans>

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.config.xml;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class InnerBeanConfigTests {
@Autowired
private EventDrivenConsumer testEndpoint;
@Autowired
private ApplicationContext context;
// INT-1528: the inner bean should not be registered in the context
@Test(expected = NoSuchBeanDefinitionException.class)
public void checkInnerBean() {
Object innerBean = TestUtils.getPropertyValue(testEndpoint, "handler.processor.delegate.targetObject");
assertNotNull(innerBean);
context.getBean(TestBean.class);
}
public static class TestBean {
public String echo(String value) {
return value;
}
}
}

View File

@@ -11,6 +11,6 @@
default-request-channel="requestChannel"
service-interface="org.springframework.integration.gateway.GatewayWithHeaderAnnotations$TestService" />
<service-activator input-channel="requestChannel" expression="payload + headers.$priority"/>
<service-activator input-channel="requestChannel" expression="payload + headers.priority + headers.$custom"/>
</beans:beans>

View File

@@ -43,13 +43,14 @@ public class GatewayWithHeaderAnnotations {
@Test // INT-1205
public void priorityAsArgument() {
TestService gateway = (TestService) applicationContext.getBean("gateway");
String result = gateway.test("foo", 99);
assertEquals("foo99", result);
String result = gateway.test("foo", 99, "bar");
assertEquals("foo99bar", result);
}
public static interface TestService {
public String test(String str, @Header(MessageHeaders.PRIORITY) int priority);
// wrt INT-1205, priority no longer has a $ prefix, so here we are testing the $custom header as well
public String test(String str, @Header(MessageHeaders.PRIORITY) int priority, @Header("$custom") String custom);
}
}

View File

@@ -37,7 +37,9 @@ import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
/**
@@ -112,18 +114,18 @@ public class ExpressionEvaluatingMessageProcessorTests {
@Test
public void testProcessMessageWithDollarInBrackets() {
Expression expression = expressionParser.parseExpression("headers['$id']");
Expression expression = expressionParser.parseExpression("headers['$foo_id']");
ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression);
GenericMessage<String> message = new GenericMessage<String>("foo");
assertEquals(message.getHeaders().getId(), processor.processMessage(message));
Message<String> message = MessageBuilder.withPayload("foo").setHeader("$foo_id", "abc").build();
assertEquals("abc", processor.processMessage(message));
}
@Test
public void testProcessMessageWithDollarPropertyAccess() {
Expression expression = expressionParser.parseExpression("headers.$id");
Expression expression = expressionParser.parseExpression("headers.$foo_id");
ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression);
GenericMessage<String> message = new GenericMessage<String>("foo");
assertEquals(message.getHeaders().getId(), processor.processMessage(message));
Message<String> message = MessageBuilder.withPayload("foo").setHeader("$foo_id", "xyz").build();
assertEquals("xyz", processor.processMessage(message));
}
@Test

View File

@@ -59,7 +59,7 @@ public class JsonInboundMessageMapperTests {
@Test
public void testToMessageWithHeadersAndStringPayload() throws Exception {
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":\"myPayloadStuff\"}";
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\"},\"payload\":\"myPayloadStuff\"}";
Message<String> expected = MessageBuilder.withPayload("myPayloadStuff").setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID, id).build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(String.class);
Message<?> result = mapper.toMessage(jsonMessage);
@@ -80,7 +80,7 @@ public class JsonInboundMessageMapperTests {
public void testToMessageWithHeadersAndBeanPayload() throws Exception {
TestBean bean = new TestBean();
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":" + getBeanAsJson(bean) + "}";
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\"},\"payload\":" + getBeanAsJson(bean) + "}";
Message<TestBean> expected = MessageBuilder.withPayload(bean).setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID, id).build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(TestBean.class);
Message<?> result = mapper.toMessage(jsonMessage);
@@ -101,7 +101,7 @@ public class JsonInboundMessageMapperTests {
public void testToMessageWithBeanHeaderAndStringPayload() throws Exception {
TestBean bean = new TestBean();
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\", \"myHeader\":" + getBeanAsJson(bean) + "},\"payload\":\"myPayloadStuff\"}";
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\", \"myHeader\":" + getBeanAsJson(bean) + "},\"payload\":\"myPayloadStuff\"}";
Message<String> expected = MessageBuilder.withPayload("myPayloadStuff").
setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID, id).setHeader("myHeader", bean).build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(String.class);
@@ -115,7 +115,7 @@ public class JsonInboundMessageMapperTests {
@Test
public void testToMessageWithHeadersAndListOfStringsPayload() throws Exception {
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":[\"myPayloadStuff1\",\"myPayloadStuff2\",\"myPayloadStuff3\"]}";
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\"},\"payload\":[\"myPayloadStuff1\",\"myPayloadStuff2\",\"myPayloadStuff3\"]}";
List<String> expectedList = Arrays.asList(new String[]{"myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3"});
Message<List<String>> expected = MessageBuilder.withPayload(expectedList).setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID, id).build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new TypeReference<List<String>>(){});
@@ -128,7 +128,7 @@ public class JsonInboundMessageMapperTests {
TestBean bean1 = new TestBean();
TestBean bean2 = new TestBean();
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"" + id + "\"},\"payload\":[" + getBeanAsJson(bean1) + "," + getBeanAsJson(bean2) + "]}";
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\"},\"payload\":[" + getBeanAsJson(bean1) + "," + getBeanAsJson(bean2) + "]}";
List<TestBean> expectedList = Arrays.asList(new TestBean[]{bean1, bean2});
Message<List<TestBean>> expected = MessageBuilder.withPayload(expectedList).setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID, id).build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new TypeReference<List<TestBean>>(){});

View File

@@ -50,8 +50,8 @@ public class JsonOutboundMessageMapperTests {
JsonOutboundMessageMapper mapper = new JsonOutboundMessageMapper();
String result = mapper.fromMessage(testMessage);
assertTrue(result.contains("\"headers\":{"));
assertTrue(result.contains("\"$timestamp\":"+testMessage.getHeaders().getTimestamp()));
assertTrue(result.contains("\"$id\":\""+testMessage.getHeaders().getId()+"\""));
assertTrue(result.contains("\"timestamp\":"+testMessage.getHeaders().getTimestamp()));
assertTrue(result.contains("\"id\":\""+testMessage.getHeaders().getId()+"\""));
assertTrue(result.contains("\"payload\":\"myPayloadStuff\""));
}
@@ -64,10 +64,10 @@ public class JsonOutboundMessageMapperTests {
JsonOutboundMessageMapper mapper = new JsonOutboundMessageMapper();
String result = mapper.fromMessage(testMessage);
assertTrue(result.contains("\"headers\":{"));
assertTrue(result.contains("\"$timestamp\":"+testMessage.getHeaders().getTimestamp()));
assertTrue(result.contains("\"$id\":\""+testMessage.getHeaders().getId()+"\""));
assertTrue(result.contains("\"timestamp\":"+testMessage.getHeaders().getTimestamp()));
assertTrue(result.contains("\"id\":\""+testMessage.getHeaders().getId()+"\""));
assertTrue(result.contains("\"payload\":\"myPayloadStuff\""));
assertTrue(result.contains("\"$history\":"));
assertTrue(result.contains("\"history\":"));
assertTrue(result.contains("testName-1"));
assertTrue(result.contains("testType-1"));
assertTrue(result.contains("testName-2"));
@@ -93,8 +93,8 @@ public class JsonOutboundMessageMapperTests {
JsonOutboundMessageMapper mapper = new JsonOutboundMessageMapper();
String result = mapper.fromMessage(testMessage);
assertTrue(result.contains("\"headers\":{"));
assertTrue(result.contains("\"$timestamp\":"+testMessage.getHeaders().getTimestamp()));
assertTrue(result.contains("\"$id\":\""+testMessage.getHeaders().getId()+"\""));
assertTrue(result.contains("\"timestamp\":"+testMessage.getHeaders().getTimestamp()));
assertTrue(result.contains("\"id\":\""+testMessage.getHeaders().getId()+"\""));
TestBean parsedPayload = extractJsonPayloadToTestBean(result);
assertEquals(payload, parsedPayload);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.context.metadata;
package org.springframework.integration.store;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -27,6 +27,7 @@ import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.integration.store.PropertiesPersistingMetadataStore;
/**
* @author Oleg Zhurakousky
@@ -53,10 +54,10 @@ public class PropertiesPersistingMetadataStoreTests {
@Test
public void validateWithCustomBaseDir() throws Exception {
File file = new File("foo" + "/metadata-store.properties");
File file = new File("target/foo" + "/metadata-store.properties");
file.deleteOnExit();
PropertiesPersistingMetadataStore metadataStore = new PropertiesPersistingMetadataStore();
metadataStore.setBaseDirectory("foo");
metadataStore.setBaseDirectory("target/foo");
metadataStore.afterPropertiesSet();
metadataStore.put("foo", "bar");
metadataStore.destroy();

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed 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.springframework.integration.feed;
/**
* Provides a place to store the header keys for {@link FeedReaderMessageSource}
*
* @author Josh Long
*/
public class FeedConstants {
static public final String FEED_URL = "FEED_URL";
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.feed;
import java.net.URL;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.store.MetadataStore;
import org.springframework.integration.store.SimpleMetadataStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FeedFetcher;
import com.sun.syndication.fetcher.FetcherEvent;
import com.sun.syndication.fetcher.FetcherListener;
import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
/**
* This implementation of {@link MessageSource} will produce individual
* {@link SyndEntry}s for a feed identified with the 'feedUrl' attribute.
*
* @author Josh Long
* @author Mario Gray
* @author Oleg Zhurakousky
* @since 2.0
*/
public class FeedEntryMessageSource extends IntegrationObjectSupport implements MessageSource<SyndEntry> {
private final URL feedUrl;
private final FeedFetcher feedFetcher;
private final Queue<SyndFeed> feeds = new ConcurrentLinkedQueue<SyndFeed>();
private final Queue<SyndEntry> entries = new ConcurrentLinkedQueue<SyndEntry>();
private volatile String metadataKey;
private volatile MetadataStore metadataStore;
private volatile long lastTime = -1;
private volatile boolean initialized;
private final Object monitor = new Object();
private final Comparator<SyndEntry> syndEntryComparator = new SyndEntryComparator();
private final Object feedMonitor = new Object();
/**
* Creates a FeedEntryMessageSource that will use a HttpURLFeedFetcher to read feeds from the given URL.
* If the feed URL has a protocol other than http*, consider providing a custom implementation of the
* {@link FeedFetcher} via the alternate constructor.
*/
public FeedEntryMessageSource(URL feedUrl) {
this(feedUrl, new HttpURLFeedFetcher(HashMapFeedInfoCache.getInstance()));
}
/**
* Creates a FeedEntryMessageSource that will use the provided FeedFetcher to read from the given feed URL.
*/
public FeedEntryMessageSource(URL feedUrl, FeedFetcher feedFetcher) {
Assert.notNull(feedUrl, "feedUrl must not be null");
Assert.notNull(feedFetcher, "feedFetcher must not be null");
this.feedUrl = feedUrl;
this.feedFetcher = feedFetcher;
}
public void setMetadataStore(MetadataStore metadataStore) {
Assert.notNull(metadataStore, "metadataStore must not be null");
this.metadataStore = metadataStore;
}
public String getComponentType() {
return "feed:inbound-channel-adapter";
}
public Message<SyndEntry> receive() {
Assert.isTrue(this.initialized, "'FeedEntryReaderMessageSource' must be initialized before it can produce Messages.");
SyndEntry entry = doReceive();
if (entry == null) {
return null;
}
return MessageBuilder.withPayload(entry).build();
}
@Override
protected void onInit() throws Exception {
this.feedFetcher.addFetcherEventListener(new FeedQueueUpdatingFetcherListener());
if (this.metadataStore == null) {
// first try to look for a 'messageStore' in the context
BeanFactory beanFactory = this.getBeanFactory();
if (beanFactory != null) {
this.metadataStore = IntegrationContextUtils.getMetadataStore(beanFactory);
}
// if no 'messageStore' in context, fall back to in-memory Map-based default
if (this.metadataStore == null) {
this.metadataStore = new SimpleMetadataStore();
}
}
StringBuilder metadataKeyBuilder = new StringBuilder();
if (StringUtils.hasText(this.getComponentType())) {
metadataKeyBuilder.append(this.getComponentType() + ".");
}
if (StringUtils.hasText(this.getComponentName())) {
metadataKeyBuilder.append(this.getComponentName() + ".");
}
else if (logger.isWarnEnabled()) {
logger.warn("FeedEntryMessageSource has no name. MetadataStore key might not be unique.");
}
metadataKeyBuilder.append(this.feedUrl);
this.metadataKey = metadataKeyBuilder.toString();
String lastTimeValue = this.metadataStore.get(this.metadataKey);
if (StringUtils.hasText(lastTimeValue)) {
this.lastTime = Long.parseLong(lastTimeValue);
}
this.initialized = true;
}
private SyndEntry doReceive() {
SyndEntry nextEntry = null;
synchronized (this.monitor) {
nextEntry = getNextEntry();
if (nextEntry == null) {
// read feed and try again
this.populateEntryList();
nextEntry = getNextEntry();
}
}
return nextEntry;
}
private SyndEntry getNextEntry() {
SyndEntry next = this.entries.poll();
if (next == null) {
return null;
}
this.lastTime = next.getPublishedDate().getTime();
this.metadataStore.put(this.metadataKey, this.lastTime + "");
return next;
}
@SuppressWarnings("unchecked")
private void populateEntryList() {
SyndFeed syndFeed = this.getFeed();
if (syndFeed != null) {
List<SyndEntry> retrievedEntries = (List<SyndEntry>) syndFeed.getEntries();
if (!CollectionUtils.isEmpty(retrievedEntries)) {
Collections.sort(retrievedEntries, this.syndEntryComparator);
for (SyndEntry entry : retrievedEntries) {
if (entry.getPublishedDate().getTime() > this.lastTime) {
this.entries.add(entry);
}
}
}
}
}
private SyndFeed getFeed() {
SyndFeed feed = null;
try {
synchronized (this.feedMonitor) {
feed = this.feedFetcher.retrieveFeed(this.feedUrl);
if (logger.isDebugEnabled()) {
logger.debug("retrieved feed at url '" + this.feedUrl + "'");
}
if (feed == null) {
if (logger.isDebugEnabled()) {
logger.debug("no feeds updated, returning null");
}
}
}
}
catch (Exception e) {
throw new MessagingException(
"Failed to retrieve feed at url '" + this.feedUrl + "'", e);
}
return feed;
}
private static class SyndEntryComparator implements Comparator<SyndEntry> {
public int compare(SyndEntry entry1, SyndEntry entry2) {
return entry1.getPublishedDate().compareTo(entry2.getPublishedDate());
}
}
private class FeedQueueUpdatingFetcherListener implements FetcherListener {
/**
* @see com.sun.syndication.fetcher.FetcherListener#fetcherEvent(com.sun.syndication.fetcher.FetcherEvent)
*/
public void fetcherEvent(final FetcherEvent event) {
String eventType = event.getEventType();
if (FetcherEvent.EVENT_TYPE_FEED_POLLED.equals(eventType)) {
logger.debug("\tEVENT: Feed Polled. URL = " + event.getUrlString());
}
else if (FetcherEvent.EVENT_TYPE_FEED_RETRIEVED.equals(eventType)) {
logger.debug("\tEVENT: Feed Retrieved. URL = " + event.getUrlString());
feeds.add(event.getFeed());
}
else if (FetcherEvent.EVENT_TYPE_FEED_UNCHANGED.equals(eventType)) {
logger.debug("\tEVENT: Feed Unchanged. URL = " + event.getUrlString());
}
}
}
}

View File

@@ -1,164 +0,0 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.feed;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.Message;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.context.metadata.MetadataStore;
import org.springframework.integration.context.metadata.SimpleMetadataStore;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
/**
* This implementation of {@link MessageSource} will produce individual
* {@link SyndEntry}s for a feed identified with the 'feedUrl' attribute.
*
* @author Josh Long
* @author Mario Gray
* @author Oleg Zhurakousky
* @since 2.0
*/
public class FeedEntryReaderMessageSource extends IntegrationObjectSupport implements MessageSource<SyndEntry> {
private final Queue<SyndEntry> entries = new ConcurrentLinkedQueue<SyndEntry>();
private final FeedReaderMessageSource feedReaderMessageSource;
private volatile String metadataKey;
private volatile MetadataStore metadataStore;
private volatile long lastTime = -1;
private volatile boolean initialized;
private final Object monitor = new Object();
private final Comparator<SyndEntry> syndEntryComparator = new SyndEntryComparator();
public FeedEntryReaderMessageSource(FeedReaderMessageSource feedReaderMessageSource) {
Assert.notNull(feedReaderMessageSource, "'feedReaderMessageSource' must not be null");
this.feedReaderMessageSource = feedReaderMessageSource;
}
public void setMetadataStore(MetadataStore metadataStore) {
Assert.notNull(metadataStore, "metadataStore must not be null");
this.metadataStore = metadataStore;
}
public String getComponentType() {
return "feed:inbound-channel-adapter";
}
public Message<SyndEntry> receive() {
Assert.isTrue(this.initialized, "'FeedEntryReaderMessageSource' must be initialized before it can produce Messages.");
SyndEntry entry = doReceive();
if (entry == null) {
return null;
}
return MessageBuilder.withPayload(entry).build();
}
@Override
protected void onInit() throws Exception {
if (this.metadataStore == null) {
// first try to look for a 'messageStore' in the context
BeanFactory beanFactory = this.getBeanFactory();
if (beanFactory != null) {
MetadataStore metadataStore = IntegrationContextUtils.getMetadataStore(beanFactory);
if (metadataStore != null) {
this.metadataStore = metadataStore;
}
}
if (this.metadataStore == null) {
this.metadataStore = new SimpleMetadataStore();
}
}
Assert.hasText(this.getComponentName(), "FeedEntryReaderMessageSource must have a name");
this.metadataKey = this.getComponentType() + "." + this.getComponentName()
+ "." + this.feedReaderMessageSource.getFeedUrl();
String lastTimeValue = this.metadataStore.get(this.metadataKey);
if (StringUtils.hasText(lastTimeValue)) {
this.lastTime = Long.parseLong(lastTimeValue);
}
this.initialized = true;
}
@SuppressWarnings("unchecked")
private SyndEntry doReceive() {
SyndEntry nextUp = null;
synchronized (this.monitor) {
nextUp = pollAndCache();
if (nextUp != null) {
return nextUp;
}
// otherwise, fill the backlog
SyndFeed syndFeed = this.feedReaderMessageSource.receiveSyndFeed();
if (syndFeed != null) {
List<SyndEntry> feedEntries = (List<SyndEntry>) syndFeed.getEntries();
if (null != feedEntries) {
Collections.sort(feedEntries, syndEntryComparator);
for (SyndEntry se : feedEntries) {
long publishedTime = se.getPublishedDate().getTime();
if (publishedTime > this.lastTime) {
entries.add(se);
}
}
}
}
nextUp = pollAndCache();
}
return nextUp;
}
private SyndEntry pollAndCache() {
SyndEntry next = this.entries.poll();
if (next == null) {
return null;
}
this.lastTime = next.getPublishedDate().getTime();
this.metadataStore.put(this.metadataKey, this.lastTime + "");
return next;
}
private static class SyndEntryComparator implements Comparator<SyndEntry> {
public int compare(SyndEntry entry1, SyndEntry entry2) {
if (entry1 == null || entry2 == null) {
}
return entry1.getPublishedDate().compareTo(entry2.getPublishedDate());
}
}
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.feed;
import java.net.URL;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FetcherEvent;
import com.sun.syndication.fetcher.FetcherListener;
import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
import com.sun.syndication.fetcher.impl.FeedFetcherCache;
import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
/**
* This implementation of {@link MessageSource} will produce {@link SyndFeed} for a feed identified
* with 'feedUrl' attribute.
*
* @author Josh Long
* @author Mario Gray
* @author Oleg Zhurakousky
*/
public class FeedReaderMessageSource extends IntegrationObjectSupport
implements InitializingBean, MessageSource<SyndFeed> {
private final AbstractFeedFetcher fetcher;
private final Object syndFeedMonitor = new Object();
private volatile URL feedUrl;
private volatile FeedFetcherCache fetcherCache;
private volatile ConcurrentLinkedQueue<SyndFeed> syndFeeds = new ConcurrentLinkedQueue<SyndFeed>();
private volatile MyFetcherListener myFetcherListener;
public FeedReaderMessageSource(URL feedUrl) {
this.feedUrl = feedUrl;
if (feedUrl.getProtocol().equals("file")){
fetcher = new FileUrlFeedFetcher();
}
else if (feedUrl.getProtocol().equals("http")){
fetcherCache = HashMapFeedInfoCache.getInstance();
fetcher = new HttpURLFeedFetcher(fetcherCache);
}
else{
throw new IllegalArgumentException("Unsupported URL protocol: " + feedUrl.getProtocol());
}
}
public URL getFeedUrl() {
return feedUrl;
}
public SyndFeed receiveSyndFeed() {
SyndFeed returnedSyndFeed = null;
try {
synchronized (syndFeedMonitor) {
returnedSyndFeed = fetcher.retrieveFeed(this.feedUrl);
logger.debug("attempted to retrieve feed '" + this.feedUrl + "'");
if (returnedSyndFeed == null) {
logger.debug("no feeds updated, return null!");
return null;
}
}
} catch (Exception e) {
throw new MessagingException("Exception thrown when trying to retrive feed at url '" + this.feedUrl + "'", e);
}
return returnedSyndFeed;
}
public Message<SyndFeed> receive() {
SyndFeed syndFeed = this.receiveSyndFeed();
if (null == syndFeed) {
return null;
}
return MessageBuilder.withPayload(syndFeed).setHeader(FeedConstants.FEED_URL, this.feedUrl).build();
}
@Override
protected void onInit() throws Exception {
fetcher.addFetcherEventListener(myFetcherListener);
Assert.notNull(this.feedUrl, "the feedURL can't be null");
}
class MyFetcherListener implements FetcherListener {
/**
* @see com.sun.syndication.fetcher.FetcherListener#fetcherEvent(com.sun.syndication.fetcher.FetcherEvent)
*/
public void fetcherEvent(final FetcherEvent event) {
String eventType = event.getEventType();
if (FetcherEvent.EVENT_TYPE_FEED_POLLED.equals(eventType)) {
logger.debug("\tEVENT: Feed Polled. URL = " + event.getUrlString());
} else if (FetcherEvent.EVENT_TYPE_FEED_RETRIEVED.equals(eventType)) {
logger.debug("\tEVENT: Feed Retrieved. URL = " + event.getUrlString());
syndFeeds.add(event.getFeed());
} else if (FetcherEvent.EVENT_TYPE_FEED_UNCHANGED.equals(eventType)) {
logger.debug("\tEVENT: Feed Unchanged. URL = " + event.getUrlString());
}
}
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed 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.springframework.integration.feed;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FetcherEvent;
import com.sun.syndication.fetcher.FetcherException;
import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
import com.sun.syndication.fetcher.impl.SyndFeedInfo;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
/**
* @author Oleg Zhurakousky
* @since 2.0
*/
public class FileUrlFeedFetcher extends AbstractFeedFetcher {
/* (non-Javadoc)
* @see com.sun.syndication.fetcher.FeedFetcher#retrieveFeed(java.net.URL)
*/
public SyndFeed retrieveFeed(URL feedUrl) throws IllegalArgumentException,
IOException, FeedException, FetcherException {
if (feedUrl == null) {
throw new IllegalArgumentException("null is not a valid URL");
}
URLConnection connection = feedUrl.openConnection();
SyndFeedInfo syndFeedInfo = new SyndFeedInfo();
retrieveAndCacheFeed(feedUrl, syndFeedInfo, connection);
return syndFeedInfo.getSyndFeed();
}
protected void retrieveAndCacheFeed(URL feedUrl, SyndFeedInfo syndFeedInfo, URLConnection connection) throws IllegalArgumentException, FeedException, FetcherException, IOException {
resetFeedInfo(feedUrl, syndFeedInfo, connection);
}
protected void resetFeedInfo(URL orignalUrl, SyndFeedInfo syndFeedInfo, URLConnection connection) throws IllegalArgumentException, IOException, FeedException {
// need to always set the URL because this may have changed due to 3xx redirects
syndFeedInfo.setUrl(connection.getURL());
// the ID is a persistant value that should stay the same even if the URL for the
// feed changes (eg, by 3xx redirects)
syndFeedInfo.setId(orignalUrl.toString());
// This will be 0 if the server doesn't support or isn't setting the last modified header
syndFeedInfo.setLastModified(new Long(connection.getLastModified()));
// get the contents
InputStream inputStream = null;
try {
inputStream = connection.getInputStream();
SyndFeed syndFeed = getSyndFeedFromStream(inputStream, connection);
syndFeedInfo.setSyndFeed(syndFeed);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
private SyndFeed getSyndFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, IllegalArgumentException, FeedException {
SyndFeed feed = readSyndFeedFromStream(inputStream, connection);
fireEvent(FetcherEvent.EVENT_TYPE_FEED_RETRIEVED, connection, feed);
return feed;
}
private SyndFeed readSyndFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, IllegalArgumentException, FeedException {
BufferedInputStream is;
if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) {
// handle gzip encoded content
is = new BufferedInputStream(new GZIPInputStream(inputStream));
} else {
is = new BufferedInputStream(inputStream);
}
XmlReader reader = null;
if (connection.getHeaderField("Content-Type") != null) {
reader = new XmlReader(is, connection.getHeaderField("Content-Type"), true);
} else {
reader = new XmlReader(is, true);
}
SyndFeedInput syndFeedInput = new SyndFeedInput();
syndFeedInput.setPreserveWireFeed(isPreserveWireFeed());
return syndFeedInput.build(reader);
}
}

View File

@@ -18,31 +18,34 @@ package org.springframework.integration.feed.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
/**
* Handles parsing the configuration for the feed inbound channel adapter.
* Handles parsing the configuration for the feed inbound-channel-adapter.
*
* @author Josh Long
* @author Oleg Zhurakousky
* @author Mark Fisher
* @since 2.0
*/
public class FeedMessageSourceBeanDefinitionParser extends AbstractPollingInboundChannelAdapterParser {
public class FeedInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected String parseSource(final Element element, final ParserContext parserContext) {
BeanDefinitionBuilder feedEntryBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.feed.FeedEntryReaderMessageSource");
BeanDefinitionBuilder feedBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.feed.FeedReaderMessageSource");
feedBuilder.addConstructorArgValue(element.getAttribute("feed-url"));
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(feedEntryBuilder, element, "metadata-store");
feedEntryBuilder.addConstructorArgValue(feedBuilder.getBeanDefinition());
return BeanDefinitionReaderUtils.registerWithGeneratedName(feedEntryBuilder.getBeanDefinition(), parserContext.getRegistry());
protected BeanMetadataElement parseSource(final Element element, final ParserContext parserContext) {
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.feed.FeedEntryMessageSource");
sourceBuilder.addConstructorArgValue(element.getAttribute("url"));
String feedFetcherRef = element.getAttribute("feed-fetcher");
if (StringUtils.hasText(feedFetcherRef)) {
sourceBuilder.addConstructorArgReference(feedFetcherRef);
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(sourceBuilder, element, "metadata-store");
return sourceBuilder.getBeanDefinition();
}
}

View File

@@ -13,19 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.feed.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;
/**
* NamespaceHandler for FEED module
* NamespaceHandler for the feed module.
*
* @author Josh Long
* @author Mark Fisher
* @since 2.0
*/
public class FeedNamespaceHandler extends NamespaceHandlerSupport {
public class FeedNamespaceHandler extends AbstractIntegrationNamespaceHandler {
public void init() {
registerBeanDefinitionParser("inbound-channel-adapter", new FeedMessageSourceBeanDefinitionParser());
}
}
public void init() {
registerBeanDefinitionParser("inbound-channel-adapter", new FeedInboundChannelAdapterParser());
}
}

View File

@@ -1,64 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/feed"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/feed"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/feed"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd" />
<xsd:element name="inbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
This adapter takes a feed URL (either RSS or ATOM) and responds to updates.
Depending on whether you've set the prefer-updated-feeds-to-entries attribute
or not, you will recieve <code>SyndFeed</code> or <code>SyndEntry</code> objects.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
<xsd:attribute name="channel" use="required" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="metadata-store" use="optional" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide cusom implementation of 'org.springframework.integration.context.metadata.MetadataStore'
to persist the state of the retrieved feeds to aviod duplicates between restarts.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.context.metadata.MetadataStore"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="feed-url" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Allows you to specify URL for RSS/ATOM feed
<xsd:element name="inbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
This adapter takes a feed URL (either RSS or ATOM) and responds to updates.
Each Message will be sent to the provided channel with a feed entry as its payload.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="url" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The URL for an RSS or ATOM feed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="channel" use="required" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="feed-fetcher" use="optional" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Reference to a FeedFetcher instance for retrieveing Feeds from the provided URL.
By default, the HTTP protocol is supported. For any other protocols or general
customizations, provide a reference to a different implementation.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="com.sun.syndication.FeedFetcher" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="metadata-store" use="optional" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Reference to a MetadataStore instance for storing metadata associated with
the retrieved feeds. If the implementation is persistent, it can help to
prevent duplicates between restarts. If shared, it can help coordinate multiple
instances of an adapter across different processes.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.context.metadata.MetadataStore" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -18,31 +18,29 @@ package org.springframework.integration.feed;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.context.metadata.PropertiesPersistingMetadataStore;
import org.springframework.integration.store.PropertiesPersistingMetadataStore;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FeedFetcher;
/**
* @author Oleg Zhurakousky
* @author Mark Fisher
* @since 2.0
*/
public class FeedEntryReaderMessageSourceTests {
public class FeedEntryMessageSourceTests {
private final FeedFetcher feedFetcher = new FileUrlFeedFetcher();
@Before
public void prepare() {
@@ -53,47 +51,36 @@ public class FeedEntryReaderMessageSourceTests {
}
@Test(expected=IllegalArgumentException.class)
public void testFailureWhenNotInitialized() {
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(mock(FeedReaderMessageSource.class));
public void testFailureWhenNotInitialized() throws Exception {
URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss");
FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url);
feedEntrySource.receive();
}
@Test
public void testReceiveFeedWithNoEntries() {
FeedReaderMessageSource feedReaderSource = mock(FeedReaderMessageSource.class);
SyndFeed feed = mock(SyndFeed.class);
when(feedReaderSource.receiveSyndFeed()).thenReturn(feed);
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
public void testReceiveFeedWithNoEntries() throws Exception {
URL url = new URL("file:src/test/java/org/springframework/integration/feed/empty.rss");
FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher);
feedEntrySource.setBeanName("feedReader");
feedEntrySource.afterPropertiesSet();
assertNull(feedEntrySource.receive());
}
@Test
public void testReceiveFeedWithEntriesSorted() {
FeedReaderMessageSource feedReaderSource = mock(FeedReaderMessageSource.class);
SyndFeed feed = mock(SyndFeed.class);
SyndEntry entry1 = mock(SyndEntry.class);
SyndEntry entry2 = mock(SyndEntry.class);
when(entry1.getPublishedDate()).thenReturn(new Date(System.currentTimeMillis()));
when(entry2.getPublishedDate()).thenReturn(new Date(System.currentTimeMillis()-10000));
List<SyndEntry> entries = new ArrayList<SyndEntry>();
entries.add(entry2);
entries.add(entry1);
when(feed.getEntries()).thenReturn(entries);
when(feedReaderSource.receiveSyndFeed()).thenReturn(feed);
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.setComponentName("feedReader");
feedEntrySource.afterPropertiesSet();
Message<SyndEntry> entryMessage = feedEntrySource.receive();
assertEquals(entry2, entryMessage.getPayload());
entryMessage = feedEntrySource.receive();
assertEquals(entry1, entryMessage.getPayload());
reset(feed);
entryMessage = feedEntrySource.receive();
assertNull(entryMessage);
public void testReceiveFeedWithEntriesSorted() throws Exception {
URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss");
FeedEntryMessageSource source = new FeedEntryMessageSource(url, this.feedFetcher);
source.setComponentName("feedReader");
source.afterPropertiesSet();
Message<SyndEntry> message1 = source.receive();
Message<SyndEntry> message2 = source.receive();
Message<SyndEntry> message3 = source.receive();
long time1 = message1.getPayload().getPublishedDate().getTime();
long time2 = message2.getPayload().getPublishedDate().getTime();
long time3 = message3.getPayload().getPublishedDate().getTime();
assertTrue(time1 < time2);
assertTrue(time2 < time3);
assertNull(source.receive());
}
// will test that last feed entry is remembered between the sessions
@@ -101,8 +88,7 @@ public class FeedEntryReaderMessageSourceTests {
@Test
public void testReceiveFeedWithRealEntriesAndRepeatWithPersistentMetadataStore() throws Exception {
URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss");
FeedReaderMessageSource feedReaderSource = new FeedReaderMessageSource(url);
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher);
feedEntrySource.setBeanName("feedReader");
PropertiesPersistingMetadataStore metadataStore = new PropertiesPersistingMetadataStore();
metadataStore.afterPropertiesSet();
@@ -126,7 +112,7 @@ public class FeedEntryReaderMessageSourceTests {
metadataStore.afterPropertiesSet();
// now test that what's been read is no longer retrieved
feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher);
feedEntrySource.setBeanName("feedReader");
metadataStore = new PropertiesPersistingMetadataStore();
metadataStore.afterPropertiesSet();
@@ -142,8 +128,7 @@ public class FeedEntryReaderMessageSourceTests {
@Test
public void testReceiveFeedWithRealEntriesAndRepeatNoPersistentMetadataStore() throws Exception {
URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss");
FeedReaderMessageSource feedReaderSource = new FeedReaderMessageSource(url);
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher);
feedEntrySource.setBeanName("feedReader");
feedEntrySource.afterPropertiesSet();
SyndEntry entry1 = feedEntrySource.receive().getPayload();
@@ -162,7 +147,7 @@ public class FeedEntryReaderMessageSourceTests {
// UNLIKE the previous test
// now test that what's been read is read AGAIN
feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher);
feedEntrySource.setBeanName("feedReader");
feedEntrySource.afterPropertiesSet();
entry1 = feedEntrySource.receive().getPayload();

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.feed;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;
import org.springframework.util.Assert;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FetcherEvent;
import com.sun.syndication.fetcher.FetcherException;
import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
import com.sun.syndication.fetcher.impl.SyndFeedInfo;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
/**
* @author Oleg Zhurakousky
* @author Mark Fisher
* @since 2.0
*/
public class FileUrlFeedFetcher extends AbstractFeedFetcher {
/**
* Retrieve a SyndFeed for the given URL.
* @see com.sun.syndication.fetcher.FeedFetcher#retrieveFeed(java.net.URL)
*/
public SyndFeed retrieveFeed(URL feedUrl) throws IOException, FeedException, FetcherException {
Assert.notNull(feedUrl, "feedUrl must not be null");
URLConnection connection = feedUrl.openConnection();
SyndFeedInfo syndFeedInfo = new SyndFeedInfo();
this.refreshFeedInfo(feedUrl, syndFeedInfo, connection);
return syndFeedInfo.getSyndFeed();
}
private void refreshFeedInfo(URL feedUrl, SyndFeedInfo syndFeedInfo, URLConnection connection) throws IOException, FeedException {
// need to always set the URL because this may have changed due to 3xx redirects
syndFeedInfo.setUrl(connection.getURL());
// the ID is a persistent value that should stay the same
// even if the URL for the feed changes (eg, by 3xx redirects)
syndFeedInfo.setId(feedUrl.toString());
// This will be 0 if the server doesn't support or isn't setting the last modified header
syndFeedInfo.setLastModified(new Long(connection.getLastModified()));
// get the contents
InputStream inputStream = null;
try {
inputStream = connection.getInputStream();
SyndFeed syndFeed = this.readFeedFromStream(inputStream, connection);
syndFeedInfo.setSyndFeed(syndFeed);
}
finally {
try {
inputStream.close();
}
catch (Exception e) {
// ignore
}
}
}
private SyndFeed readFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, FeedException {
BufferedInputStream bufferedInputStream;
if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) {
// handle gzip encoded content
bufferedInputStream = new BufferedInputStream(new GZIPInputStream(inputStream));
}
else {
bufferedInputStream = new BufferedInputStream(inputStream);
}
XmlReader reader = null;
if (connection.getHeaderField("Content-Type") != null) {
reader = new XmlReader(bufferedInputStream, connection.getHeaderField("Content-Type"), true);
}
else {
reader = new XmlReader(bufferedInputStream, true);
}
SyndFeedInput syndFeedInput = new SyndFeedInput();
syndFeedInput.setPreserveWireFeed(isPreserveWireFeed());
SyndFeed feed = syndFeedInput.build(reader);
fireEvent(FetcherEvent.EVENT_TYPE_FEED_RETRIEVED, connection, feed);
return feed;
}
}

View File

@@ -1,23 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-feed="http://www.springframework.org/schema/integration/feed"
xmlns:feed="http://www.springframework.org/schema/integration/feed"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed-2.0.xsd">
<int-feed:inbound-channel-adapter id="feedAdapter"
<feed:inbound-channel-adapter id="feedAdapter"
channel="feedChannel"
auto-startup="false"
feed-fetcher="fileUrlFeedFetcher"
metadata-store="customMetadataStore"
feed-url="file:src/test/java/org/springframework/integration/feed/config/sample.rss">
url="file:src/test/java/org/springframework/integration/feed/config/sample.rss">
<int:poller fixed-rate="10000" max-messages-per-poll="100" />
</int-feed:inbound-channel-adapter>
</feed:inbound-channel-adapter>
<int:channel id="feedChannel">
<int:queue/>
</int:channel>
<bean id="customMetadataStore" class="org.springframework.integration.feed.config.FeedMessageSourceBeanDefinitionParserTests.SampleMetadataStore"/>
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.FileUrlFeedFetcher"/>
<bean id="customMetadataStore" class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests.SampleMetadataStore"/>
</beans>

View File

@@ -10,14 +10,17 @@
<int-feed:inbound-channel-adapter id="feedAdapterUsage"
channel="feedChannelUsage"
feed-url="file:src/test/java/org/springframework/integration/feed/config/sample.rss">
url="file:src/test/java/org/springframework/integration/feed/config/sample.rss"
feed-fetcher="fileUrlFeedFetcher">
<int:poller fixed-rate="10000" max-messages-per-poll="100" fixed-delay="10000"/>
</int-feed:inbound-channel-adapter>
<int:service-activator id="sampleActivator" input-channel="feedChannelUsage">
<bean class="org.springframework.integration.feed.config.FeedMessageSourceBeanDefinitionParserTests$SampleService" />
<bean class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests$SampleService" />
</int:service-activator>
<bean id="metadataStore" class="org.springframework.integration.context.metadata.PropertiesPersistingMetadataStore"/>
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.FileUrlFeedFetcher"/>
<bean id="metadataStore" class="org.springframework.integration.store.PropertiesPersistingMetadataStore"/>
</beans>

View File

@@ -7,12 +7,15 @@
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed-2.0.xsd">
<int-feed:inbound-channel-adapter channel="feedChannelUsage"
feed-url="file:src/test/java/org/springframework/integration/feed/config/sample.rss">
url="file:src/test/java/org/springframework/integration/feed/config/sample.rss"
feed-fetcher="fileUrlFeedFetcher">
<int:poller fixed-rate="10000" max-messages-per-poll="100" fixed-delay="10000"/>
</int-feed:inbound-channel-adapter>
<int:service-activator id="sampleActivator" input-channel="feedChannelUsage">
<bean class="org.springframework.integration.feed.config.FeedMessageSourceBeanDefinitionParserTests$SampleServiceNoHistory" />
<bean class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests$SampleServiceNoHistory" />
</int:service-activator>
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.FileUrlFeedFetcher"/>
</beans>

View File

@@ -8,10 +8,11 @@
<int-feed:inbound-channel-adapter id="feedAdapter"
channel="feedChannel"
auto-startup="false"
feed-url="http://feeds.bbci.co.uk/news/rss.xml">
url="http://feeds.bbci.co.uk/news/rss.xml"
auto-startup="false">
<int:poller fixed-rate="10000" max-messages-per-poll="100" />
</int-feed:inbound-channel-adapter>
<int:channel id="feedChannel" />
</beans>

View File

@@ -38,13 +38,11 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.context.metadata.MetadataStore;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.feed.FeedEntryReaderMessageSource;
import org.springframework.integration.feed.FeedReaderMessageSource;
import org.springframework.integration.feed.FileUrlFeedFetcher;
import org.springframework.integration.feed.FeedEntryMessageSource;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.store.MetadataStore;
import org.springframework.integration.test.util.TestUtils;
import com.sun.syndication.feed.synd.SyndEntry;
@@ -56,7 +54,7 @@ import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
* @author Mark Fisher
* @since 2.0
*/
public class FeedMessageSourceBeanDefinitionParserTests {
public class FeedInboundChannelAdapterParserTests {
private static CountDownLatch latch;
@@ -69,23 +67,28 @@ public class FeedMessageSourceBeanDefinitionParserTests {
}
@Test
public void validateSuccessfulConfigurationWithCustomMetadataStore() {
public void validateSuccessfulFileConfigurationWithCustomMetadataStore() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-file-context.xml", this.getClass());
"FeedInboundChannelAdapterParserTests-file-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = context.getBean("feedAdapter", SourcePollingChannelAdapter.class);
FeedEntryReaderMessageSource source = (FeedEntryReaderMessageSource) TestUtils.getPropertyValue(adapter, "source");
FeedEntryMessageSource source = (FeedEntryMessageSource) TestUtils.getPropertyValue(adapter, "source");
MetadataStore metadataStore = (MetadataStore) TestUtils.getPropertyValue(source, "metadataStore");
assertTrue(metadataStore instanceof SampleMetadataStore);
assertEquals(metadataStore, context.getBean("customMetadataStore"));
FeedReaderMessageSource feedReaderMessageSource = (FeedReaderMessageSource) TestUtils.getPropertyValue(source, "feedReaderMessageSource");
AbstractFeedFetcher fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(feedReaderMessageSource, "fetcher");
assertTrue(fetcher instanceof FileUrlFeedFetcher);
context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-http-context.xml", this.getClass());
adapter = context.getBean("feedAdapter", SourcePollingChannelAdapter.class);
source = (FeedEntryReaderMessageSource) TestUtils.getPropertyValue(adapter, "source");
feedReaderMessageSource = (FeedReaderMessageSource) TestUtils.getPropertyValue(source, "feedReaderMessageSource");
fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(feedReaderMessageSource, "fetcher");
AbstractFeedFetcher fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(source, "feedFetcher");
assertEquals("FileUrlFeedFetcher", fetcher.getClass().getSimpleName());
context.destroy();
}
public void validateSuccessfulHttpConfigurationWithCustomMetadataStore() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"FeedInboundChannelAdapterParserTests-http-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = context.getBean("feedAdapter", SourcePollingChannelAdapter.class);
FeedEntryMessageSource source = (FeedEntryMessageSource) TestUtils.getPropertyValue(adapter, "source");
MetadataStore metadataStore = (MetadataStore) TestUtils.getPropertyValue(source, "metadataStore");
assertTrue(metadataStore instanceof SampleMetadataStore);
assertEquals(metadataStore, context.getBean("customMetadataStore"));
AbstractFeedFetcher fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(source, "feedFetcher");
assertTrue(fetcher instanceof HttpURLFeedFetcher);
context.destroy();
}
@@ -99,7 +102,7 @@ public class FeedMessageSourceBeanDefinitionParserTests {
//Test file samples.rss has 3 news items
latch = spy(new CountDownLatch(3));
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml", this.getClass());
"FeedInboundChannelAdapterParserTests-file-usage-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(3)).countDown();
context.destroy();
@@ -108,7 +111,7 @@ public class FeedMessageSourceBeanDefinitionParserTests {
// in this iteration no new feeds will be received and the latch will timeout
latch = spy(new CountDownLatch(3));
context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml", this.getClass());
"FeedInboundChannelAdapterParserTests-file-usage-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(0)).countDown();
context.destroy();
@@ -119,7 +122,7 @@ public class FeedMessageSourceBeanDefinitionParserTests {
//Test file samples.rss has 3 news items
latch = spy(new CountDownLatch(3));
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml", this.getClass());
"FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(3)).countDown();
context.destroy();
@@ -128,7 +131,7 @@ public class FeedMessageSourceBeanDefinitionParserTests {
// in this iteration no new feeds will be received and the latch will timeout
latch = spy(new CountDownLatch(3));
context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml", this.getClass());
"FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(3)).countDown();
context.destroy();
@@ -144,7 +147,7 @@ public class FeedMessageSourceBeanDefinitionParserTests {
}
});
ApplicationContext context = new ClassPathXmlApplicationContext(
"FeedMessageSourceBeanDefinitionParserTests-http-context.xml", this.getClass());
"FeedInboundChannelAdapterParserTests-http-context.xml", this.getClass());
DirectChannel feedChannel = context.getBean("feedChannel", DirectChannel.class);
feedChannel.subscribe(handler);
latch.await(5, TimeUnit.SECONDS);

View File

@@ -0,0 +1,20 @@
<rss version="2.0">
<channel>
<title>Spring Integration</title>
<link>http://www.springsource.org/spring-integration</link>
<description>
Spring Integration is a really cool framework
</description>
<language>en-us</language>
<copyright>Copyright 2004-2010 SpringSource/VMWare
All Rights Reserved.</copyright>
<lastBuildDate>Tue, 12 Apr 2010 18:21:32 EST</lastBuildDate>
<ttl>240</ttl>
<image>
<url>http://www.springsource.org/sites/all/themes/dotorg09/images/dotorg09_logo.png</url>
<title>Spring Integration</title>
<link>http://www.springsource.org/spring-integration</link>
</image>
</channel>
</rss>

View File

@@ -16,7 +16,11 @@
package org.springframework.integration.file.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
@@ -24,7 +28,6 @@ import org.springframework.integration.config.xml.AbstractPollingInboundChannelA
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parser for the &lt;inbound-channel-adapter&gt; element of the 'file' namespace.
@@ -38,7 +41,7 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
PACKAGE_NAME + ".config.FileReadingMessageSourceFactoryBean");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "comparator");
@@ -52,7 +55,8 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
builder.addPropertyReference("locker", lockerBeanName);
}
builder.addPropertyReference("filter", filterBeanName);
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
return new RuntimeBeanReference(beanName);
}
private String registerLocker(Element element, ParserContext parserContext) {

View File

@@ -1,40 +1,65 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import java.io.IOException;
import java.net.SocketException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.integration.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.SocketException;
/**
*
* base class for the other {@link org.springframework.integration.ftp.FtpClientFactory} implementations.
* Most of this came out of the {@link DefaultFtpClientFactory} and was refactored into a base class
*
* @author Iwein Fuld
*
* @param <T>
*/
abstract public class AbstractFtpClientFactory<T extends FTPClient> implements FtpClientFactory<T> {
private static final Log logger = LogFactory.getLog(FtpClientFactory.class);
private static final String DEFAULT_REMOTE_WORKING_DIRECTORY = "/";
protected FTPClientConfig config;
protected String username;
protected String host;
protected String password;
protected int port = FTP.DEFAULT_PORT;
protected String remoteWorkingDirectory = DEFAULT_REMOTE_WORKING_DIRECTORY;
protected int clientMode = FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE;
protected int fileType = FTP.BINARY_FILE_TYPE;
public void setFileType(int fileType) {
this.fileType = fileType;
}
@@ -70,9 +95,9 @@ abstract public class AbstractFtpClientFactory<T extends FTPClient> implements F
}
/**
* Set client mode for example
* <code>FTPClient.ACTIVE_LOCAL_CONNECTION_MODE</code> (default) Only local
* modes are supported.
* Set client mode, for example
* <code>FTPClient.ACTIVE_LOCAL_CONNECTION_MODE</code> (default)
* Only local modes are supported.
*/
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
@@ -118,7 +143,6 @@ abstract public class AbstractFtpClientFactory<T extends FTPClient> implements F
}
setClientMode(client);
client.setFileType(this.fileType);
if (logger.isDebugEnabled()) {
@@ -135,7 +159,6 @@ abstract public class AbstractFtpClientFactory<T extends FTPClient> implements F
logger.debug("working directory is: " +
client.printWorkingDirectory());
}
return client;
}
@@ -146,16 +169,13 @@ abstract public class AbstractFtpClientFactory<T extends FTPClient> implements F
switch (clientMode) {
case FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE:
client.enterLocalActiveMode();
break;
case FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE:
client.enterLocalPassiveMode();
break;
default:
break;
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.springframework.util.StringUtils;
@@ -5,91 +21,78 @@ import org.springframework.util.StringUtils;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* Factors out the client factory creaton
* Factors out the client factory creation.
*
* @author Josh Long
*/
public class ClientFactorySupport {
public static DefaultFtpsClientFactory ftpsClientFactory(String host,
int port, String remoteDir, String user, String pw, int fileType,
public static DefaultFtpsClientFactory ftpsClientFactory(String host, int port, String remoteWorkingDirectory, String user, String password, int fileType,
int clientMode, String prot, String protocol, String authValue,
Boolean implicit, TrustManager trustManager, KeyManager keyManager,
Boolean sessionCreation, Boolean useClientMode,
Boolean wantsClientAuth, Boolean needClientAuth, String[] cipherSuites) {
DefaultFtpsClientFactory defaultFtpClientFactory = new DefaultFtpsClientFactory();
defaultFtpClientFactory.setHost(host);
defaultFtpClientFactory.setPassword(pw);
defaultFtpClientFactory.setPort((port));
defaultFtpClientFactory.setRemoteWorkingDirectory(remoteDir);
defaultFtpClientFactory.setPassword(password);
defaultFtpClientFactory.setPort(port);
defaultFtpClientFactory.setRemoteWorkingDirectory(remoteWorkingDirectory);
defaultFtpClientFactory.setUsername(user);
defaultFtpClientFactory.setFileType(fileType);
defaultFtpClientFactory.setClientMode(clientMode);
if (cipherSuites != null) {
defaultFtpClientFactory.setCipherSuites(cipherSuites);
}
if (StringUtils.hasText(prot)) {
defaultFtpClientFactory.setProt(prot);
}
if (StringUtils.hasText(protocol)) {
defaultFtpClientFactory.setProtocol(protocol);
}
if (StringUtils.hasText(authValue)) {
defaultFtpClientFactory.setAuthValue(authValue);
}
if (null != implicit) {
defaultFtpClientFactory.setImplicit(implicit);
}
if (trustManager != null) {
defaultFtpClientFactory.setTrustManager(trustManager);
}
if (keyManager != null) {
defaultFtpClientFactory.setKeyManager(keyManager);
}
if (needClientAuth != null) {
defaultFtpClientFactory.setNeedClientAuth(needClientAuth);
}
if (wantsClientAuth != null) {
defaultFtpClientFactory.setWantsClientAuth(wantsClientAuth);
}
if (sessionCreation != null) {
defaultFtpClientFactory.setSessionCreation(sessionCreation);
}
if (useClientMode != null) {
defaultFtpClientFactory.setUseClientMode(useClientMode);
}
return defaultFtpClientFactory;
}
public static DefaultFtpClientFactory ftpClientFactory(String host,
int port,
String remoteDir,
String remoteWorkingDirectory,
String user,
String pw,
String password,
int clientMode,
int fileType) {
DefaultFtpClientFactory defaultFtpClientFactory = new DefaultFtpClientFactory();
defaultFtpClientFactory.setHost(host);
defaultFtpClientFactory.setPassword(pw);
defaultFtpClientFactory.setPassword(password);
defaultFtpClientFactory.setPort(port);
defaultFtpClientFactory.setRemoteWorkingDirectory(remoteDir);
defaultFtpClientFactory.setRemoteWorkingDirectory(remoteWorkingDirectory);
defaultFtpClientFactory.setUsername(user);
defaultFtpClientFactory.setClientMode(clientMode);
defaultFtpClientFactory.setFileType(fileType);
return defaultFtpClientFactory;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,20 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPClient;
/**
* Default implementation of FtpClientFactory.
*
* @author iwein
* @author Iwein Fuld
* @author Josh Long
*/
public class DefaultFtpClientFactory extends AbstractFtpClientFactory<FTPClient> {
@Override
protected FTPClient createSingleInstanceOfClient() {
return new FTPClient();
}
}

View File

@@ -1,22 +1,31 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPSClient;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.SocketException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
import org.apache.commons.net.ftp.FTPSClient;
import org.springframework.util.StringUtils;
/**
* provides a working FTPS implementation. Based heavily on {@link org.springframework.integration.ftp.DefaultFtpClientFactory}
@@ -25,19 +34,32 @@ import java.util.Properties;
* @author Iwein Fuld
*/
public class DefaultFtpsClientFactory extends AbstractFtpClientFactory<FTPSClient> {
private Boolean useClientMode;
private Boolean sessionCreation;
private String authValue;
private TrustManager trustManager;
private String[] cipherSuites;
private String[] protocols;
private KeyManager keyManager;
private Boolean needClientAuth;
private Boolean wantsClientAuth;
private boolean implicit = false;
private String prot = "P";
private String protocol;
public void setProtocol(String protocol) {
this.protocol = protocol;
}
@@ -82,9 +104,12 @@ public class DefaultFtpsClientFactory extends AbstractFtpClientFactory<FTPSClien
this.prot = prot;
}
public void setImplicit(boolean implicit) {
this.implicit = implicit;
}
@Override
protected void onAfterConnect(FTPSClient ftpsClient)
throws IOException {
protected void onAfterConnect(FTPSClient ftpsClient) throws IOException {
ftpsClient.execPBSZ(0);
ftpsClient.execPROT(this.prot);
}
@@ -92,64 +117,50 @@ public class DefaultFtpsClientFactory extends AbstractFtpClientFactory<FTPSClien
@Override
public FTPSClient getClient() throws SocketException, IOException {
FTPSClient ftpsClient = super.getClient();
if (StringUtils.hasText(this.authValue)) {
ftpsClient.setAuthValue(authValue);
}
if (this.trustManager != null) {
ftpsClient.setTrustManager(this.trustManager);
}
if (this.cipherSuites != null) {
ftpsClient.setEnabledCipherSuites(this.cipherSuites);
}
if (this.protocols != null) {
ftpsClient.setEnabledProtocols(this.protocols);
}
if (this.sessionCreation != null) {
ftpsClient.setEnabledSessionCreation(this.sessionCreation);
}
if (this.useClientMode != null) {
ftpsClient.setUseClientMode(this.useClientMode);
}
if (this.sessionCreation != null) {
ftpsClient.setEnabledSessionCreation(this.sessionCreation);
}
if (this.keyManager != null) {
ftpsClient.setKeyManager(keyManager);
}
if (this.needClientAuth != null) {
ftpsClient.setNeedClientAuth(this.needClientAuth);
}
if (this.wantsClientAuth != null) {
ftpsClient.setWantClientAuth(this.wantsClientAuth);
}
return ftpsClient;
}
public void setImplicit(boolean implicit) {
this.implicit = implicit;
}
@Override
protected FTPSClient createSingleInstanceOfClient() {
try {
if (StringUtils.hasText(this.protocol)) {
return new FTPSClient(this.protocol, this.implicit);
}
return new FTPSClient(this.implicit);
} catch (NoSuchAlgorithmException e) {
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,22 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPClient;
import java.io.IOException;
/**
* Factory for {@link FTPClient}.
*
* @author Iwein Fuld
*/
public interface FtpClientFactory<T extends FTPClient> {
/**
* @return Fully configured and connected FTPClient. Never <code>null</code>.
* @throws IOException thrown when a networking IO subsystem error occurs
*/
T getClient() throws IOException;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPClient;
/**
* A pool of {@link FTPClient} instances. The pool can be used to control the
* number of open FTP connections and reuse these connections efficiently.
@@ -25,6 +25,7 @@ import org.apache.commons.net.ftp.FTPClient;
* @author Iwein Fuld
*/
public interface FtpClientPool extends FtpClientFactory {
/**
* Releases the client back to the pool. When calling this method the caller
* is no longer responsible for the connection. The pool is free to do with
@@ -43,4 +44,5 @@ public interface FtpClientPool extends FtpClientFactory {
* <code>null</code>.
*/
void releaseClient(FTPClient client);
}

View File

@@ -1,16 +1,33 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.entries.EntryNamer;
/**
* A {@link org.springframework.integration.file.entries.EntryNamer} for {@link org.apache.commons.net.ftp.FTPFile} objects
*
* @author Josh Long
*/
public class FtpFileEntryNamer implements EntryNamer<FTPFile> {
public String nameOf(FTPFile entry) {
return entry.getName();
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPClient;
@@ -17,24 +33,22 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
/**
* An FTP-adapter implementation of {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer}
*
* @author Iwein Fuld
* @author Josh Long
*/
public class FtpInboundRemoteFileSystemSynchronizer
extends AbstractInboundRemoteFileSystemSychronizer<FTPFile> {
protected FtpClientPool clientPool;
public class FtpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemoteFileSystemSychronizer<FTPFile> {
private volatile Trigger trigger = new PeriodicTrigger(10 * 1000);
protected volatile FtpClientPool clientPool;
@Override
protected void onInit() throws Exception {
Assert.notNull(this.clientPool, "clientPool can't be null");
if (this.shouldDeleteSourceFile) {
this.entryAcknowledgmentStrategy = new DeletionEntryAcknowledgmentStrategy();
}
protected Trigger getTrigger() {
return this.trigger;
}
/**
@@ -46,37 +60,40 @@ public class FtpInboundRemoteFileSystemSynchronizer
this.clientPool = clientPool;
}
protected boolean copyFileToLocalDirectory(FTPClient client,
FTPFile ftpFile, Resource localDirectory)
throws IOException, FileNotFoundException {
String remoteFileName = ftpFile.getName();
String localFileName = localDirectory.getFile().getPath() + "/" +
remoteFileName;
File localFile = new File(localFileName);
@Override
protected void onInit() throws Exception {
Assert.notNull(this.clientPool, "clientPool must not be null");
if (this.shouldDeleteSourceFile) {
this.entryAcknowledgmentStrategy = new DeletionEntryAcknowledgmentStrategy();
}
}
private boolean copyFileToLocalDirectory(FTPClient client, FTPFile ftpFile, Resource localDirectory)
throws IOException, FileNotFoundException {
String remoteFileName = ftpFile.getName();
String localFileName = localDirectory.getFile().getPath() + "/" + remoteFileName;
File localFile = new File(localFileName);
if (!localFile.exists()) {
String tempFileName = localFileName +
AbstractInboundRemoteFileSystemSynchronizingMessageSource.INCOMPLETE_EXTENSION;
File file = new File(tempFileName);
FileOutputStream fos = new FileOutputStream(file);
try {
client.retrieveFile(remoteFileName, fos);
// Perhaps we have some dispatch of hte source file to do?
// Perhaps we have some dispatch of the source file to do?
acknowledge(client, ftpFile);
} catch (Throwable th) {
}
catch (Throwable th) {
throw new RuntimeException(th);
} finally {
}
finally {
fos.close();
}
file.renameTo(localFile);
return true;
} else {
return false;
}
return false;
}
@Override
@@ -86,44 +103,38 @@ public class FtpInboundRemoteFileSystemSynchronizer
Assert.state(client != null,
FtpClientPool.class.getSimpleName() +
" returned a 'null' client. " +
"This most likely a bug in the pool implementation.");
"This is most likely a bug in the pool implementation.");
Collection<FTPFile> fileList = this.filter.filterEntries(client.listFiles());
try {
for (FTPFile ftpFile : fileList) {
if ((ftpFile != null) && ftpFile.isFile()) {
copyFileToLocalDirectory(client, ftpFile,
this.localDirectory);
copyFileToLocalDirectory(client, ftpFile, this.localDirectory);
}
}
} finally {
}
finally {
this.clientPool.releaseClient(client);
}
} catch (IOException e) {
throw new MessagingException("Problem occurred while synchronizing remote to local directory",
e);
}
catch (IOException e) {
throw new MessagingException("Problem occurred while synchronizing remote to local directory", e);
}
}
@Override
protected Trigger getTrigger() {
return new PeriodicTrigger(10 * 1000);
}
/**
* An ackowledgment strategy that deletes
* An acknowledgment strategy that deletes the file.
*/
class DeletionEntryAcknowledgmentStrategy implements AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy<FTPFile> {
public void acknowledge(Object useful, FTPFile msg)
throws Exception {
FTPClient ftpClient = (FTPClient) useful;
private class DeletionEntryAcknowledgmentStrategy implements AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy<FTPFile> {
if ((msg != null) && ftpClient.deleteFile(msg.getName())) {
public void acknowledge(Object useful, FTPFile fptFile) throws Exception {
FTPClient ftpClient = (FTPClient) useful;
if ((fptFile != null) && ftpClient.deleteFile(fptFile.getName())) {
if (logger.isDebugEnabled()) {
logger.debug("deleted " + msg.getName());
logger.debug("deleted " + fptFile.getName());
}
}
}
}
}

View File

@@ -1,23 +1,51 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
/**
* a {@link org.springframework.integration.core.MessageSource} implementation for FTP
* A {@link org.springframework.integration.core.MessageSource} implementation for FTP.
*
* @author Iwein Fuld
* @author Josh Long
*/
public class FtpInboundRemoteFileSystemSynchronizingMessageSource
extends AbstractInboundRemoteFileSystemSynchronizingMessageSource<FTPFile, FtpInboundRemoteFileSystemSynchronizer> {
private volatile FtpClientPool clientPool;
public void setClientPool(FtpClientPool clientPool) {
this.clientPool = clientPool;
}
public String getComponentType() {
return "ftp:inbound-channel-adapter";
}
@Override
protected void onInit() {
super.onInit();
this.synchronizer.setClientPool(this.clientPool);
}
@Override
protected void doStart() {
this.synchronizer.start();
@@ -28,13 +56,4 @@ public class FtpInboundRemoteFileSystemSynchronizingMessageSource
this.synchronizer.stop();
}
@Override
protected void onInit() {
super.onInit();
this.synchronizer.setClientPool(this.clientPool);
}
public String getComponentType() {
return "ftp:inbound-channel-adapter";
}
}

View File

@@ -1,9 +1,28 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import java.io.File;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
@@ -14,9 +33,6 @@ import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import org.springframework.util.StringUtils;
import java.io.File;
/**
* Factory to make building the namespace easier
*
@@ -24,35 +40,91 @@ import java.io.File;
* @author Josh Long
*/
public class FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean
extends AbstractFactoryBean<FtpInboundRemoteFileSystemSynchronizingMessageSource>
implements ResourceLoaderAware {
extends AbstractFactoryBean<FtpInboundRemoteFileSystemSynchronizingMessageSource> implements ResourceLoaderAware {
protected volatile String port;
protected volatile String autoCreateDirectories;
protected volatile String filenamePattern;
protected volatile String username;
protected volatile String password;
protected volatile String host;
protected volatile String remoteDirectory;
protected volatile String localWorkingDirectory;
protected volatile ResourceLoader resourceLoader;
protected volatile Resource localDirectoryResource;
protected volatile EntryListFilter<FTPFile> filter;
protected volatile int clientMode = FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE;
protected volatile int fileType = FTP.BINARY_FILE_TYPE;
private volatile String autoDeleteRemoteFilesOnSync;
protected String defaultFtpInboundFolderName = "ftpInbound";
@SuppressWarnings("unused")
public void setFileType(int fileType) {
this.fileType = fileType;
}
@SuppressWarnings("unused")
public void setAutoDeleteRemoteFilesOnSync(
String autoDeleteRemoteFilesOnSync) {
public void setAutoDeleteRemoteFilesOnSync(String autoDeleteRemoteFilesOnSync) {
this.autoDeleteRemoteFilesOnSync = autoDeleteRemoteFilesOnSync;
}
public void setPort(String port) {
this.port = port;
}
public void setAutoCreateDirectories(String autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setHost(String host) {
this.host = host;
}
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
public void setLocalWorkingDirectory(String localWorkingDirectory) {
this.localWorkingDirectory = localWorkingDirectory;
}
public void setFilter(EntryListFilter<FTPFile> filter) {
this.filter = filter;
}
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public Class<?> getObjectType() {
return FtpInboundRemoteFileSystemSynchronizingMessageSource.class;
@@ -61,122 +133,52 @@ public class FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean
private Resource fromText(String path) {
ResourceEditor resourceEditor = new ResourceEditor(this.resourceLoader);
resourceEditor.setAsText(path);
return (Resource) resourceEditor.getValue();
}
protected AbstractFtpClientFactory defaultClientFactory()
throws Exception {
protected AbstractFtpClientFactory<?> defaultClientFactory() throws Exception {
return ClientFactorySupport.ftpClientFactory(this.host,
Integer.parseInt(this.port), this.remoteDirectory, this.username,
this.password, this.clientMode, this.fileType);
}
@Override
protected FtpInboundRemoteFileSystemSynchronizingMessageSource createInstance()
throws Exception {
protected FtpInboundRemoteFileSystemSynchronizingMessageSource createInstance() throws Exception {
boolean autoCreatDirs = Boolean.parseBoolean(this.autoCreateDirectories);
boolean ackRemoteDir = Boolean.parseBoolean(this.autoDeleteRemoteFilesOnSync);
FtpInboundRemoteFileSystemSynchronizingMessageSource ftpRemoteFileSystemSynchronizingMessageSource =
new FtpInboundRemoteFileSystemSynchronizingMessageSource();
ftpRemoteFileSystemSynchronizingMessageSource.setAutoCreateDirectories(autoCreatDirs);
if (!StringUtils.hasText(this.localWorkingDirectory)) {
File tmp = new File(SystemUtils.getJavaIoTmpDir(), defaultFtpInboundFolderName);
this.localWorkingDirectory = "file://" + tmp.getAbsolutePath();
}
this.localDirectoryResource = this.fromText(this.localWorkingDirectory);
FtpFileEntryNamer ftpFileEntryNamer = new FtpFileEntryNamer();
CompositeEntryListFilter<FTPFile> compositeFtpFileListFilter = new CompositeEntryListFilter<FTPFile>();
if (StringUtils.hasText(this.filenamePattern)) {
PatternMatchingEntryListFilter<FTPFile> ftpFilePatternMatchingEntryListFilter =
new PatternMatchingEntryListFilter<FTPFile>(ftpFileEntryNamer, filenamePattern);
compositeFtpFileListFilter.addFilter(ftpFilePatternMatchingEntryListFilter);
}
if (this.filter != null) {
compositeFtpFileListFilter.addFilter(this.filter);
}
QueuedFtpClientPool queuedFtpClientPool = new QueuedFtpClientPool(15,
defaultClientFactory());
QueuedFtpClientPool queuedFtpClientPool = new QueuedFtpClientPool(15, defaultClientFactory());
FtpInboundRemoteFileSystemSynchronizer ftpRemoteFileSystemSynchronizer = new FtpInboundRemoteFileSystemSynchronizer();
ftpRemoteFileSystemSynchronizer.setClientPool(queuedFtpClientPool);
ftpRemoteFileSystemSynchronizer.setLocalDirectory(this.localDirectoryResource);
ftpRemoteFileSystemSynchronizer.setShouldDeleteSourceFile(ackRemoteDir);
ftpRemoteFileSystemSynchronizer.setFilter(compositeFtpFileListFilter);
ftpRemoteFileSystemSynchronizingMessageSource.setRemotePredicate(compositeFtpFileListFilter);
ftpRemoteFileSystemSynchronizingMessageSource.setSynchronizer(ftpRemoteFileSystemSynchronizer);
ftpRemoteFileSystemSynchronizingMessageSource.setClientPool(queuedFtpClientPool);
ftpRemoteFileSystemSynchronizingMessageSource.setLocalDirectory(this.localDirectoryResource);
ftpRemoteFileSystemSynchronizingMessageSource.setBeanFactory(this.getBeanFactory());
ftpRemoteFileSystemSynchronizingMessageSource.setAutoStartup(true);
ftpRemoteFileSystemSynchronizingMessageSource.afterPropertiesSet();
ftpRemoteFileSystemSynchronizingMessageSource.start();
return ftpRemoteFileSystemSynchronizingMessageSource;
}
@SuppressWarnings("unused")
public void setPort(String port) {
this.port = port;
}
@SuppressWarnings("unused")
public void setAutoCreateDirectories(String autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
@SuppressWarnings("unused")
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
@SuppressWarnings("unused")
public void setUsername(String username) {
this.username = username;
}
@SuppressWarnings("unused")
public void setPassword(String password) {
this.password = password;
}
@SuppressWarnings("unused")
public void setHost(String host) {
this.host = host;
}
@SuppressWarnings("unused")
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
@SuppressWarnings("unused")
public void setLocalWorkingDirectory(String localWorkingDirectory) {
this.localWorkingDirectory = localWorkingDirectory;
}
@SuppressWarnings("unused")
public void setFilter(EntryListFilter<FTPFile> filter) {
this.filter = filter;
}
@SuppressWarnings("unused")
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
}
@SuppressWarnings("unused")
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}

View File

@@ -85,14 +85,12 @@ public class FtpSendingMessageHandler extends AbstractMessageHandler{
}
protected void onInit() throws Exception {
Assert.notNull(ftpClientPool, "'ftpClientPool' must not be null");
Assert.notNull(temporaryBufferFolder,
Assert.notNull(this.ftpClientPool, "'ftpClientPool' must not be null");
Assert.notNull(this.temporaryBufferFolder,
"'temporaryBufferFolder' must not be null");
temporaryBufferFolderFile = this.temporaryBufferFolder.getFile();
this.temporaryBufferFolderFile = this.temporaryBufferFolder.getFile();
}
/* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */
private File handleFileMessage(File sourceFile, File tempFile, File resultFile) throws IOException {
if (sourceFile.renameTo(resultFile)) {
return resultFile;
@@ -108,8 +106,7 @@ public class FtpSendingMessageHandler extends AbstractMessageHandler{
return resultFile;
}
private File handleStringMessage(String content, File tempFile,
File resultFile, String charset) throws IOException {
private File handleStringMessage(String content, File tempFile, File resultFile, String charset) throws IOException {
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile), charset);
FileCopyUtils.copy(content, writer);
tempFile.renameTo(resultFile);
@@ -162,25 +159,21 @@ public class FtpSendingMessageHandler extends AbstractMessageHandler{
Assert.notNull(message, "'message' must not be null");
Object payload = message.getPayload();
Assert.notNull(payload, "Message payload must not be null");
File file = this.redeemForStorableFile(message);
if ((file != null) && file.exists()) {
FTPClient client = null;
boolean sentSuccesfully;
try {
client = getFtpClient();
sentSuccesfully = sendFile(file, client);
} catch (FileNotFoundException e) {
}
catch (FileNotFoundException e) {
throw new MessageDeliveryException(message,
"File [" + file +
"] not found in local working directory; it was moved or deleted unexpectedly",
e);
"File [" + file + "] not found in local working directory; it was moved or deleted unexpectedly", e);
}
catch (IOException e) {
throw new MessageDeliveryException(message,
"Error transferring file [" + file +
"] from local working directory to remote FTP directory", e);
"Error transferring file [" + file + "] from local working directory to remote FTP directory", e);
}
catch (Exception e) {
throw new MessageDeliveryException(message,
@@ -190,8 +183,9 @@ public class FtpSendingMessageHandler extends AbstractMessageHandler{
if (file.exists()) {
try {
file.delete();
} catch (Throwable th) {
/// noop
}
catch (Throwable th) {
// ignore
}
}
if (client != null) {
@@ -199,8 +193,7 @@ public class FtpSendingMessageHandler extends AbstractMessageHandler{
}
}
if (!sentSuccesfully) {
throw new MessageDeliveryException(message,
"Failed to store file '" + file + "'");
throw new MessageDeliveryException(message, "Failed to store file '" + file + "'");
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.springframework.beans.BeansException;
@@ -10,100 +26,106 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.file.FileNameGenerator;
/**
* A factory bean implementation that handles constructing an outbound FTP adapter.
*
* A factory bean implementation that handles constructing an outbound FTP
* adapter.
*
* @author Iwein Fuld
* @author Josh Long
*/
public class FtpSendingMessageHandlerFactoryBean extends AbstractFactoryBean<FtpSendingMessageHandler>
implements ResourceLoaderAware, ApplicationContextAware {
protected int port;
protected String username;
protected String password;
protected String host;
protected String remoteDirectory;
private String charset;
protected int clientMode;
private int fileType;
implements ResourceLoaderAware, ApplicationContextAware {
protected int port;
protected String username;
protected String password;
protected String host;
protected String remoteDirectory;
private String charset;
protected int clientMode;
private int fileType;
private ResourceLoader resourceLoader;
private FileNameGenerator fileNameGenerator;
private ApplicationContext applicationContext;
public void setCharset(String charset) {
this.charset = charset;
}
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
public void setCharset(String charset) {
this.charset = charset;
}
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
this.fileNameGenerator = fileNameGenerator;
}
public void setFileType(int fileType) {
this.fileType = fileType;
}
public void setFileType(int fileType) {
this.fileType = fileType;
}
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
}
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setPort(int port) {
this.port = port;
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Class<?extends FtpSendingMessageHandler> getObjectType() {
return FtpSendingMessageHandler.class;
}
public void setPassword(String password) {
this.password = password;
}
protected AbstractFtpClientFactory clientFactory() {
return ClientFactorySupport.ftpClientFactory(this.host, this.port,
this.remoteDirectory, this.username, this.password,
this.clientMode, this.fileType);
}
public void setHost(String host) {
this.host = host;
}
@Override
protected FtpSendingMessageHandler createInstance()
throws Exception {
// the dependencies for the outbound-adapter are much simpler
// they only require an instance of the pool
AbstractFtpClientFactory defaultFtpClientFactory = clientFactory();
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
QueuedFtpClientPool queuedFtpClientPool = new QueuedFtpClientPool(15,
defaultFtpClientFactory);
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
FtpSendingMessageHandler ftpSendingMessageHandler = new FtpSendingMessageHandler(queuedFtpClientPool);
ftpSendingMessageHandler.setFileNameGenerator(this.fileNameGenerator);
if (this.charset != null) {
ftpSendingMessageHandler.setCharset(this.charset);
}
ftpSendingMessageHandler.afterPropertiesSet();
return ftpSendingMessageHandler;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setPort(int port) {
this.port = port;
}
@Override
public Class<? extends FtpSendingMessageHandler> getObjectType() {
return FtpSendingMessageHandler.class;
}
public void setUsername(String username) {
this.username = username;
}
protected AbstractFtpClientFactory<?> clientFactory() {
return ClientFactorySupport.ftpClientFactory(this.host, this.port,
this.remoteDirectory, this.username, this.password,
this.clientMode, this.fileType);
}
public void setPassword(String password) {
this.password = password;
}
@Override
protected FtpSendingMessageHandler createInstance() throws Exception {
AbstractFtpClientFactory<?> defaultFtpClientFactory = clientFactory();
QueuedFtpClientPool queuedFtpClientPool = new QueuedFtpClientPool(15, defaultFtpClientFactory);
FtpSendingMessageHandler ftpSendingMessageHandler = new FtpSendingMessageHandler(
queuedFtpClientPool);
ftpSendingMessageHandler.setFileNameGenerator(this.fileNameGenerator);
if (this.charset != null) {
ftpSendingMessageHandler.setCharset(this.charset);
}
ftpSendingMessageHandler.afterPropertiesSet();
return ftpSendingMessageHandler;
}
public void setHost(String host) {
this.host = host;
}
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPClient;
@@ -5,14 +21,13 @@ import org.apache.commons.net.ftp.FTPClient;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* Factory to make building the namespace easier
* Factory to make building the namespace easier.
*
* @author Josh Long
*/
public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean
extends FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean {
public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean extends FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean {
/**
* Sets whether the connection is implicit. Local testing reveals this to be a good choice.
*/
@@ -27,20 +42,30 @@ public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean
* "P"
*/
protected volatile String prot;
private KeyManager keyManager;
private TrustManager trustManager;
protected volatile String authValue;
private Boolean sessionCreation;
private Boolean useClientMode;
private Boolean needClientAuth;
private Boolean wantsClientAuth;
private String[] cipherSuites;
public FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean() {
this.defaultFtpInboundFolderName = "ftpsInbound";
this.clientMode = FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE;
}
public void setKeyManager(KeyManager keyManager) {
this.keyManager = keyManager;
}
@@ -81,8 +106,11 @@ public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean
this.wantsClientAuth = wantsClientAuth;
}
protected AbstractFtpClientFactory defaultClientFactory()
throws Exception {
public void setCipherSuites(String[] cipherSuites) {
this.cipherSuites = cipherSuites;
}
protected AbstractFtpClientFactory<?> defaultClientFactory() throws Exception {
DefaultFtpsClientFactory factory = ClientFactorySupport.ftpsClientFactory(this.host,
Integer.parseInt(this.port), this.remoteDirectory,
this.username, this.password, this.fileType, this.clientMode,
@@ -94,7 +122,4 @@ public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean
return factory;
}
public void setCipherSuites(String[] cipherSuites) {
this.cipherSuites = cipherSuites;
}
}

View File

@@ -1,16 +1,30 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* Sends files to a remote FTPS file system. Based heavily on {@link org.springframework.integration.ftp.FtpSendingMessageHandler}
*
* @author Josh Long
* @author Iwein Fuld
*/
public class FtpsSendingMessageHandlerFactoryBean extends FtpSendingMessageHandlerFactoryBean {
/**
@@ -27,16 +41,26 @@ public class FtpsSendingMessageHandlerFactoryBean extends FtpSendingMessageHandl
* "P"
*/
protected volatile String prot;
private KeyManager keyManager;
private TrustManager trustManager;
protected volatile String authValue;
private Boolean sessionCreation;
private Boolean useClientMode;
private Boolean needClientAuth;
private Boolean wantsClientAuth;
private String[] cipherSuites;
private int fileType;
public void setImplicit(Boolean implicit) {
this.implicit = implicit;
}
@@ -86,7 +110,7 @@ public class FtpsSendingMessageHandlerFactoryBean extends FtpSendingMessageHandl
}
@Override
protected AbstractFtpClientFactory clientFactory() {
protected AbstractFtpClientFactory<?> clientFactory() {
DefaultFtpsClientFactory factory = ClientFactorySupport.ftpsClientFactory(this.host,
(this.port), this.remoteDirectory, this.username,
this.password, this.fileType, this.clientMode, this.prot,
@@ -94,7 +118,7 @@ public class FtpsSendingMessageHandlerFactoryBean extends FtpSendingMessageHandl
this.trustManager, this.keyManager, this.sessionCreation,
this.useClientMode, this.wantsClientAuth, this.needClientAuth,
this.cipherSuites);
return factory;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp;
import org.apache.commons.logging.Log;
@@ -25,7 +26,6 @@ import java.net.SocketException;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
/**
* FtpClientPool implementation based on a Queue. This implementation has a
* default pool size of 5, but this is configurable with a constructor argument.
@@ -36,22 +36,28 @@ import java.util.concurrent.ArrayBlockingQueue;
* @author Iwein Fuld
*/
public class QueuedFtpClientPool implements FtpClientPool {
private static final Log log = LogFactory.getLog(QueuedFtpClientPool.class);
private static final int DEFAULT_POOL_SIZE = 5;
private final Queue<FTPClient> pool;
private final FtpClientFactory factory;
public QueuedFtpClientPool(FtpClientFactory factory) {
private static final Log logger = LogFactory.getLog(QueuedFtpClientPool.class);
private static final int DEFAULT_POOL_SIZE = 5;
private final Queue<FTPClient> pool;
private final FtpClientFactory<?> factory;
public QueuedFtpClientPool(FtpClientFactory<?> factory) {
this(DEFAULT_POOL_SIZE, factory);
}
/**
* @param maxPoolSize the maximum size of the pool
*/
public QueuedFtpClientPool(int maxPoolSize, FtpClientFactory factory) {
Assert.notNull(factory);
public QueuedFtpClientPool(int maxPoolSize, FtpClientFactory<?> factory) {
Assert.notNull(factory, "factory must not be null");
this.factory = factory;
pool = new ArrayBlockingQueue<FTPClient>(maxPoolSize);
this.pool = new ArrayBlockingQueue<FTPClient>(maxPoolSize);
}
/**
@@ -65,12 +71,10 @@ public class QueuedFtpClientPool implements FtpClientPool {
* reason large pools are not recommended in poor networking conditions.
*/
public FTPClient getClient() throws SocketException, IOException {
FTPClient client = pool.poll();
FTPClient client = this.pool.poll();
if (client == null) {
client = factory.getClient();
client = this.factory.getClient();
}
return prepareClient(client);
}
@@ -88,8 +92,7 @@ public class QueuedFtpClientPool implements FtpClientPool {
* @throws SocketException
* @throws IOException
*/
protected FTPClient prepareClient(FTPClient client)
throws SocketException, IOException {
protected FTPClient prepareClient(FTPClient client) throws SocketException, IOException {
return isClientAlive(client) ? client : getClient();
}
@@ -98,20 +101,26 @@ public class QueuedFtpClientPool implements FtpClientPool {
if (client.sendNoOp()) {
return true;
}
} catch (IOException e) {
log.warn("Client [" + client + "] discarded: ", e);
}
catch (IOException e) {
if (logger.isWarnEnabled()) {
logger.warn("Client [" + client + "] discarded: ", e);
}
}
return false;
}
public void releaseClient(FTPClient client) {
if ((client != null) && !pool.offer(client)) {
if ((client != null) && !this.pool.offer(client)) {
try {
client.disconnect();
} catch (IOException e) {
log.warn("Error disconnecting ftpclient", e);
}
catch (IOException e) {
if (logger.isWarnEnabled()) {
logger.warn("Error disconnecting ftpclient", e);
}
}
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -8,25 +24,21 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.ftp.FtpSendingMessageHandlerFactoryBean;
import org.w3c.dom.Element;
/**
* Logic for parsing the ftp:outbound-channel-adapter
*
* @author Josh Long
*/
public class FtpMessageSendingConsumerBeanDefinitionParser
extends AbstractOutboundChannelAdapterParser {
public class FtpMessageSendingConsumerBeanDefinitionParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element,
ParserContext parserContext) {
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
FtpSendingMessageHandlerFactoryBean.class.getName());
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,element,"charset");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder,element,"filename-generator", "fileNameGenerator");
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element,
parserContext);
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element, parserContext);
return builder.getBeanDefinition();
}
}

View File

@@ -1,45 +1,58 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp.config;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.ftp.FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean;
import org.w3c.dom.Element;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Logic that configures an ftp:inbound-channel-adapter
* Parser for the FTP inbound-channel-adapter.
*
* @author Josh Long
*/
public class FtpMessageSourceBeanDefinitionParser
extends AbstractPollingInboundChannelAdapterParser {
public class FtpMessageSourceBeanDefinitionParser extends AbstractPollingInboundChannelAdapterParser {
private Set<String> receiveAttrs = new HashSet<String>(Arrays.asList(
"auto-delete-remote-files-on-sync,filename-pattern,local-working-directory".split(
",")));
"auto-delete-remote-files-on-sync,filename-pattern,local-working-directory".split(",")));
@Override
@SuppressWarnings("unused")
protected String parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder,
element, "filter");
for (String a : receiveAttrs)
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,
element, a);
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element,
parserContext);
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(),
parserContext.getRegistry());
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "filter");
for (String a : receiveAttrs) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, a);
}
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element, parserContext);
String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());
return new RuntimeBeanReference(beanName);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010 the original author or authors
* Copyright 2002-2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp.config;
import org.apache.commons.net.ftp.FTP;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;
import java.util.HashMap;
import java.util.Map;
/**
* Provides namespace support for using FTP
* <p/>
@@ -29,9 +29,10 @@ import java.util.Map;
*
* @author Josh Long
*/
@SuppressWarnings("unused")
public class FtpNamespaceHandler extends NamespaceHandlerSupport {
public class FtpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
static public Map<String, Integer> FILE_TYPES = new HashMap<String, Integer>();
static public Map<String, Integer> CLIENT_MODES = new HashMap<String, Integer>();
static {
@@ -47,10 +48,10 @@ public class FtpNamespaceHandler extends NamespaceHandlerSupport {
CLIENT_MODES.put("passive-remote-data-connection-mode", 3);
}
public void init() {
registerBeanDefinitionParser("inbound-channel-adapter",
new FtpMessageSourceBeanDefinitionParser());
registerBeanDefinitionParser("outbound-channel-adapter",
new FtpMessageSendingConsumerBeanDefinitionParser());
registerBeanDefinitionParser("inbound-channel-adapter", new FtpMessageSourceBeanDefinitionParser());
registerBeanDefinitionParser("outbound-channel-adapter", new FtpMessageSendingConsumerBeanDefinitionParser());
}
}

View File

@@ -1,42 +1,52 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.w3c.dom.Element;
/**
* A lot of parsers need to support the same set of core attributes, so I'm hiding that logic here
* General support for parsers in the FTP namespace.
*
* @author Josh Long
*/
public class FtpNamespaceParserSupport {
/**
* lots of values are supported across all adapters, let this code handle it initially
*
* Handles values that are supported across all adapters.
* @param builder a builder
* @param element an element
* @param parserContext a parser context
*/
public static void configureCoreFtpClient(BeanDefinitionBuilder builder,
Element element, ParserContext parserContext) {
for (String p : "auto-create-directories,username,port,password,host,remote-directory".split(
",")) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,
element, p);
public static void configureCoreFtpClient(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
for (String p : "auto-create-directories,username,port,password,host,remote-directory".split(",")) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, p);
}
if (element.hasAttribute("file-type")) {
int fileType = FtpNamespaceHandler.FILE_TYPES.get(element.getAttribute(
"file-type"));
int fileType = FtpNamespaceHandler.FILE_TYPES.get(element.getAttribute("file-type"));
builder.addPropertyValue("fileType", fileType);
}
if (element.hasAttribute("client-mode")) {
int clientMode = FtpNamespaceHandler.CLIENT_MODES.get(element.getAttribute(
"client-mode"));
int clientMode = FtpNamespaceHandler.CLIENT_MODES.get(element.getAttribute("client-mode"));
builder.addPropertyValue("clientMode", clientMode);
}
}
}

View File

@@ -1,32 +1,44 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.ftp.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.ftp.FtpsSendingMessageHandlerFactoryBean;
import org.w3c.dom.Element;
/**
* Logic for parsing the ftp:outbound-channel-adapter
* Parser for the FTPS outbound-channel-adapter
*
* @author Josh Long
*/
public class FtpsMessageSendingConsumerBeanDefinitionParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element,
ParserContext parserContext) {
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
FtpsSendingMessageHandlerFactoryBean.class.getName());
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,element,"charset");
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element,
parserContext);
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element, parserContext);
return builder.getBeanDefinition();
}
}

View File

@@ -1,45 +1,54 @@
package org.springframework.integration.ftp.config;
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.
*/
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.ftp.FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean;
import org.w3c.dom.Element;
package org.springframework.integration.ftp.config;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.ftp.FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean;
/**
* Logic that configures an ftp:inbound-channel-adapter
* Parser for the FTPS inbound-channel-adapter
*
* @author Josh Long
*/
public class FtpsMessageSourceBeanDefinitionParser
extends AbstractPollingInboundChannelAdapterParser {
public class FtpsMessageSourceBeanDefinitionParser extends AbstractPollingInboundChannelAdapterParser {
private Set<String> receiveAttrs = new HashSet<String>(Arrays.asList(
"auto-delete-remote-files-on-sync,filename-pattern,local-working-directory".split(
",")));
"auto-delete-remote-files-on-sync,filename-pattern,local-working-directory".split(",")));
@Override
@SuppressWarnings("unused")
protected String parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder,
element, "filter");
for (String a : receiveAttrs)
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder,
element, a);
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element,
parserContext);
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(),
parserContext.getRegistry());
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "filter");
for (String a : receiveAttrs) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, a);
}
FtpNamespaceParserSupport.configureCoreFtpClient(builder, element, parserContext);
return builder.getBeanDefinition();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010 the original author or authors
* Copyright 2002-2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,25 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp.config;
/**
* Provides namespace support for using FTP
* Provides namespace support for using FTP.
* <p/>
* This is *heavily* influenced by the good work done by Iwein before.
*
* @author Josh Long
*/
@SuppressWarnings("unused")
public class FtpsNamespaceHandler extends FtpNamespaceHandler {
@Override
public void init() {
this.registerBeanDefinitionParser("inbound-channel-adapter",
new FtpsMessageSourceBeanDefinitionParser());
// todo test this
this.registerBeanDefinitionParser("outbound-channel-adapter",
new FtpsMessageSendingConsumerBeanDefinitionParser());
this.registerBeanDefinitionParser("inbound-channel-adapter", new FtpsMessageSourceBeanDefinitionParser());
this.registerBeanDefinitionParser("outbound-channel-adapter", new FtpsMessageSendingConsumerBeanDefinitionParser());
}
}

View File

@@ -1,117 +0,0 @@
package org.springframework.integration.ftp.impl;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import org.springframework.integration.ftp.*;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* Factory to make building the namespace easier
*
* @author Iwein Fuld
* @author Josh Long
*/
public class FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean extends FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean {
/**
* Sets whether the connection is implicit. Local testing reveals this to be a good choice.
*/
protected volatile Boolean implicit = Boolean.FALSE;
/**
* "TLS" or "SSL"
*/
protected volatile String protocol;
/**
* "P"
*/
protected volatile String prot;
private KeyManager keyManager;
private TrustManager trustManager;
protected volatile String authValue;
private Boolean sessionCreation;
private Boolean useClientMode;
private Boolean needClientAuth;
private Boolean wantsClientAuth;
private String[] cipherSuites;
public FtpsRemoteFileSystemSynchronizingMessageSourceFactoryBean() {
this.defaultFtpInboundFolderName = "ftpsInbound";
this.clientMode = FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE;
}
public void setKeyManager(KeyManager keyManager) {
this.keyManager = keyManager;
}
public void setTrustManager(TrustManager trustManager) {
this.trustManager = trustManager;
}
public void setImplicit(Boolean implicit) {
this.implicit = implicit;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public void setProt(String prot) {
this.prot = prot;
}
public void setAuthValue(String authValue) {
this.authValue = authValue;
}
public void setSessionCreation(Boolean sessionCreation) {
this.sessionCreation = sessionCreation;
}
public void setUseClientMode(Boolean useClientMode) {
this.useClientMode = useClientMode;
}
public void setNeedClientAuth(Boolean needClientAuth) {
this.needClientAuth = needClientAuth;
}
public void setWantsClientAuth(Boolean wantsClientAuth) {
this.wantsClientAuth = wantsClientAuth;
}
protected AbstractFtpClientFactory defaultClientFactory()
throws Exception {
DefaultFtpsClientFactory factory = ClientFactorySupport.ftpsClientFactory(this.host, Integer.parseInt(this.port), this.remoteDirectory, this.username, this.password, this.fileType,
this.clientMode, this.prot, this.protocol, this.authValue, this.implicit, this.trustManager, this.keyManager, this.sessionCreation, this.useClientMode, this.wantsClientAuth,
this.needClientAuth, this.cipherSuites);
return factory;
}
public void setCipherSuites(String[] cipherSuites) {
this.cipherSuites = cipherSuites;
}
}

View File

@@ -29,13 +29,10 @@
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd"/>
<xsd:element name="outbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Builds an outbound-channel-adapter that writes files to a remote FTP endpoint.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
@@ -49,7 +46,6 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="username" type="xsd:string" use="required"/>
<xsd:attribute name="remote-directory" type="xsd:string" use="required"/>
<xsd:attribute name="host" type="xsd:string" use="required"/>
@@ -59,7 +55,7 @@
<xsd:attribute name="filename-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to specify a reference to
Allows you to specify a reference to
[org.springframework.integration.file.FileNameGenerator] implementation.
</xsd:documentation>
<xsd:appinfo>
@@ -71,10 +67,9 @@
</xsd:attribute>
<xsd:attribute use="optional" name="client-mode" default="active-local-data-connection-mode">
<xsd:annotation>
<xsd:documentation><![CDATA[
the FTP Client-Mode.
/***
<xsd:documentation><![CDATA[the FTP Client-Mode
/***
* A constant indicating the FTP session is expecting all transfers
* to occur between the client (local) and server and that the server
* should connect to the client's data port to initiate a data transfer.
@@ -107,8 +102,6 @@
* transfer.
***/
PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -120,13 +113,10 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute use="optional" name="file-type" default="binary-file-type">
<xsd:attribute name="file-type" default="binary-file-type" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Binary, ASCII, or EBDIC. Binary's a good default
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -137,17 +127,13 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="inbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Builds an inbound-channel-adapter that synchronizes a local directory with the contents of a remote FTP endpoint.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
@@ -164,12 +150,10 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute use="optional" name="file-type" default="binary-file-type">
<xsd:attribute name="file-type" default="binary-file-type" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Binary, ASCII, or EBDIC. Binary's a good default
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -190,24 +174,19 @@
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filename-pattern" type="xsd:string"/>
<xsd:attribute name="local-working-directory" type="xsd:string"/>
<xsd:attribute name="auto-create-directories" type="xsd:boolean"/>
<xsd:attribute name="auto-delete-remote-files-on-sync" type="xsd:boolean"/>
<xsd:attribute name="username" type="xsd:string" use="required"/>
<xsd:attribute name="remote-directory" type="xsd:string" use="required"/>
<xsd:attribute name="host" type="xsd:string" use="required"/>
<xsd:attribute name="password" type="xsd:string"/>
<xsd:attribute name="port" type="xsd:int" default="22"/>
<xsd:attribute use="optional" name="client-mode" default="active-local-data-connection-mode">
<xsd:attribute name="client-mode" default="active-local-data-connection-mode" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
the FTP Client-Mode.
/***
<xsd:documentation><![CDATA[the FTP Client-Mode
/***
* A constant indicating the FTP session is expecting all transfers
* to occur between the client (local) and server and that the server
* should connect to the client's data port to initiate a data transfer.
@@ -240,8 +219,6 @@
* transfer.
***/
PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -250,13 +227,10 @@
<xsd:enumeration value="active-remote-data-connection-mode"/>
<xsd:enumeration value="passive-local-data-connection-mode"/>
<xsd:enumeration value="passive-remote-data-connection-mode"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -29,13 +29,10 @@
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd"/>
<xsd:element name="outbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Builds an outbound-channel-adapter that writes files to a remote FTPS endpoint.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
@@ -49,8 +46,6 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="username" type="xsd:string" use="required"/>
<xsd:attribute name="remote-directory" type="xsd:string" use="required"/>
<xsd:attribute name="host" type="xsd:string" use="required"/>
@@ -70,10 +65,10 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute use="optional" name="client-mode" default="active-local-data-connection-mode">
<xsd:attribute name="client-mode" default="active-local-data-connection-mode" use="optional" >
<xsd:annotation>
<xsd:documentation><![CDATA[
the FTP Client-Mode.
the FTP Client-Mode
/***
* A constant indicating the FTP session is expecting all transfers
@@ -109,7 +104,6 @@
***/
PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -121,14 +115,11 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute use="optional" name="file-type" default="binary-file-type">
<xsd:attribute name="file-type" default="binary-file-type" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Binary, ASCII, or EBDIC. Binary's a good default
]]></xsd:documentation>
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
@@ -138,26 +129,19 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="inbound-channel-adapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Builds an inbound-channel-adapter that synchronizes a local directory with the contents of a remote FTP endpoint.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="channel" use="required" type="xsd:string">
<xsd:annotation>
@@ -171,10 +155,8 @@
<xsd:attribute use="optional" name="file-type" default="binary-file-type">
<xsd:annotation>
<xsd:documentation><![CDATA[
Binary, ASCII, or EBDIC. Binary's a good default
]]></xsd:documentation>
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
@@ -194,13 +176,9 @@
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filename-pattern" type="xsd:string"/>
<xsd:attribute name="local-working-directory" type="xsd:string"/>
<xsd:attribute name="auto-create-directories" type="xsd:boolean"/>
<xsd:attribute name="auto-delete-remote-files-on-sync" type="xsd:boolean"/>
<xsd:attribute name="username" type="xsd:string" use="required"/>
<xsd:attribute name="remote-directory" type="xsd:string" use="required"/>
<xsd:attribute name="host" type="xsd:string" use="required"/>
@@ -244,8 +222,6 @@
* transfer.
***/
PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -254,7 +230,6 @@
<xsd:enumeration value="active-remote-data-connection-mode"/>
<xsd:enumeration value="passive-local-data-connection-mode"/>
<xsd:enumeration value="passive-remote-data-connection-mode"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
@@ -262,5 +237,4 @@
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -4,8 +4,5 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
log4j.category.org.springframework=WARN
# log4j.category.org.springframework.integration=DEBUG
# log4j.category.org.springframework.integration.jdbc=DEBUG
log4j.category.org.springframework.ftp=DEBUG

View File

@@ -27,6 +27,7 @@ import java.util.Map;
import javax.xml.transform.Source;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
@@ -47,7 +48,6 @@ import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.util.SimpleBeanResolver;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
@@ -202,7 +202,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
super.onInit();
BeanFactory beanFactory = this.getBeanFactory();
if (beanFactory != null) {
this.evaluationContext.setBeanResolver(new SimpleBeanResolver(beanFactory));
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
ConversionService conversionService = this.getConversionService();
if (conversionService != null) {

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.ip.util;
import static org.junit.Assert.assertEquals;
import static org.springframework.integration.ip.util.RegexUtils.escapeRegExSpecials;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.integration.MessageHeaders;
@@ -41,6 +42,7 @@ public class RegexUtilsTests {
* And one of the ones we are actually using
*/
@Test
@Ignore // no longer relevant due to INT-1568
public void testSiPrefix () {
// protect the test in case we ever change the prefix
if ("$^[]{()}+*\\?|.".contains(MessageHeaders.PREFIX)) {

View File

@@ -16,14 +16,15 @@
package org.springframework.integration.jdbc.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for {@link org.springframework.integration.jdbc.JdbcPollingChannelAdapter}.
@@ -42,7 +43,7 @@ public class JdbcPollingChannelAdapterParser extends AbstractPollingInboundChann
}
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("org.springframework.integration.jdbc.JdbcPollingChannelAdapter");
@@ -75,8 +76,7 @@ public class JdbcPollingChannelAdapterParser extends AbstractPollingInboundChann
builder.addPropertyValue("updateSql", update);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "update-per-row");
return BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());
return builder.getBeanDefinition();
}
}

View File

@@ -77,7 +77,7 @@ public class JdbcMessageHandlerIntegrationTests {
@Test
public void testIdHeaderDynamicInsert() {
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate.getJdbcOperations(), "insert into foos (id, status, name) values (:headers[$id], 0, :payload)");
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate.getJdbcOperations(), "insert into foos (id, status, name) values (:headers[id], 0, :payload)");
Message<String> message = new GenericMessage<String>("foo");
handler.handleMessage(message);
String id = message.getHeaders().getId().toString();

View File

@@ -37,10 +37,10 @@ public class JdbcMessageHandlerParserTests {
@Test
public void testDollarHeaderOutboundChannelAdapter(){
setUp("handlingDollarHeaderJdbcOutboundChannelAdapterTest.xml", getClass());
Message<?> message = MessageBuilder.withPayload("foo").build();
Message<?> message = MessageBuilder.withPayload("foo").setHeader("$foo_id", "abc").build();
channel.send(message);
Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * from FOOS");
assertEquals("Wrong id", message.getHeaders().getId().toString(), map.get("ID"));
assertEquals("Wrong id", message.getHeaders().get("$foo_id").toString(), map.get("ID"));
assertEquals("Wrong id", "foo", map.get("name"));
}

View File

@@ -9,7 +9,7 @@
http://www.springframework.org/schema/integration/jdbc
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[$id], 0, :payload)"
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[$foo_id], 0, :payload)"
channel="target" data-source="dataSource"/>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />

View File

@@ -9,7 +9,7 @@
http://www.springframework.org/schema/integration/jdbc
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[$id], 0, :payload[foo])"
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[id], 0, :payload[foo])"
channel="target" data-source="dataSource"/>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />

View File

@@ -13,7 +13,7 @@
<si:queue />
</si:channel>
<outbound-gateway query="select * from foos where id=:headers[$id]" update="insert into foos (id, status, name) values (:headers[$id], 0, :payload[foo])"
<outbound-gateway query="select * from foos where id=:headers[id]" update="insert into foos (id, status, name) values (:headers[id], 0, :payload[foo])"
request-channel="target" reply-channel="output" data-source="dataSource" />
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />

View File

@@ -10,7 +10,7 @@
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<outbound-channel-adapter channel="target" data-source="dataSource">
<query>insert into foos (id, status, name) values (:headers[$id], 0, :payload[foo])</query>
<query>insert into foos (id, status, name) values (:headers[id], 0, :payload[foo])</query>
</outbound-channel-adapter>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />

View File

@@ -9,7 +9,7 @@
http://www.springframework.org/schema/integration/jdbc
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[$id], 0, :foo)"
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[id], 0, :foo)"
channel="target" data-source="dataSource" sql-parameter-source-factory="sqlParameterSourceFactory"/>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.jms.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -43,7 +44,7 @@ public class JmsInboundChannelAdapterParser extends AbstractPollingInboundChanne
}
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.jms.JmsDestinationPollingSource");
String componentName = this.resolveId(element, builder.getBeanDefinition(), parserContext);
@@ -88,9 +89,7 @@ public class JmsInboundChannelAdapterParser extends AbstractPollingInboundChanne
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "selector", "messageSelector");
BeanDefinition beanDefinition = builder.getBeanDefinition();
String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, parserContext.getRegistry());
BeanComponentDefinition component = new BeanComponentDefinition(beanDefinition, beanName);
parserContext.registerBeanComponent(component);
return beanName;
return new BeanComponentDefinition(beanDefinition, beanName);
}
}

View File

@@ -18,8 +18,8 @@ package org.springframework.integration.jmx.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
@@ -36,14 +36,13 @@ public class AttributePollingChannelAdapterParser extends AbstractPollingInbound
}
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(
"org.springframework.integration.jmx.AttributePollingMessageSource");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server", "server");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "attribute-name");
return BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());
return builder.getBeanDefinition();
}
}

View File

@@ -18,9 +18,9 @@ package org.springframework.integration.mail.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
@@ -28,8 +28,7 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Parser for the &lt;inbound-channel-adapter&gt; element of Spring
* Integration's 'mail' namespace.
* Parser for the &lt;inbound-channel-adapter&gt; element of Spring Integration's 'mail' namespace.
*
* @author Jonas Partner
* @author Mark Fisher
@@ -41,12 +40,11 @@ public class MailInboundChannelAdapterParser extends AbstractPollingInboundChann
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
BASE_PACKAGE + ".MailReceivingMessageSource");
builder.addConstructorArgValue(this.parseMailReceiver(element, parserContext));
return BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());
return builder.getBeanDefinition();
}
private BeanDefinition parseMailReceiver(Element element, ParserContext parserContext) {
@@ -85,7 +83,6 @@ public class MailInboundChannelAdapterParser extends AbstractPollingInboundChann
if (StringUtils.hasText(markAsRead)){
receiverBuilder.addPropertyValue("shouldMarkMessagesAsRead", markAsRead);
}
return receiverBuilder.getBeanDefinition();
}

View File

@@ -13,27 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import org.springframework.beans.factory.InitializingBean;
package org.springframework.integration.sftp;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* This approach - of having a SessionPool ({@link SftpSessionPool}) that has an
* implementation of Queued*SessionPool ({@link QueuedSftpSessionPool}) - was
* taken pretty directly from the incredibly good Spring IntegrationFTP adapter.
* taken almost directly from the Spring Integration FTP adapter.
*
* @author Josh Long
* @since 2.0
*/
public class QueuedSftpSessionPool implements SftpSessionPool, InitializingBean {
public static final int DEFAULT_POOL_SIZE = 10;
private Queue<SftpSession> queue;
private volatile Queue<SftpSession> queue;
private final SftpSessionFactory sftpSessionFactory;
private int maxPoolSize;
private final int maxPoolSize;
public QueuedSftpSessionPool(SftpSessionFactory factory) {
this(DEFAULT_POOL_SIZE, factory);
@@ -44,34 +51,32 @@ public class QueuedSftpSessionPool implements SftpSessionPool, InitializingBean
this.maxPoolSize = maxPoolSize;
}
public void afterPropertiesSet() throws Exception {
assert maxPoolSize > 0 : "poolSize must be greater than 0!";
queue = new ArrayBlockingQueue<SftpSession>(maxPoolSize, true); // size, faireness to avoid starvation
assert sftpSessionFactory != null : "sftpSessionFactory must not be null!";
Assert.notNull(this.sftpSessionFactory, "sftpSessionFactory must not be null");
Assert.isTrue(this.maxPoolSize > 0, "poolSize must be greater than 0");
this.queue = new ArrayBlockingQueue<SftpSession>(this.maxPoolSize, true); // size, fairness to avoid starvation
}
public SftpSession getSession() throws Exception {
SftpSession session = this.queue.poll();
if (null == session) {
session = this.sftpSessionFactory.getObject();
if (queue.size() < maxPoolSize) {
queue.add(session);
if (this.queue.size() < this.maxPoolSize) {
this.queue.add(session);
}
}
if (null == session) {
session = queue.poll();
}
return session;
}
public void release(SftpSession session) {
if (queue.size() < maxPoolSize) {
queue.add(session); // somehow one snuck in before <code>session</code> was finished!
} else {
}
else {
dispose(session);
}
}
@@ -80,18 +85,16 @@ public class QueuedSftpSessionPool implements SftpSessionPool, InitializingBean
if (s == null) {
return;
}
if (queue.contains(s)) //this should never happen, but if it does ...
{
if (queue.contains(s)) {
//this should never happen, but if it does...
queue.remove(s);
}
if ((s.getChannel() != null) && s.getChannel().isConnected()) {
s.getChannel().disconnect();
}
if (s.getSession().isConnected()) {
s.getSession().disconnect();
}
}
}

View File

@@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import org.springframework.integration.file.entries.EntryNamer;
import com.jcraft.jsch.ChannelSftp;
/**
* Knows how to name a {@link com.jcraft.jsch.ChannelSftp.LsEntry} instance
* Stratgy for naming a {@link com.jcraft.jsch.ChannelSftp.LsEntry} instance.
*
* @author Josh Long
*/
@@ -28,4 +30,5 @@ public class SftpEntryNamer implements EntryNamer<ChannelSftp.LsEntry> {
public String nameOf(ChannelSftp.LsEntry entry) {
return entry.getFilename();
}
}

View File

@@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
package org.springframework.integration.sftp;
/**
* @author Josh Long
* @since 2.0
*/
public class SftpConstants {
public static final String SFTP_REMOTE_DIRECTORY_HEADER = "SFTP_REMOTE_DIRECTORY_HEADER";
public abstract class SftpHeaders {
public static final String REMOTE_DIRECTORY = "sftp_remoteDirectory";
}

View File

@@ -34,6 +34,7 @@ import org.springframework.core.io.Resource;
import org.springframework.integration.Message;
import org.springframework.integration.MessageDeliveryException;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
@@ -43,9 +44,8 @@ import org.springframework.util.FileCopyUtils;
import com.jcraft.jsch.ChannelSftp;
/**
* Sending a message payload to a remote SFTP endpoint. For now, we assume that the payload of the inbound message is of
* type {@link java.io.File}. Perhaps we could support a payload of java.io.InputStream with a Header designating the file
* name?
* Sends message payloads to a remote SFTP endpoint.
* Assumes that the payload of the inbound message is of type {@link java.io.File}.
*
* @author Josh Long
* @since 2.0
@@ -65,7 +65,7 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
private volatile Resource temporaryBufferFolder = new FileSystemResource(SystemUtils.getJavaIoTmpDir());
private volatile boolean afterPropertiesSetRan;
private volatile boolean initialized;
private volatile String charset = Charset.defaultCharset().name();
@@ -88,7 +88,7 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
}
public String getRemoteDirectory() {
return remoteDirectory;
return this.remoteDirectory;
}
public void setCharset(String charset) {
@@ -96,19 +96,16 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
}
public void afterPropertiesSet() throws Exception {
Assert.state(this.pool != null, "the pool can't be null!");
temporaryBufferFolderFile = this.temporaryBufferFolder.getFile();
if (!afterPropertiesSetRan) {
Assert.notNull(this.pool, "the pool must not be null");
this.temporaryBufferFolderFile = this.temporaryBufferFolder.getFile();
if (!this.initialized) {
if (StringUtils.isEmpty(this.remoteDirectory)) {
remoteDirectory = null;
this.remoteDirectory = null;
}
this.afterPropertiesSetRan = true;
this.initialized = true;
}
}
/* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */
private File handleFileMessage(File sourceFile, File tempFile, File resultFile) throws IOException {
if (sourceFile.renameTo(resultFile)) {
return resultFile;
@@ -131,13 +128,13 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
return resultFile;
}
private File redeemForStorableFile(Message<?> msg) throws MessageDeliveryException {
private File redeemForStorableFile(Message<?> message) throws MessageDeliveryException {
try {
Object payload = msg.getPayload();
String generateFileName = this.fileNameGenerator.generateFileName(msg);
File tempFile = new File(temporaryBufferFolderFile, generateFileName + TEMPORARY_FILE_SUFFIX);
File resultFile = new File(temporaryBufferFolderFile, generateFileName);
File sendableFile;
Object payload = message.getPayload();
String generateFileName = this.fileNameGenerator.generateFileName(message);
File tempFile = new File(this.temporaryBufferFolderFile, generateFileName + TEMPORARY_FILE_SUFFIX);
File resultFile = new File(this.temporaryBufferFolderFile, generateFileName);
File sendableFile = null;
if (payload instanceof String) {
sendableFile = this.handleStringMessage((String) payload, tempFile, resultFile, this.charset);
}
@@ -147,30 +144,23 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
else if (payload instanceof byte[]) {
sendableFile = this.handleByteArrayMessage((byte[]) payload, tempFile, resultFile);
}
else {
sendableFile = null;
}
return sendableFile;
}
catch (Throwable th) {
throw new MessageDeliveryException(msg);
throw new MessageDeliveryException(message);
}
}
/* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */
public void handleMessage(final Message<?> message) {
Assert.state(this.pool != null, "need a working pool");
Assert.notNull(this.pool, "the pool must not be null");
File inboundFilePayload = this.redeemForStorableFile(message);
try {
if ((inboundFilePayload != null) && inboundFilePayload.exists()) {
sendFileToRemoteEndpoint(message, inboundFilePayload);
}
}
catch (Throwable thr) {
// logger.debug("recieved an exception.", thr);
throw new MessageDeliveryException(message, "couldn't deliver the message!", thr);
catch (Exception e) {
throw new MessageDeliveryException(message, "failed to deliver the message", e);
}
finally {
if (inboundFilePayload != null && inboundFilePayload.exists())
@@ -178,11 +168,11 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
}
}
private boolean sendFileToRemoteEndpoint(Message<?> message, File file) throws Throwable {
assert this.pool != null : "need a working pool";
private boolean sendFileToRemoteEndpoint(Message<?> message, File file) throws Exception {
Assert.notNull(this.pool, "pool must not be null");
SftpSession session = this.pool.getSession();
if (session == null) {
throw new RuntimeException("the session returned from the pool is null, can't possibly proceed.");
throw new MessagingException("The session returned from the pool is null, cannot proceed.");
}
session.start();
ChannelSftp sftp = session.getChannel();
@@ -190,13 +180,12 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
try {
fileInputStream = new FileInputStream(file);
String baseOfRemotePath = StringUtils.isEmpty(this.remoteDirectory) ? StringUtils.EMPTY : remoteDirectory; // the safe default
// logger.debug("going to send " + file.getAbsolutePath() + " to a remote sftp endpoint");
String dynRd = null;
MessageHeaders messageHeaders = null;
if (message != null) {
messageHeaders = message.getHeaders();
if ((messageHeaders != null) && messageHeaders.containsKey(SftpConstants.SFTP_REMOTE_DIRECTORY_HEADER)) {
dynRd = (String) messageHeaders.get(SftpConstants.SFTP_REMOTE_DIRECTORY_HEADER);
if ((messageHeaders != null) && messageHeaders.containsKey(SftpHeaders.REMOTE_DIRECTORY)) {
dynRd = (String) messageHeaders.get(SftpHeaders.REMOTE_DIRECTORY);
if (!StringUtils.isEmpty(dynRd)) {
baseOfRemotePath = dynRd;
}
@@ -210,8 +199,8 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe
}
finally {
IOUtils.closeQuietly(fileInputStream);
if (pool != null) {
pool.release(session);
if (this.pool != null) {
this.pool.release(session);
}
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
@@ -23,9 +24,7 @@ import org.apache.commons.lang.StringUtils;
import java.io.InputStream;
/**
* c
* There are many ways to create a {@link SftpSession} just as there are many ways to SSH into a remote system.
* You may use a username and password, you may use a username and private key, you may use a username and a private key with a password, etc.
* <p/>
@@ -36,10 +35,15 @@ import java.io.InputStream;
* @author Mario Gray
*/
public class SftpSession {
private volatile ChannelSftp channel;
private volatile Session session;
private String privateKey;
private String privateKeyPassphrase;
private volatile UserInfo userInfo;
@@ -66,44 +70,41 @@ public class SftpSession {
* to surmount that, we need the private key passphrase. Specify that here.
* @throws Exception thrown if any of a myriad of scenarios plays out
*/
public SftpSession(String userName, String hostName, String userPassword, int port, String knownHostsFile, InputStream knownHostsInputStream, String privateKey, String pvKeyPassPhrase)
throws Exception {
JSch jSch = new JSch();
public SftpSession(String userName, String hostName, String userPassword, int port, String knownHostsFile,
InputStream knownHostsInputStream, String privateKey, String pvKeyPassPhrase) throws Exception {
JSch jSch = new JSch();
if (port <= 0) {
port = 22;
}
this.privateKey = privateKey;
this.privateKeyPassphrase = pvKeyPassPhrase;
if (!StringUtils.isEmpty(knownHostsFile)) {
jSch.setKnownHosts(knownHostsFile);
} else if (null != knownHostsInputStream) {
}
else if (null != knownHostsInputStream) {
jSch.setKnownHosts(knownHostsInputStream);
}
// private key
if (!StringUtils.isEmpty(this.privateKey)) {
if (!StringUtils.isEmpty(privateKeyPassphrase)) {
jSch.addIdentity(this.privateKey, privateKeyPassphrase);
} else {
}
else {
jSch.addIdentity(this.privateKey);
}
}
session = jSch.getSession(userName, hostName, port);
this.session = jSch.getSession(userName, hostName, port);
if (!StringUtils.isEmpty(userPassword)) {
session.setPassword(userPassword);
this.session.setPassword(userPassword);
}
userInfo = new OptimisticUserInfoImpl(userPassword);
session.setUserInfo(userInfo);
session.connect();
channel = (ChannelSftp) session.openChannel("sftp");
this.userInfo = new OptimisticUserInfoImpl(userPassword);
this.session.setUserInfo(userInfo);
this.session.connect();
this.channel = (ChannelSftp) this.session.openChannel("sftp");
}
public ChannelSftp getChannel() {
return channel;
}
@@ -118,15 +119,17 @@ public class SftpSession {
}
}
/**
* this is a simple, optimistic implementation of this interface. It simply returns in the positive where possible
* and handles interactive authentication (ie, 'Please enter your password: ' prompts are dispatched automatically using this)
*/
private static class OptimisticUserInfoImpl implements UserInfo {
private String pw;
private String password;
public OptimisticUserInfoImpl(String password) {
this.pw = password;
this.password = password;
}
public String getPassphrase() {
@@ -134,7 +137,7 @@ public class SftpSession {
}
public String getPassword() {
return pw;
return password;
}
public boolean promptPassphrase(String string) {
@@ -152,4 +155,5 @@ public class SftpSession {
public void showMessage(String string) {
}
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import org.springframework.beans.factory.FactoryBean;
@@ -20,43 +21,30 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factories {@link SftpSession} instances. There are lots of ways to construct a
* {@link SftpSession} instance, and not all of them are obvious. This factory
* does its best to make it work.
* Factory for creating {@link SftpSession} instances. There are lots of ways to construct a
* {@link SftpSession} instance, and not all of them are obvious. This factory should help.
*
* @author Josh Long
* @author Mario Gray
*/
public class SftpSessionFactory implements FactoryBean<SftpSession>, InitializingBean {
private volatile String knownHosts;
private volatile String password;
private volatile String privateKey;
private volatile String privateKeyPassphrase;
private volatile String remoteHost;
private volatile String user;
private volatile int port = 22; // the default
public void afterPropertiesSet() throws Exception {
Assert.hasText(this.remoteHost, "remoteHost can't be empty!");
Assert.hasText(this.user, "user can't be empty!");
Assert.state(StringUtils.hasText(this.password) || StringUtils.hasText(this.privateKey) || StringUtils.hasText(this.privateKeyPassphrase),
"you must configure either a password or a private key and/or a private key passphrase!");
Assert.state(this.port >= 0, "port must be a valid number! ");
}
public SftpSession getObject() throws Exception {
return new SftpSession(this.user, this.remoteHost, this.password, this.port, this.knownHosts, null, this.privateKey, this.privateKeyPassphrase);
}
public Class<? extends SftpSession> getObjectType() {
return SftpSession.class;
}
public boolean isSingleton() {
return false;
}
public void setKnownHosts(String knownHosts) {
this.knownHosts = knownHosts;
@@ -85,4 +73,25 @@ public class SftpSessionFactory implements FactoryBean<SftpSession>, Initializin
public void setUser(String user) {
this.user = user;
}
public void afterPropertiesSet() throws Exception {
Assert.hasText(this.remoteHost, "remoteHost must not be empty");
Assert.hasText(this.user, "user mut not be empty");
Assert.state(StringUtils.hasText(this.password) || StringUtils.hasText(this.privateKey) || StringUtils.hasText(this.privateKeyPassphrase),
"either a password or a private key and/or a private key passphrase is required");
Assert.state(this.port >= 0, "port must be a positive number");
}
public SftpSession getObject() throws Exception {
return new SftpSession(this.user, this.remoteHost, this.password, this.port, this.knownHosts, null, this.privateKey, this.privateKeyPassphrase);
}
public Class<? extends SftpSession> getObjectType() {
return SftpSession.class;
}
public boolean isSingleton() {
return false;
}
}

View File

@@ -13,29 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
/**
* Holds instances of {@link SftpSession} since they're stateful
* and might be in use while another run happens.
* Holds instances of {@link SftpSession} since they are stateful
* and might be in use while another operation runs.
*
* @author Josh Long
*/
public interface SftpSessionPool {
/**
* this returns a session that can be used to connct to an sftp instance and perform operations
* Returns a session that can be used to connect to an sftp instance and perform operations
*
* @return the session from the pool ready to be connected to.
* @throws Exception thrown if theres any of the numerous faults possible when trying to connect to the remote
* server
* @throws Exception if any fault occurs when trying to connect to the remote server
*/
SftpSession getSession() throws Exception;
/**
* Frees up the client. Im not sure what the meaningful semantics of this are. Perhaps it just calls <code>(session
* ,channel).disconnect()</code> ?
*
* Releases the session.
*
* @param session the session to relinquish / renew
*/
void release(SftpSession session);
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp.config;
import org.springframework.beans.factory.FactoryBean;
@@ -20,46 +21,32 @@ import org.springframework.integration.sftp.QueuedSftpSessionPool;
import org.springframework.integration.sftp.SftpSendingMessageHandler;
import org.springframework.integration.sftp.SftpSessionFactory;
/**
* Supports the construction of a MessagHandler that knows how to take inbound #java.io.File objects and send them to a
* remote destination.
* Supports the construction of a MessagHandler that knows how to take inbound File objects
* and send them to a remote destination.
*
* @author Josh Long
*/
public class SftpMessageSendingConsumerFactoryBean implements FactoryBean<SftpSendingMessageHandler> {
private String host;
private String keyFile;
private String keyFilePassword;
private String password;
private String remoteDirectory;
private String username;
private boolean autoCreateDirectories;
private int port;
private String charset;
public SftpSendingMessageHandler getObject() throws Exception {
SftpSessionFactory sessionFactory = SftpSessionUtils.buildSftpSessionFactory(
this.host, this.password, this.username, this.keyFile, this.keyFilePassword, this.port);
QueuedSftpSessionPool queuedSFTPSessionPool = new QueuedSftpSessionPool(15, sessionFactory);
queuedSFTPSessionPool.afterPropertiesSet();
SftpSendingMessageHandler sftpSendingMessageHandler = new SftpSendingMessageHandler(queuedSFTPSessionPool);
sftpSendingMessageHandler.setRemoteDirectory(this.remoteDirectory);
sftpSendingMessageHandler.setCharset(this.charset);
sftpSendingMessageHandler.afterPropertiesSet();
return sftpSendingMessageHandler;
}
public Class<? extends SftpSendingMessageHandler> getObjectType() {
return SftpSendingMessageHandler.class;
}
public boolean isSingleton() {
return false;
}
public void setAutoCreateDirectories(final boolean autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
@@ -96,4 +83,25 @@ public class SftpMessageSendingConsumerFactoryBean implements FactoryBean<SftpSe
public void setUsername(final String username) {
this.username = username;
}
public SftpSendingMessageHandler getObject() throws Exception {
SftpSessionFactory sessionFactory = SftpSessionUtils.buildSftpSessionFactory(
this.host, this.password, this.username, this.keyFile, this.keyFilePassword, this.port);
QueuedSftpSessionPool queuedSFTPSessionPool = new QueuedSftpSessionPool(15, sessionFactory);
queuedSFTPSessionPool.afterPropertiesSet();
SftpSendingMessageHandler sftpSendingMessageHandler = new SftpSendingMessageHandler(queuedSFTPSessionPool);
sftpSendingMessageHandler.setRemoteDirectory(this.remoteDirectory);
sftpSendingMessageHandler.setCharset(this.charset);
sftpSendingMessageHandler.afterPropertiesSet();
return sftpSendingMessageHandler;
}
public Class<? extends SftpSendingMessageHandler> getObjectType() {
return SftpSendingMessageHandler.class;
}
public boolean isSingleton() {
return false;
}
}

View File

@@ -13,66 +13,63 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.w3c.dom.Element;
/**
* Provides namespace support for using SFTP.
* This is very largely based on the FTP support by Iwein Fuld.
* This is largely based on the FTP support by Iwein Fuld.
*
* @author Josh Long
*/
@SuppressWarnings("unused")
public class SftpNamespaceHandler extends NamespaceHandlerSupport {
public class SftpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
public void init() {
registerBeanDefinitionParser("inbound-channel-adapter", new SFTPMessageSourceBeanDefinitionParser());
registerBeanDefinitionParser("outbound-channel-adapter", new SFTPMessageSendingConsumerBeanDefinitionParser());
}
/**
* Configures an object that can take inbound messages and send them.
*/
private static class SFTPMessageSendingConsumerBeanDefinitionParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SftpMessageSendingConsumerFactoryBean.class.getName());
for (String p : "auto-create-directories,username,password,host,port,key-file,key-file-password,remote-directory,charset".split(",")) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, p);
}
return builder.getBeanDefinition();
}
}
/**
* Configures an object that can recieve files from a remote SFTP endpoint and broadcast their arrival to the
* consumer
* Configures an object that can receive files from a remote SFTP endpoint and broadcast their arrival to the consumer.
*/
private static class SFTPMessageSourceBeanDefinitionParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected String parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "filter");
for (String p : "filename-pattern,auto-create-directories,username,password,host,key-file,key-file-password,remote-directory,local-directory-path,auto-delete-remote-files-on-sync".split(",")) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, p);
}
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
return builder.getBeanDefinition();
}
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp.config;
import com.jcraft.jsch.ChannelSftp;
@@ -34,75 +35,79 @@ import org.springframework.util.StringUtils;
import java.io.File;
/**
* Factory bean to hide the fairly complex configuration possibilities for an SFTP endpoint
*
* @author Josh Long
*/
public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends AbstractFactoryBean<SftpInboundRemoteFileSystemSynchronizingMessageSource> implements ResourceLoaderAware {
public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean
extends AbstractFactoryBean<SftpInboundRemoteFileSystemSynchronizingMessageSource> implements ResourceLoaderAware {
private volatile ResourceLoader resourceLoader;
private volatile Resource localDirectoryResource;
private volatile String localDirectoryPath;
private volatile String autoCreateDirectories;
private volatile String autoDeleteRemoteFilesOnSync;
private volatile String filenamePattern;
private volatile EntryListFilter<ChannelSftp.LsEntry> filter;
private int port = 22;
private String host;
private String keyFile;
private String keyFilePassword;
private String remoteDirectory;
private String username;
private String password;
@SuppressWarnings("unused")
public void setLocalDirectoryResource(Resource localDirectoryResource) {
this.localDirectoryResource = localDirectoryResource;
}
@SuppressWarnings("unused")
public void setLocalDirectoryPath(String localDirectoryPath) {
this.localDirectoryPath = localDirectoryPath;
}
@SuppressWarnings("unused")
public void setAutoCreateDirectories(String autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
@SuppressWarnings("unused")
public void setAutoDeleteRemoteFilesOnSync(String autoDeleteRemoteFilesOnSync) {
this.autoDeleteRemoteFilesOnSync = autoDeleteRemoteFilesOnSync;
}
@SuppressWarnings("unused")
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
@SuppressWarnings("unused")
public void setFilter(EntryListFilter<ChannelSftp.LsEntry> filter) {
this.filter = filter;
}
@SuppressWarnings("unused")
public void setPort(int port) {
this.port = port;
}
@SuppressWarnings("unused")
public void setHost(String host) {
this.host = host;
}
@SuppressWarnings("unused")
public void setKeyFile(String keyFile) {
this.keyFile = keyFile;
}
@SuppressWarnings("unused")
public void setKeyFilePassword(String keyFilePassword) {
this.keyFilePassword = keyFilePassword;
}
@@ -110,7 +115,6 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
/**
* Set the remote directory to synchronize with
*/
@SuppressWarnings("unused")
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
@@ -118,7 +122,6 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
/**
* Set the user name to be used for authentication with the remote server
*/
@SuppressWarnings("unused")
public void setUsername(String username) {
this.username = username;
}
@@ -127,7 +130,6 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
* Set the password to be used for authentication with the remote server
* @param password
*/
@SuppressWarnings("unused")
public void setPassword(String password) {
this.password = password;
}
@@ -152,11 +154,9 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
* @return Fully configured SftpInboundRemoteFileSystemSynchronizingMessageSource
*/
@Override
protected SftpInboundRemoteFileSystemSynchronizingMessageSource createInstance()
throws Exception {
protected SftpInboundRemoteFileSystemSynchronizingMessageSource createInstance() throws Exception {
boolean autoCreatDirs = Boolean.parseBoolean(this.autoCreateDirectories);
boolean ackRemoteDir = Boolean.parseBoolean(this.autoDeleteRemoteFilesOnSync);
SftpInboundRemoteFileSystemSynchronizingMessageSource sftpMsgSrc = new SftpInboundRemoteFileSystemSynchronizingMessageSource();
sftpMsgSrc.setAutoCreateDirectories(autoCreatDirs);
@@ -166,27 +166,22 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
File sftpTmp = new File(tmp, "sftpInbound");
this.localDirectoryPath = "file://" + sftpTmp.getAbsolutePath();
}
this.localDirectoryResource = this.resourceFromString(localDirectoryPath);
// remote predicates
SftpEntryNamer sftpEntryNamer = new SftpEntryNamer();
CompositeEntryListFilter<ChannelSftp.LsEntry> compositeFtpFileListFilter = new CompositeEntryListFilter<ChannelSftp.LsEntry>();
if (StringUtils.hasText(this.filenamePattern)) {
PatternMatchingEntryListFilter<ChannelSftp.LsEntry> ftpFilePatternMatchingEntryListFilter = new PatternMatchingEntryListFilter<ChannelSftp.LsEntry>(sftpEntryNamer, filenamePattern);
compositeFtpFileListFilter.addFilter(ftpFilePatternMatchingEntryListFilter);
}
if (this.filter != null) {
compositeFtpFileListFilter.addFilter(this.filter);
}
this.filter = compositeFtpFileListFilter;
// pools
SftpSessionFactory sessionFactory = SftpSessionUtils.buildSftpSessionFactory(this.host, this.password, this.username, this.keyFile, this.keyFilePassword, this.port);
QueuedSftpSessionPool pool = new QueuedSftpSessionPool(15, sessionFactory);
pool.afterPropertiesSet();
@@ -197,8 +192,8 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
sftpSync.setFilter(compositeFtpFileListFilter);
sftpSync.setBeanFactory(this.getBeanFactory());
sftpSync.setRemotePath(this.remoteDirectory);
sftpSync.afterPropertiesSet(); // todo is this correct ?
sftpSync.start(); //todo
sftpSync.afterPropertiesSet();
sftpSync.start();
sftpMsgSrc.setRemotePredicate(compositeFtpFileListFilter);
sftpMsgSrc.setSynchronizer(sftpSync);
@@ -209,14 +204,13 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
sftpMsgSrc.setAutoStartup(true);
sftpMsgSrc.afterPropertiesSet();
sftpMsgSrc.start();
return sftpMsgSrc;
}
private Resource resourceFromString(String path) {
ResourceEditor resourceEditor = new ResourceEditor(this.resourceLoader);
resourceEditor.setAsText(path);
return (Resource) resourceEditor.getValue();
}
}

View File

@@ -13,17 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp.config;
import org.springframework.integration.sftp.SftpSessionFactory;
/**
* Provides a single place to handle this tedious chore.
* Utility methods for SFTP Session management.
*
* @author Josh Long
*/
public class SftpSessionUtils {
public abstract class SftpSessionUtils {
/**
* This method hides the minutae required to build an #SftpSessionFactory.
*
@@ -39,8 +40,7 @@ public class SftpSessionUtils {
* commands against a remote SFTP/SSH filesystem
* @throws Exception thrown in case of darned near <em>anything</em>
*/
public static SftpSessionFactory buildSftpSessionFactory(String host, String pw, String usr, String pvKey, String pvKeyPass, int port)
throws Exception {
public static SftpSessionFactory buildSftpSessionFactory(String host, String pw, String usr, String pvKey, String pvKeyPass, int port) throws Exception {
SftpSessionFactory sftpSessionFactory = new SftpSessionFactory();
sftpSessionFactory.setPassword(pw);
sftpSessionFactory.setPort(port);
@@ -49,7 +49,7 @@ public class SftpSessionUtils {
sftpSessionFactory.setPrivateKey(pvKey);
sftpSessionFactory.setPrivateKeyPassphrase(pvKeyPass);
sftpSessionFactory.afterPropertiesSet();
return sftpSessionFactory;
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp.impl;
import com.jcraft.jsch.ChannelSftp;
@@ -34,13 +35,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
/**
* This handles the synchronization between a remote SFTP endpoint and a local mount
* Gandles the synchronization between a remote SFTP endpoint and a local mount.
*
* @author Josh Long
*/
public class SftpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemoteFileSystemSychronizer<ChannelSftp.LsEntry> {
/**
* the path on the remote mount
*/
@@ -51,62 +52,61 @@ public class SftpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemo
*/
private volatile SftpSessionPool clientPool;
public void setRemotePath(String remotePath) {
this.remotePath = remotePath;
}
@Override
protected void onInit() throws Exception {
Assert.notNull(this.clientPool, "'clientPool' can't be null");
Assert.notNull(this.remotePath, "'remotePath' can't be null");
if (this.shouldDeleteSourceFile) {
this.entryAcknowledgmentStrategy = new DeletionEntryAcknowledgmentStrategy();
}
}
@Required
public void setClientPool(SftpSessionPool clientPool) {
this.clientPool = clientPool;
}
@SuppressWarnings("ignored")
private boolean copyFromRemoteToLocalDirectory(SftpSession sftpSession, ChannelSftp.LsEntry entry, Resource localDir)
throws Exception {
@Override
protected Trigger getTrigger() {
return new PeriodicTrigger(10 * 1000);
}
@Override
protected void onInit() throws Exception {
Assert.notNull(this.clientPool, "'clientPool' must not be null");
Assert.notNull(this.remotePath, "'remotePath' must not be null");
if (this.shouldDeleteSourceFile) {
this.entryAcknowledgmentStrategy = new DeletionEntryAcknowledgmentStrategy();
}
}
private boolean copyFromRemoteToLocalDirectory(SftpSession sftpSession, ChannelSftp.LsEntry entry, Resource localDir) throws Exception {
File fileForLocalDir = localDir.getFile();
File localFile = new File(fileForLocalDir, entry.getFilename());
if (!localFile.exists()) {
InputStream in = null;
FileOutputStream fileOutputStream = null;
try {
File tmpLocalTarget = new File(localFile.getAbsolutePath() +
AbstractInboundRemoteFileSystemSynchronizingMessageSource.INCOMPLETE_EXTENSION);
fileOutputStream = new FileOutputStream(tmpLocalTarget);
String remoteFqPath = this.remotePath + "/" + entry.getFilename();
in = sftpSession.getChannel().get(remoteFqPath);
try {
IOUtils.copy(in, fileOutputStream);
} finally {
}
finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(fileOutputStream);
}
if (tmpLocalTarget.renameTo(localFile)) {
this.acknowledge(sftpSession, entry);
}
return true;
} catch (Throwable th) {
logger.error("exception thrown in #copyFromRemoteToLocalDirectory", th);
}
} else {
catch (Throwable th) {
logger.error("failure occurred while copying from remote to local directory", th);
}
}
else {
return true;
}
return false;
}
@@ -114,47 +114,39 @@ public class SftpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemo
@SuppressWarnings("unchecked")
protected void syncRemoteToLocalFileSystem() throws Exception {
SftpSession session = null;
try {
session = clientPool.getSession();
session.start();
ChannelSftp channelSftp = session.getChannel();
Collection<ChannelSftp.LsEntry> beforeFilter = channelSftp.ls(remotePath);
ChannelSftp.LsEntry[] entries = (beforeFilter == null) ? new ChannelSftp.LsEntry[0] : beforeFilter.toArray(new ChannelSftp.LsEntry[beforeFilter.size()]);
Collection<ChannelSftp.LsEntry> files = this.filter.filterEntries(entries);
for (ChannelSftp.LsEntry lsEntry : files) {
if ((lsEntry != null) && !lsEntry.getAttrs().isDir() && !lsEntry.getAttrs().isLink()) {
copyFromRemoteToLocalDirectory(session, lsEntry, this.localDirectory);
}
}
} catch (IOException e) {
}
catch (IOException e) {
throw new MessagingException("couldn't synchronize remote to local directory", e);
} finally {
}
finally {
if ((session != null) && (clientPool != null)) {
clientPool.release(session);
}
}
}
@Override
protected Trigger getTrigger() {
return new PeriodicTrigger(10 * 1000);
}
class DeletionEntryAcknowledgmentStrategy implements AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy<ChannelSftp.LsEntry> {
public void acknowledge(Object useful, ChannelSftp.LsEntry msg)
throws Exception {
private class DeletionEntryAcknowledgmentStrategy implements AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy<ChannelSftp.LsEntry> {
public void acknowledge(Object useful, ChannelSftp.LsEntry msg) throws Exception {
SftpSession sftpSession = (SftpSession) useful;
String remoteFqPath = remotePath + "/" + msg.getFilename();
sftpSession.getChannel().rm(remoteFqPath);
if (logger.isDebugEnabled()) {
logger.debug("deleted " + msg.getFilename());
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@ package org.springframework.integration.stream.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.util.StringUtils;
@@ -32,7 +32,7 @@ import org.springframework.util.StringUtils;
public class ConsoleInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected String parseSource(Element element, ParserContext parserContext) {
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.stream.CharacterStreamReadingMessageSource");
builder.setFactoryMethod("stdin");
@@ -40,7 +40,7 @@ public class ConsoleInboundChannelAdapterParser extends AbstractPollingInboundCh
if (StringUtils.hasText(charsetName)) {
builder.addConstructorArgValue(charsetName);
}
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
return builder.getBeanDefinition();
}
}

View File

@@ -77,6 +77,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>

View File

@@ -13,32 +13,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.twitter.config;
import static org.springframework.integration.twitter.config.TwitterNamespaceHandler.BASE_PACKAGE;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
import static org.springframework.integration.twitter.config.TwitterNamespaceHandler.BASE_PACKAGE;
/**
* Parser for 'twitter-connection' element
* Parser for the 'twitter-connection' element.
*
* @author Josh Long
* @author Mark Fisher
* @since 2.0
*/
public class ConnectionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected String getBeanClassName(Element element) {
return BASE_PACKAGE + ".oauth.OAuthConfigurationFactoryBean";
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
TwitterNamespaceHandler.configureTwitterConnection(element, parserContext, builder);
}
@Override
protected String getBeanClassName(Element element) {
return BASE_PACKAGE + ".oauth.OAuthConfigurationFactoryBean";
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String ref = element.getAttribute("twitter-connection");
if (StringUtils.hasText(ref)) {
builder.addPropertyReference("twitterConnection", ref);
}
else {
for (String attribute : new String[] { "consumer-key", "consumer-secret", "access-token", "access-token-secret" }) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, attribute);
}
}
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed 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.springframework.integration.twitter.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.w3c.dom.Element;
import static org.springframework.integration.twitter.config.TwitterNamespaceHandler.BASE_PACKAGE;
/**
* Parser for 'outbound-dm-channel-adapter' element
*
* @author Josh Long
* @since 2.0
*
*/
public class OutboundDirectMessageMessageHandlerParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
BASE_PACKAGE + ".outbound.OutboundDirectMessageMessageHandler" );
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "twitter-connection", "configuration");
return builder.getBeanDefinition();
}
}

Some files were not shown because too many files have changed in this diff Show More