diff --git a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java index 69500e6..e17778a 100644 --- a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java +++ b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java @@ -5,8 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.exception.BadPayloadException; -import mops.gruppen2.domain.exception.GroupNotFoundException; -import mops.gruppen2.domain.service.helper.JsonHelper; +import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.persistance.EventRepository; import mops.gruppen2.persistance.dto.EventDTO; import org.springframework.stereotype.Service; @@ -55,15 +54,14 @@ public class EventStoreService { */ private static EventDTO getDTOFromEvent(Event event) { try { - String payload = JsonHelper.serializeEvent(event); + String payload = FileHelper.serializeEventJson(event); return new EventDTO(null, event.getGroupid().toString(), event.getVersion(), event.getExec(), event.getTarget(), - event.type(), - payload, - Timestamp.valueOf(event.getDate())); + Timestamp.valueOf(event.getDate()), + payload); } catch (JsonProcessingException e) { log.error("Event ({}) konnte nicht serialisiert werden!", event, e); throw new BadPayloadException(EventStoreService.class.toString()); @@ -85,7 +83,7 @@ public class EventStoreService { private static Event getEventFromDTO(EventDTO dto) { try { - return JsonHelper.deserializeEvent(dto.getEvent_payload()); + return FileHelper.deserializeEventJson(dto.getEvent_payload()); } catch (JsonProcessingException e) { log.error("Payload {} konnte nicht deserialisiert werden!", dto.getEvent_payload(), e); throw new BadPayloadException(EventStoreService.class.toString()); @@ -104,10 +102,11 @@ public class EventStoreService { return getEventsFromDTOs(eventStore.findGroupEvents(groupId.toString())); } - public String findGroupPayloads(UUID groupId) { - return eventStore.findGroupPayloads(groupId.toString()).stream() - .map(payload -> payload + "\n") - .reduce((String payloadA, String payloadB) -> payloadA + payloadB) - .orElseThrow(() -> new GroupNotFoundException("Keine Payloads gefunden.")); + public List findGroupPayloads(UUID groupId) { + return eventStore.findGroupPayloads(groupId.toString()); + } + + public List findGroupDTOs(UUID groupid) { + return eventStore.findGroupEvents(groupid.toString()); } } diff --git a/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java deleted file mode 100644 index 99dddc7..0000000 --- a/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java +++ /dev/null @@ -1,64 +0,0 @@ -package mops.gruppen2.domain.service.helper; - -import com.fasterxml.jackson.databind.ObjectReader; -import com.fasterxml.jackson.dataformat.csv.CsvMapper; -import com.fasterxml.jackson.dataformat.csv.CsvSchema; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.exception.WrongFileException; -import mops.gruppen2.domain.model.group.User; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@Log4j2 -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class CsvHelper { - - public static List readCsvFile(MultipartFile file) throws EventException { - if (file == null || file.isEmpty()) { - return Collections.emptyList(); - } - - try { - List userList = read(file.getInputStream()); - return userList.stream() - .distinct() - .collect(Collectors.toList()); //filter duplicates from list - } catch (IOException e) { - log.error("File konnte nicht gelesen werden!", e); - throw new WrongFileException(file.getOriginalFilename()); - } - } - - private static List read(InputStream stream) throws IOException { - CsvMapper mapper = new CsvMapper(); - - CsvSchema schema = mapper.schemaFor(User.class).withHeader().withColumnReordering(true); - ObjectReader reader = mapper.readerFor(User.class).with(schema); - - return reader.readValues(stream).readAll(); - } - - public static String writeCsvUserList(List members) { - StringBuilder builder = new StringBuilder(); - builder.append("id,givenname,familyname,email\n"); - - members.forEach(user -> builder.append(user.getId()) - .append(",") - .append(user.getGivenname()) - .append(",") - .append(user.getFamilyname()) - .append(",") - .append(user.getEmail()) - .append("\n")); - - return builder.toString(); - } -} diff --git a/src/main/java/mops/gruppen2/domain/service/helper/FileHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/FileHelper.java new file mode 100644 index 0000000..78857a6 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/service/helper/FileHelper.java @@ -0,0 +1,148 @@ +package mops.gruppen2.domain.service.helper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.event.Event; +import mops.gruppen2.domain.exception.EventException; +import mops.gruppen2.domain.exception.GroupNotFoundException; +import mops.gruppen2.domain.exception.WrongFileException; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.persistance.dto.EventDTO; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class FileHelper { + + // ######################################## CSV ############################################# + + + public static List readCsvFile(MultipartFile file) throws EventException { + if (file == null || file.isEmpty()) { + return Collections.emptyList(); + } + + try { + List userList = readCsv(file.getInputStream()); + return userList.stream() + .distinct() + .collect(Collectors.toList()); //filter duplicates from list + } catch (IOException e) { + log.error("File konnte nicht gelesen werden!", e); + throw new WrongFileException(file.getOriginalFilename()); + } + } + + private static List readCsv(InputStream stream) throws IOException { + CsvMapper mapper = new CsvMapper(); + + CsvSchema schema = mapper.schemaFor(User.class).withHeader().withColumnReordering(true); + ObjectReader reader = mapper.readerFor(User.class).with(schema); + + return reader.readValues(stream).readAll(); + } + + public static String writeCsvUserList(List members) { + StringBuilder builder = new StringBuilder(); + builder.append("id,givenname,familyname,email\n"); + + members.forEach(user -> builder.append(user.getId()) + .append(",") + .append(user.getGivenname()) + .append(",") + .append(user.getFamilyname()) + .append(",") + .append(user.getEmail()) + .append("\n")); + + return builder.toString(); + } + + + // ########################################## JSON ########################################### + + + /** + * Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload. + * + * @param event Java-Event-Repräsentation + * + * @return JSON-Event-Payload als String + * + * @throws JsonProcessingException Bei JSON Fehler + */ + + public static String serializeEventJson(Event event) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + String payload = mapper.writeValueAsString(event); + log.trace(payload); + return payload; + } + + /** + * Übersetzt eine JSON-Event-Payload zu einer Java-Event-Repräsentation. + * + * @param json JSON-Event-Payload als String + * + * @return Java-Event-Repräsentation + * + * @throws JsonProcessingException Bei JSON Fehler + */ + public static Event deserializeEventJson(String json) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + Event event = mapper.readValue(json, Event.class); + log.trace(event); + return event; + } + + + // ############################################### TXT ####################################### + + + public static String payloadsToPlain(List payloads) { + return payloads.stream() + .map(payload -> payload + "\n") + .reduce((String payloadA, String payloadB) -> payloadA + payloadB) + .orElseThrow(() -> new GroupNotFoundException("Keine Payloads gefunden.")); + } + + public static String eventDTOsToSql(List dtos) { + StringBuilder builder = new StringBuilder(); + + builder.append("INSERT INTO event(group_id, group_version, exec_id, target_id, event_date, event_payload)\nVALUES\n"); + + dtos.forEach(dto -> builder.append("('") + .append(dto.getGroup_id()) + .append("','") + .append(dto.getGroup_version()) + .append("','") + .append(dto.getExec_id()) + .append("','") + .append(dto.getTarget_id()) + .append("','") + .append(dto.getEvent_date()) + .append("','") + .append(dto.getEvent_payload()) + .append("'),\n")); + + builder.replace(builder.length() - 2, builder.length(), ";"); + + return builder.toString(); + } + + + // ############################################### SQL ####################################### +} diff --git a/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java deleted file mode 100644 index b66c57f..0000000 --- a/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -package mops.gruppen2.domain.service.helper; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.event.Event; - -/** - * Übersetzt JSON-Event-Payloads zu Java-Event-Repräsentationen und zurück. - */ -@Log4j2 -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class JsonHelper { - - /** - * Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload. - * - * @param event Java-Event-Repräsentation - * - * @return JSON-Event-Payload als String - * - * @throws JsonProcessingException Bei JSON Fehler - */ - - public static String serializeEvent(Event event) throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); - String payload = mapper.writeValueAsString(event); - log.trace(payload); - return payload; - } - - /** - * Übersetzt eine JSON-Event-Payload zu einer Java-Event-Repräsentation. - * - * @param json JSON-Event-Payload als String - * - * @return Java-Event-Repräsentation - * - * @throws JsonProcessingException Bei JSON Fehler - */ - public static Event deserializeEvent(String json) throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); - Event event = mapper.readValue(json, Event.class); - log.trace(event); - return event; - } -} diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java index 84d4e1e..60cb879 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java @@ -11,7 +11,7 @@ import mops.gruppen2.domain.model.group.wrapper.Limit; import mops.gruppen2.domain.model.group.wrapper.Parent; import mops.gruppen2.domain.model.group.wrapper.Title; import mops.gruppen2.domain.service.GroupService; -import mops.gruppen2.domain.service.helper.CsvHelper; +import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.domain.service.helper.ValidationHelper; import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; @@ -67,7 +67,7 @@ public class GroupCreationController { // ROLE_studentin kann kein CSV importieren if (token.getAccount().getRoles().contains("orga")) { - groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); + groupService.addUsersToGroup(group, principal, FileHelper.readCsvFile(file)); } return "redirect:/gruppen2/details/" + group.getId(); diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java index 81a3dc2..e7043fd 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java @@ -10,7 +10,7 @@ import mops.gruppen2.domain.model.group.wrapper.Limit; import mops.gruppen2.domain.model.group.wrapper.Title; import mops.gruppen2.domain.service.EventStoreService; import mops.gruppen2.domain.service.GroupService; -import mops.gruppen2.domain.service.helper.CsvHelper; +import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.domain.service.helper.ValidationHelper; import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; @@ -112,9 +112,9 @@ public class GroupDetailsController { } @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) - @GetMapping(value = "details/{id}/export/history", produces = "text/plain;charset=UTF-8") - public void getDetailsExportHistory(HttpServletResponse response, - @PathVariable("id") String groupId) { + @GetMapping(value = "details/{id}/export/history/plain", produces = "text/plain;charset=UTF-8") + public void getDetailsExportHistoryPlain(HttpServletResponse response, + @PathVariable("id") String groupId) { String filename = "eventlog-" + groupId + ".txt"; @@ -123,7 +123,29 @@ public class GroupDetailsController { "attachment; filename=\"" + filename + "\""); try { - response.getWriter().write(eventStoreService.findGroupPayloads(UUID.fromString(groupId))); + response.getWriter() + .write(FileHelper.payloadsToPlain( + eventStoreService.findGroupPayloads(UUID.fromString(groupId)))); + } catch (IOException e) { + log.error("Payloads konnten nicht geschrieben werden.", e); + } + } + + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping(value = "details/{id}/export/history/sql", produces = "application/sql;charset=UTF-8") + public void getDetailsExportHistorySql(HttpServletResponse response, + @PathVariable("id") String groupId) { + + String filename = "data.sql"; + + response.setContentType("application/sql;charset=UTF-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + filename + "\""); + + try { + response.getWriter() + .write(FileHelper.eventDTOsToSql( + eventStoreService.findGroupDTOs(UUID.fromString(groupId)))); } catch (IOException e) { log.error("Payloads konnten nicht geschrieben werden.", e); } @@ -142,7 +164,7 @@ public class GroupDetailsController { try { response.getWriter() - .print(CsvHelper.writeCsvUserList(groupCache.group(UUID.fromString(groupId)).getMembers())); + .print(FileHelper.writeCsvUserList(groupCache.group(UUID.fromString(groupId)).getMembers())); } catch (IOException e) { log.error("Teilnehmerliste konnte nicht geschrieben werden.", e); } @@ -209,7 +231,7 @@ public class GroupDetailsController { String principal = token.getName(); Group group = groupCache.group(UUID.fromString(groupId)); - groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); + groupService.addUsersToGroup(group, principal, FileHelper.readCsvFile(file)); return "redirect:/gruppen2/details/" + groupId + "/edit"; } diff --git a/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java b/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java index 1032027..c46878a 100644 --- a/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java +++ b/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java @@ -21,8 +21,6 @@ public class EventDTO { String exec_id; String target_id; - String event_type; + Timestamp event_date; String event_payload; - - Timestamp timestamp; } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..45613fb --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,27 @@ +INSERT INTO event(group_id, group_version, exec_id, target_id, event_date, event_payload) +VALUES ('e65dd5f1-b252-4512-8db4-0407b31d199f', '1', 'orga', 'null', '2020-04-17 18:52:01.259555', + '{"class":"CREATEGROUP","date":[2020,4,17,18,52,1,259555000],"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":1,"exec":"orga","target":null}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '2', 'orga', 'orga', '2020-04-17 18:52:01.291448', + '{"class":"ADDMEMBER","user":{"id":"orga","givenname":"Thomas","familyname":"Organisator","email":"orga@hhu.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":2,"exec":"orga","target":"orga","date":[2020,4,17,18,52,1,291448000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '3', 'orga', 'orga', '2020-04-17 18:52:01.301972', + '{"class":"UPDATEROLE","role":"ADMIN","groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":3,"exec":"orga","target":"orga","date":[2020,4,17,18,52,1,301972000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '4', 'orga', 'null', '2020-04-17 18:52:01.3053', + '{"class":"SETLIMIT","limit":{"value":1000},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":4,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,305300000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '5', 'orga', 'null', '2020-04-17 18:52:01.310406', + '{"class":"SETTYPE","type":"LECTURE","groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":5,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,310406000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '6', 'orga', 'null', '2020-04-17 18:52:01.313421', + '{"class":"SETPARENT","parent":{"id":"00000000-0000-0000-0000-000000000000"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":6,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,313421000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '7', 'orga', 'null', '2020-04-17 18:52:01.317931', + '{"class":"SETTITLE","title":{"value":"Programmierung SS2020"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":7,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,317931000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '8', 'orga', 'null', '2020-04-17 18:52:01.320693', + '{"class":"SETDESCRIPTION","desc":{"value":"Einführung in die objektorientierte Programmierung mit Java."},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":8,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,320693000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '9', 'orga', 'A5ggd', '2020-04-17 18:52:01.330879', + '{"class":"ADDMEMBER","user":{"id":"A5ggd","givenname":"peter","familyname":"pan","email":"peter@pan.com"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":9,"exec":"orga","target":"A5ggd","date":[2020,4,17,18,52,1,330879000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '10', 'orga', 'Affs', '2020-04-17 18:52:01.333756', + '{"class":"ADDMEMBER","user":{"id":"Affs","givenname":"olaf","familyname":"pomodoro","email":"ol@f-99.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":10,"exec":"orga","target":"Affs","date":[2020,4,17,18,52,1,333756000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '11', 'orga', '55fdd', '2020-04-17 18:52:01.336206', + '{"class":"ADDMEMBER","user":{"id":"55fdd","givenname":"dieter","familyname":"niemöller","email":"pfarrer@erde.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":11,"exec":"orga","target":"55fdd","date":[2020,4,17,18,52,1,336206000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '12', 'orga', '22ööl', '2020-04-17 18:52:01.338582', + '{"class":"ADDMEMBER","user":{"id":"22ööl","givenname":"thomas","familyname":"müller","email":"thot@scheisse.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":12,"exec":"orga","target":"22ööl","date":[2020,4,17,18,52,1,338582000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '13', 'orga', 'tdsd8', '2020-04-17 18:52:01.341216', + '{"class":"ADDMEMBER","user":{"id":"tdsd8","givenname":"tobidignouserandingdong","familyname":"abraham","email":"g@gmail.mail"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":13,"exec":"orga","target":"tdsd8","date":[2020,4,17,18,52,1,341216000]}'); diff --git a/src/main/resources/schema-h2.sql b/src/main/resources/schema-h2.sql index 8cc499d..0f2779a 100644 --- a/src/main/resources/schema-h2.sql +++ b/src/main/resources/schema-h2.sql @@ -7,7 +7,6 @@ CREATE TABLE event group_version INT NOT NULL, exec_id VARCHAR(50) NOT NULL, target_id VARCHAR(50), - event_type VARCHAR(32) NOT NULL, - timestamp DATETIME NOT NULL, + event_date DATETIME NOT NULL, event_payload VARCHAR(2500) NOT NULL ); diff --git a/src/main/resources/templates/edit.html b/src/main/resources/templates/edit.html index 853d15d..f93120b 100644 --- a/src/main/resources/templates/edit.html +++ b/src/main/resources/templates/edit.html @@ -100,9 +100,15 @@ - Event-Log exportieren + Event-Log exportieren (TXT) + + + + Event-Log exportieren (SQL)