1
This commit is contained in:
Christoph
2020-04-18 00:17:07 +02:00
parent 02fe3659b3
commit 9dd6794a1c
10 changed files with 133 additions and 312 deletions

View File

@ -82,6 +82,18 @@ public abstract class Event {
applyEvent(group);
}
public void apply(Group group) throws EventException {
log.trace("Event wird angewendet:\t{}", this);
if (version == 0) {
throw new BadArgumentException("Event wurde nicht initialisiert.");
}
checkGroupIdMatch(group.getId());
group.updateVersion(version);
applyEvent(group);
}
private void checkGroupIdMatch(UUID groupid) throws IdMismatchException {
// CreateGroupEvents müssen die Id erst initialisieren
if (this instanceof CreateGroupEvent) {

View File

@ -5,12 +5,14 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.exception.BadPayloadException;
import mops.gruppen2.domain.service.helper.CommonHelper;
import mops.gruppen2.domain.service.helper.FileHelper;
import mops.gruppen2.persistance.EventRepository;
import mops.gruppen2.persistance.dto.EventDTO;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@ -102,6 +104,14 @@ public class EventStoreService {
return getEventsFromDTOs(eventStore.findGroupEvents(groupId.toString()));
}
public List<Event> findGroupEvents(List<UUID> ids) {
return ids.stream()
.map(id -> eventStore.findGroupEvents(id.toString()))
.map(EventStoreService::getEventsFromDTOs)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public List<String> findGroupPayloads(UUID groupId) {
return eventStore.findGroupPayloads(groupId.toString());
}
@ -109,4 +119,12 @@ public class EventStoreService {
public List<EventDTO> findGroupDTOs(UUID groupid) {
return eventStore.findGroupEvents(groupid.toString());
}
public List<UUID> findChangedGroups(long eventid) {
return CommonHelper.stringsToUUID(eventStore.findChangedGroupIds(eventid));
}
public long findMaxEventId() {
return eventStore.findMaxEventId();
}
}

View File

@ -3,13 +3,25 @@ package mops.gruppen2.domain.service.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.api.GroupRequestWrapper;
import mops.gruppen2.infrastructure.api.GroupWrapper;
import java.util.List;
import java.util.stream.Collectors;
//TODO: sinnvolles format
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class APIHelper {
/*public static GroupRequestWrapper wrap(long status, List<Group> groupList) {
return new GroupRequestWrapper(status, groupList);
}*/
public static GroupRequestWrapper wrap(long status, List<Group> groupList) {
return new GroupRequestWrapper(status, wrap(groupList));
}
public static List<GroupWrapper> wrap(List<Group> groups) {
return groups.stream()
.map(GroupWrapper::new)
.collect(Collectors.toUnmodifiableList());
}
}

View File

@ -4,7 +4,9 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@ -13,4 +15,10 @@ public final class CommonHelper {
public static boolean uuidIsEmpty(UUID uuid) {
return "00000000-0000-0000-0000-000000000000".equals(uuid.toString());
}
public static List<UUID> stringsToUUID(List<String> changedGroupIds) {
return changedGroupIds.stream()
.map(UUID::fromString)
.collect(Collectors.toUnmodifiableList());
}
}

View File

@ -7,6 +7,9 @@ import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.GroupCache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -19,6 +22,22 @@ import java.util.UUID;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ProjectionHelper {
public static List<Group> project(List<Event> events) {
Map<UUID, Group> groups = new HashMap<>();
if (events.isEmpty()) {
return Collections.emptyList();
}
log.trace(groups);
log.trace(events);
events.forEach(event -> event.apply(getOrCreateGroup(groups, event.getGroupid())));
return new ArrayList<>(groups.values());
}
public static void project(Map<UUID, Group> groups, List<Event> events, GroupCache cache) {
if (events.isEmpty()) {
return;

View File

@ -1,4 +1,31 @@
package mops.gruppen2.infrastructure.api;
import lombok.Value;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import java.util.List;
import java.util.UUID;
@Value
public class GroupWrapper {
UUID groupid;
Type type;
UUID parent;
String title;
String description;
List<User> admins;
List<User> regulars;
public GroupWrapper(Group group) {
groupid = group.getId();
type = group.getType();
parent = group.getParent();
title = group.getTitle();
description = group.getDescription();
admins = group.getAdmins();
regulars = group.getRegulars();
}
}

View File

@ -1,13 +1,27 @@
package mops.gruppen2.infrastructure.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.service.EventStoreService;
import mops.gruppen2.domain.service.helper.APIHelper;
import mops.gruppen2.domain.service.helper.ProjectionHelper;
import mops.gruppen2.infrastructure.GroupCache;
import mops.gruppen2.infrastructure.api.GroupRequestWrapper;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Api zum Datenabgleich.
*/
@ -18,8 +32,7 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/gruppen2/api")
public class APIController {
//TODO: redo api
private final GroupCache cache;
private final EventStoreService eventStoreService;
/**
@ -28,38 +41,41 @@ public class APIController {
*
* @param eventId Die Event-ID, welche der Anfragesteller beim letzten Aufruf erhalten hat
*/
/*@GetMapping("/update/{id}")
@GetMapping("/update/{id}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt veränderte Gruppen zurück")
public GroupRequestWrapper getApiUpdate(@ApiParam("Letzte gespeicherte EventId des Anfragestellers")
@PathVariable("id") long eventId) {
return APIHelper.wrap(eventStoreService.findMaxEventId(),
projectionHelper.projectChangedGroups(eventId));
}*/
ProjectionHelper.project(eventStoreService.findGroupEvents(eventStoreService.findChangedGroups(eventId))));
}
/**
* Gibt die Gruppen-IDs von Gruppen, in welchen der übergebene Nutzer teilnimmt, zurück.
*/
/*@GetMapping("/usergroups/{id}")
@GetMapping("/usergroups/{id}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt Gruppen zurück, in welchen ein Nutzer teilnimmt")
public List<String> getApiUserGroups(@ApiParam("Nutzer-Id")
@PathVariable("id") String userId) {
return CommonHelper.uuidsToString(eventStoreService.findExistingUserGroups(userId));
}*/
return cache.userGroups(userId).stream()
.map(Group::getId)
.map(UUID::toString)
.collect(Collectors.toUnmodifiableList());
}
/**
* Konstruiert eine einzelne, vollständige Gruppe.
*/
/*@GetMapping("/group/{id}")
@GetMapping("/group/{id}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück")
public Group getApiGroup(@ApiParam("Gruppen-Id der gefordeten Gruppe")
@PathVariable("id") String groupId) {
return projectionHelper.projectGroupById(UUID.fromString(groupId));
}*/
return cache.group(UUID.fromString(groupId));
}
}

View File

@ -23,4 +23,10 @@ public interface EventRepository extends CrudRepository<EventDTO, Long> {
@Query("SELECT event_payload FROM event WHERE group_id = :groupid")
List<String> findGroupPayloads(@Param("groupid") String groupId);
@Query("SELECT MAX(event_id) FROM event")
long findMaxEventId();
@Query("SELECT DISTINCT group_id FROM event WHERE event_id > :eventid")
List<String> findChangedGroupIds(@Param("eventid") long eventid);
}

View File

@ -20,7 +20,7 @@
<div class="content" th:insert="~{fragments/groups :: groupcontent}"></div>
<!--Dummy Integrationen-->
<!--Dummy Integrationen, der Inhalt kommt natürlich aus der Gruppe bzw. von anderen Systemen-->
<div class="d-flex flex-row flex-wrap mr-n2 mt-n2">
<!--Materialsammlung-->
<div th:if="${group.hasMaterial()}" class="content flex-grow-1 mr-2 mt-2">

View File

@ -1,297 +0,0 @@
package mops.gruppen2;
public class TestBuilder {
/*private static final Faker faker = new Faker();
public static Account account(String name) {
return new Account(name,
"",
"",
"",
"",
null);
}
public static Group apply(Group group, Event... events) {
for (Event event : events) {
event.apply(group);
}
return group;
}
public static Group apply(Event... events) {
return apply(new Group(), events);
}
*//**
* Baut eine UUID.
*
* @param id Integer id
*
* @return UUID
*//*
public static UUID uuidMock(int id) {
String idString = String.valueOf(Math.abs(id + 1));
return UUID.fromString("00000000-0000-0000-0000-"
+ "0".repeat(11 - idString.length())
+ idString);
}
*//**
* Generiert ein EventLog mit mehreren Gruppen und Usern.
*
* @param count Gruppenanzahl
* @param membercount Mitgliederanzahl pro Gruppe
*
* @return Eventliste
*//*
public static List<Event> completePublicGroups(int count, int membercount) {
return IntStream.range(0, count)
.parallel()
.mapToObj(i -> completePublicGroup(membercount))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public static List<Event> completePrivateGroups(int count, int membercount) {
return IntStream.range(0, count)
.parallel()
.mapToObj(i -> completePrivateGroup(membercount))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public static List<Event> completePublicGroup(int membercount) {
List<Event> eventList = new ArrayList<>();
UUID groupId = UUID.randomUUID();
eventList.add(createPublicGroupEvent(groupId));
eventList.add(updateGroupTitleEvent(groupId));
eventList.add(updateGroupDescriptionEvent(groupId));
eventList.add(new SetLimitEvent(groupId, "fgsadggas", new Limit(Long.MAX_VALUE)));
eventList.addAll(addUserEvents(membercount, groupId));
return eventList;
}
public static List<Event> completePrivateGroup(int membercount) {
List<Event> eventList = new ArrayList<>();
UUID groupId = UUID.randomUUID();
eventList.add(createPrivateGroupEvent(groupId));
eventList.add(updateGroupTitleEvent(groupId));
eventList.add(updateGroupDescriptionEvent(groupId));
eventList.add(new SetLimitEvent(groupId, "fgsadggas", new Limit(Long.MAX_VALUE)));
eventList.addAll(addUserEvents(membercount, groupId));
return eventList;
}
public static List<Event> completePublicGroup() {
return completePublicGroup(100);
}
public static List<Event> completePrivateGroup() {
return completePrivateGroup(100);
}
*//**
* Generiert mehrere CreateGroupEvents, 1 <= groupId <= count.
*
* @param count Anzahl der verschiedenen Gruppen
*
* @return Eventliste
*//*
public static List<Event> createPublicGroupEvents(int count) {
return IntStream.range(0, count)
.parallel()
.mapToObj(i -> createPublicGroupEvent())
.collect(Collectors.toList());
}
public static List<Event> createPrivateGroupEvents(int count) {
return IntStream.range(0, count)
.parallel()
.mapToObj(i -> createPublicGroupEvent())
.collect(Collectors.toList());
}
public static List<Event> createMixedGroupEvents(int count) {
return IntStream.range(0, count)
.parallel()
.mapToObj(i -> faker.random().nextInt(0, 1) > 0.5
? createPublicGroupEvent()
: createPrivateGroupEvent())
.collect(Collectors.toList());
}
public static List<Event> createPrivateGroupEvents(UUID groupId) {
return (new ArrayList<>()).addAll(createGroupEvent(groupId)),
new SetTypeEvent(groupId);
}
public static Event createPrivateGroupEvent() {
return createPrivateGroupEvent(UUID.randomUUID());
}
public static Event createPublicGroupEvent(UUID groupId) {
return createGroupEvent(groupId, Type.PUBLIC);
}
public static Event createPublicGroupEvent() {
return createPublicGroupEvent(UUID.randomUUID());
}
public static Event createGroupEvent(UUID groupId) {
return new CreateGroupEvent(groupId,
faker.random().hex(),
LocalDateTime.now());
}
public static Event createLectureEvent() {
return createLectureEvent(UUID.randomUUID());
}
public static Event createLectureEvent(UUID groupId) {
return new CreateGroupEvent(
groupId,
faker.random().hex(),
null,
Type.LECTURE
);
}
*//**
* Generiert mehrere AddUserEvents für eine Gruppe, 1 <= user_id <= count.
*
* @param count Anzahl der Mitglieder
* @param groupId Gruppe, zu welcher geaddet wird
*
* @return Eventliste
*//*
public static List<Event> addUserEvents(int count, UUID groupId) {
return IntStream.range(0, count)
.parallel()
.mapToObj(i -> addUserEvent(groupId, String.valueOf(i)))
.collect(Collectors.toList());
}
public static Event addUserEvent(UUID groupId, String userId) {
String firstname = firstname();
String lastname = lastname();
return new AddMemberEvent(
groupId,
userId,
firstname,
lastname,
firstname + "." + lastname + "@mail.de"
);
}
public static Event addUserEvent(UUID groupId) {
return addUserEvent(groupId, faker.random().hex());
}
// Am besten einfach nicht benutzen
public static List<Event> deleteUserEvents(int count, List<Event> eventList) {
List<Event> removeEvents = new ArrayList<>();
List<Event> shuffle = eventList.parallelStream()
.filter(event -> event instanceof AddMemberEvent)
.collect(Collectors.toList());
Collections.shuffle(shuffle);
for (Event event : shuffle) {
removeEvents.add(new KickMemberEvent(event.getGroupid(), event.getTarget()));
if (removeEvents.size() >= count) {
break;
}
}
return removeEvents;
}
*//**
* Erzeugt mehrere DeleteUserEvents, sodass eine Gruppe komplett geleert wird.
*
* @param group Gruppe welche geleert wird
*
* @return Eventliste
*//*
public static List<Event> deleteUserEvents(Group group) {
return group.getMemberships().parallelStream()
.map(user -> deleteUserEvent(group.getGroupid(), user.getUserId()))
.collect(Collectors.toList());
}
public static Event deleteUserEvent(UUID groupId, String userId) {
return new KickMemberEvent(
groupId,
userId
);
}
public static Event updateGroupDescriptionEvent(UUID groupId) {
return updateGroupDescriptionEvent(groupId, quote());
}
public static Event updateGroupDescriptionEvent(UUID groupId, String description) {
return new SetDescriptionEvent(
groupId,
faker.random().hex(),
description
);
}
public static Event updateGroupTitleEvent(UUID groupId) {
return updateGroupTitleEvent(groupId, champion());
}
public static Event updateGroupTitleEvent(UUID groupId, String title) {
return new SetTitleEvent(
groupId,
faker.random().hex(),
title
);
}
public static Event updateUserLimitMaxEvent(UUID groupId) {
return new SetLimitEvent(groupId, firstname(), Long.MAX_VALUE);
}
public static Event updateRoleEvent(UUID groupId, String userId, Role role) {
return new UpdateRoleEvent(
groupId,
userId,
role
);
}
public static Event deleteGroupEvent(UUID groupId) {
return new DestroyGroupEvent(groupId, faker.random().hex());
}
private static String firstname() {
return clean(faker.name().firstName());
}
private static String lastname() {
return clean(faker.name().lastName());
}
private static String champion() {
return clean(faker.leagueOfLegends().champion());
}
private static String quote() {
return clean(faker.leagueOfLegends().quote());
}
private static String clean(String string) {
return string.replaceAll("['\";,]", "");
}*/
}