Schedule a function in spring boot with #Scheduled annotation - spring

I would like to execute the following method at the date and time speified on my Angular form
-Here is the input:
<input required [(ngModel)]="emailNotification.sendingDate" class="form-control" type="datetime-local" name="sendingDate" id="time">
The sending emails method(from the controller)
#PostMapping(value="/getdetails")
public #ResponseBody void sendMail(#RequestBody EmailNotification details) throws Exception {
try {
JavaMailSenderImpl jms = (JavaMailSenderImpl) sender;
MimeMessage message = jms.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, StandardCharsets.UTF_8.name());
helper.setFrom("smsender4#gmail.com");
List<String> recipients = fileRepo.findWantedEmails(details.getDaysNum());
String[] to = recipients.stream().toArray(String[]::new);
helper.setTo(to);
helper.setText(details.getMessage(),true);
helper.setSubject("Test Mail");
details.setRecipients(to);
sender.send(message);
enr.save(new EmailNotification(details.getId(), "Test mail", details.getMessage(), details.getDaysNum(), details.getRecipients(), details.getSendingDate()));
} catch (MessagingException e) {
throw new RuntimeException("fail to send emails: " + e.getMessage());
}
EmailNotification.class
public class EmailNotification {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String subject;
private String message;
private int daysNum;
private String[] recipients;
/*
* #Temporal(TemporalType.TIMESTAMP)
*/
//"yyyy-MM-dd'T'HH:mm:ss.SSSX"
#Column(name = "sending_date")
#Basic(fetch = FetchType.EAGER)
private LocalDateTime sendingDate;
public EmailNotification(long id, String subject,String message, int daysNum, String[] recipients, LocalDateTime sendingDate) {
super();
this.sendingDate = sendingDate;
this.daysNum = daysNum;
this.id = id;
this.message = message;
this.subject = subject;
}
I don't know how to proceed, IĆ¹ll be so gratefull if someone helped

Now I think I understand what you are trying to achieve, so check if this works for you.
Let's say you want to send an email when in the given table (I will name it PAYMENT), there's 5 days left for the deadline. Let's say this table has an equivalent entity class named Payment, you could do something like this:
Asumming the Payment entity has it's own CrudRepository extention, which you are using to access the dabatase, you would make a query to find the payments close to the deadline. This of course will depend on how you are accessing your database. The next example query probably won't work (because as far as I know, JPA doesn't support DATEDIFF), but will work as base for what you are trying to achieve
#Repository
public interface PaymentRepository extends CrudRepository<Payment, Integer> {
/* You need to find every payment that's 5 days from the deadline */
#Query("SELECT p FROM PAYMENT p WHERE DATEDIFF(day, CURRENT_TIMESTAMP, p.deadline) <= 5")
public List<PaymentRepository> findPaymentsCloseToDeadline();
}
Make a scheduled task class that sends the email for every payment close to the deadline. Instead of passing parameters to the method, use the database access you already have to fill your email. You should not depend of information received by the front (like Angular), because it's easily manipulable. Use the information you know, the information stored in the database. In this example the tasks starts a 10:00 hours:
#Configuration
#EnableScheduling
public class EmailScheduler {
#Autowired
private PaymentRepository paymentRepository;
#Scheduled(cron = "0 0 10 * * ?")
public void sendEmails() {
JavaMailSenderImpl jms = (JavaMailSenderImpl) sender;
MimeMessage message = jms.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, StandardCharsets.UTF_8.name());
// Here you are accessing the payments close to the deadline
List<Payment> payments = paymentRepository.findPaymentsCloseToDeadline();
/* Send an email for each payment. Use the database info you already have to fill the information */
for (Payment payment : payments) {
try {
helper.setFrom("smsender4#gmail.com");
// Fill the recipients with the info you need
String[] recipients = {payment.getUserEmail(), "other#email.com"};
helper.setTo(recipients);
String msg = "Here would go the message";
helper.setText(msg, true);
helper.setSubject("Test Mail");
sender.send(message);
// You would still need to calculate the ID and the days if you require them
enr.save(new EmailNotification(id, "Test mail", msg, days,
recipients, "today's date in LocalDate"));
} catch (MessagingException e) {
throw new RuntimeException("fail to send emails: " + e.getMessage());
}
}
}
}
This is only a guideline because there's still some things I don't know about your code, but I hope it's clear enough so it helps you. Good luck.

Related

Spring WebFlux Mono.block() is not returning any response even no time out occured

there i had tried to get a Mono from my repository of Mongo. But my debugger is lost after receiving the response from repo as Mono object and applied block() to it.
HERE IS THE DETAILED CODE...
private Map<String, Object> parameters(Bid bid,String tripId) {
final Map<String, Object> parameters = new HashMap<>();
ShipperLoad shipperLoad = (ShipperLoad)bid.getLoad();
// getting supplier data from other service..
SupplierUserDTO supplier =
WebClient.
create("http://localhost:8888/XXXXXXXX/"+bid.getSupplierId())
.get()
.retrieve()
.bodyToMono(SupplierUserDTO.class)
.block();
TripInvoiceDetails tripInvoice = bidService.getInvoiceDetails(tripId).block();
parameters.put("load", shipperLoad);
parameters.put("bid", bid);
parameters.put("logo", getClass().getResourceAsStream(logo_path));
parameters.put("supplier", supplier);
parameters.put("invoice", tripInvoice);
return parameters;
}
Bid service method:
public Mono<TripInvoiceDetails> getInvoiceDetails(String tripId)
{
Mono<TripInvoiceDetails> invoice = tripInvoiceRepository.findByTripId(tripId);
return invoice;
}
Repository
public interface TripInvoiceRepository extends ReactiveMongoRepository<TripInvoiceDetails, String>{
Mono<TripInvoiceDetails> findByTripId(String tripId);
}
The control is lossing on bidService.getInvoiceDetails(tripId).block();
TripInvoiceDetails.java
#Data
#Document("tripInvoice")
#NoArgsConstructor
#AllArgsConstructor
public class TripInvoiceDetails {
#Id
String id;
String invoiceNo;
Double invoiceamount;
Double invoiceamountGst;
ValueLabel packageType;
Integer noOfUnits;
ValueLabel materialType;
List<ValueLabel> materialTypeSecondary;
String hsnCode;
String consigneeName;
String address;
String gstOrPan;
String tripId;
String transporterId;
String shipperName;
String loadId;
}
console log
Resolved
try {
//pls don't remove futureData since. it is required to resolve futureData of Mono..
CompletableFuture<TripInvoiceDetails> futureData = tripInvoice.toFuture();
invoice = tripInvoice.toFuture().get();
} catch (Exception e) {
e.printStackTrace();
}
Thanks all for your valuable response. Finally, I got a solution, I just break the .bock() method into steps, which means the did the exact implementation of the .block() method instead of calling .block() it works for me, maybe the block in the earlier case is waiting for any producer which may be in this case not marked as a producer.
try {
//pls don't remove futureData since. it is required to resolve futureData of Mono..
CompletableFuture<TripInvoiceDetails> futureData = tripInvoice.toFuture();
invoice = tripInvoice.toFuture().get();
} catch (Exception e) {
e.printStackTrace();
}

Send websocket message to user across dynos

I have a spring boot application running on heroku. I make use of websockets for sending messages to and from client and server for a specific user . I use spring boot's SimpMessagingTemplate.convertAndSendToUser to send and receive messages, which works fine for when a user needs get a message back from the server. I use Heroku session affinity which means that even if I scale up the number of sessions the user and websocket still share the same session.
My problem comes when I need a user to send a message to another user. It works fine if both users are sharing the session, but not if the message will not come through.
Is it possible to send a message from one user to another across different sessions using, SimpMessagingTemple? Or would I need to use a message broker, eg Redis.
I was looking into implementing sending a message using StringRedisTemplate but not sure how to send a message to a particular user.
private SimpMessagingTemplate messagingTemplate;
#Autowired
public MessageController(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#MessageMapping("/secured/user-in")
public void sendToDevice(Message msg, #AuthenticationPrincipal User principal) throws Exception {
if (msg.getTo() != null) {
String email = msg.getTo();
Message out = new Message();
out.setMsg(msg.getMsg());
out.setFrom(msg.getFrom());
out.setTo(msg.getTo());
out.setSentTime(new Date());
out.setStatus(msg.getStatus());
messagingTemplate.convertAndSendToUser(email, "/secured/topic", out);
}
}
JS
function connect() {
var socket = new SockJS('/secured/user-in');
ST.stompClient = Stomp.over(socket);
var headers = {};
headers[ST.getHeader()] = ST.getToken();
ST.getStompClient().connect(headers, function (frame) {
retries = 1;
console.log('Connected: ' + frame);
ST.getStompClient().subscribe('/user/secured/topic', function (event){
var msg = JSON.parse(event.body);
showMessage(msg.msg);
});
});
}
UPDATE 1
I am guessing I could do something like this, as done here:
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload,
headerAccessor.getMessageHeaders());
But how could I get the session id of another user, I am using Redis to store session info: #EnableRedisHttpSession
I had my terminology a bit mixed up I was trying to send a message to another user on another dyno rather than session.
Ended up using redis sub/pub.
So when a message is receive by the controller it is published to redis, and the redis MessageListenerAdapter envokes the convertAndSendToUser method.
#MessageMapping("/secured/user-in")
public void sendToDevice(Message msg, #AuthenticationPrincipal User principal) throws Exception {
publishMessageToRedis(msg);
}
private void publishMessageToRedis(Message message) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String messageString = objectMapper.writeValueAsString(message);
stringRedisTemplate.convertAndSend("message", messageString);
}
redis config
#Bean
RedisMessageListenerContainer container( MessageListenerAdapter chatMessageListenerAdapter) throws URISyntaxException {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.addMessageListener(chatMessageListenerAdapter, new PatternTopic("message"));
return container;
}
#Bean("chatMessageListenerAdapter")
MessageListenerAdapter chatMessageListenerAdapter(RedisReceiver redisReceiver) {
return new MessageListenerAdapter(redisReceiver, "receiveChatMessage");
}
public class RedisReceiver {
private static final Logger LOG = LogManager.getLogger(RedisReceiver.class);
private final WebSocketMessageService webSocketMessageService;
#Autowired
public RedisReceiver(WebSocketMessageService webSocketMessageService) {
this.webSocketMessageService = webSocketMessageService;
}
// Invoked when message is publish to "chat" channel
public void receiveChatMessage(String messageStr) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Message message = objectMapper.readValue(messageStr, Message.class);
webSocketMessageService.sendChatMessage(message);
}
}
#Service
public class WebSocketMessageService {
private final SimpMessagingTemplate template;
private static final Logger LOG = LogManager.getLogger(WebSocketMessageService.class);
public WebSocketMessageService(SimpMessagingTemplate template) {
this.template = template;
}
public void sendChatMessage(Message message) {
template.convertAndSendToUser(message.getTo(), "/secured/topic", message);
}
}
Solution was based off this git repository

Spring Transactional method not working properly (not saving db)

I have spent day after day trying to find a solution for my problem with Transactional methods. The logic is like this:
Controller receive request, call queueService, put it in a PriorityBlockingQueue and another thread process the data (find cards, update status,assign to current game, return data)
Controller:
#RequestMapping("/queue")
public DeferredResult<List<Card>> queueRequest(#Params...){
queueService.put(result, size, terminal, time)
result.onCompletion(() -> assignmentService.assignCards(result, game,room, cliente));
}
QueueService:
#Service
public class QueueService {
private BlockingQueue<RequestQueue> queue = new PriorityBlockingQueue<>();
#Autowired
GameRepository gameRepository;
#Autowired
TerminalRepository terminalRepository;
#Autowired
RoomRpository roomRepository;
private long requestId = 0;
public void put(DeferredResult<List<Card>> result, int size, String client, LocalDateTime time_order){
requestId++;
--ommited code(find Entity: game, terminal, room)
try {
RequestQueue request= new RequestCola(requestId, size, terminal,time_order, result);
queue.put(request);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
CardService:
#Transactional
public class CardService {
#Autowired
EntityManager em;
#Autowired
CardRepository cardRepository;
#Autowired
AsignService asignacionService;
public List<Cards> processRequest(int size, BigDecimal value)
{
List<Card> carton_query = em.createNativeQuery("{call cards_available(?,?,?)}",
Card.class)
.setParameter(1, false)
.setParameter(2, value)
.setParameter(3, size).getResultList();
List<String> ids = new ArrayList<String>();
carton_query.forEach(action -> ids.add(action.getId_card()));
String update_query = "UPDATE card SET available=true WHERE id_card IN :ids";
em.createNativeQuery(update_query).setParameter("ids", ids).executeUpdate();
return card_query;
}
QueueExecutor (Consumer)
#Component
public class QueueExecute {
#Autowired
QueueService queueRequest;
#Autowired
AsignService asignService;
#Autowired
CardService cardService;
#PostConstruct
public void init(){
new Thread(this::execute).start();
}
private void execute(){
while (true){
try {
RequestQueue request;
request = queueRequest.take();
if(request != null) {
List<Card> cards = cardService.processRequest(request.getSize(), new BigDecimal("1.0"));
request.getCards().setResult((ArrayList<Card>) cards);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
AssignService:
#Transactional
public void assignCards(DeferredResult<List<Card>> cards, Game game, Room room, Terminal terminal)
{
game = em.merge(game);
room = em.merge(room);
terminal = em.merge(terminal);
Order order = new Order();
LocalDateTime datetime = LocalDateTime.now();
BigDecimal total = new BigDecimal("0.0");
order.setTime(datetime)
order.setRoom(room);
order.setGame(game);
order.setId_terminal(terminal);
for(Card card: (List<Card>)cards.getResult()) {
card= em.merge(card)
--> System.out.println("CARD STATUS" + card.getStatus());
// This shows the OLD value of the Card (not updated)
card.setOrder(order);
order.getOrder().add(card);
}
game.setOrder(order);
//gameRepository.save(game)
}
With this code, it does not save new Card status on DB but Game, Terminal and Room saves ok on DB (more or less...). If I remove the assignService, CardService saves the new status on DB correctly.
I have tried to flush manually, save with repo and so on... but the result is almost the same. Could anybody help me?
I think I found a solution (probably not the optimum), but it's more related to the logic of my program.
One of the main problems was the update of Card status property, because it was not reflected on the entity object. When the assignOrder method is called it received the old Card value because it's not possible to share information within Threads/Transactions (as far I know). This is normal within transactions because em.executeUpdate() only commits database, so if I want to get the updated entity I need to refresh it with em.refresh(Entity), but this caused performance to go down.
At the end I changed the logic: first create Orders (transactional) and then assign cards to the orders (transactional). This way works correctly.

Launch a function automatically with spring boot after a regular period of time

I have an API in my spring boot app that sends an email contains a report
i can generate i when click the send button
I want now to automtize this process therefore i want this email to be sent every week without any human interference and i am blocked . How is that possible
thank you for you any help .
#RequestMapping(path = "/email/trigger", method = RequestMethod.POST)
public String triggerEmail( #RequestBody Map<String,String> msg) {
SimpleMailMessage message = new SimpleMailMessage();
String d = msg.get("data");
String dd = msg.get("mail");
String ddd = collaborateurDao.find(dd);
message.setSubject("Test");
message.setText(d);
message.setTo("x#s.com");
message.setFrom("app#sa.com");
try {
mailSender.send(message);
return "{\"message\": \"OK\"}";
} catch (Exception e) {
e.printStackTrace();
return "{\"message\": \"Error\"}";
}
}
Use Spring Scheduler. Examples
#Scheduled(fixedDelay =30000)
public void triggerEmail() {... }
// Like Unix cron
#Scheduled(cron="0 0 * * * *")
public void triggerEmail() {... }
You can refer to the document for more information
https://spring.io/guides/gs/scheduling-tasks/
You can also use OS's dependent schedular like cron jobs in Linux.

Stomp Client not always fetching data even if connection is established

I am sending data from spring boot to client using stomp client and web socket. It is able to send data to the first user but as soon as user increases it is fetching data for only some users. This seems weird because its behavior should be same for all the users. I have found out after extensive researching that the reason for this is because i am connecting to a queue ('/user/queue') and have more than one client listening to it. How to avoid this problem or is it impossible to solve this issue.
My controller code-
#Controller
public class ScheduledUpdatesOnTopic {
#Autowired
public SimpMessageSendingOperations messagingTemplate;
#Autowired
private SimpMessagingTemplate template;
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
String json[][] = {{"Lokesh Gupta","34","India",df.format(date)},{"Meenal","23","Pakistan",df.format(date)},{"Gongo","12","Indonesia",df.format(date)},{"Abraham","17","US",df.format(date)},{"Saddam","56","Iraq",df.format(date)},{"Yimkov","67","Japan",df.format(date)},{"Salma","22","Pakistan",df.format(date)},{"Georgia","28","Russia",df.format(date)},{"Jaquline","31","Sri Lanka",df.format(date)},{"Chenchui","78","China",df.format(date)}};
String t[] = {"Lokesh Gupta","34","India","11/8/2017"};
String temp[][];
int p=0;
int count=0;
private MessageHeaderInitializer headerInitializer;
#MessageMapping("/hello")
public void start(SimpMessageHeaderAccessor accessor) throws Exception
{
String applicantId=accessor.getSessionId();
System.out.println("session id " + applicantId);
this.messagingTemplate.convertAndSendToUser(applicantId,"/queue/cache",json,createHeaders(applicantId));
}
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
if (getHeaderInitializer() != null) {
getHeaderInitializer().initHeaders(headerAccessor);
}
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
public MessageHeaderInitializer getHeaderInitializer() {
return this.headerInitializer;
}
public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) {
this.headerInitializer = headerInitializer;
}
And client side html is-
var socket = new SockJS('/gs-guide-websocket');
var stompClient = Stomp.over(socket);
stompClient.connect({ }, function(frame) {
console.log('Connected this ' + frame);
stompClient.subscribe("/user/queue/cache", function(data) {
// code to display this data..........
});
I have to use queue because that is the only way to send data to particular session ids. Any help will be appreciated !!
It sounds like you need to use the "Request-Reply" messaging pattern.
When the client connects to the server on the common queue, it includes a private return address. This return address can be used to generate a new private message queue name for use by the server and client exclusively (since they are the only 2 that know the private return address. The server can then send the client data over the private message queue.
The return address could be a random UUID for example, and the private queue name could be /queue/private. .
This "Request-Reply" messaging pattern is more formally explained here, among other useful messaging patterns:
http://www.enterpriseintegrationpatterns.com/patterns/messaging/ReturnAddress.html

Resources