Spring Tool Suite mit Gradle unter JDK 11

Im allgemeinen funktioniert die Spring Tool Suite 4 (Version 4.0.2) unter dem aktuellen Java 11 LTS. Leider ist die Erkennung Java Versionen anscheinend sehr fragil, da zum Beispiel die aktuelle Version 11.0.1 nicht erkannt wird.

Dadurch funktioniert das Gradle Tooling (Eclipse Buildship Plugin) nicht und es wird das öffnen eines Gradle Projektes mit einer Fehlermeldung der Editoren quittiert.

An internal error occurred during: "Initializing Java Tooling".
Could not determine java version from '11.0.1'.

Bestimmung der JVM

Unter Arch Linux oder Manjaro werden die verschiedenen Java Versionen parallel unterhalb /usr/lib/jvm abgelegt. Mit dem kleinen Hilfsprogramm archlinux-java kann die Default JVM angezeigt und bestimmt werden. Ist diese nun zum Beispiel auf die aktuelle Version 11 eingestellt, dann läuft auch Eclipse bzw. die RCP und somit auch die Spring Tool Suite unter der JVM. Abhilfe schafft die .ini Datei im Root-Verzeichnis. Hier lässt sich u.a. die zu verwendende JVM angegeben.

[sp@Laptop ~]$ sudo archlinux-java status
Available Java environments:
  java-11-openjdk (default)
  java-8-graal
  java-8-openjdk

Der Parameter vm bestimmt den Pfad zur JVM. Dieser muss unbedingt vor dem Parameter vm-args angegeben werden!

-vm
/usr/lib/jvm/java-8-graal/bin/java
-vmargs
-Dosgi.requiredJavaVersion=1.8

In diesem Beispiel läuft Spring Tool Suite unter der GraalVM, obwohl die Default VM auf JDK 11 zeigt.

Makepkg

Das Tool makepkg gehört zur Toolsammlung von Arch Linux und deren Derivaten. Mit dem Bash Skript lassen sich die Pakete, die der Paketmanager Pacman verwaltet, bauen. Zum Bau der Pakete verwendet makepkg die BUILDSKRIPTS.

Das Temporäre Filesystem

Standardmäßig hängt Arch Linux oder dessen Derivat Manjaro das tmpfs auf /tmp ein, um eine dynamische RAM Disk für temporäre Dateien zur Verfügung zu stellen. Dieses bringt bei vielen Anwendungen Performanceverbesserung wie z.B. Firefox und Chrome die viele kleine Dateien zum Cachen ablegen.

Das Build

Beim Bau der Pakete werden zunächst die Quellen entpackt. Verwendet man yaourt, dann werden die Dateien nach /tmp/yaourt-user in ein eigenes Verzeichnis entpackt. Als nächstes folgt in der Regel die Kompilation der Quelltext und ein anschließendes Packen zu einem Archiv mit und auch ohne Kompression.

Das Problem

Da heute Softwaren nicht immer klein sind und nicht jedes Gerät mehr als 4 GB Arbeitsspeicher verfügt, kann es beim Bau der Pakete schnell zu dem Fehler “no space left on device” kommen, was nichts anderes bedeutet als das der Arbeitsspeicher verbraucht worden ist.

Lösungen

Zum einen kann man das automatische Mounten des temporären Dateisystems mit Systemd tmpfs maskieren und somit das einhängen beim Start verhindern. Das hat aber auch zur Folge das der gewüschte Performancezuwachs nicht mehr vorhanden ist.

Eine andere Lösung ist, dass man dem Programm makepkg den Pfad für die temporären Dateien vorgibt.

#-- Specify a directory for package building.
BUILDDIR=/home/sascha/PKGBUILDS/build

Ich habe immer ein lokales Verzeichnis, wo ich Änderungen an den Buildskripten vornehme. Dieses ist das Verzeichnis /home/sascha/PKGBUILDS und darin befindet sich der Ordner build. Wenn man jetzt ein großes Paket baut, so werden die Daten auf die Festplatte geschrieben und das Paket kann erfolgreich gebaut werden.

Am 30.11.2018 hat Syntevo den Java basierenden Git Client SmartGIT 18.2.0 veröffentlicht. Das Update von SmartGit wird, wie die Installation mit einem Paketmanager für das UserRepository von Arch Linux (z.B. yaourt), gefunden und installiert. Im AUR Repository ist die aktuelle Version zu finden.

yaourt –noconfirm -S smartgit

Das Changelog ist sehr umfangreich. Ein Dialog fragt jetzt Syntevo das Log View als Hauptfenster zu setzen. Diese Einstellung lässt sich in den Einstellungen wieder rückgängig machen. Aus dem Log lässt sich jetzt ein Stash über das Kontextmenü auch anwenden. Syntevo setzt jetzt auf die JSCH SSH Implementation, anstatt wie bisher SSHJ. Ürsprünglich wurde Git mit im Bundle ausgeliefert, aber man hat den Plan nach zu vielen Problemen verworfen und SmartGIT wird wieder ohne Git ausgeliefert. Beim Clonen wird, eine sich im Clipboard befindliche URL, in das URL Feld eingetragen.

Die Konfiguration wird nun unter ~/.config/smartgit gespeichert und Update Dateien landen unter ~/.local/share/smartgit.

Wie in den letzten Versionen von SmartGIT auch, muss man zunächst wieder die Art der Verwendung angeben und eine eingebaute Verzögerung abwarten, bevor man das Programm nutzen kann.

Am 30.11.2018 wurde das Update von der Spring Tools Suite 4.0.2 veröffentlicht. Es enthält neben dem Bugfix für fehlende Context-Path Unterstützung (siehe Liver Hover fehlerhaft) Verbesserungen für die Navigation in application.properties und application.yaml Dateien, Vereinheitlichung der Autovervollständigung in Property und YAML, Autovervollständigung im Config-Editor in der Boot Dash View, Verbesserte Anzeige der Live Hover bei functional configuration für WebFlux, Unterstützung für Logging Groups in Property und YAML Editoren.

Außerdem wurden eine Reihe von Fehlern beseitigt, die die Stabilität des Language Server beeinträchtigten. So wurden hänger und zähflüssige Antwortzeiten beseitigt. Es wurde QuickFix mit CTRL + F1 in Editoren die das LSP (Language Server Protocol) verwenden behoben.

Alles passt zusammen

Nun fügen sich alle Puzzelteile zusammen und es läßt sich unter dem aktuellen LTS von Java, dem JDK 11, ein Beispiel mit Hibernate mit SQLite Datenbank, Jooq und Spring Boot 2.1 unter der Verwendung von Gradle als Buildsystem umsetzen. Bis hierhin gab es einige Baustellen, die im Zuge der Modularisierung (Projekt Jigsaw), noch zu beseitigen waren. Jetzt ist eine Umsetzung mit den aktuellsten Versionen möglich und dieses möchte ich hier vorstellen.

Hier die benötigten Komponenten im Einzelnen:

  • Spring Boot 2.1
  • Spring Data JPA (spring-boot-starter-data-jpa)
  • Jooq 3.11.4 (im Spring Boot Starter definiert)
  • Gradle 5.0-RC4
  • sqlite-dialect (Hibernate Dialekt)
  • sqlite-jdbc (Treiber)

Die Demo

Die Anwendung erstellt zwei Invocation Objekte und persistiert sie mit JPA (Hibernate) in der Datenbank. Im Anschluss wird mit Hilfe von Jooq die Tabelle aus SQLite Datenbank wieder gelesen und in Tabellenform auf der Konsole ausgegeben. Das Ganze passiert in der Klasse InitDatabase, die das CommandLineRunner Interface implemenmtiert und somit beim Start der Anwendung ausgeführt wird. Die SQLite Datenbank legt die Daten in der Datei application.db ab. Eine initiale Version ist im Git Repository vorhanden (s.u.).

Ausführen der Anwendung

Der Build wird wie immer mit dem Dreisatz git clone, cd, ./gradlew clean build bootRun angestoßen.

git clone https://mrpeacockgit.duckdns.org:443/Public/spring-hibernate-jooq-sqlite-demo.git
cd spring-hibernate-jooq-sqlite-demo/
./gradlew clean build bootRun

Hier sehen wir den schön formatierten Output von Jooq auf der Konsole.

ACHTUNG: Es muss der Gradle Wrapper unter JDK 11 ausgeführt werden, weil Java 11 als Source- und TargetCompatibility angegeben ist. Wird Gradle z.B. unter Java 8 ausgeführt, dann bricht Gradle den Buildvorgang mit > Could not target platform: ‘Java SE 11’ using tool chain: ‘JDK 8 (1.8)’ ab. Hat man keine JDK 11 zur Hand, dann kann Source und Target auf Java 8 reduziert werden, da keine Sprachfeatures von Java 11 genutzt werden.

Import in Eclipse

Damit in Eclipse die Anwendung fehlerfrei ist, müssen 2 Schritte ausgeführt werden.

  • Generierung des Java Codes. Es muss die Generierung der Java Quelltexte, aus dem gegebenen Datenbankschema mit Hilfe des Jooq Code Generators, durchgeführt werden. Der Task generateSpringhibernatesqliteJooqSchemaSource wird von dem Jooq Plugin bereitgestellt.

  • Damit die statischen Importe gefunden werden, müssen sie auch im Eclipse Buildpath vorhanden sein. Dazu muss einmalig der Task eclipse ausgeführt werden.

Jetzt sollte das Projekt fehlerfrei im Eclipse sein.

Die Entität

Die Entity die hier verwendet wird, ist der Demo bedingt, sehr einfach gehalten. Wie immer verwende ich das Project Lombok, um den Code sauber von Boilerplate Code zu halten.

@Entity
@Data
public class Invocation {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    private String name;
}

GenerationType

Der GenerationType muss hier auf AUTO stehen, ansonsten wird der Start der Anwendung mit einer Exception quittiert.

Caused by: org.sqlite.SQLiteException: [SQLITE_CONSTRAINT]  Abort due to constraint violation (NOT NULL constraint failed: invocation.id)

Hibernate Schema Erzeugung / Jooq Code Generation

Hier haben wir ein Henne-/Ei-Problem! Der Code Generator von Jooq benötigt eine Datenbank aus der er das Schema ausliest, um den Code zu erzeugen. Im Quelltext wird aber bereits per Import auf generierten Code zugegriffen, was zu einer Fehlermeldung beim Kompilieren während des Builds führen würde. Da die Code Generierung vor der Kompilierung erfolgt, habe ich eine leere Datenbank mit in das Git Repository aufgenommen. Somit kann die Anwendung über den Gradle Build gebaut und auch ausgeführt werden.

Problem der automatischen Schema Generierung

Anhand des oben beschriebenen Henne-/Ei-Problems, sollte eigentlich jedem schnell klar werden, dass das keine Lösung für große Anwendungen ist. Es sollte hier besser auf andere Lösungsansätze ausgewichen werden. Z.B. Flyway als Datenbankschemamigrationstool oder einfach die Verwendung von Spring Boot Bordmitteln (Spring liest schema.sql ein und verarbeitet die SQL Anweisungen).

Jooq Konfiguration

Für das Erstellen der Konfiguration Jooq Code Generator wird das Plugin von Etienne Struder verwendet. Hier in den Beispiel ist nur eine Grundkonfiguration vorgenommen worden. Alle Einstellungsmöglichkeiten die möglich sind, lassen sich über das XSD für Jooq Code Generator erfahren. Das lesen und verstehen des recht umfangreichen XSD ist nicht einfach. Man kann aber z.B. über Visual XSD sich das XSD visualisieren lassen und so den Aufbau schneller verstehen.

Autovervollständigung in Eclipse

Bei der Eingabe in Eclipse werden nicht automatisch die statischen Imports für die generierten Tabellen von Jooq angezeigt. Hier muss man in Eclipse erst den Umweg über eine nicht statischen Import nehmen, um ihn dann per STRG + 1 in einen statischen Import zu überführen.

Beispiel

In der Klasse InitDatabase die den CommadLineRunner implementiert, möchten wir alle Invocations aus der Datenbank listen.

private void listInvocations() {
        ctx.select()
            .from(INVOCATION)
            .fetch()
            .forEach(System.out::println);
    }

INVOCATION ist hier über den statischen Import

import static db.Tables.INVOCATION;

bekannt. Damit die Autovervollständigung funktioniert, muss der Import vorhanden sein. Das heißt erst nach dem man den Import eingefügt hat, lässt sich mit STRG + SPACE das Code-Fragment .from(INVOCATION) einfügen. Das ist nicht sehr effektiv, da man immer erst den Import einfügen muss.

Einstellung der Favoriten

Der Generator von Jooq ist so konfiguriert, dass die Tabelle aus der Datenbank im Java Package de.Tables.* gelistet werden. Die Quelltexte werden außerhalb von main in src/db gespeichert, so dass jederzeit durch löschen von dem Verzeichnis src/db die generierten Quelltext sauber neu erstellt werden können, ohne das alte Artefakte noch verhanden sind.

Man kann Eclipse anweisen, bestimmte static Members anzuzeigen, auch wenn das Import noch fehlt. Und genau das führt hier zum Ziel und erleichtert die Eingabe von Jooq Queries mit der hervorragenden DSL enorm.

In den Präferenzen von Eclipse wählen Sie java –> Editor –> Content Assist –> Favorites und erstellen einen neuen Typen. Hier ist es *db.Tables.**.

Jetzt kann die Eingabe vervollständigt werden, ohne das der Import zuvor vorhanden war

Hier in diesem Betrag gehe ich auf ein paar grundlegende Sicherheitsmaßnahmen ein. Ziel ist es einige wichtige Maßnahmen zum Schutz der Daten vorzustellen und wie diese mit Spring Security genutzt werden können.

CSRF

Cross Site Request Forgery kann man frei mit Seitenübergreifende Anfragenfälschung übersetzen. Um zu verhindern das ein Angreifer Anfragen an die Anwendung stellt, muss man sicherstellen dass der Link auf den der Benutzer geklickt hat nicht untergeschoben worden ist. Dieses kann man recht einfach erreichen, in dem man ein Geheimnis in der Webseite unterbringt. Dieses Geheimnis, es handelt sich hierbei um das sogenannte CSRF-Token, kennt nur die Anwendung (Server) und der Anwender (Client). Das Token hat eine kurz Lebensdauer und wird als hidden input field mit ausgegeben. Hier am Beispiel in Verwendung mit Thymeleaf Templating Engine.

<input
  type="hidden"
  th:name="${_csrf.parameterName}"
  th:value="${_csrf.token}" />

Es ist also nicht sehr kompliziert, aber dennoch eine Wirkungsvolle Waffe gegen mögliche Angreifer. Alle Anwendungen sollten also möglichst CSRF verhindern. Weitere Sicherungsmaßnahmen befinden sich gut beschrieben in der Referenzdokumentation von Spring Boot Security.

Hinweis: Ab Thymeleaf 2.1 und gesetzter @EnableWebSecurity fügt Thymeleaf dieses automatisch ein.

CSRF deaktivieren

Per default ist CSRF aktiviert. Das heißt das Seiten die von der Anwendung ausgeliefert werden, auch das Token beinhalten müssen, da sonst zu recht ein 403 Fehler angezeigt wird. In dem WebSecurityConfigurerAdapter kann man CSRF deaktivieren.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
            .disable() // TODO remove and secure template with CSRF token
    ...
}

Die Deaktivierung, auch wenn es nur in der Entwicklung ist, ist nicht zu empfehlen. Sicherheit in Anwendungen sollten von Anfang an integraler Bestandteil der Anwendung sein und nicht im Nachhinein übergestülpt werden. Daher rate ich von dieser Vorgehensweise hier ausdrücklich ab.

Dependency

Es muss nur der Starter spring-boot-starter-security eingebunden werden. Dieses ist die einzige Abhängigkeit, um die Anwendung mit Spring Security abzusichern.

dependencies {
    // Spring Boot
    ...    
    implementation('org.springframework.boot:spring-boot-starter-security')
    ...
}

Sicherung auf Methoden Level

Mit welchen Einstellungen lässt sich die Methodensicherung konfigurieren?

Grundsätzlich erfolgt die Steuerung über Annotationen. Welche Annotationen Wirkung haben, hängt davon ab welche Sicherungsmaßnahmen aktiviert sind. In der @EnableGlobalMethodeSecurity können als Paramter folgende Annotationen gesteuert werden:

  • securedEnabled

Methoden oder Klassen können nun mit @Secure() annotiert werden.

  • jsr250Enaabled Die Annotaion jsr250Enabled ist das Equivalent zur Spring Annotation Secured

  • order

  • prePostEnabled

Wenn prePostEnabled wahr ist, dann lassen sich Methoden vor Aufruf oder bevor die Rückgabewerte übergeben werden prüfen.

Beispiel

Wie sieht nun eine typische Konfiguration aus? Hier nachfolgend eine mit @Configuration annotierte Konfiguration, die secured ermöglicht und prePost aktiviert.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class EnableMethodSecurity extends GlobalMethodSecurityConfiguration {

}

Kurztipp für die Eingabe

Legt man die Klasse mit CTRL + n –> class neu an, dann kann man direkt GMSC + STRG + SPACE eingeben und Eclipse expandiert dann zur gewünschten Klasse GlobalMethodSecurityConfiguration. Man muss also nur die Anfangsbuchstaben wählen und dann mit STRG + SPACE die Autovervollständigung akitivieren.

Den Ausdruck hasPermission() implementieren

Neben dem Standard hasRole(“”) kann man in Spring auch eigene Ausdrücke implementieren. Hier zeige ich an dem Beispiel, wie man die hasPermissions() implementieren kann.

Dazu muss die Klasse das Interface PermissionEvaluator implementieren. Zunächst das Grundgerüst:

@Override
public boolean hasPermission(Authentication authentication,
                             Object targetDomainObject,
                             Object permissionObject) {
    if(authentication==null ||
            targetDomainObject==null ||
            !(permissionObject instanceof String)) {

        log.info("Permission denied");

        return false;
    }

    String targetType = targetDomainObject.getClass()
                            .getSimpleName()
                            .toUpperCase();
    String permission = permissionObject
                            .toString()
                            .toUpperCase();

    return hasPrivilege(authentication, targetType, permission);
}

@Override
public boolean hasPermission(Authentication authentication,
                             Serializable targetId,
                             String targetType,
                             Object permissionObject) {
    if ((authentication == null) || 
        (targetType == null) || 
        !(permissionObject instanceof String)) {
        return false;
    }

    String permission = permissionObject
                            .toString()
                            .toUpperCase();

    return hasPrivilege(authentication, targetType, permission);
}

/**
    * Check privilege on given constraints
    * 
    * @param authentication
    * @param targetType
    * @param permission
    * 
    * @return true if access is granted, otherwise false
    */
private boolean hasPrivilege(Authentication authentication,
                             String targetType,
                             String permission) {
    for (final GrantedAuthority grantedAuth : authentication.getAuthorities()) {
        if (grantedAuth.getAuthority().startsWith(targetType)) {
            if (grantedAuth.getAuthority().contains(permission)) {
                return true;
            }
        }
    }
    return false;
}

Methoden absichern

@Controller
public class CustomerController {
    /**
     * Show table with all Customers. This endpoint is protected
     * by hasPermission expression evaluation in PreAuthorize() annotation.
     */
    @GetMapping("/allcustomersHP")
    @PreAuthorize("hasPermission(#model, 'String', 'xxx')")
    public String allCustomersHP(Model model) {
        model.addAttribute("customers", customerService.findAll());
        return "customerListing";
    }

ACHUTNG: Damit @PreAuthorize ausgeführt und somit auch der Ausdruck hasPermission() evaluiert wird, muss in der Konfiguration prePostEnabled=true gesetzt werden. Siehe oben.

Benutzerdefinierte Ausdrücke für die Validierung

Es lassen sich nicht immer alle Umgebungen auf die hasPermission() abbilden, was dazu führen kann das die Funktion, die ja nur die Permission klären soll, auch andere Zwecke erfüllt. Diese Zweckentfremdung führt früher oder später zu Problemen.

Gegeben sei ein größerer Betrieb mit vielen Organisationseinheiten. Dann macht es Sinn zum Beispiel den Zugriff auf die Umsatzzahlen, einer bestimmten Gruppe von Mitarbeiten zu gewähren. Hier wäre es die Controllingabteilung.

Anstelle mit hasPermission auf isController oder ähnliches zu Prüfen, kann man auch gleich die Organisationeinheiten mit in die Benutzerdaten integrieren und z.B. mit einer Funktion isMemberOf(String organization) prüfen. Dazu muss eine Klasse den SecurityExpressionRoot erweitern. Dieses Thema werde ich in einem weiterem Beitrag ausführlich behandeln.

Sicherheitsmaßnahmen komplett deaktivieren

Wenn sich Spring Security im Classpath befindet, dann werden alle Endpoints (bis auf 2 Actuator /health, /info) gesichert und sind ohne weiteres Zutun nicht mehr erreichbar. Und es ist auch gut so, dass das Spring Team dieses konservative Standardverhalten gewählt hat.

Möchte man zum lokalen Testen nicht jedesmal die Dependencies anpassen, so kann man zum Beispiel auch eine 2. Konfiguration anlegen, die nur bei einem bestimmten Profil aktiviert wird. Überschreibt man die beiden Methodensignaturen configure(final AuthenticationManagerBuilder) und configure(final HttpSecurity), dann hat man den Effekt, dass Spring Security nicht im Classpath vorhanden sei.

@Profile("localdebug")
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(final AuthenticationManagerBuilder auth) throws Exception {}

  @Override
  protected void configure(final HttpSecurity http) throws Exception {}
}

Vorwort

In diesem Beitrag zeige ich wie man einen reactive RestController erstellt. Es wird die gesamte Anwendung reaktiv sein, d.h. wir verwenden hier den spring-boot-starter-data-mongodb-reactive Client mit seiner reaktiven Repository Schnittstelle.

Das Beispiel ist mit Absicht extrem einfach gehalten, um das Wesentliche besser darzustellen. In dem Beispiel soll ein RestController eine API mit den üblichen CRUD Methoden zur Verfügung stellen. Es soll in der Datenbank für Reservierungen geschaffen werden. Die Konfiguration wird nicht klassisch mit Annotationen durchgeführt, sondern ich zeige hier die Anwendung von functional configuration zum Setzen der Routen zu den entsprechenden Handlermethoden. Die Anwendung soll es ermöglichen eine Reservierung zu erstellen, lesen, löschen und zu ändern.

Die Dependencies

Um das Beispiel umzusetzen benötigen wir Abhängigkeiten für spring-boot-starter-data-mongodb-reactive und spring-boot-starter-webflux. Die weitere Abhängigkeiten (s.u.) sind optional, aber sollten dennoch mit eingebunden werden, da sie das Leben doch vereinfachen können.

dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
    compile('org.springframework.boot:spring-boot-starter-webflux')
    compile('org.springframework.boot:spring-boot-actuator')
    runtime('org.springframework.boot:spring-boot-devtools')

    // Project Lombok
    // Since Gradle warns if an AnnotationProcessor is found on classpath, put 
    // it into annotationProcessor directive
    compileOnly('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')

    // Testing
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('io.projectreactor:reactor-test')

    // flapdoodle runs a temporary embedded instance of mongodb
    testRuntime('de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.0.1')
}

Project Lombok

Ich mag keinen Boilerplate Code, da er gerade bei Entitäten den eigentlichen Verwendungszweck verdeckt. Daher bin ich ein großer Fan von dem Project Lombok und setze es hier in diesem Beispiel auch ein.

Flapdoodle

Zusätzlich für die Entwicklung und das Unittesting binde ich hier Flapdoodle ein. Flapdoodle kann eine temporäre embedded Version von MongoDB starten. Dazu lädt es ggf. MongoDB herunter und startet für jeden Test eine temporäre Instanz der MongoDB Datenbank. Es wird immer mit einer leeren Datenbank gestartet, so dass hier im Reinraum getestet werden kann, ohne das man zuvor aufräumen muss.

Actuator

Ich habe hier auch spring-boot-actuator mit aufgenommen, damit ich in der Spring Tool Suite 4 die Endpoints in den Properties der Anwendung angezeigt bekomme, bzw. die Live Hover Informationen nutzen kann (siehe Bug in Live Hover).

Die Klasse Reservation

Die Klasse Reservation bildet eine Reservierung ab. Da es hier nur um eine Demonstration handelt, ist hier natürlich genügend Spielraum vorhanden für Erweiterungen. Wie bereits beschrieben nutze ich hier Lombok Annotationen, um möglichst sauber von Boilerplate Code zu arbeiten

Die Klasse Reservation ist also kurz und knackig.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document
class Reservation {
    private String id;

    private String reservationName;
}

Die Annotation @Document beschreibt des sich um ein MongoDB Dokument handelt. Ein Dokument hält einen Datensatz. In MongoDB werden als JSON Objekte übergeben und dann intern als BSON abgespeichert.

Mono und Flux

Bei der reaktiven Programmierung gibt es die beiden Klassen Mono und Flux die das Publisher Interface implementieren. Beide Implementierungen liefern einen Stream von Elementen mit…

Mono genau 0 oder 1 Elemente
Flux 0 oder 1...n Elemente

Die Konfiguration der Routen

Die Klasse ReservationRouter übernimmt das Routing der Request zu den entsprechenden Handlermethoden in der Klasse RequestHandler.

Ein GET Aufruf auf /reservations gibt JSON mit allen Reservierungen aus der Datenbank zurück

Ein GET Aufruf auf /reservation/{id} lädt genau eine Reservierung mit der angegebenen Id

Ein DELETE Aufruf auf /reservation/{id} löscht den referenzierten Datensatz

Ein POST Aufruf auf /reservation erzeugt eine neue Reservierung

Ein PUT Aufruf auf /reservation/{id} modifiziert den referenzierten Datensatz

Die statische Methode i macht die Aufrufe CaseInsensitiv.

@Configuration
public class ReservationRouter {
    @Bean
    RouterFunction<ServerResponse> routes(RequestHandler handler) {
        return route(i(GET("/reservations")), handler::all)
            .andRoute(i(GET("/reservation/{id}")), handler::getById)
            .andRoute(i(DELETE("/reservation/{id}")), handler::deleteById)
            .andRoute(i(POST("/reservation")), handler::create)
            .andRoute(i(PUT("/reservation/{id}")), handler::updateById)
            ;
    }

    private static RequestPredicate i(RequestPredicate target) {
        return new CaseInsensitiveRequestPredicate(target);
    }
}

Die statische Methode route definiert nun mit den angegebenen RequestPredicates (GET, DELETE, POST und PUT) und einem Patternstring, welche Handlermethode den Request bearbeitet.

Warum muss ich schon wieder eine neue Methode zur Konfiguration lernen?

Sicherlich haben Sie sich auch schon gefragt, Moment Mal, das geht doch auch alles mit den bewährten Annotationen. Ja, das stimmt und daran gibt es auch nicht auszusetzen. Es gibt allerdings einen triftigen Grund warum man sich de functional configuration annehmen sollte. Es ist die Performance. Es lassen sich die Startupzeiten mit dem neuen funktionalen Ansatz drastisch, gegenüber der klassischen Methode mit Annotationen, verkürzen. Dieses ist insbesondere im Microservice Bereich, wo schnell mal ein paar zusätzliche Instanzen gespawnt werden müssen, wichtig.

Die Handler Klasse

Die Handlerklasse RequestHandler verarbeitet nun die ankommenden Requests und führt Aktionen auf dem Service durch. Die Methode getById und deleteById werden direkt durchgereicht an den Service. Es wird nur die Pathvariable id extrahiert und dem Service als Parameter übergeben.

Die Handlermethoden liefern ein Mono<ServerResponse> zurück. Spannend wird es der Aufruf von create hier wird der Übergebene ServerRequest in eine Reservation.class gemappt und der Service soll die Reservierung anlegen.

Zu bemerken ist, dass das reaktive Paradigma hier in beide Richtungen eingehalten wird. Das Anfragen die und auch Antworten als Mono oder Flux zwischen dem Server und dem Service ausgetauscht werden. Der hier gezeigte Handler ist vollkommen Asynchron und auch reaktiv, da hier keine blockierenden Operationen ausgeführt werden.

@Component
public class RequestHandler {
    private ReservationService service;

    public RequestHandler(ReservationService service) {
        this.service = service;
    }

    Mono<ServerResponse> getById(ServerRequest r) {
        return defaultReadResponse(service.getById(id(r)));
    }

    Mono<ServerResponse> deleteById(ServerRequest r) {
        return defaultReadResponse(service.deleteById(id(r)));
    }

    Mono<ServerResponse> create(ServerRequest r) {
        Flux<Reservation> flux = r
                .bodyToFlux(Reservation.class)
                .flatMap(toWrite -> service.create(toWrite.getReservationName()));
        return defaultWriteResponse(flux);
    }

...

    private static Mono<ServerResponse> defaultWriteResponse(Publisher<Reservation> reservations) {
        return Mono
                .from(reservations)
                .flatMap(r -> ServerResponse.created(URI.create("/reservation/" + r.getId()))
                .contentType(MediaType.APPLICATION_JSON_UTF8).build());
    }

    private static Mono<ServerResponse> defaultReadResponse(Publisher<Reservation> publisher) {
        return ServerResponse
                .ok()
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(publisher, Reservation.class);
    }

    private static String id(ServerRequest r) {
        return r.pathVariable("id");
    }
}

Der Service

Im vorherigen Abschnitt haben wir beleuchtet wie die Anfragen im Handler verarbeitet werden. Der Service bildet hier fast 1:1 das Repository ab.

@Service
public class ReservationService {
    private ReservationRepository repo;

    public ReservationService(ReservationRepository repo) {
        this.repo = repo;
    }

    public Mono<Reservation> getById(String id) {
        return repo.findById(id);
    }

    public Flux<Reservation> findAll() {
        return repo.findAll();
    }

    public Mono<Reservation> deleteById(String id) {
        return repo.findById(id)
                .flatMap(
                        r -> repo.deleteById(r.getId())
                        .thenReturn(r)
                );
    }

    public Mono<Reservation> create(String reservationName) {
        return repo.save(new Reservation(null, reservationName));
    }
}

Der Service greift auf das reaktive Repository ReservationRepository zu und es werden entsprechend Mono oder Flux<reservation> zurückgegeben. Bei der findAll() haben wir einen Stream mit allen Datensätzen aus der Datenbank. Deshalb kommt hier Flux als Implementierung des Publisher Interfaces zum Einsatz, weil es 0 bis n Datensätze sein können.

Das ReservationRepository

Das Repository ist von Typ ReactiveMongoRepository und stellt die CRUD Methoden zur Verfügung. Wir verwenden hier derived queries, um eine zusätzliche Anfrage für den Service bereitzustellen. Mit findBy+Property kann Spring Data aus dem Methodennamen den gewünschten Query erzeugen. Das heißt wir suchen mit findByReservationName(String name) nach einem Datensatz wo die Property ReservationName dem übergebenen Parameter entspricht.

Dieses ist aber ein Standard Feature von Spring Data und hat nichts mit der reaktiven Implementierung zu tun. Ansonsten gibt es nichts besonderes in dem Interface.

public interface ReservationRepository extends ReactiveMongoRepository<Reservation, String> {
    Flux<Reservation> findByReservationName(String name);
}

Mit curl die Rest API aufrufen

Das Testen der Anwendung möchte ich hier mit dem Kommandozeilenprogramm curl demonstrieren. Es sind standard HTTP Anfragen an den Server, so dass ich hier nur kurz insert und delete zeige.

Einfügen einer Reservierung

curl -i -X POST -H 'Content-Type: application/json' -d '{ "reservationName": "1. Test" }' http://localhost:8080/reservation

Es wird eine Antwort wie zum Beispiel…

HTTP/1.1 201 Created
Content-Type: application/json;charset=UTF-8
Location: /reservation/5bf68a11dbcbb837d33f4be6
content-length: 0

erzeugt, die das erfolgreiche Anlegen des Datensatze bestätigen.

Löschen einer Reservierung

curl -i -X DELETE http://localhost:8080/reservation/5bf3eda398947a5158e60200

SQLite-JDBC in Memory

Der Java Treiber sqlite-jdbc besitzt die Möglichkeit eine schnelle in Memory Datenbank anzulegen. Hierzu muss nur die JDBC URL angepasst werden.

jdbc:sqlite::memory:

Mit dieser URL wird keine Datenbank Datei im Dateisystem erzeugt und auch der Zugriff auf die Daten ist performanter.

In diesem Beitrag möchte ich auf die Änderungen der bevorstehenden Release von Gradle 5 eingehen. Aktuell ist das 3. Release Candidate veröffentlicht.

Update des Wrappers

Der Wrapper läßt sich wie immer mit wrapper –gradle-version updaten. Etwas verwirrend ist hier das Versionsschema. Es ist mit GIT v5.0.0-RC3 getagt, aber die Version heißt 5.0-rc-3!

./gradlew wrapper --gradle-version=5.0-rc-3

Was gibt es neues in Gradle 5.0

Java 11 Support

Nun kann man endlich Gradle mit Java 11 verwenden. Mit Versionen kleiner 5.0 konnten in einer Java 11 Umgebung keine Builds ausgeführt werden. Es gab diverse Fehlermeldungen.

$ ./gradlew -version

------------------------------------------------------------
Gradle 5.0-rc-3
------------------------------------------------------------

Build time:   2018-11-14 16:01:47 UTC
Revision:     63f11c722124617f7cbe2f95ad5a5e045b8b42f6

Kotlin DSL:   1.0.3
Kotlin:       1.3.0
Groovy:       2.5.3
Ant:          Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM:          11.0.1 (Oracle Corporation 11.0.1+13)
OS:           Linux 4.19.1-1-MANJARO amd64

Was wurde deprecated

Annotation Prozessoren auf dem Klassenpfad

Verwendet man Project Lombok, dann läuft man potenziell Gefahr auf diese Meldung zu stoßen, wenn man einen build mit Warnhinweisen anstößt.

$ ./gradlew --warn --warning-mode=all build


Detecting annotation processors on the compile classpath has been deprecated. Gradle 5.0 will ignore annotation processors on the compile classpath. The following annotation processors were detected on the compile classpath: 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor' and 'lombok.launch.AnnotationProcessorHider$ClaimingProcessor'.  Please add them to the annotation processor path instead. If you did not intend to use annotation processors, you can use the '-proc:none' compiler argument to ignore them.

Gradle möchte ab Version 4, dass ein Annotation Processor in der Konfiguration annotationProcessor hinterlegt wird. Also muss in den Dependencies compileOnly und annotationProcessor wie folgt angegeben werden:

// Project Lombok
// Since Gradle warns if an AnnotationProcessor is found on classpath, put 
// it into annotationProcessor directive
compileOnly('org.projectlombok:lombok')
annotationProcessor('org.projectlombok:lombok')

Was ist die GraalVM

Die GraalVM ist eine neuartige Virtuelle Maschine die hochoptimierten Code erzeugen kann. Die VM ist Polyglot, das heißt sie ist nicht auf eine Programmiersprache begrenzt und auch nicht auf ein Zielsystem festgelegt. Es lassen sich JavaScript Programme für Oracle Datenbank kompilieren und es ist auch schon MySQL in Planung.

Die Version 1.0.0 ist kurz vor dem Release und es ist Release Candidate 9 aktuell. Da GraalVM völlig OpenSource ist, ist auch jeder der interesse an dem Projekt hat, aufgerufen das auszuprobieren und Fehler und Verbesesserungsvorschläge zu melden.

In diesem Betrag stelle ich kurz einige der Möglichkeiten von GraalVM vor. Ich beleuchte Polyglotte Programmierung und das Kompilieren in native Code. Auf Details zur Implementierung der VM und andere technische Hintergründe gehe ich in diesem Beitrag nicht ein.

Installation

Wie immer hier die nötigen Pakete die unter Manjaro/Arch Linux zu installieren sind.

yaourt --noconfirm -S graal-bin graalpython-bin truffleruby-bin npm
yaourt --noconfirm -S fastr-bin

Installation überprüfen

$ sudo archlinux-java status
Available Java environments:
  java-10-openjdk
  java-11-openjdk
  java-8-graal
  java-8-openjdk (default)

Es muss noch die Default VM geändert werden.

sudo archlinux-java set java-8-graal

Nun sollte auch das Graal Component Updater (gu) im Pfad vorhanden sein.

$ gu
GraalVM Component Updater v1.0.0

Usage: 
        gu info [-cFlprstuv] <param>      prints info about specific component (from file, URL or catalog)
        gu available [-lv] <expr>         lists components available in catalog
        gu install [-0cfFnorvyxY] <param> installs a component package
        gu list [-clv] <expression>       lists installed components, or components from catalog
        gu uninstall [-0fxv] <id>         uninstalls a component
        gu rebuild-images                 rebuilds native images. Use -h for detailed usage

Common options:
...

Mit dem Graal Updater lassen sich Komponenten nachinstallieren und updaten. Da wir für die Installation den Paketmanager Pacman verwendet haben und die Installation im Systemverzeichnis liegt, schlägt ein Update oder eine Installation fehl.

Polyglottes Beispiel

Auf der Homepage von Graal gibt es ein paar Beispiele. Ein Beispiel was mich am meisten fasziniert, ist das Beispiel wo die Anwendung der Polyglotten Programmierung gezeigt wird.

Vorbereitung

Zunächst müssen die Quelltexte ausgecheckt werden. Dazu wird die übliche Prozedur mit GIT clone verwedendet. Danach rufen wir das Buildskript auf, dass per NPM die benötigten Dependencies installiert.

git clone https://github.com/graalvm/graalvm-demos
cd graalvm-demos/polyglot-javascript-java-r
./build.sh

Quelltext des Servers

Die server.js ist sehr kurz und daher zeige ich hier den gesamten Quelltext des Servers.

/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const express = require('express')
const app = express()

const BigInteger = Java.type('java.math.BigInteger')


app.get('/', function (req, res) {
  var text = 'Hello World from Graal.js!<br> '

  // Using Java standard library classes
  text += BigInteger.valueOf(10).pow(100)
          .add(BigInteger.valueOf(43)).toString() + '<br>'

  // Using R methods to return arrays
  text += Polyglot.eval('R',
      'ifelse(1 > 2, "no", paste(1:42, c="|"))') + '<br>'

  // Using R interoperability to create graphs
  text += Polyglot.eval('R',
    `svg();
     require(lattice);
     x <- 1:100
     y <- sin(x/10)
     z <- cos(x^1.3/(runif(1)*5+10))
     print(cloud(x~y*z, main="cloud plot"))
     grDevices:::svg.off()
    `);

  res.send(text)
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

Was heißt Polyglotte Programmierung?

Wenn man den Quelltext des Server sich anschaut, dann erkennt man das Polyglot.eval() aufgerufen wird. Mit .eval können Ausdrücke von anderen Sprachen (hier R) evaluiert werden und das Ergebnis kann normal weiterverarbeitet werden. Auch der Zugriff auf Java Klassen funktioniert, ohne eine spezielle Anweisung.

Starten des NodeJS Server

Der Server der mitgelieferten NodeJS Version in dem graal-bin Package gestartet. Der Parameter –polyglot ermöglicht das die Polyglotte Ausführung möglich wird. Der Parameter –jvm sorgt dafür, dass GraalVM zum Zuge kommt und die Optimierungen durchgeführt werden.

/usr/lib/jvm/java-8-graal/jre/bin/node --polyglot --jvm server.js

Nach dem Starten kann man die Anwendung unter http://localhost:3000 aufrufen.

Debuggen der Anwendung

Die Anwendung läßt sich im Chrome Devtools Debugger debuggen. Man muss den Server nur mit dem Parameter –inspect starten.

$ /usr/lib/jvm/java-8-graal/jre/bin/node --inspect --polyglot --jvm server.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ea4a92b-3aa8163241f

Nun sehen wir in der Konsole die URL mit der man die Anwendung debuggen kann.

Hinweis: Aktuell kennt KDE das Custom Protocol nicht, so dass die URL aus der Konsole heraus nicht direkt geöffnet werden kann. Es läßt sich bestimmt ein Protocol Handler registieren, so dass die URI direkt geöffnet werden kann.

Jetzt muss man nur den Ordner mit den Quelltexten in den Workspace ziehen und kann dann den Quelltext des server.js öffnen und zum Beispiel einen Breakpoint setzen und Variablen inspizieren.

Nativer Code

Die GraalVM kann aus einer JavaVM und einem JAR-File ein Blob erzeugen, der sich genauso verhält wie eine Anwendung die in einer “normalen” Java VM läuft. Die Startupzeiten sind dann extrem gering, im Vergleich zu den Startupzeiten einer HotSpot VM. Dieses macht Java Anwendungen noch interessanter in Containern, wo schnell eine neue Instanz einer Anwendung gespawnt werden muss.

Hierfür ist ein Sub Projekt der GraalVM zuständig. Es nennt sich SubstrateVM. JVM Ökosystem muss noch an GraalVM angepasst werden. Für Spring ist mit der Version 5.1 initialer Support für die neue VM vorhanden.

Übersetzen eines JAR

Für die Übersetzung wird in der GraalVM ein Programm native-image mitgeliefert, welches eine JAR untersucht und dann ein Blob erzeugen kann. Der Blob kann dann agnz normal ausgeführt werden.

native-image -jar /Pfad/zum/JAR

Limitierungen

Die SubstrateVM befindet sich noch in der aktiven Entwicklung und daher werden noch nicht alle Funktionen vollständig unterstützt (siehe hier).

In nativen Code übersetzen

Das bauen von nativen Anwendungen mit native-image funktioniert mit Spring Boot Anwendungen noch nicht, weil das dynamische laden von Klassen wohl (noch) nicht voll umfänglich unterstützt wird.

Hier die Fehlermeldung, wenn versucht ein Spring Boot Application zu übersetzen.

$ native-image -jar ./build/libs/spring-data-jdbc-demo-0.0.1-SNAPSHOT.jar 
Build on Server(pid: 8784, port: 42925)
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]    classlist:     334.59 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]        (cap):   3,266.94 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]        setup:   3,846.85 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]     analysis:   4,875.69 ms
error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
        at parsing java.net.URL.setURLStreamHandlerFactory(URL.java:1118)
Call path from entry point to java.net.URL.setURLStreamHandlerFactory(URLStreamHandlerFactory): 
        at java.net.URL.setURLStreamHandlerFactory(URL.java:1110)
        at org.springframework.boot.loader.jar.JarFile.resetCachedUrlHandlers(JarFile.java:401)
        at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:391)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:48)
        at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
        at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:164)
        at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)

Error: Processing image build request failed