From 0005fa7ceb5e8ffed2edf2bb6f64304a33c88fb7 Mon Sep 17 00:00:00 2001 From: Tester Date: Wed, 2 Aug 2023 19:19:31 +0200 Subject: [PATCH 01/52] StringUtils: add method to check mail address --- .../java/de/rwth/idsg/steve/utils/StringUtils.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java b/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java index 7e5cc2413..979ad0884 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java +++ b/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java @@ -32,6 +32,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.regex.Pattern; /** * @author Sevket Goekay @@ -95,4 +96,14 @@ public static String getLastBitFromUrl(final String input) { return input.substring(index + substring.length()); } } + + // https://www.baeldung.com/java-email-validation-regex + public static boolean isValidAddress(String emailAddress) { + String regexPattern = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$"; // Strict Regular Expression Validation + //String regexPattern = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9-]+(\\.[\\p{L}0-9-]+)*(\\.[\\p{L}]{2,})$"; //Regular Expression for Validation of Non-Latin or Unicode Characters Email + //String regexPattern = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$" // Regular Expression by RFC 5322 for Email Validation + return Pattern.compile(regexPattern) + .matcher(emailAddress) + .matches(); + } } From 4cbea6f40ec4349818f6d245e8904be54c558e0c Mon Sep 17 00:00:00 2001 From: Tester Date: Wed, 2 Aug 2023 19:21:46 +0200 Subject: [PATCH 02/52] UserRepository: add method "User.Details getDetails(String ocppTag)" --- .../idsg/steve/repository/UserRepository.java | 1 + .../repository/impl/UserRepositoryImpl.java | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java b/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java index 840185ecd..63619c5d5 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java @@ -31,6 +31,7 @@ public interface UserRepository { List getOverview(UserQueryForm form); User.Details getDetails(int userPk); + User.Details getDetails(String ocppTag); void add(UserForm form); void update(UserForm form); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index b35f69189..cafe551fa 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -116,6 +116,43 @@ public User.Details getDetails(int userPk) { .build(); } + @Override + public User.Details getDetails(String ocppIdTag) { + + Integer ocppPk = ctx.select(OCPP_TAG.OCPP_TAG_PK) + .from(OCPP_TAG) + .where(OCPP_TAG.ID_TAG.eq(ocppIdTag)) + .fetchOne(OCPP_TAG.OCPP_TAG_PK); + + if (ocppPk == null) { + throw new SteveException("There is no OCPP_Tag: '%s'", ocppIdTag); + } + + // ------------------------------------------------------------------------- + // 1. user table + // ------------------------------------------------------------------------- + + UserRecord ur = ctx.selectFrom(USER) + .where(USER.OCPP_TAG_PK.equal(ocppPk)) + .fetchOne(); + + if (ur == null) { + throw new SteveException("There is no user with OCPP_TAG '%s'", ocppIdTag); + } + + // ------------------------------------------------------------------------- + // 2. address table + // ------------------------------------------------------------------------- + + AddressRecord ar = addressRepository.get(ctx, ur.getAddressPk()); + + return User.Details.builder() + .userRecord(ur) + .address(ar) + .ocppIdTag(Optional.ofNullable(ocppIdTag)) + .build(); + } + @Override public void add(UserForm form) { ctx.transaction(configuration -> { From 75fd4faf8ba2d1ed0c66551e8f9f1d3c640c5aba Mon Sep 17 00:00:00 2001 From: Tester Date: Wed, 2 Aug 2023 19:28:37 +0200 Subject: [PATCH 03/52] TransactionRepository: add method "Transaction getTransaction(int transaction_pk);" --- .../idsg/steve/repository/TransactionRepository.java | 2 ++ .../repository/impl/TransactionRepositoryImpl.java | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index dec544eee..2efedb959 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -30,6 +30,8 @@ * @since 19.08.2014 */ public interface TransactionRepository { + Transaction getTransaction(int transaction_pk); + List getTransactions(TransactionQueryForm form); void writeTransactionsCSV(TransactionQueryForm form, Writer writer); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 52847e188..77a083133 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -65,6 +65,16 @@ public TransactionRepositoryImpl(DSLContext ctx) { this.ctx = ctx; } + @Override + public Transaction getTransaction(int transaction_pk) { + TransactionQueryForm form = new TransactionQueryForm(); + form.setTransactionPk(transaction_pk); + form.setReturnCSV(false); + form.setType(TransactionQueryForm.QueryType.ALL); + return getInternal(form).fetch() + .map(new TransactionMapper()).get(0); + } + @Override public List getTransactions(TransactionQueryForm form) { return getInternal(form).fetch() From e254786da33346626617902c539f6404965e428a Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Wed, 2 Aug 2023 19:45:48 +0200 Subject: [PATCH 04/52] MailService: change method "send(String subject, String body)" to "send(String subject, String body, String RecipientAddresses)" use addresses from setting if RecipientAddresses is empty; adding method "sendAsync(String subject, String body, String RecipientAddresses)" --- .../rwth/idsg/steve/service/MailService.java | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/MailService.java b/src/main/java/de/rwth/idsg/steve/service/MailService.java index ee338ad0f..6cc9876ce 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailService.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailService.java @@ -40,6 +40,9 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; +import static de.rwth.idsg.steve.utils.StringUtils.isValidAddress; +import java.util.List; /** * @author Sevket Goekay @@ -81,7 +84,7 @@ public MailSettings getSettings() { public void sendTestMail() { try { - send("Test", "Test"); + send("Test", "Test",""); } catch (MessagingException e) { throw new SteveException("Failed to send mail", e); } @@ -90,29 +93,60 @@ public void sendTestMail() { public void sendAsync(String subject, String body) { executorService.execute(() -> { try { - send(subject, body); + send(subject, body, ""); } catch (MessagingException e) { log.error("Failed to send mail", e); } }); } - public void send(String subject, String body) throws MessagingException { - MailSettings settings = getSettings(); + public void sendAsync(String subject, String body, String RecipientAddresses) { + executorService.execute(() -> { + try { + send(subject, body, RecipientAddresses); + } catch (MessagingException e) { + log.error("Failed to send mail", e); + } + }); + } + + private void send(String subject, String body, String RecipientAddresses) throws MessagingException { + MailSettings settingsLocal = getSettings(); - Message mail = new MimeMessage(session); - mail.setSubject("[SteVe] " + subject); - mail.setContent(body, "text/plain"); - mail.setFrom(new InternetAddress(settings.getFrom())); + Message mail = new MimeMessage(session); + mail.setSubject("[SteVe] " + subject); + mail.setContent(body, "text/plain"); + mail.setFrom(new InternetAddress(settingsLocal.getFrom())); - for (String rep : settings.getRecipients()) { + List eMailAddresses; + + if (RecipientAddresses.isEmpty()) + { + eMailAddresses = settingsLocal.getRecipients(); + } + else + { + + eMailAddresses = splitByComma(RecipientAddresses); + } + + for (String rep : eMailAddresses) { + if (isValidAddress(rep)){ mail.addRecipient(Message.RecipientType.TO, new InternetAddress(rep)); } + else{ + log.error("Failed to send mail to " + rep + "! Format of the address is invalid."); + } + } try (Transport transport = session.getTransport()) { transport.connect(); transport.sendMessage(mail, mail.getAllRecipients()); } + catch(Exception e) + { + log.error("Failed to send mail(s)! ", e); + } } // ------------------------------------------------------------------------- From 4eadaa8e8979b12cb2b88884245645895948df18 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Wed, 2 Aug 2023 20:12:34 +0200 Subject: [PATCH 05/52] NotificationService: in method "ocppTransactionEnded(OcppTransactionEnded notification)" adding send mail to user (if email address is in the database), adding createContent method for user mail --- .../steve/service/NotificationService.java | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 269b42b2a..6368b3808 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -41,7 +41,11 @@ import static de.rwth.idsg.steve.NotificationFeature.OcppStationWebSocketDisconnected; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionStarted; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionEnded; +import de.rwth.idsg.steve.repository.TransactionRepository; +import de.rwth.idsg.steve.repository.UserRepository; +import de.rwth.idsg.steve.repository.dto.Transaction; import static java.lang.String.format; +import jooq.steve.db.tables.records.UserRecord; /** * @author Sevket Goekay @@ -52,6 +56,8 @@ public class NotificationService { @Autowired private MailService mailService; + @Autowired private TransactionRepository transactionRepository; + @Autowired private UserRepository userRepository; @EventListener public void ocppStationBooted(OccpStationBooted notification) { @@ -117,7 +123,24 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { @EventListener public void ocppTransactionEnded(OcppTransactionEnded notification) { - if (isDisabled(OcppTransactionEnded)) { + Transaction TransActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); + + TransActParams.getOcppTagPk(); + UserRecord userRecord = userRepository.getDetails(TransActParams.getOcppIdTag()).getUserRecord(); + String eMailAddress = userRecord.getEMail(); + + // mail to user + if (!Strings.isNullOrEmpty(eMailAddress)) { + String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", TransActParams.getId(), TransActParams.getChargeBoxId()); + + // if the Transactionstop is received within the first 1 Minute don't send an E-Mail + if (TransActParams.getStopTimestamp().isAfter(TransActParams.getStartTimestamp().plusMinutes(1))){ + mailService.sendAsync(subjectUserMail, addTimestamp(createContent(TransActParams, userRecord)), eMailAddress); + } + } + + /* mail defined in settings */ + if (isDisabled(OcppTransactionEnded)) { return; } @@ -156,6 +179,39 @@ private static String createContent(UpdateTransactionParams params) { .toString(); } +private static String createContent(Transaction params, UserRecord userRecord) { + Double meterValueDiff; + Integer meterValueStop; + Integer meterValueStart; + String str_meterValueDiff = "-"; + try + { + meterValueStop = Integer.valueOf(params.getStopValue()); + meterValueStart = Integer.valueOf(params.getStartValue()); + meterValueDiff = (meterValueStop - meterValueStart)/1000.0; // --> kWh + str_meterValueDiff = meterValueDiff.toString() + " kWh"; + } + catch(NumberFormatException e) + { + log.error("Failed to calculate charged energy! ", e); + } + + return new StringBuilder("User: ") + .append(userRecord.getFirstName()).append(" ").append(userRecord.getLastName()) + .append(System.lineSeparator()) + .append(System.lineSeparator()) + .append("Details:").append(System.lineSeparator()) + .append("- chargeBoxId: ").append(params.getChargeBoxId()).append(System.lineSeparator()) + .append("- connectorId: ").append(params.getConnectorId()).append(System.lineSeparator()) + .append("- transactionId: ").append(params.getId()).append(System.lineSeparator()) // getTransactionId() + .append("- startTimestamp (UTC): ").append(params.getStartTimestamp()).append(System.lineSeparator()) + .append("- startMeterValue: ").append(params.getStartValue()).append(System.lineSeparator()) + .append("- stopTimestamp (UTC): ").append(params.getStopTimestamp()).append(System.lineSeparator()) + .append("- stopMeterValue: ").append(params.getStopValue()).append(System.lineSeparator()) + .append("- stopReason: ").append(params.getStopReason()).append(System.lineSeparator()) + .append("- charged energy: ").append(str_meterValueDiff).append(System.lineSeparator()) + .toString(); + } private boolean isDisabled(NotificationFeature f) { MailSettings settings = mailService.getSettings(); From 1b366b4e0e30ac13b03a04b263df63e3bef631c2 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Wed, 2 Aug 2023 21:14:33 +0200 Subject: [PATCH 06/52] OcppStationStatusSuspendedEV.java added --- .../OcppStationStatusSuspendedEV.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java diff --git a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java new file mode 100644 index 000000000..ce6906feb --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java @@ -0,0 +1,33 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2023 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.service.notification; + +import lombok.Data; + +/** + * @author Frank Brosi + * @since 12.10.2022 + * + */ +@Data +public class OcppStationStatusSuspendedEV { + + private final String chargeBoxId; + private final int connectorId; +} From e38a1084512765dcd698ddb00869bf1c3480407c Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Wed, 2 Aug 2023 21:16:12 +0200 Subject: [PATCH 07/52] NotificationFeature.java: added NotificationFeature "OcppStationStatusSuspendedEV" --- src/main/java/de/rwth/idsg/steve/NotificationFeature.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/rwth/idsg/steve/NotificationFeature.java b/src/main/java/de/rwth/idsg/steve/NotificationFeature.java index a5fd343d0..ba43fc6f3 100644 --- a/src/main/java/de/rwth/idsg/steve/NotificationFeature.java +++ b/src/main/java/de/rwth/idsg/steve/NotificationFeature.java @@ -35,6 +35,7 @@ public enum NotificationFeature { OcppStationWebSocketConnected(" a JSON charging station connects"), OcppStationWebSocketDisconnected(" a JSON charging station disconnects"), OcppTransactionStarted(" a charging station starts a transaction"), + OcppStationStatusSuspendedEV(" a EV suspended charging"), OcppTransactionEnded(" a charging station ends a transaction"); From f51a3daea538dc70bd72f425414b61c203c94318 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Wed, 2 Aug 2023 21:20:46 +0200 Subject: [PATCH 08/52] CentralSystemService16_Service.java: on a statusNotification, added publishing a event if the status is "SuspendedEV" --- .../idsg/steve/service/CentralSystemService16_Service.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java b/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java index a55e9ca67..09916db6e 100644 --- a/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java +++ b/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java @@ -27,6 +27,7 @@ import de.rwth.idsg.steve.repository.dto.UpdateTransactionParams; import de.rwth.idsg.steve.service.notification.OccpStationBooted; import de.rwth.idsg.steve.service.notification.OcppStationStatusFailure; +import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import de.rwth.idsg.steve.service.notification.OcppTransactionEnded; import de.rwth.idsg.steve.service.notification.OcppTransactionStarted; import jooq.steve.db.enums.TransactionStopEventActor; @@ -147,6 +148,11 @@ public StatusNotificationResponse statusNotification( chargeBoxIdentity, parameters.getConnectorId(), parameters.getErrorCode().value())); } + if (parameters.getStatus() == ChargePointStatus.SUSPENDED_EV) { + applicationEventPublisher.publishEvent(new OcppStationStatusSuspendedEV( + chargeBoxIdentity, parameters.getConnectorId())); //, connPk)); + } + return new StatusNotificationResponse(); } From df0b5c4fc53dc2d998f6586cb917f2e11be4ce9a Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Wed, 2 Aug 2023 21:22:28 +0200 Subject: [PATCH 09/52] OcppServerRepository: add method getConnectorPK --- .../rwth/idsg/steve/repository/OcppServerRepository.java | 1 + .../steve/repository/impl/OcppServerRepositoryImpl.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java b/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java index 4d9d86f46..968bd5e9d 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java @@ -42,6 +42,7 @@ public interface OcppServerRepository { void updateChargeboxHeartbeat(String chargeBoxIdentity, DateTime ts); void insertConnectorStatus(InsertConnectorStatusParams params); + public Integer getConnectorPk(String chargeBoxId, int connectorId); void insertMeterValues(String chargeBoxIdentity, List list, int connectorId, Integer transactionId); void insertMeterValues(String chargeBoxIdentity, List list, int transactionId); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java index 8ec2542c8..651af9534 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java @@ -165,6 +165,15 @@ public void insertConnectorStatus(InsertConnectorStatusParams p) { }); } + @Override + public Integer getConnectorPk(String chargeBoxId, int connectorId){ + return ctx.select(CONNECTOR.CONNECTOR_PK) + .from(CONNECTOR) + .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) + .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) + .fetchOne().value1(); + } + @Override public void insertMeterValues(String chargeBoxIdentity, List list, int connectorId, Integer transactionId) { if (CollectionUtils.isEmpty(list)) { From 7f73ecdb4056a859e2418a0295e313d2bbb48237 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Wed, 2 Aug 2023 21:25:43 +0200 Subject: [PATCH 10/52] TransactionRepository: add method getOcppTagOfActiveTransaction(integer connectorPK) --- .../idsg/steve/repository/TransactionRepository.java | 2 ++ .../steve/repository/impl/TransactionRepositoryImpl.java | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index 2efedb959..70e325968 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -38,6 +38,8 @@ public interface TransactionRepository { List getActiveTransactionIds(String chargeBoxId); + String getOcppTagOfActiveTransaction(Integer connectorPk); + TransactionDetails getDetails(int transactionPk, boolean firstArrivingMeterValueIfMultiple); default TransactionDetails getDetails(int transactionPk) { diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 77a083133..a2402168b 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -98,6 +98,15 @@ public List getActiveTransactionIds(String chargeBoxId) { .fetch(TRANSACTION.TRANSACTION_PK); } + @Override + public String getOcppTagOfActiveTransaction(Integer connectorPk) { + return ctx.select(TRANSACTION.ID_TAG) + .from(TRANSACTION) + .where(TRANSACTION.CONNECTOR_PK.eq(connectorPk)) + .and(TRANSACTION.STOP_VALUE.isNull()) + .fetchAny(TRANSACTION.ID_TAG); + } + @Override public TransactionDetails getDetails(int transactionPk, boolean firstArrivingMeterValueIfMultiple) { From 4ca23a6e2814b4131da2280cb0ab640ac2fe2b2b Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Wed, 2 Aug 2023 21:29:36 +0200 Subject: [PATCH 11/52] NotificationService: added Event ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) to send a mail to the user --- .../steve/service/NotificationService.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 6368b3808..617166f8c 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -41,11 +41,14 @@ import static de.rwth.idsg.steve.NotificationFeature.OcppStationWebSocketDisconnected; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionStarted; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionEnded; +import de.rwth.idsg.steve.repository.OcppServerRepository; import de.rwth.idsg.steve.repository.TransactionRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; +import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import static java.lang.String.format; import jooq.steve.db.tables.records.UserRecord; +import org.springframework.scheduling.annotation.Async; /** * @author Sevket Goekay @@ -58,6 +61,7 @@ public class NotificationService { @Autowired private MailService mailService; @Autowired private TransactionRepository transactionRepository; @Autowired private UserRepository userRepository; + @Autowired private OcppServerRepository ocppServerRepository; @EventListener public void ocppStationBooted(OccpStationBooted notification) { @@ -122,6 +126,28 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { } @EventListener + @Async + public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification){ + // Connector_pk + Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), notification.getConnectorId()); + String ocppTag = transactionRepository.getOcppTagOfActiveTransaction(connectorPk); + if (ocppTag == null){return;} + UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + String eMailAddy = userRecord.getEMail(); //userRepository.getMailAddy(OCPP_TAG); + if (Strings.isNullOrEmpty(eMailAddy)) + {return;} + + String subject = format("EV stopped charging at charging station %s", notification.getChargeBoxId()); + + //String body = format("Connector '%s' of charging station '%s' notifies Suspended_EV", connectorId, chargeBoxId); + String body = "User: " + userRecord.getFirstName() + " " + userRecord.getLastName() + System.lineSeparator() + System.lineSeparator() + + "Connector " + notification.getConnectorId() + " of charging station " + notification.getChargeBoxId() + " notifies Suspended_EV"; + + mailService.sendAsync( subject, addTimestamp(body), eMailAddy); + } + + @EventListener + @Async public void ocppTransactionEnded(OcppTransactionEnded notification) { Transaction TransActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); From 64707eeed3eafdd0a7cb54086c857f7610b9a78f Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Fri, 4 Aug 2023 16:20:22 +0200 Subject: [PATCH 12/52] NotificationService & CentralSystemService16_Service: Format and Comment improvements --- .../CentralSystemService16_Service.java | 2 +- .../steve/service/NotificationService.java | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java b/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java index 09916db6e..6b60818a8 100644 --- a/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java +++ b/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java @@ -150,7 +150,7 @@ public StatusNotificationResponse statusNotification( if (parameters.getStatus() == ChargePointStatus.SUSPENDED_EV) { applicationEventPublisher.publishEvent(new OcppStationStatusSuspendedEV( - chargeBoxIdentity, parameters.getConnectorId())); //, connPk)); + chargeBoxIdentity, parameters.getConnectorId())); } return new StatusNotificationResponse(); diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 617166f8c..f802d6109 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -128,18 +128,20 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { @EventListener @Async public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification){ - // Connector_pk Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), notification.getConnectorId()); String ocppTag = transactionRepository.getOcppTagOfActiveTransaction(connectorPk); - if (ocppTag == null){return;} + + if (ocppTag == null){ + return; + } + UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - String eMailAddy = userRecord.getEMail(); //userRepository.getMailAddy(OCPP_TAG); - if (Strings.isNullOrEmpty(eMailAddy)) - {return;} + String eMailAddy = userRecord.getEMail(); + if (Strings.isNullOrEmpty(eMailAddy)){ + return; + } String subject = format("EV stopped charging at charging station %s", notification.getChargeBoxId()); - - //String body = format("Connector '%s' of charging station '%s' notifies Suspended_EV", connectorId, chargeBoxId); String body = "User: " + userRecord.getFirstName() + " " + userRecord.getLastName() + System.lineSeparator() + System.lineSeparator() + "Connector " + notification.getConnectorId() + " of charging station " + notification.getChargeBoxId() + " notifies Suspended_EV"; @@ -159,7 +161,7 @@ public void ocppTransactionEnded(OcppTransactionEnded notification) { if (!Strings.isNullOrEmpty(eMailAddress)) { String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", TransActParams.getId(), TransActParams.getChargeBoxId()); - // if the Transactionstop is received within the first 1 Minute don't send an E-Mail + // if the Transactionstop is received within the first Minute don't send an E-Mail if (TransActParams.getStopTimestamp().isAfter(TransActParams.getStartTimestamp().plusMinutes(1))){ mailService.sendAsync(subjectUserMail, addTimestamp(createContent(TransActParams, userRecord)), eMailAddress); } @@ -179,7 +181,6 @@ public void ocppTransactionEnded(OcppTransactionEnded notification) { // Private helpers // ------------------------------------------------------------------------- - private static String createContent(InsertTransactionParams params) { StringBuilder sb = new StringBuilder("Details:").append(System.lineSeparator()) .append("- chargeBoxId: ").append(params.getChargeBoxId()).append(System.lineSeparator()) @@ -229,7 +230,7 @@ private static String createContent(Transaction params, UserRecord userRecord) { .append("Details:").append(System.lineSeparator()) .append("- chargeBoxId: ").append(params.getChargeBoxId()).append(System.lineSeparator()) .append("- connectorId: ").append(params.getConnectorId()).append(System.lineSeparator()) - .append("- transactionId: ").append(params.getId()).append(System.lineSeparator()) // getTransactionId() + .append("- transactionId: ").append(params.getId()).append(System.lineSeparator()) .append("- startTimestamp (UTC): ").append(params.getStartTimestamp()).append(System.lineSeparator()) .append("- startMeterValue: ").append(params.getStartValue()).append(System.lineSeparator()) .append("- stopTimestamp (UTC): ").append(params.getStopTimestamp()).append(System.lineSeparator()) From 00853ffdd7e8903a37eb7c7fcffe187ad1992a75 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Sat, 5 Aug 2023 14:25:31 +0200 Subject: [PATCH 13/52] NotificationService: added mail to "admin" in OcppStationStatusSuspendedEV --- .../de/rwth/idsg/steve/service/NotificationService.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index f802d6109..6b289cc2a 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -28,6 +28,7 @@ import de.rwth.idsg.steve.service.notification.OcppStationWebSocketConnected; import de.rwth.idsg.steve.service.notification.OcppStationWebSocketDisconnected; import de.rwth.idsg.steve.service.notification.OcppTransactionEnded; +import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import de.rwth.idsg.steve.service.notification.OcppTransactionStarted; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; @@ -40,12 +41,12 @@ import static de.rwth.idsg.steve.NotificationFeature.OcppStationWebSocketConnected; import static de.rwth.idsg.steve.NotificationFeature.OcppStationWebSocketDisconnected; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionStarted; +import static de.rwth.idsg.steve.NotificationFeature.OcppStationStatusSuspendedEV; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionEnded; import de.rwth.idsg.steve.repository.OcppServerRepository; import de.rwth.idsg.steve.repository.TransactionRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; -import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import static java.lang.String.format; import jooq.steve.db.tables.records.UserRecord; import org.springframework.scheduling.annotation.Async; @@ -146,6 +147,12 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati + "Connector " + notification.getConnectorId() + " of charging station " + notification.getChargeBoxId() + " notifies Suspended_EV"; mailService.sendAsync( subject, addTimestamp(body), eMailAddy); + + /* mail defined in settings */ + if (isDisabled(OcppStationStatusSuspendedEV)) { + return; + } + mailService.sendAsync( subject, addTimestamp(body), ""); } @EventListener From bf113abd95939915909eadbe2716e3dd3d8fa74a Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Sat, 5 Aug 2023 15:11:58 +0200 Subject: [PATCH 14/52] NotificationService: formated mail text in OcppStationStatusSuspendedEV, rearange the if clauses --- .../steve/service/NotificationService.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 6b289cc2a..118e05923 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -132,26 +132,34 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), notification.getConnectorId()); String ocppTag = transactionRepository.getOcppTagOfActiveTransaction(connectorPk); - if (ocppTag == null){ - return; - } - - UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - String eMailAddy = userRecord.getEMail(); - if (Strings.isNullOrEmpty(eMailAddy)){ - return; + String subject = format("EV stopped charging at charging station %s, Connector %d", + notification.getChargeBoxId(), + notification.getConnectorId()); + if (ocppTag != null){ + + UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + String eMailAddy = userRecord.getEMail(); + if (!Strings.isNullOrEmpty(eMailAddy)){ + //String bodyUserMail = "User: " + userRecord.getFirstName() + " " + userRecord.getLastName() + System.lineSeparator() + System.lineSeparator() + // + "Connector " + notification.getConnectorId() + " of charging station " + notification.getChargeBoxId() + " notifies Suspended_EV"; + + String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId()); + + mailService.sendAsync( subject, addTimestamp(bodyUserMail), eMailAddy); + } } - String subject = format("EV stopped charging at charging station %s", notification.getChargeBoxId()); - String body = "User: " + userRecord.getFirstName() + " " + userRecord.getLastName() + System.lineSeparator() + System.lineSeparator() - + "Connector " + notification.getConnectorId() + " of charging station " + notification.getChargeBoxId() + " notifies Suspended_EV"; - - mailService.sendAsync( subject, addTimestamp(body), eMailAddy); - /* mail defined in settings */ if (isDisabled(OcppStationStatusSuspendedEV)) { return; } + String body = format("Connector %d of charging station %s notifies Suspended_EV", + notification.getConnectorId(), + notification.getChargeBoxId()); mailService.sendAsync( subject, addTimestamp(body), ""); } From 43922964455b49de38e6cef32b508c1d26b7885d Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Thu, 12 Oct 2023 12:28:44 +0200 Subject: [PATCH 15/52] NotificationService.java & OcppStationStatusSuspend: commeting and format changes --- .../steve/service/NotificationService.java | 34 +++++++++++++------ .../OcppStationStatusSuspendedEV.java | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 118e05923..ba646608e 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -109,7 +109,10 @@ public void ocppStationStatusFailure(OcppStationStatusFailure notification) { return; } - String subject = format("Connector '%s' of charging station '%s' is FAULTED", notification.getConnectorId(), notification.getChargeBoxId()); + String subject = format("Connector '%s' of charging station '%s' is FAULTED", + notification.getConnectorId(), + notification.getChargeBoxId() + ); String body = format("Status Error Code: '%s'", notification.getErrorCode()); mailService.sendAsync(subject, addTimestamp(body)); @@ -121,7 +124,11 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { return; } - String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", notification.getTransactionId(), notification.getParams().getChargeBoxId(), notification.getParams().getConnectorId()); + String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", + notification.getTransactionId(), + notification.getParams().getChargeBoxId(), + notification.getParams().getConnectorId() + ); mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } @@ -139,15 +146,14 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); String eMailAddy = userRecord.getEMail(); + // send email if user with eMail address found if (!Strings.isNullOrEmpty(eMailAddy)){ - //String bodyUserMail = "User: " + userRecord.getFirstName() + " " + userRecord.getLastName() + System.lineSeparator() + System.lineSeparator() - // + "Connector " + notification.getConnectorId() + " of charging station " + notification.getChargeBoxId() + " notifies Suspended_EV"; - String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId()); + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId() + ); mailService.sendAsync( subject, addTimestamp(bodyUserMail), eMailAddy); } @@ -174,7 +180,10 @@ public void ocppTransactionEnded(OcppTransactionEnded notification) { // mail to user if (!Strings.isNullOrEmpty(eMailAddress)) { - String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", TransActParams.getId(), TransActParams.getChargeBoxId()); + String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", + TransActParams.getId(), + TransActParams.getChargeBoxId() + ); // if the Transactionstop is received within the first Minute don't send an E-Mail if (TransActParams.getStopTimestamp().isAfter(TransActParams.getStartTimestamp().plusMinutes(1))){ @@ -187,7 +196,10 @@ public void ocppTransactionEnded(OcppTransactionEnded notification) { return; } - String subject = format("Transaction '%s' has ended on charging station '%s'", notification.getParams().getTransactionId(), notification.getParams().getChargeBoxId()); + String subject = format("Transaction '%s' has ended on charging station '%s'", + notification.getParams().getTransactionId(), + notification.getParams().getChargeBoxId() + ); mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } diff --git a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java index ce6906feb..8a984a004 100644 --- a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java +++ b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java @@ -21,7 +21,7 @@ import lombok.Data; /** - * @author Frank Brosi + * @author fnkbsi * @since 12.10.2022 * */ From 8e44e3891f12ae3ce7c5941aab3ec00e3e4c8850 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Thu, 12 Oct 2023 13:54:17 +0200 Subject: [PATCH 16/52] style correction --- .../repository/OcppServerRepository.java | 2 +- .../repository/TransactionRepository.java | 2 +- .../impl/OcppServerRepositoryImpl.java | 4 +- .../impl/TransactionRepositoryImpl.java | 4 +- .../repository/impl/UserRepositoryImpl.java | 3 +- .../steve/service/NotificationService.java | 76 ++++++++++--------- .../OcppStationStatusSuspendedEV.java | 2 +- .../de/rwth/idsg/steve/utils/StringUtils.java | 11 ++- 8 files changed, 57 insertions(+), 47 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java b/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java index 968bd5e9d..e0f2e451c 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java @@ -42,7 +42,7 @@ public interface OcppServerRepository { void updateChargeboxHeartbeat(String chargeBoxIdentity, DateTime ts); void insertConnectorStatus(InsertConnectorStatusParams params); - public Integer getConnectorPk(String chargeBoxId, int connectorId); + Integer getConnectorPk(String chargeBoxId, int connectorId); void insertMeterValues(String chargeBoxIdentity, List list, int connectorId, Integer transactionId); void insertMeterValues(String chargeBoxIdentity, List list, int transactionId); diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index 70e325968..9375e46b5 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -30,7 +30,7 @@ * @since 19.08.2014 */ public interface TransactionRepository { - Transaction getTransaction(int transaction_pk); + Transaction getTransaction(int transactionPk); List getTransactions(TransactionQueryForm form); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java index 651af9534..3042475e9 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java @@ -166,12 +166,12 @@ public void insertConnectorStatus(InsertConnectorStatusParams p) { } @Override - public Integer getConnectorPk(String chargeBoxId, int connectorId){ + public Integer getConnectorPk(String chargeBoxId, int connectorId) { return ctx.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) - .fetchOne().value1(); + .fetchOne().value1(); } @Override diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index a2402168b..badf6d71d 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -66,9 +66,9 @@ public TransactionRepositoryImpl(DSLContext ctx) { } @Override - public Transaction getTransaction(int transaction_pk) { + public Transaction getTransaction(int transactionPk) { TransactionQueryForm form = new TransactionQueryForm(); - form.setTransactionPk(transaction_pk); + form.setTransactionPk(transactionPk); form.setReturnCSV(false); form.setType(TransactionQueryForm.QueryType.ALL); return getInternal(form).fetch() diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index cafe551fa..dd255b707 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -118,12 +118,11 @@ public User.Details getDetails(int userPk) { @Override public User.Details getDetails(String ocppIdTag) { - Integer ocppPk = ctx.select(OCPP_TAG.OCPP_TAG_PK) .from(OCPP_TAG) .where(OCPP_TAG.ID_TAG.eq(ocppIdTag)) .fetchOne(OCPP_TAG.OCPP_TAG_PK); - + if (ocppPk == null) { throw new SteveException("There is no OCPP_Tag: '%s'", ocppIdTag); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index ba646608e..0cd4fb4b5 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -109,7 +109,7 @@ public void ocppStationStatusFailure(OcppStationStatusFailure notification) { return; } - String subject = format("Connector '%s' of charging station '%s' is FAULTED", + String subject = format("Connector '%s' of charging station '%s' is FAULTED", notification.getConnectorId(), notification.getChargeBoxId() ); @@ -135,27 +135,31 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { @EventListener @Async - public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification){ - Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), notification.getConnectorId()); + public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) { + Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), + notification.getConnectorId() + ); String ocppTag = transactionRepository.getOcppTagOfActiveTransaction(connectorPk); - String subject = format("EV stopped charging at charging station %s, Connector %d", - notification.getChargeBoxId(), - notification.getConnectorId()); + String subject = format("EV stopped charging at charging station %s, Connector %d", + notification.getChargeBoxId(), + notification.getConnectorId() + ); if (ocppTag != null){ UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); String eMailAddy = userRecord.getEMail(); // send email if user with eMail address found - if (!Strings.isNullOrEmpty(eMailAddy)){ - String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId() - ); - - mailService.sendAsync( subject, addTimestamp(bodyUserMail), eMailAddy); + if (!Strings.isNullOrEmpty(eMailAddy)) { + String bodyUserMail = + format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId() + ); + + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddy); } } @@ -163,31 +167,35 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati if (isDisabled(OcppStationStatusSuspendedEV)) { return; } - String body = format("Connector %d of charging station %s notifies Suspended_EV", + String body = format("Connector %d of charging station %s notifies Suspended_EV", notification.getConnectorId(), - notification.getChargeBoxId()); - mailService.sendAsync( subject, addTimestamp(body), ""); + notification.getChargeBoxId() + ); + mailService.sendAsync(subject, addTimestamp(body), ""); } @EventListener @Async public void ocppTransactionEnded(OcppTransactionEnded notification) { - Transaction TransActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); + Transaction transActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); - TransActParams.getOcppTagPk(); - UserRecord userRecord = userRepository.getDetails(TransActParams.getOcppIdTag()).getUserRecord(); + transActParams.getOcppTagPk(); + UserRecord userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); String eMailAddress = userRecord.getEMail(); // mail to user if (!Strings.isNullOrEmpty(eMailAddress)) { - String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", - TransActParams.getId(), - TransActParams.getChargeBoxId() + String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", + transActParams.getId(), + transActParams.getChargeBoxId() ); // if the Transactionstop is received within the first Minute don't send an E-Mail - if (TransActParams.getStopTimestamp().isAfter(TransActParams.getStartTimestamp().plusMinutes(1))){ - mailService.sendAsync(subjectUserMail, addTimestamp(createContent(TransActParams, userRecord)), eMailAddress); + if (transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { + mailService.sendAsync(subjectUserMail, + addTimestamp(createContent(transActParams, userRecord)), + eMailAddress + ); } } @@ -233,20 +241,18 @@ private static String createContent(UpdateTransactionParams params) { .toString(); } -private static String createContent(Transaction params, UserRecord userRecord) { + private static String createContent(Transaction params, UserRecord userRecord) { Double meterValueDiff; Integer meterValueStop; Integer meterValueStart; - String str_meterValueDiff = "-"; - try - { + String strMeterValueDiff = "-"; + try { meterValueStop = Integer.valueOf(params.getStopValue()); meterValueStart = Integer.valueOf(params.getStartValue()); - meterValueDiff = (meterValueStop - meterValueStart)/1000.0; // --> kWh - str_meterValueDiff = meterValueDiff.toString() + " kWh"; + meterValueDiff = (meterValueStop - meterValueStart) / 1000.0; // --> kWh + strMeterValueDiff = meterValueDiff.toString() + " kWh"; } - catch(NumberFormatException e) - { + catch(NumberFormatException e) { log.error("Failed to calculate charged energy! ", e); } @@ -263,7 +269,7 @@ private static String createContent(Transaction params, UserRecord userRecord) { .append("- stopTimestamp (UTC): ").append(params.getStopTimestamp()).append(System.lineSeparator()) .append("- stopMeterValue: ").append(params.getStopValue()).append(System.lineSeparator()) .append("- stopReason: ").append(params.getStopReason()).append(System.lineSeparator()) - .append("- charged energy: ").append(str_meterValueDiff).append(System.lineSeparator()) + .append("- charged energy: ").append(strMeterValueDiff).append(System.lineSeparator()) .toString(); } diff --git a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java index 8a984a004..487380206 100644 --- a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java +++ b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java @@ -23,8 +23,8 @@ /** * @author fnkbsi * @since 12.10.2022 - * */ + @Data public class OcppStationStatusSuspendedEV { diff --git a/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java b/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java index 979ad0884..fe3d38496 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java +++ b/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java @@ -99,9 +99,14 @@ public static String getLastBitFromUrl(final String input) { // https://www.baeldung.com/java-email-validation-regex public static boolean isValidAddress(String emailAddress) { - String regexPattern = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$"; // Strict Regular Expression Validation - //String regexPattern = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9-]+(\\.[\\p{L}0-9-]+)*(\\.[\\p{L}]{2,})$"; //Regular Expression for Validation of Non-Latin or Unicode Characters Email - //String regexPattern = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$" // Regular Expression by RFC 5322 for Email Validation + // Strict Regular Expression Validation + String regexPattern = + "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$"; + //Regular Expression for Validation of Non-Latin or Unicode Characters Email + //String regexPattern = + // "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9-]+(\\.[\\p{L}0-9-]+)*(\\.[\\p{L}]{2,})$"; + // Regular Expression by RFC 5322 for Email Validation + //String regexPattern = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$" return Pattern.compile(regexPattern) .matcher(emailAddress) .matches(); From 472b75dea6ae921bd718157103e6db09e2208d3e Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Tue, 17 Oct 2023 13:58:04 +0200 Subject: [PATCH 17/52] style correction --- .../rwth/idsg/steve/service/MailService.java | 50 ++++++++----------- .../steve/service/NotificationService.java | 16 +++--- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/MailService.java b/src/main/java/de/rwth/idsg/steve/service/MailService.java index 6cc9876ce..864dc0f1a 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailService.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailService.java @@ -84,7 +84,7 @@ public MailSettings getSettings() { public void sendTestMail() { try { - send("Test", "Test",""); + send("Test", "Test", ""); } catch (MessagingException e) { throw new SteveException("Failed to send mail", e); } @@ -100,51 +100,45 @@ public void sendAsync(String subject, String body) { }); } - public void sendAsync(String subject, String body, String RecipientAddresses) { + public void sendAsync(String subject, String body, String recipientAddresses) { executorService.execute(() -> { try { - send(subject, body, RecipientAddresses); + send(subject, body, recipientAddresses); } catch (MessagingException e) { log.error("Failed to send mail", e); } }); } - private void send(String subject, String body, String RecipientAddresses) throws MessagingException { - MailSettings settingsLocal = getSettings(); + private void send(String subject, String body, String recipientAddresses) throws MessagingException { + MailSettings settingsLocal = getSettings(); - Message mail = new MimeMessage(session); - mail.setSubject("[SteVe] " + subject); - mail.setContent(body, "text/plain"); - mail.setFrom(new InternetAddress(settingsLocal.getFrom())); + Message mail = new MimeMessage(session); + mail.setSubject("[SteVe] " + subject); + mail.setContent(body, "text/plain"); + mail.setFrom(new InternetAddress(settingsLocal.getFrom())); - List eMailAddresses; + List eMailAddresses; - if (RecipientAddresses.isEmpty()) - { - eMailAddresses = settingsLocal.getRecipients(); - } - else - { - - eMailAddresses = splitByComma(RecipientAddresses); - } - - for (String rep : eMailAddresses) { - if (isValidAddress(rep)){ - mail.addRecipient(Message.RecipientType.TO, new InternetAddress(rep)); + if (recipientAddresses.isEmpty()) { + eMailAddresses = settingsLocal.getRecipients(); + } else { + eMailAddresses = splitByComma(recipientAddresses); } - else{ - log.error("Failed to send mail to " + rep + "! Format of the address is invalid."); + + for (String rep : eMailAddresses) { + if (isValidAddress(rep)) { + mail.addRecipient(Message.RecipientType.TO, new InternetAddress(rep)); + } else { + log.error("Failed to send mail to " + rep + "! Format of the address is invalid."); + } } - } try (Transport transport = session.getTransport()) { transport.connect(); transport.sendMessage(mail, mail.getAllRecipients()); } - catch(Exception e) - { + catch (Exception e) { log.error("Failed to send mail(s)! ", e); } } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 0cd4fb4b5..faba8a2d5 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -136,7 +136,7 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { @EventListener @Async public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) { - Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), + Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), notification.getConnectorId() ); String ocppTag = transactionRepository.getOcppTagOfActiveTransaction(connectorPk); @@ -145,13 +145,12 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati notification.getChargeBoxId(), notification.getConnectorId() ); - if (ocppTag != null){ - + if (ocppTag != null) { UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); String eMailAddy = userRecord.getEMail(); // send email if user with eMail address found if (!Strings.isNullOrEmpty(eMailAddy)) { - String bodyUserMail = + String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", userRecord.getFirstName(), userRecord.getLastName(), @@ -163,7 +162,7 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati } } - /* mail defined in settings */ + /* mail defined in settings */ if (isDisabled(OcppStationStatusSuspendedEV)) { return; } @@ -199,7 +198,7 @@ public void ocppTransactionEnded(OcppTransactionEnded notification) { } } - /* mail defined in settings */ + /* mail defined in settings */ if (isDisabled(OcppTransactionEnded)) { return; } @@ -251,11 +250,10 @@ private static String createContent(Transaction params, UserRecord userRecord) { meterValueStart = Integer.valueOf(params.getStartValue()); meterValueDiff = (meterValueStop - meterValueStart) / 1000.0; // --> kWh strMeterValueDiff = meterValueDiff.toString() + " kWh"; - } - catch(NumberFormatException e) { + } catch (NumberFormatException e) { log.error("Failed to calculate charged energy! ", e); } - + return new StringBuilder("User: ") .append(userRecord.getFirstName()).append(" ").append(userRecord.getLastName()) .append(System.lineSeparator()) From 953abb4937af129aab2ddea9cfc6581baf87bcc6 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Sat, 23 Dec 2023 21:33:59 +0100 Subject: [PATCH 18/52] TransactionRepositoryImpl, method getOcppTagOfActiveTransaction: order by transactionPk desc, to avoid fetching ghost transactions --- .../idsg/steve/repository/impl/TransactionRepositoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index badf6d71d..4d344b99f 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -104,6 +104,7 @@ public String getOcppTagOfActiveTransaction(Integer connectorPk) { .from(TRANSACTION) .where(TRANSACTION.CONNECTOR_PK.eq(connectorPk)) .and(TRANSACTION.STOP_VALUE.isNull()) + .orderBy(TRANSACTION.TRANSACTION_PK.desc()) // to avoid fetching ghost transactions, fetch the latest .fetchAny(TRANSACTION.ID_TAG); } From 48d7a68a929a1ccca8cf7a2649f9688ad43cb423 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Sat, 23 Dec 2023 21:38:41 +0100 Subject: [PATCH 19/52] NotificationService, SuspendedEV & TransactionStop: catch exception in case of no user is found by the OcppTag --- .../steve/service/NotificationService.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index faba8a2d5..6466242a5 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -146,10 +146,16 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati notification.getConnectorId() ); if (ocppTag != null) { - UserRecord userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - String eMailAddy = userRecord.getEMail(); + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + try { + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + eMailAddress = userRecord.getEMail(); + } catch (Exception e) { + log.error("Failed to send email (SuspendedEV). User not found! ", e); + } // send email if user with eMail address found - if (!Strings.isNullOrEmpty(eMailAddy)) { + if (!Strings.isNullOrEmpty(eMailAddress)) { String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", userRecord.getFirstName(), @@ -158,7 +164,7 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati notification.getChargeBoxId() ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddy); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); } } @@ -170,17 +176,24 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati notification.getConnectorId(), notification.getChargeBoxId() ); - mailService.sendAsync(subject, addTimestamp(body), ""); + mailService.sendAsync(subject, addTimestamp(body)); } @EventListener @Async public void ocppTransactionEnded(OcppTransactionEnded notification) { + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + Transaction transActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); transActParams.getOcppTagPk(); - UserRecord userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); - String eMailAddress = userRecord.getEMail(); + try { + userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); + eMailAddress = userRecord.getEMail(); + } catch (Exception e) { + log.error("Failed to send email (TransactionStop). User not found! ", e); + } // mail to user if (!Strings.isNullOrEmpty(eMailAddress)) { From 6a2a70234ed6ed148666ff2cb18c2b554c19ae44 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Mon, 25 Dec 2023 18:44:04 +0100 Subject: [PATCH 20/52] Removed unnecessary code --- .../java/de/rwth/idsg/steve/service/NotificationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 6466242a5..bb9b82401 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -187,7 +187,6 @@ public void ocppTransactionEnded(OcppTransactionEnded notification) { Transaction transActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); - transActParams.getOcppTagPk(); try { userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); eMailAddress = userRecord.getEMail(); From eb64190779ba198307ac48d031d0688ee8d7caf8 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Mon, 25 Dec 2023 22:34:43 +0100 Subject: [PATCH 21/52] NotificationService: shorten the error log message (no stack info) --- .../de/rwth/idsg/steve/service/NotificationService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index bb9b82401..0f9289e7b 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -152,7 +152,7 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati userRecord = userRepository.getDetails(ocppTag).getUserRecord(); eMailAddress = userRecord.getEMail(); } catch (Exception e) { - log.error("Failed to send email (SuspendedEV). User not found! ", e); + log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); } // send email if user with eMail address found if (!Strings.isNullOrEmpty(eMailAddress)) { @@ -184,14 +184,14 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati public void ocppTransactionEnded(OcppTransactionEnded notification) { String eMailAddress = null; UserRecord userRecord = new UserRecord(); - + Transaction transActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); try { userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); eMailAddress = userRecord.getEMail(); } catch (Exception e) { - log.error("Failed to send email (TransactionStop). User not found! ", e); + log.error("Failed to send email (TransactionStop). User not found! " + e.getMessage()); } // mail to user From 102ea169d594c5d168d5d727529ce4ad23a44065 Mon Sep 17 00:00:00 2001 From: fnkbsi <> Date: Mon, 25 Dec 2023 22:41:42 +0100 Subject: [PATCH 22/52] BeanConfiguration: add @EnableAsync to activate @Async in NotificationService --- src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java index 234c2caad..dc50ff9cc 100644 --- a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java @@ -48,6 +48,7 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -79,6 +80,7 @@ @Configuration @EnableWebMvc @EnableScheduling +@EnableAsync @ComponentScan("de.rwth.idsg.steve") public class BeanConfiguration implements WebMvcConfigurer { From 1798ba913fa4ca2b6fd3ee6922f5d629be688116 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 26 Dec 2023 01:18:41 +0100 Subject: [PATCH 23/52] BeanConfiguration: removed @EnableAsync because it's notcompiling under Java 17 --- src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java index dc50ff9cc..234c2caad 100644 --- a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java @@ -48,7 +48,6 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -80,7 +79,6 @@ @Configuration @EnableWebMvc @EnableScheduling -@EnableAsync @ComponentScan("de.rwth.idsg.steve") public class BeanConfiguration implements WebMvcConfigurer { From 14ac662c7d939264029a4349c3714020b4322c50 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 26 Dec 2023 01:23:17 +0100 Subject: [PATCH 24/52] NotificationService: removing @Async annotations. Realizing async instead with ScheduledExecutorService, so response to the chargepoint is not blocked. --- .../steve/service/NotificationService.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 0f9289e7b..0b44d4b70 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -49,7 +49,7 @@ import de.rwth.idsg.steve.repository.dto.Transaction; import static java.lang.String.format; import jooq.steve.db.tables.records.UserRecord; -import org.springframework.scheduling.annotation.Async; +import java.util.concurrent.ScheduledExecutorService; /** * @author Sevket Goekay @@ -63,6 +63,7 @@ public class NotificationService { @Autowired private TransactionRepository transactionRepository; @Autowired private UserRepository userRepository; @Autowired private OcppServerRepository ocppServerRepository; + @Autowired private ScheduledExecutorService executorService; @EventListener public void ocppStationBooted(OccpStationBooted notification) { @@ -134,8 +135,17 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { } @EventListener - @Async public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) { + executorService.execute(() -> { + try { + notificationActionSuspendedEV(notification); + } catch (Exception e) { + log.error("Failed to execute the notification of SuspendedEV", e); + } + }); + } + + private void notificationActionSuspendedEV(OcppStationStatusSuspendedEV notification) { Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), notification.getConnectorId() ); @@ -180,8 +190,17 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati } @EventListener - @Async public void ocppTransactionEnded(OcppTransactionEnded notification) { + executorService.execute(() -> { + try { + notificationActionTransactionEnded(notification); + } catch (Exception e) { + log.error("Failed to execute the notification of SuspendedEV", e); + } + }); + } + + private void notificationActionTransactionEnded(OcppTransactionEnded notification) { String eMailAddress = null; UserRecord userRecord = new UserRecord(); From 6a9091c2aea761de470b8bb85b151af4fbbe3dd0 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 23 Jan 2024 12:45:52 +0100 Subject: [PATCH 25/52] OcppServerRepository remove method getConnectorPk --- .../idsg/steve/repository/OcppServerRepository.java | 3 +-- .../repository/impl/OcppServerRepositoryImpl.java | 11 +---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java b/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java index e0f2e451c..7bb7ece35 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/OcppServerRepository.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -42,7 +42,6 @@ public interface OcppServerRepository { void updateChargeboxHeartbeat(String chargeBoxIdentity, DateTime ts); void insertConnectorStatus(InsertConnectorStatusParams params); - Integer getConnectorPk(String chargeBoxId, int connectorId); void insertMeterValues(String chargeBoxIdentity, List list, int connectorId, Integer transactionId); void insertMeterValues(String chargeBoxIdentity, List list, int transactionId); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java index c290261e8..14530fad1 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -165,15 +165,6 @@ public void insertConnectorStatus(InsertConnectorStatusParams p) { }); } - @Override - public Integer getConnectorPk(String chargeBoxId, int connectorId) { - return ctx.select(CONNECTOR.CONNECTOR_PK) - .from(CONNECTOR) - .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) - .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) - .fetchOne().value1(); - } - @Override public void insertMeterValues(String chargeBoxIdentity, List list, int connectorId, Integer transactionId) { if (CollectionUtils.isEmpty(list)) { From 3d26aa225e2413d75f7973c443dcc17f934dcafd Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 23 Jan 2024 12:54:22 +0100 Subject: [PATCH 26/52] TransactionRepository: add method getActiveTransactionId(String chagerBox, Integer connectorId); remove method getOcppTagOfActiveTransaction (Integer connectorPk) --- .../repository/TransactionRepository.java | 5 ++--- .../impl/TransactionRepositoryImpl.java | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index 9375e46b5..835ce8665 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -37,8 +37,7 @@ public interface TransactionRepository { void writeTransactionsCSV(TransactionQueryForm form, Writer writer); List getActiveTransactionIds(String chargeBoxId); - - String getOcppTagOfActiveTransaction(Integer connectorPk); + Integer getActiveTransactionId(String chargeBoxId, Integer connectorId); TransactionDetails getDetails(int transactionPk, boolean firstArrivingMeterValueIfMultiple); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 4d344b99f..70ec7a739 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -99,13 +99,15 @@ public List getActiveTransactionIds(String chargeBoxId) { } @Override - public String getOcppTagOfActiveTransaction(Integer connectorPk) { - return ctx.select(TRANSACTION.ID_TAG) - .from(TRANSACTION) - .where(TRANSACTION.CONNECTOR_PK.eq(connectorPk)) - .and(TRANSACTION.STOP_VALUE.isNull()) - .orderBy(TRANSACTION.TRANSACTION_PK.desc()) // to avoid fetching ghost transactions, fetch the latest - .fetchAny(TRANSACTION.ID_TAG); + public Integer getActiveTransactionId(String chargeBoxId, Integer connectorId) { + return ctx.select(TRANSACTION.TRANSACTION_PK) + .from(TRANSACTION) + .join(CONNECTOR) + .on(TRANSACTION.CONNECTOR_PK.equal(CONNECTOR.CONNECTOR_PK)) + .and(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) + .where(TRANSACTION.STOP_TIMESTAMP.isNull()) + .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) + .fetchAny(TRANSACTION.TRANSACTION_PK); } @Override From 4c66f000db23e700a8c9b94f0b630f4e6c76f8c1 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 23 Jan 2024 12:56:59 +0100 Subject: [PATCH 27/52] add timestamp to SUSPENDED_EV notification --- .../idsg/steve/service/CentralSystemService16_Service.java | 4 ++-- .../service/notification/OcppStationStatusSuspendedEV.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java b/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java index 6b60818a8..e337a9883 100644 --- a/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java +++ b/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -150,7 +150,7 @@ public StatusNotificationResponse statusNotification( if (parameters.getStatus() == ChargePointStatus.SUSPENDED_EV) { applicationEventPublisher.publishEvent(new OcppStationStatusSuspendedEV( - chargeBoxIdentity, parameters.getConnectorId())); + chargeBoxIdentity, parameters.getConnectorId(), parameters.getTimestamp())); } return new StatusNotificationResponse(); diff --git a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java index 487380206..fed6cbdd0 100644 --- a/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java +++ b/src/main/java/de/rwth/idsg/steve/service/notification/OcppStationStatusSuspendedEV.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.service.notification; import lombok.Data; +import org.joda.time.DateTime; /** * @author fnkbsi @@ -30,4 +31,5 @@ public class OcppStationStatusSuspendedEV { private final String chargeBoxId; private final int connectorId; + private final DateTime timestamp; } From 4d2963abfc8c3820fb0e4aeede241b4106033c75 Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 23 Jan 2024 13:08:39 +0100 Subject: [PATCH 28/52] adapt methode notificationActionSuspendedEV -> removed ocppServerRepository and suppress sending a mail in the first minute --- .../steve/service/NotificationService.java | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 0b44d4b70..f2a6d5560 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2023 SteVe Community Team + * Copyright (C) 2013-2024 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -43,7 +43,6 @@ import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionStarted; import static de.rwth.idsg.steve.NotificationFeature.OcppStationStatusSuspendedEV; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionEnded; -import de.rwth.idsg.steve.repository.OcppServerRepository; import de.rwth.idsg.steve.repository.TransactionRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; @@ -62,7 +61,6 @@ public class NotificationService { @Autowired private MailService mailService; @Autowired private TransactionRepository transactionRepository; @Autowired private UserRepository userRepository; - @Autowired private OcppServerRepository ocppServerRepository; @Autowired private ScheduledExecutorService executorService; @EventListener @@ -146,35 +144,39 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati } private void notificationActionSuspendedEV(OcppStationStatusSuspendedEV notification) { - Integer connectorPk = ocppServerRepository.getConnectorPk(notification.getChargeBoxId(), - notification.getConnectorId() - ); - String ocppTag = transactionRepository.getOcppTagOfActiveTransaction(connectorPk); - String subject = format("EV stopped charging at charging station %s, Connector %d", notification.getChargeBoxId(), notification.getConnectorId() ); - if (ocppTag != null) { - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - eMailAddress = userRecord.getEMail(); - } catch (Exception e) { - log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); - } - // send email if user with eMail address found - if (!Strings.isNullOrEmpty(eMailAddress)) { - String bodyUserMail = - format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId() - ); - - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + + Integer transactionPk = transactionRepository.getActiveTransactionId(notification.getChargeBoxId(), + notification.getConnectorId()); + if (transactionPk != null) { + Transaction transaction = transactionRepository.getTransaction(transactionPk); + String ocppTag = transaction.getOcppIdTag(); + if (ocppTag != null) { + // No mail directly after the start of the transaction, + if (notification.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + try { + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + eMailAddress = userRecord.getEMail(); + } catch (Exception e) { + log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); + } + // send email if user with eMail address found + if (!Strings.isNullOrEmpty(eMailAddress)) { + String bodyUserMail = + format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId() + ); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + } + } } } From 230c1ee8816b08c2be01bd1498c0437075806c7a Mon Sep 17 00:00:00 2001 From: fnkbsi Date: Tue, 23 Jan 2024 13:21:34 +0100 Subject: [PATCH 29/52] getActiveTransactionId(String chargeBox, Integer connectorId) method, add .orderBy(TRANSACTION.TRANSACTION_PK.desc()) to avoid fetching ghost transactions, fetch the latest --- .../idsg/steve/repository/impl/TransactionRepositoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 70ec7a739..339421071 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -107,6 +107,7 @@ public Integer getActiveTransactionId(String chargeBoxId, Integer connectorId) { .and(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) .where(TRANSACTION.STOP_TIMESTAMP.isNull()) .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) + .orderBy(TRANSACTION.TRANSACTION_PK.desc()) // to avoid fetching ghost transactions, fetch the latest .fetchAny(TRANSACTION.TRANSACTION_PK); } From 9faa7b4d112ae27064c59cbd4b396b159c3f9e02 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:46:40 +0200 Subject: [PATCH 30/52] Restore @override annotation after reslove conflict --- .../idsg/steve/repository/impl/TransactionRepositoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 9417e7d7e..4f7697d36 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -110,6 +110,7 @@ public Integer getActiveTransactionId(String chargeBoxId, Integer connectorId) { .fetchAny(TRANSACTION.TRANSACTION_PK); } + @Override public TransactionDetails getDetails(int transactionPk) { // ------------------------------------------------------------------------- From b2c6908c978b2b65864242499fe7cd7a54f240a1 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:00:50 +0200 Subject: [PATCH 31/52] Adding user individual notification selection. Selection is stored in a new row of the user table as comma separated list. --- .../rwth/idsg/steve/repository/dto/User.java | 5 +- .../dto/UserNotificationFeature.java | 72 +++++++ .../repository/impl/UserRepositoryImpl.java | 14 +- .../steve/service/NotificationService.java | 193 +++++++++++++----- .../steve/utils/mapper/UserFormMapper.java | 2 + .../steve/web/controller/UsersController.java | 4 + .../de/rwth/idsg/steve/web/dto/UserForm.java | 4 + .../resources/db/migration/V1_0_6__update.sql | 2 + .../views/data-man/00-user-profile.jsp | 6 + .../webapp/WEB-INF/views/data-man/users.jsp | 8 +- 10 files changed, 253 insertions(+), 57 deletions(-) create mode 100644 src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java create mode 100644 src/main/resources/db/migration/V1_0_6__update.sql diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/User.java b/src/main/java/de/rwth/idsg/steve/repository/dto/User.java index b919a420e..05c6a5c71 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/User.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/User.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ package de.rwth.idsg.steve.repository.dto; - +import java.util.List; import jooq.steve.db.tables.records.AddressRecord; import jooq.steve.db.tables.records.UserRecord; import lombok.Builder; @@ -36,6 +36,7 @@ public class User { public static final class Overview { private final Integer userPk, ocppTagPk; private final String ocppIdTag, name, phone, email; + private final List enabledFeatures; } @Getter @@ -43,6 +44,6 @@ public static final class Overview { public static final class Details { private final UserRecord userRecord; private final AddressRecord address; - private Optional ocppIdTag; + private final Optional ocppIdTag; } } diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java new file mode 100644 index 000000000..247251485 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java @@ -0,0 +1,72 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2024 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.rwth.idsg.steve.repository.dto; + +//import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; +//import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; +//import java.util.List; +//import java.util.stream.Collectors; +import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; +import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; +import java.util.List; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * + * @author fnkbsi + */ +@RequiredArgsConstructor +public enum UserNotificationFeature { + + // Ocpp related + // + //OcppStationBooted(" a charging station sends a boot notification (Note: This activates notifications about failed connection attempts for unregistered JSON stations, as well)"), + OcppStationStatusFailure(" a connector gets faulted"), + //OcppStationWebSocketConnected(" a JSON charging station connects"), + //OcppStationWebSocketDisconnected(" a JSON charging station disconnects"), + OcppTransactionStarted(" a charging station starts a transaction"), + OcppStationStatusSuspendedEV(" a EV suspended charging"), + OcppTransactionEnded(" a charging station ends a transaction"); + + + @Getter + private final String text; + + public static UserNotificationFeature fromName(String v) { + for (UserNotificationFeature c: UserNotificationFeature.values()) { + if (c.name().equalsIgnoreCase(v)) { + return c; + } + } + throw new IllegalArgumentException(v); + } + + public static List splitFeatures(String str) { + return splitByComma(str).stream() + .map(UserNotificationFeature::fromName) + .collect(Collectors.toList()); + } + + public static String joinFeatures(List enablesFeatures) { + return joinByComma(enablesFeatures); + } +} \ No newline at end of file diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index a4504cae2..ac013bb59 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -22,6 +22,7 @@ import de.rwth.idsg.steve.repository.AddressRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.User; +import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.web.dto.UserForm; import de.rwth.idsg.steve.web.dto.UserQueryForm; import jooq.steve.db.tables.records.AddressRecord; @@ -31,7 +32,7 @@ import org.jooq.Field; import org.jooq.JoinType; import org.jooq.Record1; -import org.jooq.Record7; +import org.jooq.Record8; import org.jooq.Result; import org.jooq.SelectConditionStep; import org.jooq.SelectQuery; @@ -68,6 +69,7 @@ public List getOverview(UserQueryForm form) { .name(r.value4() + " " + r.value5()) .phone(r.value6()) .email(r.value7()) + .enabledFeatures(UserNotificationFeature.splitFeatures(r.value8())) .build() ); } @@ -199,7 +201,9 @@ public void delete(int userPk) { // ------------------------------------------------------------------------- @SuppressWarnings("unchecked") - private Result> getOverviewInternal(UserQueryForm form) { + private Result> + getOverviewInternal(UserQueryForm form) { + SelectQuery selectQuery = ctx.selectQuery(); selectQuery.addFrom(USER); selectQuery.addJoin(OCPP_TAG, JoinType.LEFT_OUTER_JOIN, USER.OCPP_TAG_PK.eq(OCPP_TAG.OCPP_TAG_PK)); @@ -210,7 +214,8 @@ private Result USER.FIRST_NAME, USER.LAST_NAME, USER.PHONE, - USER.E_MAIL + USER.E_MAIL, + USER.USER_NOTIFICATION_FEATURES ); if (form.isSetUserPk()) { @@ -261,6 +266,8 @@ private void addInternal(DSLContext ctx, UserForm form, Integer addressPk) { .set(USER.NOTE, form.getNote()) .set(USER.ADDRESS_PK, addressPk) .set(USER.OCPP_TAG_PK, selectOcppTagPk(form.getOcppIdTag())) + .set(USER.USER_NOTIFICATION_FEATURES, + UserNotificationFeature.joinFeatures(form.getEnabledFeatures())) .execute(); if (count != 1) { @@ -279,6 +286,7 @@ private void updateInternal(DSLContext ctx, UserForm form, Integer addressPk) { .set(USER.NOTE, form.getNote()) .set(USER.ADDRESS_PK, addressPk) .set(USER.OCPP_TAG_PK, selectOcppTagPk(form.getOcppIdTag())) + .set(USER.USER_NOTIFICATION_FEATURES, UserNotificationFeature.joinFeatures(form.getEnabledFeatures())) .where(USER.USER_PK.eq(form.getUserPk())) .execute(); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index f2a6d5560..e824a72fe 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -46,6 +46,7 @@ import de.rwth.idsg.steve.repository.TransactionRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; +import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import static java.lang.String.format; import jooq.steve.db.tables.records.UserRecord; import java.util.concurrent.ScheduledExecutorService; @@ -72,7 +73,8 @@ public void ocppStationBooted(OccpStationBooted notification) { String subject = format("Received boot notification from '%s'", notification.getChargeBoxId()); String body; if (notification.getStatus().isPresent()) { - body = format("Charging station '%s' is in database and has registration status '%s'.", notification.getChargeBoxId(), notification.getStatus().get().value()); + body = format("Charging station '%s' is in database and has registration status '%s'.", + notification.getChargeBoxId(), notification.getStatus().get().value()); } else { body = format("Charging station '%s' is NOT in database", notification.getChargeBoxId()); } @@ -104,64 +106,166 @@ public void ocppStationWebSocketDisconnected(OcppStationWebSocketDisconnected no @EventListener public void ocppStationStatusFailure(OcppStationStatusFailure notification) { - if (isDisabled(OcppStationStatusFailure)) { - return; - } - String subject = format("Connector '%s' of charging station '%s' is FAULTED", notification.getConnectorId(), notification.getChargeBoxId() ); + + // user mail in separate task, so database queries don't block the execution + executorService.execute(() -> { + try { + userNotificationocppStationStatusFailure(notification, subject); + } catch (Exception e) { + log.error("Failed to execute the user notification of ocppStationStatusFailure.", e); + } + }); + + /* mail defined in settings */ + if (isDisabled(OcppStationStatusFailure)) { + return; + } + String body = format("Status Error Code: '%s'", notification.getErrorCode()); mailService.sendAsync(subject, addTimestamp(body)); } - @EventListener - public void ocppTransactionStarted(OcppTransactionStarted notification) { - if (isDisabled(OcppTransactionStarted)) { - return; + private void userNotificationocppStationStatusFailure(OcppStationStatusFailure notification, String subject) { + + Integer transactionPk = transactionRepository.getActiveTransactionId(notification.getChargeBoxId(), + notification.getConnectorId()); + if (transactionPk != null) { + Transaction transaction = transactionRepository.getTransaction(transactionPk); + String ocppTag = transaction.getOcppIdTag(); + if (ocppTag != null) { + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + try { + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppStationStatusFailure.toString())) { + eMailAddress = userRecord.getEMail(); + } + } catch (Exception e) { + log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); + } + // send email if user with eMail address found + if (!Strings.isNullOrEmpty(eMailAddress)) { + String bodyUserMail = + format("User: %s %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId(), + notification.getErrorCode() + ); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + } + } } + } + @EventListener + public void ocppTransactionStarted(OcppTransactionStarted notification) { String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", notification.getTransactionId(), notification.getParams().getChargeBoxId(), notification.getParams().getConnectorId() ); + // user mail in separate task, so database queries don't block the execution + executorService.execute(() -> { + try { + userNotificationOcppTransactionStarted(notification, subject); + } catch (Exception e) { + log.error("Failed to execute the user notification of ocppStationStatusFailure.", e); + } + }); + + /* mail defined in settings */ + if (isDisabled(OcppTransactionStarted)) { + return; + } + mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } - @EventListener - public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) { - executorService.execute(() -> { + private void userNotificationOcppTransactionStarted(OcppTransactionStarted notification, String subject) { + + String ocppTag = notification.getParams().getIdTag(); + if (ocppTag != null) { + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); try { - notificationActionSuspendedEV(notification); + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppTransactionStarted.toString())) { + eMailAddress = userRecord.getEMail(); + } } catch (Exception e) { - log.error("Failed to execute the notification of SuspendedEV", e); + log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); } - }); + // send email if user with eMail address found + if (!Strings.isNullOrEmpty(eMailAddress)) { + String bodyUserMail = + format("User: '%s' '%s' started transaction '%d' on connector '%s' of charging station '%s'", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getTransactionId(), + notification.getParams().getConnectorId(), + notification.getParams().getChargeBoxId() + ); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + } + } } - private void notificationActionSuspendedEV(OcppStationStatusSuspendedEV notification) { + @EventListener + public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) { String subject = format("EV stopped charging at charging station %s, Connector %d", notification.getChargeBoxId(), notification.getConnectorId() ); + // user mail in separate task, so database queries don't block the execution + executorService.execute(() -> { + try { + userNotificationActionSuspendedEV(notification, subject); + } catch (Exception e) { + log.error("Failed to execute the user notification of SuspendedEV", e); + } + }); + + /* mail defined in settings */ + if (isDisabled(OcppStationStatusSuspendedEV)) { + return; + } + + String body = format("Connector %d of charging station %s notifies Suspended_EV", + notification.getConnectorId(), + notification.getChargeBoxId() + ); + mailService.sendAsync(subject, addTimestamp(body)); + } + + private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV notification, String subject) { + Integer transactionPk = transactionRepository.getActiveTransactionId(notification.getChargeBoxId(), notification.getConnectorId()); if (transactionPk != null) { Transaction transaction = transactionRepository.getTransaction(transactionPk); String ocppTag = transaction.getOcppIdTag(); if (ocppTag != null) { - // No mail directly after the start of the transaction, + // No mail directly after the start of the transaction, if (notification.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { String eMailAddress = null; UserRecord userRecord = new UserRecord(); try { userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - eMailAddress = userRecord.getEMail(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppStationStatusSuspendedEV.toString())) { + eMailAddress = userRecord.getEMail(); + } } catch (Exception e) { log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); } @@ -179,30 +283,33 @@ private void notificationActionSuspendedEV(OcppStationStatusSuspendedEV notifica } } } - - /* mail defined in settings */ - if (isDisabled(OcppStationStatusSuspendedEV)) { - return; - } - String body = format("Connector %d of charging station %s notifies Suspended_EV", - notification.getConnectorId(), - notification.getChargeBoxId() - ); - mailService.sendAsync(subject, addTimestamp(body)); } @EventListener public void ocppTransactionEnded(OcppTransactionEnded notification) { - executorService.execute(() -> { + String subject = format("Transaction '%s' has ended on charging station '%s'", + notification.getParams().getTransactionId(), + notification.getParams().getChargeBoxId() + ); + + // user mail in separate task, so database queries don't block the execution + executorService.execute(() -> { try { - notificationActionTransactionEnded(notification); + userNotificationActionTransactionEnded(notification, subject); } catch (Exception e) { log.error("Failed to execute the notification of SuspendedEV", e); } }); + + /* mail defined in settings */ + if (isDisabled(OcppTransactionEnded)) { + return; + } + + mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } - private void notificationActionTransactionEnded(OcppTransactionEnded notification) { + private void userNotificationActionTransactionEnded(OcppTransactionEnded notification, String subject) { String eMailAddress = null; UserRecord userRecord = new UserRecord(); @@ -210,38 +317,24 @@ private void notificationActionTransactionEnded(OcppTransactionEnded notificatio try { userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); - eMailAddress = userRecord.getEMail(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppTransactionEnded.toString())) { + eMailAddress = userRecord.getEMail(); + } } catch (Exception e) { log.error("Failed to send email (TransactionStop). User not found! " + e.getMessage()); } // mail to user if (!Strings.isNullOrEmpty(eMailAddress)) { - String subjectUserMail = format("Transaction '%s' has ended on charging station '%s'", - transActParams.getId(), - transActParams.getChargeBoxId() - ); - // if the Transactionstop is received within the first Minute don't send an E-Mail if (transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { - mailService.sendAsync(subjectUserMail, + mailService.sendAsync(subject, addTimestamp(createContent(transActParams, userRecord)), eMailAddress ); } } - - /* mail defined in settings */ - if (isDisabled(OcppTransactionEnded)) { - return; - } - - String subject = format("Transaction '%s' has ended on charging station '%s'", - notification.getParams().getTransactionId(), - notification.getParams().getChargeBoxId() - ); - - mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } // ------------------------------------------------------------------------- diff --git a/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java b/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java index f43f4e888..72bb8639b 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java +++ b/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.utils.mapper; import de.rwth.idsg.steve.repository.dto.User; +import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.utils.ControllerHelper; import de.rwth.idsg.steve.web.dto.UserForm; import de.rwth.idsg.steve.web.dto.UserSex; @@ -44,6 +45,7 @@ public static UserForm toForm(User.Details details) { form.setPhone(userRecord.getPhone()); form.setSex(UserSex.fromDatabaseValue(userRecord.getSex())); form.setEMail(userRecord.getEMail()); + form.setEnabledFeatures(UserNotificationFeature.splitFeatures(userRecord.getUserNotificationFeatures())); form.setNote(userRecord.getNote()); form.setAddress(AddressMapper.recordToDto(details.getAddress())); form.setOcppIdTag(details.getOcppIdTag().orElse(ControllerHelper.EMPTY_OPTION)); diff --git a/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java b/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java index 542facbc4..fc31c6604 100644 --- a/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java +++ b/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.web.controller; +import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.User; import de.rwth.idsg.steve.service.OcppTagService; @@ -79,6 +80,7 @@ public String getQuery(@ModelAttribute(PARAMS) UserQueryForm params, Model model private void initList(Model model, UserQueryForm params) { model.addAttribute(PARAMS, params); model.addAttribute("userList", userRepository.getOverview(params)); + model.addAttribute("features", UserNotificationFeature.values()); } @RequestMapping(value = DETAILS_PATH, method = RequestMethod.GET) @@ -87,6 +89,7 @@ public String getDetails(@PathVariable("userPk") int userPk, Model model) { UserForm form = UserFormMapper.toForm(details); model.addAttribute("userForm", form); + model.addAttribute("features", UserNotificationFeature.values()); setTags(model); return "data-man/userDetails"; } @@ -95,6 +98,7 @@ public String getDetails(@PathVariable("userPk") int userPk, Model model) { public String addGet(Model model) { setTags(model); model.addAttribute("userForm", new UserForm()); + model.addAttribute("features", UserNotificationFeature.values()); return "data-man/userAdd"; } diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java index 3d5eb8d49..c310c0bd3 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java @@ -18,6 +18,8 @@ */ package de.rwth.idsg.steve.web.dto; +import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; +import java.util.List; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -52,6 +54,8 @@ public class UserForm { @Email(message = "Not a valid e-mail address") private String eMail; + private List enabledFeatures; + private Address address; } diff --git a/src/main/resources/db/migration/V1_0_6__update.sql b/src/main/resources/db/migration/V1_0_6__update.sql new file mode 100644 index 000000000..2cfb2206b --- /dev/null +++ b/src/main/resources/db/migration/V1_0_6__update.sql @@ -0,0 +1,2 @@ +ALTER TABLE `user` + ADD COLUMN `user_notification_features` TEXT NULL DEFAULT NULL COMMENT 'comma separated list' COLLATE 'utf8mb3_unicode_ci' AFTER `e_mail`; \ No newline at end of file diff --git a/src/main/resources/webapp/WEB-INF/views/data-man/00-user-profile.jsp b/src/main/resources/webapp/WEB-INF/views/data-man/00-user-profile.jsp index c128b4af3..586fb0fce 100644 --- a/src/main/resources/webapp/WEB-INF/views/data-man/00-user-profile.jsp +++ b/src/main/resources/webapp/WEB-INF/views/data-man/00-user-profile.jsp @@ -33,5 +33,11 @@ Phone: E-mail: + + Notify when... + + + Additional Note: \ No newline at end of file diff --git a/src/main/resources/webapp/WEB-INF/views/data-man/users.jsp b/src/main/resources/webapp/WEB-INF/views/data-man/users.jsp index 90f2cd91d..f147fdbb8 100644 --- a/src/main/resources/webapp/WEB-INF/views/data-man/users.jsp +++ b/src/main/resources/webapp/WEB-INF/views/data-man/users.jsp @@ -61,6 +61,7 @@ Name Phone E-Mail + Notifications @@ -79,8 +80,11 @@ ${cr.name} ${cr.phone} ${cr.email} - - + + ${eF}
+
+ + From ac38d1e7fd97291c6c69150787671aa1b6619966 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:19:07 +0200 Subject: [PATCH 32/52] Rename db migration script V1_06 to V1_08 --- src/main/resources/db/migration/V1_0_8__update.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/resources/db/migration/V1_0_8__update.sql diff --git a/src/main/resources/db/migration/V1_0_8__update.sql b/src/main/resources/db/migration/V1_0_8__update.sql new file mode 100644 index 000000000..2cfb2206b --- /dev/null +++ b/src/main/resources/db/migration/V1_0_8__update.sql @@ -0,0 +1,2 @@ +ALTER TABLE `user` + ADD COLUMN `user_notification_features` TEXT NULL DEFAULT NULL COMMENT 'comma separated list' COLLATE 'utf8mb3_unicode_ci' AFTER `e_mail`; \ No newline at end of file From 239e62749d4e4655e798f56ffaf7289cba5a301f Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:03:58 +0100 Subject: [PATCH 33/52] manually merge origin/master into MailUserAtTransactionStop (previous merge was incomplete) --- README.md | 4 + pom.xml | 53 +-- .../java/de/rwth/idsg/steve/JettyServer.java | 8 - .../rwth/idsg/steve/SteveConfiguration.java | 2 + .../steve/config/ApiDocsConfiguration.java | 3 - .../rwth/idsg/steve/service/MailService.java | 34 +- .../steve/service/NotificationService.java | 1 + .../idsg/steve/service/WebUserService.java | 22 +- .../controller/AboutSettingsController.java | 2 - src/main/resources/checkstyle.xml | 336 +++++++++--------- 10 files changed, 226 insertions(+), 239 deletions(-) diff --git a/README.md b/README.md index e72367078..e6153a0c4 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ SteVe is considered as an open platform to implement, test and evaluate novel id The project is distributed under [GPL](LICENSE.txt) and is free to use. If you are going to deploy it we are happy to see the [logo](website/logo/managed-by-steve.pdf) on a charge point. +## Relation to Powerfill + +[Powerfill](https://powerfill.co/) is a SaaS company to expand beyond the basics of SteVe: While SteVe covers the basics of OCPP functionality in a DIY sense, Powerfill offers more and enterprise features with ease of use. [See the announcement](https://github.com/steve-community/steve/issues/1643) and [sign up for early access](https://powerfill.co/early-access/). + ### Charge Point Support Electric charge points using the following OCPP versions are supported: diff --git a/pom.xml b/pom.xml index 728b49856..0ef520dc6 100644 --- a/pom.xml +++ b/pom.xml @@ -35,16 +35,16 @@ 17 UTF-8 - 3.19.11 - 10.18.2 - 4.0.5 - 6.1.11 - 6.3.3 - 9.0.0 - 12.0.13 - 1.18.34 - 2.18.0 - 4.5 + 3.19.16 + 11.1.0 + 4.1.0 + 6.2.0 + 6.4.2 + 9.1.0 + 12.0.16 + 1.18.36 + 2.18.2 + 4.6 jdbc:mysql://${db.ip}:${db.port}/${db.schema}?useSSL=true&serverTimezone=UTC @@ -181,6 +181,13 @@ ${java.version} ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + @@ -188,14 +195,14 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.0 + 3.5.2 org.apache.maven.plugins maven-pmd-plugin - 3.25.0 + 3.26.0 ${project.basedir}/src/main/resources/maven-pmd-plugin-default.xml @@ -205,7 +212,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.8.6.4 + 4.8.6.6 false de.rwth.idsg.steve.- @@ -221,7 +228,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.5.0 + 3.6.0 ${basedir}/src/main/resources/checkstyle.xml @@ -268,7 +275,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.8.0 + 3.8.1 copy-dependencies @@ -493,14 +500,14 @@ org.apache.logging.log4j log4j-bom - 2.24.0 + 2.24.3 import pom org.junit junit-bom - 5.11.2 + 5.11.4 pom import @@ -539,7 +546,7 @@ org.jetbrains annotations - 25.0.0 + 26.0.1 compile @@ -557,12 +564,12 @@ org.hibernate.validator hibernate-validator - 8.0.1.Final + 8.0.2.Final com.google.guava guava - 33.3.1-jre + 33.4.0-jre com.fasterxml.jackson.core @@ -588,7 +595,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.4 + 5.4.1 jakarta.websocket @@ -747,7 +754,7 @@ com.zaxxer HikariCP - 5.1.0 + 6.2.1 org.jooq @@ -786,7 +793,7 @@ net.bytebuddy byte-buddy - 1.15.1 + 1.15.11 test diff --git a/src/main/java/de/rwth/idsg/steve/JettyServer.java b/src/main/java/de/rwth/idsg/steve/JettyServer.java index 3a417c69a..3b4117666 100644 --- a/src/main/java/de/rwth/idsg/steve/JettyServer.java +++ b/src/main/java/de/rwth/idsg/steve/JettyServer.java @@ -135,14 +135,6 @@ private ServerConnector httpsConnector(HttpConfiguration httpConfig) { sslContextFactory.setKeyStorePath(CONFIG.getJetty().getKeyStorePath()); sslContextFactory.setKeyStorePassword(CONFIG.getJetty().getKeyStorePassword()); sslContextFactory.setKeyManagerPassword(CONFIG.getJetty().getKeyStorePassword()); - sslContextFactory.setExcludeCipherSuites( - "SSL_RSA_WITH_DES_CBC_SHA", - "SSL_DHE_RSA_WITH_DES_CBC_SHA", - "SSL_DHE_DSS_WITH_DES_CBC_SHA", - "SSL_RSA_EXPORT_WITH_RC4_40_MD5", - "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", - "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"); // SSL HTTP Configuration HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); diff --git a/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java b/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java index 9df315f23..96f2341a9 100644 --- a/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java @@ -67,7 +67,9 @@ public enum SteveConfiguration { contextPath = sanitizeContextPath(p.getOptionalString("context.path")); steveVersion = p.getString("steve.version"); gitDescribe = useFallbackIfNotSet(p.getOptionalString("git.describe"), null); + profile = ApplicationProfile.fromName(p.getString("profile")); + System.setProperty("spring.profiles.active", profile.name().toLowerCase()); jetty = Jetty.builder() .serverHost(p.getString("server.host")) diff --git a/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java index 81a77b24d..2b1d65076 100644 --- a/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java @@ -19,7 +19,6 @@ package de.rwth.idsg.steve.config; import de.rwth.idsg.steve.SteveConfiguration; -import de.rwth.idsg.steve.SteveProdCondition; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; @@ -34,7 +33,6 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -52,7 +50,6 @@ SwaggerUiConfigProperties.class, SwaggerUiOAuthProperties.class, JacksonAutoConfiguration.class}) -@Conditional(SteveProdCondition.class) public class ApiDocsConfiguration { static { diff --git a/src/main/java/de/rwth/idsg/steve/service/MailService.java b/src/main/java/de/rwth/idsg/steve/service/MailService.java index 692910743..6375ddd61 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailService.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailService.java @@ -26,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import jakarta.annotation.PostConstruct; import jakarta.mail.Authenticator; import jakarta.mail.Message; import jakarta.mail.MessagingException; @@ -35,6 +34,7 @@ import jakarta.mail.Transport; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; + import java.util.Properties; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.locks.Lock; @@ -55,31 +55,8 @@ public class MailService { @Autowired private SettingsRepository settingsRepository; @Autowired private ScheduledExecutorService executorService; - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final Lock readLock = readWriteLock.readLock(); - private final Lock writeLock = readWriteLock.writeLock(); - - private MailSettings settings; - private Session session; - - @PostConstruct - public void loadSettingsFromDB() { - writeLock.lock(); - try { - settings = settingsRepository.getMailSettings(); - } finally { - writeLock.unlock(); - } - session = createSession(getSettings()); - } - public MailSettings getSettings() { - readLock.lock(); - try { - return this.settings; - } finally { - readLock.unlock(); - } + return settingsRepository.getMailSettings(); } public void sendTestMail() { @@ -111,17 +88,18 @@ public void sendAsync(String subject, String body, String recipientAddresses) { } private void send(String subject, String body, String recipientAddresses) throws MessagingException { - MailSettings settingsLocal = getSettings(); + MailSettings settings = getSettings(); + Session session = createSession(settings); Message mail = new MimeMessage(session); mail.setSubject("[SteVe] " + subject); mail.setContent(body, "text/plain"); - mail.setFrom(new InternetAddress(settingsLocal.getFrom())); + mail.setFrom(new InternetAddress(settings.getFrom())); List eMailAddresses; if (recipientAddresses.isEmpty()) { - eMailAddresses = settingsLocal.getRecipients(); + eMailAddresses = settings.getRecipients(); } else { eMailAddresses = splitByComma(recipientAddresses); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index cfda57ee5..9a6d12b98 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -341,6 +341,7 @@ private void userNotificationActionTransactionEnded(OcppTransactionEnded notific // Private helpers // ------------------------------------------------------------------------- + private static String createContent(InsertTransactionParams params) { StringBuilder sb = new StringBuilder("Details:").append(System.lineSeparator()) .append("- chargeBoxId: ").append(params.getChargeBoxId()).append(System.lineSeparator()) diff --git a/src/main/java/de/rwth/idsg/steve/service/WebUserService.java b/src/main/java/de/rwth/idsg/steve/service/WebUserService.java index 9be5368eb..f2305e94e 100644 --- a/src/main/java/de/rwth/idsg/steve/service/WebUserService.java +++ b/src/main/java/de/rwth/idsg/steve/service/WebUserService.java @@ -26,12 +26,14 @@ import de.rwth.idsg.steve.repository.WebUserRepository; import jooq.steve.db.tables.records.WebUserRecord; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.jooq.JSON; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; @@ -80,14 +82,20 @@ public void afterStart(ContextRefreshedEvent event) { return; } - var user = User - .withUsername(SteveConfiguration.CONFIG.getAuth().getUserName()) - .password(SteveConfiguration.CONFIG.getAuth().getEncodedPassword()) - .disabled(false) - .authorities("ADMIN") - .build(); + var headerVal = SteveConfiguration.CONFIG.getWebApi().getHeaderValue(); + + var encodedApiPassword = StringUtils.isEmpty(headerVal) + ? null + : SteveConfiguration.CONFIG.getAuth().getPasswordEncoder().encode(headerVal); + + var user = new WebUserRecord() + .setUsername(SteveConfiguration.CONFIG.getAuth().getUserName()) + .setPassword(SteveConfiguration.CONFIG.getAuth().getEncodedPassword()) + .setApiPassword(encodedApiPassword) + .setEnabled(true) + .setAuthorities(toJson(AuthorityUtils.createAuthorityList("ADMIN"))); - this.createUser(user); + webUserRepository.createUser(user); } @Override diff --git a/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java b/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java index 46c76f2a9..c56b10a43 100644 --- a/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java +++ b/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java @@ -94,7 +94,6 @@ public String postSettings(@Valid @ModelAttribute("settingsForm") SettingsForm s } settingsRepository.update(settingsForm); - mailService.loadSettingsFromDB(); return "redirect:/manager/settings"; } @@ -107,7 +106,6 @@ public String testMail(@Valid @ModelAttribute("settingsForm") SettingsForm setti } settingsRepository.update(settingsForm); - mailService.loadSettingsFromDB(); mailService.sendTestMail(); return "redirect:/manager/settings"; diff --git a/src/main/resources/checkstyle.xml b/src/main/resources/checkstyle.xml index cdc639ad6..3c8f5dd9e 100644 --- a/src/main/resources/checkstyle.xml +++ b/src/main/resources/checkstyle.xml @@ -1,168 +1,168 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1fe84cebf3f6f48f3cd717d60ac2173745407c1a Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Thu, 13 Mar 2025 14:29:43 +0100 Subject: [PATCH 34/52] MailService removed method send(String subject, String body, String recipientAddresses) from interface --- src/main/java/de/rwth/idsg/steve/service/MailService.java | 4 ---- .../java/de/rwth/idsg/steve/service/MailServiceDefault.java | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/MailService.java b/src/main/java/de/rwth/idsg/steve/service/MailService.java index c435aaa91..3e8944bfc 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailService.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailService.java @@ -20,8 +20,6 @@ import de.rwth.idsg.steve.repository.dto.MailSettings; -import jakarta.mail.MessagingException; - /** * @author Sevket Goekay * @since 11.03.2025 @@ -35,6 +33,4 @@ public interface MailService { void sendAsync(String subject, String body); void sendAsync(String subject, String body, String recipientAddresses); - - void send(String subject, String body, String recipientAddresses) throws MessagingException; } diff --git a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java index dd49ede4e..de7a0b2dd 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java @@ -16,6 +16,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package de.rwth.idsg.steve.service; import jakarta.mail.Authenticator; @@ -88,9 +89,8 @@ public void sendAsync(String subject, String body, String recipientAddresses) { } }); } - - @Override - public void send(String subject, String body, String recipientAddresses) throws MessagingException { + + private void send(String subject, String body, String recipientAddresses) throws MessagingException { MailSettings settings = getSettings(); Session session = createSession(settings); From 3ecbd70efc5e64bc5d9ca9a1b923b40ba51cc04a Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Sat, 26 Apr 2025 18:33:36 +0200 Subject: [PATCH 35/52] NotificationService reduce DB calls on user notification of OcppStationStatusFailure and SuspendedEV event. adapt QueryForm and TransactionRepository --- .../repository/TransactionRepository.java | 3 +- .../impl/TransactionRepositoryImpl.java | 35 ++++++++++++------- .../steve/service/NotificationService.java | 18 +++++----- .../steve/web/dto/TransactionQueryForm.java | 9 +++++ 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index ae786abe5..68a49abf5 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -31,13 +31,14 @@ */ public interface TransactionRepository { Transaction getTransaction(int transactionPk); + + Transaction getActiveTransaction(String chargeBoxId, Integer connectorId); List getTransactions(TransactionQueryForm form); void writeTransactionsCSV(TransactionQueryForm form, Writer writer); List getActiveTransactionIds(String chargeBoxId); - Integer getActiveTransactionId(String chargeBoxId, Integer connectorId); TransactionDetails getDetails(int transactionPk); } diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 54d1afe21..81b9e155f 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -75,6 +75,24 @@ public Transaction getTransaction(int transactionPk) { return getInternal(form).fetch() .map(new TransactionMapper()).get(0); } + + @Override + public Transaction getActiveTransaction(String chargeBoxId, Integer connectorId) { + Transaction retVal = null; + TransactionQueryForm form = new TransactionQueryForm(); + form.setChargeBoxId(chargeBoxId); + form.setConnectorId(connectorId); + form.setReturnCSV(false); + form.setType(TransactionQueryForm.QueryType.ACTIVE); + Record12 + transactionRecord = getInternal(form).fetchAny(); + if (transactionRecord != null) { + TransactionMapper mapper = new TransactionMapper(); + retVal = mapper.map(transactionRecord); + } + return retVal; + } @Override public List getTransactions(TransactionQueryForm form) { @@ -99,19 +117,6 @@ public List getActiveTransactionIds(String chargeBoxId) { .fetch(TRANSACTION.TRANSACTION_PK); } - @Override - public Integer getActiveTransactionId(String chargeBoxId, Integer connectorId) { - return ctx.select(TRANSACTION.TRANSACTION_PK) - .from(TRANSACTION) - .join(CONNECTOR) - .on(TRANSACTION.CONNECTOR_PK.equal(CONNECTOR.CONNECTOR_PK)) - .and(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) - .where(TRANSACTION.STOP_TIMESTAMP.isNull()) - .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) - .orderBy(TRANSACTION.TRANSACTION_PK.desc()) // to avoid fetching ghost transactions, fetch the latest - .fetchAny(TRANSACTION.TRANSACTION_PK); - } - @Override public TransactionDetails getDetails(int transactionPk) { @@ -307,6 +312,10 @@ private SelectQuery addConditions(SelectQuery selectQuery, TransactionQueryForm selectQuery.addConditions(CONNECTOR.CHARGE_BOX_ID.eq(form.getChargeBoxId())); } + if (form.isConnectorId()) { + selectQuery.addConditions(CONNECTOR.CONNECTOR_ID.eq(form.getConnectorId())); + } + if (form.isOcppIdTagSet()) { selectQuery.addConditions(TRANSACTION.ID_TAG.eq(form.getOcppIdTag())); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 03333226c..dc307d191 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -48,7 +48,9 @@ import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; +import de.rwth.idsg.steve.web.dto.TransactionQueryForm; import static java.lang.String.format; +import java.util.List; import jooq.steve.db.tables.records.UserRecord; /** @@ -114,7 +116,7 @@ public void ocppStationStatusFailure(OcppStationStatusFailure notification) { // user mail in separate task, so database queries don't block the execution asyncTaskExecutor.execute(() -> { try { - userNotificationocppStationStatusFailure(notification, subject); + userNotificationOcppStationStatusFailure(notification, subject); } catch (Exception e) { log.error("Failed to execute the user notification of ocppStationStatusFailure.", e); } @@ -130,12 +132,11 @@ public void ocppStationStatusFailure(OcppStationStatusFailure notification) { mailService.sendAsync(subject, addTimestamp(body)); } - private void userNotificationocppStationStatusFailure(OcppStationStatusFailure notification, String subject) { + private void userNotificationOcppStationStatusFailure(OcppStationStatusFailure notification, String subject) { - Integer transactionPk = transactionRepository.getActiveTransactionId(notification.getChargeBoxId(), + Transaction transaction = transactionRepository.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); - if (transactionPk != null) { - Transaction transaction = transactionRepository.getTransaction(transactionPk); + if (transaction != null) { String ocppTag = transaction.getOcppIdTag(); if (ocppTag != null) { String eMailAddress = null; @@ -249,11 +250,10 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati } private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV notification, String subject) { - - Integer transactionPk = transactionRepository.getActiveTransactionId(notification.getChargeBoxId(), + + Transaction transaction= transactionRepository.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); - if (transactionPk != null) { - Transaction transaction = transactionRepository.getTransaction(transactionPk); + if (transaction != null) { String ocppTag = transaction.getOcppIdTag(); if (ocppTag != null) { // No mail directly after the start of the transaction, diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java index ef16d8d7c..6f5ddf970 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java @@ -39,6 +39,9 @@ public class TransactionQueryForm extends QueryForm { // Internal database Id @Schema(description = "Database primary key of the transaction") private Integer transactionPk; + + @Schema(description = "ID of the connector") + Integer connectorId; @Schema(description = "Disabled for the Web APIs. Do not use and set", hidden = true) private boolean returnCSV = false; @@ -59,6 +62,12 @@ public boolean isPeriodFromToCorrect() { public boolean isTransactionPkSet() { return transactionPk != null; } + + @Schema(hidden = true) + public boolean isConnectorId() { + return connectorId != null; + } + public QueryType getType() { return Objects.requireNonNullElse(type, QueryType.ALL); From 8d1ede80771a383694aa2307d75691e1df370292 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Sat, 26 Apr 2025 19:19:38 +0200 Subject: [PATCH 36/52] NotificationService reduce nested if-blocks --- .../steve/service/NotificationService.java | 197 ++++++++++-------- 1 file changed, 108 insertions(+), 89 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index dc307d191..c768d28de 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -48,9 +48,7 @@ import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; -import de.rwth.idsg.steve.web.dto.TransactionQueryForm; import static java.lang.String.format; -import java.util.List; import jooq.steve.db.tables.records.UserRecord; /** @@ -136,34 +134,41 @@ private void userNotificationOcppStationStatusFailure(OcppStationStatusFailure n Transaction transaction = transactionRepository.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); - if (transaction != null) { - String ocppTag = transaction.getOcppIdTag(); - if (ocppTag != null) { - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppStationStatusFailure.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); - } - // send email if user with eMail address found - if (!Strings.isNullOrEmpty(eMailAddress)) { - String bodyUserMail = - format("User: %s %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId(), - notification.getErrorCode() - ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); - } + if (transaction == null) { + return; + } + + String ocppTag = transaction.getOcppIdTag(); + if (ocppTag == null) { + return; + } + + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + try { + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppStationStatusFailure.toString())) { + eMailAddress = userRecord.getEMail(); } + } catch (Exception e) { + log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); + } + + if (Strings.isNullOrEmpty(eMailAddress)) { + return; } + + // send email if user with eMail address found + String bodyUserMail = + format("User: %s %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId(), + notification.getErrorCode() + ); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); } @EventListener @@ -194,31 +199,36 @@ public void ocppTransactionStarted(OcppTransactionStarted notification) { private void userNotificationOcppTransactionStarted(OcppTransactionStarted notification, String subject) { String ocppTag = notification.getParams().getIdTag(); - if (ocppTag != null) { - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppTransactionStarted.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); - } - // send email if user with eMail address found - if (!Strings.isNullOrEmpty(eMailAddress)) { - String bodyUserMail = - format("User: '%s' '%s' started transaction '%d' on connector '%s' of charging station '%s'", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getTransactionId(), - notification.getParams().getConnectorId(), - notification.getParams().getChargeBoxId() - ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + if (ocppTag == null) { + return; + } + + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + try { + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppTransactionStarted.toString())) { + eMailAddress = userRecord.getEMail(); } + } catch (Exception e) { + log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); + } + + if (Strings.isNullOrEmpty(eMailAddress)) { + return; } + + // send email if user with eMail address found + String bodyUserMail = + format("User: '%s' '%s' started transaction '%d' on connector '%s' of charging station '%s'", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getTransactionId(), + notification.getParams().getConnectorId(), + notification.getParams().getChargeBoxId() + ); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); } @EventListener @@ -250,44 +260,51 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati } private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV notification, String subject) { - + Transaction transaction= transactionRepository.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); - if (transaction != null) { - String ocppTag = transaction.getOcppIdTag(); - if (ocppTag != null) { - // No mail directly after the start of the transaction, - if (notification.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppStationStatusSuspendedEV.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); - } - // send email if user with eMail address found - if (!Strings.isNullOrEmpty(eMailAddress)) { - String bodyUserMail = - format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId() - ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); - } - } + if (transaction == null) { + return;} + + String ocppTag = transaction.getOcppIdTag(); + if (ocppTag == null) { + return;} + + // No mail directly after the start of the transaction, + if (!notification.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { + return; + } + + String eMailAddress = null; + UserRecord userRecord = new UserRecord(); + try { + userRecord = userRepository.getDetails(ocppTag).getUserRecord(); + if (userRecord.getUserNotificationFeatures() + .contains(UserNotificationFeature.OcppStationStatusSuspendedEV.toString())) { + eMailAddress = userRecord.getEMail(); } + } catch (Exception e) { + log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); + } + + if (Strings.isNullOrEmpty(eMailAddress)) { + return; } + + // send email if user with eMail address found + String bodyUserMail = + format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", + userRecord.getFirstName(), + userRecord.getLastName(), + notification.getConnectorId(), + notification.getChargeBoxId() + ); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); } @EventListener public void ocppTransactionEnded(OcppTransactionEnded notification) { - String subject = format("Transaction '%s' has ended on charging station '%s'", + String subject = format("Transaction '%s' has ended on charging station '%s'", notification.getParams().getTransactionId(), notification.getParams().getChargeBoxId() ); @@ -326,14 +343,16 @@ private void userNotificationActionTransactionEnded(OcppTransactionEnded notific } // mail to user - if (!Strings.isNullOrEmpty(eMailAddress)) { - // if the Transactionstop is received within the first Minute don't send an E-Mail - if (transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { - mailService.sendAsync(subject, - addTimestamp(createContent(transActParams, userRecord)), - eMailAddress - ); - } + if (Strings.isNullOrEmpty(eMailAddress)) { + return; + } + + // if the Transactionstop is received within the first Minute don't send an E-Mail + if (transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { + mailService.sendAsync(subject, + addTimestamp(createContent(transActParams, userRecord)), + eMailAddress + ); } } From 7a5dba7d3b0635945ef9879668a96e78dbe60b20 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Sat, 26 Apr 2025 19:53:03 +0200 Subject: [PATCH 37/52] MailService: changed eMailAdresses form string to List --- .../rwth/idsg/steve/service/MailService.java | 5 ++-- .../steve/service/MailServiceDefault.java | 20 ++++++------- .../steve/service/NotificationService.java | 28 ++++++++++++------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/MailService.java b/src/main/java/de/rwth/idsg/steve/service/MailService.java index 3e8944bfc..d08ac89bf 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailService.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailService.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.service; import de.rwth.idsg.steve.repository.dto.MailSettings; +import java.util.List; /** * @author Sevket Goekay @@ -31,6 +32,6 @@ public interface MailService { void sendTestMail(); void sendAsync(String subject, String body); - - void sendAsync(String subject, String body, String recipientAddresses); + + void sendAsync(String subject, String body, List eMailAddresses); } diff --git a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java index de7a0b2dd..10a50747f 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java @@ -39,8 +39,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; import static de.rwth.idsg.steve.utils.StringUtils.isValidAddress; +import java.util.ArrayList; import java.util.List; /** @@ -62,7 +62,8 @@ public MailSettings getSettings() { @Override public void sendTestMail() { try { - send("Test", "Test", ""); + List noAddress = new ArrayList(); + send("Test", "Test", noAddress); } catch (MessagingException e) { throw new SteveException("Failed to send mail", e); } @@ -72,7 +73,8 @@ public void sendTestMail() { public void sendAsync(String subject, String body) { asyncTaskExecutor.execute(() -> { try { - send(subject, body, ""); + List noAddress = new ArrayList(); + send(subject, body, noAddress); } catch (MessagingException e) { log.error("Failed to send mail", e); } @@ -80,17 +82,17 @@ public void sendAsync(String subject, String body) { } @Override - public void sendAsync(String subject, String body, String recipientAddresses) { + public void sendAsync(String subject, String body, List eMailAddresses) { asyncTaskExecutor.execute(() -> { try { - send(subject, body, recipientAddresses); + send(subject, body, eMailAddresses); } catch (MessagingException e) { log.error("Failed to send mail", e); } }); } - private void send(String subject, String body, String recipientAddresses) throws MessagingException { + private void send(String subject, String body, List eMailAddresses) throws MessagingException { MailSettings settings = getSettings(); Session session = createSession(settings); @@ -99,12 +101,8 @@ private void send(String subject, String body, String recipientAddresses) throws mail.setContent(body, "text/plain"); mail.setFrom(new InternetAddress(settings.getFrom())); - List eMailAddresses; - - if (recipientAddresses.isEmpty()) { + if (eMailAddresses.isEmpty()) { eMailAddresses = settings.getRecipients(); - } else { - eMailAddresses = splitByComma(recipientAddresses); } for (String rep : eMailAddresses) { diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index c768d28de..56214f877 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -49,6 +49,8 @@ import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import static java.lang.String.format; +import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; +import java.util.List; import jooq.steve.db.tables.records.UserRecord; /** @@ -159,6 +161,7 @@ private void userNotificationOcppStationStatusFailure(OcppStationStatusFailure n return; } + List eMailAddressList = splitByComma(eMailAddress); // send email if user with eMail address found String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", @@ -168,7 +171,7 @@ private void userNotificationOcppStationStatusFailure(OcppStationStatusFailure n notification.getChargeBoxId(), notification.getErrorCode() ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddressList); } @EventListener @@ -219,6 +222,7 @@ private void userNotificationOcppTransactionStarted(OcppTransactionStarted notif return; } + List eMailAddressList = splitByComma(eMailAddress); // send email if user with eMail address found String bodyUserMail = format("User: '%s' '%s' started transaction '%d' on connector '%s' of charging station '%s'", @@ -228,7 +232,7 @@ private void userNotificationOcppTransactionStarted(OcppTransactionStarted notif notification.getParams().getConnectorId(), notification.getParams().getChargeBoxId() ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddressList); } @EventListener @@ -291,6 +295,7 @@ private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV noti return; } + List eMailAddressList = splitByComma(eMailAddress); // send email if user with eMail address found String bodyUserMail = format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", @@ -299,7 +304,7 @@ private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV noti notification.getConnectorId(), notification.getChargeBoxId() ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddress); + mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddressList); } @EventListener @@ -332,6 +337,10 @@ private void userNotificationActionTransactionEnded(OcppTransactionEnded notific Transaction transActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); + // if the Transactionstop is received within the first Minute don't send an E-Mail + if (!transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { + return; + } try { userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); if (userRecord.getUserNotificationFeatures() @@ -347,13 +356,12 @@ private void userNotificationActionTransactionEnded(OcppTransactionEnded notific return; } - // if the Transactionstop is received within the first Minute don't send an E-Mail - if (transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { - mailService.sendAsync(subject, - addTimestamp(createContent(transActParams, userRecord)), - eMailAddress - ); - } + List eMailAddressList = splitByComma(eMailAddress); + + mailService.sendAsync(subject, + addTimestamp(createContent(transActParams, userRecord)), + eMailAddressList + ); } // ------------------------------------------------------------------------- From d28937327c10728a731ecaf604db0a83aeece123 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:08:25 +0200 Subject: [PATCH 38/52] style check improvements --- .../rwth/idsg/steve/repository/TransactionRepository.java | 2 +- .../idsg/steve/repository/dto/UserNotificationFeature.java | 5 +++-- .../steve/repository/impl/TransactionRepositoryImpl.java | 6 +++--- .../de/rwth/idsg/steve/service/NotificationService.java | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index 68a49abf5..840c2cfff 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -31,7 +31,7 @@ */ public interface TransactionRepository { Transaction getTransaction(int transactionPk); - + Transaction getActiveTransaction(String chargeBoxId, Integer connectorId); List getTransactions(TransactionQueryForm form); diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java index 247251485..9616d0070 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java @@ -39,7 +39,8 @@ public enum UserNotificationFeature { // Ocpp related // - //OcppStationBooted(" a charging station sends a boot notification (Note: This activates notifications about failed connection attempts for unregistered JSON stations, as well)"), + //OcppStationBooted(" a charging station sends a boot notification (Note: This activates notifications " + // + "about failed connection attempts for unregistered JSON stations, as well)"), OcppStationStatusFailure(" a connector gets faulted"), //OcppStationWebSocketConnected(" a JSON charging station connects"), //OcppStationWebSocketDisconnected(" a JSON charging station disconnects"), @@ -69,4 +70,4 @@ public static List splitFeatures(String str) { public static String joinFeatures(List enablesFeatures) { return joinByComma(enablesFeatures); } -} \ No newline at end of file +} diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 81b9e155f..9fd57b143 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -75,7 +75,7 @@ public Transaction getTransaction(int transactionPk) { return getInternal(form).fetch() .map(new TransactionMapper()).get(0); } - + @Override public Transaction getActiveTransaction(String chargeBoxId, Integer connectorId) { Transaction retVal = null; @@ -91,7 +91,7 @@ public Transaction getActiveTransaction(String chargeBoxId, Integer connectorId) TransactionMapper mapper = new TransactionMapper(); retVal = mapper.map(transactionRecord); } - return retVal; + return retVal; } @Override @@ -315,7 +315,7 @@ private SelectQuery addConditions(SelectQuery selectQuery, TransactionQueryForm if (form.isConnectorId()) { selectQuery.addConditions(CONNECTOR.CONNECTOR_ID.eq(form.getConnectorId())); } - + if (form.isOcppIdTagSet()) { selectQuery.addConditions(TRANSACTION.ID_TAG.eq(form.getOcppIdTag())); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 56214f877..69d21db17 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -265,10 +265,11 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV notification, String subject) { - Transaction transaction= transactionRepository.getActiveTransaction(notification.getChargeBoxId(), + Transaction transaction = transactionRepository.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); if (transaction == null) { - return;} + return; + } String ocppTag = transaction.getOcppIdTag(); if (ocppTag == null) { From a29b5947f001ae4500a4f9232dc4f104efbb7049 Mon Sep 17 00:00:00 2001 From: fnkbsi <135032168+fnkbsi@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:18:25 +0200 Subject: [PATCH 39/52] Update Licence --- .../idsg/steve/repository/dto/UserNotificationFeature.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java index 9616d0070..63f5d9904 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java @@ -1,6 +1,6 @@ /* * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2024 SteVe Community Team + * Copyright (C) 2013-2025 SteVe Community Team * All Rights Reserved. * * This program is free software: you can redistribute it and/or modify @@ -19,10 +19,6 @@ package de.rwth.idsg.steve.repository.dto; -//import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; -//import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; -//import java.util.List; -//import java.util.stream.Collectors; import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; import java.util.List; From 7b0d4a8f45a0e73e704d3c51eeb9001f19601b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Thu, 2 Oct 2025 15:46:36 +0200 Subject: [PATCH 40/52] rename sql migration file --- .../db/migration/{V1_0_8__update.sql => V1_0_9__update.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V1_0_8__update.sql => V1_0_9__update.sql} (100%) diff --git a/src/main/resources/db/migration/V1_0_8__update.sql b/src/main/resources/db/migration/V1_0_9__update.sql similarity index 100% rename from src/main/resources/db/migration/V1_0_8__update.sql rename to src/main/resources/db/migration/V1_0_9__update.sql From af66951f56dd417b3012f7ef68f2fb505c10aede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Thu, 2 Oct 2025 23:30:05 +0200 Subject: [PATCH 41/52] simplify: remove isValidAddress method --- .../idsg/steve/service/MailServiceDefault.java | 8 +------- .../de/rwth/idsg/steve/utils/StringUtils.java | 16 ---------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java index 260845e17..1b64acde0 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java @@ -41,8 +41,6 @@ import java.util.List; import java.util.Properties; -import static de.rwth.idsg.steve.utils.StringUtils.isValidAddress; - /** * @author Sevket Goekay * @since 24.01.2016 @@ -107,11 +105,7 @@ private void send(String subject, String body, List eMailAddresses) thro } for (String rep : eMailAddresses) { - if (isValidAddress(rep)) { - mail.addRecipient(Message.RecipientType.TO, new InternetAddress(rep)); - } else { - log.error("Failed to send mail to " + rep + "! Format of the address is invalid."); - } + mail.addRecipient(Message.RecipientType.TO, new InternetAddress(rep)); } try (Transport transport = session.getTransport()) { diff --git a/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java b/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java index bab666c98..89e3c4fff 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java +++ b/src/main/java/de/rwth/idsg/steve/utils/StringUtils.java @@ -32,7 +32,6 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; -import java.util.regex.Pattern; /** * @author Sevket Goekay @@ -96,19 +95,4 @@ public static String getLastBitFromUrl(final String input) { return input.substring(index + substring.length()); } } - - // https://www.baeldung.com/java-email-validation-regex - public static boolean isValidAddress(String emailAddress) { - // Strict Regular Expression Validation - String regexPattern = - "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$"; - //Regular Expression for Validation of Non-Latin or Unicode Characters Email - //String regexPattern = - // "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9-]+(\\.[\\p{L}0-9-]+)*(\\.[\\p{L}]{2,})$"; - // Regular Expression by RFC 5322 for Email Validation - //String regexPattern = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$" - return Pattern.compile(regexPattern) - .matcher(emailAddress) - .matches(); - } } From d45ced3ccca67639445b56af6e817d8b00f02f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Thu, 2 Oct 2025 23:57:53 +0200 Subject: [PATCH 42/52] refactor --- .../repository/TransactionRepository.java | 4 -- .../impl/TransactionRepositoryImpl.java | 30 +-------- .../steve/service/NotificationService.java | 8 +-- .../steve/service/TransactionService.java | 64 +++++++++++++++++++ .../steve/web/dto/TransactionQueryForm.java | 4 +- 5 files changed, 71 insertions(+), 39 deletions(-) create mode 100644 src/main/java/de/rwth/idsg/steve/service/TransactionService.java diff --git a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index 840c2cfff..adb662863 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -30,10 +30,6 @@ * @since 19.08.2014 */ public interface TransactionRepository { - Transaction getTransaction(int transactionPk); - - Transaction getActiveTransaction(String chargeBoxId, Integer connectorId); - List getTransactions(TransactionQueryForm form); void writeTransactionsCSV(TransactionQueryForm form, Writer writer); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 635ec4c17..8beaeaa30 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -63,34 +63,6 @@ public class TransactionRepositoryImpl implements TransactionRepository { private final DSLContext ctx; - @Override - public Transaction getTransaction(int transactionPk) { - TransactionQueryForm form = new TransactionQueryForm(); - form.setTransactionPk(transactionPk); - form.setReturnCSV(false); - form.setType(TransactionQueryForm.QueryType.ALL); - return getInternal(form).fetch() - .map(new TransactionMapper()).get(0); - } - - @Override - public Transaction getActiveTransaction(String chargeBoxId, Integer connectorId) { - Transaction retVal = null; - TransactionQueryForm form = new TransactionQueryForm(); - form.setChargeBoxId(chargeBoxId); - form.setConnectorId(connectorId); - form.setReturnCSV(false); - form.setType(TransactionQueryForm.QueryType.ACTIVE); - Record12 - transactionRecord = getInternal(form).fetchAny(); - if (transactionRecord != null) { - TransactionMapper mapper = new TransactionMapper(); - retVal = mapper.map(transactionRecord); - } - return retVal; - } - @Override public List getTransactions(TransactionQueryForm form) { return getInternal(form).fetch() @@ -309,7 +281,7 @@ private SelectQuery addConditions(SelectQuery selectQuery, TransactionQueryForm selectQuery.addConditions(CONNECTOR.CHARGE_BOX_ID.eq(form.getChargeBoxId())); } - if (form.isConnectorId()) { + if (form.isConnectorIdSet()) { selectQuery.addConditions(CONNECTOR.CONNECTOR_ID.eq(form.getConnectorId())); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index 642311e78..c1aacfb40 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -64,7 +64,7 @@ public class NotificationService { private final MailService mailService; - private final TransactionRepository transactionRepository; + private final TransactionService transactionService; private final UserRepository userRepository; private final DelegatingTaskExecutor asyncTaskExecutor; @@ -136,7 +136,7 @@ public void ocppStationStatusFailure(OcppStationStatusFailure notification) { private void userNotificationOcppStationStatusFailure(OcppStationStatusFailure notification, String subject) { - Transaction transaction = transactionRepository.getActiveTransaction(notification.getChargeBoxId(), + Transaction transaction = transactionService.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); if (transaction == null) { return; @@ -267,7 +267,7 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notificati private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV notification, String subject) { - Transaction transaction = transactionRepository.getActiveTransaction(notification.getChargeBoxId(), + Transaction transaction = transactionService.getActiveTransaction(notification.getChargeBoxId(), notification.getConnectorId()); if (transaction == null) { return; @@ -338,7 +338,7 @@ private void userNotificationActionTransactionEnded(OcppTransactionEnded notific String eMailAddress = null; UserRecord userRecord = new UserRecord(); - Transaction transActParams = transactionRepository.getTransaction(notification.getParams().getTransactionId()); + Transaction transActParams = transactionService.getTransaction(notification.getParams().getTransactionId()); // if the Transactionstop is received within the first Minute don't send an E-Mail if (!transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { diff --git a/src/main/java/de/rwth/idsg/steve/service/TransactionService.java b/src/main/java/de/rwth/idsg/steve/service/TransactionService.java new file mode 100644 index 000000000..3ebb68614 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/service/TransactionService.java @@ -0,0 +1,64 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.service; + +import de.rwth.idsg.steve.repository.TransactionRepository; +import de.rwth.idsg.steve.repository.dto.Transaction; +import de.rwth.idsg.steve.web.dto.TransactionQueryForm; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * @author Sevket Goekay + * @since 02.10.2025 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TransactionService { + + private final TransactionRepository transactionRepository; + + public Transaction getTransaction(int transactionPk) { + TransactionQueryForm form = new TransactionQueryForm(); + form.setTransactionPk(transactionPk); + form.setReturnCSV(false); + form.setType(TransactionQueryForm.QueryType.ALL); + + return transactionRepository.getTransactions(form).getFirst(); + } + + public Transaction getActiveTransaction(String chargeBoxId, Integer connectorId) { + TransactionQueryForm form = new TransactionQueryForm(); + form.setChargeBoxId(chargeBoxId); + form.setConnectorId(connectorId); + form.setReturnCSV(false); + form.setType(TransactionQueryForm.QueryType.ACTIVE); + + var transactions = transactionRepository.getTransactions(form); + if (transactions.isEmpty()) { + return null; + } else if (transactions.size() == 1) { + return transactions.get(0); + } else { + throw new IllegalStateException("There are multiple active transactions with the same charge box id and connector id"); + } + } +} diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java index 6f5ddf970..78dd7c917 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/TransactionQueryForm.java @@ -41,7 +41,7 @@ public class TransactionQueryForm extends QueryForm { private Integer transactionPk; @Schema(description = "ID of the connector") - Integer connectorId; + private Integer connectorId; @Schema(description = "Disabled for the Web APIs. Do not use and set", hidden = true) private boolean returnCSV = false; @@ -64,7 +64,7 @@ public boolean isTransactionPkSet() { } @Schema(hidden = true) - public boolean isConnectorId() { + public boolean isConnectorIdSet() { return connectorId != null; } From 9828d8b6df45bf4149ca1490a3d75219f8f1deb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Fri, 3 Oct 2025 12:20:09 +0200 Subject: [PATCH 43/52] improve user notification impl * separate user notifications from others: introduction of NotificationServiceForUser * simplify and dedup user notification logic in NotificationServiceForUser * refactor MailService: async not needed for mail for user notifications, because the whole user notification processing is done in async * UserRepository.getDetails(String ocppTag) is not needed. the use cases can be achieved via getOverview(..) --- .../idsg/steve/config/BeanConfiguration.java | 2 + .../idsg/steve/repository/UserRepository.java | 1 - .../repository/impl/UserRepositoryImpl.java | 25 -- .../rwth/idsg/steve/service/MailService.java | 6 +- .../steve/service/MailServiceDefault.java | 39 +-- .../steve/service/NotificationService.java | 284 +----------------- .../service/NotificationServiceForUser.java | 252 ++++++++++++++++ 7 files changed, 282 insertions(+), 327 deletions(-) create mode 100644 src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java diff --git a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java index e6364bc30..5fae52964 100644 --- a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java @@ -43,6 +43,7 @@ import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @@ -67,6 +68,7 @@ @Configuration @EnableWebMvc @EnableScheduling +@EnableAsync @ComponentScan("de.rwth.idsg.steve") public class BeanConfiguration implements WebMvcConfigurer { diff --git a/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java b/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java index cd991afb6..4b06f4d61 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java +++ b/src/main/java/de/rwth/idsg/steve/repository/UserRepository.java @@ -31,7 +31,6 @@ public interface UserRepository { List getOverview(UserQueryForm form); User.Details getDetails(int userPk); - User.Details getDetails(String ocppTag); void add(UserForm form); void update(UserForm form); diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index 1386bc32a..b90c332ac 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -45,7 +45,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import static de.rwth.idsg.steve.utils.CustomDSL.includes; import static jooq.steve.db.Tables.USER_OCPP_TAG; @@ -120,30 +119,6 @@ public User.Details getDetails(int userPk) { .build(); } - @Override - public User.Details getDetails(String ocppIdTag) { - Map> ocppTagEntries = getOcppTagsInternal(null, ocppIdTag); - if (CollectionUtils.isEmpty(ocppTagEntries)) { - throw new SteveException("There is no user with OcppTag '%s'", ocppIdTag); - } - - Integer userPk = ocppTagEntries.keySet().iterator().next(); - - UserRecord ur = ctx.selectFrom(USER) - .where(USER.USER_PK.equal(userPk)) - .fetchOne(); - - if (ur == null) { - throw new SteveException("There is no user with id '%s'", userPk); - } - - return User.Details.builder() - .userRecord(ur) - .address(addressRepository.get(ctx, ur.getAddressPk())) - .ocppTagEntries(ocppTagEntries.getOrDefault(userPk, List.of())) - .build(); - } - @Override public void add(UserForm form) { ctx.transaction(configuration -> { diff --git a/src/main/java/de/rwth/idsg/steve/service/MailService.java b/src/main/java/de/rwth/idsg/steve/service/MailService.java index a26ee4125..4ccb319df 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailService.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailService.java @@ -20,6 +20,8 @@ import de.rwth.idsg.steve.web.dto.SettingsForm.MailSettings; +import jakarta.mail.MessagingException; + import java.util.List; /** @@ -34,5 +36,7 @@ public interface MailService { void sendAsync(String subject, String body); - void sendAsync(String subject, String body, List eMailAddresses); + void send(String subject, String body) throws MessagingException; + + void send(String subject, String body, List eMailAddresses); } diff --git a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java index 1b64acde0..c8e91e19c 100644 --- a/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java +++ b/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java @@ -16,7 +16,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - package de.rwth.idsg.steve.service; import com.google.common.base.Strings; @@ -37,9 +36,9 @@ import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; -import java.util.ArrayList; import java.util.List; import java.util.Properties; +import java.util.function.Function; /** * @author Sevket Goekay @@ -61,8 +60,7 @@ public MailSettings getSettings() { @Override public void sendTestMail() { try { - List noAddress = new ArrayList(); - send("Test", "Test", noAddress); + send("Test", "Test"); } catch (MessagingException e) { throw new SteveException("Failed to send mail", e); } @@ -72,8 +70,7 @@ public void sendTestMail() { public void sendAsync(String subject, String body) { asyncTaskExecutor.execute(() -> { try { - List noAddress = new ArrayList(); - send(subject, body, noAddress); + send(subject, body); } catch (MessagingException e) { log.error("Failed to send mail", e); } @@ -81,17 +78,20 @@ public void sendAsync(String subject, String body) { } @Override - public void sendAsync(String subject, String body, List eMailAddresses) { - asyncTaskExecutor.execute(() -> { - try { - send(subject, body, eMailAddresses); - } catch (MessagingException e) { - log.error("Failed to send mail", e); - } - }); + public void send(String subject, String body) throws MessagingException { + sendInternal(subject, body, MailSettings::getRecipients); + } + + @Override + public void send(String subject, String body, List eMailAddresses) { + try { + sendInternal(subject, body, mailSettings -> eMailAddresses); + } catch (MessagingException e) { + log.error("Failed to send mail", e); + } } - private void send(String subject, String body, List eMailAddresses) throws MessagingException { + public void sendInternal(String subject, String body, Function> emailAddressProvider) throws MessagingException { MailSettings settings = getSettings(); Session session = createSession(settings); @@ -100,11 +100,7 @@ private void send(String subject, String body, List eMailAddresses) thro mail.setContent(body, "text/plain"); mail.setFrom(new InternetAddress(settings.getFrom())); - if (eMailAddresses.isEmpty()) { - eMailAddresses = settings.getRecipients(); - } - - for (String rep : eMailAddresses) { + for (String rep : emailAddressProvider.apply(settings)) { mail.addRecipient(Message.RecipientType.TO, new InternetAddress(rep)); } @@ -112,9 +108,6 @@ private void send(String subject, String body, List eMailAddresses) thro transport.connect(); transport.sendMessage(mail, mail.getAllRecipients()); } - catch (Exception e) { - log.error("Failed to send mail(s)! ", e); - } } // ------------------------------------------------------------------------- diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java index c1aacfb40..14ea477e0 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationService.java @@ -20,38 +20,27 @@ import com.google.common.base.Strings; import de.rwth.idsg.steve.NotificationFeature; -import de.rwth.idsg.steve.config.DelegatingTaskExecutor; -import de.rwth.idsg.steve.repository.TransactionRepository; -import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.InsertTransactionParams; -import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.UpdateTransactionParams; -import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.service.notification.OccpStationBooted; import de.rwth.idsg.steve.service.notification.OcppStationStatusFailure; -import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import de.rwth.idsg.steve.service.notification.OcppStationWebSocketConnected; import de.rwth.idsg.steve.service.notification.OcppStationWebSocketDisconnected; import de.rwth.idsg.steve.service.notification.OcppTransactionEnded; import de.rwth.idsg.steve.service.notification.OcppTransactionStarted; import de.rwth.idsg.steve.web.dto.SettingsForm.MailSettings; -import jooq.steve.db.tables.records.UserRecord; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import java.util.List; - import static de.rwth.idsg.steve.NotificationFeature.OcppStationBooted; import static de.rwth.idsg.steve.NotificationFeature.OcppStationStatusFailure; -import static de.rwth.idsg.steve.NotificationFeature.OcppStationStatusSuspendedEV; import static de.rwth.idsg.steve.NotificationFeature.OcppStationWebSocketConnected; import static de.rwth.idsg.steve.NotificationFeature.OcppStationWebSocketDisconnected; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionEnded; import static de.rwth.idsg.steve.NotificationFeature.OcppTransactionStarted; -import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; import static java.lang.String.format; /** @@ -64,9 +53,6 @@ public class NotificationService { private final MailService mailService; - private final TransactionService transactionService; - private final UserRepository userRepository; - private final DelegatingTaskExecutor asyncTaskExecutor; @EventListener public void ocppStationBooted(OccpStationBooted notification) { @@ -77,8 +63,7 @@ public void ocppStationBooted(OccpStationBooted notification) { String subject = format("Received boot notification from '%s'", notification.getChargeBoxId()); String body; if (notification.getStatus().isPresent()) { - body = format("Charging station '%s' is in database and has registration status '%s'.", - notification.getChargeBoxId(), notification.getStatus().get().value()); + body = format("Charging station '%s' is in database and has registration status '%s'.", notification.getChargeBoxId(), notification.getStatus().get().value()); } else { body = format("Charging station '%s' is NOT in database", notification.getChargeBoxId()); } @@ -110,261 +95,36 @@ public void ocppStationWebSocketDisconnected(OcppStationWebSocketDisconnected no @EventListener public void ocppStationStatusFailure(OcppStationStatusFailure notification) { - String subject = format("Connector '%s' of charging station '%s' is FAULTED", - notification.getConnectorId(), - notification.getChargeBoxId() - ); - - // user mail in separate task, so database queries don't block the execution - asyncTaskExecutor.execute(() -> { - try { - userNotificationOcppStationStatusFailure(notification, subject); - } catch (Exception e) { - log.error("Failed to execute the user notification of ocppStationStatusFailure.", e); - } - }); - - /* mail defined in settings */ if (isDisabled(OcppStationStatusFailure)) { return; } + String subject = format("Connector '%s' of charging station '%s' is FAULTED", notification.getConnectorId(), notification.getChargeBoxId()); String body = format("Status Error Code: '%s'", notification.getErrorCode()); mailService.sendAsync(subject, addTimestamp(body)); } - private void userNotificationOcppStationStatusFailure(OcppStationStatusFailure notification, String subject) { - - Transaction transaction = transactionService.getActiveTransaction(notification.getChargeBoxId(), - notification.getConnectorId()); - if (transaction == null) { - return; - } - - String ocppTag = transaction.getOcppIdTag(); - if (ocppTag == null) { - return; - } - - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppStationStatusFailure.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); - } - - if (Strings.isNullOrEmpty(eMailAddress)) { - return; - } - - List eMailAddressList = splitByComma(eMailAddress); - // send email if user with eMail address found - String bodyUserMail = - format("User: %s %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId(), - notification.getErrorCode() - ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddressList); - } - @EventListener public void ocppTransactionStarted(OcppTransactionStarted notification) { - String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", - notification.getTransactionId(), - notification.getParams().getChargeBoxId(), - notification.getParams().getConnectorId() - ); - - // user mail in separate task, so database queries don't block the execution - asyncTaskExecutor.execute(() -> { - try { - userNotificationOcppTransactionStarted(notification, subject); - } catch (Exception e) { - log.error("Failed to execute the user notification of ocppStationStatusFailure.", e); - } - }); - - /* mail defined in settings */ if (isDisabled(OcppTransactionStarted)) { return; } - mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); - } - - private void userNotificationOcppTransactionStarted(OcppTransactionStarted notification, String subject) { - - String ocppTag = notification.getParams().getIdTag(); - if (ocppTag == null) { - return; - } - - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppTransactionStarted.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (StationStatusFailure). User not found! " + e.getMessage()); - } - - if (Strings.isNullOrEmpty(eMailAddress)) { - return; - } - - List eMailAddressList = splitByComma(eMailAddress); - // send email if user with eMail address found - String bodyUserMail = - format("User: '%s' '%s' started transaction '%d' on connector '%s' of charging station '%s'", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getTransactionId(), - notification.getParams().getConnectorId(), - notification.getParams().getChargeBoxId() - ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddressList); - } - - @EventListener - public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV notification) { - String subject = format("EV stopped charging at charging station %s, Connector %d", - notification.getChargeBoxId(), - notification.getConnectorId() - ); - - // user mail in separate task, so database queries don't block the execution - asyncTaskExecutor.execute(() -> { - try { - userNotificationActionSuspendedEV(notification, subject); - } catch (Exception e) { - log.error("Failed to execute the user notification of SuspendedEV", e); - } - }); - - /* mail defined in settings */ - if (isDisabled(OcppStationStatusSuspendedEV)) { - return; - } - - String body = format("Connector %d of charging station %s notifies Suspended_EV", - notification.getConnectorId(), - notification.getChargeBoxId() - ); - mailService.sendAsync(subject, addTimestamp(body)); - } - - private void userNotificationActionSuspendedEV(OcppStationStatusSuspendedEV notification, String subject) { - - Transaction transaction = transactionService.getActiveTransaction(notification.getChargeBoxId(), - notification.getConnectorId()); - if (transaction == null) { - return; - } - - String ocppTag = transaction.getOcppIdTag(); - if (ocppTag == null) { - return;} - - // No mail directly after the start of the transaction, - if (!notification.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { - return; - } + String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", notification.getTransactionId(), notification.getParams().getChargeBoxId(), notification.getParams().getConnectorId()); - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - try { - userRecord = userRepository.getDetails(ocppTag).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppStationStatusSuspendedEV.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (SuspendedEV). User not found! " + e.getMessage()); - } - - if (Strings.isNullOrEmpty(eMailAddress)) { - return; - } - - List eMailAddressList = splitByComma(eMailAddress); - // send email if user with eMail address found - String bodyUserMail = - format("User: %s %s \n\n Connector %d of charging station %s notifies Suspended_EV", - userRecord.getFirstName(), - userRecord.getLastName(), - notification.getConnectorId(), - notification.getChargeBoxId() - ); - mailService.sendAsync(subject, addTimestamp(bodyUserMail), eMailAddressList); + mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } @EventListener public void ocppTransactionEnded(OcppTransactionEnded notification) { - String subject = format("Transaction '%s' has ended on charging station '%s'", - notification.getParams().getTransactionId(), - notification.getParams().getChargeBoxId() - ); - - // user mail in separate task, so database queries don't block the execution - asyncTaskExecutor.execute(() -> { - try { - userNotificationActionTransactionEnded(notification, subject); - } catch (Exception e) { - log.error("Failed to execute the notification of SuspendedEV", e); - } - }); - - /* mail defined in settings */ - if (isDisabled(OcppTransactionEnded)) { - return; - } - - mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); - } - - private void userNotificationActionTransactionEnded(OcppTransactionEnded notification, String subject) { - String eMailAddress = null; - UserRecord userRecord = new UserRecord(); - - Transaction transActParams = transactionService.getTransaction(notification.getParams().getTransactionId()); - - // if the Transactionstop is received within the first Minute don't send an E-Mail - if (!transActParams.getStopTimestamp().isAfter(transActParams.getStartTimestamp().plusMinutes(1))) { + if (isDisabled(OcppTransactionEnded)) { return; } - try { - userRecord = userRepository.getDetails(transActParams.getOcppIdTag()).getUserRecord(); - if (userRecord.getUserNotificationFeatures() - .contains(UserNotificationFeature.OcppTransactionEnded.toString())) { - eMailAddress = userRecord.getEMail(); - } - } catch (Exception e) { - log.error("Failed to send email (TransactionStop). User not found! " + e.getMessage()); - } - // mail to user - if (Strings.isNullOrEmpty(eMailAddress)) { - return; - } + String subject = format("Transaction '%s' has ended on charging station '%s'", notification.getParams().getTransactionId(), notification.getParams().getChargeBoxId()); - List eMailAddressList = splitByComma(eMailAddress); - - mailService.sendAsync(subject, - addTimestamp(createContent(transActParams, userRecord)), - eMailAddressList - ); + mailService.sendAsync(subject, addTimestamp(createContent(notification.getParams()))); } // ------------------------------------------------------------------------- @@ -397,36 +157,6 @@ private static String createContent(UpdateTransactionParams params) { .toString(); } - private static String createContent(Transaction params, UserRecord userRecord) { - Double meterValueDiff; - Integer meterValueStop; - Integer meterValueStart; - String strMeterValueDiff = "-"; - try { - meterValueStop = Integer.valueOf(params.getStopValue()); - meterValueStart = Integer.valueOf(params.getStartValue()); - meterValueDiff = (meterValueStop - meterValueStart) / 1000.0; // --> kWh - strMeterValueDiff = meterValueDiff.toString() + " kWh"; - } catch (NumberFormatException e) { - log.error("Failed to calculate charged energy! ", e); - } - - return new StringBuilder("User: ") - .append(userRecord.getFirstName()).append(" ").append(userRecord.getLastName()) - .append(System.lineSeparator()) - .append(System.lineSeparator()) - .append("Details:").append(System.lineSeparator()) - .append("- chargeBoxId: ").append(params.getChargeBoxId()).append(System.lineSeparator()) - .append("- connectorId: ").append(params.getConnectorId()).append(System.lineSeparator()) - .append("- transactionId: ").append(params.getId()).append(System.lineSeparator()) - .append("- startTimestamp (UTC): ").append(params.getStartTimestamp()).append(System.lineSeparator()) - .append("- startMeterValue: ").append(params.getStartValue()).append(System.lineSeparator()) - .append("- stopTimestamp (UTC): ").append(params.getStopTimestamp()).append(System.lineSeparator()) - .append("- stopMeterValue: ").append(params.getStopValue()).append(System.lineSeparator()) - .append("- stopReason: ").append(params.getStopReason()).append(System.lineSeparator()) - .append("- charged energy: ").append(strMeterValueDiff).append(System.lineSeparator()) - .toString(); - } private boolean isDisabled(NotificationFeature f) { MailSettings settings = mailService.getSettings(); diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java new file mode 100644 index 000000000..89c6947ce --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -0,0 +1,252 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.service; + +import com.google.common.base.Strings; +import de.rwth.idsg.steve.repository.UserRepository; +import de.rwth.idsg.steve.repository.dto.Transaction; +import de.rwth.idsg.steve.repository.dto.User; +import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; +import de.rwth.idsg.steve.service.notification.OcppStationStatusFailure; +import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; +import de.rwth.idsg.steve.service.notification.OcppTransactionEnded; +import de.rwth.idsg.steve.service.notification.OcppTransactionStarted; +import de.rwth.idsg.steve.web.dto.UserQueryForm; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.joda.time.DateTime; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static java.lang.String.format; + +/** + * @author Sevket Goekay + * @since 02.10.2025 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class NotificationServiceForUser { + + private final MailService mailService; + private final TransactionService transactionService; + private final UserRepository userRepository; + + @Async + @EventListener + public void ocppStationStatusFailure(OcppStationStatusFailure event) { + String subject = format("Connector '%s' of charging station '%s' is FAULTED", + event.getConnectorId(), + event.getChargeBoxId() + ); + + var transaction = transactionService.getActiveTransaction(event.getChargeBoxId(), event.getConnectorId()); + if (transaction == null) { + return; + } + + var user = getUserForMail(transaction.getOcppIdTag(), UserNotificationFeature.OcppStationStatusFailure); + if (user == null) { + return; + } + + // send email if user with eMail address found + String bodyUserMail = + format("User: %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", + user.getName(), + event.getConnectorId(), + event.getChargeBoxId(), + event.getErrorCode() + ); + mailService.send(subject, addTimestamp(bodyUserMail), List.of(user.getEmail())); + } + + @Async + @EventListener + public void ocppTransactionStarted(OcppTransactionStarted event) { + String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", + event.getTransactionId(), + event.getParams().getChargeBoxId(), + event.getParams().getConnectorId() + ); + + var user = getUserForMail(event.getParams().getIdTag(), UserNotificationFeature.OcppTransactionStarted); + if (user == null) { + return; + } + + // send email if user with eMail address found + String bodyUserMail = + format("User: %s started transaction '%d' on connector '%s' of charging station '%s'", + user.getName(), + event.getTransactionId(), + event.getParams().getConnectorId(), + event.getParams().getChargeBoxId() + ); + mailService.send(subject, addTimestamp(bodyUserMail), List.of(user.getEmail())); + } + + @Async + @EventListener + public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { + String subject = format("EV stopped charging at charging station %s, Connector %d", + event.getChargeBoxId(), + event.getConnectorId() + ); + + var transaction = transactionService.getActiveTransaction(event.getChargeBoxId(), event.getConnectorId()); + if (transaction == null) { + return; + } + + // No mail directly after the start of the transaction, + if (!event.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { + return; + } + + var user = getUserForMail(transaction.getOcppIdTag(), UserNotificationFeature.OcppStationStatusSuspendedEV); + if (user == null) { + return; + } + + // send email if user with eMail address found + String bodyUserMail = + format("User: %s \n\n Connector %d of charging station %s notifies Suspended_EV", + user.getName(), + event.getConnectorId(), + event.getChargeBoxId() + ); + mailService.send(subject, addTimestamp(bodyUserMail), List.of(user.getEmail())); + } + + @Async + @EventListener + public void ocppTransactionEnded(OcppTransactionEnded event) { + String subject = format("Transaction '%s' has ended on charging station '%s'", + event.getParams().getTransactionId(), + event.getParams().getChargeBoxId() + ); + + var transaction = transactionService.getTransaction(event.getParams().getTransactionId()); + + // if the TransactionStop is received within the first Minute don't send an E-Mail + if (!transaction.getStopTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { + return; + } + + var user = getUserForMail(transaction.getOcppIdTag(), UserNotificationFeature.OcppTransactionEnded); + if (user == null) { + return; + } + + mailService.send(subject, addTimestamp(createContent(transaction, user)), List.of(user.getEmail())); + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + private User.Overview getUserForMail(String ocppIdTag, UserNotificationFeature feature) { + UserQueryForm form = new UserQueryForm(); + form.setOcppIdTag(ocppIdTag); + + List overview = userRepository.getOverview(form); + if (overview.isEmpty()) { + return null; + } else if (overview.size() > 1) { + // should not happen + log.warn("Multiple users found for OcppTag {}", ocppIdTag); + return null; + } + + var user = overview.get(0); + if (!hasOcppTag(user, ocppIdTag)) { + return null; + } + + String eMailAddress = user.getEmail(); + if (Strings.isNullOrEmpty(eMailAddress)) { + return null; + } + + if (!user.getEnabledFeatures().contains(feature)) { + return null; + } + + return user; + } + + /** + * We check this here again, because userRepository.getOverview(..) also returns partial match OcppTags. + */ + private static boolean hasOcppTag(User.Overview user, String ocppIdTag) { + for (User.OcppTagEntry ocppTagEntry : user.getOcppTagEntries()) { + if (ocppTagEntry.getIdTag().equals(ocppIdTag)) { + return true; + } + } + return false; + } + + private static String createContent(Transaction params, User.Overview user) { + Double meterValueDiff; + Integer meterValueStop; + Integer meterValueStart; + String strMeterValueDiff = "-"; + try { + meterValueStop = Integer.valueOf(params.getStopValue()); + meterValueStart = Integer.valueOf(params.getStartValue()); + meterValueDiff = (meterValueStop - meterValueStart) / 1000.0; // --> kWh + strMeterValueDiff = meterValueDiff.toString() + " kWh"; + } catch (NumberFormatException e) { + log.error("Failed to calculate charged energy! ", e); + } + + return new StringBuilder("User: ") + .append(user.getName()) + .append(System.lineSeparator()) + .append(System.lineSeparator()) + .append("Details:").append(System.lineSeparator()) + .append("- chargeBoxId: ").append(params.getChargeBoxId()).append(System.lineSeparator()) + .append("- connectorId: ").append(params.getConnectorId()).append(System.lineSeparator()) + .append("- transactionId: ").append(params.getId()).append(System.lineSeparator()) + .append("- startTimestamp (UTC): ").append(params.getStartTimestamp()).append(System.lineSeparator()) + .append("- startMeterValue: ").append(params.getStartValue()).append(System.lineSeparator()) + .append("- stopTimestamp (UTC): ").append(params.getStopTimestamp()).append(System.lineSeparator()) + .append("- stopMeterValue: ").append(params.getStopValue()).append(System.lineSeparator()) + .append("- stopReason: ").append(params.getStopReason()).append(System.lineSeparator()) + .append("- charged energy: ").append(strMeterValueDiff).append(System.lineSeparator()) + .toString(); + } + + private static String addTimestamp(String body) { + String eventTs = "Timestamp of the event: " + DateTime.now(); + String newLine = System.lineSeparator() + System.lineSeparator(); + + if (Strings.isNullOrEmpty(body)) { + return eventTs; + } else { + return body + newLine + "--" + newLine + eventTs; + } + } +} From 32e93e9ee73282175ef491131329939f06b67cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Fri, 3 Oct 2025 13:40:18 +0200 Subject: [PATCH 44/52] merge UserNotificationFeature into NotificationFeature .. by adding a boolean flag to express that this feature is also for user --- .../rwth/idsg/steve/NotificationFeature.java | 44 ++++++++++-- .../rwth/idsg/steve/repository/dto/User.java | 3 +- .../dto/UserNotificationFeature.java | 69 ------------------- .../repository/impl/UserRepositoryImpl.java | 8 +-- .../service/NotificationServiceForUser.java | 12 ++-- .../steve/utils/mapper/UserFormMapper.java | 4 +- .../steve/web/controller/UsersController.java | 8 +-- .../de/rwth/idsg/steve/web/dto/UserForm.java | 5 +- 8 files changed, 57 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java diff --git a/src/main/java/de/rwth/idsg/steve/NotificationFeature.java b/src/main/java/de/rwth/idsg/steve/NotificationFeature.java index a5a9a4360..8854826a9 100644 --- a/src/main/java/de/rwth/idsg/steve/NotificationFeature.java +++ b/src/main/java/de/rwth/idsg/steve/NotificationFeature.java @@ -21,6 +21,13 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; +import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; + /** * @author Sevket Goekay * @since 22.01.2016 @@ -30,18 +37,31 @@ public enum NotificationFeature { // Ocpp related // - OcppStationBooted(" a charging station sends a boot notification (Note: This activates notifications about failed connection attempts for unregistered JSON stations, as well)"), - OcppStationStatusFailure(" a connector gets faulted"), - OcppStationWebSocketConnected(" a JSON charging station connects"), - OcppStationWebSocketDisconnected(" a JSON charging station disconnects"), - OcppTransactionStarted(" a charging station starts a transaction"), - OcppStationStatusSuspendedEV(" a EV suspended charging"), - OcppTransactionEnded(" a charging station ends a transaction"); + OcppStationBooted(false, " a charging station sends a boot notification (Note: This activates notifications about failed connection attempts for unregistered JSON stations, as well)"), + OcppStationStatusFailure(true, " a connector gets faulted"), + OcppStationWebSocketConnected(false, " a JSON charging station connects"), + OcppStationWebSocketDisconnected(false, " a JSON charging station disconnects"), + OcppTransactionStarted(true, " a charging station starts a transaction"), + OcppStationStatusSuspendedEV(true, " a EV suspended charging"), + OcppTransactionEnded(true, " a charging station ends a transaction"); + private static final List userNotificationFeatures = Arrays.stream(NotificationFeature.values()) + .filter(NotificationFeature::isForUser) + .collect(Collectors.toList()); + + /** + * Whether this notification type is intended for end-users as well. + */ + @Getter + private final boolean forUser; @Getter private final String text; + public static List getUserValues() { + return userNotificationFeatures; + } + public static NotificationFeature fromName(String v) { for (NotificationFeature c: NotificationFeature.values()) { if (c.name().equalsIgnoreCase(v)) { @@ -50,4 +70,14 @@ public static NotificationFeature fromName(String v) { } throw new IllegalArgumentException(v); } + + public static List splitFeatures(String str) { + return splitByComma(str).stream() + .map(NotificationFeature::fromName) + .collect(Collectors.toList()); + } + + public static String joinFeatures(List enablesFeatures) { + return joinByComma(enablesFeatures); + } } diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/User.java b/src/main/java/de/rwth/idsg/steve/repository/dto/User.java index d8a1d98a6..957f4ccdc 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/User.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/User.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.repository.dto; +import de.rwth.idsg.steve.NotificationFeature; import jooq.steve.db.tables.records.AddressRecord; import jooq.steve.db.tables.records.UserRecord; import lombok.Builder; @@ -38,7 +39,7 @@ public static final class Overview { private final Integer userPk; private final String name, phone, email; private final List ocppTagEntries; - private final List enabledFeatures; + private final List enabledFeatures; } @Getter diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java b/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java deleted file mode 100644 index 63f5d9904..000000000 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/UserNotificationFeature.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2025 SteVe Community Team - * All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package de.rwth.idsg.steve.repository.dto; - -import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; -import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; -import java.util.List; -import java.util.stream.Collectors; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * - * @author fnkbsi - */ -@RequiredArgsConstructor -public enum UserNotificationFeature { - - // Ocpp related - // - //OcppStationBooted(" a charging station sends a boot notification (Note: This activates notifications " - // + "about failed connection attempts for unregistered JSON stations, as well)"), - OcppStationStatusFailure(" a connector gets faulted"), - //OcppStationWebSocketConnected(" a JSON charging station connects"), - //OcppStationWebSocketDisconnected(" a JSON charging station disconnects"), - OcppTransactionStarted(" a charging station starts a transaction"), - OcppStationStatusSuspendedEV(" a EV suspended charging"), - OcppTransactionEnded(" a charging station ends a transaction"); - - - @Getter - private final String text; - - public static UserNotificationFeature fromName(String v) { - for (UserNotificationFeature c: UserNotificationFeature.values()) { - if (c.name().equalsIgnoreCase(v)) { - return c; - } - } - throw new IllegalArgumentException(v); - } - - public static List splitFeatures(String str) { - return splitByComma(str).stream() - .map(UserNotificationFeature::fromName) - .collect(Collectors.toList()); - } - - public static String joinFeatures(List enablesFeatures) { - return joinByComma(enablesFeatures); - } -} diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index b90c332ac..2fcc4fb00 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -19,11 +19,11 @@ package de.rwth.idsg.steve.repository.impl; import com.google.common.base.Strings; +import de.rwth.idsg.steve.NotificationFeature; import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.repository.AddressRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.User; -import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.web.dto.UserForm; import de.rwth.idsg.steve.web.dto.UserQueryForm; import jooq.steve.db.tables.records.UserRecord; @@ -78,7 +78,7 @@ public List getOverview(UserQueryForm form) { .phone(r.value4()) .email(r.value5()) .ocppTagEntries(tags) - .enabledFeatures(UserNotificationFeature.splitFeatures(r.value6())) + .enabledFeatures(NotificationFeature.splitFeatures(r.value6())) .build(); // TODO: Improve later. This is not efficient, because we filter after fetching all results. However, this @@ -260,7 +260,7 @@ private Integer addInternal(DSLContext ctx, UserForm form, Integer addressPk) { .set(USER.E_MAIL, form.getEMail()) .set(USER.NOTE, form.getNote()) .set(USER.ADDRESS_PK, addressPk) - .set(USER.USER_NOTIFICATION_FEATURES, UserNotificationFeature.joinFeatures(form.getEnabledFeatures())) + .set(USER.USER_NOTIFICATION_FEATURES, NotificationFeature.joinFeatures(form.getEnabledFeatures())) .returning(USER.USER_PK) .fetchOne() .getUserPk(); @@ -279,7 +279,7 @@ private void updateInternal(DSLContext ctx, UserForm form, Integer addressPk) { .set(USER.E_MAIL, form.getEMail()) .set(USER.NOTE, form.getNote()) .set(USER.ADDRESS_PK, addressPk) - .set(USER.USER_NOTIFICATION_FEATURES, UserNotificationFeature.joinFeatures(form.getEnabledFeatures())) + .set(USER.USER_NOTIFICATION_FEATURES, NotificationFeature.joinFeatures(form.getEnabledFeatures())) .where(USER.USER_PK.eq(form.getUserPk())) .execute(); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index 89c6947ce..2b795dafb 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -19,10 +19,10 @@ package de.rwth.idsg.steve.service; import com.google.common.base.Strings; +import de.rwth.idsg.steve.NotificationFeature; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.User; -import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.service.notification.OcppStationStatusFailure; import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import de.rwth.idsg.steve.service.notification.OcppTransactionEnded; @@ -65,7 +65,7 @@ public void ocppStationStatusFailure(OcppStationStatusFailure event) { return; } - var user = getUserForMail(transaction.getOcppIdTag(), UserNotificationFeature.OcppStationStatusFailure); + var user = getUserForMail(transaction.getOcppIdTag(), NotificationFeature.OcppStationStatusFailure); if (user == null) { return; } @@ -90,7 +90,7 @@ public void ocppTransactionStarted(OcppTransactionStarted event) { event.getParams().getConnectorId() ); - var user = getUserForMail(event.getParams().getIdTag(), UserNotificationFeature.OcppTransactionStarted); + var user = getUserForMail(event.getParams().getIdTag(), NotificationFeature.OcppTransactionStarted); if (user == null) { return; } @@ -124,7 +124,7 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { return; } - var user = getUserForMail(transaction.getOcppIdTag(), UserNotificationFeature.OcppStationStatusSuspendedEV); + var user = getUserForMail(transaction.getOcppIdTag(), NotificationFeature.OcppStationStatusSuspendedEV); if (user == null) { return; } @@ -154,7 +154,7 @@ public void ocppTransactionEnded(OcppTransactionEnded event) { return; } - var user = getUserForMail(transaction.getOcppIdTag(), UserNotificationFeature.OcppTransactionEnded); + var user = getUserForMail(transaction.getOcppIdTag(), NotificationFeature.OcppTransactionEnded); if (user == null) { return; } @@ -166,7 +166,7 @@ public void ocppTransactionEnded(OcppTransactionEnded event) { // Private helpers // ------------------------------------------------------------------------- - private User.Overview getUserForMail(String ocppIdTag, UserNotificationFeature feature) { + private User.Overview getUserForMail(String ocppIdTag, NotificationFeature feature) { UserQueryForm form = new UserQueryForm(); form.setOcppIdTag(ocppIdTag); diff --git a/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java b/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java index 5dd457737..8fdce254b 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java +++ b/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java @@ -18,8 +18,8 @@ */ package de.rwth.idsg.steve.utils.mapper; +import de.rwth.idsg.steve.NotificationFeature; import de.rwth.idsg.steve.repository.dto.User; -import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.web.dto.UserForm; import de.rwth.idsg.steve.web.dto.UserSex; import jooq.steve.db.tables.records.UserRecord; @@ -44,7 +44,7 @@ public static UserForm toForm(User.Details details) { form.setPhone(userRecord.getPhone()); form.setSex(UserSex.fromDatabaseValue(userRecord.getSex())); form.setEMail(userRecord.getEMail()); - form.setEnabledFeatures(UserNotificationFeature.splitFeatures(userRecord.getUserNotificationFeatures())); + form.setEnabledFeatures(NotificationFeature.splitFeatures(userRecord.getUserNotificationFeatures())); form.setNote(userRecord.getNote()); form.setAddress(AddressMapper.recordToDto(details.getAddress())); form.setIdTagList(details.getOcppTagEntries().stream().map(User.OcppTagEntry::getIdTag).toList()); diff --git a/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java b/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java index 4af9666d0..8abe5cff8 100644 --- a/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java +++ b/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java @@ -18,9 +18,9 @@ */ package de.rwth.idsg.steve.web.controller; +import de.rwth.idsg.steve.NotificationFeature; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.User; -import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; import de.rwth.idsg.steve.service.OcppTagService; import de.rwth.idsg.steve.utils.ControllerHelper; import de.rwth.idsg.steve.utils.mapper.UserFormMapper; @@ -84,7 +84,7 @@ public String getQuery(@ModelAttribute(PARAMS) UserQueryForm params, Model model private void initList(Model model, UserQueryForm params) { model.addAttribute(PARAMS, params); model.addAttribute("userList", userRepository.getOverview(params)); - model.addAttribute("features", UserNotificationFeature.values()); + model.addAttribute("features", NotificationFeature.getUserValues()); } @RequestMapping(value = DETAILS_PATH, method = RequestMethod.GET) @@ -93,7 +93,7 @@ public String getDetails(@PathVariable("userPk") int userPk, Model model) { UserForm form = UserFormMapper.toForm(details); model.addAttribute("userForm", form); - model.addAttribute("features", UserNotificationFeature.values()); + model.addAttribute("features", NotificationFeature.getUserValues()); setTags(model, form.getIdTagList()); return "data-man/userDetails"; } @@ -102,7 +102,7 @@ public String getDetails(@PathVariable("userPk") int userPk, Model model) { public String addGet(Model model) { setTags(model, List.of()); model.addAttribute("userForm", new UserForm()); - model.addAttribute("features", UserNotificationFeature.values()); + model.addAttribute("features", NotificationFeature.getUserValues()); return "data-man/userAdd"; } diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java index 7a1fb2a1b..2af885f36 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java @@ -18,8 +18,7 @@ */ package de.rwth.idsg.steve.web.dto; -import de.rwth.idsg.steve.repository.dto.UserNotificationFeature; -import java.util.List; +import de.rwth.idsg.steve.NotificationFeature; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -57,7 +56,7 @@ public class UserForm { @Email(message = "Not a valid e-mail address") private String eMail; - private List enabledFeatures; + private List enabledFeatures; private Address address; From 4e69bab1b797f95b63c45817729fe7594c7f57a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Fri, 3 Oct 2025 14:28:40 +0200 Subject: [PATCH 45/52] refactor --- .../service/NotificationServiceForUser.java | 15 +++------------ .../utils/TransactionStopServiceHelper.java | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index 2b795dafb..b64b9216e 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -27,6 +27,7 @@ import de.rwth.idsg.steve.service.notification.OcppStationStatusSuspendedEV; import de.rwth.idsg.steve.service.notification.OcppTransactionEnded; import de.rwth.idsg.steve.service.notification.OcppTransactionStarted; +import de.rwth.idsg.steve.utils.TransactionStopServiceHelper; import de.rwth.idsg.steve.web.dto.UserQueryForm; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -209,18 +210,8 @@ private static boolean hasOcppTag(User.Overview user, String ocppIdTag) { } private static String createContent(Transaction params, User.Overview user) { - Double meterValueDiff; - Integer meterValueStop; - Integer meterValueStart; - String strMeterValueDiff = "-"; - try { - meterValueStop = Integer.valueOf(params.getStopValue()); - meterValueStart = Integer.valueOf(params.getStartValue()); - meterValueDiff = (meterValueStop - meterValueStart) / 1000.0; // --> kWh - strMeterValueDiff = meterValueDiff.toString() + " kWh"; - } catch (NumberFormatException e) { - log.error("Failed to calculate charged energy! ", e); - } + Double consumption = TransactionStopServiceHelper.calculateEnergyConsumptionInKWh(params); + String strMeterValueDiff = (consumption == null) ? "- kWh" : consumption + " kWh"; return new StringBuilder("User: ") .append(user.getName()) diff --git a/src/main/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelper.java b/src/main/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelper.java index 0740764b3..00dda6f39 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelper.java +++ b/src/main/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelper.java @@ -19,13 +19,31 @@ package de.rwth.idsg.steve.utils; import com.google.common.base.Strings; +import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.TransactionDetails; +import lombok.extern.slf4j.Slf4j; import ocpp.cs._2015._10.Measurand; import ocpp.cs._2015._10.UnitOfMeasure; import ocpp.cs._2015._10.ValueFormat; +@Slf4j public class TransactionStopServiceHelper { + public static Double calculateEnergyConsumptionInKWh(Transaction t) { + if (t.getStopValue() == null) { + return null; // this transaction did not finish yet + } + + try { + Integer meterValueStop = Integer.valueOf(t.getStopValue()); + Integer meterValueStart = Integer.valueOf(t.getStartValue()); + return (meterValueStop - meterValueStart) / 1000.0; // --> kWh + } catch (Exception e) { + log.error("Failed to calculate charged energy", e); + return null; + } + } + public static String floatingStringToIntString(String s) { // meter values can be floating, whereas start/end values are int return Integer.toString((int) Math.ceil(Double.parseDouble(s))); From 8ea68e93361f0b48304f346fbf3bba223d9637f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20G=C3=B6kay?= Date: Mon, 6 Oct 2025 22:02:42 +0200 Subject: [PATCH 46/52] nits: remove duplicate newline --- .../de/rwth/idsg/steve/service/NotificationServiceForUser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index b64b9216e..181d41533 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -226,7 +226,7 @@ private static String createContent(Transaction params, User.Overview user) { .append("- stopTimestamp (UTC): ").append(params.getStopTimestamp()).append(System.lineSeparator()) .append("- stopMeterValue: ").append(params.getStopValue()).append(System.lineSeparator()) .append("- stopReason: ").append(params.getStopReason()).append(System.lineSeparator()) - .append("- charged energy: ").append(strMeterValueDiff).append(System.lineSeparator()) + .append("- charged energy: ").append(strMeterValueDiff) .toString(); } From 297ee81a8415b31b771cbb9c947da7866695b621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20G=C3=B6kay?= Date: Mon, 6 Oct 2025 22:56:34 +0200 Subject: [PATCH 47/52] remove "dont send mail" early-exits cannot understant their use case. maybe to prevent user flood? but then, the user would be thinking false-positively that the transaction is going on without issues even though it was stopped. it was stopped and we did not let the user know. --- .../idsg/steve/service/NotificationServiceForUser.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index 181d41533..d52604634 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -120,11 +120,6 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { return; } - // No mail directly after the start of the transaction, - if (!event.getTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { - return; - } - var user = getUserForMail(transaction.getOcppIdTag(), NotificationFeature.OcppStationStatusSuspendedEV); if (user == null) { return; @@ -150,11 +145,6 @@ public void ocppTransactionEnded(OcppTransactionEnded event) { var transaction = transactionService.getTransaction(event.getParams().getTransactionId()); - // if the TransactionStop is received within the first Minute don't send an E-Mail - if (!transaction.getStopTimestamp().isAfter(transaction.getStartTimestamp().plusMinutes(1))) { - return; - } - var user = getUserForMail(transaction.getOcppIdTag(), NotificationFeature.OcppTransactionEnded); if (user == null) { return; From 1bc9b161e23df37857ba473ecb3077c31702def7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20G=C3=B6kay?= Date: Mon, 6 Oct 2025 23:05:27 +0200 Subject: [PATCH 48/52] add debug logging --- .../steve/repository/dto/InsertTransactionParams.java | 2 ++ .../steve/repository/dto/UpdateTransactionParams.java | 2 ++ .../idsg/steve/service/NotificationServiceForUser.java | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/InsertTransactionParams.java b/src/main/java/de/rwth/idsg/steve/repository/dto/InsertTransactionParams.java index ce7376495..d7152e809 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/InsertTransactionParams.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/InsertTransactionParams.java @@ -20,6 +20,7 @@ import lombok.Builder; import lombok.Getter; +import lombok.ToString; import org.joda.time.DateTime; /** @@ -28,6 +29,7 @@ */ @Getter @Builder +@ToString public class InsertTransactionParams { private final String chargeBoxId; private final int connectorId; diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java b/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java index 231c72db1..ce9e04cf6 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java @@ -21,6 +21,7 @@ import jooq.steve.db.enums.TransactionStopEventActor; import lombok.Builder; import lombok.Getter; +import lombok.ToString; import org.joda.time.DateTime; /** @@ -29,6 +30,7 @@ */ @Getter @Builder +@ToString public class UpdateTransactionParams { private final String chargeBoxId; private final int transactionId; diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index d52604634..e0139caf8 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -56,6 +56,8 @@ public class NotificationServiceForUser { @Async @EventListener public void ocppStationStatusFailure(OcppStationStatusFailure event) { + log.debug("Processing: {}", event); + String subject = format("Connector '%s' of charging station '%s' is FAULTED", event.getConnectorId(), event.getChargeBoxId() @@ -85,6 +87,8 @@ public void ocppStationStatusFailure(OcppStationStatusFailure event) { @Async @EventListener public void ocppTransactionStarted(OcppTransactionStarted event) { + log.debug("Processing: {}", event); + String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", event.getTransactionId(), event.getParams().getChargeBoxId(), @@ -110,6 +114,8 @@ public void ocppTransactionStarted(OcppTransactionStarted event) { @Async @EventListener public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { + log.debug("Processing: {}", event); + String subject = format("EV stopped charging at charging station %s, Connector %d", event.getChargeBoxId(), event.getConnectorId() @@ -138,6 +144,8 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { @Async @EventListener public void ocppTransactionEnded(OcppTransactionEnded event) { + log.debug("Processing: {}", event); + String subject = format("Transaction '%s' has ended on charging station '%s'", event.getParams().getTransactionId(), event.getParams().getChargeBoxId() From 89d66ce9f580ca74536bcf29173ab50ec6e5637a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20G=C3=B6kay?= Date: Mon, 6 Oct 2025 23:24:28 +0200 Subject: [PATCH 49/52] refactor: apply consistent naming for the feature --- src/main/java/de/rwth/idsg/steve/repository/dto/User.java | 2 +- .../idsg/steve/repository/impl/UserRepositoryImpl.java | 8 ++++---- .../idsg/steve/service/NotificationServiceForUser.java | 2 +- .../de/rwth/idsg/steve/utils/mapper/UserFormMapper.java | 2 +- src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java | 2 +- src/main/resources/db/migration/V1_0_9__update.sql | 2 +- .../webapp/WEB-INF/views/data-man/00-user-profile.jsp | 2 +- src/main/webapp/WEB-INF/views/data-man/users.jsp | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/dto/User.java b/src/main/java/de/rwth/idsg/steve/repository/dto/User.java index 957f4ccdc..015f286ed 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/dto/User.java +++ b/src/main/java/de/rwth/idsg/steve/repository/dto/User.java @@ -39,7 +39,7 @@ public static final class Overview { private final Integer userPk; private final String name, phone, email; private final List ocppTagEntries; - private final List enabledFeatures; + private final List notificationFeatures; } @Getter diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index 2fcc4fb00..daf89803d 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -78,7 +78,7 @@ public List getOverview(UserQueryForm form) { .phone(r.value4()) .email(r.value5()) .ocppTagEntries(tags) - .enabledFeatures(NotificationFeature.splitFeatures(r.value6())) + .notificationFeatures(NotificationFeature.splitFeatures(r.value6())) .build(); // TODO: Improve later. This is not efficient, because we filter after fetching all results. However, this @@ -203,7 +203,7 @@ private Result> getOver USER.LAST_NAME, USER.PHONE, USER.E_MAIL, - USER.USER_NOTIFICATION_FEATURES) + USER.NOTIFICATION_FEATURES) .from(USER) .where(conditions) .fetch(); @@ -260,7 +260,7 @@ private Integer addInternal(DSLContext ctx, UserForm form, Integer addressPk) { .set(USER.E_MAIL, form.getEMail()) .set(USER.NOTE, form.getNote()) .set(USER.ADDRESS_PK, addressPk) - .set(USER.USER_NOTIFICATION_FEATURES, NotificationFeature.joinFeatures(form.getEnabledFeatures())) + .set(USER.NOTIFICATION_FEATURES, NotificationFeature.joinFeatures(form.getNotificationFeatures())) .returning(USER.USER_PK) .fetchOne() .getUserPk(); @@ -279,7 +279,7 @@ private void updateInternal(DSLContext ctx, UserForm form, Integer addressPk) { .set(USER.E_MAIL, form.getEMail()) .set(USER.NOTE, form.getNote()) .set(USER.ADDRESS_PK, addressPk) - .set(USER.USER_NOTIFICATION_FEATURES, NotificationFeature.joinFeatures(form.getEnabledFeatures())) + .set(USER.NOTIFICATION_FEATURES, NotificationFeature.joinFeatures(form.getNotificationFeatures())) .where(USER.USER_PK.eq(form.getUserPk())) .execute(); } diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index e0139caf8..2fd1b9198 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -188,7 +188,7 @@ private User.Overview getUserForMail(String ocppIdTag, NotificationFeature featu return null; } - if (!user.getEnabledFeatures().contains(feature)) { + if (!user.getNotificationFeatures().contains(feature)) { return null; } diff --git a/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java b/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java index 8fdce254b..2bda6432c 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java +++ b/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java @@ -44,7 +44,7 @@ public static UserForm toForm(User.Details details) { form.setPhone(userRecord.getPhone()); form.setSex(UserSex.fromDatabaseValue(userRecord.getSex())); form.setEMail(userRecord.getEMail()); - form.setEnabledFeatures(NotificationFeature.splitFeatures(userRecord.getUserNotificationFeatures())); + form.setNotificationFeatures(NotificationFeature.splitFeatures(userRecord.getNotificationFeatures())); form.setNote(userRecord.getNote()); form.setAddress(AddressMapper.recordToDto(details.getAddress())); form.setIdTagList(details.getOcppTagEntries().stream().map(User.OcppTagEntry::getIdTag).toList()); diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java index 2af885f36..b5c26df7c 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java @@ -56,7 +56,7 @@ public class UserForm { @Email(message = "Not a valid e-mail address") private String eMail; - private List enabledFeatures; + private List notificationFeatures; private Address address; diff --git a/src/main/resources/db/migration/V1_0_9__update.sql b/src/main/resources/db/migration/V1_0_9__update.sql index 2cfb2206b..b340783e6 100644 --- a/src/main/resources/db/migration/V1_0_9__update.sql +++ b/src/main/resources/db/migration/V1_0_9__update.sql @@ -1,2 +1,2 @@ ALTER TABLE `user` - ADD COLUMN `user_notification_features` TEXT NULL DEFAULT NULL COMMENT 'comma separated list' COLLATE 'utf8mb3_unicode_ci' AFTER `e_mail`; \ No newline at end of file + ADD COLUMN `notification_features` TEXT NULL DEFAULT NULL COMMENT 'comma separated list' COLLATE 'utf8mb3_unicode_ci' AFTER `e_mail`; \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/data-man/00-user-profile.jsp b/src/main/webapp/WEB-INF/views/data-man/00-user-profile.jsp index a3a2d8421..0429e3fa4 100644 --- a/src/main/webapp/WEB-INF/views/data-man/00-user-profile.jsp +++ b/src/main/webapp/WEB-INF/views/data-man/00-user-profile.jsp @@ -35,7 +35,7 @@ E-mail: Notify when... - diff --git a/src/main/webapp/WEB-INF/views/data-man/users.jsp b/src/main/webapp/WEB-INF/views/data-man/users.jsp index f79ab96db..e817ed86c 100644 --- a/src/main/webapp/WEB-INF/views/data-man/users.jsp +++ b/src/main/webapp/WEB-INF/views/data-man/users.jsp @@ -88,7 +88,7 @@ ${cr.name} ${cr.phone} ${cr.email} - + ${eF}
From 9dff7cc15311fe062c2ae7e3b37e8564f09a19c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20G=C3=B6kay?= Date: Mon, 6 Oct 2025 23:41:28 +0200 Subject: [PATCH 50/52] add extra validation for selected features to be for users --- .../de/rwth/idsg/steve/web/dto/UserForm.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java index b5c26df7c..6329db055 100644 --- a/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java +++ b/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java @@ -19,9 +19,11 @@ package de.rwth.idsg.steve.web.dto; import de.rwth.idsg.steve.NotificationFeature; +import jakarta.validation.constraints.AssertTrue; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.apache.cxf.common.util.CollectionUtils; import org.joda.time.LocalDate; import jakarta.validation.constraints.Email; @@ -60,4 +62,19 @@ public class UserForm { private Address address; + @AssertTrue(message = "Some of the selected notification features cannot be enabled for a user") + public boolean isNotificationFeaturesForUser() { + if (CollectionUtils.isEmpty(notificationFeatures)) { + return true; + } + + for (var selectedFeature : notificationFeatures) { + if (!selectedFeature.isForUser()) { + return false; + } + } + + return true; + } + } From be663b166a980cb16c48aa509a36581693d7a693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Tue, 21 Oct 2025 10:07:27 +0200 Subject: [PATCH 51/52] fix after merging main branch --- .../idsg/steve/repository/impl/TransactionRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index 6613048b7..22d09c4ec 100644 --- a/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -290,7 +290,7 @@ private List getConditions(TransactionQueryForm form) { } if (form.isConnectorIdSet()) { - selectQuery.addConditions(CONNECTOR.CONNECTOR_ID.eq(form.getConnectorId())); + conditions.add(CONNECTOR.CONNECTOR_ID.eq(form.getConnectorId())); } if (form.isOcppIdTagSet()) { From 4ba01b00239e446668007ef4927251744b47e8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Tue, 21 Oct 2025 10:11:45 +0200 Subject: [PATCH 52/52] shift mail "subject" construction after early exits reason: dont do if not needed. --- .../service/NotificationServiceForUser.java | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java index 2fd1b9198..6de50f50b 100644 --- a/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java +++ b/src/main/java/de/rwth/idsg/steve/service/NotificationServiceForUser.java @@ -58,11 +58,6 @@ public class NotificationServiceForUser { public void ocppStationStatusFailure(OcppStationStatusFailure event) { log.debug("Processing: {}", event); - String subject = format("Connector '%s' of charging station '%s' is FAULTED", - event.getConnectorId(), - event.getChargeBoxId() - ); - var transaction = transactionService.getActiveTransaction(event.getChargeBoxId(), event.getConnectorId()); if (transaction == null) { return; @@ -73,6 +68,11 @@ public void ocppStationStatusFailure(OcppStationStatusFailure event) { return; } + String subject = format("Connector '%s' of charging station '%s' is FAULTED", + event.getConnectorId(), + event.getChargeBoxId() + ); + // send email if user with eMail address found String bodyUserMail = format("User: %s \n\n Connector %d of charging station %s notifies FAULTED! \n\n Error code: %s", @@ -81,6 +81,7 @@ public void ocppStationStatusFailure(OcppStationStatusFailure event) { event.getChargeBoxId(), event.getErrorCode() ); + mailService.send(subject, addTimestamp(bodyUserMail), List.of(user.getEmail())); } @@ -89,17 +90,17 @@ public void ocppStationStatusFailure(OcppStationStatusFailure event) { public void ocppTransactionStarted(OcppTransactionStarted event) { log.debug("Processing: {}", event); - String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", - event.getTransactionId(), - event.getParams().getChargeBoxId(), - event.getParams().getConnectorId() - ); - var user = getUserForMail(event.getParams().getIdTag(), NotificationFeature.OcppTransactionStarted); if (user == null) { return; } + String subject = format("Transaction '%s' has started on charging station '%s' on connector '%s'", + event.getTransactionId(), + event.getParams().getChargeBoxId(), + event.getParams().getConnectorId() + ); + // send email if user with eMail address found String bodyUserMail = format("User: %s started transaction '%d' on connector '%s' of charging station '%s'", @@ -108,6 +109,7 @@ public void ocppTransactionStarted(OcppTransactionStarted event) { event.getParams().getConnectorId(), event.getParams().getChargeBoxId() ); + mailService.send(subject, addTimestamp(bodyUserMail), List.of(user.getEmail())); } @@ -116,11 +118,6 @@ public void ocppTransactionStarted(OcppTransactionStarted event) { public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { log.debug("Processing: {}", event); - String subject = format("EV stopped charging at charging station %s, Connector %d", - event.getChargeBoxId(), - event.getConnectorId() - ); - var transaction = transactionService.getActiveTransaction(event.getChargeBoxId(), event.getConnectorId()); if (transaction == null) { return; @@ -131,6 +128,11 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { return; } + String subject = format("EV stopped charging at charging station %s, Connector %d", + event.getChargeBoxId(), + event.getConnectorId() + ); + // send email if user with eMail address found String bodyUserMail = format("User: %s \n\n Connector %d of charging station %s notifies Suspended_EV", @@ -138,6 +140,7 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { event.getConnectorId(), event.getChargeBoxId() ); + mailService.send(subject, addTimestamp(bodyUserMail), List.of(user.getEmail())); } @@ -146,11 +149,6 @@ public void ocppStationStatusSuspendedEV(OcppStationStatusSuspendedEV event) { public void ocppTransactionEnded(OcppTransactionEnded event) { log.debug("Processing: {}", event); - String subject = format("Transaction '%s' has ended on charging station '%s'", - event.getParams().getTransactionId(), - event.getParams().getChargeBoxId() - ); - var transaction = transactionService.getTransaction(event.getParams().getTransactionId()); var user = getUserForMail(transaction.getOcppIdTag(), NotificationFeature.OcppTransactionEnded); @@ -158,6 +156,11 @@ public void ocppTransactionEnded(OcppTransactionEnded event) { return; } + String subject = format("Transaction '%s' has ended on charging station '%s'", + event.getParams().getTransactionId(), + event.getParams().getChargeBoxId() + ); + mailService.send(subject, addTimestamp(createContent(transaction, user)), List.of(user.getEmail())); }