de.svws_nrw.data.DataManagerRevised Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of svws-db-utils Show documentation
Show all versions of svws-db-utils Show documentation
Diese Bibliothek unterstützt bei dem Zugriff auf Datenbanken für die Schulverwaltungssoftware in NRW
package de.svws_nrw.data;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import de.svws_nrw.core.types.benutzer.BenutzerKompetenz;
import de.svws_nrw.db.Benutzer;
import de.svws_nrw.db.DBEntityManager;
import de.svws_nrw.db.dto.current.schild.klassen.DTOKlassen;
import de.svws_nrw.db.dto.current.schild.schule.DTOSchuljahresabschnitte;
import de.svws_nrw.db.utils.ApiOperationException;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
/**
* Diese abstrakte Klasse ist die Grundlage für das einheitliche Aggregieren von
* Informationen für die OpenAPI und das einheitliche Bereitstellen von
* Funktionalitäten für GET-, CREATE-, PATCH- und DELETE-Operationen.
*
* @param die Typ, welcher als ID für die Informationen verwendet wird.
* @param der Typ des zugrundeliegenden Datenbank-DTOs
* @param der Typ des zugrundeliegenden Core-DTOs
*/
public abstract class DataManagerRevised {
/** Die Klasse des zugehörigen Datenbank-DTOs */
final Class classDatabaseDTO;
/** Die Datenbank-Verbindung zum Aggregieren der Informationen aus der DB und zum Schreiben der Informationen bzw. Teilinformationen */
protected final DBEntityManager conn;
/** Ein Set von Attributen, welche neben der ID explizit notwendig sind und beim Erstellen eines DTOs gemappt werden müssen. */
private final Set attributesRequiredOnCreation = new HashSet<>();
/** Ein Set von Attributen, wo das Patchen explizit verboten ist und nur beim Erstellen von DTOs ein Mapping erlaubt ist. */
private final Set attributesNotPatchable = new HashSet<>();
/** Ein Set von Attributen, wo das Mapping beim Hinzufügen in einem zweiten Schritt passiert, nachdem das Datenbank-DTO ein erstes Mal persistiert wurde. */
private final Set attributesDelayedOnCreation = new HashSet<>();
/**
* Erstellt einen neuen Datenmanager mit der angegebenen Verbindung
*
* @param conn die Datenbank-Verbindung, welche vom Daten-Manager benutzt werden soll
*/
protected DataManagerRevised(final DBEntityManager conn) {
this.conn = Objects.requireNonNull(conn, "DBEntityManager darf nicht null sein.");
this.classDatabaseDTO = getClassDatabaseDTO();
}
/**
* Methode liefert die Class des Generics für das Datenbank-DTO.
*
* @return Class des Datenbank-DTO
*/
@SuppressWarnings("unchecked")
Class getClassDatabaseDTO() {
final ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
return ((Class) type.getActualTypeArguments()[1]);
}
/**
* Methode liefert die Class des Generics für die ID.
*
* @return Class der ID
*/
@SuppressWarnings("unchecked")
Class getClassID() {
final ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
return ((Class) type.getActualTypeArguments()[0]);
}
/**
* Setzt die Attribute, welche neben der ID explizit notwendig sind und beim Erstellen eines DTOs gemappt werden müssen.
*
* @param attrs die Attribute
*/
protected void setAttributesRequiredOnCreation(final String... attrs) {
attributesRequiredOnCreation.clear();
attributesRequiredOnCreation.addAll(Arrays.asList(attrs));
}
/**
* Setzt die Attribute, wo das Patchen explizit verboten ist und nur beim Erstellen von DTOs ein Mapping erlaubt ist.
*
* @param attrs die Attribute
*/
protected void setAttributesNotPatchable(final String... attrs) {
attributesNotPatchable.clear();
attributesNotPatchable.addAll(Arrays.asList(attrs));
}
/**
* Setzt die Attribute, wo das Mapping beim Hinzufügen in einem Zweiten Schritt passiert, nachdem das Datenbank-DTO ein erstes
* mal persistiert wurde. Dies dient z.B. dazu, dass das Patchen eines Attributes ggf. auch Einfluss auf andere Datenbank-Tabellen
* hat, wo eine Fremdschlüsselbeziehung besteht.
*
* @param attrs die Attribute
*/
protected void setAttributesDelayedOnCreation(final String... attrs) {
attributesDelayedOnCreation.clear();
attributesDelayedOnCreation.addAll(Arrays.asList(attrs));
}
/**
* Erzeugt bzw. ermittelt die custom ID für ein neues oder zu veränderndes Datenbank-DTO anhand der übergebenen Attribute.
* Wichtig: Wird eine neue ID benötigt, die abweichend von der Default Implementierung in der Methode {@link #createNextLongID(Long)} ist oder
* nicht vom Typ {@link Long} ist, muss diese Methode überschrieben werden. Ebenfalls muss die Methode implementiert werden, wenn die Patch-Operation
* {@link #patchMultipleAsResponse(InputStream)} genutzt wird.
*
* @param attributes die Map mit initialen oder zu patchenden Attributen für ein DTO
*
* @return die custom ID
*
* @throws ApiOperationException im Fehlerfall
*/
protected ID getID(final Map attributes) throws ApiOperationException {
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Die Methode getID() ist standardmäßig nicht implementiert.");
}
/**
* Erstellt und initialisiert ein neues Datenbank-DTO.
*
* @param newID die neue ID für das DTO
*
* @return das neue DTO
*
* @throws ApiOperationException im Fehlerfall
*/
protected DatabaseDTO newDTO(final ID newID) throws ApiOperationException {
try {
// Erstelle ein neues DTO für die DB und wende Initialisierung und das Mapping der Attribute an
final Constructor constructor = classDatabaseDTO.getDeclaredConstructor();
constructor.setAccessible(true);
final DatabaseDTO dto = constructor.newInstance();
initDTO(dto, newID);
return dto;
} catch (final Exception e) {
if (e instanceof final ApiOperationException apiOperationException)
throw apiOperationException;
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, e);
}
}
/**
* Initialisiert das Datenbank-DTO mit der übergebenen ID.
* Wichtig: Diese Methode muss überschrieben werden, damit die Add-Methoden ausführbar sind.
*
* @param dto das Datenbank-DTO
* @param id die ID
*/
protected void initDTO(final DatabaseDTO dto, final ID id) throws ApiOperationException {
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Die Methode initDTO() ist standardmäßig nicht implementiert.");
}
/**
* Wandelt das Datenbank-DTO in das Core-DTO um, soweit die Daten
* in dem Datenbank-DTO enthalten sind.
*
* @param dto das Datenbank-DTO
*
* @return das neu erstellte Core-DTO
*/
protected abstract CoreDTO map(DatabaseDTO dto) throws ApiOperationException;
/**
* Wandelt die Datenbank-DTOs in der übergebenen Collection in Core-DTOs und gibt die Liste dieser umgewandelten DTOs zurück.
*
* @param dtos die Collection der Datenbank-DTOs
*
* @return die Liste der Core-DTOs
*
* @throws ApiOperationException im Fehlerfall
*/
public List mapList(final Collection dtos) throws ApiOperationException {
final List daten = new ArrayList<>();
for (final DatabaseDTO dto : dtos)
daten.add(map(dto));
return daten;
}
/**
* Führt das Mapping eines Attributes des Core-DTOs auf das zugehörige Datenbank-DTO durch.
* Wichtig: Diese Methode muss überschrieben werden, damit die Add-Methoden und Patch-Methoden ausführbar sind.
*
* @param dto das Datenbank-DTO
* @param name der Name des Core-DTO-Attributes
* @param value der Wert des Core-DTO-Attributes
* @param map die map von Attribut-Namen des Core-DTOs auf den zugehörigen Attributwertes
*
* @throws ApiOperationException wenn ein Fehler bei dem Mapping auftritt
*/
protected void mapAttribute(final DatabaseDTO dto, final String name, final Object value, final Map map) throws ApiOperationException {
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Die Methode mapAttribute() ist standardmäßig nicht implementiert.");
}
/**
* Ermittelt eine Liste mit allen Core-DTOs aus der DB. Wird in seltenen Fällen
* verwendet, wenn auch eine Filterung bei der Methode {@link #getList()} implementiert wird.
* Wichtig: Diese Methode muss überschrieben werden, damit die Methode {@link #getAllAsResponse()} ausführbar ist.
*
* @return eine Liste der Core-DTOs
*
* @throws ApiOperationException im Fehlerfall
*/
public List getAll() throws ApiOperationException {
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Die Methode getAll() ist standardmäßig nicht implementiert.");
}
/**
* Ermittelt eine Liste mit allen Core-DTOs aus der DB. Wird in seltenen Fällen
* verwendet, wenn auch eine Filterung bei der Methode {@link #getList()} implementiert wird.
* Diese wird im Erfolgsfall als JSON eingebettet in einer HTTP-Response 200 zurückgegeben.
*
* @return eine HTTP-Response mit der Liste der Core-DTOs
*
* @throws ApiOperationException im Fehlerfall
*/
public Response getAllAsResponse() throws ApiOperationException {
final var daten = getAll();
return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).entity(daten).build();
}
/**
* Ermittelt eine Liste mit Core-DTOs aus der DB. Wenn bei dieser Methode eine
* Filterung in der abgeleiteten Klasse genutzt wird, so steht als zweite Option
* die Methode {@link #getAll()} zur Verfügung, um den Abruf aller Core-DTOs zu
* implementieren.
* Wichtig: Diese Methode muss überschrieben werden, damit die Methode {@link #getListAsResponse()} ausführbar ist.
*
* @return eine Liste der Core-DTOs
*
* @throws ApiOperationException im Fehlerfall
*/
public List getList() throws ApiOperationException {
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Die Methode getList() ist standardmäßig nicht implementiert.");
}
/**
* Ermittelt eine Liste mit Core-DTOs aus der DB. Wenn bei dieser Methode eine
* Filterung in der abgeleiteten Klasse genutzt wird, so seht als zweite Option
* die Methode getAll zur Verfügung, um den Abruf aller Datenbank-Werte zu
* implementieren.
* Diese wird im Erfolgsfall als JSON eingebettet in einer HTTP-Response 200 zurückgegeben.
*
* @return eine HTTP-Response mit der Liste der Core-DTOs
*
* @throws ApiOperationException im Fehlerfall
*/
public Response getListAsResponse() throws ApiOperationException {
final List coreDTOs = getList();
return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).entity(coreDTOs).build();
}
/**
* Ermittelt das Core-DTO mit der angegebenen ID.
* Wichtig: Diese Methode muss überschrieben werden, damit die Methode {@link #getByIdAsResponse(Object)} ausführbar ist.
*
* @param id die ID
*
* @return das Core-DTO
*
* @throws ApiOperationException im Fehlerfall
*/
public CoreDTO getById(final ID id) throws ApiOperationException {
throw new ApiOperationException(Status.INTERNAL_SERVER_ERROR, "Die Methode getById() ist standardmäßig nicht implementiert.");
}
/**
* Ermittelt das Core-DTO mit der angegebenen ID.
* Dieses wird im Erfolgsfall als JSON eingebettet in einer HTTP-Response 200 zurückgegeben.
*
* @param id die ID
*
* @return eine HTTP-Response mit dem Core-DTO
*
* @throws ApiOperationException im Fehlerfall
*/
public Response getByIdAsResponse(final ID id) throws ApiOperationException {
final CoreDTO coreDTO = getById(id);
return Response.status(Status.OK).type(MediaType.APPLICATION_JSON).entity(coreDTO).build();
}
/**
* Wendet die angegebenen Mappings für die Attribute des Core-DTOs (übergebene Map) auf das übergebene Datenbank-DTO an.
*
* @param dto das Datenbank-DTO
* @param patchMappings eine Map mit den Attributen und den Attributwerten des Core-DTOs
* @param attributesToPatch eine Menge von Attributen, die gepatched werden sollen; null
wenn alle Attribute berücksichtigt werden sollen
* @param attributesToSkip eine Menge von Attributen, die beim Patch ausgelassen werden sollen
* @param isCreation gibt an, ob es sich um ein neues DTO handelt. Wenn true
, dann werden die Attribute aus
* {@link #attributesNotPatchable} ignoriert.
*
* @throws ApiOperationException im Fehlerfall
*/
protected void applyPatchMappings(final DatabaseDTO dto, final Map patchMappings, final Set attributesToPatch,
final Set attributesToSkip, final boolean isCreation) throws ApiOperationException {
// Beim initialen Erstellen eines DTOs werden die "not patchable" Attribute ignoriert
if (!isCreation) {
// Prüfe, ob Attribute enthalten sind, die nicht gepacht werden dürfen und es werfe ggf. eine ApiOperationException
final String notPatchableAttrsStr = patchMappings.keySet().stream()
.filter(key -> (attributesToPatch == null) || attributesToPatch.contains(key))
.filter(attributesNotPatchable::contains)
.collect(Collectors.joining(","));
if (!notPatchableAttrsStr.isBlank())
throw new ApiOperationException(Status.BAD_REQUEST,
"Folgende Attribute werden für ein Patch nicht zugelassen: %s.".formatted(String.join(",", notPatchableAttrsStr)));
}
for (final Entry patchMapping : patchMappings.entrySet()) {
final String key = patchMapping.getKey();
final Object value = patchMapping.getValue();
// Es wird geprüft, ob das Attribut ausgelassen werden soll oder nicht bei den zu patchenden Attributen dabei ist
if (attributesToSkip.contains(key) || ((attributesToPatch != null) && !attributesToPatch.contains(key)))
continue;
// Patching für Attribut auf DTO anwenden
mapAttribute(dto, key, value, patchMappings);
}
}
/**
* Passt die Informationen des Datenbank-DTO mit der angegebenen ID mithilfe des
* JSON-Patches aus dem übergebenen {@link InputStream} an. Dabei werden nur die
* übergebenen Mappings zugelassen.
*
* @param id die ID des zu patchenden DTOs
* @param is der Input-Stream
*
* @return die Response
*
* @throws ApiOperationException im Fehlerfall
*/
public Response patchAsResponse(final ID id, final InputStream is) throws ApiOperationException {
patch(id, JSONMapper.toMap(is));
// TODO ggf. Anpassung, so dass Status.OK mit den veränderten Daten zurückgegeben wird
return Response.status(Status.NO_CONTENT).build();
}
/**
* Passt die Informationen der Datenbank-DTOs mithilfe des
* JSON-Patches aus dem übergebenen {@link InputStream} an. Dabei werden nur die
* übergebenen Mappings zugelassen.
*
* @param is der Input-Stream
*
* @return die Response
*
* @throws ApiOperationException im Fehlerfall
*/
public Response patchMultipleAsResponse(final InputStream is) throws ApiOperationException {
for (final Map map : JSONMapper.toMultipleMaps(is))
patch(getID(map), map);
// TODO ggf. Anpassung, so dass Status.OK mit den veränderten Daten zurückgegeben wird
return Response.status(Status.NO_CONTENT).build();
}
/**
* Fügt ein neues DTO in die Datenbank hinzu, indem in der Datenbank eine neue ID abgefragt wird
* und die Attribute des JSON-Objektes gemäß dem Attribut-Mapper integriert werden.
*
* @param is der Input-Stream
*
* @return die Response mit dem Core-DTO
*
* @throws ApiOperationException im Fehlerfall
*/
public Response addAsResponse(final InputStream is) throws ApiOperationException {
final Map initAttributes = JSONMapper.toMap(is);
final var daten = this.addBasic(getNextID(null, initAttributes), initAttributes);
return Response.status(Status.CREATED).type(MediaType.APPLICATION_JSON).entity(daten).build();
}
/**
* Fügt mehrere neue DTOs in die Datenbank hinzu, indem in der Datenbank jeweils eine neue ID
* abgefragt wird und die Attribute des JSON-Objektes gemäß dem Attribut-Mapper integriert werden.
*
* @param is der Input-Stream
*
* @return die Response mit dem Core-DTO
*
* @throws ApiOperationException im Fehlerfall
*/
public Response addMultipleAsResponse(final InputStream is) throws ApiOperationException {
return Response.status(Status.CREATED).type(MediaType.APPLICATION_JSON).entity(addMultiple(is)).build();
}
/**
* Fügt mehrere neue DTOs in die Datenbank hinzu, indem in der Datenbank jeweils eine neue ID
* abgefragt wird und die Attribute des JSON-Objektes gemäß dem Attribut-Mapper integriert werden.
*
* @param is der Input-Stream
*
* @return die Liste mit den Core-DTOs
*
* @throws ApiOperationException im Fehlerfall
*/
public List addMultiple(final InputStream is) throws ApiOperationException {
// initiale Attribute der anzulegenden DTOs durchlaufen
final List