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,7 @@
# flex-training-filmfestival
iSAQB Advanced Level FLEX training: This code contains our case study code for the "filmfestival" example.
This is the main repo containing blueprint and solution.
## Architecture as a modulithic system
Part 1 is in the folder flexinale-modulith-1-onion.
Part 2 is in the folder flexinale-modulith-1-components.

View file

@ -0,0 +1,30 @@
<?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.domain.model.Kino" />
<Bug pattern="NP_NULL_ON_SOME_PATH_MIGHT_BE_INFEASIBLE" />
</Match>
<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.api_contract.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.api_contract.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.api_contract.model.KinoTO" />
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Class name="de.accso.flexinale.common.infrastructure.FlexinaleSpringConfig" />
<Bug pattern="CT_CONSTRUCTOR_THROW"/>
</Match>
</FindBugsFilter>

View file

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

View file

@ -0,0 +1,6 @@
docker-compose \
--project-name flexinale-modulith-2-components \
-f ../../../infrastructure/docker/flexinale-network.yml \
-f ../../../infrastructure/docker/postgres/flexinale-postgres.yml \
-f flexinale-modulith-2-components.yml \
up

View file

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

View file

@ -0,0 +1,13 @@
version: '2.2'
services:
flexinale-modulith-2-components:
image: de.accso/flexinale-modulith-2-components:2024.3.0
depends_on:
- flexinale-postgres
ports:
- "8080:8080"
networks:
- flexinale-network
command: sh -c "java -Dspring.datasource.url=jdbc:postgresql://flexinale-postgres:5432/modulith-2 -jar flexinale-modulith-2-components-2024.3.0-spring-boot-fat-jar.jar"

View file

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

View file

@ -0,0 +1,13 @@
version: '2.2'
services:
flexinale-modulith-2-components:
image: de.accso/flexinale-modulith-2-components:2024.3.0
depends_on:
- flexinale-postgres
ports:
- "8080:8080"
networks:
- flexinale-network
command: sh -c "java -Dspring.datasource.url=jdbc:postgresql://flexinale-postgres:5432/modulith-2 -jar flexinale-modulith-2-components-2024.3.0-spring-boot-fat-jar.jar"

View file

@ -0,0 +1,6 @@
podman-compose ^
--project-name flexinale-modulith-2-components ^
-f ..\..\..\infrastructure\podman\flexinale-network.yml ^
-f ..\..\..\infrastructure\podman\postgres\flexinale-postgres.yml ^
-f flexinale-modulith-2-components.yml ^
up

View file

@ -0,0 +1 @@
podman build -t de.accso/flexinale-modulith-2-components:2024.3.0 -f Dockerfile ../../

View file

@ -0,0 +1,120 @@
<?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</artifactId>
<version>2024.3.0</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>flexinale-modulith-2-components</artifactId>
<version>2024.3.0</version>
<name>Flexinale Modulith, Step 2, Components</name>
<description>Flexinale - FLEX case-study &quot;film festival&quot;, modulith system, step 2 - components</description>
<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-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</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.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${apache-poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${apache-poi.version}</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>
</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>
<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,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - Component Dependencies Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Modulith2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.ComponentDependenciesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - Domain Classes Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Modulith2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfDomainClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - Entities Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Modulith2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfEntityClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - No Dependencies To Spring Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Modulith2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.NoDependenciesToSpringTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - Onion Dependencies Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Modulith2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.OnionDependenciesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - REST upload to add filme" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="REST upload Modulith 2" path="$PROJECT_DIR$/flexinale-modulith-2-components/src/test/curl/rest-post-upload-to-add-and-update-filme.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - REST upload to add kinos" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="REST upload Modulith 2" path="$PROJECT_DIR$/flexinale-modulith-2-components/src/test/curl/rest-post-upload-to-add-and-update-kinos.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - REST upload to add vorfuehrungen" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="REST upload Modulith 2" path="$PROJECT_DIR$/flexinale-modulith-2-components/src/test/curl/rest-post-upload-to-add-and-update-vorfuehrungen.http" requestIdentifier="#1">
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - TO Classes Use Correct Interfaces And Internal Structure Test" type="JUnit" factoryName="JUnit" folderName="ArchitectureTests Modulith2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="architecturetests.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="architecturetests" />
<option name="MAIN_CLASS_NAME" value="architecturetests.InternalStructureOfTOClassesUsingCorrectInterfacesAndAttributeTypesTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - delete Benutzer data" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.backoffice.api_contract.event.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.BenutzerDatabaseCleaner" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea -Dspring.jpa.hibernate.ddl-auto=create" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - delete Film, Kino, KinoSaal, Vorfuehrung data" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.backoffice.infrastructure.persistence.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.FKKsVDatabaseCleaner" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea -Dspring.jpa.hibernate.ddl-auto=create" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - delete Ticket and Kontingente data" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.backoffice.api_contract.event.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.TicketsAndKontingenteDatabaseCleaner" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea -Dspring.jpa.hibernate.ddl-auto=create" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - delete all data from database" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="testdata.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.AllDatabaseCleaner" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - load Benutzer data" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.backoffice.api_contract.event.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.BenutzerTestDataLoader" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - load Film, Kino, KinoSaal, Vorfuehrung data" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.backoffice.api_contract.event.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.FKKsVTestDataLoader" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - load all data to database" type="JUnit" factoryName="JUnit" folderName="Testdata Modulith 2">
<module name="flexinale-modulith-2-components" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="testdata.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="testdata" />
<option name="MAIN_CLASS_NAME" value="testdata.AllTestDataLoader" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Modulith 2 - start app (&quot;FlexinaleModulith2ComponentsApplication&quot;)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" folderName="App Modulith 2">
<module name="flexinale-modulith-2-components" />
<option name="SPRING_BOOT_MAIN_CLASS" value="de.accso.flexinale.FlexinaleModulith2ComponentsApplication" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,21 @@
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.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@SpringBootApplication
public class FlexinaleModulith2ComponentsApplication {
public static void main(String[] args) {
SpringApplication.run(FlexinaleModulith2ComponentsApplication.class, args);
}
@Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}

View file

@ -0,0 +1,41 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.application.services.ExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedInputStream;
import java.util.Collection;
abstract class AbstractExcelRestController {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExcelRestController.class);
@SuppressWarnings("rawtypes")
protected RestUploadResult uploadViaRestCall(final ExcelDataUploadService excelDataUploadService, final MultipartFile file) {
try(BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
if (!ExcelHelper.isExcelFile(stream)) {
String message = "Please upload a valid Excel file!";
throw new FlexinaleIllegalStateException(message);
}
Collection uploadedData =
excelDataUploadService.loadDataFromExcelSheetAndPersistAndReturn(stream, PersistMode.ADD_ONLY);
String message = "Uploaded the Excel file %s adding %d new entities (while not updating any existing data)."
.formatted(file.getOriginalFilename(), uploadedData.size());
return new RestUploadResult(HttpStatus.OK, message);
}
catch (Exception ex) {
String message = "Could not upload the Excel file: %s: %s".formatted(file.getOriginalFilename(), ex.getMessage());
RestBadRequestException exception = new RestBadRequestException(message, ex);
LOGGER.error(message, exception);
throw exception;
}
}
}

View file

@ -0,0 +1,15 @@
package de.accso.flexinale.backoffice.api_in.rest;
import org.apache.poi.poifs.filesystem.FileMagic;
import java.io.IOException;
import java.io.InputStream;
final class ExcelHelper {
private ExcelHelper() {
}
static boolean isExcelFile(final InputStream stream) throws IOException {
return (FileMagic.valueOf(stream) == FileMagic.OOXML);
}
}

View file

@ -0,0 +1,49 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.backoffice.application.services.FilmUploadService;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.application.services.FilmService;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
public class FilmRestController extends AbstractExcelRestController {
@Autowired
private FilmService filmService;
@Autowired
private FilmUploadService filmUploadService;
@GetMapping(value = "/rest/filme", produces = "application/json")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public List<Film> filme() {
return filmService.filme();
}
@GetMapping(value = "/rest/film/{id}", produces = "application/json")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public Film film(@PathVariable final String id) {
Identifiable.Id filmId = Identifiable.Id.of(id);
Film film = filmService.film(filmId);
if (film == null) {
throw new RestResourceNotFoundException("no Film %s found.".formatted(id));
}
return film;
}
@PostMapping("/rest/filme")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<RestResponseMessage> uploadFilme(@RequestParam("file") final MultipartFile file) {
RestUploadResult restUploadResult = uploadViaRestCall(filmUploadService, file);
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
}

View file

@ -0,0 +1,79 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.backoffice.application.services.KinoUploadService;
import de.accso.flexinale.backoffice.application.services.KinoSaalUploadService;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.application.services.KinoService;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedInputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@RestController
public class KinoKinoSaalRestController extends AbstractExcelRestController {
@Autowired
private KinoService kinoService;
@Autowired
private KinoUploadService kinoUploadService;
@Autowired
private KinoSaalUploadService kinoSaalUploadService;
@GetMapping(value = "/rest/kinos", produces = "application/json")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public List<Kino> kinos() {
return kinoService.kinos();
}
@GetMapping(value = "/rest/kino/{id}", produces = "application/json")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public Kino kino(@PathVariable final String id) {
Identifiable.Id kinoId = Identifiable.Id.of(id);
Kino kino = kinoService.kino(kinoId);
if (kino == null) {
throw new RestResourceNotFoundException("no Kino %s found.".formatted(id));
}
return kino;
}
@PostMapping("/rest/kinos")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<RestResponseMessage> uploadKinosKinoSaele(@RequestParam("file") final MultipartFile file) {
try(BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
// load KinoSaele first
Collection<KinoSaal> alleKinoSaele =
kinoSaalUploadService.loadDataFromExcelSheet(stream);
Map<Identifiable.Id, Collection<KinoSaal>> alleKinosUndIhreSaele =
kinoSaalUploadService.mapKinoSaeleToKino(alleKinoSaele);
// .. then persist them as part of a Kino
RestUploadResult restUploadResult;
try {
kinoUploadService.beforeLoad(alleKinosUndIhreSaele); // fix state of Kino-KinoSaal in map
restUploadResult = uploadViaRestCall(kinoUploadService, file);
}
finally {
kinoUploadService.afterLoad(); // clear map
}
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
catch (Exception ex) {
String message = "Could not upload the Excel file: %s: %s".formatted(file.getOriginalFilename(), ex.getMessage());
throw new RestBadRequestException(message, ex);
}
}
}

View file

@ -0,0 +1,19 @@
package de.accso.flexinale.backoffice.api_in.rest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@SuppressWarnings("unused")
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class RestBadRequestException extends RuntimeException {
// see https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services
public RestBadRequestException(final String message) {
super(message);
}
public RestBadRequestException(final String message, final Exception ex) {
super(message, ex);
}
}

View file

@ -0,0 +1,14 @@
package de.accso.flexinale.backoffice.api_in.rest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class RestResourceNotFoundException extends RuntimeException {
// see https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services
public RestResourceNotFoundException(final String message) {
super(message);
}
}

View file

@ -0,0 +1,4 @@
package de.accso.flexinale.backoffice.api_in.rest;
record RestResponseMessage(String message) {
}

View file

@ -0,0 +1,7 @@
package de.accso.flexinale.backoffice.api_in.rest;
import org.springframework.http.HttpStatus;
@SuppressWarnings("SameParameterValue")
record RestUploadResult(HttpStatus status, String message) {
}

View file

@ -0,0 +1,74 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.backoffice.application.services.VorfuehrungUploadService;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice.application.services.VorfuehrungService;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing_api_contract.api_contract.Ticketing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedInputStream;
import java.util.Collection;
import java.util.List;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
@RestController
public class VorfuehrungRestController extends AbstractExcelRestController {
@Autowired
private VorfuehrungService vorfuehrungService;
@Autowired
private VorfuehrungUploadService vorfuehrungUploadService;
@Autowired
private Ticketing ticketing;
@GetMapping(value = "/rest/vorfuehrungen", produces = "application/json")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public List<Vorfuehrung> vorfuehrungen() {
return vorfuehrungService.vorfuehrungen();
}
@GetMapping(value = "/rest/vorfuehrungen/{id}", produces = "application/json")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public Vorfuehrung vorfuehrung(@PathVariable final String id) {
Identifiable.Id vorfuehrungId = Identifiable.Id.of(id);
Vorfuehrung vorfuehrung = vorfuehrungService.vorfuehrung(vorfuehrungId);
if (vorfuehrung == null) {
throw new RestResourceNotFoundException("no Vorfuehrung %s found.".formatted(id));
}
return vorfuehrung;
}
@PostMapping("/rest/vorfuehrungen")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<RestResponseMessage> uploadVorfuehrungen(@RequestParam("file") final MultipartFile file) {
try(BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
Collection<Vorfuehrung> vorfuehrungen = vorfuehrungUploadService.loadDataFromExcelSheet(stream);
// and advice ticketing (results in kontingente)
for (Vorfuehrung vorfuehrung : vorfuehrungen) {
try {
ticketing.gebeVorfuehrungInDenVerkauf(vorfuehrung.id(), getRawOrNull(vorfuehrung.kinoSaal.anzahlPlaetze));
}
catch (IllegalStateException ise) {
// do nothing... Kontingent already exists. We do not support any update of entities here
}
}
} catch (Exception ex) {
String message = "Could not upload the Excel file: %s: %s".formatted(file.getOriginalFilename(), ex.getMessage());
throw new RestBadRequestException(message, ex);
}
RestUploadResult restUploadResult = uploadViaRestCall(vorfuehrungUploadService, file);
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
}

View file

@ -0,0 +1,37 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.application.services.mapper.Film2FilmTOMapper;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice_api_contract.api_contract.FilmRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Transactional
public class FilmRetrieverService implements FilmRetriever {
private final FilmDao filmDao;
public FilmRetrieverService(final FilmDao filmDao) {
this.filmDao = filmDao;
}
@Override
public FilmTO film(final Identifiable.Id id) {
Optional<Film> filmOptional = filmDao.findById(id);
return filmOptional.map(Film2FilmTOMapper::map).orElse(null);
}
@Override
public List<FilmTO> filme() {
List<Film> filme = filmDao.findAll();
return filme.stream().map(Film2FilmTOMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class FilmService {
private final de.accso.flexinale.backoffice.domain.services.FilmService filmService;
public FilmService(de.accso.flexinale.backoffice.domain.services.FilmService filmService) {
this.filmService = filmService;
}
public List<Film> filme() {
return filmService.filme();
}
public Film film(final Identifiable.Id id) {
return filmService.film(id);
}
}

View file

@ -0,0 +1,58 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Transactional
public class FilmUploadService extends AbstractExcelDataUploadService<Film> {
private static final Logger LOGGER = LoggerFactory.getLogger(FilmUploadService.class);
private final FilmDao filmDao;
public FilmUploadService(final FilmDao filmDao) {
this.filmDao = filmDao;
}
@Override
public AbstractDao<Film> getDao() {
return filmDao;
}
@Override
public String getNameOfExcelDataType() {
return Film.class.getSimpleName();
}
@Override
public Film createDataFromExcelRow(final Row excelRow) {
String filmId = excelRow.getCell(0).getStringCellValue();
Cell cellDauer = excelRow.getCell(3);
int dauerInMinuten =
switch(cellDauer.getCellType()) {
case NUMERIC -> (int) cellDauer.getNumericCellValue();
case STRING -> Integer.parseInt(cellDauer.getStringCellValue());
default -> throw new FlexinaleIllegalArgumentException("Dauer is not a number");
};
Film film = new Film(
Identifiable.Id.of(filmId),
new Film.Titel(excelRow.getCell(1).getStringCellValue()),
new Film.ImdbUrl(excelRow.getCell(2).getStringCellValue()),
new Film.DauerInMinuten(dauerInMinuten)
);
LOGGER.debug("New Film created " + film);
return film;
}
}

View file

@ -0,0 +1,38 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.application.services.mapper.KinoKinoSaal2KinoKinoSaalTOMapper;
import de.accso.flexinale.backoffice.domain.dao.KinoDao;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice_api_contract.api_contract.KinoRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Transactional
public class KinoRetrieverService implements KinoRetriever {
private final KinoDao kinoDao;
public KinoRetrieverService(final KinoDao kinoDao) {
this.kinoDao = kinoDao;
}
@Override
public KinoTO kino(final Identifiable.Id id) {
Optional<Kino> kinoOptional = kinoDao.findById(id);
return kinoOptional.map(KinoKinoSaal2KinoKinoSaalTOMapper::mapKino).orElse(null);
}
@Override
public List<KinoTO> kinos() {
List<Kino> kinos = kinoDao.findAll();
return kinos.stream()
.map(KinoKinoSaal2KinoKinoSaalTOMapper::mapKino)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,85 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.dao.KinoSaalDao;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@Transactional
public class KinoSaalUploadService extends AbstractExcelDataUploadService<KinoSaal> {
private static final Logger LOGGER = LoggerFactory.getLogger(KinoSaalUploadService.class);
private final KinoSaalDao kinoSaalDao;
public KinoSaalUploadService(final KinoSaalDao kinoSaalDao) {
this.kinoSaalDao = kinoSaalDao;
}
@Override
public AbstractDao<KinoSaal> getDao() {
return kinoSaalDao;
}
@Override
public String getNameOfExcelDataType() {
return KinoSaal.class.getSimpleName();
}
@Override
public KinoSaal createDataFromExcelRow(final Row excelRow) {
String kinoSaalId = excelRow.getCell(0).getStringCellValue();
String kinoId = excelRow.getCell(3).getStringCellValue();
Cell cellAnzahlPlaetze = excelRow.getCell(2);
int anzahlPlaetze =
switch(cellAnzahlPlaetze.getCellType()) {
case NUMERIC -> (int) cellAnzahlPlaetze.getNumericCellValue();
case STRING -> Integer.parseInt(cellAnzahlPlaetze.getStringCellValue());
default -> throw new FlexinaleIllegalArgumentException("Anzahl Plaetze is not a number");
};
KinoSaal kinoSaal = new KinoSaal(
Identifiable.Id.of(kinoSaalId),
new KinoSaal.Name(excelRow.getCell(1).getStringCellValue()),
new KinoSaal.AnzahlPlaetze(anzahlPlaetze),
new Kino(Identifiable.Id.of(kinoId))
);
LOGGER.debug("New KinoSaal created: " + kinoSaal);
return kinoSaal;
}
public Map<Identifiable.Id, Collection<KinoSaal>> mapKinoSaeleToKino(final Collection<KinoSaal> kinoSaele) {
Map<Identifiable.Id, Collection<KinoSaal>> saeleUndKinos = new HashMap<>();
for (KinoSaal saal : kinoSaele) {
Identifiable.Id key = saal.kino.id();
Collection<KinoSaal> kinoSaeleAlreadyMapped = saeleUndKinos.get(key);
if (kinoSaeleAlreadyMapped == null) {
HashSet<KinoSaal> saele = new HashSet<>();
saele.add(saal);
saeleUndKinos.put(key, saele);
}
else {
kinoSaeleAlreadyMapped.add(saal);
saeleUndKinos.put(key, kinoSaeleAlreadyMapped);
}
}
return saeleUndKinos;
}
}

View file

@ -0,0 +1,26 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class KinoService {
private final de.accso.flexinale.backoffice.domain.services.KinoService kinoService;
public KinoService(de.accso.flexinale.backoffice.domain.services.KinoService kinoService) {
this.kinoService = kinoService;
}
public List<Kino> kinos() {
return kinoService.kinos();
}
public Kino kino(Identifiable.Id kinoId) {
return kinoService.kino(kinoId);
}
}

View file

@ -0,0 +1,81 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.dao.KinoDao;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.DeveloperMistakeException;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Transactional
public class KinoUploadService extends AbstractExcelDataUploadService<Kino> {
private static final Logger LOGGER = LoggerFactory.getLogger(KinoUploadService.class);
private final KinoDao kinoDao;
private final Map<Identifiable.Id, Collection<KinoSaal>> alleKinosUndIhreSaele = new HashMap<>();
public KinoUploadService(KinoDao kinoDao) {
this.kinoDao = kinoDao;
}
@Override
public AbstractDao<Kino> getDao() {
return kinoDao;
}
@Override
public String getNameOfExcelDataType() {
return Kino.class.getSimpleName();
}
@Override
public void beforeLoad(Object... o) {
if ((o.length != 1) && (!(o[0] instanceof Map))) {
throw new DeveloperMistakeException("wrong type: expecting a " +
"Map<Identifiable.Id, Collection<KinoSaal>> of Kino and their KinoSaele");
}
@SuppressWarnings("unchecked")
Map<Identifiable.Id, Collection<KinoSaal>> newKinoAndKinoSaele = (Map<Identifiable.Id, Collection<KinoSaal>>) o[0];
this.alleKinosUndIhreSaele.putAll(newKinoAndKinoSaele);
}
@Override
public void afterLoad(Object... o) {
alleKinosUndIhreSaele.clear();
}
@Override
public Kino createDataFromExcelRow(final Row excelRow) {
String kinoId = excelRow.getCell(0).getStringCellValue();
Collection<KinoSaal> saeleImKino = alleKinosUndIhreSaele.get(Identifiable.Id.of(kinoId));
if (saeleImKino == null || saeleImKino.isEmpty()) {
throw new FlexinaleIllegalStateException("no Saele for Kino %s found".formatted(kinoId));
}
Kino kino = new Kino(
Identifiable.Id.of(kinoId),
new Kino.Name(excelRow.getCell(1).getStringCellValue()),
new Kino.Adresse(excelRow.getCell(2).getStringCellValue()),
new Kino.EmailAdresse(excelRow.getCell(3).getStringCellValue()),
Set.copyOf(saeleImKino)
);
LOGGER.debug("New Kino created: " + kino);
return kino;
}
}

View file

@ -0,0 +1,37 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.application.services.mapper.Vorfuehrung2VorfuehrungTOMapper;
import de.accso.flexinale.backoffice.domain.dao.VorfuehrungDao;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Transactional
public class VorfuehrungRetrieverService implements VorfuehrungRetriever {
private final VorfuehrungDao vorfuehrungDao;
public VorfuehrungRetrieverService(VorfuehrungDao vorfuehrungDao) {
this.vorfuehrungDao = vorfuehrungDao;
}
@Override
public VorfuehrungTO vorfuehrung(final Identifiable.Id id) {
Optional<Vorfuehrung> vorfuehrungOptional = vorfuehrungDao.findById(id);
return vorfuehrungOptional.map(Vorfuehrung2VorfuehrungTOMapper::map).orElse(null);
}
@Override
public List<VorfuehrungTO> vorfuehrungenByFilmId(final Identifiable.Id filmId) {
List<Vorfuehrung> vorfuehrungen = vorfuehrungDao.findByFilmId(filmId);
return vorfuehrungen.stream().map(Vorfuehrung2VorfuehrungTOMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,27 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class VorfuehrungService {
private final de.accso.flexinale.backoffice.domain.services.VorfuehrungService vorfuehrungService;
public VorfuehrungService(de.accso.flexinale.backoffice.domain.services.VorfuehrungService vorfuehrungService) {
this.vorfuehrungService = vorfuehrungService;
}
public List<Vorfuehrung> vorfuehrungen() {
return vorfuehrungService.vorfuehrungen();
}
public Vorfuehrung vorfuehrung(Identifiable.Id vorfuehrungId) {
return vorfuehrungService.vorfuehrung(vorfuehrungId);
}
}

View file

@ -0,0 +1,65 @@
package de.accso.flexinale.backoffice.application.services;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.dao.KinoSaalDao;
import de.accso.flexinale.backoffice.domain.dao.VorfuehrungDao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
@Transactional
public class VorfuehrungUploadService extends AbstractExcelDataUploadService<Vorfuehrung> {
private static final Logger LOGGER = LoggerFactory.getLogger(VorfuehrungUploadService.class);
private final FilmDao filmDao;
private final KinoSaalDao kinoSaalDao;
private final VorfuehrungDao vorfuehrungDao;
public VorfuehrungUploadService(final VorfuehrungDao vorfuehrungDao,
final FilmDao filmDao,
final KinoSaalDao kinoSaalDao) {
this.filmDao = filmDao;
this.kinoSaalDao = kinoSaalDao;
this.vorfuehrungDao = vorfuehrungDao;
}
@Override
public AbstractDao<Vorfuehrung> getDao() {
return vorfuehrungDao;
}
@Override
public String getNameOfExcelDataType() {
return Vorfuehrung.class.getSimpleName();
}
@Override
public Vorfuehrung createDataFromExcelRow(final Row excelRow) {
String vorfuehrungId = excelRow.getCell(0).getStringCellValue();
// expects date/time in format like for example 2023-02-20T17:45
LocalDateTime zeit = LocalDateTime.parse(excelRow.getCell(1).getStringCellValue());
String filmId = excelRow.getCell(2).getStringCellValue();
Film film = filmDao.findById(Identifiable.Id.of(filmId)).orElseThrow(IllegalStateException::new);
String kinoSaalId = excelRow.getCell(3).getStringCellValue();
KinoSaal kinoSaal = kinoSaalDao.findById(Identifiable.Id.of(kinoSaalId)).orElseThrow(IllegalStateException::new);
Vorfuehrung vorfuehrung = new Vorfuehrung(Identifiable.Id.of(vorfuehrungId), new Vorfuehrung.Zeit(zeit), film, kinoSaal);
LOGGER.debug("New Vorfuehrung created: " + vorfuehrung);
return vorfuehrung;
}
}

View file

@ -0,0 +1,16 @@
package de.accso.flexinale.backoffice.application.services.mapper;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class Film2FilmTOMapper {
public static FilmTO map(final Film film) {
return new FilmTO(film.id(), film.version(),
new FilmTO.Titel(getRawOrNull(film.titel)),
new FilmTO.ImdbUrl(getRawOrNull(film.imdbUrl)),
new FilmTO.DauerInMinuten(getRawOrNull(film.dauerInMinuten)));
}
}

View file

@ -0,0 +1,44 @@
package de.accso.flexinale.backoffice.application.services.mapper;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoSaalTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoTO;
import java.util.HashSet;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class KinoKinoSaal2KinoKinoSaalTOMapper {
public static KinoTO mapKino(final Kino kino) {
final KinoTO mappedKino = new KinoTO(kino.id(), kino.version(),
new KinoTO.Name(getRawOrNull(kino.name)),
new KinoTO.Adresse(getRawOrNull(kino.adresse)),
new KinoTO.EmailAdresse(getRawOrNull(kino.emailAdresse)),
new HashSet<>());
kino.kinoSaele.stream()
.map(kinoSaal -> mapKinoSaal(kinoSaal, mappedKino))
.forEach(kinoSaal -> mappedKino.kinoSaele().add(kinoSaal));
return mappedKino;
}
static KinoSaalTO mapKinoSaal(final KinoSaal kinoSaal) {
Kino kino = kinoSaal.kino;
KinoTO mappedKino = mapKino(kino);
return new KinoSaalTO(kinoSaal.id(), kino.version(),
new KinoSaalTO.Name(getRawOrNull(kinoSaal.name)),
new KinoSaalTO.AnzahlPlaetze(getRawOrNull(kinoSaal.anzahlPlaetze)),
mappedKino);
}
private static KinoSaalTO mapKinoSaal(final KinoSaal kinoSaal, final KinoTO kino) {
return new KinoSaalTO(kinoSaal.id(), kinoSaal.version(),
new KinoSaalTO.Name(getRawOrNull(kinoSaal.name)),
new KinoSaalTO.AnzahlPlaetze(getRawOrNull(kinoSaal.anzahlPlaetze)),
kino);
}
}

View file

@ -0,0 +1,20 @@
package de.accso.flexinale.backoffice.application.services.mapper;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoSaalTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class Vorfuehrung2VorfuehrungTOMapper {
public static VorfuehrungTO map(final Vorfuehrung vorfuehrung) {
FilmTO filmTO = Film2FilmTOMapper.map(vorfuehrung.film);
KinoSaalTO kinoSaalTO = KinoKinoSaal2KinoKinoSaalTOMapper.mapKinoSaal(vorfuehrung.kinoSaal);
return new VorfuehrungTO(vorfuehrung.id(), vorfuehrung.version(),
new VorfuehrungTO.Zeit(getRawOrNull(vorfuehrung.zeit)),
filmTO, kinoSaalTO);
}
}

View file

@ -0,0 +1,13 @@
package de.accso.flexinale.backoffice.domain.dao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.Optional;
public interface FilmDao extends AbstractDao<Film> {
@Override
Optional<Film> findById(final Identifiable.Id id);
}

View file

@ -0,0 +1,13 @@
package de.accso.flexinale.backoffice.domain.dao;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.Optional;
public interface KinoDao extends AbstractDao<Kino> {
@Override
Optional<Kino> findById(final Identifiable.Id id);
}

View file

@ -0,0 +1,13 @@
package de.accso.flexinale.backoffice.domain.dao;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.Optional;
public interface KinoSaalDao extends AbstractDao<KinoSaal> {
@Override
Optional<KinoSaal> findById(final Identifiable.Id id);
}

View file

@ -0,0 +1,16 @@
package de.accso.flexinale.backoffice.domain.dao;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
public interface VorfuehrungDao extends AbstractDao<Vorfuehrung> {
@Override
Optional<Vorfuehrung> findById(final Identifiable.Id id);
List<Vorfuehrung> findByFilmId(final Identifiable.Id filmId);
}

View file

@ -0,0 +1,92 @@
package de.accso.flexinale.backoffice.domain.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 org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Film implements Identifiable, Versionable, EqualsByContent {
public record Titel(String raw) implements RawWrapper<String> {}
public record ImdbUrl(String raw) implements RawWrapper<String> {}
public record DauerInMinuten(Integer raw) implements RawWrapper<Integer> {}
public final Id id;
public final Version version;
public Titel titel;
public ImdbUrl imdbUrl;
public DauerInMinuten dauerInMinuten;
public Film(final Id id) {
this(id, Versionable.unknownVersion());
}
public Film(final Id id, final Version version) {
this.id = id;
this.version = version;
}
public Film(final Id id,
final Titel titel, final ImdbUrl imdbUrl, final DauerInMinuten dauerInMinuten) {
this(id, Versionable.unknownVersion(), titel, imdbUrl, dauerInMinuten);
}
public Film(final Id id, final Version version,
final Titel titel, final ImdbUrl imdbUrl, final DauerInMinuten dauerInMinuten) {
this.id = id;
this.version = version;
this.titel = titel;
this.imdbUrl = imdbUrl;
this.dauerInMinuten = dauerInMinuten;
}
@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;}
final Film that = (Film) 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;}
final Film that = (Film) o;
return new EqualsBuilder()
.append(id, that.id)
.append(titel, that.titel).append(imdbUrl, that.imdbUrl)
.append(dauerInMinuten, that.dauerInMinuten).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(titel).append(imdbUrl).append(dauerInMinuten).toHashCode();
}
}

View file

@ -0,0 +1,121 @@
package de.accso.flexinale.backoffice.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 java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Kino implements Identifiable, Versionable, EqualsByContent {
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 final Id id;
public final Version version;
public Name name;
public Adresse adresse;
public EmailAdresse emailAdresse;
@DoNotCheckInArchitectureTests
public Set<KinoSaal> kinoSaele = new HashSet<>();
public Kino(final Id id) {
this(id, Versionable.unknownVersion());
}
public Kino(final Id id, final Version version) {
this.id = id;
this.version = version;
}
public Kino(final Id id,
final Name name, final Adresse adresse,
final EmailAdresse emailAdresse, final Set<KinoSaal> kinoSaele) {
this(id, Versionable.unknownVersion(), name, adresse, emailAdresse, kinoSaele);
}
public Kino(final Id id, final Version version,
final Name name, final Adresse adresse,
final EmailAdresse emailAdresse, final Set<KinoSaal> kinoSaele) {
this.id = id;
this.version = version;
this.name = name;
this.adresse = adresse;
this.emailAdresse = emailAdresse;
this.kinoSaele = kinoSaele;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
public void addSaele(Collection<KinoSaal> saele) {
kinoSaele.addAll(saele);
}
@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;}
Kino that = (Kino) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@SuppressWarnings("ConstantValue")
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Kino that = (Kino) 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<KinoSaal> thisKSList = kinoSaele.stream().toList();
List<KinoSaal> 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;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(name).append(adresse).append(emailAdresse)
.append(kinoSaele.stream().toList()) // need to use list, as Set does not support deep equals
.toHashCode();
}
}

View file

@ -0,0 +1,111 @@
package de.accso.flexinale.backoffice.domain.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;
public class KinoSaal implements Identifiable, Versionable, EqualsByContent {
public record Name(String raw) implements RawWrapper<String> {}
public record AnzahlPlaetze(Integer raw) implements RawWrapper<Integer> {}
public final Id id;
public final Version version;
public Name name;
public AnzahlPlaetze anzahlPlaetze = new AnzahlPlaetze(0);
@JsonIgnore
@DoNotCheckInArchitectureTests
public Kino kino;
public KinoSaal(final Id id) {
this(id, Versionable.unknownVersion());
}
public KinoSaal(final Id id, final Version version) {
this.id = id;
this.version = version;
}
public KinoSaal(final Id id,
final Name name, final AnzahlPlaetze anzahlPlaetze, final Kino kino) {
this(id, Versionable.unknownVersion(), name, anzahlPlaetze, kino);
}
public KinoSaal(final Id id, final Version version,
final Name name, final AnzahlPlaetze anzahlPlaetze, final Kino kino) {
this.id = id;
this.version = version;
this.name = name;
this.anzahlPlaetze = anzahlPlaetze;
this.kino = kino;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
@Override
public String toString() {
return "KinoSaal{" +
"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;}
KinoSaal that = (KinoSaal) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@SuppressWarnings("ConstantValue")
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KinoSaal that = (KinoSaal) 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;
}
@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();
}
}

View file

@ -0,0 +1,123 @@
package de.accso.flexinale.backoffice.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 java.time.LocalDateTime;
public final class Vorfuehrung implements Identifiable, Versionable, EqualsByContent { // TODO is only final as otherwise Spotbugs complains, get rid of Exception in constructor!
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public final Id id;
public final Version version;
public final Zeit zeit;
@DoNotCheckInArchitectureTests
public final Film film;
@DoNotCheckInArchitectureTests
public final KinoSaal kinoSaal;
public Vorfuehrung(final Id id,
final Zeit zeit,
final Film film,
final KinoSaal kinoSaal) {
this(id, Versionable.unknownVersion(), zeit, film, kinoSaal);
}
public Vorfuehrung(final Id id, final Version version,
final Zeit zeit,
final Film film,
final KinoSaal kinoSaal) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
//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");
}
}
@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;}
Vorfuehrung that = (Vorfuehrung) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@SuppressWarnings({"UnusedAssignment", "DataFlowIssue"})
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Vorfuehrung that = (Vorfuehrung) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(zeit, that.zeit)
.isEquals();
if (!result) return false;
if (film == null && that.film == null) result = true;
if (film == null && that.film != null) return false;
if (film != null) {
result = film.equalsByContent(that.film);
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;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(zeit)
.append(film)
.append(kinoSaal)
.toHashCode();
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.backoffice.domain.services;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public class FilmService {
private final FilmDao filmDao;
public FilmService(final FilmDao filmDao) {
this.filmDao = filmDao;
}
public List<Film> filme() {
return filmDao.findAll();
}
public Film film(final Identifiable.Id id) {
return filmDao.findById(id).orElse(null);
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.backoffice.domain.services;
import de.accso.flexinale.backoffice.domain.dao.KinoDao;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public class KinoService {
private final KinoDao kinoDao;
public KinoService(final KinoDao kinoDao) {
this.kinoDao = kinoDao;
}
public List<Kino> kinos() {
return kinoDao.findAll();
}
public Kino kino(final Identifiable.Id id) {
return kinoDao.findById(id).orElse(null);
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.backoffice.domain.services;
import de.accso.flexinale.backoffice.domain.dao.VorfuehrungDao;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public class VorfuehrungService {
private final VorfuehrungDao vorfuehrungDao;
public VorfuehrungService(final VorfuehrungDao vorfuehrungDao) {
this.vorfuehrungDao = vorfuehrungDao;
}
public List<Vorfuehrung> vorfuehrungen() {
return vorfuehrungDao.findAll();
}
public Vorfuehrung vorfuehrung(final Identifiable.Id id) {
return vorfuehrungDao.findById(id).orElse(null);
}
}

View file

@ -0,0 +1,125 @@
package de.accso.flexinale.backoffice.infrastructure;
import de.accso.flexinale.backoffice.application.services.FilmService;
import de.accso.flexinale.backoffice.application.services.KinoService;
import de.accso.flexinale.backoffice.application.services.VorfuehrungService;
import de.accso.flexinale.backoffice.application.services.FilmUploadService;
import de.accso.flexinale.backoffice.application.services.KinoUploadService;
import de.accso.flexinale.backoffice.application.services.KinoSaalUploadService;
import de.accso.flexinale.backoffice.application.services.VorfuehrungUploadService;
import de.accso.flexinale.backoffice.application.services.FilmRetrieverService;
import de.accso.flexinale.backoffice.application.services.KinoRetrieverService;
import de.accso.flexinale.backoffice.application.services.VorfuehrungRetrieverService;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.dao.KinoDao;
import de.accso.flexinale.backoffice.domain.dao.KinoSaalDao;
import de.accso.flexinale.backoffice.domain.dao.VorfuehrungDao;
import de.accso.flexinale.backoffice.infrastructure.persistence.*;
import de.accso.flexinale.backoffice_api_contract.api_contract.FilmRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.KinoRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableJpaRepositories({"de.accso.flexinale.backoffice.infrastructure.persistence"})
@EnableTransactionManagement
@EntityScan(basePackages={"de.accso.flexinale.backoffice.infrastructure.persistence"})
public class FlexinaleBackofficeSpringFactory {
@Bean
public de.accso.flexinale.backoffice.domain.services.FilmService createBackofficeFilmService(final FilmDao filmDao) {
return new de.accso.flexinale.backoffice.domain.services.FilmService(filmDao);
}
@Bean
public FilmService createApplicationFilmService(final de.accso.flexinale.backoffice.domain.services.FilmService filmService){
return new FilmService(filmService);
}
@Bean
public FilmUploadService createBackofficeFilmUploadService(final FilmDao filmDao) {
return new FilmUploadService(filmDao);
}
@Bean
public FilmDao createBackofficeFilmDao(final FilmJpaRepository filmJpaRepository) {
return new FilmJpaRepositoryDelegate(filmJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public de.accso.flexinale.backoffice.domain.services.KinoService createBackofficeKinoService(final KinoDao KinoDao) {
return new de.accso.flexinale.backoffice.domain.services.KinoService(KinoDao);
}
@Bean
public KinoService createApplicationKinoService(final de.accso.flexinale.backoffice.domain.services.KinoService kinoService){
return new KinoService(kinoService);
}
@Bean
public KinoUploadService createBackofficeKinoUploadService(final KinoDao kinoDao) {
return new KinoUploadService(kinoDao);
}
@Bean
public KinoSaalUploadService createBackofficeKinoSaalUploadService(final KinoSaalDao kinoSaalDao) {
return new KinoSaalUploadService(kinoSaalDao);
}
@Bean
public KinoDao createBackofficeKinoDao(final KinoJpaRepository kinoJpaRepository) {
return new KinoJpaRepositoryDelegate(kinoJpaRepository);
}
@Bean
public KinoSaalDao createBackofficeKinoSaalDao(final KinoSaalJpaRepository kinoSaalJpaRepository) {
return new KinoSaalJpaRepositoryDelegate(kinoSaalJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public de.accso.flexinale.backoffice.domain.services.VorfuehrungService createBackofficeVorfuehrungService(final VorfuehrungDao vorfuehrungDao) {
return new de.accso.flexinale.backoffice.domain.services.VorfuehrungService(vorfuehrungDao);
}
@Bean
public VorfuehrungService createApplicationVorfuehrungService(final de.accso.flexinale.backoffice.domain.services.VorfuehrungService vorfuehrungService) {
return new VorfuehrungService(vorfuehrungService);
}
@Bean
public VorfuehrungUploadService createBackofficeVorfuehrungUploadService(final VorfuehrungDao vorfuehrungDao,
final FilmDao filmDao,
final KinoSaalDao kinoSaalDao) {
return new VorfuehrungUploadService(vorfuehrungDao, filmDao, kinoSaalDao);
}
@Bean
public VorfuehrungDao createBackofficeVorfuehrungDao(final VorfuehrungJpaRepository vorfuehrungJpaRepository) {
return new VorfuehrungJpaRepositoryDelegate(vorfuehrungJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public FilmRetriever createBackofficeFilmRetriever(final FilmDao filmDao) {
return new FilmRetrieverService(filmDao);
}
@Bean
public KinoRetriever createBackofficeKinoRetriever(final KinoDao kinoDao) {
return new KinoRetrieverService(kinoDao);
}
@Bean
public VorfuehrungRetriever createBackofficeVorfuehrungRetriever(final VorfuehrungDao vorfuehrungDao) {
return new VorfuehrungRetrieverService(vorfuehrungDao);
}
}

View file

@ -0,0 +1,78 @@
package de.accso.flexinale.backoffice.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 = "Film")
public class FilmEntity implements Identifiable, Versionable, Serializable {
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column
public String titel;
@jakarta.persistence.Column
public String imdbUrl;
@jakarta.persistence.Column
public Integer dauerInMinuten;
@jakarta.persistence.Version
private Integer version = 0;
protected FilmEntity() {
}
public FilmEntity(final String id, final Integer version,
final String titel, final String imdbUrl, final Integer dauerInMinuten) {
this.id = id;
this.version = version;
this.titel = titel;
this.imdbUrl = imdbUrl;
this.dauerInMinuten = dauerInMinuten;
}
@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;}
FilmEntity that = (FilmEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(titel, that.titel)
.append(imdbUrl, that.imdbUrl)
.append(dauerInMinuten, that.dauerInMinuten)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(titel).append(imdbUrl).append(dauerInMinuten).toHashCode();
}
}

View file

@ -0,0 +1,8 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface FilmJpaRepository extends JpaRepository<FilmEntity, String> {
}

View file

@ -0,0 +1,59 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.infrastructure.persistence.mapper.FilmEntity2FilmMapper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class FilmJpaRepositoryDelegate implements FilmDao {
private final FilmJpaRepository filmJpaRepository;
public FilmJpaRepositoryDelegate(FilmJpaRepository filmJpaRepository) {
this.filmJpaRepository = filmJpaRepository;
}
@Override
public List<Film> findAll() {
List<FilmEntity> filmEntities = filmJpaRepository.findAll();
return filmEntities.stream()
.map(FilmEntity2FilmMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public Optional<Film> findById(final Identifiable.Id id) {
Optional<FilmEntity> filmEntity = filmJpaRepository.findById(id.id());
return FilmEntity2FilmMapper.map(filmEntity);
}
@Override
public Film save(final Film film) {
FilmEntity filmEntity = FilmEntity2FilmMapper.map(film);
FilmEntity savedEntity = filmJpaRepository.save(filmEntity);
return FilmEntity2FilmMapper.map(savedEntity);
}
@Override
public void delete(final Film film) {
FilmEntity filmEntity = FilmEntity2FilmMapper.map(film);
filmJpaRepository.delete(filmEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
filmJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
filmJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,89 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import jakarta.persistence.CascadeType;
import jakarta.persistence.FetchType;
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;
import java.util.HashSet;
import java.util.Set;
@jakarta.persistence.Entity(name = "Kino")
public class KinoEntity implements Identifiable, Versionable, Serializable {
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column
public String name;
@jakarta.persistence.Column
public String adresse;
@jakarta.persistence.Column
public String emailAdresse;
@jakarta.persistence.Version
private Integer version = 0;
@jakarta.persistence.OneToMany(mappedBy = "kino", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<KinoSaalEntity> kinoSaele = new HashSet<>();
protected KinoEntity() {
}
public KinoEntity(final String id, final Integer version,
final String name, final String adresse, final String emailAdresse) {
this.id = id;
this.version = version;
this.name = name;
this.adresse = adresse;
this.emailAdresse = emailAdresse;
}
@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;}
KinoEntity that = (KinoEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(name, that.name)
.append(adresse, that.adresse)
.append(emailAdresse, that.emailAdresse)
.append(kinoSaele.stream().toList(), that.kinoSaele.stream().toList()) // need to use list, as Set does not support deep equals
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(name).append(adresse)
.append(emailAdresse)
.append(kinoSaele.stream().toList()) // need to use list, as Set does not support deep equals
.toHashCode();
}
}

View file

@ -0,0 +1,8 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface KinoJpaRepository extends JpaRepository<KinoEntity, String> {
}

View file

@ -0,0 +1,59 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.backoffice.domain.dao.KinoDao;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.infrastructure.persistence.mapper.KinoKinoSaalEntity2KinoKinoSaalMapper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class KinoJpaRepositoryDelegate implements KinoDao {
private final KinoJpaRepository kinoJpaRepository;
public KinoJpaRepositoryDelegate(KinoJpaRepository KinoJpaRepository) {
this.kinoJpaRepository = KinoJpaRepository;
}
@Override
public List<Kino> findAll() {
List<KinoEntity> kinoEntities = kinoJpaRepository.findAll();
return kinoEntities.stream()
.map(KinoKinoSaalEntity2KinoKinoSaalMapper::mapKino)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public Optional<Kino> findById(final Identifiable.Id id) {
Optional<KinoEntity> kinoEntity = kinoJpaRepository.findById(id.id());
return KinoKinoSaalEntity2KinoKinoSaalMapper.mapKino(kinoEntity);
}
@Override
public Kino save(final Kino kino) {
KinoEntity kinoEntity = KinoKinoSaalEntity2KinoKinoSaalMapper.mapKino(kino);
KinoEntity savedEntity = kinoJpaRepository.save(kinoEntity);
return KinoKinoSaalEntity2KinoKinoSaalMapper.mapKino(savedEntity);
}
@Override
public void delete(final Kino kino) {
KinoEntity kinoEntity = KinoKinoSaalEntity2KinoKinoSaalMapper.mapKino(kino);
kinoJpaRepository.delete(kinoEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
kinoJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
kinoJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,88 @@
package de.accso.flexinale.backoffice.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 java.io.Serializable;
@jakarta.persistence.Entity(name = "KinoSaal")
public class KinoSaalEntity implements Identifiable, Versionable, Serializable {
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column
public String name;
@jakarta.persistence.Column
public Integer anzahlPlaetze = 0;
@jakarta.persistence.ManyToOne
@jakarta.persistence.JoinColumn(name = "kino", nullable = false)
public KinoEntity kino;
@jakarta.persistence.Version
private Integer version = 0;
protected KinoSaalEntity() {
}
public KinoSaalEntity(final String id, final Integer version,
final String name, final Integer anzahlPlaetze, final KinoEntity kino) {
this.id = id;
this.version = version;
this.name = name;
this.anzahlPlaetze = anzahlPlaetze;
this.kino = kino;
}
@Override
public Id id() {
return Identifiable.Id.of(id);
}
@Override
public Version version() {
return Versionable.Version.of(version);
}
@Override
public String toString() {
return "KinoSaalEntity{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", anzahlPlaetze=" + anzahlPlaetze +
(kino != null ? ", kino.id=" + kino.id : ", kino=null") +
", version=" + version +
'}';
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KinoSaalEntity that = (KinoSaalEntity) o;
EqualsBuilder builder = new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(name, that.name).append(anzahlPlaetze, that.anzahlPlaetze);
if (kino != null) {
builder.append(kino.id, that.kino.id); // do not check kino but only its id (otherwise Stackoverflow error)
}
return builder.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 check kino but only its id (otherwise Stackoverflow error)
}
return builder.toHashCode();
}
}

View file

@ -0,0 +1,8 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface KinoSaalJpaRepository extends JpaRepository<KinoSaalEntity, String> {
}

View file

@ -0,0 +1,58 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.backoffice.domain.dao.KinoSaalDao;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.infrastructure.persistence.mapper.KinoKinoSaalEntity2KinoKinoSaalMapper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class KinoSaalJpaRepositoryDelegate implements KinoSaalDao {
private final KinoSaalJpaRepository kinoSaalJpaRepository;
public KinoSaalJpaRepositoryDelegate(final KinoSaalJpaRepository kinoSaalJpaRepository) {
this.kinoSaalJpaRepository = kinoSaalJpaRepository;
}
@Override
public List<KinoSaal> findAll() {
List<KinoSaalEntity> kinoSaalEntities = kinoSaalJpaRepository.findAll();
return kinoSaalEntities.stream()
.map(KinoKinoSaalEntity2KinoKinoSaalMapper::mapKinoSaal)
.collect(Collectors.toList());
}
@Override
public Optional<KinoSaal> findById(final Identifiable.Id id) {
Optional<KinoSaalEntity> kinoSaalEntity = kinoSaalJpaRepository.findById(id.id());
return KinoKinoSaalEntity2KinoKinoSaalMapper.mapToKinoSaal(kinoSaalEntity);
}
@Override
public KinoSaal save(final KinoSaal kinoSaal) {
KinoSaalEntity kinoSaalEntity = KinoKinoSaalEntity2KinoKinoSaalMapper.mapKinoSaal(kinoSaal);
KinoSaalEntity savedEntity = kinoSaalJpaRepository.save(kinoSaalEntity);
return KinoKinoSaalEntity2KinoKinoSaalMapper.mapKinoSaal(savedEntity);
}
@Override
public void delete(final KinoSaal kinoSaal) {
KinoSaalEntity kinoSaalEntity = KinoKinoSaalEntity2KinoKinoSaalMapper.mapKinoSaal(kinoSaal);
kinoSaalJpaRepository.delete(kinoSaalEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
kinoSaalJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
kinoSaalJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,96 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.DateTimeHelper;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalArgumentException;
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;
import java.time.LocalDateTime;
@jakarta.persistence.Entity(name = "Vorfuehrung")
public final class VorfuehrungEntity implements Identifiable, Versionable, Serializable { // TODO is only final as otherwise Spotbugs complains, get rid of Exception in constructor!
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column
public Long zeit; // save EPOCH seconds instead of Date/Time (Timezone is UTC)
@jakarta.persistence.ManyToOne
public FilmEntity film;
@jakarta.persistence.ManyToOne
public KinoSaalEntity kinoSaal;
@jakarta.persistence.Version
private Integer version = 0;
protected VorfuehrungEntity() {
}
public VorfuehrungEntity(final String id, final Integer version,
final LocalDateTime zeit,
final FilmEntity film,
final KinoSaalEntity kinoSaal) {
this.id = id;
this.version = version;
this.zeit = DateTimeHelper.toEpochSeconds(zeit);
this.film = film;
this.kinoSaal = kinoSaal;
//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");
}
}
@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;}
VorfuehrungEntity that = (VorfuehrungEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(zeit, that.zeit)
.append(film, that.film).append(kinoSaal, that.kinoSaal)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(zeit).append(film).append(kinoSaal)
.toHashCode();
}
}

View file

@ -0,0 +1,14 @@
package de.accso.flexinale.backoffice.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 VorfuehrungJpaRepository extends JpaRepository<VorfuehrungEntity, String> {
@Query("SELECT v FROM Vorfuehrung v WHERE v.film.id = :filmId ORDER BY v.zeit")
List<VorfuehrungEntity> findByFilmId(final String filmId);
}

View file

@ -0,0 +1,65 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.backoffice.domain.dao.VorfuehrungDao;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice.infrastructure.persistence.mapper.VorfuehrungEntity2VorfuehrungMapper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class VorfuehrungJpaRepositoryDelegate implements VorfuehrungDao {
private final VorfuehrungJpaRepository vorfuehrungJpaRepository;
public VorfuehrungJpaRepositoryDelegate(VorfuehrungJpaRepository vorfuehrungJpaRepository) {
this.vorfuehrungJpaRepository = vorfuehrungJpaRepository;
}
@Override
public List<Vorfuehrung> findAll() {
List<VorfuehrungEntity> vorfuehrungEntities = vorfuehrungJpaRepository.findAll();
return vorfuehrungEntities.stream()
.map(VorfuehrungEntity2VorfuehrungMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public Optional<Vorfuehrung> findById(final Identifiable.Id id) {
Optional<VorfuehrungEntity> vorfuehrungEntity = vorfuehrungJpaRepository.findById(id.id());
return VorfuehrungEntity2VorfuehrungMapper.map(vorfuehrungEntity);
}
@Override
public List<Vorfuehrung> findByFilmId(final Identifiable.Id filmId) {
List<VorfuehrungEntity> vorfuehrungEntities = vorfuehrungJpaRepository.findByFilmId(filmId.id());
return VorfuehrungEntity2VorfuehrungMapper.map(vorfuehrungEntities);
}
@Override
public Vorfuehrung save(final Vorfuehrung vorfuehrung) {
VorfuehrungEntity vorfuehrungEntity = VorfuehrungEntity2VorfuehrungMapper.map(vorfuehrung);
VorfuehrungEntity savedEntity = vorfuehrungJpaRepository.save(vorfuehrungEntity);
return VorfuehrungEntity2VorfuehrungMapper.map(savedEntity);
}
@Override
public void delete(final Vorfuehrung vorfuehrung) {
VorfuehrungEntity vorfuehrungEntity = VorfuehrungEntity2VorfuehrungMapper.map(vorfuehrung);
vorfuehrungJpaRepository.delete(vorfuehrungEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
vorfuehrungJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
vorfuehrungJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,34 @@
package de.accso.flexinale.backoffice.infrastructure.persistence.mapper;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.infrastructure.persistence.FilmEntity;
import java.util.Optional;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class FilmEntity2FilmMapper {
public static Film map(final FilmEntity filmEntity) {
return new Film(filmEntity.id(), filmEntity.version(),
new Film.Titel(filmEntity.titel),
new Film.ImdbUrl(filmEntity.imdbUrl),
new Film.DauerInMinuten(filmEntity.dauerInMinuten));
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<Film> map(final Optional<FilmEntity> optionalFilmEntity) {
if (optionalFilmEntity.isEmpty()) {
return Optional.empty();
}
else {
FilmEntity filmEntity = optionalFilmEntity.get();
return Optional.of(map(filmEntity));
}
}
public static FilmEntity map(final Film film) {
return new FilmEntity(film.id().id(), film.version().version(),
getRawOrNull(film.titel), getRawOrNull(film.imdbUrl), getRawOrNull(film.dauerInMinuten));
}
}

View file

@ -0,0 +1,86 @@
package de.accso.flexinale.backoffice.infrastructure.persistence.mapper;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.infrastructure.persistence.KinoEntity;
import de.accso.flexinale.backoffice.infrastructure.persistence.KinoSaalEntity;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class KinoKinoSaalEntity2KinoKinoSaalMapper {
public static Kino mapKino(final KinoEntity kinoEntity) {
final Kino kino = new Kino(kinoEntity.id(), kinoEntity.version());
Set<KinoSaal> mappedKinoSaele = kinoEntity.kinoSaele.stream()
.map(kinoSaalEntity -> mapKinoSaal(kinoSaalEntity, kino))
.collect(Collectors.toCollection(HashSet::new));
kino.name = new Kino.Name(kinoEntity.name);
kino.adresse = new Kino.Adresse(kinoEntity.adresse);
kino.emailAdresse = new Kino.EmailAdresse(kinoEntity.emailAdresse);
kino.kinoSaele = mappedKinoSaele;
return kino;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<Kino> mapKino(final Optional<KinoEntity> optionalKinoEntity) {
if (optionalKinoEntity.isEmpty()) {
return Optional.empty();
}
else {
KinoEntity kinoEntity = optionalKinoEntity.get();
return Optional.of(mapKino(kinoEntity));
}
}
public static KinoEntity mapKino(final Kino kino) {
final KinoEntity kinoEntity = new KinoEntity(kino.id().id(), kino.version().version(),
getRawOrNull(kino.name), getRawOrNull(kino.adresse), getRawOrNull(kino.emailAdresse));
kinoEntity.kinoSaele = kino.kinoSaele.stream()
.map(kinoSaal -> mapKinoSaal(kinoSaal, kinoEntity))
.collect(Collectors.toCollection(HashSet::new));
return kinoEntity;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<KinoSaal> mapToKinoSaal(final Optional<KinoSaalEntity> kinoSaalEntity) {
return kinoSaalEntity.map(KinoKinoSaalEntity2KinoKinoSaalMapper::mapKinoSaal);
}
public static KinoSaal mapKinoSaal(final KinoSaalEntity kinoSaalEntity) {
KinoEntity kinoEntity = kinoSaalEntity.kino;
Kino mappedKino = mapKino(kinoEntity);
return new KinoSaal(kinoSaalEntity.id(), kinoEntity.version(),
new KinoSaal.Name(kinoSaalEntity.name), new KinoSaal.AnzahlPlaetze(kinoSaalEntity.anzahlPlaetze), mappedKino);
}
private static KinoSaal mapKinoSaal(final KinoSaalEntity kinoSaalEntity, final Kino kino) {
return new KinoSaal(kinoSaalEntity.id(), kinoSaalEntity.version(),
new KinoSaal.Name(kinoSaalEntity.name), new KinoSaal.AnzahlPlaetze(kinoSaalEntity.anzahlPlaetze), kino);
}
@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "unused"})
public static Optional<KinoSaalEntity> mapFromKinoSaal(final Optional<KinoSaal> kinoSaal) {
return kinoSaal.map(KinoKinoSaalEntity2KinoKinoSaalMapper::mapKinoSaal);
}
public static KinoSaalEntity mapKinoSaal(final KinoSaal kinoSaal) {
Kino kino = kinoSaal.kino;
KinoEntity mappedKino = mapKino(kino);
return new KinoSaalEntity(kinoSaal.id().id(), kinoSaal.version().version(),
getRawOrNull(kinoSaal.name), getRawOrNull(kinoSaal.anzahlPlaetze), mappedKino);
}
private static KinoSaalEntity mapKinoSaal(final KinoSaal kinoSaal, final KinoEntity kinoEntity) {
return new KinoSaalEntity(kinoSaal.id().id(), kinoEntity.version().version(),
getRawOrNull(kinoSaal.name), getRawOrNull(kinoSaal.anzahlPlaetze), kinoEntity);
}
}

View file

@ -0,0 +1,52 @@
package de.accso.flexinale.backoffice.infrastructure.persistence.mapper;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice.infrastructure.persistence.FilmEntity;
import de.accso.flexinale.backoffice.infrastructure.persistence.KinoSaalEntity;
import de.accso.flexinale.backoffice.infrastructure.persistence.VorfuehrungEntity;
import de.accso.flexinale.common.shared_kernel.DateTimeHelper;
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 class VorfuehrungEntity2VorfuehrungMapper {
public static Vorfuehrung map(final VorfuehrungEntity vorfuehrungEntity) {
Film mappedFilm = FilmEntity2FilmMapper.map(vorfuehrungEntity.film);
KinoSaal mappedKinoSaal = KinoKinoSaalEntity2KinoKinoSaalMapper.mapKinoSaal(vorfuehrungEntity.kinoSaal);
return new Vorfuehrung(vorfuehrungEntity.id(), vorfuehrungEntity.version(),
new Vorfuehrung.Zeit(DateTimeHelper.fromEpochSeconds(vorfuehrungEntity.zeit)),
mappedFilm, mappedKinoSaal);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<Vorfuehrung> map(final Optional<VorfuehrungEntity> optionalVorfuehrungEntity) {
if (optionalVorfuehrungEntity.isEmpty()) {
return Optional.empty();
}
else {
VorfuehrungEntity vorfuehrungEntity = optionalVorfuehrungEntity.get();
return Optional.of(map(vorfuehrungEntity));
}
}
public static VorfuehrungEntity map(final Vorfuehrung vorfuehrung) {
FilmEntity mappedFilm = FilmEntity2FilmMapper.map(vorfuehrung.film);
KinoSaalEntity mappedKinoSaal = KinoKinoSaalEntity2KinoKinoSaalMapper.mapKinoSaal(vorfuehrung.kinoSaal);
return new VorfuehrungEntity(vorfuehrung.id().id(), vorfuehrung.version().version(),
getRawOrNull(vorfuehrung.zeit), mappedFilm, mappedKinoSaal);
}
public static List<Vorfuehrung> map(final List<VorfuehrungEntity> vorfuehrungEntities) {
return vorfuehrungEntities.stream().map(VorfuehrungEntity2VorfuehrungMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,11 @@
package de.accso.flexinale.backoffice_api_contract.api_contract;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public interface FilmRetriever {
FilmTO film(final Identifiable.Id id);
List<FilmTO> filme();
}

View file

@ -0,0 +1,11 @@
package de.accso.flexinale.backoffice_api_contract.api_contract;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public interface KinoRetriever{
KinoTO kino(final Identifiable.Id id);
List<KinoTO> kinos();
}

View file

@ -0,0 +1,11 @@
package de.accso.flexinale.backoffice_api_contract.api_contract;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public interface VorfuehrungRetriever {
VorfuehrungTO vorfuehrung(final Identifiable.Id id);
List<VorfuehrungTO> vorfuehrungenByFilmId(final Identifiable.Id filmId);
}

View file

@ -0,0 +1,33 @@
package de.accso.flexinale.backoffice_api_contract.api_contract.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,76 @@
package de.accso.flexinale.backoffice_api_contract.api_contract.model;
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,
@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) { // though KinoSaalTO is a record, this method is needed
// (as otherwise stackoverflow error because of the bidirectional
// relationship with KinoTO
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() { // though KinoSaalTO is a record, this method is needed
// (as otherwise stackoverflow error because of the bidirectional
// relationship with KinoTO
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,60 @@
package de.accso.flexinale.backoffice_api_contract.api_contract.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,68 @@
package de.accso.flexinale.backoffice_api_contract.api_contract.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)
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) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
//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");
}
}
@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)
.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,98 @@
package de.accso.flexinale.besucherportal.api_in.web;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoSaalTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.besucherportal.application.services.FilmService;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.security_api_contract.api_contract.BesucherRetriever;
import de.accso.flexinale.ticketing_api_contract.api_contract.Ticketing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
@Controller
public class FilmWebController {
private record VorfuehrungMitRestkontingentTO(Identifiable.Id vorfuehrungId, FilmTO film, Zeit zeit,
KinoSaalTO kinoSaal, RestKontingent restkontingentOnline) {
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public record RestKontingent(Integer raw) implements RawWrapper<Integer> {}
}
@Autowired
private FilmService filmService;
@Autowired
private BesucherRetriever besucherRetriever;
@Autowired
private Ticketing ticketing;
@GetMapping(value="/filme")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
@SuppressWarnings("SameReturnValue")
public String filme(final Model model) {
List<FilmTO> filme = filmService.filme();
model.addAttribute("filme", filme);
return "filme";
}
@GetMapping(value="/film/{id}")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
@SuppressWarnings("SameReturnValue")
public String film(@PathVariable("id") final String id, final Model model) {
Identifiable.Id filmId = Identifiable.Id.of(id);
FilmTO film = filmService.film(filmId);
if (film == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "film %s not available".formatted(id));
}
else {
model.addAttribute("film", film);
List<VorfuehrungTO> vorfuehrungenOfFilm = filmService.vorfuehrungenFuer(filmId);
List<VorfuehrungMitRestkontingentTO> vorfuehrungenMitRestkontingent = vorfuehrungenOfFilm.stream()
.map(v -> getRestkontingentOnlineAndMap(v, film)).collect(Collectors.toList());
model.addAttribute("vorfuehrungenMitRestkontingent", vorfuehrungenMitRestkontingent);
if (!vorfuehrungenOfFilm.isEmpty()) {
Identifiable.Id idOfLoggedInBesucher = besucherRetriever.getIdOfLoggedInBesucher();
List<String> vorfuehrungenMitUeberlapp =
filmService.vorfuehrungenMitUeberlapp(vorfuehrungenOfFilm, idOfLoggedInBesucher);
model.addAttribute("vorfuehrungenMitUeberlapp", vorfuehrungenMitUeberlapp);
List<String> vorfuehrungenMitTicket =
filmService.vorfuehrungenFuerDieDerBenutzerEinTicketHat(vorfuehrungenOfFilm, idOfLoggedInBesucher);
model.addAttribute("vorfuehrungenMitTicket", vorfuehrungenMitTicket);
}
}
return "film";
}
private VorfuehrungMitRestkontingentTO getRestkontingentOnlineAndMap(final VorfuehrungTO vorfuehrung, final FilmTO film) {
VorfuehrungMitRestkontingentTO.RestKontingent restKontingentOnline =
new VorfuehrungMitRestkontingentTO.RestKontingent(ticketing.getRestkontingentOnline(vorfuehrung.id()));
return new VorfuehrungMitRestkontingentTO(vorfuehrung.id(), film,
new VorfuehrungMitRestkontingentTO.Zeit(getRawOrNull(vorfuehrung.zeit())),
vorfuehrung.kinoSaal(), restKontingentOnline);
}
}

View file

@ -0,0 +1,23 @@
package de.accso.flexinale.besucherportal.api_in.web;
import de.accso.flexinale.common.application.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexWebController {
@Autowired
private Config config;
@GetMapping(value="/")
@SuppressWarnings("SameReturnValue")
public String index(final Model model) {
model.addAttribute("applicationTitle", config.getApplicationTitle());
model.addAttribute("buildVersion", config.getBuildVersion());
model.addAttribute("buildDate", config.getBuildDate());
return "index";
}
}

View file

@ -0,0 +1,46 @@
package de.accso.flexinale.besucherportal.api_in.web;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoTO;
import de.accso.flexinale.besucherportal.application.services.KinoService;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Controller
public class KinoWebController {
@Autowired
private KinoService kinoService;
@GetMapping(value="/kinos")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
@SuppressWarnings("SameReturnValue")
public String kinos(final Model model) {
List<KinoTO> kinos = kinoService.kinos();
model.addAttribute("kinos", kinos);
return "kinos";
}
@GetMapping(value="/kino/{id}")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
@SuppressWarnings("SameReturnValue")
public String kino(@PathVariable("id") final String id, final Model model) {
Identifiable.Id kinoId = Identifiable.Id.of(id);
KinoTO kino = kinoService.kino(kinoId);
if (kino == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "kino %s not available".formatted(id));
}
else {
model.addAttribute("kino", kino);
return "kino";
}
}
}

View file

@ -0,0 +1,132 @@
package de.accso.flexinale.besucherportal.api_in.web;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.ticketing_api_contract.api_contract.model.TicketTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
class TicketSorter {
private static final Logger LOGGER = LoggerFactory.getLogger(TicketSorter.class);
/**
* return a list of list of tickets, sorted by time (inner list) and date (outer list).
* each list contains the tickets for a day
*/
public static List<List<VorfuehrungMitAnzahlTicketsTO>> sortAndMapTicketsPerDay(final List<TicketTO> allTickets,
final VorfuehrungRetriever vorfuehrungRetriever) {
if (allTickets == null || allTickets.isEmpty()) {
return new ArrayList<>();
}
Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung = createTicketToVorfuehrungMap(allTickets, vorfuehrungRetriever);
// 0) sort tickets by time (not done in database SQL query as before)
allTickets.sort(new TicketTOByZeitSortingComparator(ticketToVorfuehrung));
// 1) tickets by day
List<List<TicketTO>> ticketsProTag = new ArrayList<>();
LocalDate previousDate = LocalDate.MIN; // far past :-)
for (TicketTO ticket : allTickets) {
LocalDateTime ticketZeit = getRawOrNull(ticketToVorfuehrung.get(ticket).zeit());
LocalDate currentDate = ticketZeit.toLocalDate();
if (currentDate.equals(previousDate)) {
ticketsProTag.getLast().add(ticket);
}
else {
List<TicketTO> tickets = new ArrayList<>();
tickets.add(ticket);
ticketsProTag.add(tickets);
}
previousDate = currentDate;
}
// 2) inner structure: Vorfuehrung and tickets
List<List<VorfuehrungMitAnzahlTicketsTO>> vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag = new ArrayList<>();
for (List<TicketTO> ticketsForOneDay : ticketsProTag) {
List<VorfuehrungMitAnzahlTicketsTO> vorfuehrungenUndAnzahlTicketsFuerEinenTag = new ArrayList<>();
int anzahlTicketsFuerVorfuehrung = 0;
VorfuehrungTO previousVorfuehrung = ticketToVorfuehrung.get(ticketsForOneDay.getFirst());
for (TicketTO ticket : ticketsForOneDay) {
// inside each day, vorfuehrungen are ordered by zeit.
// There is never more than one vorfuehrung at a zeit for a user.
// So tickets for the same vorfuehrung are all in a row - that's why the following works.
VorfuehrungTO currentVorfuehrung = ticketToVorfuehrung.get(ticket);
if (currentVorfuehrung.equals(previousVorfuehrung)) {
anzahlTicketsFuerVorfuehrung++;
}
else {
// add the information for previous Vorfuehrung
VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
anzahlTicketsFuerVorfuehrung, previousVorfuehrung);
vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
anzahlTicketsFuerVorfuehrung = 1; // ... we also have a new ticket for the next Vorfuehrung
}
previousVorfuehrung = currentVorfuehrung;
}
// Also add the last Vorfuehrung an anzahlTickets when day is over
VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
anzahlTicketsFuerVorfuehrung, previousVorfuehrung);
vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag.add(vorfuehrungenUndAnzahlTicketsFuerEinenTag);
}
return vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag;
}
private static VorfuehrungMitAnzahlTicketsTO mapVorfuehrungToTO(final int anzahlTicketsFuerVorfuehrung,
final VorfuehrungTO vorfuehrung) {
return new VorfuehrungMitAnzahlTicketsTO(
new VorfuehrungMitAnzahlTicketsTO.Zeit(getRawOrNull(vorfuehrung.zeit())),
vorfuehrung.film(),
vorfuehrung.kinoSaal(),
new VorfuehrungMitAnzahlTicketsTO.AnzahlTickets(anzahlTicketsFuerVorfuehrung)
);
}
private static Map<TicketTO, VorfuehrungTO> createTicketToVorfuehrungMap(final List<TicketTO> allTickets, final VorfuehrungRetriever vorfuehrungRetriever) {
Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung = new HashMap<>();
for (TicketTO ticket : allTickets) {
VorfuehrungTO vorfuehrung = vorfuehrungRetriever.vorfuehrung(ticket.vorfuehrungId());
if (vorfuehrung == null) {
LOGGER.error("Vorfuehrung %s of ticket %s could not be found.".formatted(ticket.vorfuehrungId(), ticket.id()));
}
else {
ticketToVorfuehrung.put(ticket, vorfuehrung);
}
}
return ticketToVorfuehrung;
}
}
class TicketTOByZeitSortingComparator implements Comparator<TicketTO>, Serializable {
private final Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung;
TicketTOByZeitSortingComparator(final Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung) {
this.ticketToVorfuehrung = ticketToVorfuehrung;
}
@Override
public int compare(final TicketTO t1, final TicketTO t2) {
VorfuehrungTO v1 = ticketToVorfuehrung.get(t1);
VorfuehrungTO v2 = ticketToVorfuehrung.get(t2);
LocalDateTime v1Zeit = getRawOrNull(v1.zeit());
LocalDateTime v2Zeit = getRawOrNull(v2.zeit());
return v1Zeit.compareTo(v2Zeit);
}
}

View file

@ -0,0 +1,98 @@
package de.accso.flexinale.besucherportal.api_in.web;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoSaalTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.security_api_contract.api_contract.BesucherRetriever;
import de.accso.flexinale.ticketing_api_contract.api_contract.KontingentBereitsAusgeschoepftException;
import de.accso.flexinale.ticketing_api_contract.api_contract.TicketRetriever;
import de.accso.flexinale.ticketing_api_contract.api_contract.Ticketing;
import de.accso.flexinale.ticketing_api_contract.api_contract.model.TicketTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.time.LocalDateTime;
import java.util.List;
@Controller
public class TicketWebController {
private static final Logger LOGGER = LoggerFactory.getLogger(TicketWebController.class);
@Autowired
private TicketRetriever ticketRetriever;
@Autowired
private VorfuehrungRetriever vorfuehrungRetriever;
@Autowired
private BesucherRetriever besucherRetriever;
@Autowired
private Ticketing ticketing;
@GetMapping(value="/tickets")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public String listTickets(final Model model) {
Identifiable.Id besucherId = besucherRetriever.getIdOfLoggedInBesucher();
List<TicketTO> allTickets = ticketRetriever.ticketsByBesucherOrderByZeit(besucherId);
List<List<VorfuehrungMitAnzahlTicketsTO>> ticketsByDay =
TicketSorter.sortAndMapTicketsPerDay(allTickets, vorfuehrungRetriever);
model.addAttribute("ticketsByDay", ticketsByDay);
int totalNumberOfTickets = ticketRetriever.gesamtZahlDerTicketsFuer(besucherId);
model.addAttribute("totalNumberOfTickets", totalNumberOfTickets);
return ("tickets");
}
@PostMapping("/vorfuehrung/loeseGutscheineOnlineEin")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public String loeseGutscheineOnlineEin(
@RequestParam("vorfuehrungId") final String vId,
@RequestParam("anzahl") final int anzahl,
final RedirectAttributes redirAttrs) {
Identifiable.Id vorfuehrungId = Identifiable.Id.of(vId);
Identifiable.Id besucherId = besucherRetriever.getIdOfLoggedInBesucher();
VorfuehrungTO vorfuehrungTO = vorfuehrungRetriever.vorfuehrung(vorfuehrungId);
Identifiable.Id filmId = vorfuehrungTO.film().id();
try {
ticketing.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrungId, filmId, besucherId, anzahl);
}
catch (KontingentBereitsAusgeschoepftException kbaEx) {
String message = "Kontingent exceeded: no %d tickets for Vorfuehrung %s and Besucher %s available".formatted(anzahl, vorfuehrungId, besucherId);
LOGGER.info(message);
redirAttrs.addFlashAttribute("error", "Keine %d Tickets mehr vorhanden".formatted(anzahl));
return "redirect:/film/" + filmId.id();
}
redirAttrs.addFlashAttribute("success", "%d Ticket(s) erfolgreich gekauft".formatted(anzahl));
return "redirect:/tickets";
}
}
// class used to "flatten" Vorfuehrung and number of Tickets for Vorfuehrung for Clients
record VorfuehrungMitAnzahlTicketsTO(Zeit zeit, FilmTO film, KinoSaalTO kinoSaal, AnzahlTickets anzahlTickets) {
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public record AnzahlTickets(Integer raw) implements RawWrapper<Integer> {}
}

View file

@ -0,0 +1,77 @@
package de.accso.flexinale.besucherportal.application.services;
import de.accso.flexinale.backoffice_api_contract.api_contract.FilmRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing_api_contract.api_contract.TicketRetriever;
import java.util.ArrayList;
import java.util.List;
public class FilmService {
private final FilmRetriever filmRetriever;
private final VorfuehrungRetriever vorfuehrungRetriever;
private final TicketRetriever ticketRetriever;
private final VorfuehrungService vorfuehrungService;
private final long minZeitZwischenVorfuehrungenInMinuten;
public FilmService(final FilmRetriever filmRetriever, final VorfuehrungRetriever vorfuehrungRetriever,
final TicketRetriever ticketRetriever,
final VorfuehrungService vorfuehrungService,
long minZeitZwischenVorfuehrungenInMinuten) {
this.filmRetriever = filmRetriever;
this.vorfuehrungRetriever = vorfuehrungRetriever;
this.ticketRetriever = ticketRetriever;
this.vorfuehrungService = vorfuehrungService;
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
}
public List<FilmTO> filme() {
return filmRetriever.filme();
}
public FilmTO film(final Identifiable.Id id) {
return filmRetriever.film(id);
}
public List<VorfuehrungTO> vorfuehrungenFuer(final Identifiable.Id filmId) {
return vorfuehrungRetriever.vorfuehrungenByFilmId(filmId);
}
public List<String> vorfuehrungenMitUeberlapp(final List<VorfuehrungTO> vorfuehrungen, final Identifiable.Id besucherId) {
List<String> vorfuehrungenMitUeberlapp = new ArrayList<>();
TicketBundle ticketBundle = new TicketBundle(besucherId,
ticketRetriever, vorfuehrungRetriever, vorfuehrungService,
minZeitZwischenVorfuehrungenInMinuten);
for (VorfuehrungTO vorfuehrung : vorfuehrungen) {
if (ticketBundle.mindestensEinTicketImTicketBundleUeberlapptMit(vorfuehrung)) {
vorfuehrungenMitUeberlapp.add(vorfuehrung.id().id());
}
}
return vorfuehrungenMitUeberlapp;
}
public List<String> vorfuehrungenFuerDieDerBenutzerEinTicketHat(final List<VorfuehrungTO> vorfuehrungen, final Identifiable.Id besucherId) {
List<String> vorfuehrungenMitTicket = new ArrayList<>();
TicketBundle ticketBundle = new TicketBundle(besucherId,
ticketRetriever, vorfuehrungRetriever, vorfuehrungService,
minZeitZwischenVorfuehrungenInMinuten);
for (VorfuehrungTO vorfuehrung : vorfuehrungen) {
if (ticketBundle.hatSchonTicketFuer(vorfuehrung)) {
vorfuehrungenMitTicket.add(vorfuehrung.id().id());
}
}
return vorfuehrungenMitTicket;
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.besucherportal.application.services;
import de.accso.flexinale.backoffice_api_contract.api_contract.KinoRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.KinoTO;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
public class KinoService {
private final KinoRetriever kinoRetriever;
public KinoService(final KinoRetriever kinoRetriever) {
this.kinoRetriever = kinoRetriever;
}
public List<KinoTO> kinos() {
return kinoRetriever.kinos();
}
public KinoTO kino(final Identifiable.Id id) {
return kinoRetriever.kino(id);
}
}

View file

@ -0,0 +1,67 @@
package de.accso.flexinale.besucherportal.application.services;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.FilmTO;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing_api_contract.api_contract.TicketRetriever;
import de.accso.flexinale.ticketing_api_contract.api_contract.model.TicketTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public class TicketBundle {
private static final Logger LOGGER = LoggerFactory.getLogger(TicketBundle.class);
private final VorfuehrungRetriever vorfuehrungRetriever;
private final VorfuehrungService vorfuehrungService;
private final long minZeitZwischenVorfuehrungenInMinuten;
private final List<TicketTO> ticketsOfBesucher;
public TicketBundle(final Identifiable.Id besucherId, final TicketRetriever ticketRetriever,
final VorfuehrungRetriever vorfuehrungRetriever,
final VorfuehrungService vorfuehrungService,
long minZeitZwischenVorfuehrungenInMinuten) {
this.vorfuehrungRetriever = vorfuehrungRetriever;
this.vorfuehrungService = vorfuehrungService;
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
this.ticketsOfBesucher = ticketRetriever.ticketsByBesucherOrderByZeit(besucherId);
}
public boolean mindestensEinTicketImTicketBundleUeberlapptMit(final VorfuehrungTO vorfuehrung) {
FilmTO filmOfVorfuehrung = vorfuehrung.film();
return ticketsOfBesucher.stream().anyMatch(ticket -> {
VorfuehrungTO vorfuehrungOfTicket = vorfuehrungRetriever.vorfuehrung(ticket.vorfuehrungId());
if (vorfuehrungOfTicket == null)
return false;
else {
FilmTO filmOfVorfuehrungOfTicket = vorfuehrungOfTicket.film();
if (filmOfVorfuehrungOfTicket == null) {
String message = "no Film for Vorfuehrung %s found".formatted(vorfuehrungOfTicket.id());
LOGGER.error(message);
throw new FlexinaleIllegalStateException(message);
}
return vorfuehrungService.vorfuehrungUeberlapptMit(vorfuehrung, vorfuehrungOfTicket,
getRawOrNull(filmOfVorfuehrung.dauerInMinuten()),
getRawOrNull(filmOfVorfuehrungOfTicket.dauerInMinuten()),
minZeitZwischenVorfuehrungenInMinuten);
}
});
}
public boolean hatSchonTicketFuer(final VorfuehrungTO vorfuehrung) {
return ticketsOfBesucher.stream()
.map(TicketTO::vorfuehrungId)
.toList()
.contains(vorfuehrung.id());
}
}

View file

@ -0,0 +1,22 @@
package de.accso.flexinale.besucherportal.application.services;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import java.time.LocalDateTime;
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
public final class VorfuehrungService {
public boolean vorfuehrungUeberlapptMit(final VorfuehrungTO one, final VorfuehrungTO other,
final Integer oneDauerInMinuten, final Integer otherDauerInMinuten,
final long puffer) {
LocalDateTime begin = getRawOrNull(one.zeit());
LocalDateTime beginOther = getRawOrNull(other.zeit());
LocalDateTime end = begin.plusMinutes(oneDauerInMinuten);
LocalDateTime endOther = beginOther.plusMinutes(otherDauerInMinuten);
return ((begin.isBefore(endOther.plusMinutes(puffer)))
&& (end.isAfter(beginOther.minusMinutes(puffer))));
}
}

View file

@ -0,0 +1,36 @@
package de.accso.flexinale.besucherportal.infrastructure;
import de.accso.flexinale.backoffice_api_contract.api_contract.FilmRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.KinoRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.besucherportal.application.services.FilmService;
import de.accso.flexinale.besucherportal.application.services.KinoService;
import de.accso.flexinale.besucherportal.application.services.VorfuehrungService;
import de.accso.flexinale.common.application.Config;
import de.accso.flexinale.ticketing_api_contract.api_contract.TicketRetriever;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FlexinaleBesucherPortalSpringFactory {
@Bean
public FilmService createBesucherPortalFilmService(final FilmRetriever filmRetriever,
final VorfuehrungRetriever vorfuehrungRetriever,
final TicketRetriever ticketRetriever,
final VorfuehrungService vorfuehrungService,
final Config config) {
return new FilmService(filmRetriever, vorfuehrungRetriever, ticketRetriever, vorfuehrungService,
config.getMinZeitZwischenVorfuehrungenInMinuten());
}
@Bean
public KinoService createBesucherPortalKinoService(final KinoRetriever kinoRetriever) {
return new KinoService(kinoRetriever);
}
@Bean
public VorfuehrungService createBesucherPortalVorfuehrungService() {
return new VorfuehrungService();
}
}

View file

@ -0,0 +1,10 @@
package de.accso.flexinale.common.application;
public interface Config {
int getQuoteOnline();
int getMinZeitZwischenVorfuehrungenInMinuten();
String getApplicationTitle();
String getBuildVersion();
String getBuildDate();
}

View file

@ -0,0 +1,9 @@
package de.accso.flexinale.common.application;
public enum PersistMode {
// updates on existing entities allowed (beware of inconsistencies with derived entities!)
UPDATE,
// only new data can be added, no updates on existing entities allowed
ADD_ONLY
}

View file

@ -0,0 +1,104 @@
package de.accso.flexinale.common.application.services;
import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
@SuppressWarnings("unused")
@DoNotCheckInArchitectureTests
public abstract class AbstractExcelDataUploadService<E extends Identifiable> implements ExcelDataUploadService<E> {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExcelDataUploadService.class);
@Override
public void beforeLoad(Object... o) {}
@Override
public void afterLoad(Object... o) {}
@Override
public final Collection<E> loadDataFromExcelSheet(final String resourceName) throws IOException {
try (InputStream stream = AbstractExcelDataUploadService.class.getResourceAsStream(resourceName)) {
if (stream == null) {
throw new FileNotFoundException("Resource '%s' not found!".formatted(resourceName));
}
return loadDataFromExcelSheet(stream);
}
}
@Override
public final Collection<E> loadDataFromExcelSheet(final InputStream stream) throws IOException {
Collection<E> dataSet = new HashSet<>();
// Excel sheet (the sheet, not the file!) needs to be named exactly like the entity's class name
// (example: "Kino")
String sheetName = getNameOfExcelDataType();
try (Workbook workbook = new XSSFWorkbook(stream)) {
// iterate through each row of first/desired sheet from the workbook
for (Row excelRow : workbook.getSheet(sheetName)) {
String firstCell = excelRow.getCell(0).getStringCellValue();
// ignore comments, starting with # , load all other rows
if (!firstCell.startsWith("#")) {
E data = createDataFromExcelRow(excelRow);
dataSet.add(data);
}
}
}
return dataSet;
}
@Override
public final int loadDataFromExcelSheetAndPersist(
final String resourceName, final PersistMode mode) throws IOException {
try (InputStream stream = AbstractExcelDataUploadService.class.getResourceAsStream(resourceName)) {
if (stream == null) {
throw new IOException("Resource '%s' not found!".formatted(resourceName));
}
return loadDataFromExcelSheetAndPersist(stream, mode);
}
}
@Override
public final int loadDataFromExcelSheetAndPersist(
final InputStream stream, final PersistMode mode) throws IOException {
return loadDataFromExcelSheetAndPersistAndReturn(stream, mode).size();
}
@Override
public final Collection<E> loadDataFromExcelSheetAndPersistAndReturn(
final InputStream stream, final PersistMode mode) throws IOException {
Collection<E> dataCollection = loadDataFromExcelSheet(stream);
Collection<E> dataLoaded = new HashSet<>();
AbstractDao<E> dao = getDao();
// in mode "ADD_ONLY" no updates are allowed
for (E dataFromExcelSheet : dataCollection) {
Identifiable.Id id = dataFromExcelSheet.id();
if (mode == PersistMode.ADD_ONLY && dao.findById(id).isPresent()) {
String sheetName = getNameOfExcelDataType();
LOGGER.warn("%s %s already exists. Will not be persisted because 'ADD' is active".formatted(sheetName, id));
} else {
dao.save(dataFromExcelSheet);
dataLoaded.add(dataFromExcelSheet);
}
}
return dataLoaded; // only return what really has been loaded
}
}

View file

@ -0,0 +1,27 @@
package de.accso.flexinale.common.application.services;
import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.apache.poi.ss.usermodel.Row;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@SuppressWarnings("unused")
public interface ExcelDataUploadService<E extends Identifiable> {
Collection<E> loadDataFromExcelSheet(final String resourceName) throws IOException;
Collection<E> loadDataFromExcelSheet(final InputStream stream) throws IOException;
int loadDataFromExcelSheetAndPersist(final String resourceName, PersistMode mode) throws IOException;
int loadDataFromExcelSheetAndPersist(final InputStream stream, PersistMode mode) throws IOException;
Collection<E> loadDataFromExcelSheetAndPersistAndReturn(final InputStream stream, PersistMode mode) throws IOException;
AbstractDao<E> getDao();
String getNameOfExcelDataType();
void beforeLoad(Object... o);
void afterLoad(Object... o);
E createDataFromExcelRow(final Row row);
}

Some files were not shown because too many files have changed in this diff Show more