I am trying to call the functions "beforeApprovingCCTransactions" and "afterApprovingCCTransactions" while "creditCardAmountBorrowedUpdation(..)" is triggered. The function is not getting triggered. Below is the snippet from XML.
File 1 :
package Logger;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
public class AuthorizeCCTransactionsLogs {
public void beforeApprovingCCTransactions() {
System.out.println("Going to begin transaction for Credit Card");
}
public void afterApprovingCCTransactions() {
System.out.println("Going to end transaction for Credit Card");
}
}
File 2:
package AuthorizeCCTransactions;
public class CreditCardHelper {
public void creditCardAmountBorrowedUpdation(CreditCardBean retievedCreditCardBean , double amount ) throws ClassNotFoundException {
retievedCreditCardBean.setAmount(retievedCreditCardBean.getAmount() + amount);
CreditCardTransactionsDao creditCardTransactionsDao = new CreditCardTransactionsDao();
creditCardTransactionsDao.update(retievedCreditCardBean);
}
}
Project Structure
XML File
Related
I'm using Java 8 Spring boot. I have below method.
public hello() {
try {
// send message
}
catch(HttpClientErrorException e) {
if (e.getRawStatusCode() == 401) {
// I need to retry the same hello() method for three times as in 10sec, 20sec and 25sec.
}
}
}
I need to call the same method three times for retrying whenever it hits the catch block.
How can I do this asynchronously?
I found below code but it didn't work.
#Retryable( value = {RestClientException.class}, maxAttempts = 3, backoff = #Backoff(3000))
Appreciate your help.
You can use #Async annotation from Spring to achieve that.
You have to create a config like this:
#Configuration
#EnableRetry
#EnableAsync
class RetryConfig {}
When you want to use Async with Retry you have to decorate the method with Async which is trying to call a Retryable method. Also, you have to make sure that you are returning Future<> or similar because you are sending that piece of code for a toss in the background
I have also implemented fallback mechanism otherwise the request will terminate with 500 exception.
If you run the code below you can see that the main request is executed on thread http-nio-8080-exec-1 while your Async code is executed on a different thread task-1.
I tried to explain this with a sample service method, but the concept will be same for local or remote service call.
A detailed exmaple is given below:
package com.example.silentsudo.springcloudssamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
#SpringBootApplication
public class SpringCloudsSamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudsSamplesApplication.class, args);
}
}
#RequestMapping(path = "sample")
#RestController
class SampleController {
private final GreetService greetService;
SampleController(GreetService greetService) {
this.greetService = greetService;
}
#GetMapping
public String hello() {
System.out.println(Thread.currentThread().getName());
return "Hello!";
}
#GetMapping(path = "greet")
public String greet(#RequestParam(value = "name", defaultValue = "John") String name) {
return greetService.greet(name);
}
#Async
#GetMapping(path = "greet-async")
public CompletableFuture<String> greetAsync(#RequestParam(value = "name", defaultValue = "John") String name) {
return CompletableFuture.completedFuture(greetService.greet(name));
}
}
#Configuration
#EnableRetry
#EnableAsync
class RetryConfig {
}
#Service
class GreetService {
private final UngaBungaService ungaBungaService;
GreetService(UngaBungaService ungaBungaService) {
this.ungaBungaService = ungaBungaService;
}
#Retryable(maxAttempts = 5, value = GreetException.class, backoff = #Backoff(value = 3000L))
public String greet(String name) {
return ungaBungaService.lol(name);
}
#Recover
public String recoverGreetException(GreetException greetException) {
return greetException.getMessage();
}
}
#Service
class UngaBungaService {
public String lol(String name) {
System.out.println(Thread.currentThread().getName());
throw new GreetException("Called greet for " + name);
}
}
class GreetException extends RuntimeException {
public GreetException(String message) {
super(message);
}
}
For retry mechanisms, you can to use the #Retryable(value = RestClientException.class)
For this to trigger, you need to actually throw this exception (or something that extends from RestClientException). Because of your catch statement, no exception is actually thrown, so the retry mechanism doesn't kick in.
#Retryable( value = {RestClientException.class}, maxAttempts = 3, backoff = #Backoff(3000))
public void hello() {
try {
// send message
}
catch(HttpClientErrorException e) {
if (e.getRawStatusCode() == 401) {
throw new RestClientException("meaningfull message");
}
}
}
If you want to run some catch code after the 3 retries failed, you can make use of the #Recover annotation on a recovery method.
If you want some more info on the retry mechanism, you could look here
Also don't forget to add #EnableRetry in your config so that the annotations are used.
Full code example with spring boot
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
context.getBean(TestService.class).hello();
context.close();
}
#Configuration
#EnableRetry
public class AppConfig {
}
#Service
public class TestService {
#Retryable(value = {IllegalArgumentException.class}, maxAttempts = 4, backoff = #Backoff(delay = 1000, multiplier = 4))
public void hello() {
try {
int a = Integer.parseInt(null);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Exception triggered");
}
}
}
Documentation is pretty straight forward which suggests exposing a Bean of type KafkaBindingRebalanceListener and onPartitiosnAssigned method would be called internally. I'm trying to do the same and somehow while spring framework creates its KafkaMessageChannelBinder Bean the ObjectProvider.getIfUnique() always return null as it not able to find the required bean. It seems when application starts SpringFramework strats creating its Beans first and isnt able to find the Rebalance Listener Bean as it is not yet created. Following are the three code snippets from project. Please help if im missing anything to instruct application to create Beans in application package first before going to Spring Framework.
RebalanceListener
package io.spring.dataflow.sample.seekoffset.config;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.common.TopicPartition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.kafka.KafkaBindingRebalanceListener;
import org.springframework.stereotype.Component;
import java.util.Collection;
#Component
public class KafkaRebalanceListener implements KafkaBindingRebalanceListener {
Logger logger = LoggerFactory.getLogger(SeekOffsetConfig.class);
#Override
public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer, Collection<TopicPartition> partitions, boolean initial) {
logger.debug("onPartitionsAssigned");
}
}
ConfigClass
package io.spring.dataflow.sample.seekoffset.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
#EnableBinding(Sink.class)
public class SeekOffsetConfig {
Logger logger = LoggerFactory.getLogger(SeekOffsetConfig.class);
#StreamListener(Sink.INPUT)
public void receiveMessage(Message<String> message) {
logger.debug("receiveMessage()");
}
}
ApplicationClass
package io.spring.dataflow.sample.seekoffset;
import io.spring.dataflow.sample.seekoffset.config.KafkaRebalanceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
public class SeekOffsetApplication {
Logger logger = LoggerFactory.getLogger(SeekOffsetApplication.class);
public static void main(String[] args) {
SpringApplication.run(SeekOffsetApplication.class, args);
}
}
What version are you using? This works fine for me with Boot 2.3.2 and Hoxton.SR6:
#SpringBootApplication
#EnableBinding(Sink.class)
public class So63157778Application {
public static void main(String[] args) {
SpringApplication.run(So63157778Application.class, args);
}
#StreamListener(Sink.INPUT)
public void listen(String in) {
System.out.println(in);
}
#Bean
KafkaBindingRebalanceListener rebal() {
return new KafkaBindingRebalanceListener() {
#Override
public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer,
Collection<TopicPartition> partitions, boolean initial) {
System.out.println(bindingName + " assignments: " + partitions + ", initial call :" + initial);
}
};
}
}
input assignments: [input-0], initial call :true
This works for me too:
#SpringBootApplication
#EnableBinding(Sink.class)
public class So63157778Application {
public static void main(String[] args) {
SpringApplication.run(So63157778Application.class, args);
}
#StreamListener(Sink.INPUT)
public void listen(String in) {
System.out.println(in);
}
}
#Component
class Foo implements KafkaBindingRebalanceListener {
#Override
public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer,
Collection<TopicPartition> partitions, boolean initial) {
System.out.println(bindingName + " assignments: " + partitions + ", initial call :" + initial);
}
}
I have a requirement to implement CustomSqlChange interface while my liquibase script runs. Following is my yaml changeSet entry:
changeSet:
customChange:
param:
_name: file
_value: "/com/example/data/user.csv"
_class: "com.example.MyClass"
id: id
author: abj
failOnError: true
Implementation for MyClass is as follows:
package com.example;
import liquibase.change.custom.CustomSqlChange;
import liquibase.change.custom.CustomSqlRollback;
import liquibase.database.Database;
import liquibase.exception.CustomChangeException;
import liquibase.exception.RollbackImpossibleException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
import liquibase.statement.SqlStatement;
public class MyClass implements CustomSqlChange, CustomSqlRollback {
//to hold the parameter value
private String file;
private ResourceAccessor resourceAccessor;
#Override
public String getConfirmationMessage() {
return "Confirmation Message from Custom SQL Change";
}
#Override
public void setUp() throws SetupException {
}
#Override
public void setFileOpener(ResourceAccessor resourceAccessor) {
this.resourceAccessor = resourceAccessor;
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
#Override
public ValidationErrors validate(Database database) {
return null;
}
#Override
public SqlStatement[] generateStatements(Database database) throws CustomChangeException {
return null;
}
#Override
public SqlStatement[] generateRollbackStatements(Database database)
throws CustomChangeException, RollbackImpossibleException {
return null;
}}
My Normal liquibase script runs successfully. I have a breakpoint in generateStatements method but the flow never comes to the breakpoint. Neither do any loggers are printed for the particular changeset at the time of application startup. Am I missing on to something?
Got it working with a slight change.
Implemented CustomTaskChange instead of CustomSqlChange.
Plus needed a parameter named changeId for some internal business logic.
The code is as follows:
yaml ChangeSet entry:
- changeSet:
id: id
author: abj
changes:
- customChange:
class: com.example.MyClass
params:
- param:
name: changeId
value: id
And Code for MyClass is:
package com.example;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.exception.CustomChangeException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
public class MyClass implements CustomTaskChange {
private String changeId;
public String getChangeId() {
return changeId;
}
public void setChangeId(String changeId) {
this.changeId = changeId;
}
#Override
public String getConfirmationMessage() {
return "Custom Task Change for "+this.getClass();
}
#Override
public void setUp() throws SetupException {
}
#Override
public void setFileOpener(ResourceAccessor resourceAccessor) {
}
#Override
public ValidationErrors validate(Database database) {
return null;
}
#Override
public void execute(Database database) throws CustomChangeException {
System.out.println("Code Flow comes Here");
}}
I have written EntityListener using eclipseLink's "DescriptorEventAdapter". I tried almost all variations whatever present online BUT the entity which I am saving from my listener is not getting saved. I suspect something fishy is going on with transaction but didn't get the root cause. Here is the code :
package com.db;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.queries.InsertObjectQuery;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class NotificationMessageListener extends DescriptorEventAdapter {
public static Logger logger = LoggerFactory.getLogger(NotificationMessageListener.class);
private static final String targetColumn = "STATUS";
//private static AuditRepository auditRepo;
#Autowired
private StatusAuditRepository statusAuditRepo;
#Autowired
private RuleResultAuditRepository ruleResultRepo;
#Autowired
private EntityManagerFactory factory;
JpaEntityManager entityManager = null;
#PostConstruct
public void init() {
try {
entityManager = (JpaEntityManager) factory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(NotificationMessage.class);
// Add this class as a listener to the class descriptor
desc.getEventManager().addListener(this);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
/*#Autowired
public void setAuditRepo(AuditRepository auditRepo) {
NotificationMessageListener.auditRepo = auditRepo;
}*/
#Transactional(value = TxType.REQUIRES_NEW)
#Override
public void postInsert(DescriptorEvent event) {
logger.info("post insert is called ");
//NotificationMessage notificationMsg = (NotificationMessage) ((InsertObjectQuery) event.getQuery()).getObject();
//entityManager.getTransaction().begin();
NotificationStatusAudit statusAudit = new NotificationStatusAudit();
statusAudit.setInsertionTime(new Date());
//statusAudit.setNewVal(notificationMsg.getStatus());
statusAudit.setNewVal("abc");
statusAudit.setOldval("asdf");
statusAudit.setTargetColumnName("from listner");
//statusAudit.setTargetRecordId(notificationMsg.getId());
statusAudit.setTargetRecordId(123L);
statusAudit = statusAuditRepo.save(statusAudit);
//entityManager.getTransaction().commit();
//logger.info("Number of records "+statusAuditRepo.count());
//auditRuleResult(notificationMsg.getMessageCorrelationId() , true);
}
#Override
public void postUpdate(DescriptorEvent event) {
ObjectChangeSet objectChanges = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
DirectToFieldChangeRecord statusChanges = (DirectToFieldChangeRecord) objectChanges
.getChangesForAttributeNamed("status");
if (statusChanges != null && !statusChanges.getNewValue().equals(statusChanges.getOldValue())) {
NotificationStatusAudit statusAudit = new NotificationStatusAudit();
statusAudit.setInsertionTime(new Date());
statusAudit.setNewVal("abc");
statusAudit.setOldval("asdf");
statusAudit.setTargetColumnName(targetColumn);
statusAudit.setTargetRecordId((Long) objectChanges.getId());
statusAudit = statusAuditRepo.save(statusAudit);
}
}
}
Here all I have to do is save the record in another (Audit) table when data is getting inserted in one table. My application is spring boot app and am using eclipseLink for persistent. I had to manually register my entity-listener in "PostConstruct" because if it is registered using #EntityListner annotation , spring-data-repos were not getting autowired. Here are my questions :
1) Using EntityListener for my requirement is good approach or should I use direct "save" operations ?
2) I debugged the EntityListener code and method is not initiated a new Transaction even after adding Requires_new. I can see method is not being called $proxy (spring-proxy). I don't understand why ?
I am not sure about what you are doing in your #PostConstruct init() method... but I suspect you should be configuring this DescriptorEventAdapter using EclipseLink's DescriptorCustomizer. Here is an example:
public class MessageEventListener extends DescriptorEventAdapter implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) {
descriptor.getEventManager().addListener(this);
}
#Override
public void postUpdate(DescriptorEvent event) {
ObjectChangeSet objectChanges = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
//More business logic...
}
}
#Entity
#Customizer(MessageEventListener.class)
public class Message {
#Id private long id;
private String content;
}
I am trying to understand Stateful Session bean life cycle.I am particularly interested when activation and passivation life cycle callback invoked.I created a demo to understand this scenario. Here is My code :
My Remote interface is :
package com.orbit.stateful;
import javax.ejb.Remote;
import java.util.List;
#Remote
public interface ShoppingCart
{
public void addItem(String itemName);
public void removeItem(String item);
public List<String> getAllItems();
public void finishShopping();
}
My Stateful session bean class is as follow:
package com.orbit.stateful;
import javax.ejb.Stateful;
import javax.ejb.Remove;
import javax.ejb.PrePassivate;
import javax.ejb.PostActivate;
import javax.ejb.StatefulTimeout;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
//import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.ArrayList;
#Stateful
//#StatefulTimeout(value=5L,unit=TimeUnit.MINUTES)
public class ShoppingCartBean implements ShoppingCart
{
private List<String> itemList;
public ShoppingCartBean(){
System.out.println("Stateful SessionBean Constructor Called");
}
#PostConstruct
public void intialize(){
System.out.println("Initializing shopping Cart");
itemList=new ArrayList<String>();
}
public void addItem(String itemName){
itemList.add(itemName);
}
public void removeItem(String item){
itemList.remove(item);
}
public List<String> getAllItems(){
return itemList;
}
#Remove
public void finishShopping(){
System.out.println("I am finished with Shopping");
}
#PreDestroy
public void tidyUp(){
System.out.println("Remove Shopping Bean");
}
#PrePassivate
public void logPassivation(){
System.out.println("Passivating Now");
}
#PostActivate
public void logActivation(){
System.out.println("Activating Now");
}
}
And standalone client for this bean is :
package com.orbit.stateful;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Properties;
import java.util.List;
public class ShoppingCartClient
{
public static void main(String[] args) throws Exception
{
Properties props = new Properties();
props.setProperty("java.naming.factory.initial",
"com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs",
"com.sun.enterprise.naming");
props.setProperty("java.naming.factory.state",
"com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
props.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
props.setProperty("org.omg.CORBA.ORBInitialPort", "3700");
Context ctx=new InitialContext(props);
ShoppingCart sc=(ShoppingCart)ctx.lookup("com.orbit.stateful.ShoppingCart");
sc.addItem("J2SE");
//Thread.sleep(17000);
sc.addItem("J2EE");
sc.addItem("JDBC");
List<String> books=sc.getAllItems();
for (String str: books )
{
System.out.println("Books is : "+ str);
}
//sc.finishShopping();
}
}
Now I have set Cache Idle Timeout=20 in GlassFish 3.1.2
But still My stateful session bean is not passivating after 20 seconds. am I doing something wrong or something is not happening correctly in Glashfish ?Help me to understand this.
Thanks in Advance