Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public interface OcppTagRepository {
OcppTagActivityRecord getRecord(int ocppTagPk);

List<String> getIdTags();
List<String> getIdTagsWithoutUser();
List<String> getActiveIdTags();

List<String> getParentIdTags();
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/de/rwth/idsg/steve/repository/dto/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
import jooq.steve.db.tables.records.UserRecord;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Optional;
import java.util.List;

/**
* @author Sevket Goekay <sevketgokay@gmail.com>
Expand All @@ -34,15 +35,24 @@ public class User {
@Getter
@Builder
public static final class Overview {
private final Integer userPk, ocppTagPk;
private final String ocppIdTag, name, phone, email;
private final Integer userPk;
private final String name, phone, email;
private final List<OcppTagEntry> ocppTagEntries;
}

@Getter
@Builder
public static final class Details {
private final UserRecord userRecord;
private final AddressRecord address;
private Optional<String> ocppIdTag;
private final List<OcppTagEntry> ocppTagEntries;
}

@Getter
@RequiredArgsConstructor
public static final class OcppTagEntry {
private final Integer ocppTagPk;
private final String idTag;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import static de.rwth.idsg.steve.utils.DateTimeUtils.toDateTime;
import static jooq.steve.db.tables.OcppTag.OCPP_TAG;
import static jooq.steve.db.tables.OcppTagActivity.OCPP_TAG_ACTIVITY;
import static jooq.steve.db.tables.UserOcppTag.USER_OCPP_TAG;

/**
* @author Sevket Goekay <sevketgokay@gmail.com>
Expand Down Expand Up @@ -161,6 +162,16 @@ public List<String> getIdTags() {
.fetch(OCPP_TAG.ID_TAG);
}

@Override
public List<String> getIdTagsWithoutUser() {
return ctx.select(OCPP_TAG.ID_TAG)
.from(OCPP_TAG)
.leftJoin(USER_OCPP_TAG).on(OCPP_TAG.OCPP_TAG_PK.eq(USER_OCPP_TAG.OCPP_TAG_PK))
.where(USER_OCPP_TAG.OCPP_TAG_PK.isNull())
.orderBy(OCPP_TAG.ID_TAG)
.fetch(OCPP_TAG.ID_TAG);
}

@Override
public List<String> getActiveIdTags() {
return ctx.select(OCPP_TAG_ACTIVITY.ID_TAG)
Expand Down
197 changes: 119 additions & 78 deletions src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,35 @@
*/
package de.rwth.idsg.steve.repository.impl;

import com.google.common.base.Strings;
import de.rwth.idsg.steve.SteveException;
import de.rwth.idsg.steve.repository.AddressRepository;
import de.rwth.idsg.steve.repository.UserRepository;
import de.rwth.idsg.steve.repository.dto.User;
import de.rwth.idsg.steve.web.dto.UserForm;
import de.rwth.idsg.steve.web.dto.UserQueryForm;
import jooq.steve.db.tables.records.AddressRecord;
import jooq.steve.db.tables.records.UserRecord;
import lombok.extern.slf4j.Slf4j;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.JoinType;
import org.jooq.Record1;
import org.jooq.Record7;
import org.jooq.Record5;
import org.jooq.Result;
import org.jooq.SelectConditionStep;
import org.jooq.SelectQuery;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.Map;

import static de.rwth.idsg.steve.utils.CustomDSL.includes;
import static jooq.steve.db.Tables.USER_OCPP_TAG;
import static jooq.steve.db.tables.OcppTag.OCPP_TAG;
import static jooq.steve.db.tables.User.USER;

Expand All @@ -60,25 +63,21 @@ public class UserRepositoryImpl implements UserRepository {

@Override
public List<User.Overview> getOverview(UserQueryForm form) {
var ocppTagsPerUser = getOcppTagsInternal(form.getUserPk(), form.getOcppIdTag());

return getOverviewInternal(form)
.map(r -> User.Overview.builder()
.userPk(r.value1())
.ocppTagPk(r.value2())
.ocppIdTag(r.value3())
.name(r.value4() + " " + r.value5())
.phone(r.value6())
.email(r.value7())
.name(r.value2() + " " + r.value3())
.phone(r.value4())
.email(r.value5())
.ocppTagEntries(ocppTagsPerUser.getOrDefault(r.value1(), List.of()))
.build()
);
}

@Override
public User.Details getDetails(int userPk) {

// -------------------------------------------------------------------------
// 1. user table
// -------------------------------------------------------------------------

UserRecord ur = ctx.selectFrom(USER)
.where(USER.USER_PK.equal(userPk))
.fetchOne();
Expand All @@ -87,32 +86,10 @@ public User.Details getDetails(int userPk) {
throw new SteveException("There is no user with id '%s'", userPk);
}

// -------------------------------------------------------------------------
// 2. address table
// -------------------------------------------------------------------------

AddressRecord ar = addressRepository.get(ctx, ur.getAddressPk());

// -------------------------------------------------------------------------
// 3. ocpp_tag table
// -------------------------------------------------------------------------

String ocppIdTag = null;
if (ur.getOcppTagPk() != null) {
Record1<String> record = ctx.select(OCPP_TAG.ID_TAG)
.from(OCPP_TAG)
.where(OCPP_TAG.OCPP_TAG_PK.eq(ur.getOcppTagPk()))
.fetchOne();

if (record != null) {
ocppIdTag = record.value1();
}
}

return User.Details.builder()
.userRecord(ur)
.address(ar)
.ocppIdTag(Optional.ofNullable(ocppIdTag))
.address(addressRepository.get(ctx, ur.getAddressPk()))
.ocppTagEntries(getOcppTagsInternal(userPk, null).getOrDefault(userPk, List.of()))
.build();
}

Expand All @@ -122,7 +99,8 @@ public void add(UserForm form) {
DSLContext ctx = DSL.using(configuration);
try {
Integer addressId = addressRepository.updateOrInsert(ctx, form.getAddress());
addInternal(ctx, form, addressId);
Integer userPk = addInternal(ctx, form, addressId);
refreshOcppTagsInternal(ctx, form, userPk);

} catch (DataAccessException e) {
throw new SteveException("Failed to add the user", e);
Expand All @@ -137,6 +115,7 @@ public void update(UserForm form) {
try {
Integer addressId = addressRepository.updateOrInsert(ctx, form.getAddress());
updateInternal(ctx, form, addressId);
refreshOcppTagsInternal(ctx, form, form.getUserPk());

} catch (DataAccessException e) {
throw new SteveException("Failed to update the user", e);
Expand All @@ -162,44 +141,73 @@ public void delete(int userPk) {
// Private helpers
// -------------------------------------------------------------------------

@SuppressWarnings("unchecked")
private Result<Record7<Integer, Integer, String, String, String, String, String>> getOverviewInternal(UserQueryForm form) {
SelectQuery selectQuery = ctx.selectQuery();
selectQuery.addFrom(USER);
selectQuery.addJoin(OCPP_TAG, JoinType.LEFT_OUTER_JOIN, USER.OCPP_TAG_PK.eq(OCPP_TAG.OCPP_TAG_PK));
selectQuery.addSelect(
USER.USER_PK,
USER.OCPP_TAG_PK,
OCPP_TAG.ID_TAG,
USER.FIRST_NAME,
USER.LAST_NAME,
USER.PHONE,
USER.E_MAIL
);
private Result<Record5<Integer, String, String, String, String>> getOverviewInternal(UserQueryForm form) {
List<Condition> conditions = new ArrayList<>();

if (form.isSetUserPk()) {
selectQuery.addConditions(USER.USER_PK.eq(form.getUserPk()));
conditions.add(USER.USER_PK.eq(form.getUserPk()));
}

if (form.isSetOcppIdTag()) {
selectQuery.addConditions(includes(OCPP_TAG.ID_TAG, form.getOcppIdTag()));
if (form.isSetEmail()) {
conditions.add(includes(USER.E_MAIL, form.getEmail()));
}

if (form.isSetEmail()) {
selectQuery.addConditions(includes(USER.E_MAIL, form.getEmail()));
if (form.isSetOcppIdTag()) {
conditions.add(DSL.exists(
DSL.selectOne()
.from(USER_OCPP_TAG)
.join(OCPP_TAG).on(USER_OCPP_TAG.OCPP_TAG_PK.eq(OCPP_TAG.OCPP_TAG_PK))
.where(USER_OCPP_TAG.USER_PK.eq(USER.USER_PK))
.and(includes(OCPP_TAG.ID_TAG, form.getOcppIdTag()))
));
}

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<String> joinedField = DSL.concat(USER.FIRST_NAME, USER.LAST_NAME);

// Find a matching sequence anywhere within the concatenated representation
selectQuery.addConditions(includes(joinedField, form.getName()));
conditions.add(includes(joinedField, form.getName()));
}

return ctx.select(
USER.USER_PK,
USER.FIRST_NAME,
USER.LAST_NAME,
USER.PHONE,
USER.E_MAIL)
.from(USER)
.where(conditions)
.fetch();
}

private Map<Integer, List<User.OcppTagEntry>> getOcppTagsInternal(Integer userPk, String ocppIdTag) {
List<Condition> conditions = new ArrayList<>();

if (userPk != null) {
conditions.add(USER_OCPP_TAG.USER_PK.eq(userPk));
}

if (!Strings.isNullOrEmpty(ocppIdTag)) {
conditions.add(includes(OCPP_TAG.ID_TAG, ocppIdTag));
}

return selectQuery.fetch();
var results = ctx.select(
USER_OCPP_TAG.USER_PK,
OCPP_TAG.OCPP_TAG_PK,
OCPP_TAG.ID_TAG)
.from(USER_OCPP_TAG)
.join(OCPP_TAG).on(USER_OCPP_TAG.OCPP_TAG_PK.eq(OCPP_TAG.OCPP_TAG_PK))
.where(conditions)
.fetch();

Map<Integer, List<User.OcppTagEntry>> map = new HashMap<>();
for (var entry : results) {
map.computeIfAbsent(entry.value1(), k -> new ArrayList<>())
.add(new User.OcppTagEntry(entry.value2(), entry.value3()));
}
return map;
}

private SelectConditionStep<Record1<Integer>> selectAddressId(int userPk) {
Expand All @@ -214,21 +222,22 @@ private SelectConditionStep<Record1<Integer>> selectOcppTagPk(String ocppIdTag)
.where(OCPP_TAG.ID_TAG.eq(ocppIdTag));
}

private void addInternal(DSLContext ctx, UserForm form, Integer addressPk) {
int count = ctx.insertInto(USER)
.set(USER.FIRST_NAME, form.getFirstName())
.set(USER.LAST_NAME, form.getLastName())
.set(USER.BIRTH_DAY, form.getBirthDay())
.set(USER.SEX, form.getSex().getDatabaseValue())
.set(USER.PHONE, form.getPhone())
.set(USER.E_MAIL, form.getEMail())
.set(USER.NOTE, form.getNote())
.set(USER.ADDRESS_PK, addressPk)
.set(USER.OCPP_TAG_PK, selectOcppTagPk(form.getOcppIdTag()))
.execute();

if (count != 1) {
throw new SteveException("Failed to insert the user");
private Integer addInternal(DSLContext ctx, UserForm form, Integer addressPk) {
try {
return ctx.insertInto(USER)
.set(USER.FIRST_NAME, form.getFirstName())
.set(USER.LAST_NAME, form.getLastName())
.set(USER.BIRTH_DAY, form.getBirthDay())
.set(USER.SEX, form.getSex().getDatabaseValue())
.set(USER.PHONE, form.getPhone())
.set(USER.E_MAIL, form.getEMail())
.set(USER.NOTE, form.getNote())
.set(USER.ADDRESS_PK, addressPk)
.returning(USER.USER_PK)
.fetchOne()
.getUserPk();
} catch (DataAccessException e) {
throw new SteveException("Failed to insert the user", e);
}
}

Expand All @@ -242,7 +251,6 @@ private void updateInternal(DSLContext ctx, UserForm form, Integer addressPk) {
.set(USER.E_MAIL, form.getEMail())
.set(USER.NOTE, form.getNote())
.set(USER.ADDRESS_PK, addressPk)
.set(USER.OCPP_TAG_PK, selectOcppTagPk(form.getOcppIdTag()))
.where(USER.USER_PK.eq(form.getUserPk()))
.execute();
}
Expand All @@ -252,4 +260,37 @@ private void deleteInternal(DSLContext ctx, int userPk) {
.where(USER.USER_PK.equal(userPk))
.execute();
}

/**
* Refresh the full the OCPP tag associations for a user:
* - 1. Delete existing OCPP tags that are not in the form.
* - 2. Insert new OCPP tags from the form that do not already exist for the user.
*/
private void refreshOcppTagsInternal(DSLContext ctx, UserForm form, Integer userPk) {
List<Integer> wantedOcppTagPks = CollectionUtils.isEmpty(form.getIdTagList())
? List.of() // This user wants no OCPP tags
: ctx.select(OCPP_TAG.OCPP_TAG_PK)
.from(OCPP_TAG)
.where(OCPP_TAG.ID_TAG.in(form.getIdTagList()))
.fetch(OCPP_TAG.OCPP_TAG_PK);

// Optimization: Execute the delete query only if we are processing an existing user.
// A new user will not have any existing OCPP tags, so no delete is needed.
//
// 1. Delete entries that are not in the wanted entries
if (form.getUserPk() != null) {
ctx.deleteFrom(USER_OCPP_TAG)
.where(USER_OCPP_TAG.USER_PK.eq(userPk))
.and(USER_OCPP_TAG.OCPP_TAG_PK.notIn(wantedOcppTagPks))
.execute();
}

// 2. Insert new entries that are not already present
if (!wantedOcppTagPks.isEmpty()) {
ctx.insertInto(USER_OCPP_TAG, USER_OCPP_TAG.USER_PK, USER_OCPP_TAG.OCPP_TAG_PK)
.valuesOfRows(wantedOcppTagPks.stream().map(pk -> DSL.row(userPk, pk)).toList())
.onDuplicateKeyIgnore() // Ignore if already exists
.execute();
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/de/rwth/idsg/steve/service/OcppTagService.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public List<String> getIdTags() {
return ocppTagRepository.getIdTags();
}

public List<String> getIdTagsWithoutUser() {
return ocppTagRepository.getIdTagsWithoutUser();
}

public List<String> getActiveIdTags() {
return ocppTagRepository.getActiveIdTags();
}
Expand Down
Loading