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 414504d225
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 FlexinaleModulith2ComponentsApplication {
public static void main(String[] args) {
SpringApplication.run(FlexinaleModulith2ComponentsApplication.class, args);
}
@Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,123 @@
package de.accso.flexinale.backoffice.domain.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.time.LocalDateTime;
public final class Vorfuehrung implements Identifiable, Versionable, EqualsByContent { // TODO is only final as otherwise Spotbugs complains, get rid of Exception in constructor!
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public final Id id;
public final Version version;
public final Zeit zeit;
@DoNotCheckInArchitectureTests
public final Film film;
@DoNotCheckInArchitectureTests
public final KinoSaal kinoSaal;
public Vorfuehrung(final Id id,
final Zeit zeit,
final Film film,
final KinoSaal kinoSaal) {
this(id, Versionable.unknownVersion(), zeit, film, kinoSaal);
}
public Vorfuehrung(final Id id, final Version version,
final Zeit zeit,
final Film film,
final KinoSaal kinoSaal) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
//TODO this validation should not be done here. If class is not final, Spotbugs complains. For DB entities use "nullable=false" and "optional=false" for DB checks. In domain classes check with jakarta.annotation NonNull? Also: Why is KinoSaal obligatory but not Film?
if (kinoSaal == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal of Vorfuehrung " + this +
" must not be null");
}
//TODO this validation should not be done here but in KinoSaal itself
if (kinoSaal.anzahlPlaetze == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal's Anzahl Plaetze of Vorfuehrung " + this +
" must not be null");
}
}
@Override
public Id id() {
return id;
}
@Override
public Version version() {
return version;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (!equalsByContent(o)) return false;
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Vorfuehrung that = (Vorfuehrung) o;
return new EqualsBuilder().append(version, that.version).isEquals();
}
@SuppressWarnings({"UnusedAssignment", "DataFlowIssue"})
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Vorfuehrung that = (Vorfuehrung) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(zeit, that.zeit)
.isEquals();
if (!result) return false;
if (film == null && that.film == null) result = true;
if (film == null && that.film != null) return false;
if (film != null) {
result = film.equalsByContent(that.film);
if (!result) return false;
}
if (kinoSaal == null && that.kinoSaal == null) result = true;
if (kinoSaal == null && that.kinoSaal != null) return false;
if (kinoSaal != null) {
result = kinoSaal.equalsByContent(that.kinoSaal);
return result;
}
return true;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(zeit)
.append(film)
.append(kinoSaal)
.toHashCode();
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,78 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
@jakarta.persistence.Entity(name = "Film")
public class FilmEntity implements Identifiable, Versionable, Serializable {
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column
public String titel;
@jakarta.persistence.Column
public String imdbUrl;
@jakarta.persistence.Column
public Integer dauerInMinuten;
@jakarta.persistence.Version
private Integer version = 0;
protected FilmEntity() {
}
public FilmEntity(final String id, final Integer version,
final String titel, final String imdbUrl, final Integer dauerInMinuten) {
this.id = id;
this.version = version;
this.titel = titel;
this.imdbUrl = imdbUrl;
this.dauerInMinuten = dauerInMinuten;
}
@Override
public Id id() {
return Identifiable.Id.of(id);
}
@Override
public Version version() {
return Versionable.Version.of(version);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
FilmEntity that = (FilmEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(titel, that.titel)
.append(imdbUrl, that.imdbUrl)
.append(dauerInMinuten, that.dauerInMinuten)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(titel).append(imdbUrl).append(dauerInMinuten).toHashCode();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,96 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.DateTimeHelper;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalArgumentException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
import java.time.LocalDateTime;
@jakarta.persistence.Entity(name = "Vorfuehrung")
public final class VorfuehrungEntity implements Identifiable, Versionable, Serializable { // TODO is only final as otherwise Spotbugs complains, get rid of Exception in constructor!
@jakarta.persistence.Id
public String id; // primary key
@jakarta.persistence.Column
public Long zeit; // save EPOCH seconds instead of Date/Time (Timezone is UTC)
@jakarta.persistence.ManyToOne
public FilmEntity film;
@jakarta.persistence.ManyToOne
public KinoSaalEntity kinoSaal;
@jakarta.persistence.Version
private Integer version = 0;
protected VorfuehrungEntity() {
}
public VorfuehrungEntity(final String id, final Integer version,
final LocalDateTime zeit,
final FilmEntity film,
final KinoSaalEntity kinoSaal) {
this.id = id;
this.version = version;
this.zeit = DateTimeHelper.toEpochSeconds(zeit);
this.film = film;
this.kinoSaal = kinoSaal;
//TODO this validation should not be done here. If class is not final, Spotbugs complains. For DB entities use "nullable=false" and "optional=false" for DB checks. In domain classes check with jakarta.annotation NonNull? Also: Why is KinoSaal obligatory but not Film?
if (kinoSaal == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal of Vorfuehrung " + this +
" must not be null");
}
//TODO this validation should not be done here but in KinoSaal itself
if (kinoSaal.anzahlPlaetze == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal's Anzahl Plaetze of Vorfuehrung " + this +
" must not be null");
}
}
@Override
public Id id() {
return Identifiable.Id.of(id);
}
@Override
public Version version() {
return Versionable.Version.of(version);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
VorfuehrungEntity that = (VorfuehrungEntity) o;
return new EqualsBuilder()
.append(id, that.id).append(version, that.version)
.append(zeit, that.zeit)
.append(film, that.film).append(kinoSaal, that.kinoSaal)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(zeit).append(film).append(kinoSaal)
.toHashCode();
}
}

View file

@ -0,0 +1,14 @@
package de.accso.flexinale.backoffice.infrastructure.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface VorfuehrungJpaRepository extends JpaRepository<VorfuehrungEntity, String> {
@Query("SELECT v FROM Vorfuehrung v WHERE v.film.id = :filmId ORDER BY v.zeit")
List<VorfuehrungEntity> findByFilmId(final String filmId);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,60 @@
package de.accso.flexinale.backoffice_api_contract.api_contract.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public record KinoTO(Id id, Version version,
Name name, Adresse adresse, EmailAdresse emailAdresse,
@DoNotCheckInArchitectureTests Set<KinoSaalTO> kinoSaele)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record Name(String raw) implements RawWrapper<String> {}
public record Adresse(String raw) implements RawWrapper<String> {}
public record EmailAdresse(String raw) implements RawWrapper<String> {}
public KinoTO(final Id id, final Version version) {
this(id, version, new KinoTO.Name(""), new KinoTO.Adresse(""), new KinoTO.EmailAdresse(""),
new HashSet<>()); // cannot use Set.of() as this would be an ImmutableCollections.EMPTY_SET
}
public KinoTO(final Id id,
final Name name, final Adresse adresse,
final EmailAdresse emailAdresse, final Set<KinoSaalTO> kinoSaele) {
this(id, Versionable.unknownVersion(), name, adresse, emailAdresse, kinoSaele);
}
@SuppressWarnings({"ConstantValue", "unused"})
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
KinoTO that = (KinoTO) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(name, that.name)
.append(adresse, that.adresse).append(emailAdresse, that.emailAdresse)
.isEquals();
if (!result) return false;
if (kinoSaele == null && that.kinoSaele == null) return true;
if (kinoSaele == null && that.kinoSaele != null) return false;
if (kinoSaele != null && that.kinoSaele == null) return false;
List<KinoSaalTO> thisKSList = kinoSaele.stream().toList();
List<KinoSaalTO> thatKSList = that.kinoSaele.stream().toList();
if (thisKSList.size() != thatKSList.size()) return false;
for (int counter = 0; counter < thisKSList.size(); counter++) {
if (!thisKSList.get(counter).equalsByContent(thatKSList.get(counter))) return false;
}
return true;
}
}

View file

@ -0,0 +1,68 @@
package de.accso.flexinale.backoffice_api_contract.api_contract.model;
import de.accso.flexinale.common.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import java.io.Serializable;
import java.time.LocalDateTime;
public record VorfuehrungTO(Id id, Version version,
Zeit zeit,
@DoNotCheckInArchitectureTests FilmTO film,
@DoNotCheckInArchitectureTests KinoSaalTO kinoSaal)
implements Identifiable, Versionable, EqualsByContent, Serializable
{
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
public Zeit(LocalDateTime raw) {
this.raw = raw.withNano(0); // precision is second
}
}
public VorfuehrungTO(final Id id, final Version version,
final Zeit zeit,
final FilmTO film,
final KinoSaalTO kinoSaal) {
this.id = id;
this.version = version;
this.zeit = zeit;
this.film = film;
this.kinoSaal = kinoSaal;
//TODO this validation should not be done here. If class is not final, Spotbugs complains. For DB entities use "nullable=false" and "optional=false" for DB checks. In domain classes check with jakarta.annotation NonNull? Also: Why is KinoSaal obligatory but not Film?
if (kinoSaal == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal of Vorfuehrung " + this +
" must not be null");
}
//TODO this validation should not be done here but in KinoSaal itself
if (kinoSaal.anzahlPlaetze() == null) {
throw new FlexinaleIllegalArgumentException("KinoSaal's Anzahl Plaetze of Vorfuehrung " + this +
" must not be null");
}
}
@SuppressWarnings({"UnusedAssignment", "DataFlowIssue", "unused"})
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
VorfuehrungTO that = (VorfuehrungTO) o;
boolean result = new EqualsBuilder()
.append(id, that.id)
.append(film, that.film)
.append(zeit, that.zeit)
.isEquals();
if (!result) return false;
if (kinoSaal == null && that.kinoSaal == null) result = true;
if (kinoSaal == null && that.kinoSaal != null) return false;
if (kinoSaal != null) {
result = kinoSaal.equalsByContent(that.kinoSaal);
return result;
}
return true;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,19 @@
package de.accso.flexinale.common.domain.dao;
import de.accso.flexinale.common.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,70 @@
package de.accso.flexinale.common.infrastructure;
import de.accso.flexinale.common.application.Config;
import de.accso.flexinale.common.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,14 @@
package de.accso.flexinale.common.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.common.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.common.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.common.shared_kernel;
@SuppressWarnings("unused")
public interface EqualsByContent {
boolean equalsByContent(final Object o);
}

View file

@ -0,0 +1,25 @@
package de.accso.flexinale.common.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.common.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.common.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.common.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.common.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,31 @@
package de.accso.flexinale.common.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,59 @@
package de.accso.flexinale.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,48 @@
package de.accso.flexinale.security;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerEntity;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerJpaRepository;
import de.accso.flexinale.security_api_contract.api_contract.BesucherRetriever;
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, BesucherRetriever {
@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());
}
@Override
public Identifiable.Id getIdOfLoggedInBesucher() {
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 benutzer.get().id();
}
}
}

View file

@ -0,0 +1,8 @@
package de.accso.flexinale.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,43 @@
package de.accso.flexinale.security;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerEntity;
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,29 @@
package de.accso.flexinale.security.infrastructure;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerDao;
import de.accso.flexinale.security.infrastructure.services.BenutzerUploadService;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerJpaRepository;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerJpaRepositoryDelegate;
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.security.infrastructure.persistence"})
@EnableTransactionManagement
@EntityScan(basePackages={"de.accso.flexinale.security.infrastructure.persistence"})
public class FlexinaleSecuritySpringFactory {
// ------------------------------------------------------------------------------------------------
@Bean
public BenutzerUploadService createBenutzerUploadService(final BenutzerDao benutzerDao) {
return new BenutzerUploadService(benutzerDao);
}
@Bean
public BenutzerDao createBenutzerDao(final BenutzerJpaRepository benutzerJpaRepository) {
return new BenutzerJpaRepositoryDelegate(benutzerJpaRepository);
}
}

View file

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

View file

@ -0,0 +1,125 @@
package de.accso.flexinale.security.infrastructure.persistence;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.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, final 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, final 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() {
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.security.infrastructure.persistence;
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.security.infrastructure.persistence;
import de.accso.flexinale.common.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,64 @@
package de.accso.flexinale.security.infrastructure.services;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.application.services.AbstractExcelDataUploadService;
import de.accso.flexinale.common.shared_kernel.Versionable;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerDao;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerEntity;
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(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,8 @@
package de.accso.flexinale.security_api_contract.api_contract;
import de.accso.flexinale.common.shared_kernel.Identifiable;
public interface BesucherRetriever {
Identifiable.Id getIdOfLoggedInBesucher();
}

View file

@ -0,0 +1,46 @@
package de.accso.flexinale.ticketing.application.services;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.dao.TicketDao;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import de.accso.flexinale.ticketing_api_contract.api_contract.TicketRetriever;
import de.accso.flexinale.ticketing_api_contract.api_contract.model.TicketTO;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Transactional
public class TicketRetrieverService implements TicketRetriever {
private final TicketDao ticketDao;
public TicketRetrieverService(final TicketDao ticketDao) {
this.ticketDao = ticketDao;
}
@Override
public List<TicketTO> ticketsByBesucherOrderByZeit(final Identifiable.Id besucherId) {
List<Ticket> tickets = ticketDao.findByBesucherOrderByZeit(besucherId);
return tickets.stream()
.map(this::map)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override
public int gesamtZahlDerTicketsFuer(final Identifiable.Id besucherId) {
return ticketDao.gesamtZahlDerTicketsFuer(besucherId);
}
private TicketTO map(final Ticket ticket) {
TicketTO.VerkaufsKanal verkaufsKanal = switch (ticket.verkaufsKanal) {
case ONLINE -> TicketTO.VerkaufsKanal.ONLINE;
case ZENTRAL -> TicketTO.VerkaufsKanal.ZENTRAL;
case KINOKASSE -> TicketTO.VerkaufsKanal.KINOKASSE;
};
return new TicketTO(ticket.id(), ticket.version(), ticket.filmId, ticket.vorfuehrungId,
ticket.besucherId, verkaufsKanal);
}
}

View file

@ -0,0 +1,36 @@
package de.accso.flexinale.ticketing.application.services;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.ticketing.domain.services.KontingentService;
import de.accso.flexinale.ticketing_api_contract.api_contract.Ticketing;
import jakarta.transaction.Transactional;
@Transactional
public class TicketService implements Ticketing {
final de.accso.flexinale.ticketing.domain.services.TicketService ticketService;
final KontingentService kontingentService;
public TicketService(final de.accso.flexinale.ticketing.domain.services.TicketService ticketService, final KontingentService kontingentService) {
this.ticketService = ticketService;
this.kontingentService = kontingentService;
}
@Override
public void gebeVorfuehrungInDenVerkauf(final Identifiable.Id vorfuehrungId, final int kapazitaet) {
ticketService.gebeVorfuehrungInDenVerkauf(vorfuehrungId, kapazitaet);
}
@Override
public void loeseGutscheineOnlineFuerVorfuehrungEin(final Identifiable.Id vorfuehrungId, final Identifiable.Id filmId,
final Identifiable.Id benutzerId,
final int anzahlGutscheine) {
ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrungId, filmId, benutzerId, anzahlGutscheine);
}
@Override
public int getRestkontingentOnline(final Identifiable.Id vorfuehrungId) {
return kontingentService.getRestkontingentOnline(vorfuehrungId);
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,98 @@
package de.accso.flexinale.ticketing.domain.model;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.common.shared_kernel.EqualsByContent;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Ticket implements Identifiable, Versionable, EqualsByContent {
public enum VerkaufsKanal {
ONLINE,
ZENTRAL,
KINOKASSE
}
public final Id id;
public final Version version;
public final Id filmId;
public final Id vorfuehrungId;
public final Id besucherId;
@DoNotCheckInArchitectureTests
public VerkaufsKanal verkaufsKanal;
//TODO why would we need to set the ID from outside at all? Why not leave it to the class internally?
public Ticket(final Id id,
final Id filmId, final Id vorfuehrungId, final Id besucherId,
final VerkaufsKanal verkaufsKanal) {
this(id, Versionable.unknownVersion(), filmId, vorfuehrungId, besucherId, verkaufsKanal);
}
public Ticket(final Id id, final Version version,
final Id filmId, final Id vorfuehrungId, final Id besucherId,
final VerkaufsKanal verkaufsKanal) {
this.id = id;
this.version = version;
this.filmId = filmId;
this.vorfuehrungId = vorfuehrungId;
this.besucherId = besucherId;
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();
}
@Override
public boolean equalsByContent(final Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Ticket that = (Ticket) o;
return new EqualsBuilder()
.append(id, that.id)
.append(filmId, that.filmId)
.append(vorfuehrungId, that.vorfuehrungId)
.append(besucherId, that.besucherId)
.append(verkaufsKanal, that.verkaufsKanal)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(version)
.append(filmId).append(vorfuehrungId).append(besucherId)
.append(verkaufsKanal)
.toHashCode();
}
}

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