Merge branch 'master' of git://git.springsource.org/spring-integration/spring-integration
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ pom.xml
|
||||
si.java.hsp
|
||||
spring-integration-jms/activemq-data/
|
||||
spring-integration-samples/loanshark/application.log*
|
||||
target
|
||||
|
||||
10
build.gradle
10
build.gradle
@@ -85,6 +85,10 @@ configure(javaprojects) {
|
||||
apply plugin: 'eclipse' // `gradle eclipse` to generate .classpath/.project
|
||||
apply plugin: 'idea' // `gradle idea` to generate .ipr/.iml
|
||||
|
||||
// ensure JDK 5 compatibility
|
||||
sourceCompatibility=1.5
|
||||
targetCompatibility=1.5
|
||||
|
||||
// set up dedicated directories for jars and source jars.
|
||||
// this makes it easier when putting together the distribution
|
||||
libsBinDir = new File(libsDir, 'bin')
|
||||
@@ -107,7 +111,7 @@ configure(javaprojects) {
|
||||
log4jVersion = '1.2.12'
|
||||
mockitoVersion = '1.8.4'
|
||||
springVersion = '3.0.5.RELEASE'
|
||||
springSecurityVersion = '3.0.3.RELEASE'
|
||||
springSecurityVersion = '3.0.4.RELEASE'
|
||||
springWsVersion = '1.5.9'
|
||||
|
||||
sourceSets {
|
||||
@@ -259,6 +263,7 @@ project('spring-integration-jms') {
|
||||
description = 'Spring Integration JMS Support'
|
||||
dependencies {
|
||||
compile project(":spring-integration-core")
|
||||
compile "org.springframework:spring-context:$springVersion"
|
||||
compile "org.springframework:spring-jms:$springVersion"
|
||||
compile "org.springframework:spring-tx:$springVersion"
|
||||
compile ("org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1") { provided = true }
|
||||
@@ -399,6 +404,7 @@ project('spring-integration-xmpp') {
|
||||
compile "jivesoftware:smackx:3.1.0"
|
||||
compile "org.springframework:spring-context-support:$springVersion"
|
||||
testCompile project(":spring-integration-test")
|
||||
testCompile project(":spring-integration-stream")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +418,8 @@ apply from: "$rootDir/gradle/dist.gradle"
|
||||
// add tasks like 'snapshotDependencyCheck'
|
||||
apply from: "${rootDir}/gradle/checks.gradle"
|
||||
|
||||
// add 'generatePom' task to generate root pom with <modules> section
|
||||
apply from: "$rootDir/gradle/maven-root-pom.gradle"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Import tasks related to releasing and managing the project
|
||||
|
||||
@@ -186,7 +186,7 @@ dependencies {
|
||||
scpAntTask("org.apache.ant:ant-jsch:1.8.1")
|
||||
}
|
||||
|
||||
checkForProps(taskPath: project.path + ':uploadArchives', requiredProps: ['sshHost', 'sshUsername'])
|
||||
checkForProps(taskPath: project.path + ':uploadArchives', requiredProps: ['sshHost', 'sshUsername', 'sshPrivateKey'])
|
||||
|
||||
uploadArchives {
|
||||
def sshHost = project.properties.sshHost
|
||||
@@ -206,8 +206,8 @@ uploadArchives {
|
||||
name = 'sshHost: ' + sshHost // used for debugging
|
||||
host = sshHost
|
||||
user = sshUsername
|
||||
if (project.hasProperty('remoteSiteDir')) {
|
||||
keyFile = sshPrivateKey as File
|
||||
if (project.hasProperty('sshPrivateKey')) {
|
||||
keyFile = sshPrivateKey ? sshPrivateKey as File : null
|
||||
}
|
||||
addArtifactPattern "${remoteDocsDir}/${archive.archiveName}"
|
||||
}
|
||||
@@ -227,13 +227,20 @@ uploadArchives {
|
||||
classpath: configurations.scpAntTask.asPath)
|
||||
|
||||
// copy the archive, unpack it, then delete it
|
||||
def unpackCommand = "cd ${remoteDocsDir} && unzip ${archive.archiveName}"
|
||||
def deleteCommand = "rm ${remoteDocsDir}/${archive.archiveName}"
|
||||
def unpackCommand = "cd ${remoteDocsDir} && rm -rf ${version} && unzip -qKo ${archive.archiveName} && rm ${archive.archiveName}"
|
||||
def wildcardSymlinkCommand = "cd ${remoteDocsDir} && rm -f ${version.wildcardValue} && ln -s ${version} ${version.wildcardValue}"
|
||||
def latestGASymlinkCommand = "cd ${remoteDocsDir} && rm -f latest-ga && ln -s ${version} latest-ga"
|
||||
|
||||
println "sshexec ${unpackCommand}"
|
||||
println "Unpacking docs archive: (${unpackCommand})"
|
||||
sshexec(host: sshHost, username: sshUsername, keyfile: sshPrivateKey, command: unpackCommand)
|
||||
println "sshexec ${deleteCommand}"
|
||||
sshexec(host: sshHost, username: sshUsername, keyfile: sshPrivateKey, command: deleteCommand)
|
||||
if (version.releaseType != 'SNAPSHOT') {
|
||||
println "Creating wildcard symlink: (${wildcardSymlinkCommand})"
|
||||
sshexec(host: sshHost, username: sshUsername, keyfile: sshPrivateKey, command: wildcardSymlinkCommand)
|
||||
}
|
||||
if (version.releaseType == 'RELEASE') {
|
||||
println "Creating latest-ga symlink: (${latestGASymlinkCommand})"
|
||||
sshexec(host: sshHost, username: sshUsername, keyfile: sshPrivateKey, command: latestGASymlinkCommand)
|
||||
}
|
||||
println "UPLOAD SUCCESSFUL - validate by visiting ${docUrl}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="aggregator"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="aggregator"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Aggregator</title>
|
||||
|
||||
@@ -622,4 +622,4 @@
|
||||
the @MessageEndpoint is defined on the class, detected automatically
|
||||
through classpath scanning.</para>
|
||||
</section>
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="bridge"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="bridge"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Messaging Bridge</title>
|
||||
|
||||
@@ -56,4 +56,4 @@
|
||||
</note>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="chain"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="chain"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Handler Chain</title>
|
||||
|
||||
@@ -111,4 +111,4 @@ When light version of <gateway> element is defined in the chain SI will co
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<chapter id="channel-adapter">
|
||||
<section id="channel-adapter">
|
||||
<title>Channel Adapter</title>
|
||||
<para>
|
||||
A Channel Adapter is a Message Endpoint that enables connecting a single sender or receiver to a Message Channel.
|
||||
@@ -75,4 +75,4 @@
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="channel"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="channel"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Channels</title>
|
||||
<para>
|
||||
@@ -599,4 +599,4 @@ public Message<?> receive(final PollableChannel<?> channel) { ... }]]></programl
|
||||
</note>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="delayer"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="delayer"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Delayer</title>
|
||||
|
||||
@@ -58,4 +58,4 @@
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="endpoint"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="endpoint"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Endpoints</title>
|
||||
<para>
|
||||
@@ -346,4 +346,4 @@ any transaction configuration essentially allowing you to enhance the behavior o
|
||||
to - <emphasis>Section 25 - Task Execution and Scheduling</emphasis> of Spring reference manual.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,62 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="applicationevent"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<title>Spring ApplicationEvent Support</title>
|
||||
|
||||
<para>
|
||||
Spring Integration provides support for inbound and outbound <classname>ApplicationEvents</classname>
|
||||
as defined by the underlying Spring Framework. For more information about the events and listeners,
|
||||
as defined by the underlying Spring Framework. For more information about Spring's support for events and listeners,
|
||||
refer to the <ulink url="http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#context-functionality-events">Spring Reference Manual</ulink>.
|
||||
</para>
|
||||
|
||||
<section id="applicationevent-inbound">
|
||||
<title>Receiving Spring ApplicationEvents</title>
|
||||
<para>
|
||||
To receive events and send them to a channel, simply define an instance of Spring Integration's
|
||||
<classname>ApplicationEventListeningChannelAdapter</classname>. This class is an implementation of
|
||||
Spring's <interfacename>ApplicationListener</interfacename> interface. By default it will pass all
|
||||
received events as Spring Integration Messages. To limit based on the type of event, configure the
|
||||
list of event types that you want to receive with the 'eventTypes' property.
|
||||
</para>
|
||||
<para>
|
||||
For convenience namespace support was provided to configure <classname>ApplicationEventListeningChannelAdapter</classname> via <emphasis>inbound-channel-adapter</emphasis>
|
||||
<programlisting language="xml"><![CDATA[<int-event:inbound-channel-adapter channel="input" event-types="foo.bar.FooApplicationEvent, foo.bar.BarApplicationEvent"/>
|
||||
|
||||
<int:publish-subscribe-channel id="sampleEventChannel"/>]]></programlisting>
|
||||
In the above sample, all Application Context events that are of type specified by the 'event-types' (optional) attribute will be
|
||||
delivered as Spring Integration Messages to 'sampleEventChannel'.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To receive events and send them to a channel, simply define an instance of Spring Integration's
|
||||
<classname>ApplicationEventListeningMessageProducer</classname>. This class is an implementation of
|
||||
Spring's <interfacename>ApplicationListener</interfacename> interface. By default it will pass all
|
||||
received events as Spring Integration Messages. To limit based on the type of event, configure the
|
||||
list of event types that you want to receive with the 'eventTypes' property. If a received event
|
||||
has a Message instance as its 'source', then that will be passed as-is. Otherwise, if a SpEL-based
|
||||
"payloadExpression" has been provided, that will be evaluated against the ApplicationEvent instance.
|
||||
If the event's source is not a Message instance and no "payloadExpression" has been provided, then
|
||||
the ApplicationEvent itself will be passed as the payload.
|
||||
</para>
|
||||
<para>
|
||||
For convenience namespace support is provided to configure an <classname>ApplicationEventListeningMessageProducer</classname>
|
||||
via the <emphasis>inbound-channel-adapter</emphasis> element.
|
||||
<programlisting language="xml"><![CDATA[<int-event:inbound-channel-adapter channel="eventChannel" event-types="example.FooEvent, example.BarEvent"/>
|
||||
|
||||
<int:publish-subscribe-channel id="eventChannel"/>]]></programlisting>
|
||||
In the above example, all Application Context events that match one of the types specified by the 'event-types' (optional) attribute will be
|
||||
delivered as Spring Integration Messages to the Message Channel named 'eventChannel'.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="applicationevent-outbound">
|
||||
<title>Sending Spring ApplicationEvents</title>
|
||||
<para>
|
||||
To send Spring <classname>ApplicationEvents</classname>, create an instance of the
|
||||
<classname>ApplicationEventPublishingMessageHandler</classname> and register it within an endpoint.
|
||||
This implementation of the <interfacename>MessageHandler</interfacename> interface also implements
|
||||
Spring's <interfacename>ApplicationEventPublisherAware</interfacename> interface and thus acts as a
|
||||
bridge between Spring Integration Messages and <classname>ApplicationEvents</classname>.
|
||||
</para>
|
||||
<para>
|
||||
For convenience namespace support was provided to configure <classname>ApplicationEventPublishingMessageHandler</classname> via <emphasis>outbound-channel-adapter</emphasis> element
|
||||
<programlisting language="xml"><![CDATA[<int:channel id="input"/>
|
||||
<title>Sending Spring ApplicationEvents</title>
|
||||
<para>
|
||||
To send Spring <classname>ApplicationEvents</classname>, create an instance of the
|
||||
<classname>ApplicationEventPublishingMessageHandler</classname> and register it within an endpoint.
|
||||
This implementation of the <interfacename>MessageHandler</interfacename> interface also implements
|
||||
Spring's <interfacename>ApplicationEventPublisherAware</interfacename> interface and thus acts as a
|
||||
bridge between Spring Integration Messages and <classname>ApplicationEvents</classname>.
|
||||
</para>
|
||||
|
||||
<int-event:outbound-channel-adapter channel="input"/>]]></programlisting>
|
||||
If you are using PollableChannel (e.g., Queue), you can also provide <emphasis>poller</emphasis> as sub-element of <emphasis>outbound-channel-adapter</emphasis>, optionally providing <emphasis>task-executor</emphasis>
|
||||
<programlisting language="xml"><![CDATA[<int:channel id="input">
|
||||
<para>
|
||||
For convenience namespace support is provided to configure an <classname>ApplicationEventPublishingMessageHandler</classname>
|
||||
via the <emphasis>outbound-channel-adapter</emphasis> element.
|
||||
<programlisting language="xml"><![CDATA[<int:channel id="eventChannel"/>
|
||||
|
||||
<int-event:outbound-channel-adapter channel="eventChannel"/>]]></programlisting>
|
||||
If you are using a PollableChannel (e.g., Queue), you can also provide <emphasis>poller</emphasis> as a sub-element of the
|
||||
<emphasis>outbound-channel-adapter</emphasis> element. You can also optionally provide a <emphasis>task-executor</emphasis>
|
||||
reference for that poller. The following example demonstrates both.
|
||||
<programlisting language="xml"><![CDATA[<int:channel id="eventChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int-event:outbound-channel-adapter channel="input">
|
||||
<int-event:outbound-channel-adapter channel="eventChannel">
|
||||
<int:poller max-messages-per-poll="1" task-executor="executor" fixed-rate="100"/>
|
||||
</int-event:outbound-channel-adapter>
|
||||
|
||||
<task:executor id="executor" pool-size="5"/>]]></programlisting>
|
||||
|
||||
In the above sample, all messages sent to an 'input' channel will be published as ApplicationEvents to Spring Application sContext
|
||||
</para>
|
||||
In the above example, all messages sent to the 'eventChannel' channel will be published as ApplicationEvents to any relevant
|
||||
ApplicationListener instances that are registered within the same Spring ApplicationContext. If the payload of the Message is
|
||||
an ApplicationEvent, it will be passed as-is. Otherwise the Message itself will be wrapped in a MessagingEvent instance.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
|
||||
14
docs/src/reference/docbook/feed.xml
Normal file
14
docs/src/reference/docbook/feed.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="feed"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Feed Adapter</title>
|
||||
<para>
|
||||
Spring Integration provides support for Feed (RSS, Atom)
|
||||
</para>
|
||||
<section id="feed-intro">
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
TODO
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="filter"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="filter"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filter</title>
|
||||
|
||||
@@ -128,4 +128,4 @@
|
||||
to be treated as Message Channel names by a router component.</note>
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
14
docs/src/reference/docbook/ftp.xml
Normal file
14
docs/src/reference/docbook/ftp.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="ftp"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>FTP Adapter</title>
|
||||
<para>
|
||||
Spring Integration provides support for FTP
|
||||
</para>
|
||||
<section id="ftp-intro">
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
TODO
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="gateway"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="gateway"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Inbound Messaging Gateways</title>
|
||||
|
||||
@@ -251,4 +251,4 @@ For a more detailed example, please refer to the <emphasis>async-gateway</emphas
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="groovy"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="groovy"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Groovy support</title>
|
||||
<para>
|
||||
@@ -69,4 +69,4 @@ You see that script could be included inline or via <code>location</code> attrib
|
||||
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
support the HTTP inbound adapters need to be deployed within a servlet container. The easiest way to do this is to provide a servlet
|
||||
definition in <emphasis>web.xml</emphasis>, see
|
||||
<xref linkend="httpinvoker-inbound"/> for further details. Below is an example bean definition for a simple HTTP inbound endpoint.
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpInbound" class="org.springframework.integration.http.HttpRequestHandlingMessagingGateway">
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpInbound" class="org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway">
|
||||
<property name="requestChannel" ref="httpRequestChannel" />
|
||||
<property name="replyChannel" ref="httpReplyChannel" />
|
||||
</bean>]]></programlisting>
|
||||
@@ -51,7 +51,7 @@
|
||||
configured to serve as a Spring MVC Controller with a view name. Because of the constructor arg value of TRUE, it wait for a reply. This also shows
|
||||
how to customize the HTTP methods accepted by the gateway, which
|
||||
are <emphasis>POST</emphasis> and <emphasis>GET</emphasis> by default.
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpInbound" class="org.springframework.integration.http.HttpRequestHandlingController">
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpInbound" class="org.springframework.integration.http.inbound.HttpRequestHandlingController">
|
||||
<constructor-arg value="true" /> <!-- indicates that a reply is expected -->
|
||||
<property name="requestChannel" ref="httpRequestChannel" />
|
||||
<property name="replyChannel" ref="httpReplyChannel" />
|
||||
@@ -75,14 +75,14 @@
|
||||
|
||||
<para>
|
||||
To configure the <classname>HttpRequestExecutingMessageHandler</classname> write a bean definition like this:
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpOutbound" class="org.springframework.integration.http.HttpRequestExecutingMessageHandler" >
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpOutbound" class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler" >
|
||||
<constructor-arg value="http://localhost:8080/example" />
|
||||
<property name="outputChannel" ref="responseChannel" />
|
||||
</bean>]]></programlisting>
|
||||
This bean definition will execute HTTP requests by delegating to a <classname>RestTemplate</classname>. That template in turn delegates
|
||||
to a list of HttpMessageConverters to generate the HTTP request body from the Message payload. You can configure those converters as well
|
||||
as the ClientHttpRequestFactory instance to use:
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpOutbound" class="org.springframework.integration.http.HttpRequestExecutingMessageHandler" >
|
||||
<programlisting language="xml"><![CDATA[<bean id="httpOutbound" class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler" >
|
||||
<constructor-arg value="http://localhost:8080/example" />
|
||||
<property name="outputChannel" ref="responseChannel" />
|
||||
<property name="messageConverters" ref="messageConverterList" />
|
||||
@@ -176,7 +176,7 @@ On the server side we have the following configuration:
|
||||
<int:channel id="receiveChannel"/>
|
||||
|
||||
<int:service-activator input-channel="receiveChannel">
|
||||
<bean class="org.springframework.integration.samples.multipart.MultipartReceiever"/>
|
||||
<bean class="org.springframework.integration.samples.multipart.MultipartReceiver"/>
|
||||
</int:service-activator>
|
||||
|
||||
<bean id="multipartResolver"
|
||||
@@ -186,8 +186,8 @@ On the server side we have the following configuration:
|
||||
<para>
|
||||
The 'httpInboundAdapter' will receive the request, convert it to a <classname>Message</classname> with a payload as <classname>LinkedMultiValueMap</classname> which
|
||||
we are parsing in the 'multipartReceiver' service-activator;
|
||||
<programlisting language="java"><![CDATA[public void recieve(LinkedMultiValueMap<String, Object> multipartRequest){
|
||||
System.out.println("### Successfully recieved multipart request ###");
|
||||
<programlisting language="java"><![CDATA[public void receive(LinkedMultiValueMap<String, Object> multipartRequest){
|
||||
System.out.println("### Successfully received multipart request ###");
|
||||
for (String elementName : multipartRequest.keySet()) {
|
||||
if (elementName.equals("company")){
|
||||
System.out.println("\t" + elementName + " - " +
|
||||
@@ -201,7 +201,7 @@ we are parsing in the 'multipartReceiver' service-activator;
|
||||
|
||||
]]></programlisting>
|
||||
You should see the following output:
|
||||
<programlisting language="xml"><![CDATA[### Successfully recieved multipart request ###
|
||||
<programlisting language="xml"><![CDATA[### Successfully received multipart request ###
|
||||
company - SpringSource
|
||||
company-logo - as UploadedMultipartFile: spring09_logo.png]]></programlisting>
|
||||
</para>
|
||||
|
||||
@@ -55,44 +55,61 @@
|
||||
</bookinfo>
|
||||
|
||||
<toc></toc>
|
||||
|
||||
<xi:include href="./overview.xml"/>
|
||||
<xi:include href="./message.xml"/>
|
||||
<xi:include href="./channel.xml"/>
|
||||
<xi:include href="./endpoint.xml"/>
|
||||
<xi:include href="./service-activator.xml"/>
|
||||
<xi:include href="./channel-adapter.xml"/>
|
||||
<xi:include href="./router.xml"/>
|
||||
<xi:include href="./filter.xml"/>
|
||||
<xi:include href="./transformer.xml"/>
|
||||
<xi:include href="./splitter.xml"/>
|
||||
<xi:include href="./aggregator.xml"/>
|
||||
<xi:include href="./resequencer.xml"/>
|
||||
<xi:include href="./delayer.xml"/>
|
||||
<xi:include href="./chain.xml"/>
|
||||
<xi:include href="./bridge.xml"/>
|
||||
<xi:include href="./gateway.xml"/>
|
||||
<xi:include href="./message-publishing.xml"/>
|
||||
<xi:include href="./transactions.xml"/>
|
||||
<xi:include href="./message-history.xml"/>
|
||||
<xi:include href="./file.xml"/>
|
||||
<xi:include href="./jdbc.xml"/>
|
||||
<xi:include href="./jms.xml"/>
|
||||
<xi:include href="./ws.xml"/>
|
||||
<xi:include href="./rmi.xml"/>
|
||||
<xi:include href="./httpinvoker.xml"/>
|
||||
<xi:include href="./http.xml"/>
|
||||
<xi:include href="./ip.xml"/>
|
||||
<xi:include href="./mail.xml"/>
|
||||
<xi:include href="./jmx.xml"/>
|
||||
<xi:include href="./xmpp.xml"/>
|
||||
<xi:include href="./stream.xml"/>
|
||||
<xi:include href="./event.xml"/>
|
||||
<xi:include href="./xml.xml"/>
|
||||
<xi:include href="./security.xml"/>
|
||||
<xi:include href="./groovy.xml"/>
|
||||
<xi:include href="./samples.xml"/>
|
||||
<xi:include href="./configuration.xml"/>
|
||||
<xi:include href="./resources.xml"/>
|
||||
|
||||
|
||||
<part id="spring-integration-introduction">
|
||||
<title>Overview of Spring Integration Framework</title>
|
||||
<partintro id="spring-integration-intro">
|
||||
<para>The Spring Integration Framework is a lightweight solution TODO</para>
|
||||
</partintro>
|
||||
<xi:include href="./overview.xml"/>
|
||||
</part>
|
||||
|
||||
<part id="spring-integration-core-messaging">
|
||||
<title>Core Messaging</title>
|
||||
<partintro id="spring-integration-core-msg">
|
||||
<para>Core Messaging TODO</para>
|
||||
</partintro>
|
||||
|
||||
<xi:include href="./messaging-channels.xml"/>
|
||||
<xi:include href="./message-construction.xml"/>
|
||||
<xi:include href="./message-routing.xml"/>
|
||||
<xi:include href="./message-transformation.xml"/>
|
||||
<xi:include href="./messaging-endpoints.xml"/>
|
||||
<xi:include href="./system-management.xml"/>
|
||||
</part>
|
||||
<part id="spring-integration-adapters">
|
||||
<title>Integration Adapters</title>
|
||||
<partintro id="spring-integration-adapters">
|
||||
<para>Adapters TODO</para>
|
||||
</partintro>
|
||||
<xi:include href="./event.xml"/>
|
||||
<xi:include href="./feed.xml"/>
|
||||
<xi:include href="./file.xml"/>
|
||||
<xi:include href="./ftp.xml"/>
|
||||
<xi:include href="./http.xml"/>
|
||||
<xi:include href="./httpinvoker.xml"/>
|
||||
<xi:include href="./mail.xml"/>
|
||||
<xi:include href="./ip.xml"/>
|
||||
<xi:include href="./jdbc.xml"/>
|
||||
<xi:include href="./jms.xml"/>
|
||||
<xi:include href="./rmi.xml"/>
|
||||
<xi:include href="./sftp.xml"/>
|
||||
<xi:include href="./stream.xml"/>
|
||||
<xi:include href="./twitter.xml"/>
|
||||
<xi:include href="./ws.xml"/>
|
||||
<xi:include href="./xml.xml"/>
|
||||
<xi:include href="./xmpp.xml"/>
|
||||
</part>
|
||||
<part id="spring-integration-appendices">
|
||||
<title>Appendices</title>
|
||||
<partintro id="spring-integration-adapters">
|
||||
<para>Appendices TODO</para>
|
||||
</partintro>
|
||||
<xi:include href="./message-publishing.xml"/>
|
||||
<xi:include href="./transactions.xml"/>
|
||||
<xi:include href="./security.xml"/>
|
||||
<xi:include href="./samples.xml"/>
|
||||
<xi:include href="./configuration.xml"/>
|
||||
<xi:include href="./resources.xml"/>
|
||||
</part>
|
||||
</book>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="jmx"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="jmx"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>JMX Support</title>
|
||||
|
||||
@@ -187,4 +187,4 @@
|
||||
foundation is available, however, we will be able to extend the attributes
|
||||
and operations that are being exposed.</para>
|
||||
</section>
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
9
docs/src/reference/docbook/message-construction.xml
Normal file
9
docs/src/reference/docbook/message-construction.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="messaging-construction-chapter"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Construction</title>
|
||||
|
||||
<xi:include href="./message.xml"/>
|
||||
|
||||
</chapter>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="message-history"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="message-history"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message History</title>
|
||||
<para>
|
||||
@@ -61,4 +61,4 @@ assertEquals("sampleChain", chainHistory.get("name"));]]></programlisting>
|
||||
be changed once written. Every attempt will end in exception.
|
||||
</note>
|
||||
</section>
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
14
docs/src/reference/docbook/message-routing.xml
Normal file
14
docs/src/reference/docbook/message-routing.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="messaging-routing-chapter"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Routing</title>
|
||||
|
||||
<xi:include href="./router.xml"/>
|
||||
<xi:include href="./filter.xml"/>
|
||||
<xi:include href="./splitter.xml"/>
|
||||
<xi:include href="./aggregator.xml"/>
|
||||
<xi:include href="./resequencer.xml"/>
|
||||
<xi:include href="./chain.xml"/>
|
||||
|
||||
</chapter>
|
||||
9
docs/src/reference/docbook/message-transformation.xml
Normal file
9
docs/src/reference/docbook/message-transformation.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="messaging-transformation-chapter"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Transformation</title>
|
||||
|
||||
<xi:include href="./transformer.xml"/>
|
||||
|
||||
</chapter>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="message"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="message"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Message Construction</title>
|
||||
<title>Message</title>
|
||||
<para>
|
||||
The Spring Integration <interfacename>Message</interfacename> is a generic container for data. Any object can
|
||||
be provided as the payload, and each <interfacename>Message</interfacename> also includes headers containing
|
||||
@@ -219,4 +219,4 @@ assertEquals(MessagePriority.HIGHEST, anotherMessage.getHeaders().getPriority())
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
11
docs/src/reference/docbook/messaging-channels.xml
Normal file
11
docs/src/reference/docbook/messaging-channels.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="messaging-channels-section"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Messaging Channels</title>
|
||||
|
||||
<xi:include href="./channel.xml"/>
|
||||
<xi:include href="./channel-adapter.xml"/>
|
||||
<xi:include href="./bridge.xml"/>
|
||||
|
||||
</chapter>
|
||||
13
docs/src/reference/docbook/messaging-endpoints.xml
Normal file
13
docs/src/reference/docbook/messaging-endpoints.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="messaging-endpoints-chapter"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Messaging Endpoints</title>
|
||||
|
||||
<xi:include href="./endpoint.xml"/>
|
||||
<xi:include href="./gateway.xml"/>
|
||||
<xi:include href="./service-activator.xml"/>
|
||||
<xi:include href="./delayer.xml"/>
|
||||
<xi:include href="./groovy.xml"/>
|
||||
|
||||
</chapter>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="resequencer"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="resequencer"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Resequencer</title>
|
||||
|
||||
@@ -123,4 +123,4 @@
|
||||
Since there is no custom behavior to be implemented in Java classes for resequencers, there is no annotation support for it.
|
||||
</note>
|
||||
</section>
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="router"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="router"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Router</title>
|
||||
|
||||
@@ -402,4 +402,4 @@ public List<String> route(@Header("orderStatus") OrderStatus status)</prog
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="service-activator"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="service-activator"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Service Activator</title>
|
||||
|
||||
@@ -68,4 +68,4 @@
|
||||
</note>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
14
docs/src/reference/docbook/sftp.xml
Normal file
14
docs/src/reference/docbook/sftp.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="Sftp"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>SFTP Adapter</title>
|
||||
<para>
|
||||
Spring Integration provides support for SFTP
|
||||
</para>
|
||||
<section id="sftp-intro">
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
TODO
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="splitter"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="splitter"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Splitter</title>
|
||||
|
||||
@@ -154,4 +154,4 @@ List<LineItem> extractItems(Order order) {
|
||||
}</programlisting></para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
10
docs/src/reference/docbook/system-management.xml
Normal file
10
docs/src/reference/docbook/system-management.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="system-management-chapter"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>System Management</title>
|
||||
|
||||
<xi:include href="./message-history.xml"/>
|
||||
<xi:include href="./jmx.xml"/>
|
||||
|
||||
</chapter>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="transformer"
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="transformer"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Transformer</title>
|
||||
|
||||
@@ -180,4 +180,4 @@ Order generateOrder(String productId, @Header("customerName") String customer) {
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</section>
|
||||
|
||||
218
docs/src/reference/docbook/twitter.xml
Normal file
218
docs/src/reference/docbook/twitter.xml
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="twitter"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Twitter Adapter</title>
|
||||
<para>
|
||||
Spring Integration provides support for interacting with Twitter via Twitter adapters. With Twitter adapters you can both
|
||||
receive and send Twitter messages.
|
||||
</para>
|
||||
<section id="twitter-intro">
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
Twitter is a social networking and microblogging service that enables its users to send and read messages known as tweets.
|
||||
Tweets are text-based posts of up to 140 characters displayed on the author's profile page and delivered to the author's
|
||||
subscribers who are known as followers.
|
||||
</para>
|
||||
<para>
|
||||
<important>
|
||||
Current Twitter support is based on <link linkend="http://twitter4j.org/en/index.html">Twitter4J API</link>,
|
||||
however future version will be changed to use <link linkend="https://jira.springframework.org/browse/SOCIAL">Spring Social</link>
|
||||
project as it is nearing its first release at the time of writing.
|
||||
</important>
|
||||
</para>
|
||||
<para>
|
||||
Spring Integration provides a convenient namespace configuration to define Twitter artifacts.
|
||||
<programlisting language="xml"><![CDATA[xmlns:twitter="http://www.springframework.org/schema/integration/twitter"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/twitter
|
||||
http://www.springframework.org/schema/integration/twitter/spring-integration-twitter-2.0.xsd"]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="twitter-oauth">
|
||||
<title>Twitter Connection and OAuth Configuration</title>
|
||||
|
||||
<para>
|
||||
Before using inbound or outbound Twitter adapters you must establish secured Twitter connection. This connection object
|
||||
could be shared by all twitter adapters connected to a particular account.
|
||||
</para>
|
||||
<para>
|
||||
Twitter uses OAuth - an authentication protocol that allows users to approve application to act on their behalf without
|
||||
sharing their password. More information can be found at <link linkend="http://oauth.net/">http://oauth.net/</link> or
|
||||
in this article <link linkend="http://hueniverse.com/oauth/">http://hueniverse.com/oauth/</link> from Hueniverse.
|
||||
Please also see <link linkend="http://dev.twitter.com/pages/oauth_faq">OAuth FAQ</link> for more information about OAuth and Twitter.
|
||||
</para>
|
||||
<para>
|
||||
In order to use OAuth authentication/authorization with Twitter you must create new Application on Twitter Developers site.
|
||||
Follow the directions below to create a new application and obtain consumer keys and access token:
|
||||
</para>
|
||||
<para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>Go to <link linkend="http://dev.twitter.com/">http://dev.twitter.com/</link></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Click on <code>Register an app</code> link and fill out all required fields on the form provided;
|
||||
set <code>Application Type</code> to <code>Client</code> and depending on the nature of your application select
|
||||
<code>Default Access Type</code> as <emphasis>Read & Write</emphasis> or <emphasis>Read-only</emphasis>
|
||||
and Submit the form. If everything is successful you'll be presented with the <code>Consumer Key</code>
|
||||
and <code>Consumer Secret</code>. Copy both values in the safe place.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>On the same page you should see <code>My Access Token</code> button on the side bar (right).
|
||||
Click on it and you'll be presented with two more values: <code>Access Token</code> and <code>Access Token Secret</code>.
|
||||
Copy these values in a safe place as well.</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
<emphasis>Twitter Connection</emphasis>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Spring Integration provides a convenient namespace configuration to define Twitter Connection. As you can see form the
|
||||
configuration below you configure Twitter connection via <code>twitter-connection</code> element while providing
|
||||
OAuth <code>attributes</code> filling them with values you have obtained in the previous step.
|
||||
|
||||
<programlisting language="xml"><![CDATA[<twitter:twitter-connection id="tc"
|
||||
access-token="21691649-4XYZY5iJEOfz2A9qCFd9SjBRGb3HLmIm4HNE6AMv4"
|
||||
access-token-secret="AbRxUAvyNCtqQtvxFK8w5ZMtMj20KFhB6oEfTA0"
|
||||
consumer-key="4XzBPabcJQxyBzzzH3TrRQ"
|
||||
consumer-secret="ab2piKdMfPu8bVa3ab6DAIvIWEVZyMDL0RSEN2I8"/>]]></programlisting>
|
||||
<note>The values above are not real</note>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
However a more practical way to manage OAuth connection attributes would be via Spring's placeholder support by simply
|
||||
creating a property file (e.g., oauth.properties):
|
||||
|
||||
<programlisting language="java"><![CDATA[twitter.oauth.consumerKey=4XzBPabcJQxyBzzzH3TrRQ
|
||||
twitter.oauth.consumerSecret=ab2piKdMfPu8bVa3ab6DAIvIWEVZyMDL0RSEN2I8
|
||||
twitter.oauth.accessToken=21691649-4XYZY5iJEOfz2A9qCFd9SjBRGb3HLmIm4HNE6AMv4
|
||||
twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtvxFK8w5ZMtMj20KFhB6oEfTA0]]></programlisting>
|
||||
|
||||
and configuring a <code>property-placeholder</code> pointing to he above property file:
|
||||
|
||||
<programlisting language="java"><![CDATA[<context:property-placeholder
|
||||
location="classpath:oauth.properties"/>
|
||||
|
||||
<twitter:twitter-connection id="tc"
|
||||
access-token="${twitter.oauth.accessToken}"
|
||||
access-token-secret="${twitter.oauth.accessTokenSecret}"
|
||||
consumer-key="${twitter.oauth.consumerKey}"
|
||||
consumer-secret="${twitter.oauth.consumerSecret}"/>]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="twitter-inbound">
|
||||
<title>Twitter Inbound Adapters</title>
|
||||
<para>
|
||||
Twitter inbound adapters allow you to receive Twitter Messages. There are several types of
|
||||
<link linkend="http://support.twitter.com/groups/31-twitter-basics/topics/109-tweets-messages/articles/119138-types-of-tweets-and-where-they-appear">twitter messages - tweets</link>
|
||||
</para>
|
||||
<para>
|
||||
Current release of Spring Integration provides support for receiving <emphasis>Public Messages</emphasis>,
|
||||
<emphasis>Direct Messages</emphasis> as well as <emphasis>Mention Messages</emphasis>
|
||||
</para>
|
||||
<para>
|
||||
Every Inbound Twitter Channel Adapter is a <emphasis>Polling consumer</emphasis> which means you have to provide a poller
|
||||
configuration. However, one important thing you must understand with regard to Twitter since its inner-workings are slightly
|
||||
different then any other poling consumer. Twitter defines a concept of Rate Limiting. You can read more about
|
||||
it here: <link linkend="http://dev.twitter.com/pages/rate-limiting">Rate Limiting</link> . In the nutshell Rate Limiting
|
||||
is the way Twitter manages how often an application can poll for updates. Luckily for you you don't have to worry about it
|
||||
since the special Rate limit aware polling thread is created when any Twitter adapter is started. This thread will
|
||||
poll Messages (Tweets) from the Twitter account at the rate allowed by Twitter at the time (it may change after every poll). The Tweets
|
||||
will be stored in the instance of the <classname>org.springframework.integration.store.MetadataStore</classname> which is a
|
||||
strategy interface designed for storing various types of metadata (e.g., last retrieved tweet) to help components such as Twitter
|
||||
to deal with duplicates. By default, Spring Integration will look for a bean of type <classname>org.springframework.integration.store.MetadataStore</classname>
|
||||
in the ApplicationContext. If one found then it will be used, otherwise it will create a new instance of <classname>SimpleMetadataStore</classname>
|
||||
which is a simple in-memory implementation that will only persist meta-data within the life-cycle of the application context
|
||||
which means upon restart you may end up with duplicate entries. If you need to persist meta-data between Application Context
|
||||
restarts, you may use <classname>PropertiesPersistingMetadataStore</classname> (property file based persister) or provide your
|
||||
own implementation of the <classname>MetedataStore</classname> interface (e.g., JdbcMetadatStore) and configure it
|
||||
as bean in the Application Context.
|
||||
<programlisting language="java"><![CDATA[<bean class="org.springframework.integration.store.PropertiesPersistingMetadataStore"/>
|
||||
]]></programlisting>
|
||||
The Poller that is configured as part of the any Inbound Twitter Adapter (see below) will simply poll from this MetadataStore
|
||||
</para>
|
||||
<section id="inbound-twitter-update">
|
||||
<title>Inbound Update Channel Adapter</title>
|
||||
<para>
|
||||
This adapter allows you to receive updates from everyone you follow.
|
||||
<programlisting language="java"><![CDATA[<twitter:inbound-mention-channel-adapter twitter-connection="tc" channel="inbound_mentions">
|
||||
<poller fixed-rate="5000" max-messages-per-poll="3"/>
|
||||
</twitter:inbound-mention-channel-adapter>]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="inbound-twitter-direct">
|
||||
<title>Inbound Direct Message Channel Adapter</title>
|
||||
<para>
|
||||
This adapter allows you to receive Twitter Messages that were sent directly to you
|
||||
<programlisting language="java"><![CDATA[<twitter:inbound-dm-channel-adapter twitter-connection="tc" channel="inbound_dm">
|
||||
<poller fixed-rate="5000" max-messages-per-poll="3"/>
|
||||
</twitter:inbound-dm-channel-adapter>]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="inbound-twitter-mention">
|
||||
<title>Inbound Mention Message Channel Adapter</title>
|
||||
<para>
|
||||
This adapter allows you to receive Twitter Messages that Mention you via @user
|
||||
<programlisting language="java"><![CDATA[<twitter:inbound-mention-channel-adapter twitter-connection="tc" channel="inbound_mentions">
|
||||
<poller fixed-rate="5000" max-messages-per-poll="3"/>
|
||||
</twitter:inbound-mention-channel-adapter>]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
<para>
|
||||
As you can see the configuration of all of these adapters is very similar to other inbound adapters with one exception.
|
||||
Each one needs to be injected with the <code>twitter-connection</code>. Once configured the Twitter Messages would be
|
||||
encapsulated into a Spring Integration Message and sent to a channel specified via <code>channel</code> attribute.
|
||||
Currently the Payload of the Message is <classname>twitter4j.DirectMessage</classname> for <emphasis>Inbound Direct Message Channel Adapter</emphasis>
|
||||
or <classname>twitter4j.Status</classname> for <emphasis>Inbound Update Channel Adapter</emphasis> and
|
||||
<emphasis>Inbound Mention Message Channel Adapter</emphasis>.
|
||||
</para>
|
||||
<para>
|
||||
For example; to get the text from the <classname>twitter4j.DirectMessage</classname> or <classname>twitter4j.Status</classname>
|
||||
simply invoke <code>getText()</code> method. For more information please refer to <link linkend="http://twitter4j.org/en/index.html">Twitter4J API</link>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="twitter-outbound">
|
||||
<title>Twitter Outbound Adapter</title>
|
||||
<para>
|
||||
Twitter outbound channels adapters allow you to send Twitter Messages - tweets
|
||||
</para>
|
||||
<para>
|
||||
Current release of Spring Integration supports sending <emphasis>Status Update Messages</emphasis> and <emphasis>Direct Messages</emphasis>.
|
||||
Twitter outbound channels adapters as any other outbound adapter will take the Message payload and send it as
|
||||
Twitter message. Currently the only supported payload type is <classname>String</classname>, so consider adding a <emphasis>transformer</emphasis>
|
||||
if the payload of the incoming message is not a String.
|
||||
</para>
|
||||
|
||||
<section id="outbound-twitter-update">
|
||||
<title>Twitter Outbound Update Channel Adapter</title>
|
||||
<para>
|
||||
This adapter allows you to send regular status updates by simply sending a Message to a channel
|
||||
identified via <code>channel</code> attribute.
|
||||
<programlisting language="java"><![CDATA[<twitter:outbound-update-channel-adapter twitter-connection="tc" channel="out"/>]]></programlisting>
|
||||
The only extra configuration that is required for this adapter is <code>twitter-connection</code>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="outbound-twitter-direct">
|
||||
<title>Twitter Outbound Direct Message Channel Adapter</title>
|
||||
<para>
|
||||
This adapter allows you to send Direct Twitter Messages (i.e., @user) by simply sending a Message to a channel
|
||||
identified via <code>channel</code> attribute.
|
||||
<programlisting language="java"><![CDATA[<twitter:outbound-dm-channel-adapter twitter-connection="tc" channel="inputChannel"/>]]></programlisting>
|
||||
The only extra configuration that is required for this adapter is <code>twitter-connection</code>
|
||||
</para>
|
||||
</section>
|
||||
<para>
|
||||
<important>Twitter does not allow you to post duplicate Messages. This is a common problem during testing when
|
||||
the same code works the first time but doesn't work the second time,so make sure to change the content of the Message.
|
||||
One thing that works good for testing is appent timestamp to the end of the message.
|
||||
</important>
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
@@ -123,6 +123,7 @@ uploadArchives {
|
||||
configurePom(deployer.pom)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Install gradle-built artifacts to the local m2 maven cache.
|
||||
* Further customizes the 'install' task contributed by the 'maven' plugin.
|
||||
@@ -134,6 +135,105 @@ install {
|
||||
configurePom(repositories.mavenInstaller.pom)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a Maven pom.xml for use at build time. Dependency information will
|
||||
* be based on Gradle metadata for the project, and other customizations such
|
||||
* as licensing information and source compatibility settings are configured
|
||||
* within.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @see http://gradle.org/0.9-preview-3/docs/userguide/userguide_single.html#pomBuilder
|
||||
*/
|
||||
task generatePom {
|
||||
group = 'Build'
|
||||
description = 'Generates a Maven POM file suitable for use in building the project'
|
||||
|
||||
generatedPomFileName = "pom.xml"
|
||||
|
||||
// enable partial cleaning with `gradle cleanGeneratePom`
|
||||
outputs.files(generatedPomFileName)
|
||||
|
||||
doLast() {
|
||||
// customize the pom creation process
|
||||
p = pom {
|
||||
project {
|
||||
name = project.description
|
||||
properties {
|
||||
setProperty('project.build.sourceEncoding', 'UTF8')
|
||||
}
|
||||
build {
|
||||
plugins {
|
||||
plugin {
|
||||
groupId = 'org.apache.maven.plugins'
|
||||
artifactId = 'maven-compiler-plugin'
|
||||
configuration {
|
||||
source = '1.5'
|
||||
target = '1.5'
|
||||
}
|
||||
}
|
||||
plugin {
|
||||
groupId = 'org.apache.maven.plugins'
|
||||
artifactId = 'maven-surefire-plugin'
|
||||
configuration {
|
||||
includes {
|
||||
include = '**/*Tests.java'
|
||||
}
|
||||
excludes {
|
||||
exclude = '**/*Abstract*.java'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resources {
|
||||
resource {
|
||||
directory = 'src/main/java'
|
||||
includes = ['**/*']
|
||||
excludes = ['**/*.java']
|
||||
}
|
||||
resource {
|
||||
directory = 'src/main/resources'
|
||||
includes = ['**/*']
|
||||
}
|
||||
}
|
||||
testResources {
|
||||
testResource {
|
||||
directory = 'src/test/java'
|
||||
includes = ['**/*']
|
||||
excludes = ['**/*.java']
|
||||
}
|
||||
testResource {
|
||||
directory = 'src/test/resources'
|
||||
includes = ['**/*']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// customizing the artifact id is a special case that must be configured
|
||||
// after the pom is fully configured, otherwise it'll be overwritten
|
||||
p.whenConfigured { pom -> pom.artifactId = project.name }
|
||||
|
||||
configurePom(p)
|
||||
|
||||
// write the pom.xml file out to the filesystem
|
||||
p.writeTo(generatedPomFileName)
|
||||
}
|
||||
|
||||
// ensure that pom generation happens every time resources are processed
|
||||
// (which practically means any time a build happens). if the dependencies
|
||||
// for the project have been updated (in $rootDir/build.gradle), the pom
|
||||
// will have diffs in it and the developer will be reminded to check in
|
||||
// the change during the next commit cycle.
|
||||
//processResources.dependsOn generatePom
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read dynamic 'optional' and 'provided' properties from gradle dependencies
|
||||
* and translate them to their maven POM equivalents.
|
||||
*/
|
||||
def configurePom(def pom) {
|
||||
pom.whenConfigured { generatedPom ->
|
||||
def optionalDeps = configurations.testRuntime.allDependencies.findAll { gradleDep ->
|
||||
|
||||
62
gradle/maven-root-pom.gradle
Normal file
62
gradle/maven-root-pom.gradle
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate a root Maven pom.xml for use at build time. Contains nothing other
|
||||
* than a 'modules' section aggregating child projects. This pom will never be
|
||||
* installed locally or deployed remotely. Child projects will not explicitly
|
||||
* declare this pom as their parent, given than nothing need be inherited from
|
||||
* it.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @see maven-deployment.gradle for per-project generatePom task
|
||||
*/
|
||||
task generatePom {
|
||||
apply plugin: 'maven'
|
||||
group = 'Build'
|
||||
description = 'Generates a root Maven pom for convenience.'
|
||||
|
||||
generatedPomFileName = "pom.xml"
|
||||
|
||||
// enable partial cleaning with `gradle cleanGeneratePom`
|
||||
outputs.files(generatedPomFileName)
|
||||
|
||||
doLast() {
|
||||
// customize the pom creation process
|
||||
p = pom {
|
||||
project {
|
||||
name = project.description
|
||||
packaging = 'pom'
|
||||
modules = javaprojects.collect { project -> project.name }
|
||||
}
|
||||
}
|
||||
|
||||
// customizing the artifact id is a special case that must be configured
|
||||
// after the pom is fully configured, otherwise it'll be overwritten
|
||||
p.whenConfigured { pom -> pom.artifactId = project.name }
|
||||
|
||||
// write the pom.xml file out to the filesystem
|
||||
p.writeTo(generatedPomFileName)
|
||||
}
|
||||
|
||||
// ensure that pom generation happens every time resources are processed
|
||||
// (which practically means any time a build happens). if the dependencies
|
||||
// for the project have been updated (in $rootDir/build.gradle), the pom
|
||||
// will have diffs in it and the developer will be reminded to check in
|
||||
// the change during the next commit cycle.
|
||||
//processResources.dependsOn generatePom
|
||||
}
|
||||
30
readme.txt
30
readme.txt
@@ -1,11 +1,31 @@
|
||||
TODO: complete
|
||||
============================== Spring Integration =============================
|
||||
To check out the project and build from source, do the following:
|
||||
|
||||
project distribution zip file can be built by executing
|
||||
git clone git://git.springsource.org/spring-integration/spring-integration.git
|
||||
cd spring-integration
|
||||
./gradlew build
|
||||
|
||||
src/main/scripts/build-distribution.sh
|
||||
-------------------------------------------------------------------------------
|
||||
To generate Eclipse metadata (.classpath and .project files), do the following:
|
||||
|
||||
./gradlew eclipse
|
||||
|
||||
licensing, changelog and other project information can be found under
|
||||
Once complete, you may then import the projects into Eclipse as usual:
|
||||
|
||||
src/main/resources
|
||||
File -> Import -> Existing projects into workspace
|
||||
|
||||
Browse to the 'spring-integration' root directory. All projects should import
|
||||
free of errors.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
To generate IDEA metadata (.iml and .ipr files), do the following:
|
||||
|
||||
./gradlew idea
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
To build the JavaDoc, do the following from within the root directory:
|
||||
|
||||
./gradlew :docs:api
|
||||
|
||||
The result will be available in 'docs/build/api'.
|
||||
===============================================================================
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
rootProject.name = 'spring-integration'
|
||||
|
||||
include 'docs'
|
||||
include 'spring-integration-core'
|
||||
include 'spring-integration-event'
|
||||
|
||||
@@ -33,7 +33,20 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* The headers for a {@link Message}.
|
||||
* The headers for a {@link Message}.<br>
|
||||
* IMPORTANT: MessageHeaders are immutable. Any mutating operation (e.g., put(..), putAll(..) etc.)
|
||||
* will result in {@link UnsupportedOperationException}
|
||||
* To create MessageHeaders instance use fluent MessageBuilder API
|
||||
* <pre>
|
||||
* MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2");
|
||||
* </pre>
|
||||
* or create an instance of GenericMessage passing payload as {@link Object} and headers as a regular {@link Map}
|
||||
* <pre>
|
||||
* Map headers = new HashMap();
|
||||
* headers.put("key1", "value1");
|
||||
* headers.put("key2", "value2");
|
||||
* new GenericMessage("foo", headers);
|
||||
* </pre>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Mark Fisher
|
||||
@@ -192,19 +205,27 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
|
||||
/*
|
||||
* Unsupported operations
|
||||
*/
|
||||
|
||||
/**
|
||||
* Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException}
|
||||
*/
|
||||
public Object put(String key, Object value) {
|
||||
throw new UnsupportedOperationException("MessageHeaders is immutable.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException}
|
||||
*/
|
||||
public void putAll(Map<? extends String, ? extends Object> t) {
|
||||
throw new UnsupportedOperationException("MessageHeaders is immutable.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException}
|
||||
*/
|
||||
public Object remove(Object key) {
|
||||
throw new UnsupportedOperationException("MessageHeaders is immutable.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException}
|
||||
*/
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException("MessageHeaders is immutable.");
|
||||
}
|
||||
|
||||
@@ -57,6 +57,22 @@ public abstract class MessageProducerSupport extends AbstractEndpoint implements
|
||||
Assert.notNull(this.outputChannel, "outputChannel is required");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes no action by default. Subclasses may override this if they
|
||||
* need lifecycle-managed behavior.
|
||||
*/
|
||||
@Override
|
||||
protected void doStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes no action by default. Subclasses may override this if they
|
||||
* need lifecycle-managed behavior.
|
||||
*/
|
||||
@Override
|
||||
protected void doStop() {
|
||||
}
|
||||
|
||||
protected void sendMessage(Message<?> message) {
|
||||
if (message == null) {
|
||||
throw new MessagingException("cannot send a null message");
|
||||
|
||||
@@ -31,11 +31,13 @@ public abstract class AbstractScriptExecutingMessageProcessor<T> implements Mess
|
||||
public final T processMessage(Message<?> message) {
|
||||
try {
|
||||
return this.executeScript(getScriptSource(message), message);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessageHandlingException(message, "failed to execute script", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses must implement this method to create a script source, optionally using the message to locate or
|
||||
* create the script.
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* A message implementation that accepts a {@link Throwable} payload.
|
||||
* Once created this object is immutable.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Base Message class defining common properties such as id, payload, and headers.
|
||||
* Once created this object is immutable.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
|
||||
@@ -16,23 +16,25 @@
|
||||
|
||||
package org.springframework.integration.event.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.AbstractChannelAdapterParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.event.ApplicationEventInboundChannelAdapter;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
*/
|
||||
public class EventInboundChannelAdapterParser extends AbstractChannelAdapterParser{
|
||||
public class EventInboundChannelAdapterParser extends AbstractChannelAdapterParser {
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
|
||||
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder.rootBeanDefinition(ApplicationEventInboundChannelAdapter.class);
|
||||
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(adapterBuilder, element, "channel", "outputChannel");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "event-types");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "payload-expression");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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,44 +13,27 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.event.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.event.ApplicationEventPublishingMessageHandler;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 2.0
|
||||
*/
|
||||
public class EventOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser{
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition parseConsumer(Element element,
|
||||
ParserContext parserContext) {
|
||||
BeanDefinitionBuilder invokerBuilder = BeanDefinitionBuilder.genericBeanDefinition(ApplicationEventPublishingMessageHandler.class);
|
||||
// BeanComponentDefinition innerHandlerDefinition =
|
||||
// IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
|
||||
// if (innerHandlerDefinition == null){
|
||||
// Assert.hasText(element.getAttribute(IntegrationNamespaceUtils.REF_ATTRIBUTE),
|
||||
// "You must provide 'ref' attribute or register inner bean for " +
|
||||
// "Outbound Channel consumer.");
|
||||
// invokerBuilder.addConstructorArgReference(element.getAttribute(IntegrationNamespaceUtils.REF_ATTRIBUTE));
|
||||
// } else {
|
||||
// invokerBuilder.addConstructorArgValue(innerHandlerDefinition);
|
||||
// }
|
||||
// invokerBuilder.addConstructorArgValue(element.getAttribute(IntegrationNamespaceUtils.METHOD_ATTRIBUTE));
|
||||
// String order = element.getAttribute(IntegrationNamespaceUtils.ORDER);
|
||||
// if (StringUtils.hasText(order)) {
|
||||
// invokerBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, order);
|
||||
// }
|
||||
return invokerBuilder.getBeanDefinition();
|
||||
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler");
|
||||
return builder.getBeanDefinition();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.event;
|
||||
package org.springframework.integration.event.core;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.integration.Message;
|
||||
@@ -14,15 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.event;
|
||||
package org.springframework.integration.event.inbound;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ApplicationContextEvent;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -31,16 +33,18 @@ import org.springframework.util.CollectionUtils;
|
||||
/**
|
||||
* An inbound Channel Adapter that passes Spring {@link ApplicationEvent ApplicationEvents} within messages.
|
||||
* If a {@link #setPayloadExpression(String) payloadExpression} is provided, it will be evaluated against
|
||||
* the ApplicationEvent instance to create the Message payload.
|
||||
* the ApplicationEvent instance to create the Message payload. Otherwise, the event itself will be the payload.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class ApplicationEventInboundChannelAdapter extends MessageProducerSupport implements ApplicationListener<ApplicationEvent> {
|
||||
public class ApplicationEventListeningMessageProducer extends MessageProducerSupport implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
private final Set<Class<? extends ApplicationEvent>> eventTypes = new CopyOnWriteArraySet<Class<? extends ApplicationEvent>>();
|
||||
|
||||
private volatile Expression payloadExpression;
|
||||
|
||||
private volatile boolean active;
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
|
||||
@@ -77,29 +81,38 @@ public class ApplicationEventInboundChannelAdapter extends MessageProducerSuppor
|
||||
}
|
||||
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (CollectionUtils.isEmpty(this.eventTypes)) {
|
||||
this.sendEventAsMessage(event);
|
||||
return;
|
||||
}
|
||||
for (Class<? extends ApplicationEvent> eventType : this.eventTypes) {
|
||||
if (eventType.isAssignableFrom(event.getClass())) {
|
||||
if (this.active || event instanceof ApplicationContextEvent) {
|
||||
if (CollectionUtils.isEmpty(this.eventTypes)) {
|
||||
this.sendEventAsMessage(event);
|
||||
return;
|
||||
}
|
||||
for (Class<? extends ApplicationEvent> eventType : this.eventTypes) {
|
||||
if (eventType.isAssignableFrom(event.getClass())) {
|
||||
this.sendEventAsMessage(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEventAsMessage(ApplicationEvent event) {
|
||||
Object payload = (this.payloadExpression != null) ? this.payloadExpression.getValue(event) : event;
|
||||
this.sendMessage(MessageBuilder.withPayload(payload).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
private void sendEventAsMessage(ApplicationEvent event) {
|
||||
if (event.getSource() instanceof Message<?>) {
|
||||
this.sendMessage((Message<?>) event.getSource());
|
||||
}
|
||||
else {
|
||||
Object payload = (this.payloadExpression != null) ? this.payloadExpression.getValue(event) : event;
|
||||
this.sendMessage(MessageBuilder.withPayload(payload).build());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.event;
|
||||
package org.springframework.integration.event.outbound;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.event.core.MessagingEvent;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -31,7 +32,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class ApplicationEventPublishingMessageHandler<T> extends AbstractMessageHandler implements ApplicationEventPublisherAware {
|
||||
public class ApplicationEventPublishingMessageHandler extends AbstractMessageHandler implements ApplicationEventPublisherAware {
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@@ -43,7 +44,12 @@ public class ApplicationEventPublishingMessageHandler<T> extends AbstractMessage
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) {
|
||||
Assert.notNull(this.applicationEventPublisher, "applicationEventPublisher is required");
|
||||
this.applicationEventPublisher.publishEvent(new MessagingEvent(message));
|
||||
if (message.getPayload() instanceof ApplicationEvent) {
|
||||
this.applicationEventPublisher.publishEvent((ApplicationEvent) message.getPayload());
|
||||
}
|
||||
else {
|
||||
this.applicationEventPublisher.publishEvent(new MessagingEvent(message));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsd:schema xmlns="http://www.springframework.org/schema/integration/event"
|
||||
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/event"
|
||||
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/event"
|
||||
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:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Defines the configuration elements for Spring Integration Event Adapters.
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Defines the configuration elements for Spring Integration Event Adapters.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:annotation>
|
||||
|
||||
<xsd:element name="inbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Configures an inbound Channel Adapter which listens for an Application Context events, converts them to
|
||||
Messages and sends them to a 'channel'
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="id" type="xsd:ID" use="optional" />
|
||||
<xsd:attribute name="channel" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies inbound 'channel' which accepts Messages generated from Application Context events.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="event-types" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Comma delimited list of event types (classes that extend ApplicationEvent) that
|
||||
this adapter should send to the message channel. By default, all event
|
||||
types will be sent [OPTIONAL]
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="payload-expression">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
SpEL expression to be evaluated against the ApplicationEvent to create the payload instance.
|
||||
If not provided, the ApplicationEvent itself will be used as the payload.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="outbound-channel-adapter">
|
||||
<xsd:element name="inbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines a Channel Adapter that receives from a MessageChannel and passes to
|
||||
a method-invoking
|
||||
MessageHandler.
|
||||
Configures an inbound Channel Adapter which listens for Application Context
|
||||
events, converts them to Messages and sends them to a Message Channel.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="id" type="xsd:ID" use="optional" />
|
||||
<xsd:attribute name="channel" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.core.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The channel to which Messages generated from Application Context events will be sent.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="event-types" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Comma delimited list of event types (classes that extend ApplicationEvent) that this adapter
|
||||
should send to the message channel. By default, all event types will be sent [OPTIONAL]
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="payload-expression">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
SpEL expression to be evaluated against the ApplicationEvent to create the payload instance.
|
||||
If not provided, the ApplicationEvent itself will be the payload.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="outbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines a Channel Adapter that receives Messages from a MessageChannel and then publishes
|
||||
MessagingEvents containing those Messages.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="channelAdapterType">
|
||||
<xsd:attribute name="order">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies the order for invocation when this endpoint is connected as a
|
||||
subscriber to a
|
||||
SubscribableChannel.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
<xsd:all>
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attribute name="id" type="xsd:ID" />
|
||||
<xsd:attribute name="channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type
|
||||
type="org.springframework.integration.core.MessageChannel" />
|
||||
</tool:annotation>
|
||||
<xsd:documentation>
|
||||
The Message Channel from which this adapter receives Messages.
|
||||
</xsd:documentation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies the order for invocation when this endpoint is connected as a
|
||||
subscriber to a SubscribableChannel.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
<xsd:complexType name="channelAdapterType">
|
||||
<xsd:all>
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attribute name="id" type="xsd:ID" />
|
||||
<xsd:attribute name="channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.event.ApplicationEventInboundChannelAdapter;
|
||||
import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -59,7 +59,7 @@ public class EventInboundChannelAdapterParserTests {
|
||||
public void validateEventParser() {
|
||||
Object adapter = context.getBean("eventAdapterSimple");
|
||||
Assert.assertNotNull(adapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventInboundChannelAdapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventListeningMessageProducer);
|
||||
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
|
||||
Assert.assertEquals(context.getBean("input"), adapterAccessor.getPropertyValue("outputChannel"));
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public class EventInboundChannelAdapterParserTests {
|
||||
public void validateEventParserWithEventTypes() {
|
||||
Object adapter = context.getBean("eventAdapterFiltered");
|
||||
Assert.assertNotNull(adapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventInboundChannelAdapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventListeningMessageProducer);
|
||||
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
|
||||
Assert.assertEquals(context.getBean("inputFiltered"), adapterAccessor.getPropertyValue("outputChannel"));
|
||||
Set<Class<? extends ApplicationEvent>> eventTypes = (Set<Class<? extends ApplicationEvent>>) adapterAccessor.getPropertyValue("eventTypes");
|
||||
@@ -84,7 +84,7 @@ public class EventInboundChannelAdapterParserTests {
|
||||
public void validateEventParserWithEventTypesAndPlaceholder() {
|
||||
Object adapter = context.getBean("eventAdapterFilteredPlaceHolder");
|
||||
Assert.assertNotNull(adapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventInboundChannelAdapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventListeningMessageProducer);
|
||||
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
|
||||
Assert.assertEquals(context.getBean("inputFilteredPlaceHolder"), adapterAccessor.getPropertyValue("outputChannel"));
|
||||
Set<Class<? extends ApplicationEvent>> eventTypes = (Set<Class<? extends ApplicationEvent>>) adapterAccessor.getPropertyValue("eventTypes");
|
||||
@@ -113,7 +113,7 @@ public class EventInboundChannelAdapterParserTests {
|
||||
public void validatePayloadExpression() {
|
||||
Object adapter = context.getBean("eventAdapterSpel");
|
||||
Assert.assertNotNull(adapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventInboundChannelAdapter);
|
||||
Assert.assertTrue(adapter instanceof ApplicationEventListeningMessageProducer);
|
||||
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
|
||||
Expression expression = (Expression) adapterAccessor.getPropertyValue("payloadExpression");
|
||||
Assert.assertEquals("source + '-test'", expression.getExpressionString());
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.integration.event.config;
|
||||
|
||||
import java.util.concurrent.BrokenBarrierException;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
|
||||
import junit.framework.Assert;
|
||||
@@ -34,7 +35,7 @@ import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.event.ApplicationEventPublishingMessageHandler;
|
||||
import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -46,13 +47,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class EventOutboundChannelAdapterParserTests {
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private boolean recievedEvent;
|
||||
|
||||
private volatile ConfigurableApplicationContext context;
|
||||
|
||||
private volatile boolean receivedEvent;
|
||||
|
||||
|
||||
@Test
|
||||
public void validateEventParser(){
|
||||
public void validateEventParser() {
|
||||
EventDrivenConsumer adapter = context.getBean("eventAdapter", EventDrivenConsumer.class);
|
||||
Assert.assertNotNull(adapter);
|
||||
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
|
||||
@@ -60,16 +63,16 @@ public class EventOutboundChannelAdapterParserTests {
|
||||
Assert.assertTrue(handler instanceof ApplicationEventPublishingMessageHandler);
|
||||
Assert.assertEquals(context.getBean("input"), adapterAccessor.getPropertyValue("inputChannel"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateUsage(){
|
||||
|
||||
ApplicationListener listener = new ApplicationListener<ApplicationEvent>() {
|
||||
public void validateUsage() {
|
||||
ApplicationListener<?> listener = new ApplicationListener<ApplicationEvent>() {
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
Object source = event.getSource();
|
||||
if (source instanceof Message){
|
||||
String payload = (String) ((Message)source).getPayload();
|
||||
if (payload.equals("hello")){
|
||||
recievedEvent = true;
|
||||
String payload = (String) ((Message<?>) source).getPayload();
|
||||
if (payload.equals("hello")) {
|
||||
receivedEvent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,34 +80,38 @@ public class EventOutboundChannelAdapterParserTests {
|
||||
context.addApplicationListener(listener);
|
||||
DirectChannel channel = context.getBean("input", DirectChannel.class);
|
||||
channel.send(new GenericMessage<String>("hello"));
|
||||
Assert.assertTrue(recievedEvent);
|
||||
Assert.assertTrue(receivedEvent);
|
||||
}
|
||||
|
||||
|
||||
@Test(timeout=2000)
|
||||
public void validateUsageWithPollableChannel() throws Exception {
|
||||
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("EventOutboundChannelAdapterParserTestsWithPollable-context.xml", EventOutboundChannelAdapterParserTests.class);
|
||||
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("EventOutboundChannelAdapterParserTestsWithPollable-context.xml", EventOutboundChannelAdapterParserTests.class);
|
||||
final CyclicBarrier barier = new CyclicBarrier(2);
|
||||
ApplicationListener listener = new ApplicationListener<ApplicationEvent>() {
|
||||
ApplicationListener<?> listener = new ApplicationListener<ApplicationEvent>() {
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
Object source = event.getSource();
|
||||
if (source instanceof Message){
|
||||
String payload = (String) ((Message)source).getPayload();
|
||||
String payload = (String) ((Message<?>) source).getPayload();
|
||||
if (payload.equals("hello")){
|
||||
recievedEvent = true;
|
||||
receivedEvent = true;
|
||||
try {
|
||||
barier.await();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
catch (BrokenBarrierException e) {
|
||||
throw new IllegalStateException("broken barrier", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
ac.addApplicationListener(listener);
|
||||
QueueChannel channel = ac.getBean("input", QueueChannel.class);
|
||||
context.addApplicationListener(listener);
|
||||
QueueChannel channel = context.getBean("input", QueueChannel.class);
|
||||
channel.send(new GenericMessage<String>("hello"));
|
||||
barier.await();
|
||||
Assert.assertTrue(recievedEvent);
|
||||
Assert.assertTrue(receivedEvent);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
</int:channel>
|
||||
|
||||
<int-event:outbound-channel-adapter id="eventAdapter" channel="input">
|
||||
<int:poller max-messages-per-poll="1" task-executor="executor">
|
||||
<int:interval-trigger interval="100" time-unit="MILLISECONDS"/>
|
||||
</int:poller>
|
||||
<int:poller max-messages-per-poll="1" task-executor="executor" fixed-delay="100"/>
|
||||
</int-event:outbound-channel-adapter>
|
||||
|
||||
<task:executor id="executor" pool-size="5"/>
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.event;
|
||||
package org.springframework.integration.event.inbound;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
@@ -26,21 +31,20 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import org.springframework.integration.event.core.MessagingEvent;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class ApplicationEventInboundChannelAdapterTests {
|
||||
public class ApplicationEventListeningMessageProducerTests {
|
||||
|
||||
@Test
|
||||
public void anyApplicationEventSentByDefault() {
|
||||
QueueChannel channel = new QueueChannel();
|
||||
ApplicationEventInboundChannelAdapter adapter = new ApplicationEventInboundChannelAdapter();
|
||||
ApplicationEventListeningMessageProducer adapter = new ApplicationEventListeningMessageProducer();
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.start();
|
||||
Message<?> message1 = channel.receive(0);
|
||||
assertNull(message1);
|
||||
adapter.onApplicationEvent(new TestApplicationEvent1());
|
||||
@@ -57,9 +61,10 @@ public class ApplicationEventInboundChannelAdapterTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onlyConfiguredEventTypesAreSent() {
|
||||
QueueChannel channel = new QueueChannel();
|
||||
ApplicationEventInboundChannelAdapter adapter = new ApplicationEventInboundChannelAdapter();
|
||||
ApplicationEventListeningMessageProducer adapter = new ApplicationEventListeningMessageProducer();
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.setEventTypes(new Class[]{TestApplicationEvent1.class});
|
||||
adapter.start();
|
||||
Message<?> message1 = channel.receive(0);
|
||||
assertNull(message1);
|
||||
adapter.onApplicationEvent(new TestApplicationEvent1());
|
||||
@@ -96,9 +101,10 @@ public class ApplicationEventInboundChannelAdapterTests {
|
||||
@Test
|
||||
public void payloadExpressionEvaluatedAgainstApplicationEvent() {
|
||||
QueueChannel channel = new QueueChannel();
|
||||
ApplicationEventInboundChannelAdapter adapter = new ApplicationEventInboundChannelAdapter();
|
||||
ApplicationEventListeningMessageProducer adapter = new ApplicationEventListeningMessageProducer();
|
||||
adapter.setPayloadExpression("'received: ' + source");
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.start();
|
||||
Message<?> message1 = channel.receive(0);
|
||||
assertNull(message1);
|
||||
adapter.onApplicationEvent(new TestApplicationEvent1());
|
||||
@@ -111,6 +117,34 @@ public class ApplicationEventInboundChannelAdapterTests {
|
||||
assertEquals("received: event2", message3.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messagingEventReceived() {
|
||||
QueueChannel channel = new QueueChannel();
|
||||
ApplicationEventListeningMessageProducer adapter = new ApplicationEventListeningMessageProducer();
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.start();
|
||||
Message<?> message1 = channel.receive(0);
|
||||
assertNull(message1);
|
||||
adapter.onApplicationEvent(new MessagingEvent(new GenericMessage<String>("test")));
|
||||
Message<?> message2 = channel.receive(20);
|
||||
assertNotNull(message2);
|
||||
assertEquals("test", message2.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageAsSourceOrCustomEventType() {
|
||||
QueueChannel channel = new QueueChannel();
|
||||
ApplicationEventListeningMessageProducer adapter = new ApplicationEventListeningMessageProducer();
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.start();
|
||||
Message<?> message1 = channel.receive(0);
|
||||
assertNull(message1);
|
||||
adapter.onApplicationEvent(new TestMessagingEvent(new GenericMessage<String>("test")));
|
||||
Message<?> message2 = channel.receive(20);
|
||||
assertNotNull(message2);
|
||||
assertEquals("test", message2.getPayload());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class TestApplicationEvent1 extends ApplicationEvent {
|
||||
@@ -118,8 +152,6 @@ public class ApplicationEventInboundChannelAdapterTests {
|
||||
public TestApplicationEvent1() {
|
||||
super("event1");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -131,4 +163,13 @@ public class ApplicationEventInboundChannelAdapterTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class TestMessagingEvent extends ApplicationEvent {
|
||||
|
||||
public TestMessagingEvent(Message<?> message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<int:queue capacity="5"/>
|
||||
</int:channel>
|
||||
|
||||
<bean id="adapter" class="org.springframework.integration.event.ApplicationEventInboundChannelAdapter">
|
||||
<bean id="adapter" class="org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer">
|
||||
<property name="outputChannel" ref="channel"/>
|
||||
</bean>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.event;
|
||||
package org.springframework.integration.event.outbound;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -24,6 +24,8 @@ import org.junit.Test;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.event.core.MessagingEvent;
|
||||
import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
/**
|
||||
@@ -32,8 +34,7 @@ import org.springframework.integration.message.GenericMessage;
|
||||
public class ApplicationEventPublishingMessageHandlerTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSendingEvent() throws InterruptedException {
|
||||
public void messagingEvent() throws InterruptedException {
|
||||
TestApplicationEventPublisher publisher = new TestApplicationEventPublisher();
|
||||
ApplicationEventPublishingMessageHandler handler = new ApplicationEventPublishingMessageHandler();
|
||||
handler.setApplicationEventPublisher(publisher);
|
||||
@@ -45,6 +46,19 @@ public class ApplicationEventPublishingMessageHandlerTests {
|
||||
assertEquals(message, ((MessagingEvent) event).getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void payloadAsEvent() {
|
||||
TestApplicationEventPublisher publisher = new TestApplicationEventPublisher();
|
||||
ApplicationEventPublishingMessageHandler handler = new ApplicationEventPublishingMessageHandler();
|
||||
handler.setApplicationEventPublisher(publisher);
|
||||
assertNull(publisher.getLastEvent());
|
||||
Message<?> message = new GenericMessage<TestEvent>(new TestEvent("foo"));
|
||||
handler.handleMessage(message);
|
||||
ApplicationEvent event = publisher.getLastEvent();
|
||||
assertEquals(TestEvent.class, event.getClass());
|
||||
assertEquals("foo", ((TestEvent) event).getSource());
|
||||
}
|
||||
|
||||
|
||||
private static class TestApplicationEventPublisher implements ApplicationEventPublisher {
|
||||
|
||||
@@ -59,4 +73,13 @@ public class ApplicationEventPublishingMessageHandlerTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class TestEvent extends ApplicationEvent {
|
||||
|
||||
public TestEvent(String text) {
|
||||
super(text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class FeedInboundChannelAdapterParser extends AbstractPollingInboundChann
|
||||
@Override
|
||||
protected BeanMetadataElement parseSource(final Element element, final ParserContext parserContext) {
|
||||
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.feed.FeedEntryMessageSource");
|
||||
"org.springframework.integration.feed.inbound.FeedEntryMessageSource");
|
||||
sourceBuilder.addConstructorArgValue(element.getAttribute("url"));
|
||||
String feedFetcherRef = element.getAttribute("feed-fetcher");
|
||||
if (StringUtils.hasText(feedFetcherRef)) {
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.feed;
|
||||
package org.springframework.integration.feed.inbound;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@@ -73,7 +74,7 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final Comparator<SyndEntry> syndEntryComparator = new SyndEntryComparator();
|
||||
private final Comparator<SyndEntry> syndEntryComparator = new SyndEntryPublishedDateComparator();
|
||||
|
||||
private final Object feedMonitor = new Object();
|
||||
|
||||
@@ -167,7 +168,12 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements
|
||||
if (next == null) {
|
||||
return null;
|
||||
}
|
||||
this.lastTime = next.getPublishedDate().getTime();
|
||||
if (next.getPublishedDate() != null) {
|
||||
this.lastTime = next.getPublishedDate().getTime();
|
||||
}
|
||||
else {
|
||||
this.lastTime += 1;
|
||||
}
|
||||
this.metadataStore.put(this.metadataKey, this.lastTime + "");
|
||||
return next;
|
||||
}
|
||||
@@ -178,10 +184,14 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements
|
||||
if (syndFeed != null) {
|
||||
List<SyndEntry> retrievedEntries = (List<SyndEntry>) syndFeed.getEntries();
|
||||
if (!CollectionUtils.isEmpty(retrievedEntries)) {
|
||||
boolean withinNewEntries = false;
|
||||
Collections.sort(retrievedEntries, this.syndEntryComparator);
|
||||
for (SyndEntry entry : retrievedEntries) {
|
||||
if (entry.getPublishedDate().getTime() > this.lastTime) {
|
||||
Date entryDate = getLastModifiedDate(entry);
|
||||
if ((entryDate != null && entryDate.getTime() > this.lastTime)
|
||||
|| (entryDate == null && withinNewEntries)) {
|
||||
this.entries.add(entry);
|
||||
withinNewEntries = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,11 +220,23 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements
|
||||
return feed;
|
||||
}
|
||||
|
||||
private static Date getLastModifiedDate(SyndEntry entry) {
|
||||
return (entry.getUpdatedDate() != null) ? entry.getUpdatedDate() : entry.getPublishedDate();
|
||||
}
|
||||
|
||||
private static class SyndEntryComparator implements Comparator<SyndEntry> {
|
||||
|
||||
private static class SyndEntryPublishedDateComparator implements Comparator<SyndEntry> {
|
||||
|
||||
public int compare(SyndEntry entry1, SyndEntry entry2) {
|
||||
return entry1.getPublishedDate().compareTo(entry2.getPublishedDate());
|
||||
Date date1 = getLastModifiedDate(entry1);
|
||||
Date date2 = getLastModifiedDate(entry2);
|
||||
if (date1 != null && date2 != null) {
|
||||
return date1.compareTo(date2);
|
||||
}
|
||||
if (date1 == null && date2 == null) {
|
||||
return 0;
|
||||
}
|
||||
return (date2 == null) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,14 +249,20 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements
|
||||
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());
|
||||
if (logger.isDebugEnabled()) {
|
||||
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());
|
||||
if (logger.isDebugEnabled()) {
|
||||
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());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("\tEVENT: Feed Unchanged. URL = " + event.getUrlString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
auto-startup="false"
|
||||
feed-fetcher="fileUrlFeedFetcher"
|
||||
metadata-store="customMetadataStore"
|
||||
url="file:src/test/java/org/springframework/integration/feed/config/sample.rss">
|
||||
url="file:src/test/java/org/springframework/integration/feed/sample.rss">
|
||||
<int:poller fixed-rate="10000" max-messages-per-poll="100" />
|
||||
</feed:inbound-channel-adapter>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.FileUrlFeedFetcher"/>
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.inbound.FileUrlFeedFetcher"/>
|
||||
|
||||
<bean id="customMetadataStore" class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests.SampleMetadataStore"/>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<int-feed:inbound-channel-adapter id="feedAdapterUsage"
|
||||
channel="feedChannelUsage"
|
||||
url="file:src/test/java/org/springframework/integration/feed/config/sample.rss"
|
||||
url="file:src/test/java/org/springframework/integration/feed/sample.rss"
|
||||
feed-fetcher="fileUrlFeedFetcher">
|
||||
<int:poller fixed-rate="10000" max-messages-per-poll="100" fixed-delay="10000"/>
|
||||
</int-feed:inbound-channel-adapter>
|
||||
@@ -19,7 +19,7 @@
|
||||
<bean class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests$SampleService" />
|
||||
</int:service-activator>
|
||||
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.FileUrlFeedFetcher"/>
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.inbound.FileUrlFeedFetcher"/>
|
||||
|
||||
<bean id="metadataStore" class="org.springframework.integration.store.PropertiesPersistingMetadataStore"/>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
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"
|
||||
url="file:src/test/java/org/springframework/integration/feed/config/sample.rss"
|
||||
url="file:src/test/java/org/springframework/integration/feed/sample.rss"
|
||||
feed-fetcher="fileUrlFeedFetcher">
|
||||
<int:poller fixed-rate="10000" max-messages-per-poll="100" fixed-delay="10000"/>
|
||||
</int-feed:inbound-channel-adapter>
|
||||
@@ -16,6 +16,6 @@
|
||||
<bean class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests$SampleServiceNoHistory" />
|
||||
</int:service-activator>
|
||||
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.FileUrlFeedFetcher"/>
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.inbound.FileUrlFeedFetcher"/>
|
||||
|
||||
</beans>
|
||||
@@ -40,7 +40,7 @@ import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
import org.springframework.integration.feed.FeedEntryMessageSource;
|
||||
import org.springframework.integration.feed.inbound.FeedEntryMessageSource;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.store.MetadataStore;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<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>
|
||||
|
||||
<item>
|
||||
<title>
|
||||
Spring Integration adapters
|
||||
</title>
|
||||
<link>http://www.springsource.org/extensions/se-sia</link>
|
||||
<description>
|
||||
Spring Integration adapters are realy cool
|
||||
</description>
|
||||
<pubDate>Tue, 23 Apr 2010 12:34:58 EST</pubDate>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>
|
||||
Spring Integration download
|
||||
</title>
|
||||
<link>http://www.springsource.com/products/spring-community-download</link>
|
||||
<description>
|
||||
Download Spring Integration
|
||||
</description>
|
||||
<pubDate>Sun, 13 Feb 2010 14:12:17 EST</pubDate>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>
|
||||
Check out Spring Integration forums
|
||||
</title>
|
||||
<link>http://forum.springsource.org/forumdisplay.php?f=42</link>
|
||||
<description>
|
||||
Spring Integration forums are awesome
|
||||
</description>
|
||||
<pubDate>Wed, 13 Mar 2010 03:38:21 EST</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.feed;
|
||||
package org.springframework.integration.feed.inbound;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
@@ -27,6 +27,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.feed.inbound.FeedEntryMessageSource;
|
||||
import org.springframework.integration.store.PropertiesPersistingMetadataStore;
|
||||
|
||||
import com.sun.syndication.feed.synd.SyndEntry;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.feed;
|
||||
package org.springframework.integration.feed.inbound;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -39,7 +39,7 @@ import com.sun.syndication.io.XmlReader;
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
*/
|
||||
public class FileUrlFeedFetcher extends AbstractFeedFetcher {
|
||||
class FileUrlFeedFetcher extends AbstractFeedFetcher {
|
||||
|
||||
/**
|
||||
* Retrieve a SyndFeed for the given URL.
|
||||
@@ -1,161 +0,0 @@
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.entries.AcceptAllEntryListFilter;
|
||||
import org.springframework.integration.file.entries.EntryListFilter;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
|
||||
/**
|
||||
* Strategy class charged with knowing how to connect to a remote file system, scan it for new files and then downloading the file.
|
||||
* <p/>
|
||||
* The implementation should run through any configured {@link org.springframework.integration.file.entries.EntryListFilter}s
|
||||
* to ensure the entry is worth downloading.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSychronizer<T> extends AbstractEndpoint {
|
||||
/**
|
||||
* Should we <emphasis>delete</emphasis> the <b>source</b> file?
|
||||
* For an FTP server, for example, this would delete the original FTPFile instance
|
||||
* <p/>
|
||||
* At the moment I can simply see this triggering an implementation specific {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy}
|
||||
* implementation that knows how to delete an entry on the remote file system.
|
||||
*/
|
||||
protected boolean shouldDeleteSourceFile;
|
||||
|
||||
/**
|
||||
* the directory we're writing our synchronizations to
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* a {@link org.springframework.integration.file.entries.EntryListFilter} that we're running against the <emphasis>remote</emphasis> file system view!
|
||||
*/
|
||||
protected volatile EntryListFilter<T> filter = new AcceptAllEntryListFilter<T>();
|
||||
|
||||
/**
|
||||
* the {@link java.util.concurrent.ScheduledFuture} instance we get when we schedule our {@link AbstractInboundRemoteFileSystemSychronizer.SynchronizeTask}
|
||||
*/
|
||||
protected ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
/**
|
||||
* Used to store the {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy} implementation
|
||||
*/
|
||||
protected EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy;
|
||||
|
||||
/**
|
||||
* Obviously thread safe - simply provides a NOOP impl so we don't have to keep dancing around NPE's
|
||||
*/
|
||||
private EntryAcknowledgmentStrategy<T> noOpEntryAcknowledgmentStrategy = new EntryAcknowledgmentStrategy<T>() {
|
||||
public void acknowledge(Object o, T msg) {
|
||||
}
|
||||
};
|
||||
|
||||
public void setEntryAcknowledgmentStrategy(EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy) {
|
||||
this.entryAcknowledgmentStrategy = entryAcknowledgmentStrategy;
|
||||
}
|
||||
|
||||
public void setShouldDeleteSourceFile(boolean shouldDeleteSourceFile) {
|
||||
this.shouldDeleteSourceFile = shouldDeleteSourceFile;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setFilter(EntryListFilter<T> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param usefulContextOrClientData this is context information to be passed to the individual {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy} implementation.
|
||||
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy#acknowledge(Object, Object)} will be called
|
||||
* in line with the {@link org.springframework.integration.core.MessageSource#receive()} call so this could conceivably be a 'live' stateful
|
||||
* client (a connection?) that is inappropriate to cache as it has per-request state.
|
||||
* @param t leverages strategy implementations to enable different behavior. It's a hook to the entry ({@link T}) after it's been successfully downloaded.
|
||||
* Conceptually, you might delete the remote one or rename it or something
|
||||
* @throws Throwable escape hatch exception, let the adapter deal with it.
|
||||
*/
|
||||
protected void acknowledge(Object usefulContextOrClientData, T t)
|
||||
throws Throwable {
|
||||
Assert.notNull(this.entryAcknowledgmentStrategy != null, "entryAcknowledgmentStrategy can't be null!");
|
||||
this.entryAcknowledgmentStrategy.acknowledge(usefulContextOrClientData, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback where we need the implementation to do some specific work
|
||||
*
|
||||
* @throws Exception thrown if anything goes wrong
|
||||
*/
|
||||
protected abstract void syncRemoteToLocalFileSystem()
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStop() {
|
||||
Assert.notNull(this.scheduledFuture, "the 'scheduledFuture' can't be null!");
|
||||
this.scheduledFuture.cancel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value in millis dictating how frequently the trigger should fire
|
||||
*
|
||||
* @return a {@link org.springframework.scheduling.Trigger} implementation (likely,
|
||||
* {@link org.springframework.scheduling.support.PeriodicTrigger})
|
||||
*/
|
||||
protected abstract Trigger getTrigger();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStart() {
|
||||
if (this.entryAcknowledgmentStrategy == null) {
|
||||
this.entryAcknowledgmentStrategy = noOpEntryAcknowledgmentStrategy;
|
||||
}
|
||||
|
||||
this.scheduledFuture = this.getTaskScheduler().schedule(new SynchronizeTask(), this.getTrigger());
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy interface to expose a hook for dispatching, moving, or deleting the file once it's been delivered.
|
||||
* This will typically be a NOOP for the implementation. Adapters should (for consistency) expose an attribute
|
||||
* dictating whether the adapter will delete the <emphasis>source</emphasis> entry on the remote file system.
|
||||
* This is the file-system version of an <code>ack-mode</code>. Future implementations should consider
|
||||
* exposing a custom attribute that plugs a custom {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy}
|
||||
* into the pipeline and also some more advanced scenarios (i.e., 'move file to another folder on delete ', or 'rename on delete')
|
||||
*
|
||||
* @param <T> the entry type (file, sftp, ftp, ...)
|
||||
*/
|
||||
public static interface EntryAcknowledgmentStrategy<T> {
|
||||
/**
|
||||
* Semantics are simple. You get a pointer to the entry just processed and any kind of helper data you could ask for. Since the strategy is a
|
||||
* singleton and the clients you might ask for as context data are pooled, it's not recommended that you try to cache them.
|
||||
*
|
||||
* @param useful any context data
|
||||
* @param msg the data / file / entry you want to process -- specific to sublcasses
|
||||
* @throws Exception thrown for any old reason
|
||||
*/
|
||||
void acknowledge(Object useful, T msg) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* This {@link Runnable} is launched as a background thread and is used to babysit the
|
||||
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer#localDirectory},
|
||||
* queueing and delivering accumulated files as possible.
|
||||
*/
|
||||
class SynchronizeTask implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
syncRemoteToLocalFileSystem();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.file.entries.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Ultimately, this factors out a lot of the common logic between the FTP and SFTP adapters. Designed to be extendable to handle
|
||||
* adapters whose task it is to synchronize a remote file system with a local file system (NB: this does *NOT* handle pushing files TO the remote
|
||||
* file system that exist uniquely in the local file system. It only handles bringing down the remote file system - as you'd expect
|
||||
* an 'inbound' adapter would).
|
||||
* <p/>
|
||||
* The base class supports configuration of whether the remote file system and local file system's directories should
|
||||
* be created on start (what 'creating a directory' means to the specific adapter is of course implementaton specific).
|
||||
* <p/>
|
||||
* This class is to be used as a pair with an implementation of
|
||||
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer<T>}. This synchronizer
|
||||
* must handle the work of actually connecting to the remote file system and delivering new {@link java.io.File}s.
|
||||
* The synchronizer is designed to be
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSynchronizingMessageSource<Y, T extends AbstractInboundRemoteFileSystemSychronizer<Y>> extends MessageProducerSupport implements MessageSource<File> {
|
||||
/**
|
||||
* Extension used when downloading files. We change it right after we know it's downloaded
|
||||
*/
|
||||
public static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
|
||||
|
||||
/**
|
||||
* Should the endpoint attempt to create the local directory and / or the remote directory?
|
||||
*/
|
||||
protected volatile boolean autoCreateDirectories = true;
|
||||
|
||||
/**
|
||||
* An implementation that will handle the chores of actually connecting to and syncing up the remote FS with the local one, in an inbound direction
|
||||
*/
|
||||
protected volatile T synchronizer;
|
||||
|
||||
/**
|
||||
* What directory should things be synced to locally ?
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* The actual {@link FileReadingMessageSource} that we continue to trust to do the job monitoring the filesystem once files are moved down
|
||||
*/
|
||||
protected volatile FileReadingMessageSource fileSource;
|
||||
|
||||
/**
|
||||
* The predicate to use in scanning the remote Fs for downloads
|
||||
*/
|
||||
protected EntryListFilter<Y> remotePredicate;
|
||||
|
||||
public void setAutoCreateDirectories(boolean autoCreateDirectories) {
|
||||
this.autoCreateDirectories = autoCreateDirectories;
|
||||
}
|
||||
|
||||
public void setSynchronizer(T synchronizer) {
|
||||
this.synchronizer = synchronizer;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setRemotePredicate(EntryListFilter<Y> remotePredicate) {
|
||||
this.remotePredicate = remotePredicate;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private EntryListFilter<File> buildFilter() {
|
||||
FileEntryNamer fileEntryNamer = new FileEntryNamer();
|
||||
Pattern completePattern = Pattern.compile("^.*(?<!" + INCOMPLETE_EXTENSION + ")$");
|
||||
return new CompositeEntryListFilter<File>(
|
||||
Arrays.asList(
|
||||
new AcceptOnceEntryFileListFilter<File>(), new PatternMatchingEntryListFilter<File>(fileEntryNamer, completePattern)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
try {
|
||||
if (this.remotePredicate != null) {
|
||||
this.synchronizer.setFilter(this.remotePredicate);
|
||||
}
|
||||
|
||||
if (this.localDirectory != null && !this.localDirectory.exists()){
|
||||
if (this.autoCreateDirectories){
|
||||
logger.debug("The '" + localDirectory + "' directory doesn't exist. Creating " + this.localDirectory);
|
||||
this.localDirectory.getFile().mkdirs();
|
||||
} else {
|
||||
throw new FileNotFoundException(localDirectory.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles making sure the remote files get here in one piece
|
||||
*/
|
||||
this.synchronizer.setLocalDirectory(this.localDirectory);
|
||||
this.synchronizer.setTaskScheduler(this.getTaskScheduler());
|
||||
this.synchronizer.setBeanFactory(this.getBeanFactory());
|
||||
this.synchronizer.setPhase(this.getPhase());
|
||||
this.synchronizer.setBeanName(this.getComponentName());
|
||||
|
||||
/**
|
||||
* Handles forwarding files once they ultimately appear in the {@link #localDirectory}
|
||||
*/
|
||||
this.fileSource = new FileReadingMessageSource();
|
||||
this.fileSource.setFilter(buildFilter());
|
||||
this.fileSource.setDirectory(this.localDirectory.getFile());
|
||||
this.fileSource.afterPropertiesSet();
|
||||
this.synchronizer.afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException){
|
||||
throw (RuntimeException)e;
|
||||
} else {
|
||||
throw new MessagingException("Failure during initialization of MessageSource for: " + this.getComponentType(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Message<File> receive() {
|
||||
return this.fileSource.receive();
|
||||
}
|
||||
}
|
||||
@@ -13,66 +13,71 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
|
||||
import org.springframework.integration.file.entries.EntryListFilter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Default directory scanner and base class for other directory scanners. It takes care of the default interrelations
|
||||
* between filtering, scanning and locking.
|
||||
*
|
||||
* Default directory scanner and base class for other directory scanners.
|
||||
* Manages the default interrelations between filtering, scanning and locking.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultDirectoryScanner implements DirectoryScanner {
|
||||
private EntryListFilter<File> filter = new AcceptOnceEntryFileListFilter<File>();
|
||||
private FileLocker locker;
|
||||
|
||||
public final List<File> listFiles(File directory) throws IllegalArgumentException {
|
||||
File[] files = listEligibleFiles(directory);
|
||||
private volatile EntryListFilter<File> filter = new AcceptOnceEntryFileListFilter<File>();
|
||||
|
||||
if (files == null) {
|
||||
throw new MessagingException("The path [" + directory + "] does not denote a properly accessible directory.");
|
||||
}
|
||||
private volatile FileLocker locker;
|
||||
|
||||
return this.filter.filterEntries(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may refine the listing strategy by overriding this method. The files returned here are passed onto the
|
||||
* filter.
|
||||
*
|
||||
* @param directory root directory to use for listing
|
||||
* @return the files this scanner should consider
|
||||
*/
|
||||
protected File[] listEligibleFiles(File directory) {
|
||||
return directory.listFiles();
|
||||
}
|
||||
public void setFilter(EntryListFilter<File> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public void setFilter(EntryListFilter<File> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public final void setLocker(FileLocker locker) {
|
||||
this.locker = locker;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p/>
|
||||
* This class takes the minimal implementation and merely delegates to the locker if set.
|
||||
*/
|
||||
public final boolean tryClaim(File file) {
|
||||
return (locker == null) || locker.lock(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public final void setLocker(FileLocker locker) {
|
||||
this.locker = locker;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p/>
|
||||
* This class takes the minimal implementation and merely delegates to the
|
||||
* locker if set.
|
||||
*/
|
||||
public final boolean tryClaim(File file) {
|
||||
return (this.locker == null) || this.locker.lock(file);
|
||||
}
|
||||
|
||||
public final List<File> listFiles(File directory) throws IllegalArgumentException {
|
||||
File[] files = listEligibleFiles(directory);
|
||||
if (files == null) {
|
||||
throw new MessagingException("The path [" + directory
|
||||
+ "] does not denote a properly accessible directory.");
|
||||
}
|
||||
return (this.filter != null) ? this.filter.filterEntries(files) : Arrays.asList(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may refine the listing strategy by overriding this method. The
|
||||
* files returned here are passed onto the filter.
|
||||
*
|
||||
* @param directory root directory to use for listing
|
||||
* @return the files this scanner should consider
|
||||
*/
|
||||
protected File[] listEligibleFiles(File directory) {
|
||||
return directory.listFiles();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -49,8 +49,7 @@ public class DefaultFileNameGenerator implements FileNameGenerator {
|
||||
|
||||
public String generateFileName(Message<?> message) {
|
||||
Object filenameProperty = message.getHeaders().get(this.headerName);
|
||||
if (filenameProperty instanceof String
|
||||
&& StringUtils.hasText((String) filenameProperty)) {
|
||||
if (filenameProperty instanceof String && StringUtils.hasText((String) filenameProperty)) {
|
||||
return (String) filenameProperty;
|
||||
}
|
||||
if (message.getPayload() instanceof File) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -22,52 +22,57 @@ import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Strategy for scanning directories. Implementations may select all children and grandchildren of the scanned directory
|
||||
* in any order. This interface is intended to enable the customization of selection, locking and ordering of files in a
|
||||
* directory like RecursiveDirectoryScanner. If the only requirement is to ignore certain files a EntryListFilter
|
||||
* implementation should suffice.
|
||||
*
|
||||
* Strategy for scanning directories. Implementations may select all children
|
||||
* and grandchildren of the scanned directory in any order. This interface is
|
||||
* intended to enable the customization of selection, locking and ordering of
|
||||
* files in a directory like RecursiveDirectoryScanner. If the only requirement
|
||||
* is to ignore certain files a EntryListFilter implementation should suffice.
|
||||
*
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public interface DirectoryScanner {
|
||||
|
||||
/**
|
||||
* Scans the directory according to the strategy particular to this implementation and returns the selected files as
|
||||
* a File array. This method may never return files that are rejected by the filter.
|
||||
*
|
||||
* @param directory the directory to scan for files
|
||||
* @return a list of files representing the content of the directory
|
||||
* @throws IllegalArgumentException thrown if the input is incorrect
|
||||
*/
|
||||
List<File> listFiles(File directory) throws IllegalArgumentException;
|
||||
/**
|
||||
* Scans the directory according to the strategy particular to this
|
||||
* implementation and returns the selected files as a File array. This
|
||||
* method may never return files that are rejected by the filter.
|
||||
*
|
||||
* @param directory the directory to scan for files
|
||||
* @return a list of files representing the content of the directory
|
||||
* @throws IllegalArgumentException if the input is incorrect
|
||||
*/
|
||||
List<File> listFiles(File directory) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Sets a custom filter to be used by this scanner. The filter will get a chance to reject files before the scanner
|
||||
* presents them through its listFiles method. A scanner may use additional filtering that is out of the control of
|
||||
* the provided filter.
|
||||
*
|
||||
* @param filter the custom filter to be used
|
||||
*/
|
||||
void setFilter(EntryListFilter<File> filter);
|
||||
/**
|
||||
* Sets a custom filter to be used by this scanner. The filter will get a
|
||||
* chance to reject files before the scanner presents them through its
|
||||
* listFiles method. A scanner may use additional filtering that is out of
|
||||
* the control of the provided filter.
|
||||
*
|
||||
* @param filter
|
||||
* the custom filter to be used
|
||||
*/
|
||||
void setFilter(EntryListFilter<File> filter);
|
||||
|
||||
/**
|
||||
* Sets a custom locker to be used by this scanner. The locker will get a
|
||||
* chance to lock files and reject claims on files that are already locked.
|
||||
*
|
||||
* @param locker
|
||||
* the custom locker to be used
|
||||
*/
|
||||
void setLocker(FileLocker locker);
|
||||
|
||||
/**
|
||||
* Claim the file to process. It is up to the implementation to decide what additional safe guards are required to
|
||||
* attain a claim to the file. But if a locker is set implementations MUST invoke its <code>lock</code> method and
|
||||
* MUST return <code>false<code> if the locker did not grant the lock.
|
||||
*
|
||||
* @param file file to be claimed
|
||||
* @return true if the claim was granted false otherwise
|
||||
*/
|
||||
boolean tryClaim(File file);
|
||||
/**
|
||||
* Claim the file to process. It is up to the implementation to decide what
|
||||
* additional safe guards are required to attain a claim to the file. But if
|
||||
* a locker is set implementations MUST invoke its <code>lock</code> method
|
||||
* and MUST return <code>false<code> if the locker did not grant the lock.
|
||||
*
|
||||
* @param file
|
||||
* file to be claimed
|
||||
* @return true if the claim was granted false otherwise
|
||||
*/
|
||||
boolean tryClaim(File file);
|
||||
|
||||
/**
|
||||
* Sets a custom locker to be used by this scanner. The locker will get a chance to lock files and reject claims on
|
||||
* files that are already locked.
|
||||
*
|
||||
* @param locker the custom locker to be used
|
||||
*/
|
||||
void setLocker(FileLocker locker);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.file;
|
||||
|
||||
import java.io.File;
|
||||
@@ -34,232 +35,275 @@ import org.springframework.integration.file.entries.EntryListFilter;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* {@link MessageSource} that creates messages from a file system directory. To prevent messages for certain files, you
|
||||
* may supply a {@link org.springframework.integration.file.entries.EntryListFilter}. By default,
|
||||
* an {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} is used. It ensures files are
|
||||
* picked up only once from the directory.
|
||||
* {@link MessageSource} that creates messages from a file system directory. To
|
||||
* prevent messages for certain files, you may supply a
|
||||
* {@link org.springframework.integration.file.entries.EntryListFilter}. By
|
||||
* default, an
|
||||
* {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter}
|
||||
* is used. It ensures files are picked up only once from the directory.
|
||||
* <p/>
|
||||
* A common problem with reading files is that a file may be detected before it is ready. The default {@link
|
||||
* org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} does not prevent this. In most cases, this can be prevented if the file-writing process
|
||||
* renames each file as soon as it is ready for reading. A pattern-matching filter that accepts only files that are
|
||||
* ready (e.g. based on a known suffix), composed with the default {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} would allow for
|
||||
* this. See {@link org.springframework.integration.file.entries.CompositeEntryListFilter} for a way to do this.
|
||||
* A common problem with reading files is that a file may be detected before it
|
||||
* is ready. The default
|
||||
* {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter}
|
||||
* does not prevent this. In most cases, this can be prevented if the
|
||||
* file-writing process renames each file as soon as it is ready for reading. A
|
||||
* pattern-matching filter that accepts only files that are ready (e.g. based on
|
||||
* a known suffix), composed with the default
|
||||
* {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter}
|
||||
* would allow for this. See
|
||||
* {@link org.springframework.integration.file.entries.CompositeEntryListFilter}
|
||||
* for a way to do this.
|
||||
* <p/>
|
||||
* A {@link Comparator} can be used to ensure internal ordering of the Files in a {@link PriorityBlockingQueue}. This
|
||||
* does not provide the same guarantees as a {@link ResequencingMessageGroupProcessor}, but in cases where writing files and failure
|
||||
* downstream are rare it might be sufficient.
|
||||
* A {@link Comparator} can be used to ensure internal ordering of the Files in
|
||||
* a {@link PriorityBlockingQueue}. This does not provide the same guarantees as
|
||||
* a {@link ResequencingMessageGroupProcessor}, but in cases where writing files
|
||||
* and failure downstream are rare it might be sufficient.
|
||||
* <p/>
|
||||
* FileReadingMessageSource is fully thread-safe under concurrent <code>receive()</code> invocations and message
|
||||
* delivery callbacks.
|
||||
*
|
||||
* FileReadingMessageSource is fully thread-safe under concurrent
|
||||
* <code>receive()</code> invocations and message delivery callbacks.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class FileReadingMessageSource extends IntegrationObjectSupport implements MessageSource<File>{
|
||||
private static final int DEFAULT_INTERNAL_QUEUE_CAPACITY = 5;
|
||||
private static final Log logger = LogFactory.getLog(FileReadingMessageSource.class);
|
||||
private volatile File directory;
|
||||
private volatile DirectoryScanner scanner = new DefaultDirectoryScanner();
|
||||
private volatile boolean autoCreateDirectory = true;
|
||||
public class FileReadingMessageSource extends IntegrationObjectSupport implements MessageSource<File> {
|
||||
|
||||
/*
|
||||
* {@link PriorityBlockingQueue#iterator()} throws {@link java.util.ConcurrentModificationException} in Java 5.
|
||||
* There is no locking around the queue, so there is also no iteration.
|
||||
*/
|
||||
private final Queue<File> toBeReceived;
|
||||
private boolean scanEachPoll = false;
|
||||
private static final int DEFAULT_INTERNAL_QUEUE_CAPACITY = 5;
|
||||
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a naturally ordered queue of unbounded capacity.
|
||||
*/
|
||||
public FileReadingMessageSource() {
|
||||
this(null);
|
||||
}
|
||||
private static final Log logger = LogFactory.getLog(FileReadingMessageSource.class);
|
||||
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a bounded queue of the given capacity. This can be used to reduce the
|
||||
* memory footprint of this component when reading from a large directory.
|
||||
*
|
||||
* @param internalQueueCapacity the size of the queue used to cache files to be received internally. This queue can
|
||||
* be made larger to optimize the directory scanning. With scanEachPoll set to false
|
||||
* and the queue to a large size, it will be filled once and then completely emptied
|
||||
* before a new directory listing is done. This is particularly useful to reduce scans
|
||||
* of large numbers of files in a directory.
|
||||
*/
|
||||
public FileReadingMessageSource(int internalQueueCapacity) {
|
||||
this(null);
|
||||
Assert.isTrue(internalQueueCapacity > 0, "Cannot create a queue with non positive capacity");
|
||||
this.setScanner(new HeadDirectoryScanner(internalQueueCapacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a {@link PriorityBlockingQueue} ordered with the passed in {@link
|
||||
* Comparator}
|
||||
* <p/>
|
||||
* The size of the queue used should be large enough to hold all the files in the input directory in order to sort
|
||||
* all of them, so restricting the size of the queue is mutually exclusive with ordering. No guarantees about file
|
||||
* delivery order can be made under concurrent access.
|
||||
* <p/>
|
||||
*
|
||||
* @param receptionOrderComparator the comparator to be used to order the files in the internal queue
|
||||
*/
|
||||
public FileReadingMessageSource(Comparator<File> receptionOrderComparator) {
|
||||
toBeReceived = new PriorityBlockingQueue<File>(DEFAULT_INTERNAL_QUEUE_CAPACITY, receptionOrderComparator);
|
||||
}
|
||||
private volatile File directory;
|
||||
|
||||
/**
|
||||
* Specify the input directory.
|
||||
*
|
||||
* @param directory to monitor
|
||||
*/
|
||||
public void setDirectory(File directory) {
|
||||
Assert.notNull(directory, "directory must not be null");
|
||||
this.directory = directory;
|
||||
}
|
||||
private volatile DirectoryScanner scanner = new DefaultDirectoryScanner();
|
||||
|
||||
/**
|
||||
* Optionally specify a custom scanner, for example the {@link org.springframework.integration.file.RecursiveLeafOnlyDirectoryScanner}
|
||||
*
|
||||
* @param scanner scanner impl
|
||||
*/
|
||||
public void setScanner(DirectoryScanner scanner) {
|
||||
this.scanner = scanner;
|
||||
}
|
||||
private volatile boolean autoCreateDirectory = true;
|
||||
|
||||
/**
|
||||
* Specify whether to create the source directory automatically if it does not yet exist upon initialization. By
|
||||
* default, this value is <emphasis>true</emphasis>. If set to <emphasis>false</emphasis> and the source directory
|
||||
* does not exist, an Exception will be thrown upon initialization.
|
||||
*
|
||||
* @param autoCreateDirectory should the directory to be monitored be created when this component starts up?
|
||||
*/
|
||||
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
|
||||
this.autoCreateDirectory = autoCreateDirectory;
|
||||
}
|
||||
/*
|
||||
* {@link PriorityBlockingQueue#iterator()} throws
|
||||
* {@link java.util.ConcurrentModificationException} in Java 5.
|
||||
* There is no locking around the queue, so there is also no iteration.
|
||||
*/
|
||||
private final Queue<File> toBeReceived;
|
||||
|
||||
/**
|
||||
* Sets a {@link org.springframework.integration.file.entries.EntryListFilter}. By default a {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} with no bounds is used. In most
|
||||
* cases a customized {@link org.springframework.integration.file.entries.EntryListFilter} will be needed to deal with modification and duplication concerns. If
|
||||
* multiple filters are required a {@link org.springframework.integration.file.entries.CompositeEntryListFilter} can be used to group them together.
|
||||
* <p/>
|
||||
* <b>The supplied filter must be thread safe.</b>.
|
||||
*
|
||||
* @param filter a filter
|
||||
*/
|
||||
public void setFilter(EntryListFilter<File> filter) {
|
||||
Assert.notNull(filter, "'filter' must not be null");
|
||||
this.scanner.setFilter(filter);
|
||||
}
|
||||
private volatile boolean scanEachPoll = false;
|
||||
|
||||
/**
|
||||
* Optional. Sets a {@link FileLocker} to be used to guard files
|
||||
* against duplicate processing.
|
||||
* <p/>
|
||||
* <b>The supplied FileLocker must be thread safe</b>
|
||||
*
|
||||
* @param locker a locker
|
||||
*/
|
||||
public void setLocker(FileLocker locker) {
|
||||
Assert.notNull(locker, "'fileLocker' must not be null.");
|
||||
this.scanner.setLocker(locker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional. Set this flag if you want to make sure the internal queue is refreshed with the latest content of the
|
||||
* input directory on each poll.
|
||||
* <p/>
|
||||
* By default this implementation will empty its queue before looking at the directory again. In cases where order
|
||||
* is relevant it is important to consider the effects of setting this flag. The internal {@link
|
||||
* java.util.concurrent.BlockingQueue} that this class is keeping will more likely be out of sync with the file
|
||||
* system if this flag is set to <code>false</code>, but it will change more often (causing expensive reordering) if
|
||||
* it is set to <code>true</code>.
|
||||
*
|
||||
* @param scanEachPoll whether or not the component should re-scan (as opposed to not rescanning until the entire backlog has been delivered)
|
||||
*/
|
||||
public void setScanEachPoll(boolean scanEachPoll) {
|
||||
this.scanEachPoll = scanEachPoll;
|
||||
}
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a naturally ordered queue of unbounded capacity.
|
||||
*/
|
||||
public FileReadingMessageSource() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
protected void onInit() {
|
||||
Assert.notNull(directory, "'directory' must not be set before initialization");
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a bounded queue of the given
|
||||
* capacity. This can be used to reduce the memory footprint of this
|
||||
* component when reading from a large directory.
|
||||
*
|
||||
* @param internalQueueCapacity
|
||||
* the size of the queue used to cache files to be received
|
||||
* internally. This queue can be made larger to optimize the
|
||||
* directory scanning. With scanEachPoll set to false and the
|
||||
* queue to a large size, it will be filled once and then
|
||||
* completely emptied before a new directory listing is done.
|
||||
* This is particularly useful to reduce scans of large numbers
|
||||
* of files in a directory.
|
||||
*/
|
||||
public FileReadingMessageSource(int internalQueueCapacity) {
|
||||
this(null);
|
||||
Assert.isTrue(internalQueueCapacity > 0,
|
||||
"Cannot create a queue with non positive capacity");
|
||||
this.setScanner(new HeadDirectoryScanner(internalQueueCapacity));
|
||||
}
|
||||
|
||||
if (!this.directory.exists() && this.autoCreateDirectory) {
|
||||
this.directory.mkdirs();
|
||||
}
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a {@link PriorityBlockingQueue}
|
||||
* ordered with the passed in {@link Comparator}
|
||||
* <p/>
|
||||
* The size of the queue used should be large enough to hold all the files
|
||||
* in the input directory in order to sort all of them, so restricting the
|
||||
* size of the queue is mutually exclusive with ordering. No guarantees
|
||||
* about file delivery order can be made under concurrent access.
|
||||
* <p/>
|
||||
*
|
||||
* @param receptionOrderComparator
|
||||
* the comparator to be used to order the files in the internal
|
||||
* queue
|
||||
*/
|
||||
public FileReadingMessageSource(Comparator<File> receptionOrderComparator) {
|
||||
this.toBeReceived = new PriorityBlockingQueue<File>(
|
||||
DEFAULT_INTERNAL_QUEUE_CAPACITY, receptionOrderComparator);
|
||||
}
|
||||
|
||||
Assert.isTrue(this.directory.exists(), "Source directory [" + directory + "] does not exist.");
|
||||
Assert.isTrue(this.directory.isDirectory(), "Source path [" + this.directory + "] does not point to a directory.");
|
||||
Assert.isTrue(this.directory.canRead(), "Source directory [" + this.directory + "] is not readable.");
|
||||
}
|
||||
|
||||
public Message<File> receive() throws MessagingException {
|
||||
Message<File> message = null;
|
||||
/**
|
||||
* Specify the input directory.
|
||||
*
|
||||
* @param directory to monitor
|
||||
*/
|
||||
public void setDirectory(File directory) {
|
||||
Assert.notNull(directory, "directory must not be null");
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
// rescan only if needed or explicitly configured
|
||||
if (scanEachPoll || toBeReceived.isEmpty()) {
|
||||
scanInputDirectory();
|
||||
}
|
||||
/**
|
||||
* Optionally specify a custom scanner, for example the
|
||||
* {@link org.springframework.integration.file.RecursiveLeafOnlyDirectoryScanner}
|
||||
*
|
||||
* @param scanner scanner implementation
|
||||
*/
|
||||
public void setScanner(DirectoryScanner scanner) {
|
||||
this.scanner = scanner;
|
||||
}
|
||||
|
||||
File file = toBeReceived.poll();
|
||||
/**
|
||||
* Specify whether to create the source directory automatically if it does
|
||||
* not yet exist upon initialization. By default, this value is
|
||||
* <emphasis>true</emphasis>. If set to <emphasis>false</emphasis> and the
|
||||
* source directory does not exist, an Exception will be thrown upon
|
||||
* initialization.
|
||||
*
|
||||
* @param autoCreateDirectory
|
||||
* should the directory to be monitored be created when this
|
||||
* component starts up?
|
||||
*/
|
||||
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
|
||||
this.autoCreateDirectory = autoCreateDirectory;
|
||||
}
|
||||
|
||||
// file == null means the queue was empty
|
||||
// we can't rely on isEmpty for concurrency reasons
|
||||
while ((file != null) && !scanner.tryClaim(file)) {
|
||||
file = toBeReceived.poll();
|
||||
}
|
||||
/**
|
||||
* Sets a
|
||||
* {@link org.springframework.integration.file.entries.EntryListFilter}. By
|
||||
* default a
|
||||
* {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter}
|
||||
* with no bounds is used. In most cases a customized
|
||||
* {@link org.springframework.integration.file.entries.EntryListFilter} will
|
||||
* be needed to deal with modification and duplication concerns. If multiple
|
||||
* filters are required a
|
||||
* {@link org.springframework.integration.file.entries.CompositeEntryListFilter}
|
||||
* can be used to group them together.
|
||||
* <p/>
|
||||
* <b>The supplied filter must be thread safe.</b>.
|
||||
*
|
||||
* @param filter a filter
|
||||
*/
|
||||
public void setFilter(EntryListFilter<File> filter) {
|
||||
Assert.notNull(filter, "'filter' must not be null");
|
||||
this.scanner.setFilter(filter);
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
message = MessageBuilder.withPayload(file).build();
|
||||
/**
|
||||
* Optional. Sets a {@link FileLocker} to be used to guard files against
|
||||
* duplicate processing.
|
||||
* <p/>
|
||||
* <b>The supplied FileLocker must be thread safe</b>
|
||||
*
|
||||
* @param locker a locker
|
||||
*/
|
||||
public void setLocker(FileLocker locker) {
|
||||
Assert.notNull(locker, "'fileLocker' must not be null.");
|
||||
this.scanner.setLocker(locker);
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Created message: [" + message + "]");
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private void scanInputDirectory() {
|
||||
List<File> filteredFiles = scanner.listFiles(directory);
|
||||
Set<File> freshFiles = new HashSet<File>(filteredFiles);
|
||||
|
||||
if (!freshFiles.isEmpty()) {
|
||||
toBeReceived.addAll(freshFiles);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Added to queue: " + freshFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the failed message back to the 'toBeReceived' queue if there is room.
|
||||
*
|
||||
* @param failedMessage the {@link org.springframework.integration.Message} that blew up
|
||||
*/
|
||||
public void onFailure(Message<File> failedMessage) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to send: " + failedMessage);
|
||||
}
|
||||
|
||||
toBeReceived.offer(failedMessage.getPayload());
|
||||
}
|
||||
|
||||
/**
|
||||
* The message is just logged. It was already removed from the queue during the call to <code>receive()</code>
|
||||
*
|
||||
* @param sentMessage the message that was successfully delivered
|
||||
*/
|
||||
public void onSend(Message<File> sentMessage) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sent: " + sentMessage);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Optional. Set this flag if you want to make sure the internal queue is
|
||||
* refreshed with the latest content of the input directory on each poll.
|
||||
* <p/>
|
||||
* By default this implementation will empty its queue before looking at the
|
||||
* directory again. In cases where order is relevant it is important to
|
||||
* consider the effects of setting this flag. The internal
|
||||
* {@link java.util.concurrent.BlockingQueue} that this class is keeping
|
||||
* will more likely be out of sync with the file system if this flag is set
|
||||
* to <code>false</code>, but it will change more often (causing expensive
|
||||
* reordering) if it is set to <code>true</code>.
|
||||
*
|
||||
* @param scanEachPoll
|
||||
* whether or not the component should re-scan (as opposed to not
|
||||
* rescanning until the entire backlog has been delivered)
|
||||
*/
|
||||
public void setScanEachPoll(boolean scanEachPoll) {
|
||||
this.scanEachPoll = scanEachPoll;
|
||||
}
|
||||
|
||||
public String getComponentType() {
|
||||
return "file:inbound-channel-adapter";
|
||||
}
|
||||
|
||||
protected void onInit() {
|
||||
Assert.notNull(directory, "'directory' must not be null");
|
||||
if (!this.directory.exists() && this.autoCreateDirectory) {
|
||||
this.directory.mkdirs();
|
||||
}
|
||||
Assert.isTrue(this.directory.exists(),
|
||||
"Source directory [" + directory + "] does not exist.");
|
||||
Assert.isTrue(this.directory.isDirectory(),
|
||||
"Source path [" + this.directory + "] does not point to a directory.");
|
||||
Assert.isTrue(this.directory.canRead(),
|
||||
"Source directory [" + this.directory + "] is not readable.");
|
||||
}
|
||||
|
||||
public Message<File> receive() throws MessagingException {
|
||||
Message<File> message = null;
|
||||
|
||||
// rescan only if needed or explicitly configured
|
||||
if (scanEachPoll || toBeReceived.isEmpty()) {
|
||||
scanInputDirectory();
|
||||
}
|
||||
|
||||
File file = toBeReceived.poll();
|
||||
|
||||
// file == null means the queue was empty
|
||||
// we can't rely on isEmpty for concurrency reasons
|
||||
while ((file != null) && !scanner.tryClaim(file)) {
|
||||
file = toBeReceived.poll();
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
message = MessageBuilder.withPayload(file).build();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Created message: [" + message + "]");
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
private void scanInputDirectory() {
|
||||
List<File> filteredFiles = scanner.listFiles(directory);
|
||||
Set<File> freshFiles = new HashSet<File>(filteredFiles);
|
||||
if (!freshFiles.isEmpty()) {
|
||||
toBeReceived.addAll(freshFiles);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Added to queue: " + freshFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the failed message back to the 'toBeReceived' queue if there is room.
|
||||
*
|
||||
* @param failedMessage
|
||||
* the {@link org.springframework.integration.Message} that failed
|
||||
*/
|
||||
public void onFailure(Message<File> failedMessage) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to send: " + failedMessage);
|
||||
}
|
||||
toBeReceived.offer(failedMessage.getPayload());
|
||||
}
|
||||
|
||||
/**
|
||||
* The message is just logged. It was already removed from the queue during
|
||||
* the call to <code>receive()</code>
|
||||
*
|
||||
* @param sentMessage
|
||||
* the message that was successfully delivered
|
||||
*/
|
||||
public void onSend(Message<File> sentMessage) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sent: " + sentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -204,9 +204,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
return resultFile;
|
||||
}
|
||||
|
||||
private File handleByteArrayMessage(byte[] bytes, File originalFile, File tempFile, File resultFile)
|
||||
throws IOException {
|
||||
|
||||
private File handleByteArrayMessage(byte[] bytes, File originalFile, File tempFile, File resultFile) throws IOException {
|
||||
FileCopyUtils.copy(bytes, tempFile);
|
||||
tempFile.renameTo(resultFile);
|
||||
if (this.deleteSourceFiles && originalFile != null) {
|
||||
@@ -215,11 +213,8 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
return resultFile;
|
||||
}
|
||||
|
||||
private File handleStringMessage(String content, File originalFile, File tempFile, File resultFile)
|
||||
throws IOException {
|
||||
|
||||
OutputStreamWriter writer = new OutputStreamWriter(
|
||||
new FileOutputStream(tempFile), this.charset);
|
||||
private File handleStringMessage(String content, File originalFile, File tempFile, File resultFile) throws IOException {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile), this.charset);
|
||||
FileCopyUtils.copy(content, writer);
|
||||
tempFile.renameTo(resultFile);
|
||||
if (this.deleteSourceFiles && originalFile != null) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import org.springframework.integration.file.entries.EntryListFilter;
|
||||
@@ -22,28 +23,32 @@ import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* A custom scanner that only returns the first <code>maxNumberOfFiles</code> elements from a directory listing. This is
|
||||
* useful to limit the number of File objects in memory and therefore mutually exclusive with AcceptOnceFileListFilter.
|
||||
*
|
||||
* A custom scanner that only returns the first <code>maxNumberOfFiles</code>
|
||||
* elements from a directory listing. This is useful to limit the number of File
|
||||
* objects in memory and therefore mutually exclusive with AcceptOnceFileListFilter.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @since 2.0.0
|
||||
* @since 2.0
|
||||
*/
|
||||
public class HeadDirectoryScanner extends DefaultDirectoryScanner {
|
||||
public HeadDirectoryScanner(int maxNumberOfFiles) {
|
||||
this.setFilter(new HeadFilter(maxNumberOfFiles));
|
||||
}
|
||||
|
||||
private class HeadFilter implements EntryListFilter<File> {
|
||||
private final int maxNumberOfFiles;
|
||||
public HeadDirectoryScanner(int maxNumberOfFiles) {
|
||||
this.setFilter(new HeadFilter(maxNumberOfFiles));
|
||||
}
|
||||
|
||||
public HeadFilter(int maxNumberOfFiles) {
|
||||
this.maxNumberOfFiles = maxNumberOfFiles;
|
||||
}
|
||||
|
||||
public List<File> filterEntries(File[] files) {
|
||||
return Arrays.asList(files).subList(0, Math.min(files.length, maxNumberOfFiles));
|
||||
}
|
||||
}
|
||||
private static class HeadFilter implements EntryListFilter<File> {
|
||||
|
||||
private final int maxNumberOfFiles;
|
||||
|
||||
public HeadFilter(int maxNumberOfFiles) {
|
||||
this.maxNumberOfFiles = maxNumberOfFiles;
|
||||
}
|
||||
|
||||
public List<File> filterEntries(File[] files) {
|
||||
return Arrays.asList(files).subList(0, Math.min(files.length, maxNumberOfFiles));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.file;
|
||||
|
||||
import java.io.File;
|
||||
@@ -6,23 +22,27 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DirectoryScanner that lists all files inside a directory and subdirectories, without limit. This scanner should not
|
||||
* be used with directories that contain a vast number of files or on deep trees, as all the file names will be read
|
||||
* DirectoryScanner that lists all files inside a directory and subdirectories,
|
||||
* without limit. This scanner should not be used with directories that contain
|
||||
* a vast number of files or on deep trees, as all the file names will be read
|
||||
* into memory and the scanning will be done recursively.
|
||||
*
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class RecursiveLeafOnlyDirectoryScanner extends DefaultDirectoryScanner {
|
||||
protected File[] listEligibleFiles(File directory) throws IllegalArgumentException {
|
||||
File[] rootFiles = directory.listFiles();
|
||||
List<File> files = new ArrayList<File>(rootFiles.length);
|
||||
for (File rootFile : rootFiles) {
|
||||
if (rootFile.isDirectory()) {
|
||||
files.addAll(Arrays.asList(listEligibleFiles(rootFile)));
|
||||
} else {
|
||||
files.add(rootFile);
|
||||
}
|
||||
}
|
||||
return files.toArray(new File[files.size()]);
|
||||
}
|
||||
|
||||
protected File[] listEligibleFiles(File directory) throws IllegalArgumentException {
|
||||
File[] rootFiles = directory.listFiles();
|
||||
List<File> files = new ArrayList<File>(rootFiles.length);
|
||||
for (File rootFile : rootFiles) {
|
||||
if (rootFile.isDirectory()) {
|
||||
files.addAll(Arrays.asList(listEligibleFiles(rootFile)));
|
||||
}
|
||||
else {
|
||||
files.add(rootFile);
|
||||
}
|
||||
}
|
||||
return files.toArray(new File[files.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -31,73 +31,72 @@ import org.springframework.util.xml.DomUtils;
|
||||
|
||||
/**
|
||||
* Parser for the <inbound-channel-adapter> element of the 'file' namespace.
|
||||
*
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class FileInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
|
||||
|
||||
private static final String PACKAGE_NAME = "org.springframework.integration.file";
|
||||
private static final String PACKAGE_NAME = "org.springframework.integration.file";
|
||||
|
||||
|
||||
@Override
|
||||
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
PACKAGE_NAME + ".config.FileReadingMessageSourceFactoryBean");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "comparator");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "scanner");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "queue-size");
|
||||
String filterBeanName = this.registerFilter(element, parserContext);
|
||||
String lockerBeanName = registerLocker(element, parserContext);
|
||||
if (lockerBeanName != null) {
|
||||
builder.addPropertyReference("locker", lockerBeanName);
|
||||
}
|
||||
builder.addPropertyReference("filter", filterBeanName);
|
||||
String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
|
||||
return new RuntimeBeanReference(beanName);
|
||||
}
|
||||
@Override
|
||||
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
PACKAGE_NAME + ".config.FileReadingMessageSourceFactoryBean");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "comparator");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "scanner");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "queue-size");
|
||||
String filterBeanName = this.registerFilter(element, parserContext);
|
||||
String lockerBeanName = registerLocker(element, parserContext);
|
||||
if (lockerBeanName != null) {
|
||||
builder.addPropertyReference("locker", lockerBeanName);
|
||||
}
|
||||
builder.addPropertyReference("filter", filterBeanName);
|
||||
String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
builder.getBeanDefinition(), parserContext.getRegistry());
|
||||
return new RuntimeBeanReference(beanName);
|
||||
}
|
||||
|
||||
private String registerLocker(Element element, ParserContext parserContext) {
|
||||
String lockerBeanName = null;
|
||||
Element nioLocker = DomUtils.getChildElementByTagName(element, "nio-locker");
|
||||
if (nioLocker != null) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(PACKAGE_NAME + ".locking.NioFileLocker");
|
||||
lockerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(),
|
||||
parserContext.getRegistry());
|
||||
} else {
|
||||
Element locker = DomUtils.getChildElementByTagName(element, "locker");
|
||||
if (locker != null) {
|
||||
lockerBeanName = locker.getAttribute("ref");
|
||||
}
|
||||
}
|
||||
return lockerBeanName;
|
||||
}
|
||||
private String registerLocker(Element element, ParserContext parserContext) {
|
||||
String lockerBeanName = null;
|
||||
Element nioLocker = DomUtils.getChildElementByTagName(element, "nio-locker");
|
||||
if (nioLocker != null) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
PACKAGE_NAME + ".locking.NioFileLocker");
|
||||
lockerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
builder.getBeanDefinition(), parserContext.getRegistry());
|
||||
}
|
||||
else {
|
||||
Element locker = DomUtils.getChildElementByTagName(element, "locker");
|
||||
if (locker != null) {
|
||||
lockerBeanName = locker.getAttribute("ref");
|
||||
}
|
||||
}
|
||||
return lockerBeanName;
|
||||
}
|
||||
|
||||
private String registerFilter(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder factoryBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
PACKAGE_NAME + ".config.FileListFilterFactoryBean");
|
||||
factoryBeanBuilder.setRole(BeanDefinition.ROLE_SUPPORT);
|
||||
String filter = element.getAttribute("filter");
|
||||
if (StringUtils.hasText(filter)) {
|
||||
factoryBeanBuilder.addPropertyReference("filterReference", filter);
|
||||
}
|
||||
String filenamePattern = element.getAttribute("filename-pattern");
|
||||
if (StringUtils.hasText(filenamePattern)) {
|
||||
if (StringUtils.hasText(filter)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"At most one of 'filter' and 'filename-pattern' may be provided.", element);
|
||||
}
|
||||
factoryBeanBuilder.addPropertyValue("filenamePattern", filenamePattern);
|
||||
}
|
||||
String preventDuplicates = element.getAttribute("prevent-duplicates");
|
||||
if (StringUtils.hasText(preventDuplicates)) {
|
||||
factoryBeanBuilder.addPropertyValue("preventDuplicates", preventDuplicates);
|
||||
}
|
||||
return BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
factoryBeanBuilder.getBeanDefinition(), parserContext.getRegistry());
|
||||
}
|
||||
private String registerFilter(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder factoryBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
PACKAGE_NAME + ".config.FileListFilterFactoryBean");
|
||||
factoryBeanBuilder.setRole(BeanDefinition.ROLE_SUPPORT);
|
||||
String filter = element.getAttribute("filter");
|
||||
if (StringUtils.hasText(filter)) {
|
||||
factoryBeanBuilder.addPropertyReference("filterReference", filter);
|
||||
}
|
||||
String filenamePattern = element.getAttribute("filename-pattern");
|
||||
if (StringUtils.hasText(filenamePattern)) {
|
||||
if (StringUtils.hasText(filter)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"At most one of 'filter' and 'filename-pattern' may be provided.", element);
|
||||
}
|
||||
factoryBeanBuilder.addPropertyValue("filenamePattern", filenamePattern);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(factoryBeanBuilder, element, "prevent-duplicates");
|
||||
return BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
factoryBeanBuilder.getBeanDefinition(), parserContext.getRegistry());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.config;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
@@ -22,107 +23,111 @@ import org.springframework.integration.file.filters.SimplePatternFileListFilter;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public class FileListFilterFactoryBean implements FactoryBean<EntryListFilter<File>> {
|
||||
private volatile EntryListFilter<File> fileListFilter;
|
||||
private volatile EntryListFilter<File> filterReference;
|
||||
private volatile String filenamePattern;
|
||||
private volatile Boolean preventDuplicates;
|
||||
private final Object monitor = new Object();
|
||||
private volatile Collection<EntryListFilter<File>> filterReferences;
|
||||
private FileEntryNamer fileNamer = new FileEntryNamer();
|
||||
|
||||
public void setFilterReferences(Collection<EntryListFilter<File>> filterReferences) {
|
||||
this.filterReferences = filterReferences;
|
||||
}
|
||||
private volatile EntryListFilter<File> fileListFilter;
|
||||
|
||||
public void setFilterReference(EntryListFilter<File> filterReference) {
|
||||
this.filterReference = filterReference;
|
||||
}
|
||||
private volatile EntryListFilter<File> filterReference;
|
||||
|
||||
public void setFilenamePattern(String filenamePattern) {
|
||||
this.filenamePattern = filenamePattern;
|
||||
}
|
||||
private volatile String filenamePattern;
|
||||
|
||||
public void setPreventDuplicates(Boolean preventDuplicates) {
|
||||
this.preventDuplicates = preventDuplicates;
|
||||
}
|
||||
private volatile Boolean preventDuplicates;
|
||||
|
||||
public EntryListFilter<File> getObject() throws Exception {
|
||||
if (this.fileListFilter == null) {
|
||||
synchronized (this.monitor) {
|
||||
this.intializeFileListFilter();
|
||||
}
|
||||
}
|
||||
private final Object monitor = new Object();
|
||||
|
||||
return this.fileListFilter;
|
||||
}
|
||||
private volatile Collection<EntryListFilter<File>> filterReferences;
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return (this.fileListFilter != null) ? this.fileListFilter.getClass() : EntryListFilter.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
public void setFilterReferences(Collection<EntryListFilter<File>> filterReferences) {
|
||||
this.filterReferences = filterReferences;
|
||||
}
|
||||
|
||||
private void intializeFileListFilter() {
|
||||
if (this.fileListFilter != null) {
|
||||
return;
|
||||
}
|
||||
public void setFilterReference(EntryListFilter<File> filterReference) {
|
||||
this.filterReference = filterReference;
|
||||
}
|
||||
|
||||
EntryListFilter<File> flf ;
|
||||
public void setFilenamePattern(String filenamePattern) {
|
||||
this.filenamePattern = filenamePattern;
|
||||
}
|
||||
|
||||
if ((this.filterReference != null) && (this.filenamePattern != null)) {
|
||||
throw new IllegalArgumentException("The 'filter' reference and " + "'filename-pattern' attributes are mutually exclusive.");
|
||||
}
|
||||
public void setPreventDuplicates(Boolean preventDuplicates) {
|
||||
this.preventDuplicates = preventDuplicates;
|
||||
}
|
||||
|
||||
if (this.filterReference != null) {
|
||||
if (Boolean.TRUE.equals(this.preventDuplicates)) {
|
||||
flf = this.createCompositeWithAcceptOnceFilter(this.filterReference);
|
||||
} else { // preventDuplicates is either FALSE or NULL
|
||||
flf = this.filterReference;
|
||||
}
|
||||
} else if (this.filenamePattern != null) {
|
||||
SimplePatternFileListFilter patternFilter = new SimplePatternFileListFilter(this.filenamePattern);
|
||||
public EntryListFilter<File> getObject() throws Exception {
|
||||
if (this.fileListFilter == null) {
|
||||
synchronized (this.monitor) {
|
||||
this.intializeFileListFilter();
|
||||
}
|
||||
}
|
||||
return this.fileListFilter;
|
||||
}
|
||||
|
||||
if (Boolean.FALSE.equals(this.preventDuplicates)) {
|
||||
flf = patternFilter;
|
||||
} else { // preventDuplicates is either TRUE or NULL
|
||||
flf = this.createCompositeWithAcceptOnceFilter(patternFilter);
|
||||
}
|
||||
} else if (Boolean.FALSE.equals(this.preventDuplicates)) {
|
||||
flf = new AcceptAllEntryListFilter<File>();
|
||||
} else { // preventDuplicates is either TRUE or NULL
|
||||
flf = new AcceptOnceEntryFileListFilter<File>();
|
||||
}
|
||||
public Class<?> getObjectType() {
|
||||
return (this.fileListFilter != null) ? this.fileListFilter.getClass() : EntryListFilter.class;
|
||||
}
|
||||
|
||||
// finally, it might be that they simply want a {@link CompositeEntryListFilter}
|
||||
if ((this.filterReferences != null) && (this.filterReferences.size() > 0)) {
|
||||
CompositeEntryListFilter<File> flfc = new CompositeEntryListFilter<File>();
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (EntryListFilter<File> ff : filterReferences)
|
||||
flfc.addFilter(ff);
|
||||
private void intializeFileListFilter() {
|
||||
if (this.fileListFilter != null) {
|
||||
return;
|
||||
}
|
||||
EntryListFilter<File> filter;
|
||||
if ((this.filterReference != null) && (this.filenamePattern != null)) {
|
||||
throw new IllegalArgumentException("The 'filter' reference and "
|
||||
+ "'filename-pattern' attributes are mutually exclusive.");
|
||||
}
|
||||
|
||||
flf = flfc;
|
||||
}
|
||||
if (this.filterReference != null) {
|
||||
if (Boolean.TRUE.equals(this.preventDuplicates)) {
|
||||
filter = this.createCompositeWithAcceptOnceFilter(this.filterReference);
|
||||
}
|
||||
else { // preventDuplicates is either FALSE or NULL
|
||||
filter = this.filterReference;
|
||||
}
|
||||
}
|
||||
else if (this.filenamePattern != null) {
|
||||
SimplePatternFileListFilter patternFilter = new SimplePatternFileListFilter(this.filenamePattern);
|
||||
if (Boolean.FALSE.equals(this.preventDuplicates)) {
|
||||
filter = patternFilter;
|
||||
}
|
||||
else { // preventDuplicates is either TRUE or NULL
|
||||
filter = this.createCompositeWithAcceptOnceFilter(patternFilter);
|
||||
}
|
||||
}
|
||||
else if (Boolean.FALSE.equals(this.preventDuplicates)) {
|
||||
filter = new AcceptAllEntryListFilter<File>();
|
||||
}
|
||||
else { // preventDuplicates is either TRUE or NULL
|
||||
filter = new AcceptOnceEntryFileListFilter<File>();
|
||||
}
|
||||
|
||||
if (flf == null) {
|
||||
flf = new CompositeEntryListFilter<File>();
|
||||
}
|
||||
// finally, it might be that they simply want a {@link CompositeEntryListFilter}
|
||||
if ((this.filterReferences != null) && (this.filterReferences.size() > 0)) {
|
||||
CompositeEntryListFilter<File> compositeFilter = new CompositeEntryListFilter<File>();
|
||||
for (EntryListFilter<File> ff : filterReferences) {
|
||||
compositeFilter.addFilter(ff);
|
||||
}
|
||||
filter = compositeFilter;
|
||||
}
|
||||
if (filter == null) {
|
||||
filter = new CompositeEntryListFilter<File>();
|
||||
}
|
||||
this.fileListFilter = filter;
|
||||
}
|
||||
|
||||
this.fileListFilter = flf;
|
||||
}
|
||||
private CompositeEntryListFilter<File> createCompositeWithAcceptOnceFilter(EntryListFilter<File> otherFilter) {
|
||||
CompositeEntryListFilter<File> compositeFilter = new CompositeEntryListFilter<File>();
|
||||
compositeFilter.addFilter(new AcceptOnceEntryFileListFilter<File>());
|
||||
compositeFilter.addFilter(otherFilter);
|
||||
return compositeFilter;
|
||||
}
|
||||
|
||||
private CompositeEntryListFilter<File> createCompositeWithAcceptOnceFilter(EntryListFilter<File> otherFilter) {
|
||||
CompositeEntryListFilter<File> compositeFilter = new CompositeEntryListFilter<File>();
|
||||
compositeFilter.addFilter(new AcceptOnceEntryFileListFilter<File>());
|
||||
compositeFilter.addFilter(otherFilter);
|
||||
|
||||
return compositeFilter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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,10 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.config;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.integration.file.DirectoryScanner;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
@@ -27,134 +29,129 @@ import org.springframework.integration.file.locking.AbstractFileLockerFilter;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Iwein Fuld
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileReadingMessageSource> {
|
||||
private static Log logger = LogFactory.getLog(FileReadingMessageSourceFactoryBean.class);
|
||||
private volatile FileReadingMessageSource source;
|
||||
private volatile File directory;
|
||||
private volatile EntryListFilter<File> filter;
|
||||
private volatile AbstractFileLockerFilter locker;
|
||||
private volatile Comparator<File> comparator;
|
||||
private volatile DirectoryScanner scanner;
|
||||
private volatile Boolean scanEachPoll;
|
||||
private volatile Boolean autoCreateDirectory;
|
||||
private volatile Integer queueSize;
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setDirectory(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
private static Log logger = LogFactory.getLog(FileReadingMessageSourceFactoryBean.class);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setComparator(Comparator<File> comparator) {
|
||||
this.comparator = comparator;
|
||||
}
|
||||
private volatile FileReadingMessageSource source;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setScanner(DirectoryScanner scanner) {
|
||||
this.scanner = scanner;
|
||||
}
|
||||
private volatile File directory;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setFilter(EntryListFilter<File> filter) {
|
||||
if (filter instanceof AbstractFileLockerFilter && (this.locker == null)) {
|
||||
this.setLocker((AbstractFileLockerFilter) filter);
|
||||
}
|
||||
private volatile EntryListFilter<File> filter;
|
||||
|
||||
this.filter = filter;
|
||||
}
|
||||
private volatile AbstractFileLockerFilter locker;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setScanEachPoll(Boolean scanEachPoll) {
|
||||
this.scanEachPoll = scanEachPoll;
|
||||
}
|
||||
private volatile Comparator<File> comparator;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setAutoCreateDirectory(Boolean autoCreateDirectory) {
|
||||
this.autoCreateDirectory = autoCreateDirectory;
|
||||
}
|
||||
private volatile DirectoryScanner scanner;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setQueueSize(Integer queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
private volatile Boolean scanEachPoll;
|
||||
|
||||
public void setLocker(AbstractFileLockerFilter locker) {
|
||||
this.locker = locker;
|
||||
}
|
||||
private volatile Boolean autoCreateDirectory;
|
||||
|
||||
public FileReadingMessageSource getObject() throws Exception {
|
||||
if (this.source == null) {
|
||||
initSource();
|
||||
}
|
||||
private volatile Integer queueSize;
|
||||
|
||||
return this.source;
|
||||
}
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return FileReadingMessageSource.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
public void setDirectory(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
private void initSource() {
|
||||
synchronized (this.initializationMonitor) {
|
||||
if (this.source != null) {
|
||||
return;
|
||||
}
|
||||
public void setComparator(Comparator<File> comparator) {
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
boolean comparatorSet = this.comparator != null;
|
||||
boolean queueSizeSet = this.queueSize != null;
|
||||
public void setScanner(DirectoryScanner scanner) {
|
||||
this.scanner = scanner;
|
||||
}
|
||||
|
||||
if (comparatorSet) {
|
||||
if (queueSizeSet) {
|
||||
logger.warn("'comparator' and 'queueSize' are mutually exclusive. Ignoring 'queueSize'");
|
||||
}
|
||||
public void setFilter(EntryListFilter<File> filter) {
|
||||
if (filter instanceof AbstractFileLockerFilter && (this.locker == null)) {
|
||||
this.setLocker((AbstractFileLockerFilter) filter);
|
||||
}
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
this.source = new FileReadingMessageSource(this.comparator);
|
||||
} else if (queueSizeSet) {
|
||||
this.source = new FileReadingMessageSource(queueSize);
|
||||
} else {
|
||||
this.source = new FileReadingMessageSource();
|
||||
}
|
||||
public void setScanEachPoll(Boolean scanEachPoll) {
|
||||
this.scanEachPoll = scanEachPoll;
|
||||
}
|
||||
|
||||
this.source.setDirectory(this.directory);
|
||||
public void setAutoCreateDirectory(Boolean autoCreateDirectory) {
|
||||
this.autoCreateDirectory = autoCreateDirectory;
|
||||
}
|
||||
|
||||
if (this.scanner != null) {
|
||||
this.source.setScanner(this.scanner);
|
||||
}
|
||||
public void setQueueSize(Integer queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
|
||||
if (this.filter != null) {
|
||||
if (this.locker == null) {
|
||||
this.source.setFilter(this.filter);
|
||||
} else {
|
||||
CompositeEntryListFilter<File> fileCompositeEntryListFilter = new CompositeEntryListFilter<File>();
|
||||
public void setLocker(AbstractFileLockerFilter locker) {
|
||||
this.locker = locker;
|
||||
}
|
||||
|
||||
public FileReadingMessageSource getObject() throws Exception {
|
||||
if (this.source == null) {
|
||||
initSource();
|
||||
}
|
||||
return this.source;
|
||||
}
|
||||
|
||||
public Class<?> getObjectType() {
|
||||
return FileReadingMessageSource.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initSource() {
|
||||
synchronized (this.initializationMonitor) {
|
||||
if (this.source != null) {
|
||||
return;
|
||||
}
|
||||
boolean comparatorSet = this.comparator != null;
|
||||
boolean queueSizeSet = this.queueSize != null;
|
||||
if (comparatorSet) {
|
||||
if (queueSizeSet) {
|
||||
logger.warn("'comparator' and 'queueSize' are mutually exclusive. Ignoring 'queueSize'");
|
||||
}
|
||||
this.source = new FileReadingMessageSource(this.comparator);
|
||||
}
|
||||
else if (queueSizeSet) {
|
||||
this.source = new FileReadingMessageSource(queueSize);
|
||||
}
|
||||
else {
|
||||
this.source = new FileReadingMessageSource();
|
||||
}
|
||||
this.source.setDirectory(this.directory);
|
||||
if (this.scanner != null) {
|
||||
this.source.setScanner(this.scanner);
|
||||
}
|
||||
if (this.filter != null) {
|
||||
if (this.locker == null) {
|
||||
this.source.setFilter(this.filter);
|
||||
}
|
||||
else {
|
||||
CompositeEntryListFilter<File> fileCompositeEntryListFilter = new CompositeEntryListFilter<File>();
|
||||
fileCompositeEntryListFilter.addFilter(this.filter);
|
||||
fileCompositeEntryListFilter.addFilter(this.locker);
|
||||
this.source.setFilter(fileCompositeEntryListFilter);
|
||||
this.source.setLocker(locker);
|
||||
}
|
||||
}
|
||||
if (this.scanEachPoll != null) {
|
||||
this.source.setScanEachPoll(this.scanEachPoll);
|
||||
}
|
||||
if (this.autoCreateDirectory != null) {
|
||||
this.source.setAutoCreateDirectory(this.autoCreateDirectory);
|
||||
}
|
||||
this.source.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
this.source.setFilter(fileCompositeEntryListFilter);
|
||||
this.source.setLocker(locker);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.scanEachPoll != null) {
|
||||
this.source.setScanEachPoll(this.scanEachPoll);
|
||||
}
|
||||
|
||||
if (this.autoCreateDirectory != null) {
|
||||
this.source.setAutoCreateDirectory(this.autoCreateDirectory);
|
||||
}
|
||||
|
||||
this.source.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
|
||||
@@ -32,8 +32,7 @@ import java.io.File;
|
||||
* @author Iwein Fuld
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public class FileWritingMessageHandlerFactoryBean implements FactoryBean<FileWritingMessageHandler>,
|
||||
BeanFactoryAware {
|
||||
public class FileWritingMessageHandlerFactoryBean implements FactoryBean<FileWritingMessageHandler>, BeanFactoryAware {
|
||||
|
||||
private volatile FileWritingMessageHandler handler;
|
||||
|
||||
@@ -61,6 +60,7 @@ public class FileWritingMessageHandlerFactoryBean implements FactoryBean<FileWri
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -20,7 +21,6 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* A convenience base class for any {@link EntryListFilter} whose criteria can be
|
||||
* evaluated against each File in isolation. If the entire List of files is
|
||||
@@ -31,14 +31,18 @@ import java.util.List;
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractEntryListFilter<T> implements InitializingBean, EntryListFilter<T> {
|
||||
public abstract boolean accept(T t);
|
||||
|
||||
/**
|
||||
* subclasses may override this if initialization is required
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public List<T> filterEntries(T[] entries) {
|
||||
List<T> accepted = new ArrayList<T>();
|
||||
|
||||
if (entries != null) {
|
||||
for (T t : entries) {
|
||||
if (this.accept(t)) {
|
||||
@@ -46,11 +50,12 @@ public abstract class AbstractEntryListFilter<T> implements InitializingBean, En
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// its all you!
|
||||
}
|
||||
/**
|
||||
* subclasses must implement this method
|
||||
*/
|
||||
public abstract boolean accept(T entry);
|
||||
|
||||
}
|
||||
|
||||
@@ -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,18 +16,19 @@
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
|
||||
/**
|
||||
* Simple NOOP implementation for {@link org.springframework.integration.file.entries.EntryListFilter} implementation.
|
||||
* Suitable as a default in implementations.
|
||||
* Simple NO-OP implementation of {@link org.springframework.integration.file.entries.EntryListFilter}.
|
||||
* Suitable as a default.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Josh Long
|
||||
* @param <T>
|
||||
*/
|
||||
public class AcceptAllEntryListFilter<T> extends AbstractEntryListFilter<T> {
|
||||
|
||||
@Override
|
||||
public boolean accept(T t) {
|
||||
public boolean accept(T entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
|
||||
/**
|
||||
* {@link EntryListFilter} that passes files only one time. This can
|
||||
* conveniently be used to prevent duplication of files, as is done in
|
||||
@@ -30,16 +30,18 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class AcceptOnceEntryFileListFilter<T> extends AbstractEntryListFilter<T> {
|
||||
|
||||
private final Queue<T> seen;
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* Creates an {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} that is based on a bounded queue. If the
|
||||
* queue overflows, files that fall out will be passed through this filter
|
||||
* again if passed to the {@link #filterEntries(Object[])} method.
|
||||
* Creates an {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter}
|
||||
* that is based on a bounded queue. If the queue overflows, files that fall out will be passed
|
||||
* through this filter again if passed to the {@link #filterEntries(Object[])} method.
|
||||
*
|
||||
* @param maxCapacity the maximum number of Files to maintain in the 'seen'
|
||||
* queue.
|
||||
* @param maxCapacity the maximum number of Files to maintain in the 'seen' queue.
|
||||
*/
|
||||
public AcceptOnceEntryFileListFilter(int maxCapacity) {
|
||||
this.seen = new LinkedBlockingQueue<T>(maxCapacity);
|
||||
@@ -52,18 +54,18 @@ public class AcceptOnceEntryFileListFilter<T> extends AbstractEntryListFilter<T>
|
||||
this.seen = new LinkedBlockingQueue<T>();
|
||||
}
|
||||
|
||||
|
||||
public boolean accept(T pathname) {
|
||||
synchronized (this.monitor) {
|
||||
if (seen.contains(pathname)) {
|
||||
if (this.seen.contains(pathname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!seen.offer(pathname)) {
|
||||
seen.poll();
|
||||
seen.add(pathname);
|
||||
if (!this.seen.offer(pathname)) {
|
||||
this.seen.poll();
|
||||
this.seen.add(pathname);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -30,8 +31,10 @@ import java.util.*;
|
||||
* @param <T>
|
||||
*/
|
||||
public class CompositeEntryListFilter<T> implements EntryListFilter<T> {
|
||||
|
||||
private final Set<EntryListFilter<T>> fileFilters;
|
||||
|
||||
|
||||
public CompositeEntryListFilter() {
|
||||
this.fileFilters = new LinkedHashSet<EntryListFilter<T>>();
|
||||
}
|
||||
@@ -40,23 +43,9 @@ public class CompositeEntryListFilter<T> implements EntryListFilter<T> {
|
||||
this.fileFilters = new LinkedHashSet<EntryListFilter<T>>(fileFilters);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<T> filterEntries(T[] entries) {
|
||||
Assert.notNull(entries, "'files' should not be null");
|
||||
|
||||
List<T> leftOver = Arrays.asList(entries);
|
||||
|
||||
for (EntryListFilter<T> fileFilter : this.fileFilters) {
|
||||
T[] ts = (T[]) leftOver.toArray();
|
||||
leftOver = fileFilter.filterEntries(ts);
|
||||
}
|
||||
|
||||
return leftOver;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") //to please the eclipse compiler
|
||||
public CompositeEntryListFilter<T> addFilter(EntryListFilter<T> filter) {
|
||||
return this.addFilters(filter);
|
||||
return this.addFilters(Collections.singletonList(filter));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,19 +65,31 @@ public class CompositeEntryListFilter<T> implements EntryListFilter<T> {
|
||||
* @param filtersToAdd a list of filters to add
|
||||
* @return this CompositeEntryListFilter instance with the added filters
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public CompositeEntryListFilter<T> addFilters(Collection<? extends EntryListFilter<T>> filtersToAdd) {
|
||||
for (EntryListFilter<? extends T> elf : filtersToAdd)
|
||||
for (EntryListFilter<? extends T> elf : filtersToAdd) {
|
||||
if (elf instanceof InitializingBean) {
|
||||
try {
|
||||
((InitializingBean) elf).afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
this.fileFilters.addAll(filtersToAdd);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<T> filterEntries(T[] entries) {
|
||||
Assert.notNull(entries, "'files' should not be null");
|
||||
List<T> leftOver = Arrays.asList(entries);
|
||||
for (EntryListFilter<T> fileFilter : this.fileFilters) {
|
||||
T[] ts = (T[]) leftOver.toArray();
|
||||
leftOver = fileFilter.filterEntries(ts);
|
||||
}
|
||||
return leftOver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Strategy interface for filtering entries representing files on a local or remote file system. This is a generic
|
||||
* variant of FileListFilter that also works with references to remote files.
|
||||
@@ -26,16 +26,15 @@ import java.util.List;
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Iwein Fuld
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @since 2.0
|
||||
* @see org.springframework.integration.file.filters.FileListFilter
|
||||
*/
|
||||
public interface EntryListFilter<T> {
|
||||
|
||||
/**
|
||||
* Filters out entries and returns the entries that are left in a list, or an
|
||||
* empty list when a null is passed in.
|
||||
* empty list when null is passed in.
|
||||
*/
|
||||
List<T> filterEntries(T[] entries);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,22 +13,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
/**
|
||||
* Responsible for coercing a String identification out of the T entry.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @param <T> the type of entry (there's an implementation for FTP, SFTP, and plain-old java.io.Files)
|
||||
* @param <T> the type of entry (there's an implementation for FTP, SFTP, and plain-old java.io.Files)
|
||||
*/
|
||||
public interface EntryNamer<T> {
|
||||
|
||||
/**
|
||||
* This is the one place I couldn't spackle over the interface differences between an FTPFile (FTP adapter), File (File adapter), and LsEntry (SFTP adapter)
|
||||
* with generics alone. So we have a typed strategy implementation for accessing a property ....
|
||||
* with generics alone. So we have a typed strategy implementation for accessing a property....
|
||||
*
|
||||
* @param entry the entry in a file system listing
|
||||
* @return the String name that might be used to reference that entry or to do regular expression checks against
|
||||
*/
|
||||
String nameOf(T entry);
|
||||
|
||||
}
|
||||
|
||||
@@ -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,16 +18,16 @@ package org.springframework.integration.file.entries;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
/**
|
||||
* {@link java.io.File} implementation of the {@link org.springframework.integration.file.entries.EntryNamer} strategy.
|
||||
* <p/>
|
||||
* This part feels a little over-engineered...
|
||||
*
|
||||
* {@link java.io.File}-based implementation of the {@link EntryNamer} strategy.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @since 2.0
|
||||
*/
|
||||
public class FileEntryNamer implements EntryNamer<File> {
|
||||
|
||||
public String nameOf(File entry) {
|
||||
return (entry != null) ? entry.getName() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file.entries;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -21,7 +22,6 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Filters a listing of entries (T) by qualifying their 'name' (as determined by {@link org.springframework.integration.file.entries.EntryNamer})
|
||||
* against a regular expression (an instance of {@link java.util.regex.Pattern})
|
||||
@@ -29,13 +29,16 @@ import java.util.regex.Pattern;
|
||||
* @author Iwein Fuld
|
||||
* @author Josh Long
|
||||
* @param <T> the type of entry
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @since 2.0
|
||||
*/
|
||||
public class PatternMatchingEntryListFilter<T> extends AbstractEntryListFilter<T> implements InitializingBean {
|
||||
private Pattern pattern;
|
||||
private String patternExpression;
|
||||
private EntryNamer<T> entryNamer;
|
||||
|
||||
private volatile EntryNamer<T> entryNamer;
|
||||
|
||||
private volatile Pattern pattern;
|
||||
|
||||
private volatile String patternExpression;
|
||||
|
||||
|
||||
public PatternMatchingEntryListFilter(EntryNamer<T> en, String p) {
|
||||
this.entryNamer = en;
|
||||
@@ -47,6 +50,11 @@ public class PatternMatchingEntryListFilter<T> extends AbstractEntryListFilter<T
|
||||
this.pattern = p;
|
||||
}
|
||||
|
||||
|
||||
public void setEntryNamer(EntryNamer<T> entryNamer) {
|
||||
this.entryNamer = entryNamer;
|
||||
}
|
||||
|
||||
public void setPattern(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
@@ -64,11 +72,8 @@ public class PatternMatchingEntryListFilter<T> extends AbstractEntryListFilter<T
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(T t) {
|
||||
return (t != null) && this.pattern.matcher(this.entryNamer.nameOf(t)).matches();
|
||||
public boolean accept(T entry) {
|
||||
return (entry != null) && this.pattern.matcher(this.entryNamer.nameOf(entry)).matches();
|
||||
}
|
||||
|
||||
public void setEntryNamer(EntryNamer<T> entryNamer) {
|
||||
this.entryNamer = entryNamer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 @@ package org.springframework.integration.file.entries;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* this simply takes an {@link org.springframework.integration.file.entries.EntryListFilter}
|
||||
* and produces an object that can field just <b>one</b> argument instea of an array
|
||||
* This simply takes an {@link org.springframework.integration.file.entries.EntryListFilter}
|
||||
* and produces an object that can field just <b>one</b> argument instead of an array.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
@@ -30,17 +29,19 @@ public class SingleEntryAdaptingEntryListFilter<T> extends AbstractEntryListFilt
|
||||
/**
|
||||
* the {@link org.springframework.integration.file.entries.EntryListFilter} that you'd like to delegate to
|
||||
*/
|
||||
private volatile EntryListFilter<T> entryFilter;
|
||||
private volatile EntryListFilter<T> entryListFilter;
|
||||
|
||||
public SingleEntryAdaptingEntryListFilter(EntryListFilter<T> ef) {
|
||||
this.entryFilter = ef;
|
||||
Assert.notNull(this.entryFilter, "the entryFilter can't be null");
|
||||
public SingleEntryAdaptingEntryListFilter(EntryListFilter<T> entryListFilter) {
|
||||
Assert.notNull(entryListFilter, "entryListFilter must not be null");
|
||||
this.entryListFilter = entryListFilter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean accept(T t) {
|
||||
T[] ts = (T[]) new Object[]{t};
|
||||
return this.entryFilter.filterEntries(ts).size() == 1;
|
||||
return this.entryListFilter.filterEntries(ts).size() == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.file.synchronization;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.entries.AcceptAllEntryListFilter;
|
||||
import org.springframework.integration.file.entries.EntryListFilter;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
/**
|
||||
* Base class charged with knowing how to connect to a remote file system,
|
||||
* scan it for new files and then download the files.
|
||||
* <p/>
|
||||
* The implementation should run through any configured
|
||||
* {@link org.springframework.integration.file.entries.EntryListFilter}s to
|
||||
* ensure the entry is acceptable.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSychronizer<T> extends AbstractEndpoint {
|
||||
|
||||
/**
|
||||
* Should we <emphasis>delete</emphasis> the <b>source</b> file? For an FTP
|
||||
* server, for example, this would delete the original FTPFile instance.
|
||||
*/
|
||||
protected boolean shouldDeleteSourceFile;
|
||||
|
||||
/**
|
||||
* The directory to which we write our synchronizations.
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* An {@link EntryListFilter} that runs against the <emphasis>remote</emphasis> file system view.
|
||||
*/
|
||||
protected volatile EntryListFilter<T> filter = new AcceptAllEntryListFilter<T>();
|
||||
|
||||
/**
|
||||
* The {@link ScheduledFuture} instance we get when we
|
||||
* schedule our {@link SynchronizeTask}
|
||||
*/
|
||||
protected ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
/**
|
||||
* The {@link EntryAcknowledgmentStrategy} implementation.
|
||||
*/
|
||||
protected EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy;
|
||||
|
||||
|
||||
public void setEntryAcknowledgmentStrategy(EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy) {
|
||||
this.entryAcknowledgmentStrategy = entryAcknowledgmentStrategy;
|
||||
}
|
||||
|
||||
public void setShouldDeleteSourceFile(boolean shouldDeleteSourceFile) {
|
||||
this.shouldDeleteSourceFile = shouldDeleteSourceFile;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setFilter(EntryListFilter<T> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param usefulContextOrClientData
|
||||
* this is context information to be passed to the individual {@link EntryAcknowledgmentStrategy}.
|
||||
* {@link EntryAcknowledgmentStrategy#acknowledge(Object, Object)} will be called in line with the
|
||||
* {@link org.springframework.integration.core.MessageSource#receive()} call so this could conceivably
|
||||
* be a 'live' stateful client (a connection?) that is inappropriate to cache as it has per-request state.
|
||||
* @param t
|
||||
* leverages strategy implementations to enable different
|
||||
* behavior. It's a hook to the entry ({@link T}) after it's been
|
||||
* successfully downloaded. Conceptually, you might delete the
|
||||
* remote one or rename it, etc.
|
||||
* @throws Throwable
|
||||
* escape hatch exception, let the adapter deal with it.
|
||||
*/
|
||||
protected void acknowledge(Object usefulContextOrClientData, T t) throws Throwable {
|
||||
Assert.notNull(this.entryAcknowledgmentStrategy != null,
|
||||
"entryAcknowledgmentStrategy can't be null!");
|
||||
this.entryAcknowledgmentStrategy.acknowledge(usefulContextOrClientData, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStart() {
|
||||
if (this.entryAcknowledgmentStrategy == null) {
|
||||
this.entryAcknowledgmentStrategy = new EntryAcknowledgmentStrategy<T>() {
|
||||
public void acknowledge(Object o, T msg) {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
}
|
||||
this.scheduledFuture = this.getTaskScheduler().schedule(new SynchronizeTask(), this.getTrigger());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStop() {
|
||||
if (this.scheduledFuture != null) {
|
||||
this.scheduledFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Trigger} that dictates how frequently the trigger should fire.
|
||||
*/
|
||||
protected abstract Trigger getTrigger();
|
||||
|
||||
/**
|
||||
* This is the callback where we need the implementation to do some specific work
|
||||
*/
|
||||
protected abstract void syncRemoteToLocalFileSystem() throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* This {@link Runnable} is launched as a background thread and is used to manage the
|
||||
* {@link AbstractInboundRemoteFileSystemSychronizer#localDirectory} by queueing and
|
||||
* delivering accumulated files as possible.
|
||||
*/
|
||||
class SynchronizeTask implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
syncRemoteToLocalFileSystem();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException("failure occurred in synchronization task", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strategy interface to expose a hook for dispatching, moving, or deleting
|
||||
* the file once it has been delivered. This will typically be a NOOP for the
|
||||
* implementation. Adapters should (for consistency) expose an attribute
|
||||
* dictating whether the adapter will delete the <emphasis>source</emphasis>
|
||||
* entry on the remote file system. This is the file-system version of an
|
||||
* <code>ack-mode</code>. Future implementations should consider exposing a
|
||||
* custom attribute that plugs a custom {@link EntryAcknowledgmentStrategy}
|
||||
* into the pipeline and also some more advanced scenarios (i.e., 'move file
|
||||
* to another folder on delete ', or 'rename on delete')
|
||||
*
|
||||
* @param <T> the entry type (file, sftp, ftp, ...)
|
||||
*/
|
||||
public static interface EntryAcknowledgmentStrategy<T> {
|
||||
|
||||
/**
|
||||
* Semantics are simple. You get a pointer to the entry just processed
|
||||
* and any kind of helper data you could ask for. Since the strategy is
|
||||
* a singleton and the clients you might ask for as context data are
|
||||
* pooled, it's not recommended that you try to cache them.
|
||||
*
|
||||
* @param useful
|
||||
* any context data
|
||||
* @param msg
|
||||
* the data / file / entry you want to process -- specific to subclasses
|
||||
* @throws Exception in case of an error while acknowledging
|
||||
*/
|
||||
void acknowledge(Object useful, T msg) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.file.synchronization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
|
||||
import org.springframework.integration.file.entries.CompositeEntryListFilter;
|
||||
import org.springframework.integration.file.entries.EntryListFilter;
|
||||
import org.springframework.integration.file.entries.FileEntryNamer;
|
||||
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
|
||||
|
||||
/**
|
||||
* Factors out the common logic between the FTP and SFTP adapters. Designed to
|
||||
* be extensible to handle adapters whose task it is to synchronize a remote
|
||||
* file system with a local file system (NB: this does *NOT* handle pushing
|
||||
* files TO the remote file system that exist uniquely in the local file system.
|
||||
* It only handles pulling from the remote file system - as you would expect
|
||||
* from an 'inbound' adapter).
|
||||
* <p/>
|
||||
* The base class supports configuration of whether the remote file system and
|
||||
* local file system's directories should be created on start (what 'creating a
|
||||
* directory' means to the specific adapter is of course implementaton
|
||||
* specific).
|
||||
* <p/>
|
||||
* This class is to be used as a pair with an implementation of
|
||||
* {@link AbstractInboundRemoteFileSystemSychronizer}. The synchronizer must
|
||||
* handle the work of actually connecting to the remote file system and
|
||||
* delivering new {@link File}s.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSynchronizingMessageSource<Y, T extends AbstractInboundRemoteFileSystemSychronizer<Y>>
|
||||
extends MessageProducerSupport implements MessageSource<File> {
|
||||
|
||||
/**
|
||||
* Extension used when downloading files. We change it right after we know it's downloaded.
|
||||
*/
|
||||
public static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
|
||||
|
||||
/**
|
||||
* Should the endpoint attempt to create the local directory and/or the remote directory?
|
||||
*/
|
||||
protected volatile boolean autoCreateDirectories = true;
|
||||
|
||||
/**
|
||||
* An implementation that will handle the chores of actually connecting to and synching up
|
||||
* the remote file system with the local one, in an inbound direction.
|
||||
*/
|
||||
protected volatile T synchronizer;
|
||||
|
||||
/**
|
||||
* Directory to which things should be synched locally.
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* The actual {@link FileReadingMessageSource} that monitors the local filesystem once files are synched.
|
||||
*/
|
||||
protected volatile FileReadingMessageSource fileSource;
|
||||
|
||||
/**
|
||||
* The predicate to use in scanning the remote File system for downloads.
|
||||
*/
|
||||
protected EntryListFilter<Y> remotePredicate;
|
||||
|
||||
|
||||
public void setAutoCreateDirectories(boolean autoCreateDirectories) {
|
||||
this.autoCreateDirectories = autoCreateDirectories;
|
||||
}
|
||||
|
||||
public void setSynchronizer(T synchronizer) {
|
||||
this.synchronizer = synchronizer;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setRemotePredicate(EntryListFilter<Y> remotePredicate) {
|
||||
this.remotePredicate = remotePredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
try {
|
||||
if (this.remotePredicate != null) {
|
||||
this.synchronizer.setFilter(this.remotePredicate);
|
||||
}
|
||||
if (this.localDirectory != null && !this.localDirectory.exists()) {
|
||||
if (this.autoCreateDirectories) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The '" + this.localDirectory + "' directory doesn't exist; Will create.");
|
||||
}
|
||||
this.localDirectory.getFile().mkdirs();
|
||||
}
|
||||
else {
|
||||
throw new FileNotFoundException(this.localDirectory.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the remote files get here.
|
||||
*/
|
||||
this.synchronizer.setLocalDirectory(this.localDirectory);
|
||||
this.synchronizer.setTaskScheduler(this.getTaskScheduler());
|
||||
this.synchronizer.setBeanFactory(this.getBeanFactory());
|
||||
this.synchronizer.setPhase(this.getPhase());
|
||||
this.synchronizer.setBeanName(this.getComponentName());
|
||||
|
||||
/**
|
||||
* Forwards files once they ultimately appear in the {@link #localDirectory}.
|
||||
*/
|
||||
this.fileSource = new FileReadingMessageSource();
|
||||
this.fileSource.setFilter(this.buildFilter());
|
||||
this.fileSource.setDirectory(this.localDirectory.getFile());
|
||||
this.fileSource.afterPropertiesSet();
|
||||
this.synchronizer.afterPropertiesSet();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException("Failure during initialization of MessageSource for: "
|
||||
+ this.getComponentType(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Message<File> receive() {
|
||||
return this.fileSource.receive();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private EntryListFilter<File> buildFilter() {
|
||||
FileEntryNamer fileEntryNamer = new FileEntryNamer();
|
||||
Pattern completePattern = Pattern.compile("^.*(?<!" + INCOMPLETE_EXTENSION + ")$");
|
||||
return new CompositeEntryListFilter<File>(Arrays.asList(
|
||||
new AcceptOnceEntryFileListFilter<File>(),
|
||||
new PatternMatchingEntryListFilter<File>(fileEntryNamer, completePattern)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.
|
||||
@@ -35,7 +35,6 @@ import static org.mockito.Mockito.*;
|
||||
* @author Iwein Fuld
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class FileReadingMessageSourceTests {
|
||||
|
||||
@@ -76,7 +75,7 @@ public class FileReadingMessageSourceTests {
|
||||
@Test
|
||||
public void requeueOnFailure() throws Exception {
|
||||
when(inputDirectoryMock.listFiles()).thenReturn(new File[]{fileMock});
|
||||
Message received = source.receive();
|
||||
Message<File> received = source.receive();
|
||||
assertNotNull(received);
|
||||
source.onFailure(received);
|
||||
assertEquals(received.getPayload(), source.receive().getPayload());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user