de.svws_nrw.db.schema.app.DTOCreatorView Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of svws-db Show documentation
Show all versions of svws-db Show documentation
Diese Bibliothek regelt den Zugriff auf Datenbanken für die Schulverwaltungssoftware in NRW
The newest version!
package de.svws_nrw.db.schema.app;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.svws_nrw.db.converter.DBAttributeConverter;
import de.svws_nrw.db.schema.Schema;
import de.svws_nrw.db.schema.View;
import de.svws_nrw.db.schema.ViewSpalte;
/**
* Diese Klasse stellt Methoden zum Erstellen des Java Quellcodes
* für eine DTO-Klasse zum Zugriff auf eine View der SVWS-Datenbank
* zur Verfügung.
*/
public class DTOCreatorView {
/** Die View für die der Java-Code erzeugt werden soll */
public final View view;
/**
* Erzeugt ein neues Objekt der Klasse DTOCreatorTable.
*
* @param view die View, für die der Java-Code erstellt werden soll.
*/
public DTOCreatorView(final View view) {
this.view = view;
}
/**
* Gibt den Package-Namen für diese View zurück.
*
* @param rev die Revision des Datenbankschemas, für welche der DTO für die View erzeugt wird.
*
* @return der Package-Name
*/
public String getPackageName(final long rev) {
if (rev == 0)
throw new IllegalArgumentException("Java-DTOs für Views brauchen nicht für die Migration erstellt werden.");
return Schema.javaPackage + "."
+ Schema.javaDTOPackage
+ ((rev < 0) ? ".current." : ".dev.")
+ view.packageName;
}
/**
* Liefert den Namen der Java-Klasse, wie er in der angegebenn Revision genutzt werden soll.
*
* @param rev die Revision
*
* @return der Name der Java-Klasse
*/
@JsonIgnore
public String getJavaKlasse(final long rev) {
if (rev == 0)
throw new IllegalArgumentException("Java-DTOs für Views brauchen nicht für die Migration erstellt werden.");
return (rev > 0) ? ("Dev" + view.dtoName) : view.dtoName;
}
/**
* Generiert die Equals und HashCode-Methoden für die DTO-Klasse.
*
* @param classname der Name der DTO-Klasse
* @param pkspalten die Primärschlüsselattribute für die DTO-Klassen
*
* @return der Java-Code für die beiden Methoden
*/
private static String getCode4EqualsAndHashcode(final String classname, final Collection pkspalten) {
final StringBuilder sb = new StringBuilder();
sb.append("\t@Override" + System.lineSeparator());
sb.append("\tpublic boolean equals(final Object obj) {" + System.lineSeparator());
sb.append("\t\tif (this == obj)" + System.lineSeparator());
sb.append("\t\t\treturn true;" + System.lineSeparator());
sb.append("\t\tif (obj == null)" + System.lineSeparator());
sb.append("\t\t\treturn false;" + System.lineSeparator());
sb.append("\t\tif (getClass() != obj.getClass())" + System.lineSeparator());
sb.append("\t\t\treturn false;" + System.lineSeparator());
sb.append("\t\t" + classname + " other = (" + classname + ") obj;" + System.lineSeparator());
sb.append(pkspalten.stream()
.map(col -> col.name)
.filter(Objects::nonNull)
.map(colname -> "\t\tif (" + colname + " == null) {" + System.lineSeparator()
+ "\t\t\tif (other." + colname + " != null)" + System.lineSeparator()
+ "\t\t\t\treturn false;" + System.lineSeparator()
+ "\t\t} else if (!" + colname + ".equals(other." + colname + "))" + System.lineSeparator()
+ "\t\t\treturn false;" + System.lineSeparator())
.filter(Objects::nonNull)
.collect(Collectors.joining(System.lineSeparator())));
sb.append("\t\treturn true;" + System.lineSeparator());
sb.append("\t}" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("\t@Override" + System.lineSeparator());
sb.append("\tpublic int hashCode() {" + System.lineSeparator());
sb.append("\t\tfinal int prime = 31;" + System.lineSeparator());
sb.append("\t\tint result = 1;" + System.lineSeparator());
sb.append(pkspalten.stream()
.map(col -> col.name)
.filter(Objects::nonNull)
.map(colname -> "\t\tresult = prime * result + ((" + colname + " == null) ? 0 : " + colname + ".hashCode());" + System.lineSeparator())
.filter(Objects::nonNull)
.collect(Collectors.joining(System.lineSeparator())));
sb.append("\t\treturn result;" + System.lineSeparator());
sb.append("\t}" + System.lineSeparator());
return sb.toString();
}
/**
* Ermittelt die Attribut-Konverter {@link DBAttributeConverter}, die für diese View definiert wurden.
*
* @return eine Liste mit den Attribut-Konvertern.
*/
private List extends DBAttributeConverter, ?>> getAttributeConverter() {
return view.spalten.stream()
.filter(spalte -> spalte.converter != null)
.map(spalte -> DBAttributeConverter.getByClass(spalte.converter))
.filter(Objects::nonNull)
.toList();
}
/**
* Generiert den Code für den Import der übergebenen Attribut-Konverter {@link DBAttributeConverter}.
*
* @param acs eine Liste von Attribut-Konvertern
*
* @return der Java-Code für den Import der Attribut-Konverter
*/
private static String getCodeImportConverter(final List extends DBAttributeConverter, ?>> acs) {
if (acs.isEmpty())
return "";
String result = "import "
+ acs.stream().map(ac -> ac.getClass().getName()).filter(Objects::nonNull).sorted().distinct()
.collect(Collectors.joining(";" + System.lineSeparator() + "import "))
+ ";" + System.lineSeparator()
+ System.lineSeparator();
final String resultTypeImports = acs.stream().map(ac -> ac.getResultType().getName())
.filter(Objects::nonNull).filter(cntt -> !cntt.startsWith("java.lang")).sorted().distinct()
.collect(Collectors.joining(";" + System.lineSeparator() + "import "));
if (!"".equals(resultTypeImports))
result += "import "
+ resultTypeImports
+ ";" + System.lineSeparator()
+ System.lineSeparator();
return result;
}
/**
* Generiert den Code für einen Query-String und hängt diesen an den Stringbuilder an.
*
* @param name der Name der Query
* @param query die Query
* @param comment der JavaDoc-Kommentar für das Attribut
*
* @return der Query-String.
*/
private static String getQueryAttribute(final String name, final String query, final String comment) {
return "%1$s\t/** %2$s */%1$s\tpublic static final String QUERY_%3$s = \"%4$s\";%1$s".formatted(System.lineSeparator(), comment, name, query);
}
/**
* Generiert den Java Code für Queries in statischen Attributen.
*
* @param rev die Revision der Tabelle, für welche die Queries generiert werden
*
* @return der Java Code
*/
private String getCode4Queries(final long rev) {
final String className = getJavaKlasse(rev);
// alle Views: Generiere Code für eine Query auf alle Datensätze der View
final StringBuilder sb = new StringBuilder();
String query = "SELECT e FROM " + className + " e";
sb.append(getQueryAttribute("ALL", query, "Die Datenbankabfrage für alle DTOs"));
// nur für Views mit Primärschlüssel...
if (!view.pkSpalten.isEmpty()) {
// Generiere Code für eine parametrisierte Query mit den Spalten des Primärschlüssels als Parameter
query = "SELECT e FROM " + className + " e WHERE ";
for (int i = 0; i < view.pkSpalten.size(); i++) {
final ViewSpalte col = view.pkSpalten.get(i);
if (i > 0)
query += " AND ";
query += "e." + col.name + " = ?" + (i + 1);
}
sb.append(getQueryAttribute("PK", query, "Die Datenbankabfrage für DTOs anhand der Primärschlüsselattribute"));
if (view.pkSpalten.size() == 1) {
final ViewSpalte col = view.pkSpalten.iterator().next();
query = "SELECT e FROM " + className + " e WHERE e." + col.name + " IN ?1";
sb.append(getQueryAttribute("LIST_PK", query, "Die Datenbankabfrage für DTOs anhand einer Liste von Primärschlüsselattributwerten"));
}
}
// alle Views: Generiere Annotationen für Queries für einzelne Attribute der View
for (final ViewSpalte spalte : view.spalten) {
if (spalte.name.startsWith("-"))
continue; // ignoriere Datenbank-Spalten, welche nicht als Java-Attribute umgesetzt werden sollen
query = "SELECT e FROM " + className + " e WHERE e." + spalte.name + " = ?1";
sb.append(getQueryAttribute("BY_" + spalte.name.toUpperCase(), query, "Die Datenbankabfrage für DTOs anhand des Attributes " + spalte.name));
query = "SELECT e FROM " + className + " e WHERE e." + spalte.name + " IN ?1";
sb.append(getQueryAttribute("LIST_BY_" + spalte.name.toUpperCase(), query,
"Die Datenbankabfrage für DTOs anhand einer Liste von Werten des Attributes " + spalte.name));
}
return sb.toString();
}
/**
* Generiert das Attribut für eine Java-Klasse, welches die angegebene Datenbank-Spalte
* repräsentiert.
*
* @param spalte die Spalte für die das Java-Attribut generiert wird
* @param withAnnotations gibt an, ob auch Annotationen für die Spalte generiert werden sollen.
*
* @return der Java-Code für das Attribut für die DB-Spalte
*/
private String getCode4Attributes(final ViewSpalte spalte, final boolean withAnnotations) {
final StringBuilder sb = new StringBuilder();
if (spalte.beschreibung != null)
sb.append("\t/** " + spalte.beschreibung + " */" + System.lineSeparator());
if (withAnnotations) {
if ((view.pkSpalten.isEmpty()) || view.pkSpalten.contains(spalte))
sb.append("\t@Id" + System.lineSeparator());
sb.append("\t@Column(name = \"" + spalte.name + "\")" + System.lineSeparator());
sb.append("\t@JsonProperty" + System.lineSeparator());
}
if ((spalte.converter != null) && (withAnnotations)) {
final var simpleConverterClassName = DBAttributeConverter.getByClass(spalte.converter).getClass().getSimpleName();
sb.append("\t@Convert(converter = " + simpleConverterClassName + ".class)" + System.lineSeparator());
sb.append("\t@JsonSerialize(using = " + simpleConverterClassName + "Serializer.class)" + System.lineSeparator());
sb.append("\t@JsonDeserialize(using = " + simpleConverterClassName + "Deserializer.class)" + System.lineSeparator());
}
sb.append("\tpublic " + spalte.datentyp + " " + spalte.name + ";" + System.lineSeparator());
return sb.toString();
}
/**
* Generiert für die angegebene Revision den Java-Code der dazugehörigen Java-DTO-Klasse.
*
* @param rev die Revision
*
* @return der Java-Code für die DTO-Klasse in der angegebenen Revision.
*/
public String getCode(final long rev) {
final var acs = getAttributeConverter();
final String className = getJavaKlasse(rev);
final StringBuilder sb = new StringBuilder();
sb.append("package " + getPackageName(rev) + ";" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("import de.svws_nrw.db.DBEntityManager;" + System.lineSeparator());
if (!acs.isEmpty())
sb.append(getCodeImportConverter(acs));
sb.append(System.lineSeparator());
sb.append("import jakarta.persistence.Cacheable;" + System.lineSeparator());
sb.append("import jakarta.persistence.Column;" + System.lineSeparator());
if (!acs.isEmpty())
sb.append("import jakarta.persistence.Convert;" + System.lineSeparator());
sb.append("import jakarta.persistence.Entity;" + System.lineSeparator());
sb.append("import jakarta.persistence.Id;" + System.lineSeparator());
if (view.pkSpalten.size() != 1)
sb.append("import jakarta.persistence.IdClass;" + System.lineSeparator());
sb.append("import jakarta.persistence.Table;" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("import com.fasterxml.jackson.annotation.JsonProperty;" + System.lineSeparator());
sb.append("import com.fasterxml.jackson.annotation.JsonPropertyOrder;" + System.lineSeparator());
if (!acs.isEmpty()) {
sb.append("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;" + System.lineSeparator());
sb.append("import com.fasterxml.jackson.databind.annotation.JsonSerialize;" + System.lineSeparator());
final String csvImportConverter = acs.stream()
.map(ac -> ac.getClass().getName())
.filter(Objects::nonNull).sorted().distinct()
.map(cn -> cn.replace(".db.", ".csv.") + "Serializer;" + System.lineSeparator() + "import " + cn.replace(".db.", ".csv.") + "Deserializer")
.collect(Collectors.joining(";" + System.lineSeparator() + "import "));
sb.append("import " + csvImportConverter + ";" + System.lineSeparator());
sb.append(System.lineSeparator());
}
sb.append("/**" + System.lineSeparator());
sb.append(" * Diese Klasse dient als DTO für die Datenbank-View " + view.name + "." + System.lineSeparator());
sb.append(" * Sie wurde automatisch per Skript generiert und sollte nicht verändert werden," + System.lineSeparator());
sb.append(" * da sie aufgrund von Änderungen am DB-Schema ggf. neu generiert und überschrieben wird." + System.lineSeparator());
sb.append(" */" + System.lineSeparator());
sb.append("@Entity" + System.lineSeparator());
if (view.pkSpalten.size() != 1) {
sb.append("@IdClass(" + className + "PK.class)" + System.lineSeparator());
}
sb.append("@Cacheable(DBEntityManager.use_db_caching)" + System.lineSeparator());
sb.append("@Table(name = \"" + view.name + "\")" + System.lineSeparator());
sb.append(view.spalten.stream()
.map(spalte -> "\"" + spalte.name + "\"")
.collect(Collectors.joining(", ", "@JsonPropertyOrder({", "})" + System.lineSeparator())));
sb.append("public final class " + className + " {" + System.lineSeparator());
sb.append(getCode4Queries(rev));
sb.append(System.lineSeparator());
sb.append(view.spalten.stream()
.map(spalte -> getCode4Attributes(spalte, true))
.filter(Objects::nonNull)
.collect(Collectors.joining(System.lineSeparator())));
sb.append(System.lineSeparator());
sb.append("\t/**" + System.lineSeparator());
sb.append("\t * Erstellt ein neues Objekt der Klasse " + className + " ohne eine Initialisierung der Attribute." + System.lineSeparator());
sb.append("\t */" + System.lineSeparator());
sb.append("\tprivate " + className + "() {" + System.lineSeparator());
sb.append("\t}" + System.lineSeparator());
sb.append(System.lineSeparator());
final Collection tmpPkSpalten = view.pkSpalten.isEmpty() ? view.spalten : view.pkSpalten;
sb.append(getCode4EqualsAndHashcode(className, tmpPkSpalten));
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("\t/**" + System.lineSeparator());
sb.append("\t * Konvertiert das Objekt in einen String. Dieser kann z.B. für Debug-Ausgaben genutzt werden." + System.lineSeparator());
sb.append("\t *" + System.lineSeparator());
sb.append("\t * @return die String-Repräsentation des Objektes" + System.lineSeparator());
sb.append("\t */" + System.lineSeparator());
sb.append("\t@Override" + System.lineSeparator());
sb.append("\tpublic String toString() {" + System.lineSeparator());
sb.append("\t\treturn \"" + className + "("
+ view.spalten.stream()
.map(spalte -> "" + spalte.name + "=\" + this." + spalte.name + " + \"")
.collect(Collectors.joining(", "))
+ ")\";" + System.lineSeparator());
sb.append("\t}" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("}");
sb.append(System.lineSeparator());
return sb.toString();
}
/**
* Generiert für die View eine DTO-Klasse für den Primärschlüssel
*
* @param rev die Revision
*
* @return der Java-Code für die Primärschlüssel-DTO-Klasse.
*/
public String getCode4PrimaryKeyClass(final long rev) {
final String className = getJavaKlasse(rev);
// Bestimme die Spalten für den Primärschlüssel und erzeuge nur Code, wenn es sich nicht um einen einfachen Primärschlüssel handelt
List pkSpalten = view.pkSpalten;
if (pkSpalten == null)
pkSpalten = view.spalten;
if (pkSpalten.size() <= 1)
return null;
final StringBuilder sb = new StringBuilder();
sb.append("package " + getPackageName(rev) + ";" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("import java.io.Serializable;" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("/**" + System.lineSeparator());
sb.append(" * Diese Klasse dient als DTO für den Primärschlüssel der Datenbank-View " + view.name + "." + System.lineSeparator());
sb.append(" * Sie wurde automatisch per Skript generiert und sollte nicht verändert werden," + System.lineSeparator());
sb.append(" * da sie aufgrund von Änderungen am DB-Schema ggf. neu generiert und überschrieben wird." + System.lineSeparator());
sb.append(" */" + System.lineSeparator());
sb.append("public final class " + className + "PK implements Serializable {" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("\t/** Die UID für diese Klasse */" + System.lineSeparator());
sb.append("\tprivate static final long serialVersionUID = 1L;" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append(view.pkSpalten.stream()
.map(col -> getCode4Attributes(col, false))
.filter(Objects::nonNull)
.collect(Collectors.joining(System.lineSeparator())));
sb.append(System.lineSeparator());
sb.append("\t/**" + System.lineSeparator());
sb.append("\t * Erstellt ein neues Objekt der Klasse " + className + "PK ohne eine Initialisierung der Attribute." + System.lineSeparator());
sb.append("\t */" + System.lineSeparator());
sb.append("\t@SuppressWarnings(\"unused\")" + System.lineSeparator());
sb.append("\tprivate " + className + "PK() {" + System.lineSeparator());
sb.append("\t}" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append("\t/**" + System.lineSeparator());
sb.append("\t * Erstellt ein neues Objekt der Klasse " + className + "PK." + System.lineSeparator());
view.pkSpalten.stream()
.forEach(spalte -> sb.append("\t * @param " + spalte.name + " der Wert für das Attribut " + spalte.name + "" + System.lineSeparator()));
sb.append("\t */" + System.lineSeparator());
sb.append("\tpublic " + className + "PK(");
sb.append(view.pkSpalten.stream()
.map(spalte -> "final " + spalte.datentyp + " " + spalte.name)
.collect(Collectors.joining(", ")));
sb.append(") {" + System.lineSeparator());
sb.append(view.pkSpalten.stream()
.map(spalte -> "\t\tif (" + spalte.name + " == null) {" + System.lineSeparator()
+ "\t\t\tthrow new NullPointerException(\"" + spalte.name + " must not be null\");" + System.lineSeparator()
+ "\t\t}" + System.lineSeparator()
+ "\t\tthis." + spalte.name + " = " + spalte.name + ";" + System.lineSeparator())
.collect(Collectors.joining()));
sb.append("\t}" + System.lineSeparator());
sb.append(System.lineSeparator());
sb.append(System.lineSeparator());
sb.append(getCode4EqualsAndHashcode(className + "PK", pkSpalten));
sb.append("}");
sb.append(System.lineSeparator());
return sb.toString();
}
}