I am using JBPM 6.2 core engine in our application.
I wanted to use my own Custom Task Event Listener to execute my own java code.
I didn't find any documentation so that I can use with JBPM core runtime engine. I found multiple places to use with console and define in deployment descriptor.
Please help!
We can register Task event Listener at RuntimeManager level or at TaskService level.
TaskService taskService = runtime.getTaskService();
((EventService<TaskLifeCycleEventListener>)taskService).registerTaskEventListener(new DefaultTaskEventListener() {
#Override
public void afterTaskAddedEvent(TaskEvent event) {
System.out.println("taskId = " + event.getTask().getId());
}
});
At RuntimeManager level:
RuntimeEnvironment environment =
RuntimeEnvironmentBuilder.getDefault()
.persistence(true)
.entityManagerFactory(emf)
.userGroupCallback(userGroupCallback)
.addAsset(ResourceFactory.newClassPathResource(process),
ResourceType.BPMN2)
.registerableItemsFactory(new DefaultRegisterableItemsFactory() {
#Override
public List getTaskListeners() {
List listeners = super.getTaskListeners();
listeners.add(new DefaultTaskEventListener() {
#Override
public void afterTaskAddedEvent(TaskEvent event) {
System.out.println("taskId = " + event.getTask().getId());
}
});
return listeners;
}
})
.get();
return RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager(environment);
Related
I have a reporting application that generates a report on ApplicationReadyEvent. I am trying to write cucumber tests for it but as the application event is fired even before my feature is executed , i am not sure what is the right way to test it. Can i control the event during testing ?
#EventListener(ApplicationReadyEvent.class)
private void generateAccuracyAnalysisReport() throws IOException
{
//some Logic
}
Cucumber Classes :
#SpringBootTest
#CucumberContextConfiguration
#ActiveProfiles("junit")
public class CucumberConfiguration
{
}
#RunWith(Cucumber.class)
#CucumberOptions(plugin = "pretty", features = "src/test/resources/cucumber/features")
public class CucumberFullIntegrationTest
{
}
Step Definition:
public class ReportStepDefs implements En {
public ReportStepDefs() {
When("^System sends an application event to generate report$", () -> {
});
Then("^Report should be generated successfully\\.$", () -> {
});
}
}
If your Cucumber tests involve Spring life-cycle you can not use cucumber-spring. Rather you have to use something like Springs ApplicationContextRunner to, configure, run and verify something about your application as part of each scenario.
// Given
ApplicationContextRunner contextRunner = new ApplicationContextRunner();
// When
contextRunner.withConfiguration(AutoConfigurations.of(...);
// Then
contextRunner.run(context -> assertThat(context).... /* something */ );
// Or assert something external to the application context.
Though it sounds like your application is doing something once and then exits. If so you should be using the CommandLineRunner instead of ApplicationReadyEvent in a web application. This is testable with cucumber-spring.
#RequiredArsConstructor
public class StepDefinitions {
final MyCommandLineRunner commandLineRunner;
#When(....)
public void something() {
commandLineRunner.run("input.txt", "input2.txt");
}
#Then(....)
public void assertSomething() {
// check if report was generated
}
}
I have created spring boot thread pool project which has thread that needs to run 24x7 once spawned but when I need to stop the app in server for some maintenance it should shutdown after completing its current task and not taking up any new task.
My code for the same is:
Config class
#Configuration
public class ThreadConfig {
#Bean
public ThreadPoolTaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executorPool = new ThreadPoolTaskExecutor();
executorPool.setCorePoolSize(10);
executorPool.setMaxPoolSize(20);
executorPool.setQueueCapacity(10);
executorPool.setWaitForTasksToCompleteOnShutdown(true);
executorPool.setAwaitTerminationSeconds(60);
executorPool.initialize();
return executorPool;
}
}
Runnable class
#Component
#Scope("prototype")
public class DataMigration implements Runnable {
String name;
private boolean run=true;
public DataMigration(String name) {
this.name = name;
}
#Override
public void run() {
while(run){
System.out.println(Thread.currentThread().getName()+" Start Thread = "+name);
processCommand();
System.out.println(Thread.currentThread().getName()+" End Thread = "+name);
if(Thread.currentThread().isInterrupted()){
System.out.println("Thread Is Interrupted");
break;
}
}
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void shutdown(){
this.run = false;
}
}
Main class:
#SpringBootApplication
public class DataMigrationPocApplication implements CommandLineRunner{
#Autowired
private ThreadPoolTaskExecutor taskExecutor;
public static void main(String[] args) {
SpringApplication.run(DataMigrationPocApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
for(int i = 1; i<=20 ; i++){
taskExecutor.execute(new DataMigration("Task " + i));
}
for (;;) {
int count = taskExecutor.getActiveCount();
System.out.println("Active Threads : " + count);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 0) {
taskExecutor.shutdown();
break;
}
}
System.out.println("Finished all threads");
}
}
I need help to understand if I need to stop my spring boot application it should stop all the 20 threads running which runs (24x7) otherwise after completing there current loop in while loop and exit.
I would propose couple of changes in this code to resolve the problem
1) since in your POC processCommand calls Thread.sleep, when you shutdown the executor and it interrupts workers InterruptedException get called but is almost ignored in your code. After that there is if(Thread.currentThread().isInterrupted()) check which will return false for the reason above. Similar problem is outlined in the post below
how does thread.interrupt() sets the flag?
the following code change should fix the problem:
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
shutdown();
}
}
2) Also because of ThreadConfig::taskExecutor executorPool.setWaitForTasksToCompleteOnShutdown(true) Spring will call executor.shutdown instead of executor.shutdownNow. According to javadoc ExecutorService.shutdown
Initiates an orderly shutdown in which previously submitted tasks are
executed, but no new tasks will be accepted.
So I would recommend to set
executorPool.setWaitForTasksToCompleteOnShutdown(false);
Other things to improve in this code: although DataMigration is annotated as a component the instances of this class are creared not by Spring. You should try using factory method similar to ThreadConfig::taskExecutor in order to make Spring initiate instances of DataMigration for example to inject other bean into DataMigration instances.
In order to shutdown executor when running jar file on linux environment you can for example add actuator module and enable shutdown endpoint:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
in application.properties:
endpoints.shutdown.enabled=true
It will enable JMX shutdown endpoint and you can call shutdown on it.
If you want current job cycle of the task to be finished you should set
executorPool.setWaitForTasksToCompleteOnShutdown(true);
In order to connect to your jvm process on linux env remotely you have to specify an RMI Registry port.
Here is a detailed article:
How to access Spring-boot JMX remotely
If you just need to connect to JMX from local env you can run jsoncole or command-line tools : Calling JMX MBean method from a shell script
Here is an example uf using one of these tools - jmxterm
$>run -d org.springframework.boot: -b org.springframework.boot:name=shutdownEndpoint,type=Endpoint shutdown
#calling operation shutdown of mbean org.springframework.boot:name=shutdownEndpoint,type=Endpoint with params []
#operation returns:
{
message = Shutting down, bye...;
}
The context is the next:
I have a web app using Spring 2.5 and Struts 1.1
I create a job dynamically in an Action using Quartz:
JobDetailBean jobDetail = new JobDetailBean();
jobDetail.setBeanName("foo");
Map<String, String> map = new HashMap<String,String>();
map.put("idFeed","foo");
map.put("idSite","foo");
jobDetail.setJobDataAsMap(map);
jobDetail.setJobClass(FeedJob.class);
jobDetail.afterPropertiesSet();
CronTriggerBean cronTrigger = new CronTriggerBean();
cronTrigger.setBeanName("foo");
String expression = " * * * * * *";
cronTrigger.setCronExpression(expression);
cronTrigger.afterPropertiesSet();
// add to schedule
scheduler.scheduleJob((JobDetail) jobDetail, cronTrigger);
scheduler is a org.quartz.Scheduler injected in the Action.
The class FeedJob has the method executeInternal(JobExecutionContext ctx) which is the code the job has to run:
public class FeedJob extends QuartzJobBean {
private FeedBL feedBL;
public void setFeedBL(FeedBL feedBL) {this.feedBL = feedBL;}
public FeedJob() {}
public String idFeed;
public String idSite;
public String getIdFeed() {
return idFeed;
}
public void setIdFeed(String idFeed) {
this.idFeed = idFeed;
}
public String getIdSite() {
return idSite;
}
public void setIdSite(String idSite) {
this.idSite = idSite;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
try {
feedBL.sincronizacionProductFeed(idFeed, idSite);
} catch (Exception e) {
e.printStackTrace();
}
}
}
And when its going to run, I get a java.lang.NullPointerException when trying to run this line of code:
feedBL.sincronizacionProductFeed(idFeed, idSite);
The reason is when I'm creating the job in the Action I'm setting the job:
jobDetail.setJobClass(FeedJob.class);
And Spring doesn't notice about the bean he has already created, so that instance of the FeedJob class hasn't god injected the feedBL class.
Any good idea for solving this problem?
I have tried to give the job the context like this:
jobDetail.setApplicationContext(applicationContext);
But doesnt work.
You may want to check this answer. It solves the same problem you are experiencing.
I am using spring.security.version = 3.1.0.RELEASE. The problem I am having is that for some reason AuthenticationFailureCredentialsExpiredEvent is not fired.
While debugging the code I found that AbstractUserDetailsAuthenticationProvider do display in the console that "User account credentials have expired". But I am still baffling as to why the event in concern is not triggered.
Here is my code:
class JpaUserDetails implements UserDetails {
...
...
#Override
public boolean isCredentialsNonExpired() {
if (some logic) {
return true;
}
else {
return false;
}
}
}
I do see AbstractUserDetailsAuthenticationProvider displaying in the console "User account credentials have expired" from the following lines of spring code:
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitilizeBean, MessageSourceAware {
...
...
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
if(!user.isCredentialsNonExpired()) {
logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(message.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired",
"User credentials have expired"), user);
}
}
}
}
The issue is that when the user credentials have expired, I am expecting the Spring to generate the event AuthenticationFailureCredentialsExpiredEvent which I am handling in the following way:
class SecurityEventDispatcher implements ApplicationListener<ApplicationEvent> {
final List<SecurityEventListener> listeners = new ArrayList<SecurityEventListener>();
public void registerListener(SecurityEventListener listener) {
this.listener.add(listener);
}
public void onApplicationEvent(ApplicationEvent event) {
for (SecurityEventListener listener : this.listeners) {
if(listener.canHandle(event)) {
listener.handle(event);
}
}
}
}
This is how I am handling the login failure event:
public class LoginFailedEvent extends SecurityEventListener {
#Override
public boolean canHandle(Object event) {
if(event instanceof AbstractAuthenticationFailureEvent) {
return true;
}
else {
return false;
}
}
#Override
public void handle(Object event) {
if (event instanceof AuthenticationFailureBadCredentialsEvent) {
// do something
}
if (event instanceof AuthenticationFailureCredentialsExpiredEvent) {
// do something
}
}
}
The issue as I mentioned before is that AuthenticationFailureCredentialsExpiredEvent is never fired. I have tested the AuthenticationFailureBadCredentialsEvent which works fine.
This is what I get in event for bad credentials: (which is working fine)
org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent
This is what I get in event for expired password:
ServletRequestHandledEvent: url=[/app/loginFailure] with failureCause = null
Does anyone have any idea what could be wrong? Any help will be highly appreciated.
Here is the answer to this question, since there isn't any much literature out there regarding the issue.
You probably need to set the ProviderManager's
('s) eventPublisher to be something other than
NullEventPublisher. There is not a simple way to do this via the
tag, so you will want to create the
AuthenticationProvider using standard beans configuration and inject
it into a standard Spring Bean for the ProviderManager.
Rob Winch - Spring Security Lead
If anyone is running into this issue, just upgrade the spring security to 3.1.2 or +, the issue is fixed.
(Applies to Spring Security 5)
Spring's default publisher is the NullEventPublisher. This will effectively publish nothing.To get events please configure a
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
Now the events a published and one can just consume them as any other event:
#EventListener
public void logAuditEvents(AbstractAuthenticationEvent event) {
...
}
and
#EventListener
public void logAuditEvents(AbstractAuthorizationEvent event) {
...
}
I'm trying to unit test the custom events that I've created in Spring and am running into an interesting problem. If I create a StaticApplicationContext and manually register and wire the beans I can trigger events and see the program flow through the publisher (implements ApplicationEventPublisherAware) through to the listener (implements ApplicationListener<?>).
Yet when I try to create a JUnit test to create the context using the SpringJunit4ClassRunner and #ContextConfiguration everything works well except that the ApplicationEvents are not showing up in the listener (I have confirmed that they are getting published).
Is there some other way to create the context so that ApplicationEvents will work correctly? I haven't found much on the web about unit testing the Spring events framework.
The events will not fire because your test classes are not registered and resolved from the spring application context, which is the event publisher.
I've implemented a workaround for this where the event is handled in another class that is registered with Spring as a bean and resolved as part of the test. It isn't pretty, but after wasting the best part of a day trying to find a better solution I am happy with this for now.
My use case was firing an event when a message is received within a RabbitMQ consumer. It is made up of the following:
The wrapper class
Note the Init() function that is called from the test to pass in the callback function after resolving from the container within the test
public class TestEventListenerWrapper {
CountDownLatch countDownLatch;
TestEventWrapperCallbackFunction testEventWrapperCallbackFunction;
public TestEventListenerWrapper(){
}
public void Init(CountDownLatch countDownLatch, TestEventWrapperCallbackFunction testEventWrapperCallbackFunction){
this.countDownLatch = countDownLatch;
this.testEventWrapperCallbackFunction = testEventWrapperCallbackFunction;
}
#EventListener
public void onApplicationEvent(MyEventType1 event) {
testEventWrapperCallbackFunction.CallbackOnEventFired(event);
countDownLatch.countDown();
}
#EventListener
public void onApplicationEvent(MyEventType2 event) {
testEventWrapperCallbackFunction.CallbackOnEventFired(event);
countDownLatch.countDown();
}
#EventListener
public void onApplicationEvent(OnQueueMessageReceived event) {
testEventWrapperCallbackFunction.CallbackOnEventFired(event);
countDownLatch.countDown();
}
}
The callback interface
public interface TestEventWrapperCallbackFunction {
void CallbackOnEventFired(ApplicationEvent event);
}
A test configuration class to define the bean which is referenced in the unit test. Before this is useful, it will need to be resolved from the applicationContext and initialsed (see next step)
#Configuration
public class TestContextConfiguration {
#Lazy
#Bean(name="testEventListenerWrapper")
public TestEventListenerWrapper testEventListenerWrapper(){
return new TestEventListenerWrapper();
}
}
Finally, the unit test itself that resolves the bean from the applicationContext and calls the Init() function to pass assertion criteria (this assumes you have registered the bean as a singleton - the default for the Spring applicationContext). The callback function is defined here and also passed to Init().
#ContextConfiguration(classes= {TestContextConfiguration.class,
//..., - other config classes
//..., - other config classes
})
public class QueueListenerUnitTests
extends AbstractTestNGSpringContextTests {
private MessageProcessorManager mockedMessageProcessorManager;
private ChannelAwareMessageListener queueListener;
private OnQueueMessageReceived currentEvent;
#BeforeTest
public void Startup() throws Exception {
this.springTestContextPrepareTestInstance();
queueListener = new QueueListenerImpl(mockedMessageProcessorManager);
((QueueListenerImpl) queueListener).setApplicationEventPublisher(this.applicationContext);
currentEvent = null;
}
#Test
public void HandleMessageReceived_QueueMessageReceivedEventFires_WhenValidMessageIsReceived() throws Exception {
//Arrange
//Other arrange logic
Channel mockedRabbitmqChannel = CreateMockRabbitmqChannel();
CountDownLatch countDownLatch = new CountDownLatch(1);
TestEventWrapperCallbackFunction testEventWrapperCallbackFunction = (ev) -> CallbackOnEventFired(ev);
TestEventListenerWrapper testEventListenerWrapper = (TestEventListenerWrapper)applicationContext.getBean("testEventWrapperOnQueueMessageReceived");
testEventListenerWrapper.Init(countDownLatch, testEventWrapperCallbackFunction);
//Act
queueListener.onMessage(message, mockedRabbitmqChannel);
long awaitTimeoutInMs = 1000;
countDownLatch.await(awaitTimeoutInMs, TimeUnit.MILLISECONDS);
//Assert - assertion goes here
}
//The callback function that passes the event back here so it can be made available to the tests for assertion
private void CallbackOnEventFired(ApplicationEvent event){
currentEvent = (OnQueueMessageReceived)event;
}
}
EDIT 1: The sample code has been updated with CountDownLatch
EDIT 2: Assertions didn't fail tests so the above was updated with a different approach**
I just run my app as SpringBootTest, application events working fine:
#TestComponent
public class EventTestListener {
#EventListener
public void handle(MyCustomEvent event) {
// nothing to do, just spy the method...
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyEventTest {
#SpyBean
private EventTestListener testEventListener;
#Test
public void testMyEventFires() {
// do something that fires the event..
verify(testEventListener).handle(any(MyCustomEvent.class));
}
}
use the #Captor / ArgumentCaptor to verify the content of your event.
You can create a context manually.
For example: I had needed to check if my ApplicationListener<ContextClosedEvent> closed Cassandra connections:
#Test
public void testSpringShutdownHookForCassandra(){
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CassandraConfig.class);
CassandraConnectionManager connectionManager = ctx.getBean(CassandraConnectionManager.class);
Session session = connectionManager.openSession(testKeySpaceName);
Assert.assertFalse( session.isClosed() );
ctx.close();
Assert.assertTrue( session.isClosed() );
}