Before you can start creating the workflow components required for implementing the use case, you first need to set up the Spring Integration flow.
14.5.1 Spring Integration flow setup
Let’s create a new project to see how the add-on sets up a Spring Integration project.
Change your directory to a new folder called coursemanager-spring-int and run the following command to set up the project:
integration project --name coursemanager-spring-int --rootDomain ➥
org.rooinaction.coursemanager
This creates a new project called coursemanager-spring-int that’s similar to how Roo creates a new Java project when you run the project command.
You can now type integration flow and press TAB twice to see what command options are available. It will show the following output on the command shell window:
integration flow read integration flow start
Because you didn’t create an existing Spring Integration flow yet, you need to use the start option with the integration flow command:
roo> integration flow start --name course-mgmt-int
The output should say
Started new flow: course-mgmt-int
If you want to edit an existing Spring Integration workflow, use the command integration flow edit, specifying the name of the flow, as this example shows:
roo> integration flow edit --name course-mgmt-int
The output will say that the flow is ready for edits:
Flow 'course-mgmt-int' is ready for modification
To see all of the available Spring Integration commands at this time, you can press TAB twice and it will display the following list of commands:
coursemanager-spring-int[course-mgmt-int] roo>
aggregate enrich filter produce route ➥
send service
stop transform validate
349 Course registration workflow components
As you can see in the previous output example, there are several commands to create the different Spring Integration components that you need in your use case.
Note that the available commands are context sensitive, meaning that you see only the commands that are relevant to the Spring Integration component that’s currently in focus.
Another interesting feature to note is that the new add-on has support for main
taining the state of the project between Roo restarts.
14.5.2 Configuring Spring Integration components
Now that the base Spring Integration flow is set up, you’re ready to start adding the integration components you need to implement the course registration use case requirements. These components include the following:
Input channel adapter (message queue)
Transformer
Router
Output channel adapter
Let’s look at how to implement each of these components using Roo commands. First, start with the input channel adapter, which is based on a JMS queue. Post the course reg
istration details to the message queue and the channel adapter helps with receiving the message from the queue for further processing in the subsequent steps of the workflow.
INPUT CHANNEL ADAPTER
The input channel adapter in this case is acting as a producer, so you need to run the produce command to set up the adapter component. Type produce and press TAB twice to see the available options for the produce command. If you do this a couple of times, you’ll see the command for setting up the channel adapter. Here’s the example of this command:
coursemanager-spring-int[course-mgmt-int] roo> produce via ➥
channel-adapter
As you can see from the output in the following listing, a number of different adapter options are available, from ftp, jdbc, and jms to some new adapters that were added in recent releases of Roo, such as twitter and xmpp.
Listing 14.5 Variations of produce command for creating channel adapter component produce via channel-adapter bean produce via channel-adapter event
produce via channel-adapter feed produce via channel-adapter file produce via channel-adapter ftp produce via channel-adapter http produce via channel-adapter jdbc produce via channel-adapter jms produce via channel-adapter mail produce via channel-adapter sftp produce via channel-adapter tcp produce via channel-adapter ➥
produce via channel-adapter udp produce via channel-adapter xmpp
You’ll use the jms option for your requirements. Here’s the full command to config- ure the JMS channel adapter:
coursemanager-spring-int[course-mgmt-int] roo> produce via channel-adapter jms
You can now use the previously mentioned focus command to switch to a specific component in the workflow. Let’s run the focus command with the --name parameter to see the list of different components available. Here’s the output of this command:
channel_fffa channel_valueA channel_valueB ➥
inbound-adapter_e57b
The channel adapter’s ID is inbound-adapter_e57b. You can now use the focus com- mand with the component ID to switch the focus to the inbound channel adapter.
After running the focus command, if you press TAB twice, it will display the list of available commands:
coursemanager-spring-int[course-mgmt-int] ...inbound-adapter_e57b roo>
aggregate config diagram enrich filter focus
produce route send
service stop transform validate TRANSFORMER
One of the commands listed is transform, which can be used to define a transformer component in the workflow. It’s useful for data transformation requirements. Let’s run the transform command, which creates a data transformer and sets the focus to this new component. Here’s output of the command after a transformer component has been added to the workflow:
coursemanager-spring-int[course-mgmt-int] ...trnsfmr_6054 roo>
aggregate config diagram enrich filter
focus produce
route send service stop transform
validate
Another command that’s available is the diagram command, which you can use for viewing Spring Integration workflow details, such as what Spring Integration compo- nents are defined and assembled so far in your use case. Type the diagram command and press Enter. The output of the diagram command showing the FLOW details with the channel adapter, message channel, and transformer components is shown here:
========================================
FLOW: -> inbound-adapter_e496(JMS) -> channel_6054 -> trnsfmr_6054
========================================
You only have two components defined so far in the process, so the diagram doesn’t have a lot of components. You’ll run this command again later in this section after cre- ating all of the components to show how the FLOW diagram will look after all of the workflow components are in place.
ROUTER
The next workflow component you need to define is a router that can be used to send the incoming messages to different steps in the process based on the predefined
351 Course registration workflow components
business rules. The command to define a router component is route. After you create the router component, you can run another command, validate, to perform a vali
dation of the Spring Integration flow you’ve created so far. Here’s the output of the validate command:
Validating Flow: course-mgmt-int
The last component you’ll create for your use case is the output channel adapter. It’ll send the email notifications to customers after performing all of the steps in the course registration workflow.
OUTPUT CHANNEL ADAPTER
Similar to the produce command you used earlier in this section to create an inbound adapter, now you’ll run the send command to define the outbound channel adapter.
You’ll also specify that you need an email-based adapter by including the mail param
eter in the send command. Here’s the command for defining the output channel adapter component:
coursemanager-spring-int[course-mgmt-int] ...router_9a29 roo> send via ➥
adapter mail --name course-reg-email
When the final workflow component has been defined, you can run the diagram com
mand one more time to see the final version of the Spring Integration flow details.
Here’s the command output:
========================================
FLOW: -> inbound-adapter_302d(JMS) -> channel_302d -> ➥
trnsfmr_33b4 -> channel_9a29 -> router_9a29(HVR)[channel_valueB, channel_valueA] -> channel_course-reg-email -> course-reg-email(MAIL) FLOW: -> channel_valueA
FLOW: -> channel_valueB
========================================
coursemanager-spring-int[course-mgmt-int] ...course-reg-email roo>
Also, when you’re finished creating and configuring all of the Spring Integration com
ponents you need for your use case, you can run the stop command to exit the course-mgmt-int workflow.
14.5.3 Spring Integration configuration details
Let’s add the Spring configuration file and Maven dependencies to test the Spring Integration components.
First you start with the Spring configuration file. The following listing shows the contents of the applicationContext-integration.xml file.
Listing 14.6 Configuration details for JMS components in course registration use case
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:integration="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/ ➥
spring-integration.xsd">
<bean id="connectionFactory" class="org.springframework.jms. ➥
connection.CachingConnectionFactory">
<property name="targetConnectionFactory">
<bean class="org.apache.activemq. ➥
ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost"/>
</bean>
</property>
<property name="sessionCacheSize" value="10"/>
<property name="cacheProducers" value="false"/>
</bean>
<bean id="courseRegistrationRequestQueue" class="org.apache. ➥
activemq.command.ActiveMQQueue">
<constructor-arg value="jms.queue. ➥
CourseRegistrationRequestQueue"/>
</bean>
<integration:poller id="poller" default="true" fixed-delay="1000"/>
</beans>
This configuration file defines the JMS queue-related Spring beans using the ActiveMQ server as the messaging container. A second configuration file, coursemanager-spring
int-config.xml, contains the Spring bean configuration for the integration compo
nents. This is shown in the following listing.
Listing 14.7 Spring configuration for course registration workflow components
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/integration/jms"
xmlns:jmx="http://www.springframework.org/schema/integration/jmx"
xmlns:stream="http://www.springframework.org/schema/➥
integration/stream"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/➥
spring-context.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/➥
spring-integration.xsd
http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/➥
spring-integration-jms.xsd
http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/➥
spring-integration-jmx.xsd
353 Course registration workflow components
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/➥
spring-integration-stream.xsd">
<!-- Test Outbound Adapter for Unit Testing -->
<channel id="stdinToJmsoutChannel"/>
<stream:stdin-channel-adapter id="stdin" ➥
channel="stdinToJmsoutChannel"/>
<jms:outbound-channel-adapter id="jmsout"
channel="stdinToJmsoutChannel"
destination="courseRegistrationRequestQueue"/>
<!-- Inbound Adapter -->
<jms:message-driven-channel-adapter id="coursemanager-jms-input-adapter"
destination="courseRegistrationRequestQueue"
channel="jmsInToTransformerChannel"/>
<channel id="jmsInToTransformerChannel"/>
<!-- Transformer -->
<transformer input-channel="jmsInToTransformerChannel"
output-channel="transformerToRouterChannel"
expression="payload.toUpperCase() + '- [' + ➥
T(java.lang.System).currentTimeMillis() + ']'"/>
<channel id="transformerToRouterChannel"/>
<recipient-list-router id="coursemanager-router" input-channel=➥
"transformerToRouterChannel">
<recipient channel="jmsinToStdoutChannel"
selector-expression=➥
"payload.contains('12345')"/>
<recipient channel="jmsinToWaitListChannel" ➥
selector-expression="payload.contains('99999')"/>
</recipient-list-router>
<channel id="jmsinToStdoutChannel"/>
<channel id="jmsinToWaitListChannel"/>
<stream:stdout-channel-adapter id="courseRegSuccess" ➥
channel="jmsinToStdoutChannel" append-newline="true"/>
<stream:stdout-channel-adapter id="courseRegWaitList" ➥
channel="jmsinToWaitListChannel" append-newline="true"/>
<!-- Mail Output Channel Adapter -->
<!-- replace 'userid and 'password' wit the real values -->
<mail:inbound-channel-adapter id="pop3ShouldDeleteTrue"
store-uri="pop3://[userid]:[password]@pop.gmail.com/INBOX"
channel="routerToOutputAdapterChannel"
should-delete-messages="true"
auto-startup="true"
java-mail-properties="javaMailProperties">
<poller fixed-rate="20000"/>
</mail:inbound-channel-adapter>
<util:properties id="javaMailProperties">
<beans:prop key="mail.pop3.socketFactory.fallback">
false</beans:prop>
<beans:prop key="mail.debug">true</beans:prop>
<beans:prop key="mail.pop3.port">995</beans:prop>
<beans:prop key="mail.pop3.socketFactory.class">
javax.net.ssl.SSLSocketFactory</beans:prop>
<beans:prop key="mail.pop3.socketFactory.port">
995</beans:prop>
</util:properties>
</beans:beans>
There are some additional Maven build dependencies for the Spring Integration classes. These dependencies are shown in the next listing. We’re using Spring Integra
tion framework version 2.0.5 in this code example. The version is specified as
<spring.integration.version>2.0.5.RELEASE</spring.integration.version> in the Maven pom.xml file.
Listing 14.8 Maven build dependencies for Spring Integration
<!-- Spring Integration Dependencies -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jms</artifactId>
<version>${spring.integration.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jmx</artifactId>
<version>${spring.integration.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<version>${spring.integration.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
<version>${spring.integration.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
355 Course registration workflow components
<artifactId>spring-integration-mail</artifactId>
<version>${spring.integration.version}</version>
</dependency>
14.5.4 Testing Spring Integration flow
To test the course registration use case with Spring Integration, you’ll write a test cli
ent class called CourseRegistrationSpringIntegrationTestClient. Because most of the workflow details are captured in the configuration file, all you need to do in the test client is to post a course registration request message to the JMS queue‚ course- RegistrationRequestQueue. To make it easier to test, use the stream support pro
vided by the Spring Integration framework. Instead of writing the code to create and post a message to the JMS queue, use a stdout-channel-adapter component, which allows you to type in the course registration test message at the console to trigger the course registration workflow. The following listing shows the test client class.
Listing 14.9 Test client for testing course registration with Spring Integration
package org.rooinaction.coursemanager.integration;
import java.io.File;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.➥
ClassPathXmlApplicationContext;
/**
* @author *
* Test Data:
* Successful Registration Text Message:
* Test Course Registration # 12345 *
* Waitlist Registration Text Message:
* Test Course Registration # - 99999 *
*/
public class CourseRegistrationSpringIntegrationTestClient { private static final Log log = LogFactory.➥
getLog(CourseRegistrationSpringIntegrationTestClient.class);
private final static String[] configFiles = { "/META-INF/spring/integration/➥
applicationContext-integration.xml",
"/META-INF/spring/integration/coursemanager-spring-int-config.xml"
};
public static void main(String[] args) {
CourseRegistrationSpringIntegrationTestClient client = ➥
new CourseRegistrationSpringIntegrationTestClient();
client.verifyThatCourseRegistrationIsSuccessful();
}
public void verifyThatCourseRegistrationIsSuccessful() { log.debug("verifyThatCourseRegistrationIsSuccessful() ➥
method is called.");
log.debug("Cleaning up the ActiveMQ Test Data.");
File tmpDirAMQ = new File("activemq-data");
cleanupActiveMQDatabase(tmpDirAMQ);
log.debug("Loading Spring Application Context.");
new ClassPathXmlApplicationContext(configFiles, ➥
CourseRegistrationSpringIntegrationTest.class);
log.debug("[For testing purposes, Successful Registration ➥
message should contain " +
"the number 12345 and the Waitlist should ➥
contain 99999].");
System.out.println("Type the Course Registration Test ➥
Message and press the Enter key.");
}
private void cleanupActiveMQDatabase(File dirAMQ) { if (dirAMQ.exists()) {
String[] children = dirAMQ.list();
if (children != null) {
for (int i = 0; i < children.length; i++) { cleanupActiveMQDatabase(new File(dirAMQ, ➥
children[i]));
} } }
dirAMQ.delete();
} }