chore: Initial import of FLEX training material

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,19 @@
package de.accso.flexinale.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.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.api_in.rest;
record RestResponseMessage(String message) {
}

View file

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

View file

@ -0,0 +1,49 @@
package de.accso.flexinale.api_in.rest;
import de.accso.flexinale.application.services.VorfuehrungUploadService;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.application.services.VorfuehrungService;
import de.accso.flexinale.shared_kernel.Identifiable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
public class VorfuehrungRestController extends AbstractExcelRestController {
@Autowired
private VorfuehrungService vorfuehrungService;
@Autowired
private VorfuehrungUploadService vorfuehrungUploadService;
@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) {
RestUploadResult restUploadResult = uploadViaRestCall(vorfuehrungUploadService, file);
return ResponseEntity.status(restUploadResult.status())
.body(new RestResponseMessage(restUploadResult.message()));
}
}

View file

@ -0,0 +1,67 @@
package de.accso.flexinale.api_in.web;
import de.accso.flexinale.application.services.FilmService;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.infrastructure.security.BenutzerDetailsService;
import de.accso.flexinale.shared_kernel.Identifiable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Controller
public class FilmWebController {
@Autowired
private FilmService filmService;
@Autowired
private BenutzerDetailsService benutzerService;
@GetMapping(value="/filme")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
@SuppressWarnings("SameReturnValue")
public String filme(final Model model) {
List<Film> filme = filmService.filme();
model.addAttribute("filme", filme);
return "filme";
}
@GetMapping(value="/film/{id}")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
@SuppressWarnings("SameReturnValue")
public String film(@PathVariable("id") final String id, final Model model) {
Identifiable.Id filmId = Identifiable.Id.of(id);
Film film = filmService.film(filmId);
if (film == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "film %s not available".formatted(id));
}
else {
model.addAttribute("film", film);
List<Vorfuehrung> vorfuehrungenOfFilm = filmService.vorfuehrungenFuer(filmId);
model.addAttribute("vorfuehrungen", vorfuehrungenOfFilm);
if (!vorfuehrungenOfFilm.isEmpty()) {
Besucher loggedinBesucher = benutzerService.getLoggedInBesucher();
List<String> vorfuehrungenMitUeberlapp =
filmService.vorfuehrungenMitUeberlapp(vorfuehrungenOfFilm, loggedinBesucher)
.stream().map(it -> it.id()).toList();
List<String> vorfuehrungenMitTicket =
filmService.vorfuehrungenFuerDieDerBenutzerEinTicketHat(vorfuehrungenOfFilm, loggedinBesucher)
.stream().map(it -> it.id()).toList();
model.addAttribute("vorfuehrungenMitUeberlapp", vorfuehrungenMitUeberlapp);
model.addAttribute("vorfuehrungenMitTicket", vorfuehrungenMitTicket);
}
}
return "film";
}
}

View file

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

View file

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

View file

@ -0,0 +1,88 @@
package de.accso.flexinale.api_in.web;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.domain.model.Vorfuehrung;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static de.accso.flexinale.shared_kernel.RawWrapper.getRawOrNull;
class TicketSorter {
/**
* return a list of list of tickets, sorted by time (inner list) and date (outer list).
* each list contains the tickets for a day
*/
static List<List<VorfuehrungMitAnzahlTicketsTO>> sortAndMapTicketsPerDay(final List<Ticket> allTickets) {
if (allTickets == null || allTickets.isEmpty()) {
return new ArrayList<>();
}
// 1) tickets by day
List<List<Ticket>> ticketsProTag = new ArrayList<>();
LocalDate previousDate = LocalDate.MIN; // far past :-)
for (Ticket ticket : allTickets) {
LocalDate currentDate = ((LocalDateTime) getRawOrNull(ticket.vorfuehrung.zeit)).toLocalDate();
if (currentDate.equals(previousDate)) {
ticketsProTag.getLast().add(ticket);
}
else {
List<Ticket> tickets = new ArrayList<>();
tickets.add(ticket);
ticketsProTag.add(tickets);
}
previousDate = currentDate;
}
// 2) inner structure: Vorfuehrung and tickets
List<List<VorfuehrungMitAnzahlTicketsTO>> vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag = new ArrayList<>();
for (List<Ticket> ticketsForOneDay : ticketsProTag) {
List<VorfuehrungMitAnzahlTicketsTO> vorfuehrungenUndAnzahlTicketsFuerEinenTag = new ArrayList<>();
int anzahlTicketsFuerVorfuehrung = 0;
Vorfuehrung previousVorfuehrung = ticketsForOneDay.getFirst().vorfuehrung;
for (Ticket ticket : ticketsForOneDay) {
// inside each day, vorfuehrungen are ordered by zeit.
// There is never more than one vorfuehrung at a zeit for a user.
// So tickets for the same vorfuehrung are all in a row - that's why the following works.
Vorfuehrung currentVorfuehrung = ticket.vorfuehrung;
if (currentVorfuehrung.equals(previousVorfuehrung)) {
anzahlTicketsFuerVorfuehrung++;
}
else {
// add the information for previous Vorfuehrung
VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
anzahlTicketsFuerVorfuehrung, previousVorfuehrung);
vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
anzahlTicketsFuerVorfuehrung = 1; // ... we also have a new ticket for the next Vorfuehrung
}
previousVorfuehrung = currentVorfuehrung;
}
// Also add the last Vorfuehrung an anzahlTickets when day is over
VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
anzahlTicketsFuerVorfuehrung, previousVorfuehrung);
vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag.add(vorfuehrungenUndAnzahlTicketsFuerEinenTag);
}
return vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag;
}
private static VorfuehrungMitAnzahlTicketsTO mapVorfuehrungToTO(final int anzahlTicketsFuerVorfuehrung,
final Vorfuehrung previousVorfuehrung) {
return new VorfuehrungMitAnzahlTicketsTO(
new VorfuehrungMitAnzahlTicketsTO.Zeit(getRawOrNull(previousVorfuehrung.zeit)),
previousVorfuehrung.film,
previousVorfuehrung.kinoSaal,
new VorfuehrungMitAnzahlTicketsTO.AnzahlTickets(anzahlTicketsFuerVorfuehrung)
);
}
}

View file

@ -0,0 +1,95 @@
package de.accso.flexinale.api_in.web;
import de.accso.flexinale.application.services.TicketService;
import de.accso.flexinale.application.services.VorfuehrungService;
import de.accso.flexinale.domain.model.KontingentBereitsAusgeschoepftException;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.KinoSaal;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.infrastructure.security.BenutzerDetailsService;
import de.accso.flexinale.shared_kernel.Identifiable;
import de.accso.flexinale.shared_kernel.RawWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.time.LocalDateTime;
import java.util.List;
@Controller
public class TicketWebController {
private static final Logger LOGGER = LoggerFactory.getLogger(TicketWebController.class);
@Autowired
private TicketService ticketService;
@Autowired
private BenutzerDetailsService benutzerService;
@Autowired
private VorfuehrungService vorfuehrungService;
@GetMapping(value="/tickets")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public String listTickets(final Model model) {
Besucher loggedInBenutzer = benutzerService.getLoggedInBesucher();
List<Ticket> allTickets = ticketService.findByBesucherOrderByZeit(loggedInBenutzer);
List<List<VorfuehrungMitAnzahlTicketsTO>> ticketsByDay = TicketSorter.sortAndMapTicketsPerDay(allTickets);
model.addAttribute("ticketsByDay", ticketsByDay);
int totalNumberOfTickets = ticketService.gesamtZahlDerTicketsFuer(loggedInBenutzer);
model.addAttribute("totalNumberOfTickets", totalNumberOfTickets);
return ("tickets");
}
@PostMapping("/vorfuehrung/loeseGutscheineOnlineEin")
@PreAuthorize("hasRole('ROLE_BESUCHER')")
public String loeseGutscheineOnlineEin(
@RequestParam("vorfuehrungId") final String vId,
@RequestParam("anzahl") final int anzahl,
final RedirectAttributes redirAttrs) {
Besucher loggedInBesucher = benutzerService.getLoggedInBesucher();
Identifiable.Id vorfuehrungId = Identifiable.Id.of(vId);
try {
ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrungId, loggedInBesucher, anzahl);
}
catch (KontingentBereitsAusgeschoepftException kbaEx) {
String message = "Kontingent exceeded: no %d tickets for Vorfuehrung %s and Besucher %s available"
.formatted(anzahl, vorfuehrungId, loggedInBesucher.login);
LOGGER.info(message);
Vorfuehrung vorfuehrung = vorfuehrungService.vorfuehrung(vorfuehrungId);
String filmId = vorfuehrung.film.id().id();
redirAttrs.addFlashAttribute("error", "Keine %d Tickets mehr vorhanden".formatted(anzahl));
return "redirect:/film/" + filmId;
}
redirAttrs.addFlashAttribute("success", "%d Ticket(s) erfolgreich gekauft".formatted(anzahl));
return "redirect:/tickets";
}
}
// class used to "flatten" Vorfuehrung and number of Tickets for Vorfuehrung for Clients
record VorfuehrungMitAnzahlTicketsTO(Zeit zeit, Film film, KinoSaal kinoSaal, AnzahlTickets anzahlTickets) {
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public record AnzahlTickets(Integer raw) implements RawWrapper<Integer> {}
}

View file

@ -0,0 +1,10 @@
package de.accso.flexinale.application;
public interface Config { //TODO split into business and technical configuration
int getQuoteOnline();
int getMinZeitZwischenVorfuehrungenInMinuten();
String getApplicationTitle();
String getBuildVersion();
String getBuildDate();
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.application;
@SuppressWarnings("unused")
public class UeberlappendeVorfuehrungException extends RuntimeException {
public UeberlappendeVorfuehrungException(final String message) {
super(message);
}
public UeberlappendeVorfuehrungException() {
super();
}
public UeberlappendeVorfuehrungException(final String message, final Throwable cause) {
super(message, cause);
}
public UeberlappendeVorfuehrungException(final Throwable cause) {
super(cause);
}
protected UeberlappendeVorfuehrungException(final String message, final Throwable cause,
final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

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

View file

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

View file

@ -0,0 +1,40 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class FilmService {
private final de.accso.flexinale.domain.services.FilmService filmService;
public FilmService(de.accso.flexinale.domain.services.FilmService filmService) {
this.filmService = filmService;
}
public Film film(Identifiable.Id filmId) {
return filmService.film(filmId);
}
public List<Film> filme() {
return filmService.filme();
}
public List<Vorfuehrung> vorfuehrungenFuer(Identifiable.Id filmId) {
return filmService.vorfuehrungenFuer(filmId);
}
public List<Identifiable.Id> vorfuehrungenMitUeberlapp(List<Vorfuehrung> vorfuehrungenOfFilm, Besucher loggedinBesucher) {
return filmService.vorfuehrungenMitUeberlapp(vorfuehrungenOfFilm, loggedinBesucher);
}
public List<Identifiable.Id> vorfuehrungenFuerDieDerBenutzerEinTicketHat(List<Vorfuehrung> vorfuehrungenOfFilm, Besucher loggedinBesucher) {
return filmService.vorfuehrungenFuerDieDerBenutzerEinTicketHat(vorfuehrungenOfFilm, loggedinBesucher);
}
}

View file

@ -0,0 +1,57 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.domain.dao.AbstractDao;
import de.accso.flexinale.domain.dao.FilmDao;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.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) {
Identifiable.Id filmId = Identifiable.Id.of(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(
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,84 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.domain.dao.AbstractDao;
import de.accso.flexinale.domain.dao.KinoSaalDao;
import de.accso.flexinale.domain.model.Kino;
import de.accso.flexinale.domain.model.KinoSaal;
import de.accso.flexinale.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@Transactional
public class KinoSaalUploadService extends AbstractExcelDataUploadService<KinoSaal> {
private static final Logger LOGGER = LoggerFactory.getLogger(KinoSaalUploadService.class);
private final KinoSaalDao kinoSaalDao;
public KinoSaalUploadService(KinoSaalDao kinoSaalDao) {
this.kinoSaalDao = kinoSaalDao;
}
@Override
public AbstractDao<KinoSaal> getDao() {
return kinoSaalDao;
}
@Override
public String getNameOfExcelDataType() {
return KinoSaal.class.getSimpleName();
}
@Override
public KinoSaal createDataFromExcelRow(final Row excelRow) {
String kinoSaalIdAsString = excelRow.getCell(0).getStringCellValue();
Identifiable.Id kinoId = Identifiable.Id.of(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(kinoSaalIdAsString),
new KinoSaal.Name(excelRow.getCell(1).getStringCellValue()),
new KinoSaal.AnzahlPlaetze(anzahlPlaetze),
new Kino(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,25 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.domain.model.Kino;
import de.accso.flexinale.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class KinoService {
private final de.accso.flexinale.domain.services.KinoService kinoService;
public KinoService(de.accso.flexinale.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,80 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.domain.dao.AbstractDao;
import de.accso.flexinale.domain.dao.KinoDao;
import de.accso.flexinale.domain.model.Kino;
import de.accso.flexinale.domain.model.KinoSaal;
import de.accso.flexinale.shared_kernel.DeveloperMistakeException;
import de.accso.flexinale.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.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,9 @@
package de.accso.flexinale.application.services;
public enum PersistMode {
// updates on existing entities allowed (beware of inconsistencies with derived entities!)
UPDATE,
// only new data can be added, no updates on existing entities allowed
ADD_ONLY
}

View file

@ -0,0 +1,30 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.shared_kernel.Identifiable;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class TicketService {
private final de.accso.flexinale.domain.services.TicketService ticketService;
public TicketService(de.accso.flexinale.domain.services.TicketService ticketService) {
this.ticketService = ticketService;
}
public List<Ticket> findByBesucherOrderByZeit(Besucher loggedInBenutzer) {
return ticketService.findByBesucherOrderByZeit(loggedInBenutzer);
}
public int gesamtZahlDerTicketsFuer(Besucher loggedInBenutzer) {
return ticketService.gesamtZahlDerTicketsFuer(loggedInBenutzer);
}
public void loeseGutscheineOnlineFuerVorfuehrungEin(Identifiable.Id vorfuehrungId, Besucher loggedInBesucher, int anzahl) {
ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrungId, loggedInBesucher, anzahl);
}
}

View file

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

View file

@ -0,0 +1,62 @@
package de.accso.flexinale.application.services;
import de.accso.flexinale.application.Config;
import de.accso.flexinale.domain.dao.AbstractDao;
import de.accso.flexinale.domain.dao.KinoSaalDao;
import de.accso.flexinale.domain.dao.VorfuehrungDao;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.KinoSaal;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.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 KinoSaalDao kinoSaalDao;
private final VorfuehrungDao vorfuehrungDao;
private final Config config;
public VorfuehrungUploadService(final VorfuehrungDao vorfuehrungDao, final KinoSaalDao kinoSaalDao, final Config config) {
this.kinoSaalDao = kinoSaalDao;
this.vorfuehrungDao = vorfuehrungDao;
this.config = config;
}
@Override
public AbstractDao<Vorfuehrung> getDao() {
return vorfuehrungDao;
}
@Override
public String getNameOfExcelDataType() {
return Vorfuehrung.class.getSimpleName();
}
@Override
public Vorfuehrung createDataFromExcelRow(final Row excelRow) {
String vorfuehrungIdAsString = 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 filmIdAsString = excelRow.getCell(2).getStringCellValue();
Film film = new Film(Identifiable.Id.of(filmIdAsString));
String kinoSaalId = excelRow.getCell(3).getStringCellValue();
KinoSaal kinoSaal = kinoSaalDao.findById(Identifiable.Id.of(kinoSaalId)).orElseThrow(IllegalStateException::new);
Vorfuehrung vorfuehrung = new Vorfuehrung(Identifiable.Id.of(vorfuehrungIdAsString),
new Vorfuehrung.Zeit(zeit), film, kinoSaal, config.getQuoteOnline());
LOGGER.debug("New Vorfuehrung created: " + vorfuehrung);
return vorfuehrung;
}
}

View file

@ -0,0 +1,19 @@
package de.accso.flexinale.domain.dao;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("unused")
public interface AbstractDao<E extends Identifiable> {
List<E> findAll();
Optional<E> findById(final Identifiable.Id id);
E save(final E entity);
void delete(final E data);
void deleteById(final Identifiable.Id id);
void deleteAll();
}

View file

@ -0,0 +1,12 @@
package de.accso.flexinale.domain.dao;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.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,12 @@
package de.accso.flexinale.domain.dao;
import de.accso.flexinale.domain.model.Kino;
import de.accso.flexinale.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,12 @@
package de.accso.flexinale.domain.dao;
import de.accso.flexinale.domain.model.KinoSaal;
import de.accso.flexinale.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,12 @@
package de.accso.flexinale.domain.dao;
import de.accso.flexinale.domain.model.Kontingent;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.Optional;
public interface KontingentDao extends AbstractDao<Kontingent> {
@Override
Optional<Kontingent> findById(final Identifiable.Id id);
}

View file

@ -0,0 +1,18 @@
package de.accso.flexinale.domain.dao;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
public interface TicketDao extends AbstractDao<Ticket> {
@Override
Optional<Ticket> findById(final Identifiable.Id id);
List<Ticket> findByBesucherOrderByZeit(final Besucher benutzer);
int gesamtZahlDerTicketsFuer(final Besucher benutzer);
}

View file

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

View file

@ -0,0 +1,87 @@
package de.accso.flexinale.domain.model;
import de.accso.flexinale.shared_kernel.EqualsByContent;
import de.accso.flexinale.shared_kernel.Identifiable;
import de.accso.flexinale.shared_kernel.RawWrapper;
import de.accso.flexinale.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Besucher implements Identifiable, Versionable, EqualsByContent {
public record Login(String raw) implements RawWrapper<String> {}
public record EmailAdresse(String raw) implements RawWrapper<String> {}
public record Name(String raw) implements RawWrapper<String> {}
public record Vorname(String raw) implements RawWrapper<String> {}
public final Id id;
public final Version version;
public final Login login;
public final EmailAdresse emailAdresse;
public final Name name;
public final Vorname vorname;
public Besucher(final Id id,
final Login login, final EmailAdresse emailAdresse, final Name name, final Vorname vorname) {
this(id, Versionable.unknownVersion(), login, emailAdresse, name, vorname);
}
public Besucher(final Id id, final Version version,
final Login login, final EmailAdresse emailAdresse, final Name name, final Vorname vorname) {
this.id = id;
this.version = version;
this.login = login;
this.emailAdresse = emailAdresse;
this.name = name;
this.vorname = vorname;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (!equalsByContent(o)) return false;
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Besucher that = (Besucher) 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;}
Besucher that = (Besucher) o;
return new EqualsBuilder()
.append(id, that.id)
.append(login, that.login)
.append(emailAdresse, that.emailAdresse).append(name, that.name)
.append(vorname, that.vorname).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(login).append(emailAdresse).append(name).append(vorname).toHashCode();
}
}

View file

@ -0,0 +1,92 @@
package de.accso.flexinale.domain.model;
import de.accso.flexinale.shared_kernel.EqualsByContent;
import de.accso.flexinale.shared_kernel.Identifiable;
import de.accso.flexinale.shared_kernel.RawWrapper;
import de.accso.flexinale.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Film implements Identifiable, Versionable, EqualsByContent {
public record Titel(String raw) implements RawWrapper<String> {}
public record ImdbUrl(String raw) implements RawWrapper<String> {}
public record DauerInMinuten(Integer raw) implements RawWrapper<Integer> {}
public final Id id;
public final Version version;
public Titel titel;
public ImdbUrl imdbUrl;
public DauerInMinuten dauerInMinuten;
public Film(final Id id) {
this(id, Versionable.unknownVersion());
}
public Film(final Id id, final Version version) {
this.id = id;
this.version = version;
}
public Film(final Id id,
final Titel titel, final ImdbUrl imdbUrl, final DauerInMinuten dauerInMinuten) {
this(id, Versionable.unknownVersion(), titel, imdbUrl, dauerInMinuten);
}
public Film(final Id id, final Version version,
final Titel titel, final ImdbUrl imdbUrl, final DauerInMinuten dauerInMinuten) {
this.id = id;
this.version = version;
this.titel = titel;
this.imdbUrl = imdbUrl;
this.dauerInMinuten = dauerInMinuten;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (!equalsByContent(o)) return false;
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final Film that = (Film) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
final Film that = (Film) o;
return new EqualsBuilder()
.append(id, that.id)
.append(titel, that.titel).append(imdbUrl, that.imdbUrl)
.append(dauerInMinuten, that.dauerInMinuten).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(titel).append(imdbUrl).append(dauerInMinuten).toHashCode();
}
}

View file

@ -0,0 +1,121 @@
package de.accso.flexinale.domain.model;
import de.accso.flexinale.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Kino implements Identifiable, Versionable, EqualsByContent {
public record Name(String raw) implements RawWrapper<String> {}
public record Adresse(String raw) implements RawWrapper<String> {}
public record EmailAdresse(String raw) implements RawWrapper<String> {}
public final Id id;
public final Version version;
public Name name;
public Adresse adresse;
public EmailAdresse emailAdresse;
@DoNotCheckInArchitectureTests
public Set<KinoSaal> kinoSaele = new HashSet<>();
public Kino(final Id id) {
this(id, Versionable.unknownVersion());
}
public Kino(final Id id, final Version version) {
this.id = id;
this.version = version;
}
public Kino(final Id id,
final Name name, final Adresse adresse,
final EmailAdresse emailAdresse, final Set<KinoSaal> kinoSaele) {
this(id, Versionable.unknownVersion(), name, adresse, emailAdresse, kinoSaele);
}
public Kino(final Id id, final Version version,
final Name name, final Adresse adresse,
final EmailAdresse emailAdresse, final Set<KinoSaal> kinoSaele) {
this.id = id;
this.version = version;
this.name = name;
this.adresse = adresse;
this.emailAdresse = emailAdresse;
this.kinoSaele = kinoSaele;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
public void addSaele(final Collection<KinoSaal> saele) {
kinoSaele.addAll(saele);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (!equalsByContent(o)) return false;
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Kino that = (Kino) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@SuppressWarnings("ConstantValue")
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Kino that = (Kino) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(name, that.name)
.append(adresse, that.adresse).append(emailAdresse, that.emailAdresse)
.isEquals();
if (!result) return false;
if (kinoSaele == null && that.kinoSaele == null) return true;
if (kinoSaele == null && that.kinoSaele != null) return false;
if (kinoSaele != null && that.kinoSaele == null) return false;
List<KinoSaal> thisKSList = kinoSaele.stream().toList();
List<KinoSaal> thatKSList = that.kinoSaele.stream().toList();
if (thisKSList.size() != thatKSList.size()) return false;
for (int counter = 0; counter < thisKSList.size(); counter++) {
if (!thisKSList.get(counter).equalsByContent(thatKSList.get(counter))) return false;
}
return true;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(name).append(adresse).append(emailAdresse)
.append(kinoSaele.stream().toList()) // need to use list, as Set does not support deep equals
.toHashCode();
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,120 @@
package de.accso.flexinale.domain.model;
import de.accso.flexinale.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.shared_kernel.EqualsByContent;
import de.accso.flexinale.shared_kernel.Identifiable;
import de.accso.flexinale.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Ticket implements Identifiable, Versionable, EqualsByContent {
public enum VerkaufsKanal {
ONLINE,
ZENTRAL,
KINOKASSE
}
public final Id id;
public final Version version;
@DoNotCheckInArchitectureTests
public final Film film;
@DoNotCheckInArchitectureTests
public final Vorfuehrung vorfuehrung;
@DoNotCheckInArchitectureTests
public final Besucher besucher;
@DoNotCheckInArchitectureTests
public VerkaufsKanal verkaufsKanal;
public Ticket(final Id id,
final Film film, final Vorfuehrung vorfuehrung, final Besucher besucher,
final VerkaufsKanal verkaufsKanal) {
this(id, Versionable.unknownVersion(), film, vorfuehrung, besucher, verkaufsKanal);
}
public Ticket(final Id id, final Version version,
final Film film, final Vorfuehrung vorfuehrung, final Besucher besucher,
final VerkaufsKanal verkaufsKanal) {
this.id = id;
this.version = version;
this.film = film;
this.vorfuehrung = vorfuehrung;
this.besucher = besucher;
this.verkaufsKanal = verkaufsKanal;
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (!equalsByContent(o)) return false;
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Ticket that = (Ticket) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@SuppressWarnings("DataFlowIssue")
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Ticket that = (Ticket) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(verkaufsKanal, that.verkaufsKanal)
.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 (vorfuehrung == null && that.vorfuehrung == null) result = true;
if (vorfuehrung == null && that.vorfuehrung != null) return false;
if (vorfuehrung != null) {
result = vorfuehrung.equalsByContent(that.vorfuehrung);
if (!result) return false;
}
if (besucher == null && that.besucher == null) result = true;
if (besucher == null && that.besucher != null) return false;
if (besucher != null) {
result = besucher.equalsByContent(that.besucher);
}
return result;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(film).append(vorfuehrung).append(besucher)
.append(verkaufsKanal)
.toHashCode();
}
}

View file

@ -0,0 +1,190 @@
package de.accso.flexinale.domain.model;
import de.accso.flexinale.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 static de.accso.flexinale.shared_kernel.RawWrapper.getRawOrNull;
public final class Vorfuehrung implements Identifiable, Versionable, EqualsByContent { // TODO is only final as otherwise Spotbugs complains, get rid of Exception in constructor!
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public final Id id;
public final Version version;
public final Zeit zeit;
@DoNotCheckInArchitectureTests
public final Film film;
@DoNotCheckInArchitectureTests
public final KinoSaal kinoSaal;
public Kontingent getKontingent() {
return kontingent;
}
@DoNotCheckInArchitectureTests
private Kontingent kontingent; // kontingent is not final because when tickets are sold, the kontingent may change.
public Vorfuehrung(final Id id,
final Zeit zeit,
final Film film, final KinoSaal kinoSaal, final Kontingent kontingent) {
this(id, Versionable.unknownVersion(), zeit, film, kinoSaal, kontingent);
}
public Vorfuehrung(final Id id, final Version version,
final Zeit zeit,
final Film film, final KinoSaal kinoSaal, final Kontingent kontingent) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
this.kontingent = kontingent;
//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");
}
if (kontingent == null) {
throw new FlexinaleIllegalArgumentException("Kontingent of Vorfuehrung " + this +
" must not be null");
}
}
public Vorfuehrung(final Id id,
final Zeit zeit, final Film film, final KinoSaal kinoSaal, int quoteOnline) {
this(id, Versionable.unknownVersion(), zeit, film, kinoSaal, quoteOnline);
}
public Vorfuehrung(final Id id, final Version version,
final Zeit zeit, final Film film, final KinoSaal kinoSaal, int quoteOnline) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
if (kinoSaal == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal of Vorfuehrung " + this +
" must not be null");
}
if (kinoSaal.anzahlPlaetze == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal's Anzahl Plaetze of Vorfuehrung " + this +
" must not be null");
}
kontingent = new Kontingent(Identifiable.Id.of("K" + id), getRawOrNull(kinoSaal.anzahlPlaetze), quoteOnline);
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
public Integer getGesamtkapazitaetVerkauf() {
return getRawOrNull(kinoSaal.anzahlPlaetze);
}
public void verkaufeTickets(final Ticket.VerkaufsKanal verkaufsKanal, final int anzahlGutscheine) {
switch (verkaufsKanal) {
case ONLINE ->
this.kontingent = kontingent.reduziereKontingent(Kontingent.KontingentType.ONLINE, anzahlGutscheine);
case ZENTRAL ->
this.kontingent = kontingent.reduziereKontingent(Kontingent.KontingentType.ZENTRAL, anzahlGutscheine);
case KINOKASSE ->
this.kontingent = kontingent.reduziereKontingent(Kontingent.KontingentType.KINOKASSE, anzahlGutscheine);
}
}
public boolean ueberlapptMit(Vorfuehrung other, long puffer) {
LocalDateTime begin = getRawOrNull(this.zeit);
LocalDateTime beginOther = getRawOrNull(other.zeit);
LocalDateTime end = begin.plusMinutes(this.film.dauerInMinuten.raw());
LocalDateTime endOther = beginOther.plusMinutes(other.film.dauerInMinuten.raw());
return ((begin.isBefore(endOther.plusMinutes(puffer)))
&& (end.isAfter(beginOther.minusMinutes(puffer))));
}
@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("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);
if (!result) return false;
}
if (kontingent == null && that.kontingent == null) result = true;
if (kontingent == null && that.kontingent != null) return false;
if (kontingent != null) {
result = kontingent.equalsByContent(that.kontingent);
}
return result;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(zeit).append(film).append(kinoSaal)
.append(kontingent)
.toHashCode();
}
}

View file

@ -0,0 +1,69 @@
package de.accso.flexinale.domain.services;
import de.accso.flexinale.domain.dao.FilmDao;
import de.accso.flexinale.domain.dao.TicketDao;
import de.accso.flexinale.domain.dao.VorfuehrungDao;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.ArrayList;
import java.util.List;
public class FilmService {
private final FilmDao filmDao;
private final VorfuehrungDao vorfuehrungDao;
private final TicketDao ticketDao;
private final long minZeitZwischenVorfuehrungenInMinuten;
public FilmService(final FilmDao filmDao, final VorfuehrungDao vorfuehrungDao, final TicketDao ticketDao,
final long minZeitZwischenVorfuehrungenInMinuten) {
this.filmDao = filmDao;
this.vorfuehrungDao = vorfuehrungDao;
this.ticketDao = ticketDao;
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
}
public List<Film> filme() {
return filmDao.findAll();
}
public Film film(Identifiable.Id id) {
return filmDao.findById(id).orElse(null);
}
public List<Vorfuehrung> vorfuehrungenFuer(final Identifiable.Id filmId) {
return vorfuehrungDao.findByFilmId(filmId);
}
public List<Identifiable.Id> vorfuehrungenMitUeberlapp(final List<Vorfuehrung> vorfuehrungen, final Besucher besucher) {
List<Identifiable.Id> vorfuehrungenMitUeberlapp = new ArrayList<>();
TicketBundle ticketBundle = new TicketBundle(besucher, ticketDao, minZeitZwischenVorfuehrungenInMinuten);
for (Vorfuehrung vorfuehrung : vorfuehrungen) {
if (ticketBundle.mindestensEinTicketImTicketBundleUeberlapptMit(vorfuehrung)) {
vorfuehrungenMitUeberlapp.add(vorfuehrung.id);
}
}
return vorfuehrungenMitUeberlapp;
}
public List<Identifiable.Id> vorfuehrungenFuerDieDerBenutzerEinTicketHat(final List<Vorfuehrung> vorfuehrungen, final Besucher besucher) {
List<Identifiable.Id> vorfuehrungenMitTicket = new ArrayList<>();
TicketBundle ticketBundle = new TicketBundle(besucher, ticketDao, minZeitZwischenVorfuehrungenInMinuten);
for (Vorfuehrung vorfuehrung : vorfuehrungen) {
if (ticketBundle.hatSchonTicketFuer(vorfuehrung)) {
vorfuehrungenMitTicket.add(vorfuehrung.id);
}
}
return vorfuehrungenMitTicket;
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.domain.services;
import de.accso.flexinale.domain.dao.KinoDao;
import de.accso.flexinale.domain.model.Kino;
import de.accso.flexinale.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,35 @@
package de.accso.flexinale.domain.services;
import de.accso.flexinale.domain.dao.TicketDao;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.domain.model.Vorfuehrung;
import java.util.List;
public class TicketBundle {
private final Besucher besucher;
private final TicketDao ticketDao;
private final long minZeitZwischenVorfuehrungenInMinuten;
public TicketBundle(final Besucher besucher, final TicketDao ticketDao, final long minZeitZwischenVorfuehrungenInMinuten) {
this.besucher = besucher;
this.ticketDao = ticketDao;
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
}
public boolean mindestensEinTicketImTicketBundleUeberlapptMit(final Vorfuehrung vorfuehrung) {
List<Ticket> tickets = ticketDao.findByBesucherOrderByZeit(besucher);
return tickets.stream().anyMatch(ticket ->
ticket.vorfuehrung.ueberlapptMit(vorfuehrung, minZeitZwischenVorfuehrungenInMinuten));
}
public boolean hatSchonTicketFuer(final Vorfuehrung vorfuehrung) {
List<Ticket> tickets = ticketDao.findByBesucherOrderByZeit(besucher);
return tickets.stream()
.map(ticket -> ticket.vorfuehrung).toList().contains(vorfuehrung);
}
}

View file

@ -0,0 +1,62 @@
package de.accso.flexinale.domain.services;
import de.accso.flexinale.domain.dao.KontingentDao;
import de.accso.flexinale.domain.dao.TicketDao;
import de.accso.flexinale.domain.dao.VorfuehrungDao;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.shared_kernel.Identifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class TicketService {
private static final Logger LOGGER = LoggerFactory.getLogger(TicketService.class);
private final TicketDao ticketDao;
private final VorfuehrungDao vorfuehrungDao;
private final KontingentDao kontingentDao;
public TicketService(final TicketDao ticketDao, final VorfuehrungDao vorfuehrungDao,
final KontingentDao kontingentDao) {
this.ticketDao = ticketDao;
this.vorfuehrungDao = vorfuehrungDao;
this.kontingentDao = kontingentDao;
}
public void loeseGutscheineOnlineFuerVorfuehrungEin(final Identifiable.Id vorfuehrungId, final Besucher besucher, final int anzahlGutscheine) {
loeseGutscheineFuerVorfuehrungEin(vorfuehrungId, besucher, anzahlGutscheine, Ticket.VerkaufsKanal.ONLINE);
}
@SuppressWarnings("SameParameterValue")
private void loeseGutscheineFuerVorfuehrungEin(final Identifiable.Id vorfuehrungId, final Besucher besucher,
final int anzahlGutscheine,
final Ticket.VerkaufsKanal verkaufsKanal) {
Vorfuehrung vorfuehrung = vorfuehrungDao.findById(vorfuehrungId).get(); //TODO check isPresent() first!
vorfuehrung.verkaufeTickets(verkaufsKanal, anzahlGutscheine);
for (int i = 0; i < anzahlGutscheine; i++) {
Ticket ticket = new Ticket(Identifiable.Id.of(), vorfuehrung.film, vorfuehrung, besucher, verkaufsKanal);
ticketDao.save(ticket);
}
// save reduction of the Kontingent reduction
kontingentDao.save(vorfuehrung.getKontingent());
LOGGER.info("%d ticket(s) bought for Vorfuehrung %s, Besucher %s, using Verkaufskanal %s"
.formatted(anzahlGutscheine, vorfuehrung.id, besucher.login, verkaufsKanal));
}
public int gesamtZahlDerTicketsFuer(final Besucher eingeloggterBesucher) {
return ticketDao.gesamtZahlDerTicketsFuer(eingeloggterBesucher);
}
public List<Ticket> findByBesucherOrderByZeit(final Besucher eingeloggterBesucher) {
return ticketDao.findByBesucherOrderByZeit(eingeloggterBesucher);
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.domain.services;
import de.accso.flexinale.domain.dao.VorfuehrungDao;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.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,70 @@
package de.accso.flexinale.infrastructure;
import de.accso.flexinale.application.Config;
import de.accso.flexinale.shared_kernel.FlexinaleIllegalArgumentException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FlexinaleSpringConfig implements Config {
@Value("${application.title}")
public String applicationTitle;
@Value("${build.version}")
public String buildVersion;
@Value("${build.date}")
public String buildDate;
@Override
public String getApplicationTitle() {
return applicationTitle;
}
@Override
public String getBuildVersion() {
return buildVersion;
}
@Override
public String getBuildDate() {
return buildDate;
}
// -----------------------------------------------------------------------------------------------------------
private final int quoteOnline;
@Override
public int getQuoteOnline() {
return quoteOnline;
}
// -----------------------------------------------------------------------------------------------------------
private final int minZeitZwischenVorfuehrungenInMinuten;
@Override
public int getMinZeitZwischenVorfuehrungenInMinuten() {
return minZeitZwischenVorfuehrungenInMinuten;
}
// -----------------------------------------------------------------------------------------------------------
public FlexinaleSpringConfig(@Value("${de.accso.flexinale.kontingent.quote.online:33}") final int quoteOnline,
@Value("${de.accso.flexinale.vorfuehrung.min-zeit-zwischen-vorfuehrungen-in-minuten:30}") final int minZeitZwischenVorfuehrungenInMinuten) {
if (quoteOnline < 0 || quoteOnline > 100) {
String message = "Quote online should be a percentage, i.e. 0 <= quote online <= 100, but was " + quoteOnline;
throw new FlexinaleIllegalArgumentException(message);
}
this.quoteOnline = quoteOnline;
if (minZeitZwischenVorfuehrungenInMinuten < 0) {
String message = "minZeitZwischenVorfuehrungenInMinuten should be positiv, but was "
+ minZeitZwischenVorfuehrungenInMinuten;
throw new FlexinaleIllegalArgumentException(message);
}
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
}
}

View file

@ -0,0 +1,148 @@
package de.accso.flexinale.infrastructure;
import de.accso.flexinale.application.Config;
import de.accso.flexinale.application.services.FilmUploadService;
import de.accso.flexinale.application.services.KinoUploadService;
import de.accso.flexinale.application.services.KinoSaalUploadService;
import de.accso.flexinale.application.services.VorfuehrungUploadService;
import de.accso.flexinale.domain.dao.*;
import de.accso.flexinale.domain.services.FilmService;
import de.accso.flexinale.domain.services.KinoService;
import de.accso.flexinale.domain.services.TicketService;
import de.accso.flexinale.domain.services.VorfuehrungService;
import de.accso.flexinale.infrastructure.persistence.*;
import de.accso.flexinale.infrastructure.security.BenutzerDao;
import de.accso.flexinale.infrastructure.security.BenutzerJpaRepository;
import de.accso.flexinale.infrastructure.security.BenutzerJpaRepositoryDelegate;
import de.accso.flexinale.infrastructure.security.services.BenutzerUploadService;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableJpaRepositories({"de.accso.flexinale.infrastructure.persistence",
"de.accso.flexinale.infrastructure.security"})
@EnableTransactionManagement
@EntityScan(basePackages={"de.accso.flexinale.infrastructure.persistence",
"de.accso.flexinale.infrastructure.security"})
public class FlexinaleSpringFactory {
@Bean
public FilmService createFilmService(final FilmDao filmDao,
final TicketDao ticketDao,
final VorfuehrungDao vorfuehrungDao,
final Config config) {
return new FilmService(filmDao, vorfuehrungDao, ticketDao, config.getMinZeitZwischenVorfuehrungenInMinuten());
}
@Bean
public FilmUploadService createFilmUploadService(final FilmDao filmDao) {
return new FilmUploadService(filmDao);
}
@Bean
public de.accso.flexinale.application.services.FilmService createApplicationFilmService(final FilmService filmService){
return new de.accso.flexinale.application.services.FilmService(filmService);
}
@Bean
public FilmDao createFilmDao(final FilmJpaRepository filmJpaRepository) {
return new FilmJpaRepositoryDelegate(filmJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public KinoService createKinoService(final KinoDao KinoDao) {
return new KinoService(KinoDao);
}
@Bean
public de.accso.flexinale.application.services.KinoService createApplicationKinoService(final KinoService kinoService){
return new de.accso.flexinale.application.services.KinoService(kinoService);
}
@Bean
public KinoUploadService createKinoUploadService(final KinoDao kinoDao) {
return new KinoUploadService(kinoDao);
}
@Bean
public KinoSaalUploadService createKinoSaalUploadService(final KinoSaalDao kinoSaalDao) {
return new KinoSaalUploadService(kinoSaalDao);
}
@Bean
public KinoDao createKinoDao(final KinoJpaRepository kinoJpaRepository) {
return new KinoJpaRepositoryDelegate(kinoJpaRepository);
}
@Bean
public KinoSaalDao createKinoSaalDao(final KinoSaalJpaRepository kinoSaalJpaRepository) {
return new KinoSaalJpaRepositoryDelegate(kinoSaalJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public VorfuehrungService createVorfuehrungService(final VorfuehrungDao vorfuehrungDao) {
return new VorfuehrungService(vorfuehrungDao);
}
@Bean
public de.accso.flexinale.application.services.VorfuehrungService createApplicationVorfuehrungService(final VorfuehrungService vorfuehrungService){
return new de.accso.flexinale.application.services.VorfuehrungService(vorfuehrungService);
}
@Bean
public VorfuehrungUploadService createVorfuehrungUploadService(final VorfuehrungDao vorfuehrungDao,
final KinoSaalDao kinoSaalDao,
final Config config) {
return new VorfuehrungUploadService(vorfuehrungDao, kinoSaalDao, config);
}
@Bean
public VorfuehrungDao createVorfuehrungDao(final VorfuehrungJpaRepository vorfuehrungJpaRepository) {
return new VorfuehrungJpaRepositoryDelegate(vorfuehrungJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public KontingentDao createKontingentDao(final KontingentJpaRepository kontingentJpaRepository) {
return new KontingentJpaRepositoryDelegate(kontingentJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public TicketService createTicketService(final TicketDao ticketDao,
final VorfuehrungDao vorfuehrungDao,
final KontingentDao kontingentDao) {
return new TicketService(ticketDao, vorfuehrungDao, kontingentDao);
}
@Bean
public de.accso.flexinale.application.services.TicketService createApplicationTicketService(final TicketService ticketService){
return new de.accso.flexinale.application.services.TicketService(ticketService);
}
@Bean
public TicketDao createTicketDao(final TicketJpaRepository ticketJpaRepository) {
return new TicketJpaRepositoryDelegate(ticketJpaRepository);
}
// ------------------------------------------------------------------------------------------------
@Bean
public BenutzerUploadService createBesucherUploadService(final BenutzerDao benutzerDao) {
return new BenutzerUploadService(benutzerDao);
}
@Bean
public BenutzerDao createBesucherDao(final BenutzerJpaRepository benutzerJpaRepository) {
return new BenutzerJpaRepositoryDelegate(benutzerJpaRepository);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,17 @@
package de.accso.flexinale.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TicketJpaRepository extends JpaRepository<TicketEntity, String> {
@Query("SELECT t FROM Ticket t WHERE t.benutzer.id = :benutzerId ORDER BY t.vorfuehrung.zeit")
List<TicketEntity> findByBenutzerOrderByZeit(String benutzerId);
@Query("SELECT count(t) FROM Ticket t WHERE t.benutzer.id = :benutzerId")
int gesamtZahlDerTicketsFuer(String benutzerId);
}

View file

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

View file

@ -0,0 +1,109 @@
package de.accso.flexinale.infrastructure.persistence;
import de.accso.flexinale.shared_kernel.DateTimeHelper;
import de.accso.flexinale.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.shared_kernel.Identifiable;
import de.accso.flexinale.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.time.LocalDateTime;
@jakarta.persistence.Entity(name = "Vorfuehrung")
public final class VorfuehrungEntity implements Identifiable, Versionable, Serializable { // TODO is only final as otherwise Spotbugs
@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.OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public KontingentEntity kontingent;
@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,
final KontingentEntity kontingent) {
this.id = id;
this.version = version;
this.zeit = DateTimeHelper.toEpochSeconds(zeit);
this.film = film;
this.kinoSaal = kinoSaal;
this.kontingent = kontingent;
//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");
}
if (kontingent == null) {
throw new FlexinaleIllegalArgumentException("Kontingent 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)
.append(kontingent, that.kontingent)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(zeit).append(film).append(kinoSaal)
.append(kontingent)
.toHashCode();
}
}

View file

@ -0,0 +1,14 @@
package de.accso.flexinale.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(String filmId);
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,55 @@
package de.accso.flexinale.infrastructure.persistence.mapper;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.Ticket;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.infrastructure.persistence.FilmEntity;
import de.accso.flexinale.infrastructure.persistence.TicketEntity;
import de.accso.flexinale.infrastructure.persistence.VorfuehrungEntity;
import de.accso.flexinale.infrastructure.security.BenutzerEntity;
import de.accso.flexinale.infrastructure.security.mapper.BenutzerEntity2BesucherMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public final class TicketEntity2TicketMapper {
public static Ticket map(final TicketEntity ticketEntity) {
Film mappedFilm = FilmEntity2FilmMapper.map(ticketEntity.film);
Vorfuehrung mappedVorfuehrung = VorfuehrungEntity2VorfuehrungMapper.map(ticketEntity.vorfuehrung);
Besucher mappedBesucher = BenutzerEntity2BesucherMapper.map(ticketEntity.benutzer);
return new Ticket(ticketEntity.id(), ticketEntity.version(),
mappedFilm, mappedVorfuehrung, mappedBesucher,
ticketEntity.verkaufsKanal);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static Optional<Ticket> map(final Optional<TicketEntity> optionalTicketEntity) {
if (optionalTicketEntity.isEmpty()) {
return Optional.empty();
}
else {
TicketEntity ticketEntity = optionalTicketEntity.get();
return Optional.of(map(ticketEntity));
}
}
public static TicketEntity map(final Ticket ticket) {
FilmEntity mappedFilm = FilmEntity2FilmMapper.map(ticket.film);
VorfuehrungEntity mappedVorfuehrung = VorfuehrungEntity2VorfuehrungMapper.map(ticket.vorfuehrung);
BenutzerEntity mappedBenutzer = BenutzerEntity2BesucherMapper.map(ticket.besucher);
return new TicketEntity(ticket.id().id(), ticket.version().version(),
mappedFilm, mappedVorfuehrung, mappedBenutzer,
ticket.verkaufsKanal);
}
public static List<Ticket> map(final List<TicketEntity> ticketEntities) {
return ticketEntities.stream().map(TicketEntity2TicketMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,57 @@
package de.accso.flexinale.infrastructure.persistence.mapper;
import de.accso.flexinale.domain.model.Film;
import de.accso.flexinale.domain.model.KinoSaal;
import de.accso.flexinale.domain.model.Kontingent;
import de.accso.flexinale.domain.model.Vorfuehrung;
import de.accso.flexinale.infrastructure.persistence.FilmEntity;
import de.accso.flexinale.infrastructure.persistence.KinoSaalEntity;
import de.accso.flexinale.infrastructure.persistence.KontingentEntity;
import de.accso.flexinale.infrastructure.persistence.VorfuehrungEntity;
import de.accso.flexinale.shared_kernel.DateTimeHelper;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static de.accso.flexinale.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);
Kontingent mappedKontingent = KontingentEntity2KontingentMapper.map(vorfuehrungEntity.kontingent);
return new Vorfuehrung(Identifiable.Id.of(vorfuehrungEntity.id), vorfuehrungEntity.version(),
new Vorfuehrung.Zeit(DateTimeHelper.fromEpochSeconds(vorfuehrungEntity.zeit)),
mappedFilm, mappedKinoSaal, mappedKontingent);
}
@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);
KontingentEntity mappedKontingent = KontingentEntity2KontingentMapper.map(vorfuehrung.getKontingent());
return new VorfuehrungEntity(vorfuehrung.id().id(), vorfuehrung.version().version(),
getRawOrNull(vorfuehrung.zeit), mappedFilm, mappedKinoSaal, mappedKontingent);
}
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,59 @@
package de.accso.flexinale.infrastructure.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
@Bean
public AuthenticationManager authManager(final HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(authenticationProvider());
return authenticationManagerBuilder.build();
}
@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
// see here: https://www.baeldung.com/spring-security-csrf
// otherwise, the rest POST requests fail with 401 errors
http.csrf(c -> c.ignoringRequestMatchers("/rest/**"));
return http.build();
}
}

View file

@ -0,0 +1,17 @@
package de.accso.flexinale.infrastructure.security;
import de.accso.flexinale.domain.dao.AbstractDao;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
public interface BenutzerDao extends AbstractDao<BenutzerEntity> {
@Override
Optional<BenutzerEntity> findById(final Identifiable.Id id);
List<BenutzerEntity> findByName(String name);
Optional<BenutzerEntity> findByLogin(String login);
}

View file

@ -0,0 +1,45 @@
package de.accso.flexinale.infrastructure.security;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.infrastructure.security.mapper.BenutzerEntity2BesucherMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class BenutzerDetailsService implements UserDetailsService {
@Autowired
private BenutzerJpaRepository benutzerJpaRepository;
@Override
public UserDetails loadUserByUsername(final String login) throws UsernameNotFoundException {
Optional<BenutzerEntity> benutzer = benutzerJpaRepository.findByLogin(login);
if (benutzer.isEmpty()) { // login is unique
throw new UsernameNotFoundException("No Benutzer %s found".formatted(login));
}
return new UserPrincipal(benutzer.get());
}
public Besucher getLoggedInBesucher() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userLogin = authentication.getName();
Optional<BenutzerEntity> benutzer = benutzerJpaRepository.findByLogin(userLogin);
if (benutzer.isEmpty()) {
throw new ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
"None or more than one Besucher with login " + userLogin);
}
else {
return BenutzerEntity2BesucherMapper.map(benutzer.get());
}
}
}

View file

@ -0,0 +1,125 @@
package de.accso.flexinale.infrastructure.security;
import de.accso.flexinale.shared_kernel.Identifiable;
import de.accso.flexinale.shared_kernel.Versionable;
import jakarta.persistence.EnumType;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.io.Serializable;
@jakarta.persistence.Entity(name="Benutzer")
@SuppressWarnings("unused")
public class BenutzerEntity implements Identifiable, Versionable, Serializable {
@SuppressWarnings("unused")
public enum Rolle { // Spring Security needs "ROLE_" as default prefix! Do not change.
ROLE_UNBEKANNT,
ROLE_BESUCHER,
ROLE_ADMIN
}
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column(unique = true)
public String login;
@jakarta.persistence.Column(unique = true)
public String emailAdresse;
@jakarta.persistence.Column
public String name;
@jakarta.persistence.Column
public String vorname;
@jakarta.persistence.Column
@jakarta.persistence.Enumerated(EnumType.STRING)
public Rolle rolle;
@jakarta.persistence.Column
private String passwort;
@jakarta.persistence.Version
private Integer version = 0;
protected BenutzerEntity() {
}
public BenutzerEntity(final String id,
final String login, final String emailAdresse, final String name, final String vorname) {
this(id, Versionable.unknownVersion().version(), login, emailAdresse, name, vorname, Rolle.ROLE_UNBEKANNT);
}
public BenutzerEntity(final String id, final Integer version,
final String login, final String emailAdresse, final String name, final String vorname) {
this(id, version, login, emailAdresse, name, vorname, Rolle.ROLE_UNBEKANNT);
}
public BenutzerEntity(final String id,
final String login, final String emailAdresse, final String name, final String vorname, Rolle rolle) {
this(id, Versionable.unknownVersion().version(), login, emailAdresse, name, vorname, rolle);
}
public BenutzerEntity(final String id, final Integer version,
final String login, final String emailAdresse, final String name, final String vorname, Rolle rolle) {
this.id = id;
this.version = version;
this.login = login;
this.emailAdresse = emailAdresse;
this.name = name;
this.vorname = vorname;
this.rolle = rolle;
}
@Override
public Id id() {
return Identifiable.Id.of(id);
}
@Override
public Version version() {
return Versionable.Version.of(version);
}
public String getPasswort() { //TODO do we need this?
return passwort;
}
public void encryptAndSetPassword(final String passwort) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
this.passwort = encoder.encode(passwort);
}
@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;}
BenutzerEntity that = (BenutzerEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(login, that.login)
.append(emailAdresse, that.emailAdresse)
.append(name, that.name)
.append(vorname, that.vorname).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(login)
.append(emailAdresse).append(name).append(vorname).toHashCode();
}
}

View file

@ -0,0 +1,21 @@
package de.accso.flexinale.infrastructure.security;
import org.springframework.data.annotation.Immutable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Immutable
@Repository
public interface BenutzerJpaRepository extends JpaRepository<BenutzerEntity, String> {
@Query("SELECT b FROM Benutzer b WHERE b.name = :name")
List<BenutzerEntity> findByName(@Param("name") String name);
@Query("SELECT b FROM Benutzer b WHERE b.login = :login")
Optional<BenutzerEntity> findByLogin(@Param("login") String login);
}

View file

@ -0,0 +1,56 @@
package de.accso.flexinale.infrastructure.security;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("unused")
public class BenutzerJpaRepositoryDelegate implements BenutzerDao {
private final BenutzerJpaRepository benutzerJpaRepository;
public BenutzerJpaRepositoryDelegate(BenutzerJpaRepository BenutzerJpaRepository) {
this.benutzerJpaRepository = BenutzerJpaRepository;
}
@Override
public List<BenutzerEntity> findAll() {
return benutzerJpaRepository.findAll();
}
@Override
public Optional<BenutzerEntity> findById(final Identifiable.Id id) {
return benutzerJpaRepository.findById(id.id());
}
@Override
public List<BenutzerEntity> findByName(final String name) {
return benutzerJpaRepository.findByName(name);
}
@Override
public Optional<BenutzerEntity> findByLogin(final String login) {
return benutzerJpaRepository.findByLogin(login);
}
@Override
public BenutzerEntity save(final BenutzerEntity benutzerEntity) {
return benutzerJpaRepository.save(benutzerEntity);
}
@Override
public void delete(final BenutzerEntity benutzerEntity) {
benutzerJpaRepository.delete(benutzerEntity);
}
@Override
public void deleteById(final Identifiable.Id id) {
benutzerJpaRepository.deleteById(id.id());
}
@Override
public void deleteAll() {
benutzerJpaRepository.deleteAll();
}
}

View file

@ -0,0 +1,8 @@
package de.accso.flexinale.infrastructure.security;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}

View file

@ -0,0 +1,42 @@
package de.accso.flexinale.infrastructure.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class UserPrincipal implements UserDetails {
private final BenutzerEntity benutzer;
public UserPrincipal(BenutzerEntity user) {
this.benutzer = user;
}
@Override
@SuppressWarnings("Convert2Lambda")
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
GrantedAuthority authority = new GrantedAuthority() {
@Override
public String getAuthority() {
return benutzer.rolle.toString();
}
};
authorities.add(authority);
return authorities;
}
@Override
public String getPassword() {
return benutzer.getPasswort();
}
@Override
public String getUsername() {
return benutzer.login;
}
}

View file

@ -0,0 +1,44 @@
package de.accso.flexinale.infrastructure.security.mapper;
import de.accso.flexinale.domain.model.Besucher;
import de.accso.flexinale.infrastructure.security.BenutzerEntity;
import de.accso.flexinale.shared_kernel.Identifiable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static de.accso.flexinale.shared_kernel.RawWrapper.getRawOrNull;
@SuppressWarnings("unused")
public class BenutzerEntity2BesucherMapper {
public static Besucher map(final BenutzerEntity benutzerEntity) {
return new Besucher(Identifiable.Id.of(benutzerEntity.id), benutzerEntity.version(),
new Besucher.Login(benutzerEntity.login), new Besucher.EmailAdresse(benutzerEntity.emailAdresse),
new Besucher.Name(benutzerEntity.name), new Besucher.Vorname(benutzerEntity.vorname));
}
@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "unused"})
public static Optional<Besucher> map(final Optional<BenutzerEntity> optionalBenutzerEntity) {
if (optionalBenutzerEntity.isEmpty()) {
return Optional.empty();
}
else {
BenutzerEntity benutzerEntity = optionalBenutzerEntity.get();
return Optional.of(map(benutzerEntity));
}
}
public static BenutzerEntity map(final Besucher besucher) {
return new BenutzerEntity(besucher.id().id(), besucher.version().version(),
getRawOrNull(besucher.login), getRawOrNull(besucher.emailAdresse),
getRawOrNull(besucher.name), getRawOrNull(besucher.vorname));
}
public static List<Besucher> map(final List<BenutzerEntity> benutzerEntities) {
return benutzerEntities.stream().map(BenutzerEntity2BesucherMapper::map)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View file

@ -0,0 +1,64 @@
package de.accso.flexinale.infrastructure.security.services;
import de.accso.flexinale.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.domain.dao.AbstractDao;
import de.accso.flexinale.infrastructure.security.BenutzerDao;
import de.accso.flexinale.infrastructure.security.BenutzerEntity;
import de.accso.flexinale.shared_kernel.Versionable;
import jakarta.transaction.Transactional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Transactional
public class BenutzerUploadService extends AbstractExcelDataUploadService<BenutzerEntity> {
private static final Logger LOGGER = LoggerFactory.getLogger(BenutzerUploadService.class);
private final BenutzerDao benutzerEntityDao;
public BenutzerUploadService(final BenutzerDao benutzerEntityDao) {
this.benutzerEntityDao = benutzerEntityDao;
}
@Override
public AbstractDao<BenutzerEntity> getDao() {
return benutzerEntityDao;
}
@Override
public String getNameOfExcelDataType() {
return "Benutzer";
}
@Override
public BenutzerEntity createDataFromExcelRow(final Row excelRow) {
String benutzerId = excelRow.getCell(0).getStringCellValue();
// set authorization roles
Cell cellRolle = excelRow.getCell(6);
BenutzerEntity.Rolle rolle;
if (cellRolle == null || cellRolle.getCellType() == CellType.BLANK) {
rolle = null;
}
else {
rolle = BenutzerEntity.Rolle.valueOf(cellRolle.getStringCellValue());
}
BenutzerEntity benutzer = new BenutzerEntity(
benutzerId, Versionable.unknownVersion().version(),
excelRow.getCell(1).getStringCellValue(),
excelRow.getCell(3).getStringCellValue(),
excelRow.getCell(4).getStringCellValue(),
excelRow.getCell(5).getStringCellValue(),
rolle
);
benutzer.encryptAndSetPassword(excelRow.getCell(2).getStringCellValue());
LOGGER.debug("New Benutzer created: " + benutzer);
return benutzer;
}
}

View file

@ -0,0 +1,14 @@
package de.accso.flexinale.shared_kernel;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
public final class DateTimeHelper {
public static LocalDateTime fromEpochSeconds(final long epochSeconds) {
return LocalDateTime.ofEpochSecond(epochSeconds, 0, ZoneOffset.UTC);
}
public static long toEpochSeconds(final LocalDateTime dateTime) {
return dateTime.toEpochSecond(ZoneOffset.UTC);
}
}

View file

@ -0,0 +1,24 @@
package de.accso.flexinale.shared_kernel;
public class DeveloperMistakeException extends RuntimeException {
public DeveloperMistakeException(final String message) {
super(message);
}
public DeveloperMistakeException() {
super();
}
public DeveloperMistakeException(final String message, final Throwable cause) {
super(message, cause);
}
public DeveloperMistakeException(final Throwable cause) {
super(cause);
}
protected DeveloperMistakeException(final String message, final Throwable cause,
final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,8 @@
package de.accso.flexinale.shared_kernel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value = RetentionPolicy.RUNTIME)
public @interface DoNotCheckInArchitectureTests {
}

View file

@ -0,0 +1,6 @@
package de.accso.flexinale.shared_kernel;
@SuppressWarnings("unused")
public interface EqualsByContent {
boolean equalsByContent(final Object o);
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.shared_kernel;
@SuppressWarnings("unused")
public class FlexinaleIllegalArgumentException extends DeveloperMistakeException {
public FlexinaleIllegalArgumentException() {
super();
}
public FlexinaleIllegalArgumentException(final String message) {
super(message);
}
public FlexinaleIllegalArgumentException(final String message, final Throwable cause) {
super(message, cause);
}
public FlexinaleIllegalArgumentException(final Throwable cause) {
super(cause);
}
protected FlexinaleIllegalArgumentException(final String message, final Throwable cause,
final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,20 @@
package de.accso.flexinale.shared_kernel;
@SuppressWarnings("unused")
public class FlexinaleIllegalStateException extends IllegalStateException {
public FlexinaleIllegalStateException() {
super();
}
public FlexinaleIllegalStateException(final String message) {
super(message);
}
public FlexinaleIllegalStateException(final String message, final Throwable cause) {
super(message, cause);
}
public FlexinaleIllegalStateException(final Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,31 @@
package de.accso.flexinale.shared_kernel;
import java.io.Serializable;
import java.util.UUID;
public interface Identifiable {
record Id(String id) implements Serializable, RawWrapper<String> {
public static Id of() {
return uuid();
}
public static Id of(String id) {
return new Id(id);
}
public static Id uuid() {
return Id.of(UUID.randomUUID().toString());
}
public static String uuidString() {
return UUID.randomUUID().toString();
}
@Override
public String raw() {
return id;
}
}
Id id();
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.shared_kernel;
import java.io.Serializable;
public interface RawWrapper<T> extends Serializable {
T raw();
default String print() { // unfortunately one cannot override toString() here, results in compiler error
return raw().toString();
}
@SuppressWarnings("unchecked")
static <T> T getRawOrNull(Object o) {
if (o == null)
return null;
else
try {
RawWrapper<T> castedObject = (RawWrapper<T>)o;
return castedObject.raw();
}
catch (ClassCastException ccex) {
throw new DeveloperMistakeException(ccex);
}
}
}

View file

@ -0,0 +1,14 @@
package de.accso.flexinale.shared_kernel;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeFormatter {
public static String calculateString(final LocalDateTime from, final Integer durationInMinutes) {
DateTimeFormatter hourMinuteFormatter = DateTimeFormatter.ofPattern("HH:mm");
return hourMinuteFormatter.format(from)
+ " - "
+ hourMinuteFormatter.format(from.plusMinutes(durationInMinutes));
}
}

View file

@ -0,0 +1,32 @@
package de.accso.flexinale.shared_kernel;
import java.io.Serializable;
public interface Versionable {
record Version(Integer version) implements Serializable, RawWrapper<Integer> {
public static Version of(Integer version) {
return new Version(version);
}
public Version inc() {
return new Version(this.version()+1);
}
@Override
public Integer raw() {
return version;
}
}
Version version();
static Version unknownVersion() {
Integer UNKNOWN = -1;
return Version.of(UNKNOWN);
}
static Version initialVersion() {
Integer INITIAL = 0;
return Version.of(INITIAL);
}
}

View file

@ -0,0 +1,53 @@
application.title=FLEXinale as a Modulith, Step 1 with Onion Architecture
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/modulith-1
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
#########################################################################
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
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=8080
#########################################################################
# Security
#########################################################################
security.enable-csrf=true
#########################################################################
# 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,10 @@
-------------------------------------------------------------------------
,------. ,--. ,------. ,--. ,--. ,--. ,--.
| .---' | | | .---' \ `.' / `--' ,--,--, ,--,--. | | ,---.
| `--, | | | `--, .' \ ,--. | \ ' ,-. | | | | .-. :
| |` | '--. | `---. / .'. \ | | | || | \ '-' | | | \ --.
`--' `-----' `------' '--' '--' `--' `--''--' `--`--' `--' `----'
${application.title} on port ${server.port}
(version ${application.version})
Powered by Spring Boot ${spring-boot.version}
-------------------------------------------------------------------------

View file

@ -0,0 +1,11 @@
<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="de.accso" level="INFO"/>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1,44 @@
<!--
Copyright 2016 Google Inc.
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.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Clear Cache</title>
</head>
<body>
<button id="button">Clear</button>
<script>
button.addEventListener('click', function() {
navigator.serviceWorker && navigator.serviceWorker.register('/sw.js').then(function(registration) {
caches.delete('your-magic-cache').then(() => {
alert('ok');
}, () => {
alert('Failed!');
});
}).catch(err => {
alert('Couldn\'t get Service Worker- is it installed?');
});
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,92 @@
// Copyright 2017 Google Inc.
//
// 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.
function urlB64ToUint8Array(base64String) {
const suffixLength = 4 - base64String.length % 4;
const base64 = (base64String + '='.repeat(suffixLength))
.replace(/\-/g, '+')
.replace(/_/g, '/');
const raw = window.atob(base64);
const output = new Uint8Array(raw.length);
Array.from(raw).forEach((c, i) => output[i] = c.charCodeAt(0));
return output;
}
function stringToBase64Url(s) {
const base64 = window.btoa(s);
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
}
function JSONToBase64Url(data) {
const s = JSON.stringify(data);
return stringToBase64Url(s);
}
function uint8ArrayToBase64Url(array) {
const s = String.fromCodePoint(...array);
return stringToBase64Url(s);
}
/**
* @param {string} publicKey public key in URL-safe base64
* @param {string} privateKey private key in URL-safe base64
* @param {string} endpoint from PushSubscription
* @param {string} sender either "mailto:<email>" or a web address
* @return {!Promise<string>} the Authorization header
*/
function prepareAuthorization(publicKey, privateKey, endpoint, sender) {
const origin = new URL(endpoint).origin;
const defaultExpiration = Math.floor(Date.now() / 1000) + 43200; // 12 hours in future
const header = {
typ: 'JWT',
alg: 'ES256'
};
const jwtPayload = {
aud: origin,
exp: defaultExpiration,
sub: sender,
};
// unsignedToken is the URL-safe base64 encoded JSON header and body joined by a dot.
const unsignedToken = JSONToBase64Url(header) + '.' + JSONToBase64Url(jwtPayload);
// Sign unsignedToken using ES256 (SHA-256 over ECDSA). This requires the private key.
const publicKeyArray = urlB64ToUint8Array(publicKey);
const key = {
kty: 'EC',
crv: 'P-256',
x: uint8ArrayToBase64Url(publicKeyArray.subarray(1, 33)),
y: uint8ArrayToBase64Url(publicKeyArray.subarray(33, 65)),
d: privateKey,
};
// Perform the signing. importKey returns a Promise, so wait for it to finish.
const args = {name: 'ECDSA', namedCurve: 'P-256'};
return crypto.subtle.importKey('jwk', key, args, true, ['sign'])
.then(key => {
return crypto.subtle.sign({
name: 'ECDSA',
hash: {
name: 'SHA-256',
},
}, key, (new TextEncoder('utf-8')).encode(unsignedToken));
})
.then(buffer => new Uint8Array(buffer))
.then(signature => {
return 'WebPush ' + unsignedToken + '.' + uint8ArrayToBase64Url(signature);
});
}

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