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,8 @@
FROM amazoncorretto:21.0.1-alpine3.18
WORKDIR /app
COPY ../../target/flexinale-distributed-ticketing-2024.3.0-spring-boot-fat-jar.jar /app
EXPOSE 8082
CMD ["java", "-jar", "flexinale-distributed-ticketing-2024.3.0-spring-boot-fat-jar.jar"]

View file

@ -0,0 +1,2 @@
docker build -t de.accso/flexinale-distributed-ticketing:2024.3.0 -f Dockerfile ../../

View file

@ -0,0 +1,8 @@
FROM amazoncorretto:21.0.1-alpine3.18
WORKDIR /app
COPY ../../target/flexinale-distributed-ticketing-2024.3.0-spring-boot-fat-jar.jar /app
EXPOSE 8082
CMD ["java", "-jar", "flexinale-distributed-ticketing-2024.3.0-spring-boot-fat-jar.jar"]

View file

@ -0,0 +1 @@
podman build -t de.accso/flexinale-distributed-ticketing:2024.3.0 -f Dockerfile ../../

View file

@ -0,0 +1,139 @@
<?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-ticketing</artifactId>
<version>2024.3.0</version>
<name>Flexinale Distributed Ticketing</name>
<description>Flexinale - FLEX case-study &quot;film festival&quot;, distributed services, ticketing</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${org-postgres.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-ticketing_api_contract</artifactId>
<version>2024.3.0</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-backoffice_api_contract</artifactId>
<version>2024.3.0</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-besucherportal_api_contract</artifactId>
<version>2024.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- name extension of spring boot fat jar-->
<configuration>
<classifier>spring-boot-fat-jar</classifier>
</configuration>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<flexinale.implementation>Distributed</flexinale.implementation>
<flexinale.app>Ticketing</flexinale.app>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
<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,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - start Ticketing app (&quot;FlexinaleDistributedApplicationTicketing&quot;)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" folderName="App Distributed">
<option name="ENABLE_LAUNCH_OPTIMIZATION" value="false" />
<module name="flexinale-distributed-ticketing" />
<option name="SPRING_BOOT_MAIN_CLASS" value="de.accso.flexinale.FlexinaleDistributedApplicationTicketing" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed Ticketing - Actuator Events Consumed" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-ticketing/src/test/curl/ticketing-actuator-events-consumed.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed Ticketing - Actuator Events Published" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-ticketing/src/test/curl/ticketing-actuator-events-published.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed Ticketing - Actuator Health" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-ticketing/src/test/curl/ticketing-actuator-health.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed Ticketing - Actuator Info" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-ticketing/src/test/curl/ticketing-actuator-info.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed Ticketing - Actuator Metrics" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-ticketing/src/test/curl/ticketing-actuator-metrics.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed Ticketing - Actuator Prometheus" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-ticketing/src/test/curl/ticketing-actuator-prometheus.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,23 @@
package de.accso.flexinale;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@SpringBootApplication
@Profile({"!test-integrated &!test-distributed & !testdata"})
public class FlexinaleDistributedApplicationTicketing {
public static void main(String[] args) {
SpringApplication.run(FlexinaleDistributedApplicationTicketing.class, args);
}
@Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}

View file

@ -0,0 +1,81 @@
package de.accso.flexinale.ticketing.api_in.event;
import de.accso.flexinale.besucherportal.api_contract.event.GutscheinEinloesenBeauftragtEvent;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
import de.accso.flexinale.common.application.caching.InMemoryCache;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.application.services.TicketService;
import de.accso.flexinale.ticketing.domain.model.KontingentBereitsAusgeschoepftException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_LAST_TIME;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
@SuppressWarnings("unused")
public class GutscheinEinloesenBeauftragtSubscriber implements EventSubscriber<GutscheinEinloesenBeauftragtEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(GutscheinEinloesenBeauftragtSubscriber.class);
private final TicketService ticketService;
// caches are currently never cleared - might want to use Cache implementation with automatic time-to-live
private final InMemoryCache<Identifiable.Id, Event> gutscheinEinloesenEventCache;
private final EventNotification notification;
public GutscheinEinloesenBeauftragtSubscriber(final EventBusFactory eventBusFactory,
final TicketService ticketService,
final EventNotification notification) {
EventBus<GutscheinEinloesenBeauftragtEvent> eventBus =
eventBusFactory.createOrGetEventBusFor(GutscheinEinloesenBeauftragtEvent.class);
eventBus.subscribe(GutscheinEinloesenBeauftragtEvent.class, this, START_READING_FROM_LAST_TIME);
this.ticketService = ticketService;
this.notification = notification;
this.gutscheinEinloesenEventCache = new InMemoryCache<>();
}
@Override
public String getName() {
return GutscheinEinloesenBeauftragtSubscriber.class.getName();
}
@Override
public String getGroupName() {
return "flexinale-distributed-ticketing"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
}
@Override
public void receive(final GutscheinEinloesenBeauftragtEvent event) {
LOGGER.debug("received new event " + event);
this.notification.notify(event);
// idem potence - do not buy same tickets twice
if (gutscheinEinloesenEventCache.containsKey(event.correlationId())) {
LOGGER.info(("received GutscheinEinloesenBeauftragtEvent twice with same correlationId=%s , " +
"so already handled. Ignore and do nothing here.").formatted(event.correlationId()));
return;
}
gutscheinEinloesenEventCache.put(event.correlationId(), event);
try {
ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(
event.gutscheinEinloesenAuftrag.vorfuehrungId(),
event.gutscheinEinloesenAuftrag.filmId(),
event.gutscheinEinloesenAuftrag.besucherId(),
getRawOrNull(event.gutscheinEinloesenAuftrag.anzahlTickets()));
}
catch (KontingentBereitsAusgeschoepftException kbaEx) {
LOGGER.info("Could not get %s tickets for Vorfuehrung %s, Film %s for Besucher %s: Kontingent bereits ausgeschöpft"
.formatted(getRawOrNull(event.gutscheinEinloesenAuftrag.anzahlTickets()),
event.gutscheinEinloesenAuftrag.vorfuehrungId(), event.gutscheinEinloesenAuftrag.filmId(),
event.gutscheinEinloesenAuftrag.besucherId()));
}
}
}

View file

@ -0,0 +1,81 @@
package de.accso.flexinale.ticketing.api_in.event;
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungCreatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungUpdatedEvent;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.ticketing.application.services.TicketService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_BEGINNING;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
@SuppressWarnings("unused")
public class VorfuehrungSubscriber implements EventSubscriber.EventSubscriber2<VorfuehrungCreatedEvent, VorfuehrungUpdatedEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(VorfuehrungSubscriber.class);
final TicketService ticketService;
private final EventNotification notification;
@SuppressWarnings({"RedundantCast", "unchecked"})
public VorfuehrungSubscriber(final EventBusFactory eventBusFactory,
final TicketService ticketService,
final EventNotification notification) {
EventBus<VorfuehrungCreatedEvent> eventBusCreated = eventBusFactory.createOrGetEventBusFor(VorfuehrungCreatedEvent.class);
EventBus<VorfuehrungUpdatedEvent> eventBusUpdated = eventBusFactory.createOrGetEventBusFor(VorfuehrungUpdatedEvent.class);
eventBusCreated.subscribe(VorfuehrungCreatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
eventBusUpdated.subscribe(VorfuehrungUpdatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
this.ticketService = ticketService;
this.notification = notification;
}
@Override
public String getName() {
return VorfuehrungSubscriber.class.getName();
}
@Override
public String getGroupName() {
return "flexinale-distributed-ticketing"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
}
@Override
public void receive(final VorfuehrungCreatedEvent event) {
LOGGER.debug("received new event " + event);
this.notification.notify(event);
try {
// check if Vorfuehrung is already there (then do nothing, event was sent more than once)
if ( ! ticketService.vorfuehrungIstBereitsImVerkauf(event.vorfuehrung.id())) {
ticketService.gebeVorfuehrungInDenVerkauf(event.vorfuehrung.id(), getRawOrNull(event.vorfuehrung.kinoSaal().anzahlPlaetze()));
}
}
catch (FlexinaleIllegalStateException ex) {
LOGGER.warn("received VorfuehrungCreatedEvent ignored as Kontingent already exist for Vorfuehrung %s"
.formatted(event.vorfuehrung.id()), ex);
}
}
@Override
public void receive2(final VorfuehrungUpdatedEvent event) {
LOGGER.debug("received new event " + event);
this.notification.notify(event);
if ( ! ticketService.vorfuehrungIstBereitsImVerkauf(event.vorfuehrung.id())){
// Events might not be delivered in same order as sent: So the Updated event might have overtaken a Created event.
LOGGER.warn("received VorfuehrungUpdatedEvent although Vorfuehrung does not yet exist: %s Handling this as VorfuehrungCreated"
.formatted(event.vorfuehrung.id()));
ticketService.gebeVorfuehrungInDenVerkauf(event.vorfuehrung.id(), getRawOrNull(event.vorfuehrung.kinoSaal().anzahlPlaetze()));
}
ticketService.aktualisiereVorfuehrung(event.vorfuehrung.id(), getRawOrNull(event.vorfuehrung.kinoSaal().anzahlPlaetze()));
}
}

View file

@ -0,0 +1,55 @@
package de.accso.flexinale.ticketing.api_out.event;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventPublisher;
import de.accso.flexinale.common.api.event.EventContext;
import de.accso.flexinale.ticketing.api_contract.event.OnlineKontingentChangedEvent;
import de.accso.flexinale.ticketing.api_contract.event.model.OnlineKontingentTO;
import de.accso.flexinale.ticketing.application.services.OnlineKontingentChangedPublication;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import java.util.List;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public class OnlineKontingentChangedPublisher implements EventPublisher<OnlineKontingentChangedEvent>, OnlineKontingentChangedPublication {
private final EventBus<OnlineKontingentChangedEvent> onlineKontingentChangedEventEventBus;
private final EventNotification notification;
public OnlineKontingentChangedPublisher(final EventBusFactory eventBusFactory,
final EventNotification notification) {
this.onlineKontingentChangedEventEventBus = eventBusFactory.createOrGetEventBusFor(OnlineKontingentChangedEvent.class);
this.notification = notification;
}
@Override
public void publishOnlineKontingentChanged(final List<Kontingent> kontingente) {
EventContext predecessorEventContext = onlineKontingentChangedEventEventBus.getEventContextHolder().get();
kontingente.forEach(kontingent -> {
OnlineKontingentTO onlineKontingentTO =
new OnlineKontingentTO(kontingent.id, kontingent.version,
kontingent.getVorfuehrungId(),
new OnlineKontingentTO.RestKontingent(getRawOrNull(kontingent.getRestKontingentOnline())));
post(OnlineKontingentChangedEvent.class,
(predecessorEventContext == null)
? new OnlineKontingentChangedEvent(onlineKontingentTO)
: new OnlineKontingentChangedEvent(predecessorEventContext, onlineKontingentTO)
);
});
}
@Override
public String getName() {
return OnlineKontingentChangedPublisher.class.getName();
}
@Override
public void post(final Class<OnlineKontingentChangedEvent> eventType, final OnlineKontingentChangedEvent event) {
this.onlineKontingentChangedEventEventBus.publish(OnlineKontingentChangedEvent.class, event);
this.notification.notify(event);
}
}

View file

@ -0,0 +1,71 @@
package de.accso.flexinale.ticketing.api_out.event;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventPublisher;
import de.accso.flexinale.common.api.event.EventContext;
import de.accso.flexinale.ticketing.api_contract.event.TicketGekauftEvent;
import de.accso.flexinale.ticketing.api_contract.event.TicketUngueltigEvent;
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
import de.accso.flexinale.ticketing.api_out.event.mapper.Ticket2TicketTOMapper;
import de.accso.flexinale.ticketing.application.services.TicketPublication;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import java.util.List;
public class TicketPublisher implements EventPublisher.EventPublisher2<TicketGekauftEvent, TicketUngueltigEvent>, TicketPublication {
private final EventBus<TicketGekauftEvent> ticketGekauftEventEventBus;
private final EventBus<TicketUngueltigEvent> ticketUngueltigEventEventBus;
private final EventNotification notification;
public TicketPublisher(final EventBusFactory eventBusFactory,
final EventNotification notification) {
this.ticketGekauftEventEventBus = eventBusFactory.createOrGetEventBusFor(TicketGekauftEvent.class);
this.ticketUngueltigEventEventBus = eventBusFactory.createOrGetEventBusFor(TicketUngueltigEvent.class);
this.notification = notification;
}
@Override
public void publishGekaufteTickets(final List<Ticket> tickets) {
EventContext predecessorEventContext = ticketGekauftEventEventBus.getEventContextHolder().get();
tickets.forEach(ticket -> {
TicketTO mappedTicket = Ticket2TicketTOMapper.map(ticket);
post(TicketGekauftEvent.class,
(predecessorEventContext == null) ? new TicketGekauftEvent(mappedTicket)
: new TicketGekauftEvent(predecessorEventContext, mappedTicket));
});
}
@Override
public void publishUngueltigeTickets(final List<Ticket> tickets) {
EventContext predecessorEventContext = ticketUngueltigEventEventBus.getEventContextHolder().get();
tickets.forEach(ticket -> {
TicketTO mappedTicket = Ticket2TicketTOMapper.map(ticket);
post2(TicketUngueltigEvent.class,
(predecessorEventContext == null) ? new TicketUngueltigEvent(mappedTicket)
: new TicketUngueltigEvent(predecessorEventContext, mappedTicket));
});
}
@Override
public String getName() {
return TicketPublisher.class.getName();
}
@Override
public void post(final Class<TicketGekauftEvent> eventType, final TicketGekauftEvent event) {
this.ticketGekauftEventEventBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post2(final Class<TicketUngueltigEvent> eventType, final TicketUngueltigEvent event) {
this.ticketUngueltigEventEventBus.publish(eventType, event);
this.notification.notify(event);
}
}

View file

@ -0,0 +1,20 @@
package de.accso.flexinale.ticketing.api_out.event.mapper;
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public class Ticket2TicketTOMapper {
public static TicketTO map(final Ticket ticket) {
TicketTO.VerkaufsKanal verkaufsKanal = switch (ticket.verkaufsKanal) {
case ONLINE -> TicketTO.VerkaufsKanal.ONLINE;
case ZENTRAL -> TicketTO.VerkaufsKanal.ZENTRAL;
case KINOKASSE -> TicketTO.VerkaufsKanal.KINOKASSE;
};
return new TicketTO(ticket.id(), ticket.version(), ticket.filmId, ticket.vorfuehrungId,
ticket.besucherId, verkaufsKanal,
new TicketTO.Gueltig(getRawOrNull(ticket.gueltig)));
}
}

View file

@ -0,0 +1,9 @@
package de.accso.flexinale.ticketing.application.services;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import java.util.List;
public interface OnlineKontingentChangedPublication {
void publishOnlineKontingentChanged(List<Kontingent> kontingente);
}

View file

@ -0,0 +1,10 @@
package de.accso.flexinale.ticketing.application.services;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import java.util.List;
public interface TicketPublication {
void publishGekaufteTickets(List<Ticket> tickets);
void publishUngueltigeTickets(List<Ticket> tickets);
}

View file

@ -0,0 +1,133 @@
package de.accso.flexinale.ticketing.application.services;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.dao.KontingentDao;
import de.accso.flexinale.ticketing.domain.dao.TicketDao;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Transactional
public class TicketService {
private static final Logger LOGGER = LoggerFactory.getLogger(TicketService.class);
private final TicketDao ticketDao;
private final KontingentDao kontingentDao;
private final OnlineKontingentChangedPublication onlineKontingentChangedPublisher;
private final TicketPublication ticketPublisher;
private final int quoteOnline;
public TicketService(final TicketDao ticketDao,
final KontingentDao kontingentDao,
final OnlineKontingentChangedPublication onlineKontingentChangedPublisher,
final TicketPublication ticketPublisher,
final int quoteOnline) {
this.ticketDao = ticketDao;
this.kontingentDao = kontingentDao;
this.onlineKontingentChangedPublisher = onlineKontingentChangedPublisher;
this.ticketPublisher = ticketPublisher;
this.quoteOnline = quoteOnline;
}
public List<Ticket> findByBesucher(final Identifiable.Id besucherId) {
return ticketDao.findByBesucher(besucherId);
}
public List<Ticket> tickets() {
return ticketDao.findAll();
}
public void loeseGutscheineOnlineFuerVorfuehrungEin(final Identifiable.Id vorfuehrungId, final Identifiable.Id filmId,
final Identifiable.Id besucherId, final int anzahlGutscheine) {
loeseGutscheineFuerVorfuehrungEin(vorfuehrungId, filmId, besucherId, anzahlGutscheine,
Ticket.VerkaufsKanal.ONLINE);
}
@SuppressWarnings("SameParameterValue")
private void loeseGutscheineFuerVorfuehrungEin(final Identifiable.Id vorfuehrungId, final Identifiable.Id filmId,
final Identifiable.Id besucherId, final int anzahlGutscheine,
final Ticket.VerkaufsKanal verkaufsKanal) {
Optional<Kontingent> optionalKontingent = kontingentDao.findByVorfuehrung(vorfuehrungId);
if (optionalKontingent.isEmpty()) {
String message = "Kontingent for Vorfuehrung %s could not be found".formatted(vorfuehrungId);
LOGGER.error(message);
throw new FlexinaleIllegalArgumentException(message);
}
Kontingent kontingent = optionalKontingent.get();
Kontingent reduziertesKontingent = kontingent.reduziereKontingent(verkaufsKanal, anzahlGutscheine);
Kontingent umAnzahlGekaufteTicketsReduziertesKontingent = kontingentDao.save(reduziertesKontingent);
List<Ticket> ticketsToBePublished = new ArrayList<>();
for (int ticketCounter=0; ticketCounter < anzahlGutscheine; ticketCounter++) {
Ticket ticket = ticketDao.save(
new Ticket(Identifiable.Id.of(), filmId, vorfuehrungId, besucherId, verkaufsKanal));
ticketsToBePublished.add(ticket);
}
ticketPublisher.publishGekaufteTickets(ticketsToBePublished);
LOGGER.info("%d tickets bought for Vorfuehrung with id=%s and Besucher with id=%s on channel=%s"
.formatted(anzahlGutscheine, vorfuehrungId, besucherId, verkaufsKanal));
onlineKontingentChangedPublisher.publishOnlineKontingentChanged(List.of(umAnzahlGekaufteTicketsReduziertesKontingent));
}
public void gebeVorfuehrungInDenVerkauf(final Identifiable.Id vorfuehrungId, final int kapazitaet) {
if (vorfuehrungIstBereitsImVerkauf(vorfuehrungId)) {
String message = "Kontingent for Vorfuehrung %s already exists. No Kontingent created".formatted(vorfuehrungId);
LOGGER.error(message);
throw new FlexinaleIllegalStateException(message);
}
Kontingent kontingent = new Kontingent(Identifiable.Id.of(), kapazitaet, quoteOnline, vorfuehrungId);
kontingentDao.save(kontingent);
onlineKontingentChangedPublisher.publishOnlineKontingentChanged(List.of(kontingent));
}
public boolean vorfuehrungIstBereitsImVerkauf(final Identifiable.Id vorfuehrungId) {
return kontingentDao.findByVorfuehrung(vorfuehrungId).isPresent();
}
public void aktualisiereVorfuehrung(final Identifiable.Id vorfuehrungId, final int kapazitaet){
// 1) Invalidate all existing tickets
List<Ticket> ticketsByVorfuehrung = ticketDao.findByVorfuehrung(vorfuehrungId);
List<Ticket> ungueltigeTickets = new ArrayList<>();
for (Ticket ticket : ticketsByVorfuehrung) {
ticket.gueltig = new Ticket.Gueltig(false);
Ticket ungueltigesTicket = ticketDao.save(ticket);
ungueltigeTickets.add(ungueltigesTicket);
}
// 2) Recalculate Kontingent
Optional<Kontingent> kontingentOptional = kontingentDao.findByVorfuehrung(vorfuehrungId);
if (kontingentOptional.isEmpty()) {
LOGGER.warn("could not recalculate Kontingente for Vorfuehrung %s. Kontingent does not exist. Creating new Kontingent"
.formatted(vorfuehrungId));
}
else {
Kontingent kontingent = kontingentOptional.get();
Kontingent neuBerechnetesKontingent = kontingentDao.save(
new Kontingent(kontingent.id, kontingent.version, kapazitaet, quoteOnline, kontingent.getVorfuehrungId())
);
// publish tickets and new Kontingent - at the end, after all database changes have succeeded.
onlineKontingentChangedPublisher.publishOnlineKontingentChanged(List.of(neuBerechnetesKontingent));
ticketPublisher.publishUngueltigeTickets(ungueltigeTickets);
}
}
}

View file

@ -0,0 +1,15 @@
package de.accso.flexinale.ticketing.domain.dao;
import de.accso.flexinale.common.domain.model.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import java.util.Optional;
public interface KontingentDao extends AbstractDao<Kontingent> {
@Override
Optional<Kontingent> findById(final Identifiable.Id id);
Optional<Kontingent> findByVorfuehrung(final Identifiable.Id vorfuehrungId);
}

View file

@ -0,0 +1,20 @@
package de.accso.flexinale.ticketing.domain.dao;
import de.accso.flexinale.common.domain.model.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import java.util.List;
import java.util.Optional;
public interface TicketDao extends AbstractDao<Ticket> {
@Override
Optional<Ticket> findById(final Identifiable.Id id);
List<Ticket> findByBesucher(final Identifiable.Id besucherId);
List<Ticket> findByVorfuehrung(final Identifiable.Id vorfuehrungId);
int gesamtZahlDerTicketsFuer(final Identifiable.Id besucherId);
}

View file

@ -0,0 +1,186 @@
package de.accso.flexinale.ticketing.domain.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public class Kontingent implements Identifiable, Versionable, EqualsByContent {
public record RestKontingent(Integer raw) implements RawWrapper<Integer> {}
public enum KontingentType {
ONLINE,
ZENTRAL,
KINOKASSE
}
public final Id id;
public final Version version;
private final Id vorfuehrungId;
private final RestKontingent restKontingentOnline;
private final RestKontingent restKontingentZentral;
private final RestKontingent restKontingentKinokasse;
public Kontingent(final Id id,
final RestKontingent restKontingentOnline,
final RestKontingent restKontingentZentral,
final RestKontingent restKontingentKinokasse,
final Id vorfuehrungId) {
this(id, Versionable.unknownVersion(), restKontingentOnline, restKontingentZentral, restKontingentKinokasse, vorfuehrungId);
}
public Kontingent(final Id id, final Version version,
final RestKontingent restKontingentOnline,
final RestKontingent restKontingentZentral,
final RestKontingent restKontingentKinokasse,
final Id vorfuehrungId) {
this.id = id;
this.version = version;
this.restKontingentOnline = restKontingentOnline;
this.restKontingentZentral = restKontingentZentral;
this.restKontingentKinokasse = restKontingentKinokasse;
this.vorfuehrungId = vorfuehrungId;
}
public Kontingent(final Id id,
final int gesamtKapazitaetVerkauf,
final int quoteOnline,
final Id vorfuehrungId) {
this(id, Versionable.unknownVersion(), gesamtKapazitaetVerkauf, quoteOnline, vorfuehrungId);
}
public Kontingent(final Id id, final Version version,
final int gesamtKapazitaetVerkauf,
final int quoteOnline,
final Id vorfuehrungId) {
this.id = id;
this.version = version;
this.vorfuehrungId = vorfuehrungId;
this.restKontingentOnline =
new RestKontingent((int) Math.ceil ((float)(gesamtKapazitaetVerkauf * quoteOnline)/100));
this.restKontingentZentral =
new RestKontingent((int) Math.floor((float)(gesamtKapazitaetVerkauf - restKontingentOnline.raw)/2));
this.restKontingentKinokasse =
new RestKontingent(gesamtKapazitaetVerkauf - restKontingentZentral.raw - restKontingentOnline.raw);
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
public RestKontingent getRestKontingentOnline() {
return restKontingentOnline;
}
public RestKontingent getRestKontingentZentral() {
return restKontingentZentral;
}
public RestKontingent getRestKontingentKinokasse() {
return restKontingentKinokasse;
}
public Id getVorfuehrungId() {
return vorfuehrungId;
}
public Kontingent reduziereKontingent(final Ticket.VerkaufsKanal verkaufsKanal, final int anzahlGutscheine) {
return switch (verkaufsKanal) {
case ONLINE ->
reduziereKontingent(Kontingent.KontingentType.ONLINE, anzahlGutscheine);
case ZENTRAL ->
reduziereKontingent(Kontingent.KontingentType.ZENTRAL, anzahlGutscheine);
case KINOKASSE ->
reduziereKontingent(Kontingent.KontingentType.KINOKASSE, anzahlGutscheine);
};
}
private Kontingent reduziereKontingent(final KontingentType kontingentType, final int kontingentReduktion) {
if (kontingentReduktion < 0) {
throw new FlexinaleIllegalArgumentException("reduction of Kontingent has to be >=0 but is "
+ kontingentReduktion);
}
return switch (kontingentType) {
case ONLINE ->
new Kontingent(this.id, this.version,
reduziereDiesesKontingent(getRawOrNull(restKontingentOnline), kontingentReduktion),
this.restKontingentZentral,
this.restKontingentKinokasse,
this.vorfuehrungId
);
case ZENTRAL ->
new Kontingent(this.id, this.version,
this.restKontingentOnline,
reduziereDiesesKontingent(getRawOrNull(restKontingentZentral), kontingentReduktion),
this.restKontingentKinokasse,
this.vorfuehrungId);
case KINOKASSE ->
new Kontingent(this.id, this.version,
this.restKontingentOnline,
this.restKontingentZentral,
reduziereDiesesKontingent(getRawOrNull(restKontingentKinokasse), kontingentReduktion),
this.vorfuehrungId);
};
}
private RestKontingent reduziereDiesesKontingent(final int kontingent, final int reduziereUm) {
int reduziertesKontingent = kontingent - reduziereUm;
if (reduziertesKontingent < 0) {
throw new KontingentBereitsAusgeschoepftException();
}
return new RestKontingent(reduziertesKontingent);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@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;}
Kontingent that = (Kontingent) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Kontingent that = (Kontingent) o;
return new EqualsBuilder()
.append(id, that.id)
.append(restKontingentOnline, that.restKontingentOnline)
.append(restKontingentZentral, that.restKontingentZentral)
.append(restKontingentKinokasse, that.restKontingentKinokasse)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(restKontingentOnline)
.append(restKontingentZentral).append(restKontingentKinokasse)
.toHashCode();
}
}

View file

@ -0,0 +1,32 @@
package de.accso.flexinale.ticketing.domain.model;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
@SuppressWarnings("unused")
@DoNotCheckInArchitectureTests
public class KontingentBereitsAusgeschoepftException extends RuntimeException {
//TODO is this exception needed? replace by boolean value in the "Ticketing" interface's method, which is currently void
// if we keep the exception, then perhaps move as inner class to "Kontingent"?
public KontingentBereitsAusgeschoepftException() {
super();
}
public KontingentBereitsAusgeschoepftException(final String message) {
super(message);
}
public KontingentBereitsAusgeschoepftException(final String message, final Throwable cause) {
super(message, cause);
}
public KontingentBereitsAusgeschoepftException(final Throwable cause) {
super(cause);
}
protected KontingentBereitsAusgeschoepftException(final String message, final Throwable cause,
final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,104 @@
package de.accso.flexinale.ticketing.domain.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Ticket implements Identifiable, Versionable, EqualsByContent {
public record Gueltig(Boolean raw) implements RawWrapper<Boolean> {}
public enum VerkaufsKanal {
ONLINE,
ZENTRAL,
KINOKASSE
}
public final Id id;
public final Version version;
public final Id filmId;
public final Id vorfuehrungId;
public final Id besucherId;
@DoNotCheckInArchitectureTests
public VerkaufsKanal verkaufsKanal;
public Gueltig gueltig;
public Ticket(final Id id,
final Id filmId, final Id vorfuehrungId, final Id besucherId,
final VerkaufsKanal verkaufsKanal){
this(id, filmId, vorfuehrungId, besucherId, verkaufsKanal, new Gueltig(true));
}
public Ticket(final Id id,
final Id filmId, final Id vorfuehrungId, final Id besucherId,
final VerkaufsKanal verkaufsKanal, final Gueltig gueltig) {
this(id, Versionable.unknownVersion(), filmId, vorfuehrungId, besucherId, verkaufsKanal, gueltig);
}
public Ticket(final Id id, final Version version,
final Id filmId, final Id vorfuehrungId, final Id besucherId,
final VerkaufsKanal verkaufsKanal, final Gueltig gueltig) {
this.id = id;
this.version = version;
this.filmId = filmId;
this.vorfuehrungId = vorfuehrungId;
this.besucherId = besucherId;
this.verkaufsKanal = verkaufsKanal;
this.gueltig = gueltig;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@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;}
Ticket that = (Ticket) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Ticket that = (Ticket) o;
return new EqualsBuilder()
.append(id, that.id)
.append(filmId, that.filmId)
.append(vorfuehrungId, that.vorfuehrungId)
.append(besucherId, that.besucherId)
.append(verkaufsKanal, that.verkaufsKanal)
.append(gueltig, that.gueltig)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(filmId).append(vorfuehrungId).append(besucherId)
.append(verkaufsKanal)
.append(gueltig)
.toHashCode();
}
}

View file

@ -0,0 +1,23 @@
package de.accso.flexinale.ticketing.domain.services;
import de.accso.flexinale.ticketing.domain.dao.KontingentDao;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
@SuppressWarnings("unused")
public class KontingentService {
private static final Logger LOGGER = LoggerFactory.getLogger(KontingentService.class);
final KontingentDao kontingentDao;
public KontingentService(final KontingentDao kontingentDao) {
this.kontingentDao = kontingentDao;
}
public List<Kontingent> kontingente() {
return kontingentDao.findAll();
}
}

View file

@ -0,0 +1,49 @@
package de.accso.flexinale.ticketing.infrastructure;
import de.accso.flexinale.ticketing.api_out.event.OnlineKontingentChangedPublisher;
import de.accso.flexinale.ticketing.api_out.event.TicketPublisher;
import de.accso.flexinale.ticketing.application.services.TicketService;
import de.accso.flexinale.ticketing.domain.services.KontingentService;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Component
@Profile({"!test-integrated & !testdata"})
public class BootstrappingPostConstructTicketing {
private static final Logger LOGGER = LoggerFactory.getLogger(BootstrappingPostConstructTicketing.class);
@Autowired
TicketService ticketService;
@Autowired
TicketPublisher ticketPublisher;
@Autowired
KontingentService kontingentService;
@Autowired
OnlineKontingentChangedPublisher onlineKontingentChangedPublisher;
// Alternatively annotate method with @EventListener(ApplicationReadyEvent.class)
@PostConstruct
public void postConstruct() {
LOGGER.info("Publishing existing Tickets and Kontingente as events");
loadAllTicketsFromDatabaseAndPublish();
loadAllKontingenteFromDatabaseAndPublish();
LOGGER.info("Publishing existing Tickets and Kontingente as events ... done");
}
private void loadAllKontingenteFromDatabaseAndPublish() {
onlineKontingentChangedPublisher.publishOnlineKontingentChanged( kontingentService.kontingente() );
}
private void loadAllTicketsFromDatabaseAndPublish() {
ticketPublisher.publishGekaufteTickets( ticketService.tickets() );
}
}

View file

@ -0,0 +1,34 @@
package de.accso.flexinale.ticketing.infrastructure;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@Component
@Endpoint(id = "ticketingEventsConsumed")
public class FlexinaleTicketingActuatorEndpointEventsConsumed implements EventNotification {
private final Queue<Event> eventsConsumed = new ConcurrentLinkedQueue<>(); // event list is ordered by consumption time
@ReadOperation
public List<Event> eventsConsumed() {
return eventsConsumed.stream().toList();
}
@ReadOperation
public List<Event> eventsConsumedFilteredBy(@Selector Identifiable.Id correlationId) {
return eventsConsumed.stream().filter(event -> event.correlationId().equals(correlationId)).toList();
}
@Override
public void notify(final Event event) {// might want to use a generic subscriber
eventsConsumed.add(event);
}
}

View file

@ -0,0 +1,34 @@
package de.accso.flexinale.ticketing.infrastructure;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@Component
@Endpoint(id = "ticketingEventsPublished")
public class FlexinaleTicketingActuatorEndpointEventsPublished implements EventNotification {
private final Queue<Event> eventsPublished = new ConcurrentLinkedQueue<>(); // event list is ordered by production time
@ReadOperation
public List<Event> eventsPublished() {
return eventsPublished.stream().toList();
}
@ReadOperation
public List<Event> eventsPublishedFilteredBy(@Selector Identifiable.Id correlationId) {
return eventsPublished.stream().filter(event -> event.correlationId().equals(correlationId)).toList();
}
@Override
public void notify(final Event event) {// might want to use a generic subscriber
eventsPublished.add(event);
}
}

View file

@ -0,0 +1,100 @@
package de.accso.flexinale.ticketing.infrastructure;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import de.accso.flexinale.common.application.Config;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.ticketing.api_in.event.GutscheinEinloesenBeauftragtSubscriber;
import de.accso.flexinale.ticketing.api_out.event.OnlineKontingentChangedPublisher;
import de.accso.flexinale.ticketing.api_out.event.TicketPublisher;
import de.accso.flexinale.ticketing.api_in.event.VorfuehrungSubscriber;
import de.accso.flexinale.ticketing.application.services.OnlineKontingentChangedPublication;
import de.accso.flexinale.ticketing.application.services.TicketPublication;
import de.accso.flexinale.ticketing.application.services.TicketService;
import de.accso.flexinale.ticketing.domain.dao.KontingentDao;
import de.accso.flexinale.ticketing.domain.dao.TicketDao;
import de.accso.flexinale.ticketing.domain.services.KontingentService;
import de.accso.flexinale.ticketing.infrastructure.persistence.KontingentJpaRepository;
import de.accso.flexinale.ticketing.infrastructure.persistence.KontingentJpaRepositoryDelegate;
import de.accso.flexinale.ticketing.infrastructure.persistence.TicketJpaRepository;
import de.accso.flexinale.ticketing.infrastructure.persistence.TicketJpaRepositoryDelegate;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@Profile({ "!testdata-backoffice & !testdata-besucherportal" })
@EnableJpaRepositories({"de.accso.flexinale.ticketing.infrastructure.persistence"})
@EnableTransactionManagement
@EntityScan(basePackages={"de.accso.flexinale.ticketing.infrastructure.persistence"})
public class FlexinaleTicketingSpringFactory {
@Bean
public KontingentDao createTicketingKontingentDao(final KontingentJpaRepository kontingentJpaRepository) {
return new KontingentJpaRepositoryDelegate(kontingentJpaRepository);
}
@Bean
public TicketDao createTicketingTicketDao(final TicketJpaRepository ticketJpaRepository) {
return new TicketJpaRepositoryDelegate(ticketJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public TicketService createTicketingTicketService(final TicketDao ticketDao,
final KontingentDao kontingentDao,
final OnlineKontingentChangedPublication onlineKontingentChangedPublisher,
final TicketPublication ticketPublisher,
final Config config) {
return new TicketService(ticketDao, kontingentDao, onlineKontingentChangedPublisher,
ticketPublisher, config.getQuoteOnline());
}
@Bean
public OnlineKontingentChangedPublication createTicketingOnlineKontingentChangedPublisher(
final EventBusFactory eventBusFactory,
final FlexinaleTicketingActuatorEndpointEventsPublished endpointEventsPublished) {
return new OnlineKontingentChangedPublisher(eventBusFactory, endpointEventsPublished);
}
@Bean
public KontingentService createTicketingKontingentService(final KontingentDao kontingentDao) {
return new KontingentService(kontingentDao);
}
// ------------------------------------------------------------------------------------------------
@Bean
public GutscheinEinloesenBeauftragtSubscriber createTicketingGutscheinEinloesenBeauftragtSubscriber(
final EventBusFactory eventBusFactory,
final TicketService ticketService,
final FlexinaleTicketingActuatorEndpointEventsConsumed endpointEventsConsumed) {
return new GutscheinEinloesenBeauftragtSubscriber(eventBusFactory, ticketService, endpointEventsConsumed);
}
@Bean
public TicketPublication createTicketingTicketPublisher(final EventBusFactory eventBusFactory,
final FlexinaleTicketingActuatorEndpointEventsPublished endpointEventsPublished) {
return new TicketPublisher(eventBusFactory, endpointEventsPublished);
}
@Bean
public VorfuehrungSubscriber createTicketingVorfuehrungSubscriber(final EventBusFactory eventBusFactory,
final TicketService ticketService,
final FlexinaleTicketingActuatorEndpointEventsConsumed endpointEventsConsumed) {
return new VorfuehrungSubscriber(eventBusFactory, ticketService, endpointEventsConsumed);
}
// ------------------------------------------------------------------------------------------------
// Actuator Event serialization
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonTicketingCustomizer() {
return builder -> builder.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
}

View file

@ -0,0 +1,104 @@
package de.accso.flexinale.ticketing.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
@jakarta.persistence.Entity(name="Kontingent")
public class KontingentEntity implements Identifiable, Versionable, Serializable {
@jakarta.persistence.Id
public String id;
@jakarta.persistence.Version
private Integer version = 0;
@jakarta.persistence.Column
private Integer restKontingentOnline = 0;
@jakarta.persistence.Column
private Integer restKontingentZentral = 0;
@jakarta.persistence.Column
private Integer restKontingentKinokasse = 0;
@jakarta.persistence.Column
private String vorfuehrungId;
protected KontingentEntity() {
}
public KontingentEntity(final String id, final Integer version,
final Integer restKontingentOnline,
final Integer restKontingentZentral,
final Integer restKontingentKinokasse,
final String vorfuehrungId) {
this.id = id;
this.version = version;
this.restKontingentOnline = restKontingentOnline;
this.restKontingentZentral = restKontingentZentral;
this.restKontingentKinokasse = restKontingentKinokasse;
this.vorfuehrungId = vorfuehrungId;
}
@Override
public Id id() {
return Identifiable.Id.of(id);
}
@Override
public Version version() {
return Versionable.Version.of(version);
}
public Integer getRestKontingentOnline() {
return restKontingentOnline;
}
public Integer getRestKontingentZentral() {
return restKontingentZentral;
}
public Integer getRestKontingentKinokasse() {
return restKontingentKinokasse;
}
public String getVorfuehrungId() {
return vorfuehrungId;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KontingentEntity that = (KontingentEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(restKontingentOnline, that.restKontingentOnline)
.append(restKontingentZentral, that.restKontingentZentral)
.append(restKontingentKinokasse, that.restKontingentKinokasse)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(restKontingentOnline)
.append(restKontingentZentral).append(restKontingentKinokasse)
.toHashCode();
}
}

View file

@ -0,0 +1,15 @@
package de.accso.flexinale.ticketing.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface KontingentJpaRepository extends JpaRepository<KontingentEntity, String> {
@Query("SELECT k FROM Kontingent k " +
"WHERE k.vorfuehrungId = :vorfuehrungId")
Optional<KontingentEntity> findByVorfuehrung(final String vorfuehrungId);
}

View file

@ -0,0 +1,65 @@
package de.accso.flexinale.ticketing.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.dao.KontingentDao;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import de.accso.flexinale.ticketing.infrastructure.persistence.mapper.KontingentEntity2KontingentMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class KontingentJpaRepositoryDelegate implements KontingentDao {
private final KontingentJpaRepository kontingentJpaRepository;
public KontingentJpaRepositoryDelegate(KontingentJpaRepository kontingentJpaRepository) {
this.kontingentJpaRepository = kontingentJpaRepository;
}
@Override
public List<Kontingent> findAll() {
List<KontingentEntity> KontingentEntities = kontingentJpaRepository.findAll();
return KontingentEntities.stream()
.map(KontingentEntity2KontingentMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public Optional<Kontingent> findById(final Identifiable.Id id) {
Optional<KontingentEntity> kontingentEntity = kontingentJpaRepository.findById(id.id());
return KontingentEntity2KontingentMapper.map(kontingentEntity);
}
@Override
public Optional<Kontingent> findByVorfuehrung(final Identifiable.Id vorfuehrungId) {
Optional<KontingentEntity> kontingentEntity = kontingentJpaRepository.findByVorfuehrung(vorfuehrungId.id());
return KontingentEntity2KontingentMapper.map(kontingentEntity);
}
@Override
public Kontingent save(final Kontingent kontingent) {
KontingentEntity kontingentEntity = KontingentEntity2KontingentMapper.map(kontingent);
KontingentEntity savedEntity = kontingentJpaRepository.save(kontingentEntity);
return KontingentEntity2KontingentMapper.map(savedEntity);
}
@Override
public void delete(final Kontingent kontingent) {
KontingentEntity kontingentEntity = KontingentEntity2KontingentMapper.map(kontingent);
kontingentJpaRepository.delete(kontingentEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
kontingentJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
kontingentJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,93 @@
package de.accso.flexinale.ticketing.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import jakarta.persistence.EnumType;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
@jakarta.persistence.Entity(name="Ticket")
public class TicketEntity implements Identifiable, Versionable, Serializable {
@jakarta.persistence.Id
public String id; // primary key
public String filmId;
public String vorfuehrungId;
public String benutzerId;
public boolean gueltig = true;
@jakarta.persistence.Enumerated(EnumType.STRING)
public Ticket.VerkaufsKanal verkaufsKanal;
@jakarta.persistence.Version
private Integer version = 0;
protected TicketEntity() {
}
public TicketEntity(final String id, final Integer version,
final String filmId,
final String vorfuehrungId,
final String benutzerId,
final Ticket.VerkaufsKanal verkaufsKanal,
final boolean gueltig) {
this.id = id;
this.version = version;
this.filmId = filmId;
this.vorfuehrungId = vorfuehrungId;
this.benutzerId = benutzerId;
this.verkaufsKanal = verkaufsKanal;
this.gueltig = gueltig;
}
@Override
public Id id() {
return Identifiable.Id.of(id);
}
@Override
public Version version() {
return Versionable.Version.of(version);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
TicketEntity that = (TicketEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(filmId, that.filmId)
.append(vorfuehrungId, that.vorfuehrungId).append(benutzerId, that.benutzerId)
.append(verkaufsKanal, that.verkaufsKanal)
.append(gueltig, that.gueltig)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(filmId).append(vorfuehrungId).append(benutzerId)
.append(verkaufsKanal)
.append(gueltig)
.toHashCode();
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.ticketing.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TicketJpaRepository extends JpaRepository<TicketEntity, String> {
@Query("SELECT t FROM Ticket t " +
"WHERE t.benutzerId = :benutzerId")
List<TicketEntity> findByBenutzer(final String benutzerId);
@Query("SELECT t FROM Ticket t " +
"WHERE t.vorfuehrungId = :vorfuehrungId")
List<TicketEntity> findByVorfuehrung(final String vorfuehrungId);
@Query("SELECT count(t) FROM Ticket t WHERE t.benutzerId = :benutzerId")
int gesamtZahlDerTicketsFuer(final String benutzerId);
}

View file

@ -0,0 +1,72 @@
package de.accso.flexinale.ticketing.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.dao.TicketDao;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import de.accso.flexinale.ticketing.infrastructure.persistence.mapper.TicketEntity2TicketMapper;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("unused")
public class TicketJpaRepositoryDelegate implements TicketDao {
private final TicketJpaRepository ticketJpaRepository;
public TicketJpaRepositoryDelegate(TicketJpaRepository ticketJpaRepository) {
this.ticketJpaRepository = ticketJpaRepository;
}
@Override
public List<Ticket> findAll() {
List<TicketEntity> ticketEntities = ticketJpaRepository.findAll();
return TicketEntity2TicketMapper.map(ticketEntities);
}
@Override
public Optional<Ticket> findById(final Identifiable.Id id) {
Optional<TicketEntity> TicketEntity = ticketJpaRepository.findById(id.id());
return TicketEntity2TicketMapper.map(TicketEntity);
}
@Override
public List<Ticket> findByBesucher(final Identifiable.Id besucherId) {
List<TicketEntity> ticketEntitiesForBesucher = ticketJpaRepository.findByBenutzer(besucherId.id());
return TicketEntity2TicketMapper.map(ticketEntitiesForBesucher);
}
@Override
public List<Ticket> findByVorfuehrung(final Identifiable.Id vorfuehrungId) {
List<TicketEntity> ticketEntitiesForBesucher = ticketJpaRepository.findByVorfuehrung(vorfuehrungId.id());
return TicketEntity2TicketMapper.map(ticketEntitiesForBesucher);
}
@Override
public int gesamtZahlDerTicketsFuer(final Identifiable.Id besucherId) {
return ticketJpaRepository.gesamtZahlDerTicketsFuer(besucherId.id());
}
@Override
public Ticket save(final Ticket ticket) {
TicketEntity ticketEntity = TicketEntity2TicketMapper.map(ticket);
TicketEntity savedEntity = ticketJpaRepository.save(ticketEntity);
return TicketEntity2TicketMapper.map(savedEntity);
}
@Override
public void delete(final Ticket ticket) {
TicketEntity ticketEntity = TicketEntity2TicketMapper.map(ticket);
ticketJpaRepository.delete(ticketEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
ticketJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
ticketJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,41 @@
package de.accso.flexinale.ticketing.infrastructure.persistence.mapper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import de.accso.flexinale.ticketing.infrastructure.persistence.KontingentEntity;
import java.util.Optional;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class KontingentEntity2KontingentMapper {
public static Kontingent map(final KontingentEntity kontingentEntity) {
return new Kontingent(kontingentEntity.id(),
kontingentEntity.version(),
new Kontingent.RestKontingent(kontingentEntity.getRestKontingentOnline()),
new Kontingent.RestKontingent(kontingentEntity.getRestKontingentZentral()),
new Kontingent.RestKontingent(kontingentEntity.getRestKontingentKinokasse()),
Identifiable.Id.of(kontingentEntity.getVorfuehrungId()));
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<Kontingent> map(final Optional<KontingentEntity> optionalKontingentEntity) {
if (optionalKontingentEntity.isEmpty()) {
return Optional.empty();
}
else {
KontingentEntity kontingentEntity = optionalKontingentEntity.get();
return Optional.of(map(kontingentEntity));
}
}
public static KontingentEntity map(final Kontingent kontingent) {
return new KontingentEntity(kontingent.id().id(),
kontingent.version().version(),
getRawOrNull(kontingent.getRestKontingentOnline()),
getRawOrNull(kontingent.getRestKontingentZentral()),
getRawOrNull(kontingent.getRestKontingentKinokasse()),
kontingent.getVorfuehrungId().id());
}
}

View file

@ -0,0 +1,44 @@
package de.accso.flexinale.ticketing.infrastructure.persistence.mapper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import de.accso.flexinale.ticketing.infrastructure.persistence.TicketEntity;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class TicketEntity2TicketMapper {
public static Ticket map(final TicketEntity ticketEntity) {
return new Ticket(ticketEntity.id(), ticketEntity.version(),
Identifiable.Id.of(ticketEntity.filmId), Identifiable.Id.of(ticketEntity.vorfuehrungId),
Identifiable.Id.of(ticketEntity.benutzerId),
ticketEntity.verkaufsKanal, new Ticket.Gueltig(ticketEntity.gueltig));
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<Ticket> map(final Optional<TicketEntity> optionalTicketEntity) {
if (optionalTicketEntity.isEmpty()) {
return Optional.empty();
}
else {
TicketEntity ticketEntity = optionalTicketEntity.get();
return Optional.of(map(ticketEntity));
}
}
public static TicketEntity map(final Ticket ticket) {
return new TicketEntity(ticket.id().id(), ticket.version().version(),
ticket.filmId.id(), ticket.vorfuehrungId.id(), ticket.besucherId.id(),
ticket.verkaufsKanal, getRawOrNull(ticket.gueltig));
}
public static List<Ticket> map(final List<TicketEntity> ticketEntities) {
return ticketEntities.stream().map(TicketEntity2TicketMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,72 @@
application.title=FLEXinale as Distributed Services, Ticketing
application.version=@pom.version@ @maven.build.timestamp@
spring.banner.location=classpath:/flexinale-banner.txt
#########################################################################
# For endpoint /version
#########################################################################
build.version=@pom.version@
build.date=@maven.build.timestamp@
#########################################################################
# Persistence
#########################################################################
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/distributed-ticketing
spring.datasource.name=flexinale
spring.datasource.username=flexinale
spring.datasource.password=flexinale
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database=postgresql
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.generate-ddl=true
# uses schema if existing (or creates anew)
# in a real production environment one should use 'validate' which does just validation but doesn't change anything
spring.jpa.hibernate.ddl-auto=update
# Pros and Cons: See https://www.baeldung.com/spring-open-session-in-view,
# https://stackoverflow.com/questions/30549489/what-is-this-spring-jpa-open-in-view-true-property-in-spring-boot
spring.jpa.open-in-view=false
# spring.jpa.show-sql=true
#########################################################################
# Web
#########################################################################
server.error.path=/error
server.error.include-stacktrace=always
server.error.include-exception=true
server.error.include-message=always
server.error.whitelabel.enabled=false
server.port=8082
#########################################################################
# Security
#########################################################################
security.enable-csrf=true
#########################################################################
# Kafka
#########################################################################
spring.kafka.consumer.group-id=flexinale-distributed-ticketing
# Spring Kafka Consumer
spring.kafka.consumer.bootstrap-servers=localhost:29092
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# Spring Kafka Producer
spring.kafka.producer.bootstrap-servers=localhost:29092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
#########################################################################
# Metrics endpoints, micrometer/prometheus/grafana
#########################################################################
# enable and expose
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false
#########################################################################
# flexinale properties
#########################################################################
# Quote for online kontingent in percent
de.accso.flexinale.kontingent.quote.online=33

View file

@ -0,0 +1,10 @@
-------------------------------------------------------------------------
,------. ,--. ,------. ,--. ,--. ,--. ,--.
| .---' | | | .---' \ `.' / `--' ,--,--, ,--,--. | | ,---.
| `--, | | | `--, .' \ ,--. | \ ' ,-. | | | | .-. :
| |` | '--. | `---. / .'. \ | | | || | \ '-' | | | \ --.
`--' `-----' `------' '--' '--' `--' `--''--' `--`--' `--' `----'
${application.title} on port ${server.port}
(version ${application.version})
Powered by Spring Boot ${spring-boot.version}
-------------------------------------------------------------------------

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,12 @@
<!DOCTYPE html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Flexinale Distributed - Ticketing</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
</head>
<body>
Flexinale Distributed - Ticketing
</body>
</html>

View file

@ -0,0 +1,3 @@
# curl -v -X GET -u admin:admin1 http://localhost:8082/actuator/ticketingEventsConsumed
GET http://localhost:8082/actuator/ticketingEventsConsumed
Authorization: Basic admin admin1

View file

@ -0,0 +1,3 @@
# curl -v -X GET -u admin:admin1 http://localhost:8082/actuator/ticketingEventsPublished
GET http://localhost:8082/actuator/ticketingEventsPublished
Authorization: Basic admin admin1

View file

@ -0,0 +1,3 @@
# curl -v -X GET -u admin:admin1 http://localhost:8082/actuator/health
GET http://localhost:8082/actuator/health
Authorization: Basic admin admin1

View file

@ -0,0 +1,3 @@
# curl -v -X GET -u admin:admin1 http://localhost:8082/actuator/info
GET http://localhost:8082/actuator/info
Authorization: Basic admin admin1

View file

@ -0,0 +1,3 @@
# curl -v -X GET -u admin:admin1 http://localhost:8082/actuator/metrics
GET http://localhost:8082/actuator/metrics
Authorization: Basic admin admin1

View file

@ -0,0 +1,3 @@
# curl -v -X GET -u admin:admin1 http://localhost:8082/actuator/prometheus
GET http://localhost:8082/actuator/prometheus
Authorization: Basic admin admin1

View file

@ -0,0 +1,21 @@
package de.accso.flexinale;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = {FlexinaleDistributedApplicationTicketing.class})
@ActiveProfiles("smoketest")
public class ApplicationPropertiesFileTest {
@Value("${application.version}")
private String propertyApplicationVersion;
@Test
public void testReadsTestPropertiesFile() {
assertThat(propertyApplicationVersion).isEqualTo("test");
}
}

View file

@ -0,0 +1,20 @@
package de.accso.flexinale;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest(classes = {FlexinaleDistributedApplicationTicketing.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@ActiveProfiles("smoketest")
@DoNotCheckInArchitectureTests
class FlexinaleDistributedApplicationTicketingSmokeTest {
@Test
@SuppressWarnings("EmptyMethod")
void testLoadInitialApplicationContext() {
// nope
}
}

View file

@ -0,0 +1,31 @@
package de.accso.flexinale;
import de.accso.flexinale.common.application.Config;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = {FlexinaleDistributedApplicationTicketing.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@ActiveProfiles("configtest")
@DoNotCheckInArchitectureTests
class FlexinaleDistributedApplicationTicketingSpringConfigTest {
@Autowired
Config config;
@Test
void testLoadSpringConfig() {
// arrange, act
// nope, is loaded auto-magically by Spring, see application-configtest.properties
// assert
assertThat(config.getQuoteOnline()).isEqualTo(12);
assertThat(config.getMinZeitZwischenVorfuehrungenInMinuten()).isEqualTo(30); // default
}
}

View file

@ -0,0 +1,2 @@
# Quote for online kontingent in percent
de.accso.flexinale.kontingent.quote.online=12

View file

@ -0,0 +1,39 @@
application.title=FLEXinale as Distributed Services, Ticketing
application.version=test
spring.banner.location=classpath:/flexinale-banner.txt
#########################################################################
# For endpoint /version
#########################################################################
build.version=@pom.version@
build.date=@maven.build.timestamp@
#########################################################################
# Persistence
#########################################################################
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/distributed-ticketing
spring.datasource.name=flexinale
spring.datasource.username=flexinale
spring.datasource.password=flexinale
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database=postgresql
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.generate-ddl=true
# spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
server.port=9092
#########################################################################
# Web
#########################################################################
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
server.error.path=/error
#########################################################################
# flexinale properties
#########################################################################
# Quote for online kontingent in percent
de.accso.flexinale.kontingent.quote.online=33
# time in minutes
de.accso.flexinale.vorfuehrung.min-zeit-zwischen-vorfuehrungen-in-minuten=30

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="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>