chore: Initial import of FLEX training material

This commit is contained in:
Alexander Kobjolke 2024-11-07 21:02:53 +01:00
parent c01246d4f7
commit 12235acc42
1020 changed files with 53940 additions and 0 deletions

View file

@ -0,0 +1,4 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<!-- spotbugs message is Possible null pointer dereference of Kino.kinoSaele in equals method.
But the if statements before that line guarantee not null. -->
<Class name="de.accso.flexinale.backoffice.api_contract.event.model.KinoTO" />
<Bug pattern="NP_NULL_ON_SOME_PATH_MIGHT_BE_INFEASIBLE" />
</Match>
<Match>
<!-- spotbugs message is ... may expose internal representation by returning KinoTO.kinoSaele -->
<Class name="de.accso.flexinale.backoffice.api_contract.event.model.KinoTO" />
<Bug pattern="EI_EXPOSE_REP" />
</Match>
<Match>
<!-- spotbugs message is ... may expose internal representation by returning KinoTO.kinoSaele -->
<Class name="de.accso.flexinale.backoffice.api_contract.event.model.KinoTO" />
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
</FindBugsFilter>

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed</artifactId>
<version>2024.3.0</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>flexinale-distributed-backoffice_api_contract</artifactId>
<version>2024.3.0</version>
<name>Flexinale Distributed Backoffice API Contract</name>
<description>Flexinale - FLEX case-study &quot;film festival&quot;, distributed services, backoffice_api-contract</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs-maven-plugin.version}</version>
<configuration>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>${findsecbugs-maven-plugin.version}</version>
</plugin>
</plugins>
<excludeFilterFile>SpotbugsExcludeFilter.xml</excludeFilterFile>
</configuration>
<dependencies>
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>${spotbugs.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,21 @@
package de.accso.flexinale.backoffice.api_contract.event;
import de.accso.flexinale.common.api.event.Event;
import java.util.Set;
public final class AllBackofficeEvents {
public static final Set<Class<? extends Event>> eventTypes =
Set.of(
FilmCreatedEvent.class,
FilmUpdatedEvent.class,
FilmDeletedEvent.class,
KinoCreatedEvent.class,
KinoUpdatedEvent.class,
KinoDeletedEvent.class,
VorfuehrungCreatedEvent.class,
VorfuehrungUpdatedEvent.class,
VorfuehrungDeletedEvent.class
);
}

View file

@ -0,0 +1,42 @@
package de.accso.flexinale.backoffice.api_contract.event;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.common.api.event.AbstractEvent;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public abstract sealed class FilmCRUDEvent extends AbstractEvent implements Event
permits FilmCreatedEvent, FilmUpdatedEvent, FilmDeletedEvent {
public enum CRUD { CREATE, UPDATE, DELETE }
@DoNotCheckInArchitectureTests
public final FilmTO film;
protected FilmCRUDEvent(final FilmTO film) {
this.film = film;
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final FilmCRUDEvent that = (FilmCRUDEvent) o;
return new EqualsBuilder().appendSuper(super.equals(o))
.append(version(), that.version())
.append(film, that.film).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.appendSuper(super.hashCode()).append(film)
.append(version())
.toHashCode();
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class FilmCreatedEvent extends FilmCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private FilmCreatedEvent() { super(null); } // needed for (de)serialization via Jackson
public FilmCreatedEvent(final FilmTO film) {
super(film);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class FilmDeletedEvent extends FilmCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private FilmDeletedEvent() { super(null); } // needed for (de)serialization via Jackson
public FilmDeletedEvent(final FilmTO film) {
super(film);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class FilmUpdatedEvent extends FilmCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private FilmUpdatedEvent() { super(null); } // needed for (de)serialization via Jackson
public FilmUpdatedEvent(final FilmTO film) {
super(film);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,42 @@
package de.accso.flexinale.backoffice.api_contract.event;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.common.api.event.AbstractEvent;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public abstract sealed class KinoCRUDEvent extends AbstractEvent implements Event
permits KinoCreatedEvent, KinoUpdatedEvent, KinoDeletedEvent {
public enum CRUD { CREATE, UPDATE, DELETE }
@DoNotCheckInArchitectureTests
public final KinoTO kino;
protected KinoCRUDEvent(final KinoTO kino) {
this.kino = kino;
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final KinoCRUDEvent that = (KinoCRUDEvent) o;
return new EqualsBuilder().appendSuper(super.equals(o))
.append(version(), that.version())
.append(kino, that.kino).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.appendSuper(super.hashCode()).append(kino)
.append(version())
.toHashCode();
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class KinoCreatedEvent extends KinoCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private KinoCreatedEvent() { super(null); } // needed for (de)serialization via Jackson
public KinoCreatedEvent(final KinoTO kino) {
super(kino);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class KinoDeletedEvent extends KinoCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private KinoDeletedEvent() { super(null); } // needed for (de)serialization via Jackson
public KinoDeletedEvent(final KinoTO kino) {
super(kino);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class KinoUpdatedEvent extends KinoCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private KinoUpdatedEvent() { super(null); } // needed for (de)serialization via Jackson
public KinoUpdatedEvent(final KinoTO kino) {
super(kino);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,42 @@
package de.accso.flexinale.backoffice.api_contract.event;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.common.api.event.AbstractEvent;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public abstract sealed class VorfuehrungCRUDEvent extends AbstractEvent implements Event
permits VorfuehrungCreatedEvent, VorfuehrungUpdatedEvent, VorfuehrungDeletedEvent {
public enum CRUD { CREATE, UPDATE, DELETE }
@DoNotCheckInArchitectureTests
public final VorfuehrungTO vorfuehrung;
protected VorfuehrungCRUDEvent(final VorfuehrungTO vorfuehrung) {
this.vorfuehrung = vorfuehrung;
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final VorfuehrungCRUDEvent that = (VorfuehrungCRUDEvent) o;
return new EqualsBuilder().appendSuper(super.equals(o))
.append(version(), that.version())
.append(vorfuehrung, that.vorfuehrung).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.appendSuper(super.hashCode()).append(vorfuehrung)
.append(version())
.toHashCode();
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class VorfuehrungCreatedEvent extends VorfuehrungCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private VorfuehrungCreatedEvent() { super(null); } // needed for (de)serialization via Jackson
public VorfuehrungCreatedEvent(final VorfuehrungTO vorfuehrung) {
super(vorfuehrung);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class VorfuehrungDeletedEvent extends VorfuehrungCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private VorfuehrungDeletedEvent() { super(null); } // needed for (de)serialization via Jackson
public VorfuehrungDeletedEvent(final VorfuehrungTO vorfuehrung) {
super(vorfuehrung);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Versionable;
@SuppressWarnings({"unused", "CanBeFinal"})
public non-sealed class VorfuehrungUpdatedEvent extends VorfuehrungCRUDEvent {
private static Version version = Versionable.initialVersion().inc();
private VorfuehrungUpdatedEvent() { super(null); } // needed for (de)serialization via Jackson
public VorfuehrungUpdatedEvent(final VorfuehrungTO vorfuehrung) {
super(vorfuehrung);
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
}

View file

@ -0,0 +1,33 @@
package de.accso.flexinale.backoffice.api_contract.event.model;
import de.accso.flexinale.common.shared_kernel.EqualsByContent;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import java.io.Serializable;
@SuppressWarnings("unused")
public record FilmTO(Id id, Version version,
Titel titel, ImdbUrl imdbUrl, DauerInMinuten dauerInMinuten)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record Titel(String raw) implements RawWrapper<String> {}
public record ImdbUrl(String raw) implements RawWrapper<String> {}
public record DauerInMinuten(Integer raw) implements RawWrapper<Integer> {}
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final FilmTO that = (FilmTO) o;
return new EqualsBuilder()
.append(id, that.id)
.append(titel, that.titel).append(imdbUrl, that.imdbUrl)
.append(dauerInMinuten, that.dauerInMinuten).isEquals();
}
}

View file

@ -0,0 +1,73 @@
package de.accso.flexinale.backoffice.api_contract.event.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.Serializable;
public record KinoSaalTO(Id id, Version version,
Name name, AnzahlPlaetze anzahlPlaetze,
@JsonIgnore @DoNotCheckInArchitectureTests KinoTO kino)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record Name(String raw) implements RawWrapper<String> {}
public record AnzahlPlaetze(Integer raw) implements RawWrapper<Integer> {}
@Override
public String toString() {
return "KinoSaalTO{" +
"id='" + id + '\'' +
", version=" + version +
", name='" + name + '\'' +
", anzahlPlaetze=" + anzahlPlaetze +
(kino != null ? ", kino.id=" + kino.id() : ", kino=null") +
'}';
}
@Override
public boolean equals(final Object o) {
if (!equalsByContent(o)) return false;
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KinoSaalTO that = (KinoSaalTO) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder(17, 37).append(id).append(version)
.append(name).append(anzahlPlaetze);
if (kino != null) {
builder.append(kino.id()); // do not use kino but only its id (otherwise Stackoverflow error)
}
return builder.toHashCode();
}
@SuppressWarnings("ConstantValue")
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KinoSaalTO that = (KinoSaalTO) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(name, that.name)
.append(anzahlPlaetze, that.anzahlPlaetze).
isEquals();
if (!result) return false;
if (kino == null && that.kino == null) return true;
if (kino == null && that.kino != null) return false;
if (kino != null) {
result = kino.id().equals(that.kino.id()); // do not check kino but only its id (otherwise Stackoverflow error)
}
return result;
}
}

View file

@ -0,0 +1,61 @@
package de.accso.flexinale.backoffice.api_contract.event.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public record KinoTO(Id id, Version version,
Name name, Adresse adresse, EmailAdresse emailAdresse,
@DoNotCheckInArchitectureTests Set<KinoSaalTO> kinoSaele)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record Name(String raw) implements RawWrapper<String> {}
public record Adresse(String raw) implements RawWrapper<String> {}
public record EmailAdresse(String raw) implements RawWrapper<String> {}
public KinoTO(final Id id, final Version version) {
this(id, version, new KinoTO.Name(""), new KinoTO.Adresse(""), new KinoTO.EmailAdresse(""),
new HashSet<>()); // cannot use Set.of() as this would be an ImmutableCollections.EMPTY_SET
}
public KinoTO(final Id id,
final Name name, final Adresse adresse,
final EmailAdresse emailAdresse, final Set<KinoSaalTO> kinoSaele) {
this(id, Versionable.unknownVersion(), name, adresse, emailAdresse, kinoSaele);
}
@SuppressWarnings({"ConstantValue", "unused"})
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KinoTO that = (KinoTO) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(name, that.name)
.append(adresse, that.adresse).append(emailAdresse, that.emailAdresse)
.isEquals();
if (!result) return false;
if (kinoSaele == null && that.kinoSaele == null) return true;
if (kinoSaele == null && that.kinoSaele != null) return false;
if (kinoSaele != null && that.kinoSaele == null) return false;
List<KinoSaalTO> thisKSList = kinoSaele.stream().toList();
List<KinoSaalTO> thatKSList = that.kinoSaele.stream().toList();
if (thisKSList.size() != thatKSList.size()) return false;
for (int counter = 0; counter < thisKSList.size(); counter++) {
if (!thisKSList.get(counter).equalsByContent(thatKSList.get(counter))) return false;
}
return true;
}
}

View file

@ -0,0 +1,80 @@
package de.accso.flexinale.backoffice.api_contract.event.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import java.io.Serializable;
import java.time.LocalDateTime;
public record VorfuehrungTO(Id id, Version version,
Zeit zeit,
@DoNotCheckInArchitectureTests FilmTO film,
@DoNotCheckInArchitectureTests KinoSaalTO kinoSaal,
Id kinoId)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public VorfuehrungTO(final Id id, final Version version,
final Zeit zeit,
final FilmTO film,
final KinoSaalTO kinoSaal,
final Id kinoId) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
// VorfuehrungTO also gets kinoId, which is actually redundant (as a KinoSaalTO already contains the Kino).
// But during serialization the Kino is set to null! Therefore, we add the extra kinoId here!
this.kinoId = kinoId;
//TODO this validation should not be done here. If class is not final, Spotbugs complains. For DB entities use "nullable=false" and "optional=false" for DB checks. In domain classes check with jakarta.annotation NonNull? Also: Why is KinoSaal obligatory but not Film?
if (kinoSaal == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal of Vorfuehrung " + this +
" must not be null");
}
//TODO this validation should not be done here but in KinoSaal itself
if (kinoSaal.anzahlPlaetze() == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal's Anzahl Plaetze of Vorfuehrung " + this +
" must not be null");
}
//TODO Why is this here (check not done in any other Vorfuehrung* class)?
if (kinoId == null) {
throw new FlexinaleIllegalArgumentException("Kino Id of Vorfuehrung " + this +
" must not be null");
}
}
@SuppressWarnings({"UnusedAssignment", "DataFlowIssue", "unused"})
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
VorfuehrungTO that = (VorfuehrungTO) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(film, that.film)
.append(zeit, that.zeit)
.append(kinoId, that.kinoId)
.isEquals();
if (!result) return false;
if (kinoSaal == null && that.kinoSaal == null) result = true;
if (kinoSaal == null && that.kinoSaal != null) return false;
if (kinoSaal != null) {
result = kinoSaal.equalsByContent(that.kinoSaal);
return result;
}
return true;
}
}

View file

@ -0,0 +1,20 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYYMMdd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.orm.jpa" level="INFO"/>
<logger name="org.springframework.boot.autoconfigure.domain.EntityScan" level="INFO"/>
<logger name="org.apache.kafka" level="WARN"/>
<logger name="org.apache.kafka.clients.admin.AdminClient" level="INFO"/>
<logger name="org.apache.kafka.clients.consumer.ConsumerConfig" level="INFO"/>
<logger name="org.apache.kafka.clients.producer.ProducerConfig" level="INFO"/>
<logger name="de.accso" level="INFO"/>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View file

@ -0,0 +1,176 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.core.JsonProcessingException;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoSaalTO;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.infrastructure.eventbus.EventSerializationHelper;
import static de.accso.flexinale.backoffice.api_contract.event.ReflectionHelper.setField;
import de.accso.flexinale.common.shared_kernel.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import static de.accso.flexinale.common.shared_kernel.Identifiable.Id.uuidString;
import static org.assertj.core.api.Assertions.assertThat;
class EventSerializationTest {
private static Stream<Arguments> allFilmEventTypes() {
return Set.of(FilmCreatedEvent.class, FilmUpdatedEvent.class, FilmDeletedEvent.class).stream().map(Arguments::of);
}
private static Stream<Arguments> allKinoEventTypes() {
return Set.of(KinoCreatedEvent.class, KinoUpdatedEvent.class, KinoDeletedEvent.class).stream().map(Arguments::of);
}
private static Stream<Arguments> allVorfuehrungEventTypes() {
return Set.of(VorfuehrungCreatedEvent.class, VorfuehrungUpdatedEvent.class, VorfuehrungDeletedEvent.class).stream().map(Arguments::of);
}
@ParameterizedTest
@MethodSource("allFilmEventTypes")
<E extends Event> void testSerializeFilmEventClass2JsonString(final Class<E> filmEventType)
throws JsonProcessingException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
// arrange
Constructor<E> constructor = filmEventType.getDeclaredConstructor();
constructor.setAccessible(true);
Event event = constructor.newInstance();
FilmTO filmTO = createFilmTO();
setField(filmEventType, event, "film", filmTO, true);
// act - serialize to JSON
String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);
// assert
assertThat(jsonString).contains(getExpectedJsonStringForVersion(event.version()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeString("titel", filmTO.titel()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeString("imdbUrl", filmTO.imdbUrl()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeInteger("dauerInMinuten", filmTO.dauerInMinuten()));
// act - deserialize back from JSON
E deserializedEvent = EventSerializationHelper.deserializeJsonString2Event(jsonString, filmEventType);
// assert
assertThat(deserializedEvent).isInstanceOf(filmEventType);
assertThat(deserializedEvent).isInstanceOf(FilmCRUDEvent.class);
FilmCRUDEvent castedDeserializedEvent = (FilmCRUDEvent) deserializedEvent;
assertThat(castedDeserializedEvent.version()).isEqualTo(event.version());
assertThat(castedDeserializedEvent.film).isEqualTo(filmTO);
}
@ParameterizedTest
@MethodSource("allKinoEventTypes")
<E extends Event> void testSerializeKinoEventClass2JsonString(final Class<E> kinoEventType)
throws JsonProcessingException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
// arrange
Constructor<E> constructor = kinoEventType.getDeclaredConstructor();
constructor.setAccessible(true);
Event event = constructor.newInstance();
KinoTO kinoTO = createKinoTO();
setField(kinoEventType, event, "kino", kinoTO, true);
// act
String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);
// assert
assertThat(jsonString).contains(getExpectedJsonStringForVersion(event.version()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeString("name", kinoTO.name()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeString("adresse", kinoTO.adresse()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeString("emailAdresse", kinoTO.emailAdresse()));
// act - deserialize back from JSON
E deserializedEvent = EventSerializationHelper.deserializeJsonString2Event(jsonString, kinoEventType);
// assert
assertThat(deserializedEvent).isInstanceOf(kinoEventType);
assertThat(deserializedEvent).isInstanceOf(KinoCRUDEvent.class);
KinoCRUDEvent castedDeserializedEvent = (KinoCRUDEvent) deserializedEvent;
assertThat(castedDeserializedEvent.version()).isEqualTo(event.version());
//TODO bidirectional relation K<->KS, assertion commented out, as does not work as the de/serialization is not working correctly for bidirectional relation between Kino and KinoSaal
// assertThat(castedDeserializedEvent.kino).isEqualTo(kinoTO);
}
@ParameterizedTest
@MethodSource("allVorfuehrungEventTypes")
<E extends Event> void testSerializeVorfuehrungEventClass2JsonString(final Class<E> vorfuehrungEventType)
throws JsonProcessingException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
// arrange
Constructor<E> constructor = vorfuehrungEventType.getDeclaredConstructor();
constructor.setAccessible(true);
Event event = constructor.newInstance();
VorfuehrungTO vorfuehrungTO = createVorfuehrungTO();
setField(vorfuehrungEventType, event, "vorfuehrung", vorfuehrungTO, true);
// act
String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);
// assert
assertThat(jsonString).contains(getExpectedJsonStringForVersion(event.version()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeLocalDateTime("zeit", vorfuehrungTO.zeit()));
// act - deserialize back from JSON
E deserializedEvent = EventSerializationHelper.deserializeJsonString2Event(jsonString, vorfuehrungEventType);
// assert
assertThat(deserializedEvent).isInstanceOf(vorfuehrungEventType);
assertThat(deserializedEvent).isInstanceOf(VorfuehrungCRUDEvent.class);
VorfuehrungCRUDEvent castedDeserializedEvent = (VorfuehrungCRUDEvent) deserializedEvent;
assertThat(castedDeserializedEvent.version()).isEqualTo(event.version());
assertThat(castedDeserializedEvent.vorfuehrung).isEqualTo(vorfuehrungTO);
}
private static FilmTO createFilmTO() {
FilmTO.Titel titel = new FilmTO.Titel(uuidString());
FilmTO.ImdbUrl imdbUrl = new FilmTO.ImdbUrl(uuidString());
FilmTO.DauerInMinuten dauerInMinuten = new FilmTO.DauerInMinuten(42);
return new FilmTO(Identifiable.Id.of(), Versionable.unknownVersion(), titel, imdbUrl, dauerInMinuten);
}
private static KinoTO createKinoTO() {
KinoTO.Name name = new KinoTO.Name(uuidString());
KinoTO.Adresse adresse = new KinoTO.Adresse(uuidString());
KinoTO.EmailAdresse emailAdresse = new KinoTO.EmailAdresse(uuidString());
KinoTO kinoTO = new KinoTO(Identifiable.Id.of(), Versionable.unknownVersion(), name, adresse, emailAdresse, new HashSet<>());
kinoTO.kinoSaele().add(createKinoSaalTO(kinoTO));
return kinoTO;
}
private static KinoSaalTO createKinoSaalTO(KinoTO kinoTO) {
KinoSaalTO.Name name = new KinoSaalTO.Name(uuidString());
KinoSaalTO.AnzahlPlaetze adresse = new KinoSaalTO.AnzahlPlaetze(42);
return new KinoSaalTO(Identifiable.Id.of(), Versionable.unknownVersion(), name, adresse, kinoTO);
}
private static VorfuehrungTO createVorfuehrungTO() {
VorfuehrungTO.Zeit zeit = new VorfuehrungTO.Zeit(LocalDateTime.now().withSecond(0)); // intentionally as ISO format changes then!
KinoSaalTO kinoSaalTO = createKinoSaalTO(null);
return new VorfuehrungTO(Identifiable.Id.of(), Versionable.unknownVersion(), zeit,
createFilmTO(), kinoSaalTO, Identifiable.Id.of());
}
private static String getExpectedJsonStringForVersion(Versionable.Version field) {
return String.format("\"%s\":{\"version\":%d", "version", field.version());
}
private static String getExpectedJsonStringForRawTypeString(String fieldName, RawWrapper<String> field) {
return String.format("\"%s\":{\"raw\":\"%s\"", fieldName, field.raw());
}
private static String getExpectedJsonStringForRawTypeInteger(String fieldName, RawWrapper<Integer> field) {
return String.format("\"%s\":{\"raw\":%d", fieldName, field.raw());
}
private static String getExpectedJsonStringForRawTypeLocalDateTime(String fieldName, RawWrapper<LocalDateTime> field) {
return String.format("\"%s\":{\"raw\":\"%s\"", fieldName, field.raw().format(DateTimeFormatter.ISO_DATE_TIME));
}
}

View file

@ -0,0 +1,54 @@
package de.accso.flexinale.backoffice.api_contract.event;
import com.fasterxml.jackson.core.JsonProcessingException;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.infrastructure.eventbus.EventSerializationHelper;
import de.accso.flexinale.common.shared_kernel.EventClassHelper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.util.stream.Stream;
import static de.accso.flexinale.backoffice.api_contract.event.ReflectionHelper.setField;
import static org.assertj.core.api.Assertions.*;
class EventStructureTest {
private static Stream<Arguments> allEventTypes() {
return AllBackofficeEvents.eventTypes.stream().map(Arguments::of);
}
// check for all Event subclasses if no exception is thrown for (de)serialization and reflection access
// a) default constructor is needed
// b) field 'version' is needed
@ParameterizedTest
@MethodSource("allEventTypes")
<E extends Event> void testCheckEventStructure(final Class<E> eventType)
throws InstantiationException, IllegalAccessException, JsonProcessingException, NoSuchMethodException, InvocationTargetException {
// arrange
Constructor<E> constructor = eventType.getDeclaredConstructor();
constructor.setAccessible(true);
Event event = constructor.newInstance();
// set id, timestamp and version explicitely, so that (de)serialization is somewhat more realistic
setField(eventType, event, "id", Identifiable.Id.of(), false);
setField(eventType, event, "timestamp", LocalDateTime.now(), false);
// act - check serialization and deserialization
String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);
EventSerializationHelper.deserializeJsonString2Event(jsonString, eventType);
// act - check instantiation and version attribute
assertThatCode(() -> {
Versionable.Version eventClazzVersion = EventClassHelper.getEventClazzVersion(eventType);
assertThat(eventClazzVersion).isEqualTo(event.version());
})
.doesNotThrowAnyException();
}
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.backoffice.api_contract.event;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalArgumentException;
import java.lang.reflect.Field;
public final class ReflectionHelper {
public static void setField(final Class<?> eventType, final Event event, final String fieldName, final Object fieldValue,
boolean includeDerivedFieldsFromSuperClasses) throws IllegalAccessException {
if (eventType.equals(Object.class)) {
throw new FlexinaleIllegalArgumentException("event hierarchy does not have a field " + fieldName);
}
try {
Field field = (includeDerivedFieldsFromSuperClasses)
? eventType.getField(fieldName)
: eventType.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(event, fieldValue);
}
catch (NoSuchFieldException nosfex) {
setField(eventType.getSuperclass(), event, fieldName, fieldValue, includeDerivedFieldsFromSuperClasses);
}
}
}