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

56
flex-training-flexinale/.gitignore vendored Normal file
View file

@ -0,0 +1,56 @@
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### IntelliJ
.idea/
out/
*.iml
*.ipr
*.iws
target/
~*xlsx
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,14 @@
# flex-training-flexinale
iSAQB Advanced Level FLEX training: This code contains our case study for the "flexinale" example.
This is the main repo containing blueprint and solution.
## Architecture as a monolithic system
This part is in the folder flexinale-monolith
## 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
## Architecture as a distributed, service-oriented, event-based system
This part is in the folder flexinale-distributed

View file

@ -0,0 +1,6 @@
# 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 distributed, service-oriented, event-based system
This part is in the folder flexinale-distributed

View file

@ -0,0 +1,14 @@
<?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>
<Class name="de.accso.flexinale.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-distributed-backoffice-2024.3.0-spring-boot-fat-jar.jar /app
EXPOSE 8081
CMD ["java", "-jar", "flexinale-distributed-backoffice-2024.3.0-spring-boot-fat-jar.jar"]

View file

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

View file

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

View file

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

View file

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed</artifactId>
<version>2024.3.0</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>flexinale-distributed-backoffice</artifactId>
<version>2024.3.0</version>
<name>Flexinale Distributed Backoffice</name>
<description>Flexinale - FLEX case-study &quot;film festival&quot;, distributed services, backoffice</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>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>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-common</artifactId>
<version>2024.3.0</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-security</artifactId>
<scope>runtime</scope>
<version>2024.3.0</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-security_api_contract</artifactId>
<version>2024.3.0</version>
</dependency>
<dependency>
<groupId>de.accso</groupId>
<artifactId>flexinale-distributed-backoffice_api_contract</artifactId>
<version>2024.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- name extension of spring boot fat jar-->
<configuration>
<classifier>spring-boot-fat-jar</classifier>
</configuration>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<flexinale.implementation>Distributed</flexinale.implementation>
<flexinale.app>Backoffice</flexinale.app>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs-maven-plugin.version}</version>
<configuration>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>${findsecbugs-maven-plugin.version}</version>
</plugin>
</plugins>
<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,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distributed - start Backoffice app (&quot;FlexinaleDistributedApplicationBackoffice&quot;)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" folderName="App Distributed">
<option name="ENABLE_LAUNCH_OPTIMIZATION" value="false" />
<module name="flexinale-distributed-backoffice" />
<option name="SPRING_BOOT_MAIN_CLASS" value="de.accso.flexinale.FlexinaleDistributedApplicationBackoffice" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.accso.flexinale.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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,130 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.backoffice.api_out.event.FilmPublisher;
import de.accso.flexinale.backoffice.api_out.event.VorfuehrungPublisher;
import de.accso.flexinale.backoffice.application.services.FilmUploadService;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice.application.services.FilmService;
import de.accso.flexinale.backoffice.application.services.VorfuehrungService;
import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.application.PersistedEntityAndResult;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
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.io.IOException;
import java.util.Collection;
import java.util.List;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.ADDED;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.UPDATED;
@RestController
@Profile({ "!testdata-besucherportal & !testdata-ticketing" })
public class FilmRestController {
private static final Logger LOGGER = LoggerFactory.getLogger(FilmRestController.class);
@Autowired
private FilmService filmService;
@Autowired
private FilmUploadService filmUploadService;
@Autowired
private FilmPublisher filmPublisher;
@Autowired
private VorfuehrungService vorfuehrungService;
@Autowired
private VorfuehrungPublisher vorfuehrungPublisher;
@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;
}
// -----------------------------------------------------------------------------------------------------------
ResponseEntity<RestResponseMessage> loadAndPersistAndPublishNewFilme(
final BufferedInputStream stream, final String originalFilename) throws IOException
{
// load and persist
Collection<PersistedEntityAndResult<Film>> addedAndUpdatedFilms =
filmUploadService.loadDataFromExcelSheetAndPersistAndReturn(stream, PersistMode.UPDATE);
List<Film> addedFilms = addedAndUpdatedFilms.stream()
.filter(filmWithResult -> filmWithResult.result().equals(ADDED))
.map(PersistedEntityAndResult::entity)
.toList();
List<Film> updatedFilms = addedAndUpdatedFilms.stream()
.filter(filmWithResult -> filmWithResult.result().equals(UPDATED))
.map(PersistedEntityAndResult::entity)
.toList();
publishFilmsAndVorfuehrungen(addedFilms, updatedFilms);
String message = "Uploaded the Excel file %s adding %d new Film entities and updating %d existing Film entities."
.formatted(originalFilename, addedFilms.size(), updatedFilms.size());
RestUploadResult restUploadResult = new RestUploadResult(HttpStatus.OK, message);
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
private void publishFilmsAndVorfuehrungen(final List<Film> addedFilms, final List<Film> updatedFilms) {
filmPublisher.publishNewFilms(addedFilms);
filmPublisher.publishUpdatedFilms(updatedFilms);
List<Vorfuehrung> vorfuehrungen = vorfuehrungService.vorfuehrungen();
for (Film updatedFilm: updatedFilms) {
List<Vorfuehrung> vorfuehrungenToPublishAsUpdated = vorfuehrungen.stream()
.filter(vorfuehrung -> vorfuehrung.film.id.equals(updatedFilm.id))
.toList();
vorfuehrungPublisher.publishUpdatedVorfuehrungen(vorfuehrungenToPublishAsUpdated);
}
}
@PostMapping("/rest/filme")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<RestResponseMessage> uploadFilme(@RequestParam("file") 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);
}
return loadAndPersistAndPublishNewFilme(stream, file.getOriginalFilename());
}
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,167 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.backoffice.api_out.event.KinoPublisher;
import de.accso.flexinale.backoffice.api_out.event.VorfuehrungPublisher;
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.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice.application.services.KinoService;
import de.accso.flexinale.backoffice.application.services.VorfuehrungService;
import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.application.PersistedEntityAndResult;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
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.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.ADDED;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.UPDATED;
@RestController
@Profile({ "!testdata-besucherportal & !testdata-ticketing" })
public class KinoKinoSaalRestController {
private static final Logger LOGGER = LoggerFactory.getLogger(KinoKinoSaalRestController.class);
@Autowired
private KinoService kinoService;
@Autowired
private KinoUploadService kinoUploadService;
@Autowired
private KinoSaalUploadService kinoSaalUploadService;
@Autowired
private KinoPublisher kinoPublisher;
@Autowired
private VorfuehrungService vorfuehrungService;
@Autowired
private VorfuehrungPublisher vorfuehrungPublisher;
@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) {
String originalFilename = file.getOriginalFilename();
try {
Collection<KinoSaal> alleKinoSaele;
// we need to open the stream twice, separately for KinoSaele ...
try (BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
if (!ExcelHelper.isExcelFile(stream)) {
String message = "Please upload a valid Excel file!";
throw new FlexinaleIllegalStateException(message);
}
alleKinoSaele = loadKinoSaele(stream);
}
// ... and for Kino
try (BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
return loadAndPersistAndPublishNewKinosKinoSaele(stream, originalFilename, alleKinoSaele);
}
}
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;
}
}
Collection<KinoSaal> loadKinoSaele(final BufferedInputStream stream) throws IOException {
return kinoSaalUploadService.loadDataFromExcelSheet(stream);
}
ResponseEntity<RestResponseMessage> loadAndPersistAndPublishNewKinosKinoSaele(
final BufferedInputStream stream, final String originalFilename,
final Collection<KinoSaal> alleKinoSaele) throws IOException {
// and then load and persist all Kinos (including their KinoSaele)
Map<Identifiable.Id, Collection<KinoSaal>> alleKinosUndIhreSaele =
kinoSaalUploadService.mapKinoSaeleToKino(alleKinoSaele);
List<Kino> addedKinos;
List<Kino> updatedKinos;
try {
kinoUploadService.beforeLoad(alleKinosUndIhreSaele); // fix state of Kino-KinoSaal in map
// load and persist
Collection<PersistedEntityAndResult<Kino>> addedAndUpdatedKinos =
kinoUploadService.loadDataFromExcelSheetAndPersistAndReturn(stream, PersistMode.UPDATE);
addedKinos = addedAndUpdatedKinos.stream()
.filter(kinoWithResult -> kinoWithResult.result().equals(ADDED))
.map(PersistedEntityAndResult::entity)
.toList();
updatedKinos = addedAndUpdatedKinos.stream()
.filter(kinoWithResult -> kinoWithResult.result().equals(UPDATED))
.map(PersistedEntityAndResult::entity)
.toList();
publishKinosAndVorfuehrungen(addedKinos, updatedKinos);
}
finally {
kinoUploadService.afterLoad(); // clear map
}
String message = "Uploaded the Excel file %s adding %d new Kinos entities and updating %d existing Kinos entities."
.formatted(originalFilename, addedKinos.size(), updatedKinos.size());
RestUploadResult restUploadResult = new RestUploadResult(HttpStatus.OK, message);
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
private void publishKinosAndVorfuehrungen(final List<Kino> addedKinos, final List<Kino> updatedKinos) {
kinoPublisher.publishNewKinos(addedKinos);
kinoPublisher.publishUpdatedKinos(updatedKinos);
List<Vorfuehrung> vorfuehrungen = vorfuehrungService.vorfuehrungen();
List<Vorfuehrung> vorfuehrungenToPublishAsUpdated = new ArrayList<>();
for (Kino updatedKino: updatedKinos) {
for (KinoSaal updatedKinoSaal: updatedKino.kinoSaele) {
vorfuehrungen.stream()
.filter(vorfuehrung -> vorfuehrung.kinoSaal.id.equals(updatedKinoSaal.id))
.forEach(vorfuehrungenToPublishAsUpdated::add);
}
}
vorfuehrungPublisher.publishUpdatedVorfuehrungen(vorfuehrungenToPublishAsUpdated);
}
}

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;
@ResponseStatus(HttpStatus.BAD_REQUEST)
@SuppressWarnings("unused")
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,109 @@
package de.accso.flexinale.backoffice.api_in.rest;
import de.accso.flexinale.backoffice.api_out.event.VorfuehrungPublisher;
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.application.PersistMode;
import de.accso.flexinale.common.application.PersistedEntityAndResult;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
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.io.IOException;
import java.util.Collection;
import java.util.List;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.ADDED;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.UPDATED;
@RestController
@Profile({ "!testdata-besucherportal & !testdata-ticketing" })
public class VorfuehrungRestController {
private static final Logger LOGGER = LoggerFactory.getLogger(VorfuehrungRestController.class);
@Autowired
private VorfuehrungService vorfuehrungService;
@Autowired
private VorfuehrungUploadService vorfuehrungUploadService;
@Autowired
private VorfuehrungPublisher vorfuehrungPublisher;
@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())) {
if (!ExcelHelper.isExcelFile(stream)) {
String message = "Please upload a valid Excel file!";
throw new FlexinaleIllegalStateException(message);
}
return loadAndPersistAndPublishNewVorfuehrungen(stream, file.getOriginalFilename());
}
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;
}
}
ResponseEntity<RestResponseMessage> loadAndPersistAndPublishNewVorfuehrungen(
final BufferedInputStream stream, final String originalFilename) throws IOException
{
// load and persist
Collection<PersistedEntityAndResult<Vorfuehrung>> addedAndUpdatedVorfuehrungen =
vorfuehrungUploadService.loadDataFromExcelSheetAndPersistAndReturn(stream, PersistMode.UPDATE);
// publish as events
List<Vorfuehrung> addedVorfuehrungen = addedAndUpdatedVorfuehrungen.stream()
.filter(vorfuehrungWithResult -> vorfuehrungWithResult.result().equals(ADDED))
.map(PersistedEntityAndResult::entity)
.toList();
vorfuehrungPublisher.publishNewVorfuehrungen(addedVorfuehrungen);
List<Vorfuehrung> updatedVorfuehrungen = addedAndUpdatedVorfuehrungen.stream()
.filter(vorfuehrungWithResult -> vorfuehrungWithResult.result().equals(UPDATED))
.map(PersistedEntityAndResult::entity)
.toList();
vorfuehrungPublisher.publishUpdatedVorfuehrungen(updatedVorfuehrungen);
String message = "Uploaded the Excel file %s adding %d new Vorfuehrungen entities and updating %d existing Vorfuehrungen entities."
.formatted(originalFilename, addedVorfuehrungen.size(), updatedVorfuehrungen.size());
RestUploadResult restUploadResult = new RestUploadResult(HttpStatus.OK, message);
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
}

View file

@ -0,0 +1,59 @@
package de.accso.flexinale.backoffice.api_out.event;
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 jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Component
@Profile({"!test-integrated & !testdata"})
public class BootstrappingPostConstructBackoffice {
private static final Logger LOGGER = LoggerFactory.getLogger(BootstrappingPostConstructBackoffice.class);
@Autowired
FilmService filmService;
@Autowired
FilmPublisher filmPublisher;
@Autowired
KinoService kinoService;
@Autowired
KinoPublisher kinoPublisher;
@Autowired
VorfuehrungService vorfuehrungService;
@Autowired
VorfuehrungPublisher vorfuehrungPublisher;
// Alternatively annotate method with @EventListener(ApplicationReadyEvent.class)
@PostConstruct
public void postConstruct() {
LOGGER.info("Publishing existing data for FKKsV as *Created events");
loadAllFilmeFromDatabaseAndPublish();
loadAllKinosFromDatabaseAndPublish();
loadAllVorfuehrungenFromDatabaseAndPublish();
LOGGER.info("Publishing existing data for FKKsV as *Created events ... done");
}
private void loadAllVorfuehrungenFromDatabaseAndPublish() {
filmPublisher.publishNewFilms( filmService.filme() );
}
private void loadAllKinosFromDatabaseAndPublish() {
kinoPublisher.publishNewKinos( kinoService.kinos() );
}
private void loadAllFilmeFromDatabaseAndPublish() {
vorfuehrungPublisher.publishNewVorfuehrungen( vorfuehrungService.vorfuehrungen() );
}
}

View file

@ -0,0 +1,80 @@
package de.accso.flexinale.backoffice.api_out.event;
import de.accso.flexinale.backoffice.api_contract.event.FilmCRUDEvent;
import de.accso.flexinale.backoffice.api_contract.event.FilmCreatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.FilmDeletedEvent;
import de.accso.flexinale.backoffice.api_contract.event.FilmUpdatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.backoffice.api_out.event.mapper.Film2FilmTOMapper;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventPublisher.EventPublisher3;
import java.util.Collection;
@SuppressWarnings("unused")
public class FilmPublisher implements EventPublisher3<FilmCreatedEvent, FilmUpdatedEvent, FilmDeletedEvent> {
private final EventBus<FilmCreatedEvent> createBus;
private final EventBus<FilmUpdatedEvent> updateBus;
private final EventBus<FilmDeletedEvent> deleteBus;
private final EventNotification notification;
public FilmPublisher(final EventBusFactory eventBusFactory,
final EventNotification notification) {
this.createBus = eventBusFactory.createOrGetEventBusFor(FilmCreatedEvent.class);
this.updateBus = eventBusFactory.createOrGetEventBusFor(FilmUpdatedEvent.class);
this.deleteBus = eventBusFactory.createOrGetEventBusFor(FilmDeletedEvent.class);
this.notification = notification;
}
public void publishNewFilms(final Collection<Film> films) {
publishCRUDEvents(films, FilmCRUDEvent.CRUD.CREATE);
}
public void publishUpdatedFilms(final Collection<Film> films) {
publishCRUDEvents(films, FilmCRUDEvent.CRUD.UPDATE);
}
// currently we do not publish Deletions events
public void publishDeletedFilms(final Collection<Film> films) {
publishCRUDEvents(films, FilmCRUDEvent.CRUD.DELETE);
}
private void publishCRUDEvents(final Collection<Film> changedFilms, final FilmCRUDEvent.CRUD mode) {
for (Film film: changedFilms) {
FilmTO filmTO = Film2FilmTOMapper.map(film);
switch (mode) {
case CREATE -> this.post (FilmCreatedEvent.class, new FilmCreatedEvent(filmTO));
case UPDATE -> this.post2(FilmUpdatedEvent.class, new FilmUpdatedEvent(filmTO));
case DELETE -> this.post3(FilmDeletedEvent.class, new FilmDeletedEvent(filmTO));
}
}
}
@Override
public String getName() {
return FilmPublisher.class.getName();
}
@Override
public void post(final Class<FilmCreatedEvent> eventType, final FilmCreatedEvent event) {
this.createBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post2(final Class<FilmUpdatedEvent> eventType, final FilmUpdatedEvent event) {
this.updateBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post3(final Class<FilmDeletedEvent> eventType, final FilmDeletedEvent event) {
this.deleteBus.publish(eventType, event);
this.notification.notify(event);
}
}

View file

@ -0,0 +1,80 @@
package de.accso.flexinale.backoffice.api_out.event;
import de.accso.flexinale.backoffice.api_contract.event.KinoCRUDEvent;
import de.accso.flexinale.backoffice.api_contract.event.KinoCreatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.KinoDeletedEvent;
import de.accso.flexinale.backoffice.api_contract.event.KinoUpdatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.backoffice.api_out.event.mapper.KinoKinoSaal2KinoKinoSaalTOMapper;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventPublisher.EventPublisher3;
import java.util.Collection;
@SuppressWarnings("unused")
public class KinoPublisher implements EventPublisher3<KinoCreatedEvent, KinoUpdatedEvent, KinoDeletedEvent> {
private final EventBus<KinoCreatedEvent> createBus;
private final EventBus<KinoUpdatedEvent> updateBus;
private final EventBus<KinoDeletedEvent> deleteBus;
private final EventNotification notification;
public KinoPublisher(final EventBusFactory eventBusFactory,
final EventNotification notification) {
this.createBus = eventBusFactory.createOrGetEventBusFor(KinoCreatedEvent.class);
this.updateBus = eventBusFactory.createOrGetEventBusFor(KinoUpdatedEvent.class);
this.deleteBus = eventBusFactory.createOrGetEventBusFor(KinoDeletedEvent.class);
this.notification = notification;
}
public void publishNewKinos(final Collection<Kino> kinos) {
publishCRUDEvents(kinos, KinoCRUDEvent.CRUD.CREATE);
}
public void publishUpdatedKinos(final Collection<Kino> kinos) {
publishCRUDEvents(kinos, KinoCRUDEvent.CRUD.UPDATE);
}
// currently we do not publish Deletions events
public void publishDeletedKinos(final Collection<Kino> kinos) {
publishCRUDEvents(kinos, KinoCRUDEvent.CRUD.DELETE);
}
private void publishCRUDEvents(final Collection<Kino> changedKinos, final KinoCRUDEvent.CRUD mode) {
for (Kino kino: changedKinos) {
KinoTO kinoTO = KinoKinoSaal2KinoKinoSaalTOMapper.mapKino(kino);
switch (mode) {
case CREATE -> this.post (KinoCreatedEvent.class, new KinoCreatedEvent(kinoTO));
case UPDATE -> this.post2(KinoUpdatedEvent.class, new KinoUpdatedEvent(kinoTO));
case DELETE -> this.post3(KinoDeletedEvent.class, new KinoDeletedEvent(kinoTO));
}
}
}
@Override
public String getName() {
return KinoPublisher.class.getName();
}
@Override
public void post(final Class<KinoCreatedEvent> eventType, final KinoCreatedEvent event) {
this.createBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post2(final Class<KinoUpdatedEvent> eventType, final KinoUpdatedEvent event) {
this.updateBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post3(final Class<KinoDeletedEvent> eventType, final KinoDeletedEvent event) {
this.deleteBus.publish(eventType, event);
this.notification.notify(event);
}
}

View file

@ -0,0 +1,80 @@
package de.accso.flexinale.backoffice.api_out.event;
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungCRUDEvent;
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungCreatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungDeletedEvent;
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungUpdatedEvent;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.backoffice.api_out.event.mapper.Vorfuehrung2VorfuehrungTOMapper;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.common.api.eventbus.EventBus;
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.eventbus.EventPublisher.EventPublisher3;
import java.util.Collection;
@SuppressWarnings("unused")
public class VorfuehrungPublisher implements EventPublisher3<VorfuehrungCreatedEvent, VorfuehrungUpdatedEvent, VorfuehrungDeletedEvent> {
private final EventBus<VorfuehrungCreatedEvent> createBus;
private final EventBus<VorfuehrungUpdatedEvent> updateBus;
private final EventBus<VorfuehrungDeletedEvent> deleteBus;
private final EventNotification notification;
public VorfuehrungPublisher(final EventBusFactory eventBusFactory,
final EventNotification notification) {
this.createBus = eventBusFactory.createOrGetEventBusFor(VorfuehrungCreatedEvent.class);
this.updateBus = eventBusFactory.createOrGetEventBusFor(VorfuehrungUpdatedEvent.class);
this.deleteBus = eventBusFactory.createOrGetEventBusFor(VorfuehrungDeletedEvent.class);
this.notification = notification;
}
public void publishNewVorfuehrungen(final Collection<Vorfuehrung> vorfuehrungen) {
publishCRUDEvents(vorfuehrungen, VorfuehrungCRUDEvent.CRUD.CREATE);
}
public void publishUpdatedVorfuehrungen(final Collection<Vorfuehrung> vorfuehrungen) {
publishCRUDEvents(vorfuehrungen, VorfuehrungCRUDEvent.CRUD.UPDATE);
}
// currently we do not publish Deletions events
public void publishDeletedVorfuehrungen(final Collection<Vorfuehrung> vorfuehrungen) {
publishCRUDEvents(vorfuehrungen, VorfuehrungCRUDEvent.CRUD.DELETE);
}
private void publishCRUDEvents(final Collection<Vorfuehrung> changedVorfuehrungen, final VorfuehrungCRUDEvent.CRUD mode) {
for (Vorfuehrung vorfuehrung: changedVorfuehrungen) {
VorfuehrungTO vorfuehrungTO = Vorfuehrung2VorfuehrungTOMapper.map(vorfuehrung);
switch (mode) {
case CREATE -> this.post (VorfuehrungCreatedEvent.class, new VorfuehrungCreatedEvent(vorfuehrungTO));
case UPDATE -> this.post2(VorfuehrungUpdatedEvent.class, new VorfuehrungUpdatedEvent(vorfuehrungTO));
case DELETE -> this.post3(VorfuehrungDeletedEvent.class, new VorfuehrungDeletedEvent(vorfuehrungTO));
}
}
}
@Override
public String getName() {
return VorfuehrungPublisher.class.getName();
}
@Override
public void post(final Class<VorfuehrungCreatedEvent> eventType, final VorfuehrungCreatedEvent event) {
this.createBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post2(final Class<VorfuehrungUpdatedEvent> eventType, final VorfuehrungUpdatedEvent event) {
this.updateBus.publish(eventType, event);
this.notification.notify(event);
}
@Override
public void post3(final Class<VorfuehrungDeletedEvent> eventType, final VorfuehrungDeletedEvent event) {
this.deleteBus.publish(eventType, event);
this.notification.notify(event);
}
}

View file

@ -0,0 +1,16 @@
package de.accso.flexinale.backoffice.api_out.event.mapper;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.backoffice.domain.model.Film;
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,43 @@
package de.accso.flexinale.backoffice.api_out.event.mapper;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoSaalTO;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
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.api_out.event.mapper;
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
import de.accso.flexinale.backoffice.api_contract.event.model.KinoSaalTO;
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
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, kinoSaalTO.kino().id());
}
}

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(Identifiable.Id filmId) {
return filmService.film(filmId);
}
}

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.model.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,95 @@
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.application.PersistMode;
import de.accso.flexinale.common.application.PersistedEntityAndResult;
import de.accso.flexinale.common.domain.model.AbstractDao;
import de.accso.flexinale.common.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.DeveloperMistakeException;
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.io.InputStream;
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 Collection<PersistedEntityAndResult<KinoSaal>> loadDataFromExcelSheetAndPersistAndReturn(
final InputStream stream, final PersistMode mode) {
throw new DeveloperMistakeException("method must not be called on KinoSaalUploadService");
}
@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 Kino kino(Identifiable.Id kinoId) {
return kinoService.kino(kinoId);
}
public List<Kino> kinos() {
return kinoService.kinos();
}
}

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.model.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,26 @@
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.model.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,13 @@
package de.accso.flexinale.backoffice.domain.dao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.common.domain.model.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.model.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.model.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,17 @@
package de.accso.flexinale.backoffice.domain.dao;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.common.domain.model.AbstractDao;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("unused")
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,95 @@
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;
public class Film implements Identifiable, Versionable, Mergeable<Film>, 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 Film merge(final Film newData) {
return new Film(this.id, this.version,
newData.titel, newData.imdbUrl, newData.dauerInMinuten);
}
@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,145 @@
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.*;
import java.util.stream.Collectors;
public class Kino implements Identifiable, Versionable, Mergeable<Kino>, 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;
}
@Override
public Kino merge(final Kino newData) {
Map<Id, KinoSaal> mapIdToOldKinoSaal = new HashMap<>();
this.kinoSaele.forEach(oldKinoSaal -> {
mapIdToOldKinoSaal.put(oldKinoSaal.id(), oldKinoSaal);
});
Set<KinoSaal> mergedKinoSaele = newData.kinoSaele.stream().map(newKinoSaal -> {
KinoSaal oldKinoSaal = mapIdToOldKinoSaal.get(newKinoSaal.id);
if (oldKinoSaal != null) {
return oldKinoSaal.merge(newKinoSaal);
}
else {
return newKinoSaal;
}
})
.collect(Collectors.toSet());
Kino mergedKino = new Kino(this.id, this.version,
newData.name, newData.adresse, newData.emailAdresse, mergedKinoSaele);
mergedKinoSaele.forEach(kinoSaal -> kinoSaal.kino = mergedKino);
return mergedKino;
}
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,118 @@
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, Mergeable<KinoSaal>, 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 KinoSaal.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 KinoSaal merge(final KinoSaal newData) {
return new KinoSaal(this.id,
newData.name, newData.anzahlPlaetze,
/* needs to be corrected outside */ null);
}
@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,141 @@
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;
import java.util.Optional;
public final class Vorfuehrung implements Identifiable, Versionable, Mergeable<Vorfuehrung>, 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 Vorfuehrung merge(final Vorfuehrung newData) {
// merging Vorfuehrung has some business flaws - like when its film or its kinoSaal is changed,
// then all sold tickets might get obsolete - we ignore this here but care for that in Ticketing
Film mergedFilm = this.film.merge(newData.film);
Kino mergedKino = this.kinoSaal.kino.merge(newData.kinoSaal.kino);
Optional<KinoSaal> mergedKinoSaal = mergedKino.kinoSaele.stream().filter(
kinoSaal -> kinoSaal.id.equals(this.kinoSaal.id)).findFirst();
if (mergedKinoSaal.isEmpty()) {
throw new DeveloperMistakeException("merged KinoSaal from Vorfuehrung could not be retrieved");
}
return new Vorfuehrung(this.id, this.version,
newData.zeit, mergedFilm, mergedKinoSaal.get());
}
@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,34 @@
package de.accso.flexinale.backoffice.infrastructure;
import de.accso.flexinale.common.api.eventbus.EventNotification;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@Component
@Endpoint(id = "backofficeEventsPublished")
public class FlexinaleBackofficeActuatorEndpointEventsPublished implements EventNotification {
private final Queue<Event> eventsPublished = new ConcurrentLinkedQueue<>(); // event list is ordered by production time
@ReadOperation
public List<Event> eventsPublished() {
return eventsPublished.stream().toList();
}
@ReadOperation
public List<Event> eventsPublishedFilteredBy(@Selector Identifiable.Id correlationId) {
return eventsPublished.stream().filter(event -> event.correlationId().equals(correlationId)).toList();
}
@Override
public void notify(final Event event) {// might want to use a generic subscriber
eventsPublished.add(event);
}
}

View file

@ -0,0 +1,137 @@
package de.accso.flexinale.backoffice.infrastructure;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import de.accso.flexinale.backoffice.api_out.event.FilmPublisher;
import de.accso.flexinale.backoffice.api_out.event.KinoPublisher;
import de.accso.flexinale.backoffice.api_out.event.VorfuehrungPublisher;
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.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.common.api.eventbus.EventBusFactory;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@Profile({ "!testdata-besucherportal & !testdata-ticketing" })
@EnableJpaRepositories({"de.accso.flexinale.backoffice.infrastructure.persistence"})
@EnableTransactionManagement
@EntityScan(basePackages={"de.accso.flexinale.backoffice.infrastructure.persistence"})
public class FlexinaleBackofficeSpringFactory {
@Bean
public FilmService createApplicationFilmService(final de.accso.flexinale.backoffice.domain.services.FilmService filmService) {
return new FilmService(filmService);
}
@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 FilmUploadService createBackofficeFilmUploadService(final FilmDao filmDao) {
return new FilmUploadService(filmDao);
}
@Bean
public FilmDao createBackofficeFilmDao(final FilmJpaRepository filmJpaRepository) {
return new FilmJpaRepositoryDelegate(filmJpaRepository);
}
@Bean
public FilmPublisher createBackofficeFilmPublisher(final EventBusFactory eventBusFactory,
final FlexinaleBackofficeActuatorEndpointEventsPublished endpointEventsPublished) {
return new FilmPublisher(eventBusFactory, endpointEventsPublished);
}
// ------------------------------------------------------------------------------------------------
@Bean
public KinoService createApplicationKinoService(final de.accso.flexinale.backoffice.domain.services.KinoService kinoService) {
return new KinoService(kinoService);
}
@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 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 KinoPublisher createBackofficeKinoPublisher(final EventBusFactory eventBusFactory,
final FlexinaleBackofficeActuatorEndpointEventsPublished endpointEventsPublished) {
return new KinoPublisher(eventBusFactory, endpointEventsPublished);
}
// ------------------------------------------------------------------------------------------------
@Bean
public VorfuehrungService createApplicationVorfuehrungService(final de.accso.flexinale.backoffice.domain.services.VorfuehrungService vorfuehrungService) {
return new VorfuehrungService(vorfuehrungService);
}
@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 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 VorfuehrungPublisher createBackofficeVorfuehrungPublisher(final EventBusFactory eventBusFactory,
final FlexinaleBackofficeActuatorEndpointEventsPublished endpointEventsPublished) {
return new VorfuehrungPublisher(eventBusFactory, endpointEventsPublished);
}
// ------------------------------------------------------------------------------------------------
// Actuator Event serialization
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonBackofficeCustomizer() {
return builder -> builder.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
}

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

View file

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

View file

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

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Flexinale Distributed - Backoffice</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
</head>
<body>
Flexinale Distributed - Backoffice
</body>
</html>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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