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,70 @@
<?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-besucherportal_api_contract</artifactId>
<version>2024.3.0</version>
<name>Flexinale Distributed Besucherportal API Contract</name>
<description>Flexinale - FLEX case-study "film festival", distributed services, besucherportal_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>
</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,14 @@
package de.accso.flexinale.besucherportal.api_contract.event;
import de.accso.flexinale.common.api.event.Event;
import java.util.Set;
public final class AllBesucherportalEvents {
public static final Set<Class<? extends Event>> eventTypes =
Set.of(
GutscheinEinloesenBeauftragtEvent.class
);
}

View file

@ -0,0 +1,53 @@
package de.accso.flexinale.besucherportal.api_contract.event;
import com.fasterxml.jackson.annotation.JsonGetter;
import de.accso.flexinale.besucherportal.api_contract.event.model.GutscheinEinloesenAuftragTO;
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 de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
@SuppressWarnings({"unused", "CanBeFinal"})
public class GutscheinEinloesenBeauftragtEvent extends AbstractEvent implements Event {
private static Version version = Versionable.initialVersion().inc();
@DoNotCheckInArchitectureTests
public GutscheinEinloesenAuftragTO gutscheinEinloesenAuftrag;
private GutscheinEinloesenBeauftragtEvent() {} // needed for (de)serialization via Jackson
public GutscheinEinloesenBeauftragtEvent(final GutscheinEinloesenAuftragTO gutscheinEinloesenAuftrag) {
this.gutscheinEinloesenAuftrag = gutscheinEinloesenAuftrag;
}
@Override
@JsonGetter("version") // needed as otherwise the static field version is not (de)serialized
public Version version() {
return version;
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final GutscheinEinloesenBeauftragtEvent that = (GutscheinEinloesenBeauftragtEvent) o;
return new EqualsBuilder().appendSuper(super.equals(o))
.append(this.version(), that.version())
.append(gutscheinEinloesenAuftrag, that.gutscheinEinloesenAuftrag)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.appendSuper(super.hashCode())
.append(this.version())
.append(gutscheinEinloesenAuftrag)
.toHashCode();
}
}

View file

@ -0,0 +1,38 @@
package de.accso.flexinale.besucherportal.api_contract.event.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import java.io.Serializable;
@SuppressWarnings("unused")
public record GutscheinEinloesenAuftragTO(Id id, Version version,
Id filmId, Id vorfuehrungId, Id besucherId,
AnzahlTickets anzahlTickets)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record AnzahlTickets(Integer raw) implements RawWrapper<Integer> {}
public enum VerkaufsKanal {
ONLINE,
ZENTRAL,
KINOKASSE
}
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
GutscheinEinloesenAuftragTO that = (GutscheinEinloesenAuftragTO) o;
return new EqualsBuilder()
.append(id, that.id)
.append(filmId, that.filmId)
.append(vorfuehrungId, that.vorfuehrungId)
.append(besucherId, that.besucherId)
.append(anzahlTickets, that.anzahlTickets)
.isEquals();
}
}

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,67 @@
package de.accso.flexinale.besucherportal.api_contract.event;
import com.fasterxml.jackson.core.JsonProcessingException;
import de.accso.flexinale.besucherportal.api_contract.event.model.GutscheinEinloesenAuftragTO;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.infrastructure.eventbus.EventSerializationHelper;
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.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.Set;
import java.util.stream.Stream;
import static de.accso.flexinale.besucherportal.api_contract.event.ReflectionHelper.setField;
import static org.assertj.core.api.Assertions.assertThat;
class EventSerializationTest {
private static Stream<Arguments> allGutscheinEventTypes() {
return Set.of(GutscheinEinloesenBeauftragtEvent.class).stream().map(Arguments::of);
}
@ParameterizedTest
@MethodSource("allGutscheinEventTypes")
<E extends Event> void testSerializeGutscheinEventClass2JsonString(final Class<E> gutscheinEventType)
throws JsonProcessingException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
// arrange
Constructor<E> constructor = gutscheinEventType.getDeclaredConstructor();
constructor.setAccessible(true);
Event event = constructor.newInstance();
GutscheinEinloesenAuftragTO gutscheinEinloesenAuftragTO = createGutscheinTO();
setField(gutscheinEventType, event, "gutscheinEinloesenAuftrag", gutscheinEinloesenAuftragTO, true);
// act
String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);
// assert
assertThat(jsonString).contains(getExpectedJsonStringForVersion(event.version()));
assertThat(jsonString).contains(getExpectedJsonStringForRawTypeInteger("anzahlTickets", gutscheinEinloesenAuftragTO.anzahlTickets()));
}
private static GutscheinEinloesenAuftragTO createGutscheinTO() {
GutscheinEinloesenAuftragTO.AnzahlTickets anzahlTickets = new GutscheinEinloesenAuftragTO.AnzahlTickets(42);
return new GutscheinEinloesenAuftragTO(Identifiable.Id.of(), Versionable.unknownVersion(),
Identifiable.Id.of(), Identifiable.Id.of(), Identifiable.Id.of(),
anzahlTickets);
}
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().toString());
}
}

View file

@ -0,0 +1,52 @@
package de.accso.flexinale.besucherportal.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 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.besucherportal.api_contract.event.ReflectionHelper.setField;
import static org.assertj.core.api.Assertions.assertThatCode;
class EventStructureTest {
private static Stream<Arguments> allEventTypes() {
return AllBesucherportalEvents.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(() -> {
EventClassHelper.getEventClazzVersion(eventType);
})
.doesNotThrowAnyException();
}
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.besucherportal.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);
}
}
}