diff --git a/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ChargePointsRestController.java b/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ChargePointsRestController.java index 45785aa1e..07503a877 100644 --- a/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ChargePointsRestController.java +++ b/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ChargePointsRestController.java @@ -76,7 +76,7 @@ public ResponseEntity create(@RequestBody @Valid ChargePointForm var body = toDto(chargePointsService.getDetails(chargepointPk)); var location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}") - .buildAndExpand(body.getChargeBoxPk()) + .buildAndExpand(chargepointPk) .toUri(); return ResponseEntity.created(location).body(body); } @@ -105,8 +105,7 @@ private static ApiChargePoint toDto(ChargePoint.Overview overview) { } private static ApiChargePoint toDto(ChargePoint.Details details) { - return new ApiChargePoint( - details.getChargeBox().getChargeBoxPk(), details.getChargeBox().getChargeBoxId()); + return new ApiChargePoint(details.getChargeBoxPk(), details.getChargeBoxId()); } @Data diff --git a/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java b/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java index 95d623811..5812355a3 100644 --- a/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java +++ b/steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java @@ -71,7 +71,7 @@ public ResponseEntity addReservation(@Valid @RequestBody Reservatio public Reservation deleteReservation(@PathVariable int id) { var reservation = reservationsService .getReservation(id) - .orElseThrow(() -> new SteveException.NotFound("Reservation with id %d not found", id)); + .orElseThrow(() -> new SteveException.NotFound("Reservation with id %d not found".formatted(id))); reservationsService.deleteReservation(id); return reservation; } diff --git a/steve-api/src/main/java/de/rwth/idsg/steve/web/api/UsersRestController.java b/steve-api/src/main/java/de/rwth/idsg/steve/web/api/UsersRestController.java index e4d060279..26a491263 100644 --- a/steve-api/src/main/java/de/rwth/idsg/steve/web/api/UsersRestController.java +++ b/steve-api/src/main/java/de/rwth/idsg/steve/web/api/UsersRestController.java @@ -63,7 +63,7 @@ public ResponseEntity addUser(@Valid @RequestBody UserForm form) { var body = usersService.add(form); var location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}") - .buildAndExpand(body.getUserRecord().getUserPk()) + .buildAndExpand(body.getUserPk()) .toUri(); return ResponseEntity.created(location).body(body); } diff --git a/steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java b/steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java index 28db21a64..2d8f51254 100644 --- a/steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java +++ b/steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java @@ -22,7 +22,6 @@ import de.rwth.idsg.steve.repository.dto.ChargePoint; import de.rwth.idsg.steve.service.ChargePointsService; import de.rwth.idsg.steve.web.dto.ChargePointForm; -import jooq.steve.db.tables.records.ChargeBoxRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -108,10 +107,10 @@ public void testGetOne_found() { } private static ChargePoint.Details createDetails(Integer pk, String chargeBoxId) { - ChargeBoxRecord cbr = new ChargeBoxRecord(); - cbr.setChargeBoxPk(pk); - cbr.setChargeBoxId(chargeBoxId); - return new ChargePoint.Details(cbr, null); + return ChargePoint.Details.builder() + .chargeBoxPk(pk) + .chargeBoxId(chargeBoxId) + .build(); } @Test @@ -161,9 +160,7 @@ public void testPut() throws Exception { @Test @DisplayName("DELETE: Entity deleted, expected 200") public void testDelete() { - var rec = new ChargeBoxRecord(); - rec.setChargeBoxPk(1); - var result = new ChargePoint.Details(rec, null); + var result = createDetails(1, "test-cb"); when(chargePointsService.getDetails(1)).thenReturn(result); assertThat(mockMvc.perform(delete("/api/v1/chargeboxes/1"))).hasStatusOk(); diff --git a/steve-api/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java b/steve-api/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java index 7dd0a4fb1..3aa07c29b 100644 --- a/steve-api/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java +++ b/steve-api/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java @@ -453,7 +453,8 @@ public void test18() throws Exception { // when when(ocppTagsService.addOcppTag(form)) - .thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk)); + .thenThrow(new SteveException.AlreadyExists( + "A user with idTag '%s' already exists.".formatted(ocppTagPk))); // then assertThat(mockMvc.perform(post("/api/v1/ocppTags") diff --git a/steve-core/pom.xml b/steve-core/pom.xml index c4357ebf0..5da02874a 100644 --- a/steve-core/pom.xml +++ b/steve-core/pom.xml @@ -10,10 +10,6 @@ steve-core - - de.rwth.idsg - steve-jooq - de.rwth.idsg steve-ocpp-1-x diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java b/steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java index 7c2f9266d..d9f8b15ee 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java @@ -18,8 +18,6 @@ */ package de.rwth.idsg.steve; -import static java.lang.String.format; - /** * @author Sevket Goekay * @since 28.08.2014 @@ -36,26 +34,6 @@ protected SteveException(String message, Throwable cause) { super(message, cause); } - // ------------------------------------------------------------------------- - // No String/variable interpolation in Java. Use format instead. - // ------------------------------------------------------------------------- - - protected SteveException(String template, Object arg1) { - this(format(template, arg1)); - } - - protected SteveException(String template, Object arg1, Throwable cause) { - this(format(template, arg1), cause); - } - - protected SteveException(String template, Object arg1, Object arg2) { - this(format(template, arg1, arg2)); - } - - protected SteveException(String template, Object arg1, Object arg2, Throwable cause) { - this(format(template, arg1, arg2), cause); - } - // ------------------------------------------------------------------------- // Custom/extending classes // ------------------------------------------------------------------------- @@ -69,28 +47,16 @@ public InternalError(String message) { public InternalError(String message, Throwable cause) { super(message, cause); } - - public InternalError(String template, Object arg1) { - this(format(template, arg1)); - } - - public InternalError(String template, Object arg1, Throwable cause) { - this(format(template, arg1), cause); - } - - public InternalError(String template, Object arg1, Object arg2) { - this(format(template, arg1, arg2)); - } - - protected InternalError(String template, Object arg1, Object arg2, Throwable cause) { - this(format(template, arg1, arg2), cause); - } } public static class AlreadyExists extends SteveException { - public AlreadyExists(String template, Object arg1) { - super(format(template, arg1)); + public AlreadyExists(String message) { + super(message); + } + + public AlreadyExists(String message, Throwable cause) { + super(message, cause); } } @@ -100,8 +66,8 @@ public NotFound(String message) { super(message); } - public NotFound(String template, Object arg1) { - this(format(template, arg1)); + public NotFound(String message, Throwable cause) { + super(message, cause); } } @@ -110,8 +76,8 @@ public BadRequest(String message) { super(message); } - public BadRequest(String template, Object arg1) { - this(format(template, arg1)); + public BadRequest(String message, Throwable cause) { + super(message, cause); } } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTaskFromDB.java b/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTaskFromDB.java index 3ff67a8f9..8621f358e 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTaskFromDB.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTaskFromDB.java @@ -22,7 +22,6 @@ import de.rwth.idsg.steve.repository.ChargingProfileRepository; import de.rwth.idsg.steve.repository.dto.ChargingProfile; import de.rwth.idsg.steve.web.dto.ocpp.SetChargingProfileParams; -import jooq.steve.db.tables.records.ChargingProfileRecord; import ocpp.cp._2015._10.ChargingProfileKindType; import ocpp.cp._2015._10.ChargingProfilePurposeType; import ocpp.cp._2015._10.ChargingRateUnitType; @@ -31,8 +30,7 @@ import ocpp.cp._2015._10.RecurrencyKindType; import ocpp.cp._2015._10.SetChargingProfileRequest; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; import static de.rwth.idsg.steve.utils.DateTimeUtils.toOffsetDateTime; @@ -64,7 +62,7 @@ public void success(String chargeBoxId, String statusValue) { addNewResponse(chargeBoxId, statusValue); if ("Accepted".equalsIgnoreCase(statusValue)) { - int chargingProfilePk = details.getProfile().getChargingProfilePk(); + var chargingProfilePk = details.getChargingProfilePk(); chargingProfileRepository.setProfile(chargingProfilePk, chargeBoxId, connectorId); } } @@ -73,9 +71,7 @@ public void success(String chargeBoxId, String statusValue) { @Override public SetChargingProfileRequest getOcpp16Request() { - ChargingProfileRecord profile = details.getProfile(); - - List schedulePeriods = details.getPeriods().stream() + var schedulePeriods = details.getPeriods().stream() .map(k -> { ChargingSchedulePeriod p = new ChargingSchedulePeriod(); p.setStartPeriod(k.getStartPeriodInSeconds()); @@ -83,26 +79,27 @@ public SetChargingProfileRequest getOcpp16Request() { p.setNumberPhases(k.getNumberPhases()); return p; }) - .collect(Collectors.toList()); + .toList(); - ChargingSchedule schedule = new ChargingSchedule() - .withDuration(profile.getDurationInSeconds()) - .withStartSchedule(toOffsetDateTime(profile.getStartSchedule())) - .withChargingRateUnit(ChargingRateUnitType.fromValue(profile.getChargingRateUnit())) - .withMinChargingRate(profile.getMinChargingRate()) + var schedule = new ChargingSchedule() + .withDuration(details.getDurationInSeconds()) + .withStartSchedule(toOffsetDateTime(details.getStartSchedule())) + .withChargingRateUnit(ChargingRateUnitType.fromValue(details.getChargingRateUnit())) + .withMinChargingRate(details.getMinChargingRate()) .withChargingSchedulePeriod(schedulePeriods); - ocpp.cp._2015._10.ChargingProfile ocppProfile = new ocpp.cp._2015._10.ChargingProfile() - .withChargingProfileId(profile.getChargingProfilePk()) - .withStackLevel(profile.getStackLevel()) - .withChargingProfilePurpose(ChargingProfilePurposeType.fromValue(profile.getChargingProfilePurpose())) - .withChargingProfileKind(ChargingProfileKindType.fromValue(profile.getChargingProfileKind())) - .withRecurrencyKind( - profile.getRecurrencyKind() == null - ? null - : RecurrencyKindType.fromValue(profile.getRecurrencyKind())) - .withValidFrom(toOffsetDateTime(profile.getValidFrom())) - .withValidTo(toOffsetDateTime(profile.getValidTo())) + var ocppProfile = new ocpp.cp._2015._10.ChargingProfile() + .withChargingProfileId(details.getChargingProfilePk()) + .withStackLevel(details.getStackLevel()) + // .withDescription(details.getDescription()) + // .withNote(details.getNote()) + .withChargingProfilePurpose(ChargingProfilePurposeType.fromValue(details.getChargingProfilePurpose())) + .withChargingProfileKind(ChargingProfileKindType.fromValue(details.getChargingProfileKind())) + .withRecurrencyKind(Optional.ofNullable(details.getRecurrencyKind()) + .map(RecurrencyKindType::fromValue) + .orElse(null)) + .withValidFrom(toOffsetDateTime(details.getValidFrom())) + .withValidTo(toOffsetDateTime(details.getValidTo())) .withChargingSchedule(schedule); var request = diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/AddressRepository.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/AddressRepository.java index 57e9372c2..494973e1e 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/AddressRepository.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/AddressRepository.java @@ -19,20 +19,18 @@ package de.rwth.idsg.steve.repository; import de.rwth.idsg.steve.web.dto.Address; -import jooq.steve.db.tables.records.AddressRecord; -import org.jooq.DSLContext; -import org.jooq.Record1; -import org.jooq.SelectConditionStep; import org.jspecify.annotations.Nullable; +import java.util.Optional; + /** * @author Sevket Goekay * @since 24.11.2015 */ public interface AddressRepository { - @Nullable AddressRecord get(DSLContext ctx, Integer addressPk); + Optional
get(Integer addressPk); - @Nullable Integer updateOrInsert(DSLContext ctx, Address address); + @Nullable Integer updateOrInsert(Address address); - void delete(DSLContext ctx, SelectConditionStep> addressPkSelect); + void delete(Integer addressPk); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/GenericRepository.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/GenericRepository.java index 03162a016..146469cf9 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/GenericRepository.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/GenericRepository.java @@ -21,6 +21,8 @@ import de.rwth.idsg.steve.repository.dto.DbVersion; import de.rwth.idsg.steve.web.dto.Statistics; +import java.util.Optional; + /** * @author Sevket Goekay * @since 19.08.2014 @@ -35,5 +37,5 @@ public interface GenericRepository { * Returns database version of SteVe and last database update timestamp * */ - DbVersion getDBVersion(); + Optional getDBVersion(); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/OcppTagRepository.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/OcppTagRepository.java index 78036be48..6fa6cc440 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/OcppTagRepository.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/OcppTagRepository.java @@ -19,12 +19,12 @@ package de.rwth.idsg.steve.repository; import de.rwth.idsg.steve.repository.dto.OcppTag; +import de.rwth.idsg.steve.repository.dto.OcppTagActivity; import de.rwth.idsg.steve.web.dto.OcppTagForm; import de.rwth.idsg.steve.web.dto.OcppTagQueryForm; -import jooq.steve.db.tables.records.OcppTagActivityRecord; -import org.jooq.Result; import java.util.List; +import java.util.Optional; /** * @author Sevket Goekay @@ -33,13 +33,13 @@ public interface OcppTagRepository { List getOverview(OcppTagQueryForm form); - Result getRecords(); + List getRecords(); - Result getRecords(List idTagList); + List getRecords(List idTagList); - OcppTagActivityRecord getRecord(String idTag); + Optional getRecord(String idTag); - OcppTagActivityRecord getRecord(int ocppTagPk); + Optional getRecord(int ocppTagPk); List getIdTags(); diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/ReservationRepository.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/ReservationRepository.java index bbc8ed917..0dd626b04 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/ReservationRepository.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/ReservationRepository.java @@ -21,8 +21,6 @@ import de.rwth.idsg.steve.repository.dto.InsertReservationParams; import de.rwth.idsg.steve.repository.dto.Reservation; import de.rwth.idsg.steve.web.dto.ReservationQueryForm; -import org.jooq.Record1; -import org.jooq.Select; import java.util.List; import java.util.Optional; @@ -54,5 +52,5 @@ public interface ReservationRepository { void cancelled(int reservationId); - void used(Select> connectorPkSelect, String ocppIdTag, int reservationId, int transactionId); + void used(int connectorPk, String ocppIdTag, int reservationId, int transactionId); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java index 390396ecc..19def97c9 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/TransactionRepository.java @@ -41,5 +41,5 @@ public interface TransactionRepository { Optional getActiveTransactionId(String chargeBoxId, int connectorId); - TransactionDetails getDetails(int transactionPk); + Optional getDetails(int transactionPk); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/WebUserRepository.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/WebUserRepository.java index a398e368c..263118cf0 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/WebUserRepository.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/WebUserRepository.java @@ -18,19 +18,20 @@ */ package de.rwth.idsg.steve.repository; +import de.rwth.idsg.steve.repository.dto.WebUser; +import de.rwth.idsg.steve.service.dto.WebUserOverview; import de.rwth.idsg.steve.web.dto.WebUserQueryForm; -import jooq.steve.db.tables.records.WebUserRecord; -import org.jooq.JSON; -import org.jooq.Record4; -import org.jooq.Result; + +import java.util.List; +import java.util.Optional; public interface WebUserRepository { - void createUser(WebUserRecord user); + void createUser(WebUser user); - void updateUser(WebUserRecord user); + void updateUser(WebUser user); - void updateUserByPk(WebUserRecord user); + void updateUserByPk(WebUser user); void deleteUser(String username); @@ -38,7 +39,7 @@ public interface WebUserRepository { void changeStatusOfUser(String username, boolean enabled); - Integer getUserCountWithAuthority(String authority); + int getUserCountWithAuthority(String authority); void changePassword(String username, String newPassword); @@ -48,9 +49,9 @@ public interface WebUserRepository { boolean userExists(String username); - WebUserRecord loadUserByUserPk(Integer webUserPk); + Optional loadUserByUserPk(Integer webUserPk); - WebUserRecord loadUserByUsername(String username); + Optional loadUserByUsername(String username); - Result> getOverview(WebUserQueryForm form); + List getOverview(WebUserQueryForm form); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargePoint.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargePoint.java index 9c9525f8f..e9f40b0d6 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargePoint.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargePoint.java @@ -18,12 +18,12 @@ */ package de.rwth.idsg.steve.repository.dto; -import jooq.steve.db.tables.records.AddressRecord; -import jooq.steve.db.tables.records.ChargeBoxRecord; +import com.neovisionaries.i18n.CountryCode; +import de.rwth.idsg.steve.web.dto.Address; import lombok.Builder; import lombok.Getter; -import lombok.RequiredArgsConstructor; +import java.math.BigDecimal; import java.time.Instant; /** @@ -40,9 +40,32 @@ public static final class Overview { } @Getter - @RequiredArgsConstructor + @Builder public static final class Details { - private final ChargeBoxRecord chargeBox; - private final AddressRecord address; + private final Integer chargeBoxPk; + private final String chargeBoxId; + private final String ocppProtocol; + private final String description; + private final BigDecimal locationLatitude; + private final BigDecimal locationLongitude; + private final String note; + private final String adminAddress; + private final boolean insertConnectorStatusAfterTransactionMsg; + private final String registrationStatus; + private final String street; + private final String houseNumber; + private final String zipCode; + private final String city; + private final CountryCode country; + + public Address getAddress() { + var address = new Address(); + address.setStreet(street); + address.setHouseNumber(houseNumber); + address.setZipCode(zipCode); + address.setCity(city); + address.setCountry(country); + return address; + } } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargingProfile.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargingProfile.java index 9b6161387..21d7057cb 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargingProfile.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargingProfile.java @@ -18,12 +18,11 @@ */ package de.rwth.idsg.steve.repository.dto; -import jooq.steve.db.tables.records.ChargingProfileRecord; -import jooq.steve.db.tables.records.ChargingSchedulePeriodRecord; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.math.BigDecimal; import java.time.Instant; import java.util.List; @@ -58,9 +57,31 @@ public static final class Overview { } @Getter - @RequiredArgsConstructor + @Builder public static final class Details { - private final ChargingProfileRecord profile; - private final List periods; + // from ChargingProfileRecord + private final int chargingProfilePk; + private final int stackLevel; + private final String description; + private final String note; + private final String chargingProfilePurpose; + private final String chargingProfileKind; + private final String recurrencyKind; + private final Instant validFrom; + private final Instant validTo; + private final Integer durationInSeconds; + private final Instant startSchedule; + private final String chargingRateUnit; + private final BigDecimal minChargingRate; + // from ChargingSchedulePeriodRecord + private final List periods; + } + + @Getter + @RequiredArgsConstructor + public static final class ChargingSchedulePeriod { + private final int startPeriodInSeconds; + private final BigDecimal powerLimit; + private final Integer numberPhases; } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/OcppTagActivity.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/OcppTagActivity.java new file mode 100644 index 000000000..6cde32a15 --- /dev/null +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/OcppTagActivity.java @@ -0,0 +1,89 @@ +/* + * 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 lombok.Builder; +import lombok.Getter; +import ocpp.cp._2015._10.AuthorizationData; +import org.jspecify.annotations.Nullable; + +import java.time.Instant; + +import static de.rwth.idsg.steve.utils.DateTimeUtils.toOffsetDateTime; + +@Getter +@Builder +public class OcppTagActivity { + private final int ocppTagPk; + private final String idTag; + private final @Nullable String parentIdTag; + private final @Nullable Instant expiryDate; + private final boolean inTransaction; + private final boolean blocked; + private final int maxActiveTransactionCount; + private final long activeTransactionCount; + private final String note; + + public boolean hasReachedLimitOfActiveTransactions() { + // blocked + if (maxActiveTransactionCount == 0) { + return true; + } + + // allow all + if (maxActiveTransactionCount < 0) { + return false; + } + + // allow as specified + return activeTransactionCount >= maxActiveTransactionCount; + } + + public boolean isExpired(Instant date) { + return expiryDate != null && date.isAfter(expiryDate); + } + + public boolean isBlocked() { + return blocked || maxActiveTransactionCount == 0; + } + + /** + * ConcurrentTx is only valid for StartTransactionRequest + */ + public ocpp.cp._2015._10.AuthorizationStatus decideStatusForAuthData(Instant date) { + if (isBlocked()) { + return ocpp.cp._2015._10.AuthorizationStatus.BLOCKED; + } else if (isExpired(date)) { + return ocpp.cp._2015._10.AuthorizationStatus.EXPIRED; + // } else if (hasReachedLimitOfActiveTransactions()) { + // return ocpp.cp._2015._10.AuthorizationStatus.CONCURRENT_TX; + } else { + return ocpp.cp._2015._10.AuthorizationStatus.ACCEPTED; + } + } + + public AuthorizationData mapToAuthorizationData(Instant date) { + return new AuthorizationData() + .withIdTag(idTag) + .withIdTagInfo(new ocpp.cp._2015._10.IdTagInfo() + .withStatus(decideStatusForAuthData(date)) + .withParentIdTag(parentIdTag) + .withExpiryDate(toOffsetDateTime(expiryDate))); + } +} diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/Transaction.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/Transaction.java index a40f56ec0..b397ae833 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/Transaction.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/Transaction.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; -import jooq.steve.db.enums.TransactionStopEventActor; import lombok.Builder; import lombok.Getter; import lombok.ToString; diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionDetails.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionDetails.java index 65b743ef6..bed470d75 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionDetails.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionDetails.java @@ -18,15 +18,20 @@ */ package de.rwth.idsg.steve.repository.dto; -import jooq.steve.db.tables.records.TransactionStartRecord; +import de.rwth.idsg.steve.utils.TransactionStopServiceHelper; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import ocpp.cs._2012._06.UnitOfMeasure; import org.jspecify.annotations.Nullable; import java.time.Instant; +import java.util.Comparator; import java.util.List; +import static de.rwth.idsg.steve.utils.TransactionStopServiceHelper.floatingStringToIntString; +import static de.rwth.idsg.steve.utils.TransactionStopServiceHelper.kWhStringToWhString; + /** * @author Sevket Goekay * @since 27.04.2016 @@ -41,7 +46,7 @@ public class TransactionDetails { * Subsequent transaction's start event (to the transaction that we give details about), * that is at the same chargebox and connector */ - private final @Nullable TransactionStartRecord nextTransactionStart; + private final @Nullable NextTransactionStart nextTransactionStart; @Getter @Builder @@ -52,4 +57,92 @@ public static class MeterValues { // New in OCPP 1.6 private final String phase; } + + @Getter + @Builder + public static class NextTransactionStart { + private final String startValue; + private final Instant startTimestamp; + } + + public TransactionDetails.@Nullable MeterValues findLastMeterValue() { + var v = values.stream() + .filter(TransactionStopServiceHelper::isEnergyValue) + .max(Comparator.comparing(TransactionDetails.MeterValues::getValueTimestamp)) + .orElse(null); + + // if the list of values is empty, we fall to this case, as well. + if (v == null) { + return null; + } + + // convert kWh to Wh + if (UnitOfMeasure.K_WH.value().equals(v.getUnit())) { + return TransactionDetails.MeterValues.builder() + .value(kWhStringToWhString(v.getValue())) + .valueTimestamp(v.getValueTimestamp()) + .readingContext(v.getReadingContext()) + .format(v.getFormat()) + .measurand(v.getMeasurand()) + .location(v.getLocation()) + .unit(UnitOfMeasure.WH.value()) + .phase(v.getPhase()) + .build(); + } else { + return v; + } + } + + @Builder + @Getter + public static class TerminationValues { + private final String stopValue; + private final Instant stopTimestamp; + } + + public TerminationValues findNeededValues() { + + // ------------------------------------------------------------------------- + // 1. intermediate meter values have priority (most accurate data) + // ------------------------------------------------------------------------- + + var last = findLastMeterValue(); + if (last != null) { + return TerminationValues.builder() + .stopValue(floatingStringToIntString(last.getValue())) + .stopTimestamp(last.getValueTimestamp()) + .build(); + } + + // ------------------------------------------------------------------------- + // 2. a latest energy meter value does not exist, use data of next tx + // ------------------------------------------------------------------------- + + if (nextTransactionStart != null) { + // some charging stations do not reset the meter value counter after each transaction and + // continue counting. in such cases, use the value of subsequent transaction's start value + if (Integer.parseInt(nextTransactionStart.getStartValue()) + > Integer.parseInt(transaction.getStartValue())) { + return TerminationValues.builder() + .stopValue(nextTransactionStart.getStartValue()) + .stopTimestamp(nextTransactionStart.getStartTimestamp()) + .build(); + } else { + // this mix of strategies might be really confusing + return TerminationValues.builder() + .stopValue(transaction.getStartValue()) + .stopTimestamp(nextTransactionStart.getStartTimestamp()) + .build(); + } + } + + // ------------------------------------------------------------------------- + // 3. neither meter values nor next tx exist, use start values + // ------------------------------------------------------------------------- + + return TerminationValues.builder() + .stopValue(transaction.getStartValue()) + .stopTimestamp(transaction.getStartTimestamp()) + .build(); + } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java new file mode 100644 index 000000000..307e9982b --- /dev/null +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java @@ -0,0 +1,24 @@ +/* + * 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; + +public enum TransactionStopEventActor { + STATION, + MANUAL +} diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java index c7b418099..d148a6381 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java @@ -18,7 +18,6 @@ */ package de.rwth.idsg.steve.repository.dto; -import jooq.steve.db.enums.TransactionStopEventActor; import lombok.Builder; import lombok.Getter; diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/User.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/User.java index 9294f38d3..27fb4b52b 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/User.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/User.java @@ -18,13 +18,13 @@ */ package de.rwth.idsg.steve.repository.dto; -import jooq.steve.db.tables.records.AddressRecord; -import jooq.steve.db.tables.records.UserRecord; +import com.neovisionaries.i18n.CountryCode; +import de.rwth.idsg.steve.web.dto.Address; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.jspecify.annotations.Nullable; +import java.time.LocalDate; import java.util.List; /** @@ -44,9 +44,33 @@ public static final class Overview { @Getter @Builder public static final class Details { - private final UserRecord userRecord; - private final @Nullable AddressRecord address; + // from UserRecord + private final Integer userPk; + private final String firstName; + private final String lastName; + private final LocalDate birthDay; + private final String phone; + private final String sex; + private final String eMail; + private final String note; + // from AddressRecord + private final String street; + private final String houseNumber; + private final String zipCode; + private final String city; + private final CountryCode country; + // from OcppTag private final List ocppTagEntries; + + public Address getAddress() { + var address = new Address(); + address.setStreet(street); + address.setHouseNumber(houseNumber); + address.setZipCode(zipCode); + address.setCity(city); + address.setCountry(country); + return address; + } } @Getter diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/WebUser.java b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/WebUser.java new file mode 100644 index 000000000..6ade17bea --- /dev/null +++ b/steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/WebUser.java @@ -0,0 +1,41 @@ +/* + * 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 de.rwth.idsg.steve.web.dto.WebUserAuthority; +import lombok.Builder; +import lombok.Getter; +import org.jspecify.annotations.Nullable; + +import java.util.Set; + +@Getter +@Builder +public class WebUser { + private final @Nullable Integer webUserPk; + private final String login; + private final @Nullable String password; + private final @Nullable String salt; + private final @Nullable String firstname; + private final @Nullable String lastname; + private final @Nullable String email; + private final boolean enabled; + private final Set authorities; + private final @Nullable String apiPassword; +} diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/service/AuthTagServiceLocal.java b/steve-core/src/main/java/de/rwth/idsg/steve/service/AuthTagServiceLocal.java index 65f8dc543..815d2d612 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/service/AuthTagServiceLocal.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/service/AuthTagServiceLocal.java @@ -20,7 +20,7 @@ import de.rwth.idsg.steve.repository.OcppTagRepository; import de.rwth.idsg.steve.repository.SettingsRepository; -import jooq.steve.db.tables.records.OcppTagActivityRecord; +import de.rwth.idsg.steve.repository.dto.OcppTagActivity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import ocpp.cs._2015._10.AuthorizationStatus; @@ -32,9 +32,6 @@ import java.time.OffsetDateTime; import static de.rwth.idsg.steve.utils.DateTimeUtils.toOffsetDateTime; -import static de.rwth.idsg.steve.utils.OcppTagActivityRecordUtils.isBlocked; -import static de.rwth.idsg.steve.utils.OcppTagActivityRecordUtils.isExpired; -import static de.rwth.idsg.steve.utils.OcppTagActivityRecordUtils.reachedLimitOfActiveTransactions; @Slf4j @Service @@ -50,50 +47,46 @@ public IdTagInfo decideStatus( boolean isStartTransactionReqContext, @Nullable String chargeBoxId, @Nullable Integer connectorId) { - OcppTagActivityRecord record = ocppTagRepository.getRecord(idTag); - if (record == null) { + var tagOpt = ocppTagRepository.getRecord(idTag); + if (tagOpt.isEmpty()) { log.error("The user with idTag '{}' is INVALID (not present in DB).", idTag); return new IdTagInfo().withStatus(AuthorizationStatus.INVALID); } - if (isBlocked(record)) { + var tag = tagOpt.get(); + if (tag.isBlocked()) { log.error("The user with idTag '{}' is BLOCKED.", idTag); - return new IdTagInfo() - .withStatus(AuthorizationStatus.BLOCKED) - .withParentIdTag(record.getParentIdTag()) - .withExpiryDate(getExpiryDateOrDefault(record)); + return buildIdTagInfo(tag, AuthorizationStatus.BLOCKED); } - if (isExpired(record, Instant.now())) { + if (tag.isExpired(Instant.now())) { log.error("The user with idTag '{}' is EXPIRED.", idTag); - return new IdTagInfo() - .withStatus(AuthorizationStatus.EXPIRED) - .withParentIdTag(record.getParentIdTag()) - .withExpiryDate(getExpiryDateOrDefault(record)); + return buildIdTagInfo(tag, AuthorizationStatus.EXPIRED); } // https://github.com/steve-community/steve/issues/219 - if (isStartTransactionReqContext && reachedLimitOfActiveTransactions(record)) { + if (isStartTransactionReqContext && tag.hasReachedLimitOfActiveTransactions()) { log.warn("The user with idTag '{}' is ALREADY in another transaction(s).", idTag); - return new IdTagInfo() - .withStatus(AuthorizationStatus.CONCURRENT_TX) - .withParentIdTag(record.getParentIdTag()) - .withExpiryDate(getExpiryDateOrDefault(record)); + return buildIdTagInfo(tag, AuthorizationStatus.CONCURRENT_TX); } - log.debug("The user with idTag '{}' is ACCEPTED.", record.getIdTag()); + log.debug("The user with idTag '{}' is ACCEPTED.", idTag); + return buildIdTagInfo(tag, AuthorizationStatus.ACCEPTED); + } + + private IdTagInfo buildIdTagInfo(OcppTagActivity tag, AuthorizationStatus status) { return new IdTagInfo() - .withStatus(AuthorizationStatus.ACCEPTED) - .withParentIdTag(record.getParentIdTag()) - .withExpiryDate(getExpiryDateOrDefault(record)); + .withStatus(status) + .withParentIdTag(tag.getParentIdTag()) + .withExpiryDate(getExpiryDateOrDefault(tag)); } /** * If the database contains an actual expiry, use it. Otherwise, calculate an expiry for cached info */ - private @Nullable OffsetDateTime getExpiryDateOrDefault(OcppTagActivityRecord record) { - if (record.getExpiryDate() != null) { - return toOffsetDateTime(record.getExpiryDate()); + private @Nullable OffsetDateTime getExpiryDateOrDefault(OcppTagActivity tag) { + if (tag.getExpiryDate() != null) { + return toOffsetDateTime(tag.getExpiryDate()); } int hoursToExpire = settingsRepository.getHoursToExpire(); @@ -101,8 +94,7 @@ public IdTagInfo decideStatus( // From web page: The value 0 disables this functionality (i.e. no expiry date will be set). if (hoursToExpire == 0) { return null; - } else { - return OffsetDateTime.now().plusHours(hoursToExpire); } + return OffsetDateTime.now().plusHours(hoursToExpire); } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java b/steve-core/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java index c007ed8b5..8294bfb71 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java @@ -23,13 +23,13 @@ import de.rwth.idsg.steve.repository.SettingsRepository; import de.rwth.idsg.steve.repository.dto.InsertConnectorStatusParams; import de.rwth.idsg.steve.repository.dto.InsertTransactionParams; +import de.rwth.idsg.steve.repository.dto.TransactionStopEventActor; import de.rwth.idsg.steve.repository.dto.UpdateChargeboxParams; 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.OcppTransactionEnded; import de.rwth.idsg.steve.service.notification.OcppTransactionStarted; -import jooq.steve.db.enums.TransactionStopEventActor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import ocpp.cs._2015._10.AuthorizationStatus; @@ -212,7 +212,7 @@ public StopTransactionResponse stopTransaction(StopTransactionRequest parameters .stopMeterValue(Integer.toString(parameters.getMeterStop())) .stopReason(stopReason) .eventTimestamp(Instant.now()) - .eventActor(TransactionStopEventActor.station) + .eventActor(TransactionStopEventActor.STATION) .build(); ocppServerRepository.updateTransaction(params); diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/service/OcppTagsService.java b/steve-core/src/main/java/de/rwth/idsg/steve/service/OcppTagsService.java index 0fd937d4d..bbc59ab05 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/service/OcppTagsService.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/service/OcppTagsService.java @@ -21,10 +21,10 @@ import com.google.common.base.Strings; import de.rwth.idsg.steve.repository.OcppTagRepository; import de.rwth.idsg.steve.repository.dto.OcppTag; +import de.rwth.idsg.steve.repository.dto.OcppTagActivity; import de.rwth.idsg.steve.service.dto.UnidentifiedIncomingObject; import de.rwth.idsg.steve.web.dto.OcppTagForm; import de.rwth.idsg.steve.web.dto.OcppTagQueryForm; -import jooq.steve.db.tables.records.OcppTagActivityRecord; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import ocpp.cp._2015._10.AuthorizationData; @@ -38,10 +38,6 @@ import java.util.List; import java.util.function.Supplier; -import static de.rwth.idsg.steve.utils.DateTimeUtils.toOffsetDateTime; -import static de.rwth.idsg.steve.utils.OcppTagActivityRecordUtils.isBlocked; -import static de.rwth.idsg.steve.utils.OcppTagActivityRecordUtils.isExpired; - /** * @author Sevket Goekay * @since 03.01.2015 @@ -60,8 +56,8 @@ public List getOverview(OcppTagQueryForm form) { return ocppTagRepository.getOverview(form); } - public OcppTagActivityRecord getRecord(int ocppTagPk) { - return ocppTagRepository.getRecord(ocppTagPk); + public OcppTagActivity getRecord(int ocppTagPk) { + return ocppTagRepository.getRecord(ocppTagPk).orElse(null); } public List getIdTags() { @@ -86,12 +82,16 @@ public String getParentIdtag(String idTag) { public List getAuthDataOfAllTags() { var now = Instant.now(); - return ocppTagRepository.getRecords().map(record -> mapToAuthorizationData(record, now)); + return ocppTagRepository.getRecords().stream() + .map(tag -> tag.mapToAuthorizationData(now)) + .toList(); } public List getAuthData(List idTagList) { var now = Instant.now(); - return ocppTagRepository.getRecords(idTagList).map(record -> mapToAuthorizationData(record, now)); + return ocppTagRepository.getRecords(idTagList).stream() + .map(tag -> tag.mapToAuthorizationData(now)) + .toList(); } public List getUnknownOcppTags() { @@ -157,33 +157,4 @@ public void updateOcppTag(OcppTagForm form) { public void deleteOcppTag(int ocppTagPk) { ocppTagRepository.deleteOcppTag(ocppTagPk); } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - /** - * ConcurrentTx is only valid for StartTransactionRequest - */ - private static ocpp.cp._2015._10.AuthorizationStatus decideStatusForAuthData( - OcppTagActivityRecord record, Instant now) { - if (isBlocked(record)) { - return ocpp.cp._2015._10.AuthorizationStatus.BLOCKED; - } else if (isExpired(record, now)) { - return ocpp.cp._2015._10.AuthorizationStatus.EXPIRED; - // } else if (reachedLimitOfActiveTransactions(record)) { - // return ocpp.cp._2015._10.AuthorizationStatus.CONCURRENT_TX; - } else { - return ocpp.cp._2015._10.AuthorizationStatus.ACCEPTED; - } - } - - private static AuthorizationData mapToAuthorizationData(OcppTagActivityRecord record, Instant nowDt) { - return new AuthorizationData() - .withIdTag(record.getIdTag()) - .withIdTagInfo(new ocpp.cp._2015._10.IdTagInfo() - .withStatus(decideStatusForAuthData(record, nowDt)) - .withParentIdTag(record.getParentIdTag()) - .withExpiryDate(toOffsetDateTime(record.getExpiryDate()))); - } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/service/TransactionStopService.java b/steve-core/src/main/java/de/rwth/idsg/steve/service/TransactionStopService.java index cb5b17126..784296640 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/service/TransactionStopService.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/service/TransactionStopService.java @@ -21,26 +21,14 @@ import com.google.common.collect.Ordering; import de.rwth.idsg.steve.repository.OcppServerRepository; import de.rwth.idsg.steve.repository.TransactionRepository; -import de.rwth.idsg.steve.repository.dto.Transaction; -import de.rwth.idsg.steve.repository.dto.TransactionDetails; +import de.rwth.idsg.steve.repository.dto.TransactionStopEventActor; import de.rwth.idsg.steve.repository.dto.UpdateTransactionParams; -import de.rwth.idsg.steve.utils.TransactionStopServiceHelper; -import jooq.steve.db.enums.TransactionStopEventActor; -import jooq.steve.db.tables.records.TransactionStartRecord; -import lombok.Builder; import lombok.RequiredArgsConstructor; -import ocpp.cs._2012._06.UnitOfMeasure; -import org.jspecify.annotations.Nullable; import org.springframework.stereotype.Service; import java.time.Instant; -import java.util.Comparator; import java.util.List; -import static de.rwth.idsg.steve.utils.DateTimeUtils.toInstant; -import static de.rwth.idsg.steve.utils.TransactionStopServiceHelper.floatingStringToIntString; -import static de.rwth.idsg.steve.utils.TransactionStopServiceHelper.kWhStringToWhString; - /** * @author Sevket Goekay * @since 09.12.2018 @@ -57,106 +45,28 @@ public void stop(List transactionPkList) { } public void stop(Integer transactionPk) { - TransactionDetails thisTxDetails = transactionRepository.getDetails(transactionPk); - Transaction thisTx = thisTxDetails.getTransaction(); + var thisTxDetails = transactionRepository.getDetails(transactionPk).orElse(null); + + if (thisTxDetails == null) { + return; + } + + var thisTx = thisTxDetails.getTransaction(); // early exit, if transaction is already stopped if (thisTx.getStopValue() != null && thisTx.getStopTimestamp() != null) { return; } - TerminationValues values = findNeededValues(thisTxDetails); + var values = thisTxDetails.findNeededValues(); ocppServerRepository.updateTransaction(UpdateTransactionParams.builder() .transactionId(thisTx.getId()) .chargeBoxId(thisTx.getChargeBoxId()) - .stopMeterValue(values.stopValue) - .stopTimestamp(values.stopTimestamp) - .eventActor(TransactionStopEventActor.manual) + .stopMeterValue(values.getStopValue()) + .stopTimestamp(values.getStopTimestamp()) + .eventActor(TransactionStopEventActor.MANUAL) .eventTimestamp(Instant.now()) .build()); } - - private static TerminationValues findNeededValues(TransactionDetails thisTxDetails) { - Transaction thisTx = thisTxDetails.getTransaction(); - TransactionStartRecord nextTx = thisTxDetails.getNextTransactionStart(); - List intermediateValues = thisTxDetails.getValues(); - - // ------------------------------------------------------------------------- - // 1. intermediate meter values have priority (most accurate data) - // ------------------------------------------------------------------------- - - TransactionDetails.MeterValues last = findLastMeterValue(intermediateValues); - if (last != null) { - return TerminationValues.builder() - .stopValue(floatingStringToIntString(last.getValue())) - .stopTimestamp(last.getValueTimestamp()) - .build(); - } - - // ------------------------------------------------------------------------- - // 2. a latest energy meter value does not exist, use data of next tx - // ------------------------------------------------------------------------- - - if (nextTx != null) { - // some charging stations do not reset the meter value counter after each transaction and - // continue counting. in such cases, use the value of subsequent transaction's start value - if (Integer.parseInt(nextTx.getStartValue()) > Integer.parseInt(thisTx.getStartValue())) { - return TerminationValues.builder() - .stopValue(nextTx.getStartValue()) - .stopTimestamp(toInstant(nextTx.getStartTimestamp())) - .build(); - } else { - // this mix of strategies might be really confusing - return TerminationValues.builder() - .stopValue(thisTx.getStartValue()) - .stopTimestamp(toInstant(nextTx.getStartTimestamp())) - .build(); - } - } - - // ------------------------------------------------------------------------- - // 3. neither meter values nor next tx exist, use start values - // ------------------------------------------------------------------------- - - return TerminationValues.builder() - .stopValue(thisTx.getStartValue()) - .stopTimestamp(thisTx.getStartTimestamp()) - .build(); - } - - private static TransactionDetails.@Nullable MeterValues findLastMeterValue( - List values) { - TransactionDetails.MeterValues v = values.stream() - .filter(TransactionStopServiceHelper::isEnergyValue) - .max(Comparator.comparing(TransactionDetails.MeterValues::getValueTimestamp)) - .orElse(null); - - // if the list of values is empty, we fall to this case, as well. - if (v == null) { - return null; - } - - // convert kWh to Wh - if (UnitOfMeasure.K_WH.value().equals(v.getUnit())) { - return TransactionDetails.MeterValues.builder() - .value(kWhStringToWhString(v.getValue())) - .valueTimestamp(v.getValueTimestamp()) - .readingContext(v.getReadingContext()) - .format(v.getFormat()) - .measurand(v.getMeasurand()) - .location(v.getLocation()) - .unit(v.getUnit()) - .phase(v.getPhase()) - .build(); - } else { - return v; - } - } - - @Builder - private static class TerminationValues { - private final String stopValue; - private final Instant stopTimestamp; - } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java b/steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java index e8da7b2c6..d8a356cdd 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java @@ -25,21 +25,18 @@ import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.repository.WebUserRepository; +import de.rwth.idsg.steve.repository.dto.WebUser; import de.rwth.idsg.steve.service.dto.WebUserOverview; import de.rwth.idsg.steve.web.dto.WebUserAuthority; import de.rwth.idsg.steve.web.dto.WebUserBaseForm; import de.rwth.idsg.steve.web.dto.WebUserForm; import de.rwth.idsg.steve.web.dto.WebUserQueryForm; -import jooq.steve.db.tables.records.WebUserRecord; import lombok.RequiredArgsConstructor; -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; import org.springframework.security.core.userdetails.UserDetails; @@ -52,8 +49,10 @@ import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -91,18 +90,12 @@ public void afterStart(ContextRefreshedEvent event) { return; } - var headerVal = config.getWebApi().getHeaderValue(); - - var encodedApiPassword = headerVal == null || headerVal.isBlank() - ? null - : config.getAuth().getPasswordEncoder().encode(headerVal); - - var user = new WebUserRecord() - .setUsername(config.getAuth().getUserName()) - .setPassword(config.getAuth().getEncodedPassword()) - .setApiPassword(encodedApiPassword) - .setEnabled(true) - .setAuthorities(toJson(AuthorityUtils.createAuthorityList("ADMIN"))); + var user = WebUser.builder() + .login(config.getAuth().getUserName()) + .password(config.getAuth().getEncodedPassword()) + .enabled(true) + .authorities(EnumSet.of(WebUserAuthority.ADMIN)) + .build(); webUserRepository.createUser(user); } @@ -110,16 +103,16 @@ public void afterStart(ContextRefreshedEvent event) { @Override public void createUser(UserDetails user) { validateUserDetails(user); - var record = toWebUserRecord(user); - webUserRepository.createUser(record); + var webUser = toWebUser(user); + webUserRepository.createUser(webUser); userCache.invalidate(user.getUsername()); } @Override public void updateUser(UserDetails user) { validateUserDetails(user); - var record = toWebUserRecord(user); - webUserRepository.updateUser(record); + var webUser = toWebUser(user); + webUserRepository.updateUser(webUser); userCache.invalidate(user.getUsername()); } @@ -135,19 +128,18 @@ public void deleteUser(String username) { */ @Override public void changePassword(String oldPassword, String newPassword) { - Authentication currentUser = - this.securityContextHolderStrategy.getContext().getAuthentication(); + var currentUser = this.securityContextHolderStrategy.getContext().getAuthentication(); if (currentUser == null) { // This would indicate bad coding somewhere throw new AccessDeniedException( "Can't change password as no Authentication object found in context for current user."); } - String username = currentUser.getName(); + var username = currentUser.getName(); webUserRepository.changePassword(username, encoder.encode(newPassword)); - Authentication authentication = createNewAuthentication(currentUser); - SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); + var authentication = createNewAuthentication(currentUser); + var context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authentication); this.securityContextHolderStrategy.setContext(context); } @@ -159,23 +151,26 @@ public boolean userExists(String username) { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - WebUserRecord webUser = webUserRepository.loadUserByUsername(username); + var webUser = webUserRepository.loadUserByUsername(username).orElse(null); if (webUser == null) { throw new UsernameNotFoundException(username); } - return User.withUsername(webUser.getUsername()) + return User.withUsername(webUser.getLogin()) .password(webUser.getPassword()) - .disabled(!webUser.getEnabled()) - .authorities(fromJson(webUser.getAuthorities())) + .disabled(!webUser.isEnabled()) + .authorities(webUser.getAuthorities().stream() + .map(WebUserAuthority::getValues) + .flatMap(Set::stream) + .toArray(String[]::new)) .build(); } public UserDetails loadUserByUsernameForApi(String username) { try { - UserDetails userExt = userCache.get(username, () -> { - UserDetails user = this.loadUserByUsernameForApiInternal(username); + var userExt = userCache.get(username, () -> { + var user = this.loadUserByUsernameForApiInternal(username); // map null to dummy return (user == null) ? DUMMY_USER : user; }); @@ -187,7 +182,11 @@ public UserDetails loadUserByUsernameForApi(String username) { } public void deleteUser(int webUserPk) { + var existing = webUserRepository.loadUserByUserPk(webUserPk).orElse(null); webUserRepository.deleteUser(webUserPk); + if (existing != null) { + userCache.invalidate(existing.getLogin()); + } } public void changeStatusOfUser(String username, boolean enabled) { @@ -196,8 +195,8 @@ public void changeStatusOfUser(String username, boolean enabled) { } public boolean hasUserWithAuthority(String authority) { - Integer count = webUserRepository.getUserCountWithAuthority(authority); - return count != null && count > 0; + var count = webUserRepository.getUserCountWithAuthority(authority); + return count > 0; } // Methods for the website @@ -207,12 +206,13 @@ public void add(WebUserForm form) { public void update(WebUserBaseForm form) { validateUserDetails(toUserDetailsBaseForm(form)); - WebUserRecord record = new WebUserRecord(); - record.setWebUserPk(form.getWebUserPk()); - record.setUsername(form.getWebUsername()); - record.setEnabled(form.getEnabled()); - record.setAuthorities(form.getAuthorities().getJsonValue()); - webUserRepository.updateUserByPk(record); + var webUser = WebUser.builder() + .webUserPk(form.getWebUserPk()) + .login(form.getWebUsername()) + .enabled(form.getEnabled()) + .authorities(Set.of(form.getAuthorities())) + .build(); + webUserRepository.updateUserByPk(webUser); userCache.invalidate(form.getWebUsername()); } @@ -229,77 +229,78 @@ public void updateApiPassword(WebUserForm form) { } public List getOverview(WebUserQueryForm form) { - return webUserRepository.getOverview(form).map(r -> WebUserOverview.builder() - .webUserPk(r.value1()) - .webUsername(r.value2()) - .enabled(r.value3()) - .authorities(WebUserAuthority.fromJsonValue(mapper, r.value4())) - .build()); + return webUserRepository.getOverview(form); } public WebUserBaseForm getDetails(Integer webUserPk) { - WebUserRecord ur = webUserRepository.loadUserByUserPk(webUserPk); - - if (ur == null) { - throw new SteveException.NotFound("There is no user with id '%d'", webUserPk); - } + var ur = webUserRepository + .loadUserByUserPk(webUserPk) + .orElseThrow(() -> new SteveException.NotFound("There is no user with id '%d'".formatted(webUserPk))); - WebUserBaseForm form = new WebUserBaseForm(); - form.setWebUserPk(ur.getWebUserPk()); - form.setEnabled(ur.getEnabled()); - form.setWebUsername(ur.getUsername()); - form.setAuthorities(WebUserAuthority.fromJsonValue(mapper, ur.getAuthorities())); - return form; + return getWebUserBaseForm(ur); } public WebUserBaseForm getDetails(String webUserName) { - WebUserRecord ur = webUserRepository.loadUserByUsername(webUserName); + var ur = webUserRepository + .loadUserByUsername(webUserName) + .orElseThrow(() -> + new SteveException.NotFound("There is no user with username '%s'".formatted(webUserName))); - if (ur == null) { - throw new SteveException.NotFound("There is no user with username '%s'", webUserName); - } + return getWebUserBaseForm(ur); + } - WebUserBaseForm form = new WebUserBaseForm(); + private static WebUserBaseForm getWebUserBaseForm(WebUser ur) { + var form = new WebUserBaseForm(); form.setWebUserPk(ur.getWebUserPk()); - form.setEnabled(ur.getEnabled()); - form.setWebUsername(ur.getUsername()); - form.setAuthorities(WebUserAuthority.fromJsonValue(mapper, ur.getAuthorities())); + form.setEnabled(ur.isEnabled()); + form.setWebUsername(ur.getLogin()); + form.setAuthorities(WebUserAuthority.fromAuthorities( + ur.getAuthorities().stream().map(WebUserAuthority::name).collect(Collectors.toSet()))); return form; } // Helpers private UserDetails loadUserByUsernameForApiInternal(String username) { - WebUserRecord record = webUserRepository.loadUserByUsername(username); - if (record == null) { + var webUser = webUserRepository.loadUserByUsername(username).orElse(null); + if (webUser == null) { return null; } // the builder User.password(..) does not allow null values - String apiPassword = record.getApiPassword(); + var apiPassword = webUser.getApiPassword(); if (apiPassword == null) { apiPassword = ""; } - return User.withUsername(record.getUsername()) + return User.withUsername(webUser.getLogin()) .password(apiPassword) - .disabled(!record.getEnabled()) - .authorities(fromJson(record.getAuthorities())) + .disabled(!webUser.isEnabled()) + .authorities(webUser.getAuthorities().stream() + .map(WebUserAuthority::name) + .toArray(String[]::new)) .build(); } - private WebUserRecord toWebUserRecord(UserDetails user) { - return new WebUserRecord() - .setUsername(user.getUsername()) - .setPassword(user.getPassword()) - .setEnabled(user.isEnabled()) - .setAuthorities(toJson(user.getAuthorities())); + private WebUser toWebUser(UserDetails user) { + var authorities = user.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .map(String::toUpperCase) + .map(WebUserAuthority::valueOf) + .collect(Collectors.toSet()); + + return WebUser.builder() + .login(user.getUsername()) + .password(user.getPassword()) + .enabled(user.isEnabled()) + .authorities(authorities) + .build(); } private UserDetails toUserDetailsBaseForm(WebUserBaseForm form) { return User.withUsername(form.getWebUsername()) .password("") .disabled(!form.getEnabled()) - .authorities(fromJson(form.getAuthorities().getJsonValue())) + .authorities(form.getAuthoritiesAsStrings().toArray(new String[0])) .build(); } @@ -313,27 +314,26 @@ private UserDetails toUserDetails(WebUserForm form) { return User.withUsername(form.getWebUsername()) .password(encPw) .disabled(!form.getEnabled()) - .authorities(fromJson(form.getAuthorities().getJsonValue())) + .authorities(form.getAuthoritiesAsStrings().toArray(new String[0])) .build(); } - private String[] fromJson(JSON jsonArray) { + private String[] fromJson(String jsonArray) { try { - return mapper.readValue(jsonArray.data(), String[].class); + return mapper.readValue(jsonArray, String[].class); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } - private JSON toJson(Collection authorities) { - Collection auths = authorities.stream() + private String toJson(Collection authorities) { + var auths = authorities.stream() .map(GrantedAuthority::getAuthority) .sorted() // keep a stable order of entries .collect(Collectors.toCollection(LinkedHashSet::new)); // prevent duplicates try { - String str = mapper.writeValueAsString(auths); - return JSON.jsonOrNull(str); + return mapper.writeValueAsString(auths); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -352,7 +352,7 @@ private static void validateUserDetails(UserDetails user) { */ private static void validateAuthorities(Collection authorities) { Assert.notNull(authorities, "Authorities list must not be null"); - for (GrantedAuthority authority : authorities) { + for (var authority : authorities) { Assert.notNull(authority, "Authorities list contains a null entry"); Assert.hasText(authority.getAuthority(), "getAuthority() method must return a non-empty string"); } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointForm.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointForm.java index a1113ed9a..495229bac 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointForm.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointForm.java @@ -38,6 +38,23 @@ @ToString public class ChargePointForm { + public ChargePointForm() {} + + public static ChargePointForm fromDetails(de.rwth.idsg.steve.repository.dto.ChargePoint.Details details) { + var form = new ChargePointForm(); + form.chargeBoxPk = details.getChargeBoxPk(); + form.chargeBoxId = details.getChargeBoxId(); + form.note = details.getNote(); + form.description = details.getDescription(); + form.locationLatitude = details.getLocationLatitude(); + form.locationLongitude = details.getLocationLongitude(); + form.insertConnectorStatusAfterTransactionMsg = details.isInsertConnectorStatusAfterTransactionMsg(); + form.adminAddress = details.getAdminAddress(); + form.registrationStatus = details.getRegistrationStatus(); + form.address = details.getAddress(); + return form; + } + // Internal database id private Integer chargeBoxPk; diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java index da43fd4c9..5acb1ac99 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java @@ -50,6 +50,36 @@ @ToString public class ChargingProfileForm { + public ChargingProfileForm() {} + + public static ChargingProfileForm fromDetails(de.rwth.idsg.steve.repository.dto.ChargingProfile.Details details) { + var form = new ChargingProfileForm(); + form.chargingProfilePk = details.getChargingProfilePk(); + form.stackLevel = details.getStackLevel(); + form.description = details.getDescription(); + form.note = details.getNote(); + form.chargingProfilePurpose = ChargingProfilePurposeType.fromValue(details.getChargingProfilePurpose()); + form.chargingProfileKind = ChargingProfileKindType.fromValue(details.getChargingProfileKind()); + form.recurrencyKind = + details.getRecurrencyKind() == null ? null : RecurrencyKindType.fromValue(details.getRecurrencyKind()); + form.validFrom = details.getValidFrom(); + form.validTo = details.getValidTo(); + form.durationInSeconds = details.getDurationInSeconds(); + form.startSchedule = details.getStartSchedule(); + form.chargingRateUnit = ChargingRateUnitType.fromValue(details.getChargingRateUnit()); + form.minChargingRate = details.getMinChargingRate(); + form.schedulePeriods = details.getPeriods().stream() + .map(p -> { + SchedulePeriod sp = new SchedulePeriod(); + sp.setStartPeriodInSeconds(p.getStartPeriodInSeconds()); + sp.setPowerLimit(p.getPowerLimit()); + sp.setNumberPhases(p.getNumberPhases()); + return sp; + }) + .toList(); + return form; + } + // Internal database id private Integer chargingProfilePk; diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java index ef1224285..17ab855a2 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java @@ -40,6 +40,19 @@ @EqualsAndHashCode public class OcppTagForm { + public OcppTagForm() {} + + public static OcppTagForm fromRecord(de.rwth.idsg.steve.repository.dto.OcppTagActivity tag) { + var form = new OcppTagForm(); + form.ocppTagPk = tag.getOcppTagPk(); + form.idTag = tag.getIdTag(); + form.parentIdTag = tag.getParentIdTag(); + form.expiryDate = tag.getExpiryDate(); + form.maxActiveTransactionCount = tag.getMaxActiveTransactionCount(); + form.note = tag.getNote(); + return form; + } + // Internal database id @Schema(hidden = true) private Integer ocppTagPk; diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java index 5c203d46f..44f287154 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/UserForm.java @@ -36,6 +36,25 @@ @ToString public class UserForm { + public UserForm() {} + + public static UserForm fromDetails(de.rwth.idsg.steve.repository.dto.User.Details details) { + var form = new UserForm(); + form.userPk = details.getUserPk(); + form.firstName = details.getFirstName(); + form.lastName = details.getLastName(); + form.birthDay = details.getBirthDay(); + form.phone = details.getPhone(); + form.sex = UserSex.fromDatabaseValue(details.getSex()); + form.eMail = details.getEMail(); + form.note = details.getNote(); + form.address = details.getAddress(); + form.idTagList = details.getOcppTagEntries().stream() + .map(de.rwth.idsg.steve.repository.dto.User.OcppTagEntry::getIdTag) + .toList(); + return form; + } + // Internal database id private Integer userPk; diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserAuthority.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserAuthority.java index fa35e8c9f..5dc09026a 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserAuthority.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserAuthority.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; -import org.jooq.JSON; import java.util.Arrays; import java.util.HashSet; @@ -32,10 +31,11 @@ public enum WebUserAuthority { ADMIN("ADMIN"), USER_ADMIN("USER", "ADMIN"); + @Getter private final Set values; @Getter - private final JSON jsonValue; + private final String jsonValue; WebUserAuthority(String... values) { if (values == null || values.length == 0) { @@ -45,7 +45,7 @@ public enum WebUserAuthority { this.jsonValue = this.values.stream() .map(v -> "\"" + v + "\"") .reduce((a, b) -> a + ", " + b) - .map(s -> JSON.json("[" + s + "]")) + .map(s -> "[" + s + "]") .orElseThrow(() -> new IllegalArgumentException("Failed to create JSON value")); } @@ -54,17 +54,35 @@ public String getValue() { return String.join(", ", values); } - public static WebUserAuthority fromJsonValue(ObjectMapper mapper, JSON v) { + public static WebUserAuthority fromJsonValue(ObjectMapper mapper, String v) { try { - var values = new HashSet<>(Arrays.asList(mapper.readValue(v.data(), String[].class))); + var values = new HashSet<>(Arrays.asList(mapper.readValue(v, String[].class))); for (WebUserAuthority c : WebUserAuthority.values()) { if (c.values.equals(values)) { return c; } } - throw new IllegalArgumentException(v.toString()); + throw new IllegalArgumentException(v); } catch (JsonProcessingException e) { - throw new IllegalArgumentException(v.toString()); + throw new IllegalArgumentException(v); + } + } + + public static WebUserAuthority fromAuthorities(Set authorities) { + for (WebUserAuthority c : WebUserAuthority.values()) { + if (c.values.equals(authorities)) { + return c; + } + } + throw new IllegalArgumentException("No matching authority for: " + authorities); + } + + public static WebUserAuthority fromAuthority(String authority) { + for (WebUserAuthority c : WebUserAuthority.values()) { + if (c.values.contains(authority)) { + return c; + } } + throw new IllegalArgumentException("No matching authority for: " + authority); } } diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserBaseForm.java b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserBaseForm.java index 10cb1a2c1..168a79cef 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserBaseForm.java +++ b/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserBaseForm.java @@ -21,6 +21,8 @@ import lombok.Getter; import lombok.Setter; +import java.util.Set; + /** * @author fnkbsi * @since 01.04.2022 @@ -37,4 +39,8 @@ public class WebUserBaseForm { private String webUsername; private WebUserAuthority authorities; + + public Set getAuthoritiesAsStrings() { + return authorities.getValues(); + } } diff --git a/steve-jooq/pom.xml b/steve-jooq/pom.xml index 540541f05..4f2adfdda 100644 --- a/steve-jooq/pom.xml +++ b/steve-jooq/pom.xml @@ -23,11 +23,48 @@ + + de.rwth.idsg + steve-core + org.jooq jooq ${jooq.version} + + com.mysql + mysql-connector-j + ${mysql.jdbc.version} + + + com.zaxxer + HikariCP + 7.0.1 + + + org.springframework + spring-context + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.neovisionaries + nv-i18n + + + org.slf4j + slf4j-api + + + org.projectlombok + lombok + ${lombok.version} + provided + diff --git a/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java new file mode 100644 index 000000000..f35943728 --- /dev/null +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java @@ -0,0 +1,105 @@ +/* + * 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.jooq.config; + +import com.mysql.cj.conf.PropertyKey; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import de.rwth.idsg.steve.SteveConfiguration; +import org.jooq.DSLContext; +import org.jooq.SQLDialect; +import org.jooq.conf.Settings; +import org.jooq.impl.DSL; +import org.jooq.impl.DataSourceConnectionProvider; +import org.jooq.impl.DefaultConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +@Configuration +public class JooqConfiguration { + + /** + * https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration + */ + @Bean + public DataSource dataSource(SteveConfiguration config) { + var dbConfig = config.getDb(); + return dataSource( + dbConfig.getJdbcUrl(), dbConfig.getUserName(), dbConfig.getPassword(), config.getTimeZoneId()); + } + + public static DataSource dataSource(String dbUrl, String dbUserName, String dbPassword, String dbTimeZoneId) { + var hc = new HikariConfig(); + + // set standard params + hc.setJdbcUrl(dbUrl); + hc.setUsername(dbUserName); + hc.setPassword(dbPassword); + + // set non-standard params + hc.addDataSourceProperty(PropertyKey.cachePrepStmts.getKeyName(), true); + hc.addDataSourceProperty(PropertyKey.useServerPrepStmts.getKeyName(), true); + hc.addDataSourceProperty(PropertyKey.prepStmtCacheSize.getKeyName(), 250); + hc.addDataSourceProperty(PropertyKey.prepStmtCacheSqlLimit.getKeyName(), 2048); + hc.addDataSourceProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); + hc.addDataSourceProperty(PropertyKey.connectionTimeZone.getKeyName(), dbTimeZoneId); + hc.addDataSourceProperty(PropertyKey.useSSL.getKeyName(), true); + + // https://github.com/steve-community/steve/issues/736 + hc.setMaxLifetime(580_000); + + return new HikariDataSource(hc); + } + + /** + * Can we re-use DSLContext as a Spring bean (singleton)? Yes, the Spring tutorial of + * Jooq also does it that way, but only if we do not change anything about the + * config after the init (which we don't do anyways) and if the ConnectionProvider + * does not store any shared state (we use DataSourceConnectionProvider of Jooq, so no problem). + * + * Some sources and discussion: + * - http://www.jooq.org/doc/3.6/manual/getting-started/tutorials/jooq-with-spring/ + * - http://jooq-user.narkive.com/2fvuLodn/dslcontext-and-threads + * - https://groups.google.com/forum/#!topic/jooq-user/VK7KQcjj3Co + * - http://stackoverflow.com/questions/32848865/jooq-dslcontext-correct-autowiring-with-spring + */ + @Bean + public DSLContext dslContext(DataSource dataSource, SteveConfiguration config) { + var settings = new Settings() + // Normally, the records are "attached" to the Configuration that created (i.e. fetch/insert) them. + // This means that they hold an internal reference to the same database connection that was used. + // The idea behind this is to make CRUD easier for potential subsequent store/refresh/delete + // operations. We do not use or need that. + .withAttachRecords(false) + // To log or not to log the sql queries, that is the question + .withExecuteLogging(config.getDb().isSqlLogging()); + + // Configuration for JOOQ + var conf = new DefaultConfiguration() + .set(SQLDialect.MYSQL) + .set(new DataSourceConnectionProvider( + // new org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy() + dataSource)) + .set(settings); + + return DSL.using(conf); + } +} diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/AddressMapper.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/AddressMapper.java similarity index 63% rename from steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/AddressMapper.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/AddressMapper.java index 1eb0de5e4..cfd94a1a2 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/AddressMapper.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/AddressMapper.java @@ -16,7 +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.utils.mapper; +package de.rwth.idsg.steve.jooq.mapper; import com.neovisionaries.i18n.CountryCode; import de.rwth.idsg.steve.web.dto.Address; @@ -24,23 +24,17 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; -/** - * @author Sevket Goekay - * @since 23.03.2021 - */ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class AddressMapper { - public static Address recordToDto(AddressRecord record) { - Address address = new Address(); - if (record != null) { - address.setAddressPk(record.getAddressPk()); - address.setStreet(record.getStreet()); - address.setHouseNumber(record.getHouseNumber()); - address.setZipCode(record.getZipCode()); - address.setCity(record.getCity()); - address.setCountry(CountryCode.getByCode(record.getCountry())); - } - return address; + public static Address fromRecord(AddressRecord r) { + var a = new Address(); + a.setAddressPk(r.getAddressPk()); + a.setStreet(r.getStreet()); + a.setHouseNumber(r.getHouseNumber()); + a.setZipCode(r.getZipCode()); + a.setCity(r.getCity()); + a.setCountry(CountryCode.getByCode(r.getCountry())); + return a; } } diff --git a/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/ChargePointMapper.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/ChargePointMapper.java new file mode 100644 index 000000000..af70b66e5 --- /dev/null +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/ChargePointMapper.java @@ -0,0 +1,53 @@ +/* + * 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.jooq.mapper; + +import de.rwth.idsg.steve.repository.dto.ChargePoint; +import de.rwth.idsg.steve.web.dto.Address; +import jooq.steve.db.tables.records.ChargeBoxRecord; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.Optional; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ChargePointMapper { + + public static ChargePoint.Details fromRecord(ChargeBoxRecord r, Optional
address) { + var builder = ChargePoint.Details.builder() + .chargeBoxPk(r.getChargeBoxPk()) + .chargeBoxId(r.getChargeBoxId()) + .ocppProtocol(r.getOcppProtocol()) + .description(r.getDescription()) + .locationLatitude(r.getLocationLatitude()) + .locationLongitude(r.getLocationLongitude()) + .note(r.getNote()) + .adminAddress(r.getAdminAddress()) + .insertConnectorStatusAfterTransactionMsg(r.getInsertConnectorStatusAfterTransactionMsg()) + .registrationStatus(r.getRegistrationStatus()); + + address.ifPresent(a -> builder.street(a.getStreet()) + .houseNumber(a.getHouseNumber()) + .zipCode(a.getZipCode()) + .city(a.getCity()) + .country(a.getCountry())); + + return builder.build(); + } +} diff --git a/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/ChargingProfileMapper.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/ChargingProfileMapper.java new file mode 100644 index 000000000..21ef4dded --- /dev/null +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/ChargingProfileMapper.java @@ -0,0 +1,59 @@ +/* + * 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.jooq.mapper; + +import de.rwth.idsg.steve.repository.dto.ChargingProfile; +import jooq.steve.db.tables.records.ChargingProfileRecord; +import jooq.steve.db.tables.records.ChargingSchedulePeriodRecord; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.stream.Collectors; + +import static de.rwth.idsg.steve.utils.DateTimeUtils.toInstant; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ChargingProfileMapper { + + public static ChargingProfile.Details fromRecord( + ChargingProfileRecord profile, List periods) { + return ChargingProfile.Details.builder() + .chargingProfilePk(profile.getChargingProfilePk()) + .stackLevel(profile.getStackLevel()) + .description(profile.getDescription()) + .note(profile.getNote()) + .chargingProfilePurpose(profile.getChargingProfilePurpose()) + .chargingProfileKind(profile.getChargingProfileKind()) + .recurrencyKind(profile.getRecurrencyKind()) + .validFrom(toInstant(profile.getValidFrom())) + .validTo(toInstant(profile.getValidTo())) + .durationInSeconds(profile.getDurationInSeconds()) + .startSchedule(toInstant(profile.getStartSchedule())) + .chargingRateUnit(profile.getChargingRateUnit()) + .minChargingRate(profile.getMinChargingRate()) + .periods(periods.stream().map(ChargingProfileMapper::fromRecord).collect(Collectors.toList())) + .build(); + } + + private static ChargingProfile.ChargingSchedulePeriod fromRecord(ChargingSchedulePeriodRecord p) { + return new ChargingProfile.ChargingSchedulePeriod( + p.getStartPeriodInSeconds(), p.getPowerLimit(), p.getNumberPhases()); + } +} diff --git a/steve-core/src/main/java/de/rwth/idsg/steve/utils/OcppTagActivityRecordUtils.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/OcppTagMapper.java similarity index 55% rename from steve-core/src/main/java/de/rwth/idsg/steve/utils/OcppTagActivityRecordUtils.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/OcppTagMapper.java index 42f2e95ca..64bafe5a5 100644 --- a/steve-core/src/main/java/de/rwth/idsg/steve/utils/OcppTagActivityRecordUtils.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/OcppTagMapper.java @@ -16,42 +16,29 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package de.rwth.idsg.steve.utils; +package de.rwth.idsg.steve.jooq.mapper; +import de.rwth.idsg.steve.repository.dto.OcppTagActivity; import jooq.steve.db.tables.records.OcppTagActivityRecord; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import java.time.Instant; - import static de.rwth.idsg.steve.utils.DateTimeUtils.toInstant; @NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class OcppTagActivityRecordUtils { - - public static boolean isExpired(OcppTagActivityRecord record, Instant now) { - var expiry = toInstant(record.getExpiryDate()); - return expiry != null && now.isAfter(expiry); - } - - public static boolean isBlocked(OcppTagActivityRecord record) { - return record.getMaxActiveTransactionCount() == 0; - } - - public static boolean reachedLimitOfActiveTransactions(OcppTagActivityRecord record) { - int max = record.getMaxActiveTransactionCount(); - - // blocked - if (max == 0) { - return true; - } - - // allow all - if (max < 0) { - return false; - } - - // allow as specified - return record.getActiveTransactionCount() >= max; +public final class OcppTagMapper { + + public static OcppTagActivity fromRecord(OcppTagActivityRecord r) { + return OcppTagActivity.builder() + .ocppTagPk(r.getOcppTagPk()) + .idTag(r.getIdTag()) + .expiryDate(toInstant(r.getExpiryDate())) + .maxActiveTransactionCount(r.getMaxActiveTransactionCount()) + .note(r.getNote()) + .parentIdTag(r.getParentIdTag()) + .blocked(r.getBlocked()) + .inTransaction(r.getInTransaction()) + .activeTransactionCount(r.getActiveTransactionCount()) + .build(); } } diff --git a/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/TransactionMapper.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/TransactionMapper.java new file mode 100644 index 000000000..1dd947509 --- /dev/null +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/TransactionMapper.java @@ -0,0 +1,62 @@ +/* + * 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.jooq.mapper; + +import de.rwth.idsg.steve.repository.dto.Transaction; +import de.rwth.idsg.steve.repository.dto.TransactionStopEventActor; +import de.rwth.idsg.steve.utils.DateTimeUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.jooq.Record; + +import static de.rwth.idsg.steve.utils.DateTimeUtils.toInstant; +import static jooq.steve.db.Tables.CHARGE_BOX; +import static jooq.steve.db.Tables.CONNECTOR; +import static jooq.steve.db.Tables.OCPP_TAG; +import static jooq.steve.db.Tables.TRANSACTION; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class TransactionMapper { + + public static Transaction fromRecord(Record r) { + return Transaction.builder() + .id(r.get(TRANSACTION.TRANSACTION_PK)) + .chargeBoxId(r.get(CHARGE_BOX.CHARGE_BOX_ID)) + .connectorId(r.get(CONNECTOR.CONNECTOR_ID)) + .ocppIdTag(r.get(TRANSACTION.ID_TAG)) + .startTimestamp(toInstant(r.get(TRANSACTION.START_TIMESTAMP))) + .startTimestampFormatted(DateTimeUtils.humanize(r.get(TRANSACTION.START_TIMESTAMP))) + .startValue(r.get(TRANSACTION.START_VALUE)) + .stopTimestamp(toInstant(r.get(TRANSACTION.STOP_TIMESTAMP))) + .stopTimestampFormatted(DateTimeUtils.humanize(r.get(TRANSACTION.STOP_TIMESTAMP))) + .stopValue(r.get(TRANSACTION.STOP_VALUE)) + .stopReason(r.get(TRANSACTION.STOP_REASON)) + .chargeBoxPk(r.get(CHARGE_BOX.CHARGE_BOX_PK)) + .ocppTagPk(r.get(OCPP_TAG.OCPP_TAG_PK)) + .stopEventActor(toDto(r.get(TRANSACTION.STOP_EVENT_ACTOR))) + .build(); + } + + private static TransactionStopEventActor toDto(jooq.steve.db.enums.TransactionStopEventActor actor) { + if (actor == null) { + return null; + } + return TransactionStopEventActor.valueOf(actor.name()); + } +} diff --git a/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/UserMapper.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/UserMapper.java new file mode 100644 index 000000000..34c35842f --- /dev/null +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/UserMapper.java @@ -0,0 +1,54 @@ +/* + * 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.jooq.mapper; + +import de.rwth.idsg.steve.repository.dto.User; +import de.rwth.idsg.steve.web.dto.Address; +import jooq.steve.db.tables.records.UserRecord; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Optional; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class UserMapper { + + public static User.Details fromRecord( + UserRecord r, Optional
address, List ocppTagEntries) { + var builder = User.Details.builder() + .userPk(r.getUserPk()) + .firstName(r.getFirstName()) + .lastName(r.getLastName()) + .birthDay(r.getBirthDay()) + .phone(r.getPhone()) + .sex(r.getSex()) + .eMail(r.getEMail()) + .note(r.getNote()) + .ocppTagEntries(ocppTagEntries); + + address.ifPresent(a -> builder.street(a.getStreet()) + .houseNumber(a.getHouseNumber()) + .zipCode(a.getZipCode()) + .city(a.getCity()) + .country(a.getCountry())); + + return builder.build(); + } +} diff --git a/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/WebUserMapper.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/WebUserMapper.java new file mode 100644 index 000000000..fb1206012 --- /dev/null +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/mapper/WebUserMapper.java @@ -0,0 +1,76 @@ +/* + * 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.jooq.mapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.rwth.idsg.steve.repository.dto.WebUser; +import de.rwth.idsg.steve.service.dto.WebUserOverview; +import de.rwth.idsg.steve.web.dto.WebUserAuthority; +import jooq.steve.db.tables.records.WebUserRecord; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jooq.JSON; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class WebUserMapper { + + public static WebUser fromRecord(WebUserRecord r, ObjectMapper objectMapper) { + return WebUser.builder() + .webUserPk(r.getWebUserPk()) + .login(r.getUsername()) + .password(r.getPassword()) + .apiPassword(r.getApiPassword()) + .enabled(r.getEnabled()) + .authorities(fromJSON(r.getAuthorities(), objectMapper)) + .build(); + } + + public static WebUserOverview overviewFromRecord(WebUserRecord r, ObjectMapper objectMapper) { + return WebUserOverview.builder() + .webUserPk(r.getWebUserPk()) + .webUsername(r.getUsername()) + .enabled(r.getEnabled()) + .authorities(WebUserAuthority.fromJsonValue( + objectMapper, + r.getAuthorities() == null ? null : r.getAuthorities().data())) + .build(); + } + + private static Set fromJSON(JSON json, ObjectMapper objectMapper) { + if (json == null) { + return Collections.emptySet(); + } + try { + Set strings = objectMapper.readValue( + json.data(), objectMapper.getTypeFactory().constructCollectionType(Set.class, String.class)); + return strings.stream().map(WebUserAuthority::fromAuthority).collect(Collectors.toSet()); + } catch (IOException e) { + log.error("Failed to deserialize authorities from JSON", e); + return new HashSet<>(); + } + } +} diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java similarity index 82% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java index 2dd9b507a..9c32a377c 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java @@ -19,17 +19,18 @@ package de.rwth.idsg.steve.repository.impl; import de.rwth.idsg.steve.SteveException; +import de.rwth.idsg.steve.jooq.mapper.AddressMapper; import de.rwth.idsg.steve.repository.AddressRepository; import de.rwth.idsg.steve.web.dto.Address; -import jooq.steve.db.tables.records.AddressRecord; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; -import org.jooq.Record1; -import org.jooq.SelectConditionStep; import org.jooq.exception.DataAccessException; import org.jspecify.annotations.Nullable; import org.springframework.stereotype.Repository; +import java.util.Optional; + import static jooq.steve.db.tables.Address.ADDRESS; /** @@ -38,17 +39,20 @@ */ @Slf4j @Repository +@RequiredArgsConstructor public class AddressRepositoryImpl implements AddressRepository { + private final DSLContext ctx; + @Override - public @Nullable AddressRecord get(DSLContext ctx, Integer addressPk) { + public Optional
get(Integer addressPk) { if (addressPk == null) { - return null; + return Optional.empty(); } return ctx.selectFrom(ADDRESS) .where(ADDRESS.ADDRESS_PK.equal(addressPk)) - .fetchOne(); + .fetchOptional(AddressMapper::fromRecord); } /** @@ -64,29 +68,31 @@ public class AddressRepositoryImpl implements AddressRepository { * */ @Override - public @Nullable Integer updateOrInsert(DSLContext ctx, Address address) { + public @Nullable Integer updateOrInsert(Address address) { if (address.isEmpty()) { return null; } else if (address.getAddressPk() == null) { - return insert(ctx, address); + return insert(address); } else { - update(ctx, address); + update(address); return address.getAddressPk(); } } @Override - public void delete(DSLContext ctx, SelectConditionStep> addressPkSelect) { - ctx.delete(ADDRESS).where(ADDRESS.ADDRESS_PK.eq(addressPkSelect)).execute(); + public void delete(Integer addressPk) { + if (addressPk != null) { + ctx.delete(ADDRESS).where(ADDRESS.ADDRESS_PK.eq(addressPk)).execute(); + } } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- - private Integer insert(DSLContext ctx, Address ad) { + private Integer insert(Address ad) { try { return ctx.insertInto(ADDRESS) .set(ADDRESS.STREET, ad.getStreet()) @@ -102,8 +108,8 @@ private Integer insert(DSLContext ctx, Address ad) { } } - private void update(DSLContext ctx, Address ad) { - int count = ctx.update(ADDRESS) + private void update(Address ad) { + var count = ctx.update(ADDRESS) .set(ADDRESS.STREET, ad.getStreet()) .set(ADDRESS.HOUSE_NUMBER, ad.getHouseNumber()) .set(ADDRESS.ZIP_CODE, ad.getZipCode()) diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java similarity index 85% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java index 00da0877b..84f916c71 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.repository.impl; import de.rwth.idsg.steve.SteveException; +import de.rwth.idsg.steve.jooq.mapper.ChargePointMapper; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.repository.AddressRepository; import de.rwth.idsg.steve.repository.ChargePointRepository; @@ -29,19 +30,13 @@ import de.rwth.idsg.steve.web.dto.ChargePointForm; import de.rwth.idsg.steve.web.dto.ChargePointQueryForm; import de.rwth.idsg.steve.web.dto.ConnectorStatusForm; -import jooq.steve.db.tables.records.AddressRecord; -import jooq.steve.db.tables.records.ChargeBoxRecord; import lombok.extern.slf4j.Slf4j; import ocpp.cs._2015._10.RegistrationStatus; import org.jooq.Condition; import org.jooq.DSLContext; -import org.jooq.Field; -import org.jooq.Record1; import org.jooq.Record5; import org.jooq.Result; -import org.jooq.SelectConditionStep; import org.jooq.SelectQuery; -import org.jooq.Table; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; import org.jspecify.annotations.Nullable; @@ -82,7 +77,7 @@ public ChargePointRepositoryImpl(DSLContext ctx, AddressRepository addressReposi @Override public Optional getRegistrationStatus(String chargeBoxId) { - String status = ctx.select(CHARGE_BOX.REGISTRATION_STATUS) + var status = ctx.select(CHARGE_BOX.REGISTRATION_STATUS) .from(CHARGE_BOX) .where(CHARGE_BOX.CHARGE_BOX_ID.eq(chargeBoxId)) .fetchOne(CHARGE_BOX.REGISTRATION_STATUS); @@ -93,7 +88,7 @@ public Optional getRegistrationStatus(String chargeBoxId) { @Override public List getChargePointSelect( OcppProtocol protocol, List inStatusFilter, List chargeBoxIdFilter) { - Condition chargeBoxIdCondition = CollectionUtils.isEmpty(chargeBoxIdFilter) + var chargeBoxIdCondition = CollectionUtils.isEmpty(chargeBoxIdFilter) ? DSL.trueCondition() : CHARGE_BOX.CHARGE_BOX_ID.in(chargeBoxIdFilter); @@ -146,7 +141,6 @@ public List getOverview(ChargePointQueryForm form) { .build()); } - @SuppressWarnings("unchecked") private Result> getOverviewInternal( ChargePointQueryForm form) { SelectQuery selectQuery = ctx.selectQuery(); @@ -199,7 +193,7 @@ private Result> getOverv @Override public Optional getDetails(int chargeBoxPk) { - ChargeBoxRecord cbr = ctx.selectFrom(CHARGE_BOX) + var cbr = ctx.selectFrom(CHARGE_BOX) .where(CHARGE_BOX.CHARGE_BOX_PK.equal(chargeBoxPk)) .fetchOne(); @@ -207,27 +201,27 @@ public Optional getDetails(int chargeBoxPk) { return Optional.empty(); } - AddressRecord ar = addressRepository.get(ctx, cbr.getAddressPk()); + var ar = addressRepository.get(cbr.getAddressPk()); - return Optional.of(new ChargePoint.Details(cbr, ar)); + return Optional.of(ChargePointMapper.fromRecord(cbr, ar)); } @Override public List getChargePointConnectorStatus(ConnectorStatusForm form) { // find out the latest timestamp for each connector - Field t1Pk = CONNECTOR_STATUS.CONNECTOR_PK.as("t1_pk"); + var t1Pk = CONNECTOR_STATUS.CONNECTOR_PK.as("t1_pk"); var t1TsMax = DSL.max(CONNECTOR_STATUS.STATUS_TIMESTAMP).as("t1_ts_max"); - Table t1 = ctx.select(t1Pk, t1TsMax) + var t1 = ctx.select(t1Pk, t1TsMax) .from(CONNECTOR_STATUS) .groupBy(CONNECTOR_STATUS.CONNECTOR_PK) .asTable("t1"); // get the status table with latest timestamps only - Field t2Pk = CONNECTOR_STATUS.CONNECTOR_PK.as("t2_pk"); + var t2Pk = CONNECTOR_STATUS.CONNECTOR_PK.as("t2_pk"); var t2Ts = CONNECTOR_STATUS.STATUS_TIMESTAMP.as("t2_ts"); - Field t2Status = CONNECTOR_STATUS.STATUS.as("t2_status"); - Field t2Error = CONNECTOR_STATUS.ERROR_CODE.as("t2_error"); - Table t2 = ctx.selectDistinct(t2Pk, t2Ts, t2Status, t2Error) + var t2Status = CONNECTOR_STATUS.STATUS.as("t2_status"); + var t2Error = CONNECTOR_STATUS.ERROR_CODE.as("t2_error"); + var t2 = ctx.selectDistinct(t2Pk, t2Ts, t2Status, t2Error) .from(CONNECTOR_STATUS) .join(t1) .on(CONNECTOR_STATUS.CONNECTOR_PK.equal(t1.field(t1Pk))) @@ -235,13 +229,13 @@ public List getChargePointConnectorStatus(ConnectorStatusForm f .asTable("t2"); // https://github.com/steve-community/steve/issues/691 - Condition chargeBoxCondition = CHARGE_BOX.REGISTRATION_STATUS.eq(RegistrationStatus.ACCEPTED.value()); + var chargeBoxCondition = CHARGE_BOX.REGISTRATION_STATUS.eq(RegistrationStatus.ACCEPTED.value()); if (form != null && form.getChargeBoxId() != null) { chargeBoxCondition = chargeBoxCondition.and(CHARGE_BOX.CHARGE_BOX_ID.eq(form.getChargeBoxId())); } - final Condition statusCondition; + Condition statusCondition; if (form == null || form.getStatus() == null) { statusCondition = DSL.noCondition(); } else { @@ -287,7 +281,7 @@ public List getNonZeroConnectorIds(String chargeBoxId) { @Override public void addChargePointList(List chargeBoxIdList) { - List batch = chargeBoxIdList.stream() + var batch = chargeBoxIdList.stream() .map(s -> ctx.newRecord(CHARGE_BOX).setChargeBoxId(s).setInsertConnectorStatusAfterTransactionMsg(false)) .collect(Collectors.toList()); @@ -298,14 +292,14 @@ public void addChargePointList(List chargeBoxIdList) { @Override public int addChargePoint(ChargePointForm form) { return ctx.transactionResult(configuration -> { - var ctx = DSL.using(configuration); + var transactionCtx = DSL.using(configuration); try { - var addressId = addressRepository.updateOrInsert(ctx, form.getAddress()); - return addChargePointInternal(ctx, form, addressId); + var addressId = addressRepository.updateOrInsert(form.getAddress()); + return addChargePointInternal(transactionCtx, form, addressId); } catch (DataAccessException e) { throw new SteveException.InternalError( - "Failed to add the charge point with chargeBoxId '%s'", form.getChargeBoxId(), e); + "Failed to add the charge point with chargeBoxId '%s'".formatted(form.getChargeBoxId()), e); } }); } @@ -313,14 +307,14 @@ public int addChargePoint(ChargePointForm form) { @Override public void updateChargePoint(ChargePointForm form) { ctx.transaction(configuration -> { - DSLContext ctx = DSL.using(configuration); + var transactionCtx = DSL.using(configuration); try { - Integer addressId = addressRepository.updateOrInsert(ctx, form.getAddress()); - updateChargePointInternal(ctx, form, addressId); + var addressId = addressRepository.updateOrInsert(form.getAddress()); + updateChargePointInternal(transactionCtx, form, addressId); } catch (DataAccessException e) { throw new SteveException.InternalError( - "Failed to update the charge point with chargeBoxId '%s'", form.getChargeBoxId(), e); + "Failed to update the charge point with chargeBoxId '%s'".formatted(form.getChargeBoxId()), e); } }); } @@ -328,10 +322,11 @@ public void updateChargePoint(ChargePointForm form) { @Override public void deleteChargePoint(int chargeBoxPk) { ctx.transaction(configuration -> { - DSLContext ctx = DSL.using(configuration); + var transactionCtx = DSL.using(configuration); try { - addressRepository.delete(ctx, selectAddressId(chargeBoxPk)); - deleteChargePointInternal(ctx, chargeBoxPk); + var addressPk = selectAddressId(chargeBoxPk); + addressRepository.delete(addressPk); + deleteChargePointInternal(transactionCtx, chargeBoxPk); } catch (DataAccessException e) { throw new SteveException.InternalError("Failed to delete the charge point", e); @@ -343,12 +338,16 @@ public void deleteChargePoint(int chargeBoxPk) { // Helpers // ------------------------------------------------------------------------- - private SelectConditionStep> selectAddressId(int chargeBoxPk) { - return ctx.select(CHARGE_BOX.ADDRESS_PK).from(CHARGE_BOX).where(CHARGE_BOX.CHARGE_BOX_PK.eq(chargeBoxPk)); + private Integer selectAddressId(int chargeBoxPk) { + return ctx.select(CHARGE_BOX.ADDRESS_PK) + .from(CHARGE_BOX) + .where(CHARGE_BOX.CHARGE_BOX_PK.eq(chargeBoxPk)) + .fetchOne(CHARGE_BOX.ADDRESS_PK); } - private int addChargePointInternal(DSLContext ctx, ChargePointForm form, Integer addressPk) { - return ctx.insertInto(CHARGE_BOX) + private int addChargePointInternal(DSLContext transactionCtx, ChargePointForm form, Integer addressPk) { + return transactionCtx + .insertInto(CHARGE_BOX) .set(CHARGE_BOX.CHARGE_BOX_ID, form.getChargeBoxId()) .set(CHARGE_BOX.DESCRIPTION, form.getDescription()) .set(CHARGE_BOX.LOCATION_LATITUDE, form.getLocationLatitude()) @@ -365,8 +364,10 @@ private int addChargePointInternal(DSLContext ctx, ChargePointForm form, Integer .getChargeBoxPk(); } - private void updateChargePointInternal(DSLContext ctx, ChargePointForm form, @Nullable Integer addressPk) { - ctx.update(CHARGE_BOX) + private void updateChargePointInternal( + DSLContext transactionCtx, ChargePointForm form, @Nullable Integer addressPk) { + transactionCtx + .update(CHARGE_BOX) .set(CHARGE_BOX.DESCRIPTION, form.getDescription()) .set(CHARGE_BOX.LOCATION_LATITUDE, form.getLocationLatitude()) .set(CHARGE_BOX.LOCATION_LONGITUDE, form.getLocationLongitude()) @@ -381,8 +382,9 @@ private void updateChargePointInternal(DSLContext ctx, ChargePointForm form, @Nu .execute(); } - private void deleteChargePointInternal(DSLContext ctx, int chargeBoxPk) { - ctx.delete(CHARGE_BOX) + private void deleteChargePointInternal(DSLContext transactionCtx, int chargeBoxPk) { + transactionCtx + .delete(CHARGE_BOX) .where(CHARGE_BOX.CHARGE_BOX_PK.equal(chargeBoxPk)) .execute(); } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java similarity index 95% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java index 3ee80a56c..3037ffd9a 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.repository.impl; import de.rwth.idsg.steve.SteveException; +import de.rwth.idsg.steve.jooq.mapper.ChargingProfileMapper; import de.rwth.idsg.steve.repository.ChargingProfileRepository; import de.rwth.idsg.steve.repository.dto.ChargingProfile; import de.rwth.idsg.steve.repository.dto.ChargingProfileAssignment; @@ -30,8 +31,6 @@ import ocpp.cp._2015._10.ChargingProfilePurposeType; import org.jooq.Condition; import org.jooq.DSLContext; -import org.jooq.Record1; -import org.jooq.SelectConditionStep; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; import org.jspecify.annotations.Nullable; @@ -70,7 +69,7 @@ public class ChargingProfileRepositoryImpl implements ChargingProfileRepository public void setProfile(int chargingProfilePk, String chargeBoxId, int connectorId) { OcppServerRepositoryImpl.insertIgnoreConnector(ctx, chargeBoxId, connectorId); - SelectConditionStep> connectorPkSelect = ctx.select(CONNECTOR.CONNECTOR_PK) + var connectorPkSelect = ctx.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.eq(chargeBoxId)) .and(CONNECTOR.CONNECTOR_ID.eq(connectorId)); @@ -83,7 +82,7 @@ public void setProfile(int chargingProfilePk, String chargeBoxId, int connectorI @Override public void clearProfile(int chargingProfilePk, String chargeBoxId) { - SelectConditionStep> connectorPkSelect = + var connectorPkSelect = ctx.select(CONNECTOR.CONNECTOR_PK).from(CONNECTOR).where(CONNECTOR.CHARGE_BOX_ID.eq(chargeBoxId)); ctx.delete(CONNECTOR_CHARGING_PROFILE) @@ -115,7 +114,6 @@ public void clearProfile( // ------------------------------------------------------------------------- Condition profilePkCondition; - if (purpose == null && stackLevel == null) { profilePkCondition = DSL.trueCondition(); } else { @@ -254,20 +252,17 @@ public List getOverview(ChargingProfileQueryForm form) @Override public Optional getDetails(int chargingProfilePk) { - var profile = ctx.selectFrom(CHARGING_PROFILE) + return ctx.selectFrom(CHARGING_PROFILE) .where(CHARGING_PROFILE.CHARGING_PROFILE_PK.eq(chargingProfilePk)) - .fetchOne(); - - if (profile == null) { - return Optional.empty(); - } - - var periods = ctx.selectFrom(CHARGING_SCHEDULE_PERIOD) - .where(CHARGING_SCHEDULE_PERIOD.CHARGING_PROFILE_PK.eq(chargingProfilePk)) - .orderBy(CHARGING_SCHEDULE_PERIOD.START_PERIOD_IN_SECONDS.asc()) - .fetch(); - - return Optional.of(new ChargingProfile.Details(profile, periods)); + .fetchOptional() + .map(profile -> { + var periods = ctx.selectFrom(CHARGING_SCHEDULE_PERIOD) + .where(CHARGING_SCHEDULE_PERIOD.CHARGING_PROFILE_PK.eq(chargingProfilePk)) + .orderBy(CHARGING_SCHEDULE_PERIOD.START_PERIOD_IN_SECONDS.asc()) + .fetch(); + + return ChargingProfileMapper.fromRecord(profile, periods); + }); } @Override @@ -357,7 +352,7 @@ public void update(ChargingProfileForm form) { insertPeriods(ctx, form); } catch (DataAccessException e) { throw new SteveException.InternalError( - "Failed to update the charging profile with id '%s'", form.getChargingProfilePk(), e); + "Failed to update the charging profile with id '%s'".formatted(form.getChargingProfilePk()), e); } }); } @@ -380,7 +375,8 @@ private void checkProfileUsage(int chargingProfilePk) { .fetch(CONNECTOR.CHARGE_BOX_ID); if (!r.isEmpty()) { throw new SteveException.InternalError( - "Cannot modify this charging profile, since the following stations are still using it: %s", r); + "Cannot modify this charging profile, since the following stations are still using it: %s" + .formatted(r)); } } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java similarity index 76% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java index 133160103..f85984977 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java @@ -24,18 +24,18 @@ import de.rwth.idsg.steve.repository.dto.DbVersion; import de.rwth.idsg.steve.utils.DateTimeUtils; import de.rwth.idsg.steve.web.dto.Statistics; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; import org.jooq.DatePart; import org.jooq.Field; -import org.jooq.Record9; import org.jooq.impl.DSL; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; +import java.util.Optional; import static de.rwth.idsg.steve.utils.CustomDSL.date; import static de.rwth.idsg.steve.utils.CustomDSL.timestampDiff; @@ -56,10 +56,10 @@ */ @Slf4j @Repository +@RequiredArgsConstructor public class GenericRepositoryImpl implements GenericRepository { - @Autowired - private DSLContext ctx; + private final DSLContext ctx; @EventListener public void afterStart(ContextRefreshedEvent event) { @@ -68,17 +68,15 @@ public void afterStart(ContextRefreshedEvent event) { @Override public void checkJavaAndMySQLOffsets() { - long java = DateTimeUtils.getOffsetFromUtcInSeconds(); + var java = DateTimeUtils.getOffsetFromUtcInSeconds(); - long sql = ctx.select(timestampDiff(DatePart.SECOND, utcTimestamp(), DSL.currentTimestamp())) + var sql = ctx.select(timestampDiff(DatePart.SECOND, utcTimestamp(), DSL.currentTimestamp())) .fetchOne() .getValue(0, Long.class); - if (sql != java) { - throw new SteveException.InternalError( - "MySQL and Java are not using the same time zone. " - + "Java offset in seconds (%s) != MySQL offset in seconds (%s)", - java, sql); + if (sql == null || sql != java) { + throw new SteveException.InternalError("MySQL and Java are not using the same time zone. " + + "Java offset in seconds (%s) != MySQL offset in seconds (%s)".formatted(java, sql)); } } @@ -122,7 +120,7 @@ public Statistics getStats() { Field numWebUsers = ctx.selectCount().from(WEB_USER).asField("num_webusers"); - Record9 gs = ctx.select( + var row = ctx.select( numChargeBoxes, numOcppTags, numUsers, @@ -132,30 +130,35 @@ public Statistics getStats() { heartbeatsYesterday, heartbeatsEarlier, numWebUsers) - .fetchOne(); + .fetchSingle(); return Statistics.builder() - .numChargeBoxes(gs.value1()) - .numOcppTags(gs.value2()) - .numUsers(gs.value3()) - .numReservations(gs.value4()) - .numTransactions(gs.value5()) - .heartbeatToday(gs.value6()) - .heartbeatYesterday(gs.value7()) - .heartbeatEarlier(gs.value8()) - .numWebUsers(gs.value9()) + .numChargeBoxes(row.get(numChargeBoxes)) + .numOcppTags(row.get(numOcppTags)) + .numUsers(row.get(numUsers)) + .numReservations(row.get(numReservations)) + .numTransactions(row.get(numTransactions)) + .heartbeatToday(row.get(heartbeatsToday)) + .heartbeatYesterday(row.get(heartbeatsYesterday)) + .heartbeatEarlier(row.get(heartbeatsEarlier)) + .numWebUsers(row.get(numWebUsers)) .build(); } @Override - public DbVersion getDBVersion() { - var record = ctx.select(SCHEMA_VERSION.VERSION, SCHEMA_VERSION.INSTALLED_ON) + public Optional getDBVersion() { + var schemaVersion = ctx.select(SCHEMA_VERSION.VERSION, SCHEMA_VERSION.INSTALLED_ON) .from(SCHEMA_VERSION) .where(SCHEMA_VERSION.INSTALLED_RANK.eq( select(max(SCHEMA_VERSION.INSTALLED_RANK)).from(SCHEMA_VERSION))) .fetchOne(); - - String ts = DateTimeUtils.humanize(record.value2()); - return DbVersion.builder().version(record.value1()).updateTimestamp(ts).build(); + if (schemaVersion == null) { + return Optional.empty(); + } + var ts = DateTimeUtils.humanize(schemaVersion.value2()); + return Optional.of(DbVersion.builder() + .version(schemaVersion.value1()) + .updateTimestamp(ts) + .build()); } } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java similarity index 87% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java index 74b0dc3b0..1e5a1c2fa 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java @@ -31,7 +31,6 @@ import de.rwth.idsg.steve.repository.dto.UpdateTransactionParams; import jooq.steve.db.enums.TransactionStopEventActor; import jooq.steve.db.enums.TransactionStopFailedEventActor; -import jooq.steve.db.tables.records.ConnectorMeterValueRecord; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -115,7 +114,7 @@ public void updateEndpointAddress(String chargeBoxIdentity, String endpointAddre public void updateChargeboxFirmwareStatus(String chargeBoxIdentity, String firmwareStatus) { ctx.update(CHARGE_BOX) .set(CHARGE_BOX.FW_UPDATE_STATUS, firmwareStatus) - .set(CHARGE_BOX.FW_UPDATE_TIMESTAMP, LocalDateTime.now()) + .set(CHARGE_BOX.FW_UPDATE_TIMESTAMP, DSL.currentLocalDateTime()) .where(CHARGE_BOX.CHARGE_BOX_ID.equal(chargeBoxIdentity)) .execute(); } @@ -124,7 +123,7 @@ public void updateChargeboxFirmwareStatus(String chargeBoxIdentity, String firmw public void updateChargeboxDiagnosticsStatus(String chargeBoxIdentity, String status) { ctx.update(CHARGE_BOX) .set(CHARGE_BOX.DIAGNOSTICS_STATUS, status) - .set(CHARGE_BOX.DIAGNOSTICS_TIMESTAMP, LocalDateTime.now()) + .set(CHARGE_BOX.DIAGNOSTICS_TIMESTAMP, DSL.currentLocalDateTime()) .where(CHARGE_BOX.CHARGE_BOX_ID.equal(chargeBoxIdentity)) .execute(); } @@ -140,7 +139,7 @@ public void updateChargeboxHeartbeat(String chargeBoxIdentity, Instant instant) @Override public void insertConnectorStatus(InsertConnectorStatusParams p) { ctx.transaction(configuration -> { - DSLContext ctx = DSL.using(configuration); + var ctx = DSL.using(configuration); // Step 1 insertIgnoreConnector(ctx, p.getChargeBoxId(), p.getConnectorId()); @@ -177,10 +176,10 @@ public void insertMeterValues( ctx.transaction(configuration -> { try { - DSLContext ctx = DSL.using(configuration); + var ctx = DSL.using(configuration); insertIgnoreConnector(ctx, chargeBoxIdentity, connectorId); - int connectorPk = getConnectorPkFromConnector(ctx, chargeBoxIdentity, connectorId); + var connectorPk = getConnectorPkFromConnector(ctx, chargeBoxIdentity, connectorId); batchInsertMeterValues(ctx, list, connectorPk, transactionId); } catch (Exception e) { log.error("Exception occurred", e); @@ -196,14 +195,15 @@ public void insertMeterValues(String chargeBoxIdentity, List list, i ctx.transaction(configuration -> { try { - DSLContext ctx = DSL.using(configuration); + var ctx = DSL.using(configuration); // First, get connector primary key from transaction table - int connectorPk = ctx.select(TRANSACTION_START.CONNECTOR_PK) + var connectorPk = ctx.select(TRANSACTION_START.CONNECTOR_PK) .from(TRANSACTION_START) .where(TRANSACTION_START.TRANSACTION_PK.equal(transactionId)) - .fetchOne() - .value1(); + .fetchOptional(TRANSACTION_START.CONNECTOR_PK) + .orElseThrow(() -> + new SteveException.InternalError("No connectorPk for transactionId=" + transactionId)); batchInsertMeterValues(ctx, list, connectorPk, transactionId); } catch (Exception e) { @@ -215,7 +215,7 @@ public void insertMeterValues(String chargeBoxIdentity, List list, i @Override public int insertTransaction(InsertTransactionParams p) { - SelectConditionStep> connectorPkQuery = DSL.select(CONNECTOR.CONNECTOR_PK) + var connectorPkQuery = DSL.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.equal(p.getChargeBoxId())) .and(CONNECTOR.CONNECTOR_ID.equal(p.getConnectorId())); @@ -227,14 +227,14 @@ public int insertTransaction(InsertTransactionParams p) { insertIgnoreConnector(ctx, p.getChargeBoxId(), p.getConnectorId()); // it is important to insert idTag before transaction, since the transaction table references it - boolean unknownTagInserted = insertIgnoreIdTag(ctx, p); + var unknownTagInserted = insertIgnoreIdTag(ctx, p); // ------------------------------------------------------------------------- // Step 2: Insert transaction if it does not exist already // ------------------------------------------------------------------------- - TransactionDataHolder data = insertIgnoreTransaction(p, connectorPkQuery); - int transactionId = data.transactionId; + var data = insertIgnoreTransaction(p, connectorPkQuery); + var transactionId = data.transactionId; if (data.existsAlready) { return transactionId; @@ -252,8 +252,10 @@ public int insertTransaction(InsertTransactionParams p) { // Step 3 for OCPP >= 1.5: A startTransaction may be related to a reservation // ------------------------------------------------------------------------- + var connectorPk = getConnectorPkFromConnector(ctx, p.getChargeBoxId(), p.getConnectorId()); + if (p.isSetReservationId()) { - reservationRepository.used(connectorPkQuery, p.getIdTag(), p.getReservationId(), transactionId); + reservationRepository.used(connectorPk, p.getIdTag(), p.getReservationId(), transactionId); } // ------------------------------------------------------------------------- @@ -261,7 +263,7 @@ public int insertTransaction(InsertTransactionParams p) { // ------------------------------------------------------------------------- if (shouldInsertConnectorStatusAfterTransactionMsg(p.getChargeBoxId())) { - insertConnectorStatus(ctx, connectorPkQuery, toLocalDateTime(p.getStartTimestamp()), p.getStatusUpdate()); + insertConnectorStatus(ctx, connectorPk, toLocalDateTime(p.getStartTimestamp()), p.getStatusUpdate()); } return transactionId; @@ -279,7 +281,7 @@ public void updateTransaction(UpdateTransactionParams p) { ctx.insertInto(TRANSACTION_STOP) .set(TRANSACTION_STOP.TRANSACTION_PK, p.getTransactionId()) .set(TRANSACTION_STOP.EVENT_TIMESTAMP, toLocalDateTime(p.getEventTimestamp())) - .set(TRANSACTION_STOP.EVENT_ACTOR, p.getEventActor()) + .set(TRANSACTION_STOP.EVENT_ACTOR, toJooq(p.getEventActor())) .set(TRANSACTION_STOP.STOP_TIMESTAMP, toLocalDateTime(p.getStopTimestamp())) .set(TRANSACTION_STOP.STOP_VALUE, p.getStopMeterValue()) .set(TRANSACTION_STOP.STOP_REASON, p.getStopReason()) @@ -295,11 +297,13 @@ public void updateTransaction(UpdateTransactionParams p) { // ------------------------------------------------------------------------- if (shouldInsertConnectorStatusAfterTransactionMsg(p.getChargeBoxId())) { - SelectConditionStep> connectorPkQuery = DSL.select(TRANSACTION_START.CONNECTOR_PK) + var connectorPk = DSL.select(TRANSACTION_START.CONNECTOR_PK) .from(TRANSACTION_START) - .where(TRANSACTION_START.TRANSACTION_PK.equal(p.getTransactionId())); + .where(TRANSACTION_START.TRANSACTION_PK.equal(p.getTransactionId())) + .fetchOne() + .value1(); - insertConnectorStatus(ctx, connectorPkQuery, toLocalDateTime(p.getStopTimestamp()), p.getStatusUpdate()); + insertConnectorStatus(ctx, connectorPk, toLocalDateTime(p.getStopTimestamp()), p.getStatusUpdate()); } } @@ -320,10 +324,10 @@ private static final class TransactionDataHolder { */ private TransactionDataHolder insertIgnoreTransaction( InsertTransactionParams p, SelectConditionStep> connectorPkQuery) { - Lock l = transactionTableLocks.get(p.getChargeBoxId()); + var l = transactionTableLocks.get(p.getChargeBoxId()); l.lock(); try { - Record1 r = ctx.select(TRANSACTION_START.TRANSACTION_PK) + var r = ctx.select(TRANSACTION_START.TRANSACTION_PK) .from(TRANSACTION_START) .where(TRANSACTION_START.CONNECTOR_PK.eq(connectorPkQuery)) .and(TRANSACTION_START.ID_TAG.eq(p.getIdTag())) @@ -335,7 +339,7 @@ private TransactionDataHolder insertIgnoreTransaction( return new TransactionDataHolder(true, r.value1()); } - Integer transactionId = ctx.insertInto(TRANSACTION_START) + var transactionId = ctx.insertInto(TRANSACTION_START) .set(TRANSACTION_START.EVENT_TIMESTAMP, toLocalDateTime(p.getEventTimestamp())) .set(TRANSACTION_START.CONNECTOR_PK, connectorPkQuery) .set(TRANSACTION_START.ID_TAG, p.getIdTag()) @@ -366,13 +370,10 @@ private TransactionDataHolder insertIgnoreTransaction( * and we have a "more recent" status, it will still be the current status. */ private void insertConnectorStatus( - DSLContext ctx, - SelectConditionStep> connectorPkQuery, - LocalDateTime timestamp, - TransactionStatusUpdate statusUpdate) { + DSLContext ctx, int connectorPk, LocalDateTime timestamp, TransactionStatusUpdate statusUpdate) { try { ctx.insertInto(CONNECTOR_STATUS) - .set(CONNECTOR_STATUS.CONNECTOR_PK, connectorPkQuery) + .set(CONNECTOR_STATUS.CONNECTOR_PK, connectorPk) .set(CONNECTOR_STATUS.STATUS_TIMESTAMP, timestamp) .set(CONNECTOR_STATUS.STATUS, statusUpdate.getStatus()) .set(CONNECTOR_STATUS.ERROR_CODE, statusUpdate.getErrorCode()) @@ -386,7 +387,7 @@ private void insertConnectorStatus( * If the connector information was not received before, insert it. Otherwise, ignore. */ public static void insertIgnoreConnector(DSLContext ctx, String chargeBoxIdentity, int connectorId) { - int count = ctx.insertInto(CONNECTOR, CONNECTOR.CHARGE_BOX_ID, CONNECTOR.CONNECTOR_ID) + var count = ctx.insertInto(CONNECTOR, CONNECTOR.CHARGE_BOX_ID, CONNECTOR.CONNECTOR_ID) .values(chargeBoxIdentity, connectorId) .onDuplicateKeyIgnore() // Important detail .execute(); @@ -402,10 +403,10 @@ public static void insertIgnoreConnector(DSLContext ctx, String chargeBoxIdentit * details will not be inserted into DB and we will lose valuable information. */ private boolean insertIgnoreIdTag(DSLContext ctx, InsertTransactionParams p) { - String note = "This unknown idTag was used in a transaction that started @ " + p.getStartTimestamp() + var note = "This unknown idTag was used in a transaction that started @ " + p.getStartTimestamp() + ". It was reported @ " + LocalDateTime.now() + "."; - int count = ctx.insertInto(OCPP_TAG) + var count = ctx.insertInto(OCPP_TAG) .set(OCPP_TAG.ID_TAG, p.getIdTag()) .set(OCPP_TAG.NOTE, note) .set(OCPP_TAG.MAX_ACTIVE_TRANSACTION_COUNT, 0) @@ -416,7 +417,7 @@ private boolean insertIgnoreIdTag(DSLContext ctx, InsertTransactionParams p) { } private boolean shouldInsertConnectorStatusAfterTransactionMsg(String chargeBoxId) { - Record1 r = ctx.selectOne() + var r = ctx.selectOne() .from(CHARGE_BOX) .where(CHARGE_BOX.CHARGE_BOX_ID.eq(chargeBoxId)) .and(CHARGE_BOX.INSERT_CONNECTOR_STATUS_AFTER_TRANSACTION_MSG.isTrue()) @@ -430,12 +431,13 @@ private int getConnectorPkFromConnector(DSLContext ctx, String chargeBoxIdentity .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxIdentity)) .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)) - .fetchOne() - .value1(); + .fetchOptional(CONNECTOR.CONNECTOR_PK) + .orElseThrow(() -> new SteveException.InternalError( + "No connectorPk for %s/%d".formatted(chargeBoxIdentity, connectorId))); } private void batchInsertMeterValues(DSLContext ctx, List list, int connectorPk, Integer transactionId) { - List batch = list.stream() + var batch = list.stream() .flatMap(t -> t.getSampledValue().stream().map(k -> ctx.newRecord(CONNECTOR_METER_VALUE) .setConnectorPk(connectorPk) .setTransactionPk(transactionId) @@ -471,13 +473,21 @@ private void tryInsertingFailed(UpdateTransactionParams p, Exception e) { } } - private static TransactionStopFailedEventActor mapActor(TransactionStopEventActor a) { - for (TransactionStopFailedEventActor b : TransactionStopFailedEventActor.values()) { - if (b.getLiteral().equalsIgnoreCase(a.getLiteral())) { + private static TransactionStopFailedEventActor mapActor( + de.rwth.idsg.steve.repository.dto.TransactionStopEventActor a) { + for (var b : TransactionStopFailedEventActor.values()) { + if (b.name().equalsIgnoreCase(a.name())) { return b; } } // if unknown, do not throw exceptions. just insert manual. return TransactionStopFailedEventActor.manual; } + + private static TransactionStopEventActor toJooq(de.rwth.idsg.steve.repository.dto.TransactionStopEventActor actor) { + if (actor == null) { + return null; + } + return TransactionStopEventActor.valueOf(actor.name()); + } } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java similarity index 89% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java index f96c8313a..52c9045fa 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java @@ -19,29 +19,29 @@ package de.rwth.idsg.steve.repository.impl; import de.rwth.idsg.steve.SteveException; +import de.rwth.idsg.steve.jooq.mapper.OcppTagMapper; import de.rwth.idsg.steve.repository.OcppTagRepository; import de.rwth.idsg.steve.repository.dto.OcppTag.OcppTagOverview; +import de.rwth.idsg.steve.repository.dto.OcppTagActivity; import de.rwth.idsg.steve.web.dto.OcppTagForm; import de.rwth.idsg.steve.web.dto.OcppTagQueryForm; -import jooq.steve.db.tables.OcppTagActivity; import jooq.steve.db.tables.records.OcppTagActivityRecord; -import jooq.steve.db.tables.records.OcppTagRecord; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; import org.jooq.JoinType; import org.jooq.Record10; import org.jooq.RecordMapper; -import org.jooq.Result; import org.jooq.SelectQuery; import org.jooq.TableField; import org.jooq.exception.DataAccessException; +import org.jooq.impl.DSL; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.sql.SQLIntegrityConstraintViolationException; import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; import static de.rwth.idsg.steve.repository.impl.RepositoryUtils.ocppTagByUserIdQuery; import static de.rwth.idsg.steve.utils.CustomDSL.includes; @@ -68,12 +68,11 @@ public OcppTagRepositoryImpl(DSLContext ctx) { } @Override - @SuppressWarnings("unchecked") public List getOverview(OcppTagQueryForm form) { SelectQuery selectQuery = ctx.selectQuery(); selectQuery.addFrom(OCPP_TAG_ACTIVITY); - OcppTagActivity parentTable = OCPP_TAG_ACTIVITY.as("parent"); + var parentTable = OCPP_TAG_ACTIVITY.as("parent"); selectQuery.addSelect( OCPP_TAG_ACTIVITY.OCPP_TAG_PK, @@ -115,12 +114,13 @@ public List getOverview(OcppTagQueryForm form) { case ALL -> { // want all: no filter } - case TRUE -> selectQuery.addConditions(OCPP_TAG_ACTIVITY.EXPIRY_DATE.lessOrEqual(LocalDateTime.now())); + case TRUE -> + selectQuery.addConditions(OCPP_TAG_ACTIVITY.EXPIRY_DATE.lessOrEqual(DSL.currentLocalDateTime())); case FALSE -> selectQuery.addConditions(OCPP_TAG_ACTIVITY .EXPIRY_DATE .isNull() - .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now()))); + .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(DSL.currentLocalDateTime()))); default -> throw new SteveException.InternalError("Unknown enum type"); } @@ -131,29 +131,29 @@ public List getOverview(OcppTagQueryForm form) { } @Override - public Result getRecords() { - return ctx.selectFrom(OCPP_TAG_ACTIVITY).fetch(); + public List getRecords() { + return ctx.selectFrom(OCPP_TAG_ACTIVITY).fetch(OcppTagMapper::fromRecord); } @Override - public Result getRecords(List idTagList) { + public List getRecords(List idTagList) { return ctx.selectFrom(OCPP_TAG_ACTIVITY) .where(OCPP_TAG_ACTIVITY.ID_TAG.in(idTagList)) - .fetch(); + .fetch(OcppTagMapper::fromRecord); } @Override - public OcppTagActivityRecord getRecord(String idTag) { + public Optional getRecord(String idTag) { return ctx.selectFrom(OCPP_TAG_ACTIVITY) .where(OCPP_TAG_ACTIVITY.ID_TAG.equal(idTag)) - .fetchOne(); + .fetchOptional(OcppTagMapper::fromRecord); } @Override - public OcppTagActivityRecord getRecord(int ocppTagPk) { + public Optional getRecord(int ocppTagPk) { return ctx.selectFrom(OCPP_TAG_ACTIVITY) .where(OCPP_TAG_ACTIVITY.OCPP_TAG_PK.equal(ocppTagPk)) - .fetchOne(); + .fetchOptional(OcppTagMapper::fromRecord); } @Override @@ -184,7 +184,7 @@ public List getActiveIdTags() { .and(OCPP_TAG_ACTIVITY .EXPIRY_DATE .isNull() - .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now()))) + .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(DSL.currentLocalDateTime()))) .fetch(OCPP_TAG_ACTIVITY.ID_TAG); } @@ -207,8 +207,8 @@ public String getParentIdTag(String idTag) { @Override public void addOcppTagList(List idTagList) { - List batch = - idTagList.stream().map(s -> ctx.newRecord(OCPP_TAG).setIdTag(s)).collect(Collectors.toList()); + var batch = + idTagList.stream().map(s -> ctx.newRecord(OCPP_TAG).setIdTag(s)).toList(); ctx.batchInsert(batch).execute(); } @@ -228,10 +228,11 @@ public int addOcppTag(OcppTagForm u) { } catch (DataAccessException e) { if (e.getCause() instanceof SQLIntegrityConstraintViolationException) { - throw new SteveException.AlreadyExists("A user with idTag '%s' already exists.", u.getIdTag()); + throw new SteveException.AlreadyExists( + "A user with idTag '%s' already exists.".formatted(u.getIdTag()), e); } else { throw new SteveException.InternalError( - "Execution of addOcppTag for idTag '%s' FAILED.", u.getIdTag(), e); + "Execution of addOcppTag for idTag '%s' FAILED.".formatted(u.getIdTag()), e); } } } @@ -248,7 +249,7 @@ public void updateOcppTag(OcppTagForm u) { .execute(); } catch (DataAccessException e) { throw new SteveException.InternalError( - "Execution of updateOcppTag for idTag '%s' FAILED.", u.getIdTag(), e); + "Execution of updateOcppTag for idTag '%s' FAILED.".formatted(u.getIdTag()), e); } } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/RepositoryUtils.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/RepositoryUtils.java similarity index 100% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/RepositoryUtils.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/RepositoryUtils.java diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java similarity index 94% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java index db9f29da2..668615ebd 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java @@ -28,11 +28,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; -import org.jooq.Record1; import org.jooq.Record10; import org.jooq.RecordMapper; -import org.jooq.Select; -import org.jooq.SelectConditionStep; import org.jooq.SelectQuery; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; @@ -63,7 +60,7 @@ public class ReservationRepositoryImpl implements ReservationRepository { @Override public Optional getReservation(int id) { - Reservation result = ctx.select( + var result = ctx.select( RESERVATION.RESERVATION_PK, RESERVATION.TRANSACTION_PK, OCPP_TAG.OCPP_TAG_PK, @@ -139,7 +136,7 @@ public List getActiveReservationIds(String chargeBoxId) { .where(RESERVATION.CONNECTOR_PK.in(DSL.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)))) - .and(RESERVATION.EXPIRY_DATETIME.greaterThan(LocalDateTime.now())) + .and(RESERVATION.EXPIRY_DATETIME.greaterThan(DSL.currentLocalDateTime())) .and(RESERVATION.STATUS.equal(ReservationStatus.ACCEPTED.name())) .fetch(RESERVATION.RESERVATION_PK); } @@ -149,12 +146,12 @@ public int insert(InsertReservationParams params) { // Check overlapping // isOverlapping(startTimestamp, expiryTimestamp, chargeBoxId); - SelectConditionStep> connectorPkQuery = DSL.select(CONNECTOR.CONNECTOR_PK) + var connectorPkQuery = DSL.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.equal(params.getChargeBoxId())) .and(CONNECTOR.CONNECTOR_ID.equal(params.getConnectorId())); - int reservationId = ctx.insertInto(RESERVATION) + var reservationId = ctx.insertInto(RESERVATION) .set(RESERVATION.CONNECTOR_PK, connectorPkQuery) .set(RESERVATION.ID_TAG, params.getIdTag()) .set(RESERVATION.START_DATETIME, toLocalDateTime(params.getStartTimestamp())) @@ -188,14 +185,13 @@ public void cancelled(int reservationId) { } @Override - public void used( - Select> connectorPkSelect, String ocppIdTag, int reservationId, int transactionId) { - int count = ctx.update(RESERVATION) + public void used(int connectorPk, String ocppIdTag, int reservationId, int transactionId) { + var count = ctx.update(RESERVATION) .set(RESERVATION.STATUS, ReservationStatus.USED.name()) .set(RESERVATION.TRANSACTION_PK, transactionId) .where(RESERVATION.RESERVATION_PK.equal(reservationId)) .and(RESERVATION.ID_TAG.equal(ocppIdTag)) - .and(RESERVATION.CONNECTOR_PK.equal(connectorPkSelect)) + .and(RESERVATION.CONNECTOR_PK.equal(connectorPk)) .and(RESERVATION.STATUS.eq(ReservationStatus.ACCEPTED.name())) .execute(); @@ -269,7 +265,8 @@ private void internalUpdateReservation(int reservationId, ReservationStatus stat private void processType(SelectQuery selectQuery, ReservationQueryForm form) { switch (form.getPeriodType()) { - case ACTIVE -> selectQuery.addConditions(RESERVATION.EXPIRY_DATETIME.greaterThan(LocalDateTime.now())); + case ACTIVE -> + selectQuery.addConditions(RESERVATION.EXPIRY_DATETIME.greaterThan(DSL.currentLocalDateTime())); case FROM_TO -> selectQuery.addConditions( RESERVATION.START_DATETIME.greaterOrEqual(toLocalDateTime(form.getFrom())), diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java similarity index 84% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java index 9c1612c5b..31a5bc66b 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java @@ -24,16 +24,15 @@ import de.rwth.idsg.steve.repository.dto.MailSettings; import de.rwth.idsg.steve.web.dto.SettingsForm; import jooq.steve.db.tables.records.SettingsRecord; +import lombok.RequiredArgsConstructor; import org.jooq.DSLContext; import org.jooq.exception.DataAccessException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import static de.rwth.idsg.steve.utils.StringUtils.joinByComma; import static de.rwth.idsg.steve.utils.StringUtils.splitByComma; @@ -44,6 +43,7 @@ * @since 06.11.2015 */ @Repository +@RequiredArgsConstructor public class SettingsRepositoryImpl implements SettingsRepository { // Totally unnecessary to specify charset here. We just do it to make findbugs plugin happy. @@ -52,15 +52,24 @@ public class SettingsRepositoryImpl implements SettingsRepository { Base64.getEncoder().encode("SteckdosenVerwaltung".getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); - @Autowired - private DSLContext ctx; + private static List parseRecipients(SettingsRecord r) { + return splitByComma(r.getMailRecipients()); + } + + private static List parseEnabledFeatures(SettingsRecord r) { + return splitByComma(r.getNotificationFeatures()).stream() + .map(NotificationFeature::fromName) + .toList(); + } + + private final DSLContext ctx; @Override public SettingsForm getForm() { - SettingsRecord r = getInternal(); + var r = getInternal(); - List eMails = splitByComma(r.getMailRecipients()); - List features = splitFeatures(r.getNotificationFeatures()); + var emails = parseRecipients(r); + var features = parseEnabledFeatures(r); return SettingsForm.builder() .heartbeat(toMin(r.getHeartbeatIntervalInSeconds())) @@ -72,17 +81,17 @@ public SettingsForm getForm() { .from(r.getMailFrom()) .protocol(r.getMailProtocol()) .port(r.getMailPort()) - .recipients(eMails) + .recipients(emails) .enabledFeatures(features) .build(); } @Override public MailSettings getMailSettings() { - SettingsRecord r = getInternal(); + var r = getInternal(); - List eMails = splitByComma(r.getMailRecipients()); - List features = splitFeatures(r.getNotificationFeatures()); + var eMails = parseRecipients(r); + var features = parseEnabledFeatures(r); return MailSettings.builder() .enabled(r.getMailEnabled()) @@ -109,8 +118,8 @@ public int getHoursToExpire() { @Override public void update(SettingsForm form) { - String eMails = joinByComma(form.getRecipients()); - String features = joinByComma(form.getEnabledFeatures()); + var eMails = joinByComma(form.getRecipients()); + var features = joinByComma(form.getEnabledFeatures()); try { ctx.update(SETTINGS) @@ -144,8 +153,4 @@ private static int toMin(int seconds) { private static int toSec(int minutes) { return (int) TimeUnit.MINUTES.toSeconds(minutes); } - - private List splitFeatures(String str) { - return splitByComma(str).stream().map(NotificationFeature::fromName).collect(Collectors.toList()); - } } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java similarity index 92% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java index 9481ffdb9..c33a339d8 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java @@ -46,7 +46,7 @@ public class TaskStoreImpl implements TaskStore { public List getOverview() { return lookupTable.entrySet().stream() .map(entry -> { - CommunicationTask r = entry.getValue(); + var r = entry.getValue(); return TaskOverview.builder() .taskId(entry.getKey()) .origin(r.getOrigin()) @@ -62,17 +62,16 @@ public List getOverview() { @Override public CommunicationTask get(Integer taskId) { - CommunicationTask r = lookupTable.get(taskId); + var r = lookupTable.get(taskId); if (r == null) { - throw new SteveException.InternalError("There is no task with taskId '%s'", taskId); - } else { - return r; + throw new SteveException.InternalError("There is no task with taskId '%s'".formatted(taskId)); } + return r; } @Override public Integer add(CommunicationTask task) { - int taskId = atomicInteger.incrementAndGet(); + var taskId = atomicInteger.incrementAndGet(); lookupTable.put(taskId, task); return taskId; } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java similarity index 62% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java index fc1e670b0..6daca730b 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java @@ -19,29 +19,22 @@ package de.rwth.idsg.steve.repository.impl; import de.rwth.idsg.steve.SteveException; +import de.rwth.idsg.steve.jooq.mapper.TransactionMapper; import de.rwth.idsg.steve.repository.TransactionRepository; import de.rwth.idsg.steve.repository.dto.Transaction; import de.rwth.idsg.steve.repository.dto.TransactionDetails; -import de.rwth.idsg.steve.utils.DateTimeUtils; import de.rwth.idsg.steve.utils.TransactionStopServiceHelper; import de.rwth.idsg.steve.web.dto.TransactionQueryForm; -import jooq.steve.db.enums.TransactionStopEventActor; -import jooq.steve.db.tables.records.ConnectorMeterValueRecord; -import jooq.steve.db.tables.records.TransactionStartRecord; import ocpp.cs._2015._10.UnitOfMeasure; import org.jooq.Condition; import org.jooq.DSLContext; -import org.jooq.Record12; -import org.jooq.Record9; -import org.jooq.RecordMapper; +import org.jooq.Record; import org.jooq.SelectQuery; -import org.jooq.Table; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.io.Writer; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -76,13 +69,12 @@ public Optional getTransaction(int transactionPk) { form.setTransactionPk(transactionPk); form.setReturnCSV(false); form.setType(TransactionQueryForm.QueryType.ALL); - var r = getInternal(form).fetchAny(new TransactionMapper()); - return Optional.ofNullable(r); + return getInternal(form).fetchOptional(TransactionMapper::fromRecord); } @Override public List getTransactions(TransactionQueryForm form) { - return getInternal(form).fetch().map(new TransactionMapper()); + return getInternal(form).fetch(TransactionMapper::fromRecord); } @Override @@ -117,48 +109,35 @@ public Optional getActiveTransactionId(String chargeBoxId, int connecto } @Override - public TransactionDetails getDetails(int transactionPk) { + public Optional getDetails(int transactionPk) { // ------------------------------------------------------------------------- // Step 1: Collect general data about transaction // ------------------------------------------------------------------------- - TransactionQueryForm form = new TransactionQueryForm(); + var form = new TransactionQueryForm(); form.setTransactionPk(transactionPk); form.setType(TransactionQueryForm.QueryType.ALL); form.setPeriodType(TransactionQueryForm.QueryPeriodType.ALL); - Record12< - Integer, - String, - Integer, - String, - LocalDateTime, - String, - LocalDateTime, - String, - String, - Integer, - Integer, - TransactionStopEventActor> - transaction = getInternal(form).fetchOne(); + var transaction = getInternal(form).fetchOne(); if (transaction == null) { - throw new SteveException.InternalError("There is no transaction with id '%s'", transactionPk); + return Optional.empty(); } - var startTimestamp = transaction.value5(); - var stopTimestamp = transaction.value7(); - String stopValue = transaction.value8(); - String chargeBoxId = transaction.value2(); - int connectorId = transaction.value3(); + var startTimestamp = transaction.get(TRANSACTION.START_TIMESTAMP); + var stopTimestamp = transaction.get(TRANSACTION.STOP_TIMESTAMP); + var stopValue = transaction.get(TRANSACTION.STOP_VALUE); + var chargeBoxId = transaction.get(CHARGE_BOX.CHARGE_BOX_ID); + var connectorId = transaction.get(CONNECTOR.CONNECTOR_ID); // ------------------------------------------------------------------------- // Step 2: Collect intermediate meter values // ------------------------------------------------------------------------- Condition timestampCondition; - TransactionStartRecord nextTx = null; + TransactionDetails.NextTransactionStart nextTx = null; if (stopTimestamp == null && stopValue == null) { @@ -169,7 +148,7 @@ public TransactionDetails getDetails(int transactionPk) { // to this zombie transaction. // // "what is the subsequent transaction at the same chargebox and connector?" - nextTx = ctx.selectFrom(TRANSACTION_START) + var nextTxRecord = ctx.selectFrom(TRANSACTION_START) .where(TRANSACTION_START.CONNECTOR_PK.eq(ctx.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) @@ -179,12 +158,19 @@ public TransactionDetails getDetails(int transactionPk) { .limit(1) .fetchOne(); - if (nextTx == null) { + if (nextTxRecord != null) { + nextTx = TransactionDetails.NextTransactionStart.builder() + .startValue(nextTxRecord.getStartValue()) + .startTimestamp(toInstant(nextTxRecord.getStartTimestamp())) + .build(); + } + + if (nextTxRecord == null) { // the last active transaction timestampCondition = CONNECTOR_METER_VALUE.VALUE_TIMESTAMP.greaterOrEqual(startTimestamp); } else { timestampCondition = - CONNECTOR_METER_VALUE.VALUE_TIMESTAMP.between(startTimestamp, nextTx.getStartTimestamp()); + CONNECTOR_METER_VALUE.VALUE_TIMESTAMP.between(startTimestamp, nextTxRecord.getStartTimestamp()); } } else { // finished transaction @@ -199,19 +185,24 @@ public TransactionDetails getDetails(int transactionPk) { // Case 1: Ideal and most accurate case. Station sends meter values with transaction id set. // - SelectQuery transactionQuery = ctx.selectFrom(CONNECTOR_METER_VALUE) + var transactionQuery = ctx.selectFrom(CONNECTOR_METER_VALUE) .where(CONNECTOR_METER_VALUE.TRANSACTION_PK.eq(transactionPk)) .and(unitCondition) .getQuery(); // Case 2: Fall back to filtering according to time windows // - SelectQuery timestampQuery = ctx.selectFrom(CONNECTOR_METER_VALUE) + var timestampQuery = ctx.selectFrom(CONNECTOR_METER_VALUE) .where(CONNECTOR_METER_VALUE.CONNECTOR_PK.eq(ctx.select(CONNECTOR.CONNECTOR_PK) .from(CONNECTOR) - .where(CONNECTOR.CHARGE_BOX_ID.eq(chargeBoxId)) - .and(CONNECTOR.CONNECTOR_ID.eq(connectorId)))) + .where(CONNECTOR.CHARGE_BOX_ID.equal(chargeBoxId)) + .and(CONNECTOR.CONNECTOR_ID.equal(connectorId)))) .and(timestampCondition) + // avoid duplicates already returned by transactionQuery + .and(CONNECTOR_METER_VALUE + .TRANSACTION_PK + .isNull() + .or(CONNECTOR_METER_VALUE.TRANSACTION_PK.ne(transactionPk))) .and(unitCondition) .getQuery(); @@ -219,52 +210,47 @@ public TransactionDetails getDetails(int transactionPk) { // executed (best case). In worst case (1 returns empty list and we fall back to case 2) though, // we make two db calls. Alternatively, we can pass both queries in one go, and make the db work. // - // UNION removes all duplicate records - // - Table t1 = - transactionQuery.union(timestampQuery).asTable("t1"); - - var dateTimeField = t1.field(2, LocalDateTime.class); - - List values = ctx - .select( - dateTimeField, - t1.field(3, String.class), - t1.field(4, String.class), - t1.field(5, String.class), - t1.field(6, String.class), - t1.field(7, String.class), - t1.field(8, String.class), - t1.field(9, String.class)) + // UNION ALL doesn't remove all duplicate records but it is faster than UNION DISTINCT which does + // and we avoid duplicates by query design anyway. + var t1 = transactionQuery.unionAll(timestampQuery).asTable("t1"); + + var ts = t1.field(CONNECTOR_METER_VALUE.VALUE_TIMESTAMP); + var val = t1.field(CONNECTOR_METER_VALUE.VALUE); + var ctxf = t1.field(CONNECTOR_METER_VALUE.READING_CONTEXT); + var fmt = t1.field(CONNECTOR_METER_VALUE.FORMAT); + var mea = t1.field(CONNECTOR_METER_VALUE.MEASURAND); + var loc = t1.field(CONNECTOR_METER_VALUE.LOCATION); + var unit = t1.field(CONNECTOR_METER_VALUE.UNIT); + var ph = t1.field(CONNECTOR_METER_VALUE.PHASE); + var values = ctx + .select(ts, val, ctxf, fmt, mea, loc, unit, ph) .from(t1) - .orderBy(dateTimeField) + .orderBy(ts) .fetch() .map(r -> TransactionDetails.MeterValues.builder() - .valueTimestamp(toInstant(r.value1())) - .value(r.value2()) - .readingContext(r.value3()) - .format(r.value4()) - .measurand(r.value5()) - .location(r.value6()) - .unit(r.value7()) - .phase(r.value8()) + .valueTimestamp(toInstant(r.get(ts))) + .value(r.get(val)) + .readingContext(r.get(ctxf)) + .format(r.get(fmt)) + .measurand(r.get(mea)) + .location(r.get(loc)) + .unit(r.get(unit)) + .phase(r.get(ph)) .build()) .stream() .filter(TransactionStopServiceHelper::isEnergyValue) .toList(); - return new TransactionDetails(new TransactionMapper().map(transaction), values, nextTx); + return Optional.of(new TransactionDetails(TransactionMapper.fromRecord(transaction), values, nextTx)); } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- - @SuppressWarnings("unchecked") - private SelectQuery> - getInternalCSV(TransactionQueryForm form) { + private SelectQuery getInternalCSV(TransactionQueryForm form) { - SelectQuery selectQuery = ctx.selectQuery(); + var selectQuery = ctx.selectQuery(); selectQuery.addFrom(TRANSACTION); selectQuery.addJoin(CONNECTOR, TRANSACTION.CONNECTOR_PK.eq(CONNECTOR.CONNECTOR_PK)); selectQuery.addSelect( @@ -285,47 +271,21 @@ public TransactionDetails getDetails(int transactionPk) { * Difference from getInternalCSV: * Joins with CHARGE_BOX and OCPP_TAG tables, selects CHARGE_BOX_PK and OCPP_TAG_PK additionally */ - @SuppressWarnings("unchecked") - private SelectQuery< - Record12< - Integer, - String, - Integer, - String, - LocalDateTime, - String, - LocalDateTime, - String, - String, - Integer, - Integer, - TransactionStopEventActor>> - getInternal(TransactionQueryForm form) { - - SelectQuery selectQuery = ctx.selectQuery(); + private SelectQuery getInternal(TransactionQueryForm form) { + + var selectQuery = ctx.selectQuery(); selectQuery.addFrom(TRANSACTION); selectQuery.addJoin(CONNECTOR, TRANSACTION.CONNECTOR_PK.eq(CONNECTOR.CONNECTOR_PK)); selectQuery.addJoin(CHARGE_BOX, CHARGE_BOX.CHARGE_BOX_ID.eq(CONNECTOR.CHARGE_BOX_ID)); selectQuery.addJoin(OCPP_TAG, OCPP_TAG.ID_TAG.eq(TRANSACTION.ID_TAG)); + selectQuery.addSelect(TRANSACTION.fields()); selectQuery.addSelect( - TRANSACTION.TRANSACTION_PK, - CONNECTOR.CHARGE_BOX_ID, - CONNECTOR.CONNECTOR_ID, - TRANSACTION.ID_TAG, - TRANSACTION.START_TIMESTAMP, - TRANSACTION.START_VALUE, - TRANSACTION.STOP_TIMESTAMP, - TRANSACTION.STOP_VALUE, - TRANSACTION.STOP_REASON, - CHARGE_BOX.CHARGE_BOX_PK, - OCPP_TAG.OCPP_TAG_PK, - TRANSACTION.STOP_EVENT_ACTOR); + CHARGE_BOX.CHARGE_BOX_ID, CONNECTOR.CONNECTOR_ID, OCPP_TAG.OCPP_TAG_PK, CHARGE_BOX.CHARGE_BOX_PK); return addConditions(selectQuery, form); } - @SuppressWarnings("unchecked") - private SelectQuery addConditions(SelectQuery selectQuery, TransactionQueryForm form) { + private SelectQuery addConditions(SelectQuery selectQuery, TransactionQueryForm form) { if (form.isTransactionPkSet()) { selectQuery.addConditions(TRANSACTION.TRANSACTION_PK.eq(form.getTransactionPk())); } @@ -358,7 +318,7 @@ private SelectQuery addConditions(SelectQuery selectQuery, TransactionQueryForm return selectQuery; } - private void processType(SelectQuery selectQuery, TransactionQueryForm form) { + private void processType(SelectQuery selectQuery, TransactionQueryForm form) { switch (form.getPeriodType()) { case TODAY -> selectQuery.addConditions(date(TRANSACTION.START_TIMESTAMP).eq(LocalDate.now())); @@ -377,60 +337,12 @@ private void processType(SelectQuery selectQuery, TransactionQueryForm form) { switch (form.getType()) { case ACTIVE -> selectQuery.addConditions(TRANSACTION.START_TIMESTAMP.between(from, to)); case STOPPED -> selectQuery.addConditions(TRANSACTION.STOP_TIMESTAMP.between(from, to)); + case ALL -> { + // want all: no timestamp filter + } } } default -> throw new SteveException.InternalError("Unknown enum type"); } } - - private static class TransactionMapper - implements RecordMapper< - Record12< - Integer, - String, - Integer, - String, - LocalDateTime, - String, - LocalDateTime, - String, - String, - Integer, - Integer, - TransactionStopEventActor>, - Transaction> { - @Override - public Transaction map( - Record12< - Integer, - String, - Integer, - String, - LocalDateTime, - String, - LocalDateTime, - String, - String, - Integer, - Integer, - TransactionStopEventActor> - r) { - return Transaction.builder() - .id(r.value1()) - .chargeBoxId(r.value2()) - .connectorId(r.value3()) - .ocppIdTag(r.value4()) - .startTimestamp(toInstant(r.value5())) - .startTimestampFormatted(DateTimeUtils.humanize(r.value5())) - .startValue(r.value6()) - .stopTimestamp(toInstant(r.value7())) - .stopTimestampFormatted(DateTimeUtils.humanize(r.value7())) - .stopValue(r.value8()) - .stopReason(r.value9()) - .chargeBoxPk(r.value10()) - .ocppTagPk(r.value11()) - .stopEventActor(r.value12()) - .build(); - } - } } diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java similarity index 92% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java index 1eb3338c3..af0c84a57 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java @@ -20,6 +20,7 @@ import com.google.common.base.Strings; import de.rwth.idsg.steve.SteveException; +import de.rwth.idsg.steve.jooq.mapper.UserMapper; import de.rwth.idsg.steve.repository.AddressRepository; import de.rwth.idsg.steve.repository.UserRepository; import de.rwth.idsg.steve.repository.dto.User; @@ -29,11 +30,8 @@ import lombok.extern.slf4j.Slf4j; import org.jooq.Condition; import org.jooq.DSLContext; -import org.jooq.Field; -import org.jooq.Record1; import org.jooq.Record5; import org.jooq.Result; -import org.jooq.SelectConditionStep; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; import org.jspecify.annotations.Nullable; @@ -84,11 +82,10 @@ public Optional getDetails(int userPk) { return Optional.empty(); } - return Optional.of(User.Details.builder() - .userRecord(ur) - .address(addressRepository.get(ctx, ur.getAddressPk())) - .ocppTagEntries(getOcppTagsInternal(userPk, null).getOrDefault(userPk, List.of())) - .build()); + var address = addressRepository.get(ur.getAddressPk()); + var ocppTagEntries = getOcppTagsInternal(userPk, null).getOrDefault(userPk, List.of()); + + return Optional.of(UserMapper.fromRecord(ur, address, ocppTagEntries)); } @Override @@ -96,7 +93,7 @@ public Integer add(UserForm form) { return ctx.transactionResult(configuration -> { var tx = DSL.using(configuration); try { - var addressId = addressRepository.updateOrInsert(tx, form.getAddress()); + var addressId = addressRepository.updateOrInsert(form.getAddress()); var userPk = addInternal(tx, form, addressId); refreshOcppTagsInternal(tx, form, userPk); return userPk; @@ -111,7 +108,7 @@ public void update(UserForm form) { ctx.transaction(configuration -> { var tx = DSL.using(configuration); try { - var addressId = addressRepository.updateOrInsert(tx, form.getAddress()); + var addressId = addressRepository.updateOrInsert(form.getAddress()); updateInternal(tx, form, addressId); refreshOcppTagsInternal(tx, form, form.getUserPk()); @@ -126,7 +123,8 @@ public void delete(int userPk) { ctx.transaction(configuration -> { var tx = DSL.using(configuration); try { - addressRepository.delete(tx, selectAddressId(userPk)); + var addressPk = selectAddressId(userPk); + addressRepository.delete(addressPk); deleteInternal(tx, userPk); } catch (DataAccessException e) { throw new SteveException.InternalError("Failed to delete the user", e); @@ -161,7 +159,7 @@ private Result> getOverviewInte if (form.isSetName()) { // Concatenate the two columns and search within the resulting representation // for flexibility, since the user can search by first or last name, or both. - Field joinedField = DSL.concat(USER.FIRST_NAME, USER.LAST_NAME); + var joinedField = DSL.concat(USER.FIRST_NAME, USER.LAST_NAME); // Find a matching sequence anywhere within the concatenated representation conditions.add(includes(joinedField, form.getName())); @@ -209,12 +207,11 @@ private Map> getOcppTagsInternal(Integer userPk return map; } - private SelectConditionStep> selectAddressId(int userPk) { - return ctx.select(USER.ADDRESS_PK).from(USER).where(USER.USER_PK.eq(userPk)); - } - - private SelectConditionStep> selectOcppTagPk(String ocppIdTag) { - return ctx.select(OCPP_TAG.OCPP_TAG_PK).from(OCPP_TAG).where(OCPP_TAG.ID_TAG.eq(ocppIdTag)); + private Integer selectAddressId(int userPk) { + return ctx.select(USER.ADDRESS_PK) + .from(USER) + .where(USER.USER_PK.eq(userPk)) + .fetchOne(USER.ADDRESS_PK); } private Integer addInternal(DSLContext ctx, UserForm form, Integer addressPk) { diff --git a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/WebUserRepositoryImpl.java b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/WebUserRepositoryImpl.java similarity index 65% rename from steve/src/main/java/de/rwth/idsg/steve/repository/impl/WebUserRepositoryImpl.java rename to steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/WebUserRepositoryImpl.java index 2e8bd4cc8..925605581 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/WebUserRepositoryImpl.java +++ b/steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/WebUserRepositoryImpl.java @@ -18,18 +18,19 @@ */ package de.rwth.idsg.steve.repository.impl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.rwth.idsg.steve.jooq.mapper.WebUserMapper; import de.rwth.idsg.steve.repository.WebUserRepository; +import de.rwth.idsg.steve.repository.dto.WebUser; +import de.rwth.idsg.steve.service.dto.WebUserOverview; import de.rwth.idsg.steve.web.dto.WebUserQueryForm; -import jooq.steve.db.tables.records.WebUserRecord; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.Condition; import org.jooq.DSLContext; import org.jooq.Field; import org.jooq.JSON; -import org.jooq.Record4; -import org.jooq.Result; -import org.jooq.SelectQuery; import org.jooq.impl.DSL; import org.jooq.impl.SQLDataType; import org.springframework.stereotype.Repository; @@ -38,6 +39,9 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static jooq.steve.db.Tables.WEB_USER; import static org.jooq.impl.DSL.count; @@ -52,35 +56,42 @@ public class WebUserRepositoryImpl implements WebUserRepository { private final DSLContext ctx; + private final ObjectMapper objectMapper; @Override - public void createUser(WebUserRecord user) { + public void createUser(WebUser user) { ctx.insertInto(WEB_USER) - .set(WEB_USER.USERNAME, user.getUsername()) + .set(WEB_USER.USERNAME, user.getLogin()) .set(WEB_USER.PASSWORD, user.getPassword()) .set(WEB_USER.API_PASSWORD, user.getApiPassword()) - .set(WEB_USER.ENABLED, user.getEnabled()) - .set(WEB_USER.AUTHORITIES, user.getAuthorities()) + .set(WEB_USER.ENABLED, user.isEnabled()) + .set( + WEB_USER.AUTHORITIES, + toJSON(user.getAuthorities().stream().map(Enum::name).collect(Collectors.toSet()))) .execute(); } @Override - public void updateUser(WebUserRecord user) { + public void updateUser(WebUser user) { // To change the password use one of the changePassword methods ctx.update(WEB_USER) - .set(WEB_USER.ENABLED, user.getEnabled()) - .set(WEB_USER.AUTHORITIES, user.getAuthorities()) - .where(WEB_USER.USERNAME.eq(user.getUsername())) + .set(WEB_USER.ENABLED, user.isEnabled()) + .set( + WEB_USER.AUTHORITIES, + toJSON(user.getAuthorities().stream().map(Enum::name).collect(Collectors.toSet()))) + .where(WEB_USER.USERNAME.eq(user.getLogin())) .execute(); } @Override - public void updateUserByPk(WebUserRecord user) { + public void updateUserByPk(WebUser user) { // To change the password use one of the changePassword methods ctx.update(WEB_USER) - .set(WEB_USER.USERNAME, user.getUsername()) - .set(WEB_USER.ENABLED, user.getEnabled()) - .set(WEB_USER.AUTHORITIES, user.getAuthorities()) + .set(WEB_USER.USERNAME, user.getLogin()) + .set(WEB_USER.ENABLED, user.isEnabled()) + .set( + WEB_USER.AUTHORITIES, + toJSON(user.getAuthorities().stream().map(Enum::name).collect(Collectors.toSet()))) .where(WEB_USER.WEB_USER_PK.eq(user.getWebUserPk())) .execute(); } @@ -104,11 +115,12 @@ public void changeStatusOfUser(String username, boolean enabled) { } @Override - public Integer getUserCountWithAuthority(String authority) { - return ctx.selectCount() + public int getUserCountWithAuthority(String authority) { + var count = ctx.selectCount() .from(WEB_USER) .where(conditionsForAuthorities(Collections.singletonList(authority))) .fetchOne(count()); + return count != null ? count : 0; } @Override @@ -145,22 +157,22 @@ public boolean userExists(String username) { } @Override - public WebUserRecord loadUserByUsername(String username) { - return ctx.selectFrom(WEB_USER).where(WEB_USER.USERNAME.eq(username)).fetchOne(); + public Optional loadUserByUsername(String username) { + return ctx.selectFrom(WEB_USER) + .where(WEB_USER.USERNAME.eq(username)) + .fetchOptional(r -> WebUserMapper.fromRecord(r, objectMapper)); } @Override - public WebUserRecord loadUserByUserPk(Integer webUserPk) { + public Optional loadUserByUserPk(Integer webUserPk) { return ctx.selectFrom(WEB_USER) .where(WEB_USER.WEB_USER_PK.eq(webUserPk)) - .fetchOne(); + .fetchOptional(r -> WebUserMapper.fromRecord(r, objectMapper)); } @Override - public Result> getOverview(WebUserQueryForm form) { - SelectQuery selectQuery = ctx.selectQuery(); - selectQuery.addFrom(WEB_USER); - selectQuery.addSelect(WEB_USER.WEB_USER_PK, WEB_USER.USERNAME, WEB_USER.ENABLED, WEB_USER.AUTHORITIES); + public List getOverview(WebUserQueryForm form) { + var selectQuery = ctx.selectFrom(WEB_USER).getQuery(); if (form.isSetWebUsername()) { selectQuery.addConditions(WEB_USER.USERNAME.eq(form.getWebUsername())); @@ -171,12 +183,21 @@ public Result> getOverview(WebUserQueryF } if (form.isSetRoles()) { - String[] split = form.getRoles().split(","); // Comma seperated String to StringArray - List roles = Arrays.stream(split).map(String::strip).toList(); + var split = form.getRoles().split(","); // Comma seperated String to StringArray + var roles = Arrays.stream(split).map(String::strip).toList(); selectQuery.addConditions(conditionsForAuthorities(roles)); } - return selectQuery.fetch(); + return selectQuery.fetch().map(r -> WebUserMapper.overviewFromRecord(r, objectMapper)); + } + + private JSON toJSON(Set authorities) { + try { + return JSON.valueOf(objectMapper.writeValueAsString(authorities)); + } catch (JsonProcessingException e) { + log.error("Failed to serialize authorities to JSON", e); + return JSON.valueOf("[]"); + } } private static List conditionsForAuthorities(List authorities) { diff --git a/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java b/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java index 1be78425a..79cef76d1 100644 --- a/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java +++ b/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java @@ -136,7 +136,7 @@ public WebSocketSession getSession(String chargeBoxId) { } return wsSessionSelectStrategy.getSession(endpointDeque); } catch (NoSuchElementException e) { - throw new SteveException.InternalError("No session context for chargeBoxId '%s'", chargeBoxId, e); + throw new SteveException.InternalError("No session context for chargeBoxId '%s'".formatted(chargeBoxId), e); } finally { l.unlock(); } diff --git a/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java b/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java index 426e2f6b5..b90872563 100644 --- a/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java +++ b/steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java @@ -82,7 +82,7 @@ public void accept(CommunicationContext context) { } } catch (IOException e) { throw new SteveException.InternalError( - "Deserialization of incoming string failed: %s", context.getIncomingString(), e); + "Deserialization of incoming string failed: %s".formatted(context.getIncomingString()), e); } } @@ -146,8 +146,8 @@ private void handleResult(CommunicationContext context, String messageId, JsonPa FutureResponseContext responseContext = futureResponseContextStore.get(context.getSession(), messageId); if (responseContext == null) { throw new SteveException.InternalError( - "A result message was received as response to a not-sent call. The message was: %s", - context.getIncomingString()); + "A result message was received as response to a not-sent call. The message was: %s" + .formatted(context.getIncomingString())); } ResponseType res; @@ -175,8 +175,8 @@ private void handleError(CommunicationContext context, String messageId, JsonPar var responseContext = futureResponseContextStore.get(context.getSession(), messageId); if (responseContext == null) { throw new SteveException.InternalError( - "An error message was received as response to a not-sent call. The message was: %s", - context.getIncomingString()); + "An error message was received as response to a not-sent call. The message was: %s" + .formatted(context.getIncomingString())); } ErrorCode code; diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargePointDetailsMapper.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargePointDetailsMapper.java deleted file mode 100644 index e6a48346d..000000000 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargePointDetailsMapper.java +++ /dev/null @@ -1,51 +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.utils.mapper; - -import de.rwth.idsg.steve.repository.dto.ChargePoint; -import de.rwth.idsg.steve.web.dto.ChargePointForm; -import jooq.steve.db.tables.records.ChargeBoxRecord; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -/** - * @author Sevket Goekay - * @since 23.03.2021 - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ChargePointDetailsMapper { - - public static ChargePointForm mapToForm(ChargePoint.Details cp) { - ChargeBoxRecord chargeBox = cp.getChargeBox(); - - ChargePointForm form = new ChargePointForm(); - form.setChargeBoxPk(chargeBox.getChargeBoxPk()); - form.setChargeBoxId(chargeBox.getChargeBoxId()); - form.setNote(chargeBox.getNote()); - form.setDescription(chargeBox.getDescription()); - form.setLocationLatitude(chargeBox.getLocationLatitude()); - form.setLocationLongitude(chargeBox.getLocationLongitude()); - form.setInsertConnectorStatusAfterTransactionMsg(chargeBox.getInsertConnectorStatusAfterTransactionMsg()); - form.setAdminAddress(chargeBox.getAdminAddress()); - form.setRegistrationStatus(chargeBox.getRegistrationStatus()); - form.setAddress(AddressMapper.recordToDto(cp.getAddress())); - - return form; - } -} diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java deleted file mode 100644 index 35e39bb5f..000000000 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java +++ /dev/null @@ -1,73 +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.utils.mapper; - -import de.rwth.idsg.steve.repository.dto.ChargingProfile; -import de.rwth.idsg.steve.web.dto.ChargingProfileForm; -import jooq.steve.db.tables.records.ChargingSchedulePeriodRecord; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import ocpp.cp._2015._10.ChargingProfileKindType; -import ocpp.cp._2015._10.ChargingProfilePurposeType; -import ocpp.cp._2015._10.ChargingRateUnitType; -import ocpp.cp._2015._10.RecurrencyKindType; - -import static de.rwth.idsg.steve.utils.DateTimeUtils.toInstant; - -/** - * @author Sevket Goekay - * @since 23.03.2021 - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ChargingProfileDetailsMapper { - - public static ChargingProfileForm mapToForm(ChargingProfile.Details details) { - var profile = details.getProfile(); - var periods = details.getPeriods(); - - var form = new ChargingProfileForm(); - form.setChargingProfilePk(profile.getChargingProfilePk()); - form.setDescription(profile.getDescription()); - form.setNote(profile.getNote()); - form.setStackLevel(profile.getStackLevel()); - form.setChargingProfilePurpose(ChargingProfilePurposeType.fromValue(profile.getChargingProfilePurpose())); - form.setChargingProfileKind(ChargingProfileKindType.fromValue(profile.getChargingProfileKind())); - form.setRecurrencyKind( - profile.getRecurrencyKind() == null ? null : RecurrencyKindType.fromValue(profile.getRecurrencyKind())); - form.setValidFrom(toInstant(profile.getValidFrom())); - form.setValidTo(toInstant(profile.getValidTo())); - form.setDurationInSeconds(profile.getDurationInSeconds()); - form.setStartSchedule(toInstant(profile.getStartSchedule())); - form.setChargingRateUnit(ChargingRateUnitType.fromValue(profile.getChargingRateUnit())); - form.setMinChargingRate(profile.getMinChargingRate()); - form.setSchedulePeriods(periods.stream() - .map(ChargingProfileDetailsMapper::mapToFormPeriod) - .toList()); - - return form; - } - - private static ChargingProfileForm.SchedulePeriod mapToFormPeriod(ChargingSchedulePeriodRecord rec) { - var p = new ChargingProfileForm.SchedulePeriod(); - p.setStartPeriodInSeconds(rec.getStartPeriodInSeconds()); - p.setPowerLimit(rec.getPowerLimit()); - p.setNumberPhases(rec.getNumberPhases()); - return p; - } -} diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/OcppTagFormMapper.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/OcppTagFormMapper.java deleted file mode 100644 index f9a76bf61..000000000 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/OcppTagFormMapper.java +++ /dev/null @@ -1,57 +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.utils.mapper; - -import de.rwth.idsg.steve.utils.ControllerHelper; -import de.rwth.idsg.steve.web.dto.OcppTagForm; -import jooq.steve.db.tables.records.OcppTagActivityRecord; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -import static de.rwth.idsg.steve.utils.DateTimeUtils.toInstant; - -/** - * @author Sevket Goekay - * @since 23.03.2021 - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class OcppTagFormMapper { - - public static OcppTagForm toForm(OcppTagActivityRecord record) { - OcppTagForm form = new OcppTagForm(); - form.setOcppTagPk(record.getOcppTagPk()); - form.setIdTag(record.getIdTag()); - - var expiryDate = record.getExpiryDate(); - if (expiryDate != null) { - form.setExpiryDate(toInstant(expiryDate)); - } - - form.setMaxActiveTransactionCount(record.getMaxActiveTransactionCount()); - form.setNote(record.getNote()); - - String parentIdTag = record.getParentIdTag(); - if (parentIdTag == null) { - parentIdTag = ControllerHelper.EMPTY_OPTION; - } - form.setParentIdTag(parentIdTag); - - return form; - } -} diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java deleted file mode 100644 index 331b54080..000000000 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/UserFormMapper.java +++ /dev/null @@ -1,54 +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.utils.mapper; - -import de.rwth.idsg.steve.repository.dto.User; -import de.rwth.idsg.steve.web.dto.UserForm; -import de.rwth.idsg.steve.web.dto.UserSex; -import jooq.steve.db.tables.records.UserRecord; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -/** - * @author Sevket Goekay - * @since 23.03.2021 - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class UserFormMapper { - - public static UserForm toForm(User.Details details) { - UserRecord userRecord = details.getUserRecord(); - - UserForm form = new UserForm(); - form.setUserPk(userRecord.getUserPk()); - form.setFirstName(userRecord.getFirstName()); - form.setLastName(userRecord.getLastName()); - form.setBirthDay(userRecord.getBirthDay()); - form.setPhone(userRecord.getPhone()); - form.setSex(UserSex.fromDatabaseValue(userRecord.getSex())); - form.setEMail(userRecord.getEMail()); - form.setNote(userRecord.getNote()); - form.setAddress(AddressMapper.recordToDto(details.getAddress())); - form.setIdTagList(details.getOcppTagEntries().stream() - .map(User.OcppTagEntry::getIdTag) - .toList()); - - return form; - } -} diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java index 7f240a3ae..83aacf116 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java @@ -71,7 +71,7 @@ public class AboutSettingsController { @GetMapping(value = ABOUT_PATH) public String getAbout(Model model) { model.addAttribute("version", config.getSteveVersion()); - model.addAttribute("db", genericRepository.getDBVersion()); + model.addAttribute("db", genericRepository.getDBVersion().orElse(null)); model.addAttribute("logFile", logController.getLogFilePath()); model.addAttribute("systemTime", Instant.now()); model.addAttribute("systemTimeZone", ZoneId.systemDefault()); diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargePointsController.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargePointsController.java index 22f160bca..5abf2fd61 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargePointsController.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargePointsController.java @@ -19,14 +19,13 @@ package de.rwth.idsg.steve.web.controller; import de.rwth.idsg.steve.ocpp.OcppProtocol; +import de.rwth.idsg.steve.repository.dto.ChargePoint; import de.rwth.idsg.steve.service.ChargePointRegistrationService; import de.rwth.idsg.steve.service.ChargePointsService; import de.rwth.idsg.steve.utils.ControllerHelper; -import de.rwth.idsg.steve.utils.mapper.ChargePointDetailsMapper; import de.rwth.idsg.steve.web.dto.ChargePointBatchInsertForm; import de.rwth.idsg.steve.web.dto.ChargePointForm; import de.rwth.idsg.steve.web.dto.ChargePointQueryForm; -import jooq.steve.db.tables.records.ChargeBoxRecord; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -111,31 +110,27 @@ private void initList(Model model, ChargePointQueryForm params) { @GetMapping(value = DETAILS_PATH) public String getDetails(@PathVariable("chargeBoxPk") int chargeBoxPk, Model model) { var cp = chargePointsService.getDetails(chargeBoxPk); - var form = ChargePointDetailsMapper.mapToForm(cp); + var form = ChargePointForm.fromDetails(cp); model.addAttribute("chargePointForm", form); model.addAttribute("cp", cp); - model.addAttribute("registrationStatusList", getRegistrationStatusList(cp.getChargeBox())); + model.addAttribute("registrationStatusList", getRegistrationStatusList(cp)); addCountryCodes(model); return "data-man/chargepointDetails"; } - private static List getRegistrationStatusList(ChargeBoxRecord chargeBoxRecord) { - if (chargeBoxRecord.getOcppProtocol() == null) { + private static List getRegistrationStatusList(ChargePoint.Details chargePoint) { + if (chargePoint.getOcppProtocol() == null) { return upToOcpp15RegistrationStatusList; } - var protocol = OcppProtocol.fromCompositeValue(chargeBoxRecord.getOcppProtocol()); - switch (protocol.getVersion()) { - case V_12, V_15 -> { - return upToOcpp15RegistrationStatusList; - } - case V_16 -> { - return ocpp16RegistrationStatusList; - } + var protocol = OcppProtocol.fromCompositeValue(chargePoint.getOcppProtocol()); + return switch (protocol.getVersion()) { + case V_12, V_15 -> upToOcpp15RegistrationStatusList; + case V_16 -> ocpp16RegistrationStatusList; default -> throw new IllegalArgumentException("Unknown OCPP version: " + protocol.getVersion()); - } + }; } @GetMapping(value = ADD_PATH) diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java index e1e4c17e4..f4a8d67e6 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java @@ -21,7 +21,6 @@ import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.repository.ChargingProfileRepository; import de.rwth.idsg.steve.service.ChargePointsService; -import de.rwth.idsg.steve.utils.mapper.ChargingProfileDetailsMapper; import de.rwth.idsg.steve.web.dto.ChargingProfileAssignmentQueryForm; import de.rwth.idsg.steve.web.dto.ChargingProfileForm; import de.rwth.idsg.steve.web.dto.ChargingProfileQueryForm; @@ -129,8 +128,8 @@ public String getDetails(@PathVariable("chargingProfilePk") int chargingProfileP var details = repository .getDetails(chargingProfilePk) .orElseThrow(() -> new SteveException.NotFound("Charging Profile not found")); - ; - var form = ChargingProfileDetailsMapper.mapToForm(details); + + var form = ChargingProfileForm.fromDetails(details); model.addAttribute("form", form); return "data-man/chargingProfileDetails"; diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/OcppTagsController.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/OcppTagsController.java index cf078ea54..bcf5cc777 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/OcppTagsController.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/OcppTagsController.java @@ -20,11 +20,9 @@ import de.rwth.idsg.steve.service.OcppTagsService; import de.rwth.idsg.steve.utils.ControllerHelper; -import de.rwth.idsg.steve.utils.mapper.OcppTagFormMapper; import de.rwth.idsg.steve.web.dto.OcppTagBatchInsertForm; import de.rwth.idsg.steve.web.dto.OcppTagForm; import de.rwth.idsg.steve.web.dto.OcppTagQueryForm; -import jooq.steve.db.tables.records.OcppTagActivityRecord; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -85,10 +83,10 @@ public String getQuery(@ModelAttribute(PARAMS) OcppTagQueryForm params, Model mo @RequestMapping(value = DETAILS_PATH, method = RequestMethod.GET) public String getDetails(@PathVariable("ocppTagPk") int ocppTagPk, Model model) { - OcppTagActivityRecord record = ocppTagsService.getRecord(ocppTagPk); - OcppTagForm form = OcppTagFormMapper.toForm(record); + var tag = ocppTagsService.getRecord(ocppTagPk); + var form = OcppTagForm.fromRecord(tag); - model.addAttribute("activeTransactionCount", record.getActiveTransactionCount()); + model.addAttribute("activeTransactionCount", tag.getActiveTransactionCount()); model.addAttribute("ocppTagForm", form); setTags(model); return "data-man/ocppTagDetails"; diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ReservationsController.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ReservationsController.java index 2055f0881..c0d696be6 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ReservationsController.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ReservationsController.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.web.controller; +import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.repository.ReservationStatus; import de.rwth.idsg.steve.repository.TransactionRepository; import de.rwth.idsg.steve.service.ChargePointsService; @@ -93,7 +94,11 @@ public String stopTransaction(@PathVariable("transactionPk") int transactionPk) @GetMapping(value = TRANSACTIONS_DETAILS_PATH) public String getTransactionDetails(@PathVariable("transactionPk") int transactionPk, Model model) { - model.addAttribute("details", transactionRepository.getDetails(transactionPk)); + var details = transactionRepository + .getDetails(transactionPk) + .orElseThrow(() -> + new SteveException.NotFound("Transaction with pk %d not found.".formatted(transactionPk))); + model.addAttribute("details", details); return "data-man/transactionDetails"; } diff --git a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java index 08451862d..c7c9af39d 100644 --- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java +++ b/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/UsersController.java @@ -21,7 +21,6 @@ import de.rwth.idsg.steve.service.OcppTagsService; import de.rwth.idsg.steve.service.UsersService; import de.rwth.idsg.steve.utils.ControllerHelper; -import de.rwth.idsg.steve.utils.mapper.UserFormMapper; import de.rwth.idsg.steve.web.dto.UserForm; import de.rwth.idsg.steve.web.dto.UserQueryForm; import lombok.RequiredArgsConstructor; @@ -83,7 +82,7 @@ private void initList(Model model, UserQueryForm params) { @GetMapping(value = DETAILS_PATH) public String getDetails(@PathVariable("userPk") int userPk, Model model) { var details = usersService.getDetails(userPk); - UserForm form = UserFormMapper.toForm(details); + var form = UserForm.fromDetails(details); model.addAttribute("userForm", form); setTags(model, form.getIdTagList()); diff --git a/steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java b/steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java index b1d578f34..1a80363cc 100644 --- a/steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java +++ b/steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java @@ -21,21 +21,12 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.mysql.cj.conf.PropertyKey; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.service.DummyReleaseCheckService; import de.rwth.idsg.steve.service.GithubReleaseCheckService; import de.rwth.idsg.steve.service.ReleaseCheckService; import de.rwth.idsg.steve.utils.InternetChecker; import lombok.extern.slf4j.Slf4j; -import org.jooq.DSLContext; -import org.jooq.SQLDialect; -import org.jooq.conf.Settings; -import org.jooq.impl.DSL; -import org.jooq.impl.DataSourceConnectionProvider; -import org.jooq.impl.DefaultConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -53,7 +44,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import java.util.List; -import javax.sql.DataSource; /** * Configuration and beans of Spring Framework. @@ -68,71 +58,6 @@ @ComponentScan("de.rwth.idsg.steve") public class BeanConfiguration implements WebMvcConfigurer { - /** - * https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration - */ - @Bean - public DataSource dataSource(SteveConfiguration config) { - var dbConfig = config.getDb(); - return dataSource( - dbConfig.getJdbcUrl(), dbConfig.getUserName(), dbConfig.getPassword(), config.getTimeZoneId()); - } - - public static DataSource dataSource(String dbUrl, String dbUserName, String dbPassword, String dbTimeZoneId) { - var hc = new HikariConfig(); - - // set standard params - hc.setJdbcUrl(dbUrl); - hc.setUsername(dbUserName); - hc.setPassword(dbPassword); - - // set non-standard params - hc.addDataSourceProperty(PropertyKey.cachePrepStmts.getKeyName(), true); - hc.addDataSourceProperty(PropertyKey.useServerPrepStmts.getKeyName(), true); - hc.addDataSourceProperty(PropertyKey.prepStmtCacheSize.getKeyName(), 250); - hc.addDataSourceProperty(PropertyKey.prepStmtCacheSqlLimit.getKeyName(), 2048); - hc.addDataSourceProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); - hc.addDataSourceProperty(PropertyKey.connectionTimeZone.getKeyName(), dbTimeZoneId); - hc.addDataSourceProperty(PropertyKey.useSSL.getKeyName(), true); - - // https://github.com/steve-community/steve/issues/736 - hc.setMaxLifetime(580_000); - - return new HikariDataSource(hc); - } - - /** - * Can we re-use DSLContext as a Spring bean (singleton)? Yes, the Spring tutorial of - * Jooq also does it that way, but only if we do not change anything about the - * config after the init (which we don't do anyways) and if the ConnectionProvider - * does not store any shared state (we use DataSourceConnectionProvider of Jooq, so no problem). - * - * Some sources and discussion: - * - http://www.jooq.org/doc/3.6/manual/getting-started/tutorials/jooq-with-spring/ - * - http://jooq-user.narkive.com/2fvuLodn/dslcontext-and-threads - * - https://groups.google.com/forum/#!topic/jooq-user/VK7KQcjj3Co - * - http://stackoverflow.com/questions/32848865/jooq-dslcontext-correct-autowiring-with-spring - */ - @Bean - public DSLContext dslContext(DataSource dataSource, SteveConfiguration config) { - var settings = new Settings() - // Normally, the records are "attached" to the Configuration that created (i.e. fetch/insert) them. - // This means that they hold an internal reference to the same database connection that was used. - // The idea behind this is to make CRUD easier for potential subsequent store/refresh/delete - // operations. We do not use or need that. - .withAttachRecords(false) - // To log or not to log the sql queries, that is the question - .withExecuteLogging(config.getDb().isSqlLogging()); - - // Configuration for JOOQ - var conf = new DefaultConfiguration() - .set(SQLDialect.MYSQL) - .set(new DataSourceConnectionProvider(dataSource)) - .set(settings); - - return DSL.using(conf); - } - @Bean(destroyMethod = "close") public DelegatingTaskScheduler asyncTaskScheduler() { var scheduler = new ThreadPoolTaskScheduler(); diff --git a/steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java b/steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java index c2970726e..60f609084 100644 --- a/steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java +++ b/steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java @@ -146,7 +146,7 @@ public void testRegisteredCP() { initStationWithBootNotification(client); var details = __DatabasePreparer__.getCBDetails(REGISTERED_CHARGE_BOX_ID); - assertThat(details.getChargeBox().getOcppProtocol()).contains("ocpp1.6"); + assertThat(details.getOcppProtocol()).contains("ocpp1.6"); } @Test diff --git a/steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java b/steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java index f33b4d26a..36c103a20 100644 --- a/steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java +++ b/steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java @@ -22,6 +22,7 @@ import de.rwth.idsg.steve.repository.dto.InsertTransactionParams; import de.rwth.idsg.steve.repository.dto.OcppTag; import de.rwth.idsg.steve.repository.dto.Transaction; +import de.rwth.idsg.steve.repository.dto.TransactionStopEventActor; import de.rwth.idsg.steve.repository.dto.UpdateTransactionParams; import de.rwth.idsg.steve.repository.impl.AddressRepositoryImpl; import de.rwth.idsg.steve.repository.impl.ChargePointRepositoryImpl; @@ -32,7 +33,6 @@ import de.rwth.idsg.steve.web.dto.OcppTagForm; import de.rwth.idsg.steve.web.dto.OcppTagQueryForm; import de.rwth.idsg.steve.web.dto.TransactionQueryForm; -import jooq.steve.db.enums.TransactionStopEventActor; import lombok.RequiredArgsConstructor; import org.jooq.DSLContext; import org.jooq.SQLDialect; @@ -122,7 +122,7 @@ private List insertStopTransactions(List insertedTransactionId .stopTimestamp(stopTimestamp) .eventTimestamp(stopTimestamp) .stopMeterValue(transaction.getStartValue() + "0") - .eventActor(TransactionStopEventActor.station) + .eventActor(TransactionStopEventActor.STATION) .build(); ocppServerRepository.updateTransaction(p); @@ -154,7 +154,7 @@ private List insertStartTransactions(int count, List ocppTags, } private List insertChargeBoxes(int count) { - var repository = new ChargePointRepositoryImpl(ctx, new AddressRepositoryImpl()); + var repository = new ChargePointRepositoryImpl(ctx, new AddressRepositoryImpl(ctx)); List ids = IntStream.range(0, count) .mapToObj(val -> UUID.randomUUID().toString()) diff --git a/steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java b/steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java index 1404d7a33..b282766de 100644 --- a/steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java +++ b/steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java @@ -20,7 +20,7 @@ import com.google.common.collect.Sets; import de.rwth.idsg.steve.SteveConfiguration; -import de.rwth.idsg.steve.config.BeanConfiguration; +import de.rwth.idsg.steve.jooq.config.JooqConfiguration; import de.rwth.idsg.steve.repository.dto.ChargePoint; import de.rwth.idsg.steve.repository.dto.ConnectorStatus; import de.rwth.idsg.steve.repository.dto.InsertReservationParams; @@ -41,19 +41,15 @@ import jooq.steve.db.tables.records.OcppTagActivityRecord; import jooq.steve.db.tables.records.TransactionRecord; import org.jooq.DSLContext; -import org.jooq.Schema; -import org.jooq.Table; import org.jooq.impl.DSL; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; +import static de.rwth.idsg.steve.utils.DateTimeUtils.toLocalDateTime; import static jooq.steve.db.tables.ChargeBox.CHARGE_BOX; import static jooq.steve.db.tables.OcppTag.OCPP_TAG; import static jooq.steve.db.tables.Transaction.TRANSACTION; @@ -73,12 +69,18 @@ public class __DatabasePreparer__ { private static final String REGISTERED_CHARGE_BOX_ID_2 = "charge_box_2aa6a783d47d_2"; private static final String REGISTERED_OCPP_TAG = "id_tag_2aa6a783d47d"; - private static final BeanConfiguration beanConfiguration = new BeanConfiguration(); + private static final JooqConfiguration jooqConfiguration = new JooqConfiguration(); private static DSLContext dslContext; public static void prepare(SteveConfiguration config) { - dslContext = beanConfiguration.dslContext(beanConfiguration.dataSource(config), config); + dslContext = jooqConfiguration.dslContext( + JooqConfiguration.dataSource( + config.getDb().getJdbcUrl(), + config.getDb().getUserName(), + config.getDb().getPassword(), + config.getTimeZoneId()), + config); runOperation(ctx -> { truncateTables(ctx); insertChargeBox(ctx); @@ -87,14 +89,14 @@ public static void prepare(SteveConfiguration config) { } public static int makeReservation(int connectorId) { - ReservationRepositoryImpl r = new ReservationRepositoryImpl(dslContext); - InsertReservationParams params = InsertReservationParams.builder() + var r = new ReservationRepositoryImpl(dslContext); + var params = InsertReservationParams.builder() .chargeBoxId(REGISTERED_CHARGE_BOX_ID) .idTag(REGISTERED_OCPP_TAG) .connectorId(connectorId) .expiryTimestamp(Instant.now().plus(1, ChronoUnit.HOURS)) .build(); - int reservationId = r.insert(params); + var reservationId = r.insert(params); r.accepted(reservationId); return reservationId; } @@ -116,7 +118,7 @@ public static String getRegisteredOcppTag() { } public static List getTransactions() { - TransactionRepositoryImpl impl = new TransactionRepositoryImpl(dslContext); + var impl = new TransactionRepositoryImpl(dslContext); return impl.getTransactions(new TransactionQueryForm()); } @@ -125,28 +127,39 @@ public static List getTransactionRecords() { } public static List getReservations() { - ReservationRepositoryImpl impl = new ReservationRepositoryImpl(dslContext); + var impl = new ReservationRepositoryImpl(dslContext); return impl.getReservations(new ReservationQueryForm()); } public static List getChargePointConnectorStatus() { - ChargePointRepositoryImpl impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl()); + var impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl(dslContext)); return impl.getChargePointConnectorStatus(); } public static TransactionDetails getDetails(int transactionPk) { - TransactionRepositoryImpl impl = new TransactionRepositoryImpl(dslContext); - return impl.getDetails(transactionPk); + var impl = new TransactionRepositoryImpl(dslContext); + return impl.getDetails(transactionPk).orElseThrow(); } public static OcppTagActivityRecord getOcppTagRecord(String idTag) { - OcppTagRepositoryImpl impl = new OcppTagRepositoryImpl(dslContext); - return impl.getRecord(idTag); + var impl = new OcppTagRepositoryImpl(dslContext); + var dto = impl.getRecord(idTag).orElseThrow(); + var activity = new OcppTagActivityRecord(); + activity.setOcppTagPk(dto.getOcppTagPk()); + activity.setIdTag(dto.getIdTag()); + activity.setParentIdTag(dto.getParentIdTag()); + activity.setExpiryDate(toLocalDateTime(dto.getExpiryDate())); + activity.setInTransaction(dto.isInTransaction()); + activity.setBlocked(dto.isBlocked()); + activity.setMaxActiveTransactionCount(dto.getMaxActiveTransactionCount()); + activity.setActiveTransactionCount(dto.getActiveTransactionCount()); + activity.setNote(dto.getNote()); + return activity; } public static ChargePoint.Details getCBDetails(String chargeboxID) { - ChargePointRepositoryImpl impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl()); - Map pkMap = impl.getChargeBoxIdPkPair(Arrays.asList(chargeboxID)); + var impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl(dslContext)); + var pkMap = impl.getChargeBoxIdPkPair(Collections.singletonList(chargeboxID)); int pk = pkMap.get(chargeboxID); return impl.getDetails(pk).orElseThrow(); } @@ -156,7 +169,7 @@ private static void runOperation(Consumer consumer) { } private static void truncateTables(DSLContext ctx) { - Set> skipList = Sets.newHashSet( + var skipList = Sets.newHashSet( SchemaVersion.SCHEMA_VERSION, Settings.SETTINGS, OcppTagActivity.OCPP_TAG_ACTIVITY, // only a view @@ -164,20 +177,20 @@ private static void truncateTables(DSLContext ctx) { ); ctx.transaction(configuration -> { - Schema schema = DefaultCatalog.DEFAULT_CATALOG.getSchemas().stream() + var schema = DefaultCatalog.DEFAULT_CATALOG.getSchemas().stream() .filter(s -> SCHEMA_TO_TRUNCATE.equals(s.getName())) .findFirst() .orElseThrow(() -> new RuntimeException("Could not find schema")); - List> tables = schema.getTables().stream() + var tables = schema.getTables().stream() .filter(t -> !skipList.contains(t)) - .collect(Collectors.toList()); + .toList(); if (tables.isEmpty()) { throw new RuntimeException("Could not find tables to truncate"); } - DSLContext internalCtx = DSL.using(configuration); + var internalCtx = DSL.using(configuration); internalCtx.execute("SET FOREIGN_KEY_CHECKS=0"); tables.forEach(t -> internalCtx.truncate(t).execute()); internalCtx.execute("SET FOREIGN_KEY_CHECKS=1");