Spring Batch Integration JobLaunchRequest from Controller - spring

I have a controller from which I would like to run a Spring Batch job. The FilePoller acts as a poller that runs on a schedule but we'd like to run it manually, too. The FilePoller is working on the cron schedule and the JobLaunchRequest works in this fashion. But when we use JobLaunchRequest called from the controller, nothing happens -- the Spring Batch job is not launched. Here is the controller:
#Controller
public class PollerController {
#Autowired
FilePoller FilePoller;
#Autowired
private ApplicationContext appContext;
#RequestMapping(value = "ui/manualPoll.action", method = RequestMethod.GET)
public void manualPollRequest() {
Message<File> message = filePoller.fileMessageSource().receive();
filePoller.setFileParameterName(message.getPayload().getName());
filePoller.setJob((Job)appContext.getBean("myJob"));
filePoller.toRequest(message);
}
The message payload has the file name and I get the Spring Batch job to run from the application context. I have debugged and stepped through code and ensured that the file name from the message payload is not null and that the Spring Batch job is also not null. Inside the FilePoller class I have this:
#Configuration
#PropertySource("classpath:my.properties")
#EnableIntegration
#IntegrationComponentScan
public class FilePoller {
private Job job;
private String fileParameterName;
#Autowired
MyProperty myProperty;
public void setFileParameterName(String fileParameterName) {
this.fileParameterName = fileParameterName;
}
public void setJob(Job job) {
this.job = job;
}
#Bean
#InboundChannelAdapter(value = "inboundFileChannel", poller = #Poller(cron="${my/POLLER}"))
public MessageSource<File> fileMessageSource() {
FileReadingMessageSource source = initialSetUp();
source.setDirectory(new File(myProperty.getProperty(MyConstants.WORKING_DIR)))
return source;
}
private FileReadingMessageSource initialSetUp() {
FileReadingMessageSource source = new FileReadingMessageSource();
CompositeFileListFilter<File> compositeFileListFilter = new CompositeFileListFilter<File>();
SimplePatternFileListFilter simplePatternFileListFilter = new SimplePatternFileListFilter("*.done");
AcceptOnceFileListFilter<File> acceptOnceFileListFilter = new AcceptOnceFileListFilter<File>();
compositeFileListFilter.addFilter(simplePatternFileListFilter);
compositeFileListFilter.addFilter(acceptOnceFileListFilter);
source.setFilter(compositeFileListFilter);
return source;
}
#Transformer
public JobLaunchRequest toRequest(Message<File> message) {
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
jobParametersBuilder.addString(fileParameterName, message.getPayload().getAbsolutePath());
return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters());
}
The JobLaunchRequest doesn't seem to do anything. I never get into my first step, shown here in the XML config:
<int:annotation-config />
<int:channel id="inboundFileChannel" />
<int:channel id="outboundJobRequestChannel" />
<int:channel id="jobLaunchReplyChannel" />
<int:transformer input-channel="inboundFileChannel"
output-channel="outboundJobRequestChannel">
<bean
class="org.my.poller.FilePoller">
<property name="job" ref="myJob" />
<property name="fileParameterName" value="input.file.name" />
</bean>
</int:transformer>
<batch-int:job-launching-gateway request-channel="outboundJobRequestChannel" reply-channel="jobLaunchReplyChannel" />
<int:logging-channel-adapter channel="jobLaunchReplyChannel" />
<job id="myJob" xmlns="http://www.springframework.org/schema/batch">
<step id="Step1" next="Step2">
<tasklet ref="checkifFileinLogTbl"/>
UPDATE
Thanks, Gary. It works now. If anyone else is interested:
#Autowired
MessageChannel outboundJobRequestChannel;
#Autowired
MessagingTemplate template;
#RequestMapping(value = "ui/manualPoll.action", method = RequestMethod.GET)
public void manualPollRequest() {
Message<File> message = filePoller.fileMessageSource().receive();
//if message !=null, there is a file present on inboundFileChannel
if(message !=null){
filePoller.setFileParameterName("input.file.name");
filePoller.setJob((Job) appContext.getBean("myJob"));
template.convertAndSend(outboundJobRequestChannel, filePoller.toRequest(message));
}
And in the XML I added:
<bean class="org.springframework.integration.core.MessagingTemplate" />

filePoller.toRequest(message);
Just builds the request object.
You need to send it to the outboundJobRequestChannel.
Use a MessagingTemplate (convertSendAndReceive()) or a MessagingGateway to do that.

Related

How to create dynamically Spring Integration MessageChannels

In the Spring integration the message channel can be configured like this:
<int:channel id="get_send_channel" />
<int:channel id="get_receive_channel">
<int:queue capacity='10' />
</int:channel>
<int-http:outbound-gateway id="get.outbound.gateway"
request-channel="get_send_channel"
url="http://localhost:8080/greeting"
http-method="GET" reply-channel="get_receive_channel"
expected-response-type="java.lang.String">
</int-http:outbound-gateway>
and used like this:
#SpringBootApplication
#ImportResource("http-outbound-gateway.xml")
public class HttpApplication {
#Autowired
#Qualifier("get_send_channel")
MessageChannel getSendChannel;
#Autowired
#Qualifier("get_receive_channel")
PollableChannel getReceiveChannel;
public static void main(String[] args) {
SpringApplication.run(HttpApplication.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
Message<?> message = MessageBuilder.withPayload("").build();
getSendChannel.send(message);
System.out.println(getReceiveChannel.receive().getPayload());
};
}
How the MessageChannel can be created and registered dynamically?
Above code is from this example
I have now tried this
return IntegrationFlows.from(MessageChannels.rendezvous("getSend1"))
.handle(Http.outboundGateway("http://localhost:8080/greeting").httpMethod(HttpMethod.GET))
.channel(MessageChannels.queue("getReceive1")).get();
with default poller, but there is the message:
preReceive on channel 'getSend1'
postReceive on channel 'getSend1', message is null
Received no Message during the poll, returning 'false'
So the configuration seems to be incorrect and the message is not fetch from the URL.
It is working like this:
#Bean
public IntegrationFlow inbound() {
return IntegrationFlows
.from(this.integerMessageSource(), c -> c.poller(Pollers.fixedRate(2000)))
.handle(Http.outboundGateway("http://localhost:8055/greeting")
.httpMethod(HttpMethod.GET).expectedResponseType(String.class))
.channel(MessageChannels.queue("getReceive"))
.handle(Http.outboundGateway("http://localhost:8055/greeting").httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.channel(MessageChannels.queue("postReceive"))
.handle(Http.outboundGateway("http://localhost:8055/greeting-final").httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.channel(MessageChannels.queue("postReceiveFinal"))
.get();
}
This does get for the first URL, then posts the answer the second URL and then posts the answer to third URL and gets the final answer.

Passing arguments from BatchJob to Tasklet in Spring Batch

To all Spring enthusiasts, here's a good challenge. Hope someone can crack it!!!
I use Spring to batch the extraction process. I have two classes 'ExtractBatchJob' and 'TaskletImpl'
public class ExtractBatchJob {
/** Logger for current class */
private static Log log = LogFactory.getLog(Extractor.class);
public static void main(String[] args)
throws IOrganizationServiceRetrieveMultipleOrganizationServiceFaultFaultFaultMessage,
IOException {
ApplicationContext context = new ClassPathXmlApplicationContext(
"/META-INF/cxf/batch-context.xml");
SpringBusFactory factory = new SpringBusFactory(context);
Bus bus = factory.createBus();
SpringBusFactory.setDefaultBus(bus);
IOrganizationService service = (IOrganizationService) factory
.getApplicationContext().getBean("service");
JobLauncher jobLauncher = (JobLauncher)context.getBean("jobLauncher");
Job job = (Job) context.getBean("firstBatchJob");
try {
JobExecution execution = jobLauncher.run(job, new JobParameters());
}catch (Exception e){
e.printStackTrace();
}
}
The second class TaskletImpl implements the Spring Tasklet interface.
public class TaskletImpl implements Tasklet {
/** Logger for current class */
private static Log log = LogFactory.getLog(CRMExtractor.class);
/* (non-Javadoc)
* #see org.springframework.batch.core.step.tasklet.Tasklet#execute(org.springframework.batch.core.StepContribution, org.springframework.batch.core.scope.context.ChunkContext)
*/
#Overridepublic RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
// TODO Auto-generated method stub
log.info("************ CRM Extraction Batch Job is executing!!! *******");
//QUESTION: To Extract Entity from third party
// web service need object reference for
//factory and service from ExtractBatchjob class
List<Entity> orderEntities = getEntities("orderQueryImpl", factory, service);
OrderDao orderDao = (OrderDao) factory.getApplicationContext()
.getBean("orderDao");
orderDao.batchInsert(orderEntities);*/
return RepeatStatus.FINISHED;
}
public static List<Entity> getEntities(String queryImpl, SpringBusFactory factory,
IOrganizationService service)
throws IOrganizationServiceRetrieveMultipleOrganizationServiceFaultFaultFaultMessage,
IOException {
QueryBuilder queryBuilder = (QueryBuilderTemplate) factory
.getApplicationContext().getBean(queryImpl);
QueryExpression queryExpr = queryBuilder.getQuery();
EntityCollection result = service
.retrieveMultiple(queryExpr);
return result.getEntities().getEntities();
}
}
Below is the snippet of the context file
`<import resource="cxf.xml" />
<bean id="firstBatch" class="com.abc.model.TaskletImpl" />
<batch:step id="firstBatchStepOne">
<batch:tasklet ref="firstBatch" />
</batch:step>
<batch:job id="firstBatchJob">
<batch:step id="stepOne" parent="firstBatchStepOne" />
</batch:job>`
My question is quite straightforward, how do I pass the two variables/objects 'service' and 'factory' to the TaskletImpl class from the ExtractBatchJob class.
The cleanest solution is to wire service and factory using Spring injection mechanism.
You have two solution:
Create SpringBusFactory as Spring bean and wire it into tasklet
Define a ContextBean (as singleton) for you job, create SpringBusFactory and set it as property of ContextBean; wire this bean to your tasklet
If you want to use object created outside Spring context (with new I meant) must be injected into Spring context.

Can I use both configuring SI with annotation in java file and xml?

Last year, spring integration released 4.0 version for us to configure using annotation without configuring in XML files. But I want to use this feature using the existing XML configurations.
So I wrote the code using spring boot and integration annotation
#Configuration
#ComponentScan(basePackages ={"com.strongjoe.blue"},excludeFilters=#ComponentScan.Filter(type=FilterType.REGEX, pattern={"com.strongjoe.blue.agent.Bootstrap*"}))
#IntegrationComponentScan
#ImportResource( {"${context.agent.path}context-bean-*.xml", // Context Configuration
"${project.agent.path}context-properties.xml" } ) // Project Based Chain configuration
public class AgentStarter implements CommandLineRunner{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Lazy
#Bean
#ServiceActivator(inputChannel="blue-hub-start-channel", outputChannel="blue-hub-route-channel")
public <T> BlueMessage<T> startJob(BlueMessage<T> msg) {
logger.debug("BluE Hub Agent started :{} [phrase:{}]", System.currentTimeMillis() , "prototype-version");
return msg;
}
#Lazy
#Bean
#ServiceActivator(inputChannel="blue-hub-end-channel")
public <T> BlueMessage<T> endJob(BlueMessage<T> msg) {
logger.debug("BluE Hub Agent ended :{} [phrase:{}]", System.currentTimeMillis() , "prototype-version");
return msg;
}
#Bean
#Transformer(inputChannel="blue-normalized-channeel", outputChannel="blue-output-channel")
public org.springframework.integration.transformer.Transformer JsonToMap( ) {
return new JsonToObjectTransformer( List.class );
}
#MessageEndpoint
public static class Echo {
#ServiceActivator(inputChannel="blue-output-channel")
public void stringEcho(Message message) {
}
}
#Autowired
Gateway gateway;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(AgentStarter.class);
app.setWebEnvironment(false);
app.run(args).close();
}
#Override
public void run(String... args) throws Exception {
System.err.println("blue-hub-agent started..");
System.out.println(gateway.sendReceive("gitlab"));
}
And I wrote the definition about every channel I use in the xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ws="http://www.springframework.org/schema/integration/ws"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
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-4.0.xsd
http://www.springframework.org/schema/integration/ws http://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd
http://www.springframework.org/schema/integration/xml http://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-4.0.xsd">
<int:channel id="blue-normalized-channel" />
<int:channel id="blue-header-channeel" />
<int:channel id="blue-request-channel" />
<int:channel id="blue-output-channel" />
<int:channel id="blue-gitlab-request-prepare-channel" />
<int:channel id="blue-hub-start-command-channel" />
<int:channel id="blue-hub-start-channel"/>
<int:channel id="blue-hub-end-channel" />
But I got error.
Caused by: org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application:8090.blue-hub-start-channel'.
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:81)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:255)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:223)
The reason will be, I think,
that spring bean in XML file and spring bean with the annotation has different context. So I think that even if blue-hub-start-channel is subscribed by the service activator named "startJob", it got error.
How can I solve this problem?
Annotating #ServiceActivator on #Bean is not for POJO Messaging. See the documentation.
When you annotate #Beans this way, you have to provide an appropriate object (MessageHandler in this case).
For POJO style annotated methods, the annotation must be on a method in a #Bean method (like you have on this one...
#MessageEndpoint
public static class Echo {
#ServiceActivator(inputChannel="blue-output-channel")
public void stringEcho(Message message) {
}
}
... and then declare a #Bean for Echo.
You can put all your #ServiceActivator methods (and #Transformers) in the same #MessageEndpoint.

Exception handling for Spring 3.2 "#Scheduled" annotation

How to customize the exception handling for #Scheduled annotation from spring ?
I have Cron jobs which will be triggered in the server (Tomcat 6) and when any exceptions occur I need to do some handling.
Spring version 3.2
Tomcat Server 6
If you want to use Java Config you will need to create configuration implementing SchedulingConfigurer
#EnableScheduling
#Configuration
class SchedulingConfiguration implements SchedulingConfigurer {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ThreadPoolTaskScheduler taskScheduler;
SchedulingConfiguration() {
taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setErrorHandler(t -> logger.error("Exception in #Scheduled task. ", t));
taskScheduler.setThreadNamePrefix("#scheduled-");
taskScheduler.initialize();
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler);
}
}
You can modify error handler for your needs. Here I only log a message.
Don't forget to call taskScheduler.initialize();. Without it you'll get:
java.lang.IllegalStateException: ThreadPoolTaskScheduler not initialized
You could implement and register an ErrorHandler for the ThreadPoolTaskScheduler that is used for your scheduling annotations.
<task:annotation-driven scheduler="yourThreadPoolTaskScheduler" />
<bean id="yourThreadPoolTaskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="5" />
<property name="errorHandler" ref="yourScheduledTaskErrorHandler" />
</bean>
<bean id="yourScheduledTaskErrorHandler"
class="com.example.YourScheduledTaskErrorHandler"/>
Why not wrap your business logic and do a simple try catch in your #schedule method. Then you can log or take whatever action is necessary for failure cases.
#Scheduled(cron = "${schedulerRate}")
public void scheduledJob() {
try {
businessLogicService.doBusinessLogic();
} catch (Exception e) {
log.error(e);
}
}

How to set EasyMock expectations after #Autowired injection?

I'm trying to get to grips with EasyMock in order to run some server side integration tests on a spring-ws web service. I have a DAO which I want to mock for my integration testing, I've managed to autowire it successfully, but I can't figure out how to set the expectations post autowire.
I have the following in my spring context xml:
<bean id="accountServiceDao" class="org.easymock.EasyMock" factory-method="createMock">
<constructor-arg value="com.xxx.account.dao.AccountServiceDao" />
</bean>
<bean id="notMockedDao" class="com.xxx.account.dao.AccountServiceDaoImpl"/>
<context:component-scan base-package="com.xxx.account" />
<context:property-placeholder location="classpath:accountDetailService_test.properties" />
<sws:annotation-driven />
<jdbc:embedded-database id="dataSource" type="HSQL">
<jdbc:script location="classpath:sql/db_schema.sql" />
<jdbc:script location="classpath:sql/test_data.sql" />
</jdbc:embedded-database>
My dummy test is as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/applicationContext_test.xml" })
public class AccountDetailServiceMockIntergrationTest {
#Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
#Before
public void createClient() {
mockClient = MockWebServiceClient.createClient(applicationContext);
/* Set the expectations for the autowired mock dao here */
}
#Test
public void customerEndpoint() throws Exception {
Source requestPayload = new StringSource(TestData.requestXML);
Source responsePayload = new StringSource(TestData.responseXML);
mockClient.sendRequest(withPayload(requestPayload)).andExpect(
payload(responsePayload));
}
}
The endpoint which is hit is below:
#Autowired
private AccountService accountService;
#PayloadRoot(localPart = "AccountSearchRequest", namespace = TARGET_NAMESPACE)
public #ResponsePayload
AccountSearchResponse getAccountDetails(
#RequestPayload AccountSearchRequest request) {
logger.info("Received request | debtornum - " + request.getDebtornum());
AccountSearchResponse accountSearchResponse = objectFactory.createAccountSearchResponse();
AccountDetailsType accountDetails = accountService.getAccountDetails(request.getDebtornum());
accountSearchResponse.setAccountDetails(accountDetails);
logger.info("Returned response | status - " + accountSearchResponse.getAccountDetails().getDebtorStatus().value());
return accountSearchResponse;
}
And here's the service class which contains the DAO which is being mocked
#Service
public class AccountServiceImpl implements AccountService {
//Autowired on a setter
private AccountServiceDao accountServiceDao;
Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
#Override
public AccountDetailsType getAccountDetails(BigInteger accountNumber) {
........................
Via debug I can see that the mock DAO is getting injected correctly, but I don't know how to set the behavior on the mock object.
For my unit tests I was able to do the following:
accountDao = EasyMock.createMock(AccountServiceDao.class);
EasyMock.expect(accountDao.checkAccountExists(new BigInteger("12345678"))).andReturn(new Account(new BigInteger("12345678"),"Y",1,0,0,0,"ROI","ROI","DO","10012054082","POST","DD","John Doe","a#a.com","123456"));
EasyMock.replay(accountDao);
testSvc.setAccountServiceDao(accountDao);
I'm not sure how to do the same configuration when the mock is autowired via spring xml config. I'm probably missing something obvious or misunderstanding EasyMock, but any help or guidance would be appreciated.
Thanks
As per Dan's comment above, I've set my expectations as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/applicationContext_test.xml" })
public class AccountDetailServiceMockIntergrationTest {
#Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
#Before
public void createClient() {
mockClient = MockWebServiceClient.createClient(applicationContext);
//get the mocked bean from the applicationContext
AccountServiceDao svcDao = (AccountServiceDao)applicationContext.getBean("accountServiceDao");
//reset just in case
EasyMock.reset();
//set expectations on the mock
EasyMock.expect(svcDao.checkAccountExists(new BigInteger("12345678"))).andReturn(new Account(new BigInteger("12345678"),"Y",1,0,0,0,"ROI","ROI","DO","10012054082","POST","DD","John Doe","a#a.com","123456"));
EasyMock.replay(svcDao);
}
#Test
public void customerEndpoint() throws Exception {
Source requestPayload = new StringSource(TestData.requestXML);
Source responsePayload = new StringSource(TestData.responseXML);
mockClient.sendRequest(withPayload(requestPayload)).andExpect(
payload(responsePayload));
}
}
Works fine.

Resources