How use dynamic scheduler in spring framework? - spring

I'm using Spring Security 4.2. I want to block user for 15 minutes after 3 login failure. For blocking user, change user status to BLOCK. I want to return it to ACTIVE after 15 minutes.
I need a dynamic task scheduler that run after 15 minutes and change user status.
Important: I don't want to run method every 15 minutes, I want to run method after 15 minutes if needed.
How do I implement it?

I have some approach. I guess that you should use User class like following:
class User {
// ...
private Status status;
private LocalDateTime lastUpdated;
// ...
}
So that, you should update status and lastUpdated fields during a user blocking.
#Service
public class UserService {
private UserRepository userRepository;
#Transactional
public void block(User user) {
user.setStatus(Status.BLOCKED);
user.setLastUpdated(LocalDateTime.now());
userRepository.save(user);
}
}
After that you can use Spring Scheduler which run every minute and finds users that were blocked 15 minutes ago and sets status to ACTIVE.
#Component
public class UserActivator {
private boolean activateUsers = false;
#Scheduled("0 * * * * *")
public void activateUsers() {
if (activateUsers) {
// find users that were deactivated 15 minutes ago and change status to active
}
}
}
Do not forget to add #EnableScheduling to your Spring config.

You might have table defination for doing this. But this is how I do it generally.
Create a seprate table to maintain the failure count.
Then on basis of this you can check in your service / controller layer.
#Controller
public Class LoginController{
#Autowired
private UserLoginFailureRepository userLoginFailureRepository;
#Autowired
private UserRepostory userRepository;
#Transactional
public void login(){
UserLoginFailure loginFailure = userLoginFailureRepository.getUserLoginfailureDetails(username);
if (loginFailure != null) {
loginFailure.setFailureCount(1l + loginFailure.getFailureCount());
if (loginFailure.getFailureCount() > loginFailureCount) {
// block the user.
User user = userRepository.getUser(username);
user.setStatus(BLOCK);
user.setModificationDate(LocalDateTime.now());
}
}
}
}
Create a seprate Scheduled Job to check and update User Status and reset UserLoginFailure count.
#Component
public class userPasswordResetJob{
#Autowired
private UserLoginFailureRepository userLoginFailureRepository;
#Autowired
private UserRepostory userRepository;
#Scheduled(cron = "${USER_STATUS_JOB}")
public void loginDay() throws Exception{
// Have your logic to update the User status and reset the failure count.
}
}

Related

Hibernate giving old data when queried

I have an entity called Animal which has animalId(pkey) and age. I have added an entry with animalId 1 and age 5.
and a controller which looks like this, when I hit the trigger API expectation is to change the age to 10.
Controller looks like this,
#RequiredArgsConstructor
#RestController
public class TestController {
private final AnimalService animalService;
private final TestAnimalService testAnimalService;
#GetMapping("/trigger")
public ResponseEntity<String> getAnimals() {
animalService.updateAnimalOuter(1); // call update method using an inner UpdateService
animalService.queryAnimal(1); // query the age
return new ResponseEntity<>("success", HttpStatus.OK);
}
}
and AnimalService
I am calling updateAnimalOuter method on AnimalService(note that it has REQUIRES_NEW), which gets the animal by age and calls the updateAnimalInner of UpdateService where the update is actually happening.
#Service
#RequiredArgsConstructor
public class AnimalService {
private final AnimalRepository animalRepository;
private final UpdateService updateService;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateAnimalOuter(int animalId) {
AnimalEntity animal = animalRepository.findByAnimalId(animalId);
//check some properties of animal and do some actions, don't udpate anything here
updateService.updateAnimalInner(animalId); // in this method update
}
#Transactional(propagation = Propagation.REQUIRED)
public void queryAnimal(int animalId) {
AnimalEntity animal = animalRepository.findByAnimalId(animalId);
Integer age = animal.getAge();
if (age == 5) {
throw new RuntimeException("Age is still 5, but the age is 10 in db and this is an new transaction..");
}
}
}
UpdateService looks like this, which has Propagation.REQUIRES_NEW, here I am updating the age to 10.
#Slf4j
#Service
public class UpdateService {
#Autowired
private AnimalRepository animalRepository;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateAnimalInner(int animalId) {
AnimalEntity animal = animalRepository.findByAnimalId(animalId);
animal.setAge(10);
animalRepository.save(animal);
log.info("exiting innerMethod");
}
}
I can see that when this method is exited, the age is propery updated to 10 in the database.
Even when it reaches controller, the age is still 10.
When queryAnimal method is called from controller however the age is returned as 5 and exception is thrown Age is still 5, not updated but I can still see that the age is 10 in db.
Why is hibernate giving age 5 which is old value when queried?
also note that if I remove AnimalEntity animal = animalRepository.findByAnimalId(animalId); from updateAnimalOuter method, it works fine.
I am wondering why this statement causing the query method to return old value even though * it's updated correctly in database * ?
Note: I am using Spring Version 2.5.2 and hibernate 5.4.32 Final

BackgroundJobs do not let you get Users with IRepository

Use case:
I need to pull a user record based on the ARGS coming from the BackgroundJob
public class ULBackgroundJob : BackgroundJob<ULJobArgsDto>, ITransientDependency, IULBackgroundJob
{
private readonly IRepository<User, long> _userRepository;
public ULBackgroundJob
(IRepository<User, long> userRepository)
{
_userRepository = userRepository;
}
public override void Execute(ULJobArgsDto args)
{
User user = _userRepository.FirstOrDefault(args.UserId);
}
}
Results:
I always get zero results and I have checked that the user id value exists.
Suspected Issue:
The SQL that is generated inserts "#__ef_filter__IsMayHaveTenantFilterEnabled_1=1" into the query so I suspect I need to somehow get that set to Zero when I run from a BackgroundJob..?
You need to disable IMayHaveTenant filter to view cross-tenant entities:
using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
{
User user = _userRepository.FirstOrDefault(args.UserId);
}
You can read more about data filters in the ASP.NET Boilerplate documentation: https://aspnetboilerplate.com/Pages/Documents/Data-Filters

Correct way to finish transaction and than do some staff

In my project I need to do some updates in database and finish transaction. Then I need to call external application that should have access to result of current transaction (via other endpoints). currently I have just #Transactional annotation above of endpoint method.
Which is common way to deal with such situations?
Use ApplicationEventPublisher to publish an event at the end of the #Transactional method. Implement a #TransactionalEventListener method to handle this event which by default will only get called after the transaction commits successfully which means you don't need to worry about it will execute accidentally if the transaction fails.
Code wise , it looks like :
#Service
public class MyServce {
#Autowired
private ApplicationEventPublisher appEventPublisher;
#Transactional
public void doSomething(){
Result result = doMyStuff();
appEventPublisher.publishEvent(new StuffFinishedEvent(result));
}
}
public class StuffFinishedEvent{
private Result result;
public StuffFinishedEvent(Result result){
this.result = result;
}
}
And the #TransactionalEventListener :
#Component
public class FinishStuffHandler {
#TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(StuffFinishedEvent event) {
//access the result here.....
event.getResult();
}
}

Why does Spring Data MongoDB not expose events for update…(…) methods?

It appears that the update for mongoOperations do not trigger the events in AbstractMongoEventListener.
This post indicates that was at least the case in Nov 2014
Is there currently any way to listen to update events like below? This seems to be quite a big omission if it is the case.
MongoTemplate.updateMulti()
Thanks!
This is no oversight. Events are designed around the lifecycle of a domain object or a document at least, which means they usually contain an instance of the domain object you're interested in.
Updates on the other hand are completely handled in the database. So there are no documents or even domain objects handled in MongoTemplate. Consider this basically the same way JPA #EntityListeners are only triggered for entities that are loaded into the persistence context in the first place, but not triggered when a query is executed as the execution of the query is happening in the database.
I know it's too late to answer this Question, I have the same situation with MongoTemplate.findAndModify method and the reason I needed events is for Auditing purpose. here is what i did.
1.EventPublisher (which is ofc MongoTemplate's methods)
public class CustomMongoTemplate extends MongoTemplate {
private ApplicationEventPublisher applicationEventPublisher;
#Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher
applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
//Default Constructor here
#Override
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
T result = super.findAndModify(query, update, entityClass);
//Publishing Custom Event on findAndModify
if(result!=null && result instanceof Parent)//All of my Domain class extends Parent
this.applicationEventPublisher.publishEvent(new AfterFindAndModify
(this,((Parent)result).getId(),
result.getClass().toString())
);
return result;
} }
2.Application Event
public class AfterFindAndModify extends ApplicationEvent {
private DocumentAuditLog documentAuditLog;
public AfterFindAndModify(Object source, String documentId,
String documentObject) {
super(source);
this.documentAuditLog = new DocumentAuditLog(documentId,
documentObject,new Date(),"UPDATE");
}
public DocumentAuditLog getDocumentAuditLog() {
return documentAuditLog;
}
}
3.Application Listener
public class FindandUpdateMongoEventListner implements ApplicationListener<AfterFindAndModify> {
#Autowired
MongoOperations mongoOperations;
#Override
public void onApplicationEvent(AfterFindAndModify event) {
mongoOperations.save(event.getDocumentAuditLog());
}
}
and then
#Configuration
#EnableMongoRepositories(basePackages = "my.pkg")
#ComponentScan(basePackages = {"my.pkg"})
public class MongoConfig extends AbstractMongoConfiguration {
//.....
#Bean
public FindandUpdateMongoEventListner findandUpdateMongoEventListner(){
return new FindandUpdateMongoEventListner();
}
}
You can listen to database changes, even the changes completely outside your program (MongoDB 4.2 and newer).
(code is in kotlin language. same for java)
#Autowired private lateinit var op: MongoTemplate
#PostConstruct
fun listenOnExternalChanges() {
Thread {
op.getCollection("Item").watch().onEach {
if(it.updateDescription.updatedFields.containsKey("name")) {
println("name changed on a document: ${it.updateDescription.updatedFields["name"]}")
}
}
}.start()
}
This code only works when replication is enabled. You can enable it even when you have a single node:
Add the following replica set details to mongodb.conf (/etc/mongodb.conf or /usr/local/etc/mongod.conf or C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg) file
replication:
replSetName: "local"
Restart mongo service, Then open mongo console and run this command:
rs.initiate()

Set the cron expression value dynamically

I want to set the cron expression value dynamically using controller. This is my code snippet.
#Service
#EnableScheduling
#Configuration
#PropertySources({
#PropertySource("classpath:properties/cron.properties")
})
public class SchedulerServiceImpl implements SchedulerService{
private final static Logger log = LoggerFactory.getLogger(SchedulerServiceImpl.class);
#Value( "${cron.expression}")
private String cronValue;
#Scheduled(cron = "${cron.expression}")
public void getTweetsFromAPI(){
log.info("Trying to make request for get Tweets through TweetController.");
log.debug("----------------Executing cron.expression {}----------------",cronValue);
}
}
how can I set the cron.expression value dynamically after the deployment. If I used a controller and replace the properties file existing value, Does it work?
Logically your question is wrong. Cron expression is for scheduling a method for a specified time i.e. the method will be executed at a specified time say 12 noon everyday.
Now if you are dynamically changing the cron expression value that means you are controlling the execution of this method and not the scheduler which is wrong.
You can do something like below:-
#Value( "${cron.expression1}")
private String cron1;
#Value( "${cron.expression2}")
private String cron2;
#Scheduled(cron=cron1)
public void method1(){
//call to method
this.method();
}
#Scheduled(cron=cron2)
public void method2(){
//call to method
this.method();
}
private void method(){
//Your scheduling logic goes here
}
Here you are reusing the method() to be scheduled at two different times.

Resources