Schlanke JEE-Services mit Apache Meecrowave

Das Programmmodell von JEE (aka Java EE / Jakarta EE) ermöglicht schon seit vielen Jahren – namentlich seit der Version 5 – eine effiziente Entwicklung von schlanken Geschäftsanwendungen. Als Ablaufumgebungen stehen ausgereifte und leistungsfähige JEE-Server wie WildFly oder Open Liberty zur Verfügung, die als eine Art Allrounder den Anwendungen recht umfassende Infrastruktur anbieten und sogar mehrere Anwendungen gleichzeitig betreiben können.

Gerade der letzte Punkt, der Parallelbetrieb mehrere Anwendungen, wird allerdings nur selten genutzt, um Störeffekte zwischen Anwendungen zu minimieren. Es gibt also typischerweise genau eine Anwendung pro Server, der ggf. sogar selbst in einem dedizierten Rechner – meist einer virtuelle Maschine – läuft.

Wenn man nun noch einen nur eingeschränkten Teil der angebotenen Infrastruktur benötigt, bspw. JPA, CDI und JAX-RS, also den klassischen Microservice-Stack, kann man sich die Frage stellen, ob tatsächlich ein kompletter JEE-Server gebraucht wird, oder ob man nicht einfach die benötigten Dienste in die Anwendung einbetten könnte.

An dieser Stelle setzt Apache Meecrowave an. Dieses Apache-Projekt verknüpft den JPA-Provider EclipseLink, den CDI-Container OpenWebBeans und die REST-Implementierung CXF zu einem ganz schlanken Server – oder eigentlich zu einer Server-Bibliothek, mit deren Hilfe man den Server einfach als Main-Programm startet.

Zur Einbindung von Meecrowave reicht es aus, die folgende Dependency in den Classpath zu holen:

  
    org.apache.meecrowave
    meecrowave-core
    1.2.3
  

Der in der Anwendung eingebettete Server wird dann z. B. so gestartet:

  public static void main(String[] args) {
    try (Meecrowave meecrowave = new Meecrowave().bake()) {
      // Do work here ...
    }

Beim Eintritt in den try-Block wird der Server gestartet, beim Verlassen wieder gestoppt. Sämtliche aus CDI bekannte Mechanismen für den Bootstrap einer Anwendung können zur Aktivierung von Services etc. genutzt werden, z. B. Observer für den Container Lifecycle:

  void startSomething(@Observes @Initialized(ApplicationScoped.class) Object obj) {
    // Gets called on server start ...
  }

Ein einfacher REST-Service ist dann nur noch ein Ding von wenigen Zeilen:

@Path("hello")
@ApplicationScoped
public class HelloResource {

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String getHello() {
    return "Hello!";
  }
}

So ist also dieser – zugegebenermaßen winzige – Microservice mit nur ganz wenig Code schon fertig. Und beeindruckt mit Startzeiten im Sekundenbruchteil-Bereich sowie einem Memory Footprint von nur ca. 12 MB.

Ein einfaches Demoprojekt kann von GitHub heruntergeladen werden: https://github.com/GEDOPLAN/meecrowave-demo.

Bis bald – vielleicht in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
http://gedoplan-it-training.de/

Advertisements

Jackson, mehr Annotationen

In früheren Artikeln haben wir bereits einige Features von Jackson gesehen die uns das Erzeugen und Verarbeiten von JSON-Strukturen einfacher macht. So lassen sich unsere Businessobjekte mittels @JsonView gefilterte in eine JSON-Ansicht bringen oder Dank @JsonIdentityInfo und entsprechenden Resolver selbst JPA-Relationen sauber verarbeiten. Zwei weitere Möglichkeiten schauen wir uns heute an

updateValue

Wenn JPA-Relationen oder auch einzelne Felder nicht ins JSON Format übertragen werden sollen lässt sich das sehr einfach mittels @JsonIgnore oder @JsonView erledigen. Bei der Verwendung solcher Strukturen als Input für das Updaten unserer Entitäten führt das jedoch zu einem Problem: nicht vorhandene Attribute werden mit „null“ vorbelegt, was in aller Regel zu nicht gewollten Änderungen unserer Businessobjekte führt. Hier kommt, ähnlich wie bei der Verwendung von DTOs, in aller Regel ein Mapping-Framework ins Spiel welches die neuen Daten aus unserem JSON dazu verwendet das bestehende Objekt zu aktualisieren. Seit der Version 2.9 können wir dies auch getrost von Jackson erledigen lassen:

   @PUT
   @Path("{id}")
   public Response updateAuthor(@PathParam("id") Integer id, JsonNode authorJson){
        Author dbAuthor = this.authorRepository.getAuthorById(id);
        dbAuthor = objectMapper.updateValue(dbAuthor, authorJson);
        dbAuthor= this.authorRepository.merge(dbAuthor);
        return Response.ok().build();
   }

@JsonAppend

DTOs sind ein praktisches Vorgehen um zusätzliche Attribute oder gänzlich andere Strukturen als unsere Businessobjekte per REST zu liefern. Der Aufwand zum Schreiben und der Pflege dieser DTO-Klassen sollte allerdings nicht unterschätzt werden. Wollen wir lediglich einige einzelne zusätzliche Attribute zu den bestehenden Businessobjekt-Attribute an unser JSON anhängen hält Jackson auch hierfür eine Lösung parat:

//- Entity - ------------------------------------------
@Entity
@JsonAppend(
    attrs = {
        @JsonAppend.Attr(value = "bookcount")
    }
)
public class Author {...}

//- Resource - ------------------------------------------

    @GET
    @Path("{id}")
    public Response getAuthor(@PathParam("id") Integer id) {
        ...
            String response=objectMapper
                    .writerFor(Author.class)
                    .withView(GlobalViews.Overview.class)
                    .withAttribute("bookcount", authorById.getBooks().size())
                    .writeValueAsString(authorById);
            
            return Response.ok(response).build();
        }
    }

(im Default sind diese Append-Attribute optional, werden also nur gerendert wenn auch ein Wert zugewiesen wird. Dieses Verhalten kann durch das Setzen des Annotation-Attributes ‚include‘ geändert werden

Jackson und Java EE 8


Jackson ist eine Bibliothek die sich unter anderem darum kümmert das unsere Businessobjekte in JSON umgewandelt werden. Als default Provider für diese Aufgabe war Jackson bisher im Wildfly Application Server vorhanden. Seit Java EE 8 (und Wildfly 13) gibt es nun einen neuen Standard der sich um diese Aufgabe kümmert: JSON-B. Dieser ist leider in Sachen Funktionsumfang bei weitem noch nicht auf den Stand den Jackson erreicht hat. Um Jackson weiterhin zu nutzen sind (leider) einige Schritte von nöten

Laut Spezifikation von JAX-RS reicht es aus eine @Provider-Klasse an zu bieten welche für einen bestimmten Media Type die Konvertierung unserer Businessobjekte übernimmt. Diesen liefert Jackson mit einer entsprechenden Maven Dependency praktischerweise gleich mit (com.fasterxml.jackson.jaxrs, jackson-jaxrs-json-provider ). Schade nur das es bei dieser Theorie bleibt. Zumindest der Wildfly 13 (im EE8 Profil) und der Glassfish 5 weigern sich dieses einfache Vorgehen mit Erfolg zu krönen.

Wildfly 13

Ein Bug in RestEasy (RESTEASY-1911 ) verhindert hier das Jackson korrekt eingebunden wird. Ab der Version 3.6 soll dieser Umstand behoben sein, bis dahin hilft der Ausschluss des Resteasy Json Providers:

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.0">
    <jdeployment>
        <jexclusions>
            <jmodule name="org.jboss.resteasy.resteasy-json-binding-provider"><j/module>
        <j/exclusions>
    <j/deployment>
<j/jboss-deployment-structure>

src/main/webapp/WEB-INF/jboss-deployment-structure.xml

Glassfish 5

Die Referenzimplementierung für Java EE 8 ist hartnäckig was das Ersetzen von JSON-B als JSON Provider angeht. Hier ist ein zusätzlicher Konfigurationsparamter notwenig:

@ApplicationPath("resources")
public class ApplicationConfig extends Application {

    @Override
    public Map getProperties() {
        Map proprties = new HashMap();
        proprties.put("jersey.config.server.disableMoxyJson", true); //Glassfish = 5

        return proprties;
    }

}

Optional: globaler ObjectMapper

Jacksons ObjectMapper bietet eine ganze Reihe von globalen Einstellungen um das Verhalten beim Parsen von JSON-Strukturen zu beeinflussen (Format, Umgang mit null-Werten und unbekannter Attribute…). Um in der gesamten Anwendung eine einheitliche Konfiguration zu verwenden bietet sich ein entsprechner CDI Producer an:

@ApplicationScoped
public class GlobalCDIProducer {

    private ObjectMapper mapper;
    
    @Produces
    public ObjectMapper getMapper(){
        if(this.mapper==null){
            this.mapper=new ObjectMapper();
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            this.mapper.setDateFormat(df);
        }
        return this.mapper;
    }
}

Diesen können wir natürlich selber in unseren Resouce-Klassen injizieren und Verwenden, aber auch JAX-RS zur Verfügung stellen:

@Provider
public class JacksonProvider implements ContextResolver<ObjectMapper> {

    @Inject
    private ObjectMapper mapper;
    
    @Override
    public ObjectMapper getContext(Class<?> type) {
        return getMapper();
    }
}

Java EE 8, Security (JSR 375)


Für die Deklaration von Security brauchen wird xml-Dateien. Sei es die web.xml oder serverspezifische Konfigurationsdateien. So ist es lange Zeit gewesen. Bis jetzt. Java EE 8 weicht diese Feststellung nun auf und führt Interfaces ein, wie den HttpAuthenticationMechanism um die Deklaration dieser Regeln in den Code zu verlagern.

ee8security

Java EE 8 liefert neben dem angesprochenen Interface (und weiterer) auch gleich drei passende Implementierungen mit:

  • BasicAuthenticationMechanismDefinition
  • FormAuthenticationMechanismDefinition
  • CustomFormAuthenrticationMechanismDefinition

In diesem Beispiel widmen wir uns einem davon: FormAuthenticationMechanismDefinition. Die Idee ist einfach. Anstatt die Konfiguration in xml-Dateien vor zu nehmen, verwenden wir Annotationen um die Security-Regeln (an einer ApplicationScoped-Bean) zu deklarieren. Eine solche Deklaration könnte so aussehen:

@FormAuthenticationMechanismDefinition(
        loginToContinue = @LoginToContinue(
                loginPage = "/login.html",
                errorPage = "/error.html"))

Damit legen wir erst einmal nur fest wie der User seine Login-Informationen übergeben soll (nämlich über die login.html Datei). Ebenso spannend ist die Frage „Wie autorisiert unsere Anwendung einen User?“. Wer hätte es erwartet, auch hier liefert der JSR375 ein passendes Interface (IdentityStore) und entsprechende Implementierungen (DataBaseIdentityStoreDefinition, LdapIdentityStoreDefinition).

@DatabaseIdentityStoreDefinition(
        dataSourceLookup = "java:jboss/datasources/DemoDS",
        callerQuery = "select PASSWORD from USER where USERNAME=?",
        groupsQuery = "select GROUPNAME from USERGROUPS where USERNAME=?",
        hashAlgorithm = de.gedoplan.PlainTextPasswordHash.class
)
@ApplicationScoped
public class ApplicationSecurityConfig {}

Hier geben wir an welche Datasource und Queries verwendet werden sollen um einen Benutzer zu validieren und dessen Benutzergruppen aus zu lesen. Zusätzlich muss hier eine PasswordHash-Implementierung angegeben werden (javax.security.enterprise.identitystore.PasswordHash). Mit Pbkdf2PasswordHash wird eine Implementierung mitgeliefert, das Beispiel oben verwendet eine eigene Implementierung welche die Passwörter in Klartext in der Datenbank ablegt (don’t do this at home).

Das war es schon ( fast ) jetzt können wir unsere Resourcen mit entsprechenden Berechtigungen versehen. Hier in Kürze 3 Varianten:

Servlets:

@WebServlet(urlPatterns = "/servlet")
@ServletSecurity(@HttpConstraint(rolesAllowed = "ADMIN"))
public class DemoServlet extends HttpServlet{...}

JAX-RS, Rest Webservices

@Path("demo")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Stateless
public class DemoResource {

    @GET
    @PermitAll
    public DemoModel getDemo() {...}
    
    @GET
    @Path("admin")
    @RolesAllowed("ADMIN")
    public DemoModel getAdminDemo() {...}
}

Webseiten über web.xml (dann doch noch mal xml)

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected user resource/url</web-resource-name>
            <url-pattern>/secured/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

Das wars. Alles. Live. In Farbe:

https://github.com/GEDOPLAN/jee8-security

Anmerkung zur Verwendung von Wildfly:
Das gezeigte Feature muss durch die Aktivierung der Security-Domain „jaspitest“ erst aktiviert werden (ab Wildfly 10)
(in der jboss-web.xml<jboss-web> <security-domain>jaspitest </jboss-web>)
Hier auch eine Anleitung für andere Versionen: click

Generics, Type Erasure ausgetrickst

Generics sind eine feine Sache die in nahezu jeder Application anwendung finden. Die meisten Entwickler werden jedoch festgestellt haben das die generische Typinformation zur Laufzeit nicht mehr zur Verfügung stehen. Aber wie dann zur Laufzeit an den Typ gelangen?

„Type Erasure“ heißt das „Phänomen“ das aus abwärtskompatiblen Gründen fester Bestandteil des Java Compilers ist. Generische Informationen sind somit nur zur Compile-Zeit vorhanden und werden bei der Übersetzung in den ByteCode entfernt. Damit fällt eine Factory wir die folgende erst einmal weg:

public class Factory<T> {

public T create() {
       return new T(); // Type-Erasure in Action
}

Wir können natürlich nun die konkrete Klasse als zusätzlichen Parameter in eine solche Factory übergeben um auf Basis dieser neue Instanzen zu erzeugen. Das funktioniert, fühlt sich aber nach „doppeltem Aufwand“ an, da die konkrete Klasse zwei mal angegeben wird.

Ein kleine Schlupfloch gibt es aber: anonyme Klassen. Anders als „normale“ Klassen sind bei anonymen Klassen die generischen Informationen auch zur Laufzeit vorhanden.

In unserer Factory können wir dann per Reflection den generischen Typ ermitteln und damit z.B. eine entsprechende Instanz erzeugen.

public class Factory<T> {

    public T create() throws InstantiationException, IllegalAccessException {
        Type superClassType = this.getClass().getGenericSuperclass();
        Type tType = ((ParameterizedType) superClassType).getActualTypeArguments()[0];
        Class clazz = (Class) tType;
        
        return clazz.newInstance();
    }
}
//Type Erasure > Exception
Customer c = new Factory<Customer>().create();

//anonyme Klasse
Customer c = new Factory<Customer>(){}.create();

Angular, asyncrone Initialisierung

Es gibt Dinge die sollte man einfach nicht aufschieben, dazu zählen auch immer wieder diverse Schritte die vor dem Start der eigentlichen Anwendung nötig sind. Sei es eine externe Konfiguration zu laden oder den alten Login Status eines Benutzers zu prüfen. Angular bietet hier die Möglichkeit diese (oftmals asynchronen) Aufgaben per APP_INITIALIZER Multi-Provider zu realisieren

angular_init_demo

Für diesen Zweck bietet Angular einen Multi Provider (analog zu z.B. NG_VALIDATORS) an: APP_INITIALIZER der in unserem Modul mittels Provider-Deklaration registriert wird.

@NgModule({
  providers: [
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [HttpClient],
      useFactory: initApplication
    },

Neben der provide-Token (APP_INITIALIZER) und der Angabe das es sich um einen Multi-Provider3 handelt (multi: true), müssen wir eventuell benötige Depedencies deklarieren die in unserem Beispiel unserer Factory-Funktion übergeben werden.

Bei der Factory handelt es sich um eine im Modul deklarierte Methode die eine neue Methode zurück liefern muss die wärend der Initialisierung aufgerufen wird.
Diese erzeugte Methode kann dann entweder synchron ablaufen oder aber ein Promise liefern auf dessen Abarbeitung gewartet wird (Achtung, auf die Rückkehr von Observables wird hier nicht gewartet).

export function initApplication(http: HttpClient) {
  return () => {
    return new Promise((resolve, reject) => {
      //make some init steps, load data for example
      http
        .get('https://jsonplaceholder.typicode.com/users/1')
        .delay(5000) //wait a little bit, just to enjoy the loading gif
        .subscribe(
          r => {
            //handle response
            console.log('Init 1');
            resolve(r);
          },
          e => {
            // dont forget to handle errors or user will only see a blank screen
            document.body.innerHTML = 'Schwerer Ausnahmefehler';
          }
        );
    });
  };
}

Die Anwendung lädt, führt dann erst die Initialisierung durch und startet dann wie gewohnt. Dadurch sind wir in der Lage im Vorfeld sicher zu stellen das benötigte Services bereits initialisiert sind oder die Anwendung korrekt konfiguriert wurde

Demo? Auf GitHub.

 

JPA + Rest, JSON-B in Action

„JSR 367“ alias JSON-B soll es endlich richten: ein standardisiertes JSON-Binding, ähnlich der Verarbeitung von XML mit JAXB. Gerade bei der Arbeit mit Rest-Schnittstellen und JPA stolpert man immer wieder über die gleichen Probleme. Wir machen einen ganz kurzen Rundflug über JSON-B und schauen uns eine Möglichkeit die JSON-B Adapters zu verwenden um JPA-Relationen zu mappen.

Einfache Möglichkeiten in die JSON-Generierung ein zu greifen fehlten bisher im Standard. Anbieter wie z.B. Jackson waren hier bereit viel weiter. JSON-B, Standard JSON-Binding in Java EE 8, bringt nun einige Möglichkeiten in diesem Bereich mit.

Annotationen

  • @JsonbProperty, Überschreiben des JSON-Property
  • @JsonbTransient, Attribute ignorieren
  • @JsonbPropertyOrder, Sortierung der Attribute anpassen
  • @JsonbDateFormat, Format für Datum
  • @JsonbNumberFormat, Format für Zahlen

Adapter

Neben der Implementierung einer Low-Level Serialisierung (implements JsonbSerializer) gibt es die Möglichkeit einen Art Converter zu implementieren der für die Verarbeitung einzelner Attribute verwendet werden kann. Ein interessantes Szenario für diesen Fall ist z.B. Entitäten die per JPA-Relationen referenziert werden nur durch ihre IDs zu repräsentieren (anstatt durch das gesamte Objekt). Das minimiert nicht nur die zu übertragenden Daten sondern verhindert auch das Problem von „circular dependencies“ bei bidirektionalen Relationen. Ein solcher (generischer) Adapter könnte so aussehen:

public abstract class EntityAdapter<T extends JPAEntity> implements JsonbAdapter<List<T>, JsonArray>{

    @Inject
    private EntityManager em;

    private Class getTargetClass() {
        return (Class) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public JsonArray adaptToJson(List orgnl) throws Exception {
        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
        orgnl.stream().map(JPAEntity::getId).forEach(arrayBuilder::add);
        return arrayBuilder.build();
    }

    public List adaptFromJson(JsonArray adptd) throws Exception {
        List list = new ArrayList();

        adptd.stream().forEach(val -> {
            Integer id = ((JsonNumber) val).intValue();
            list.add(em.getReference(getTargetClass(), id));
        });

        return list;
    }

}

Das Vorgehen ist schnell erklärt. Die Liste der referenzierten Entitäten wird nur mit den IDs befüllt. Diese werden bei der Deserialisierung dazu verwendet JPA-Referenzen zu laden um die korrekten Relationen wieder her zu stellen.

jsonb_demo.png

Dieser Adpater kann nun in unserer Resource verwendet werden um die JSON-B Konfiguration zu erweitern

    @GET
    public Response getTalks() {
        JsonbConfig config = new JsonbConfig().withAdapters(new EntityAdapter() {});
        Jsonb jsonb = JsonbBuilder.create(config);

        List allTalks = this.talkRepository.findAll();
        return Response.ok(jsonb.toJson(allTalks)).build();
    }

(zu beachten ist die Verwendung der anonymen Klasse bei der Instanziierung des Adapters. Dies wird benötigt damit JSON-B den generischen Typ zur Laufzeit ermitteln zu kann)

Alternativ ließe sich ein Adapter auch fest per Annotation an der Relation definieren:

@JsonbTypeAdapter(SpeakerAdapter.class)

(stand heute gibt es hier jedoch keine Möglichkeit zusätzliche Parameter, wie den Ziel-Entity-Typ mit zu geben (im Beispiel oben durch Generics und Reflection gelöst). Demzufolge müsste hier ein konkreter Adapter pro Entity Klasse implementiert werden)

Keine Frage viele Bibliotheken zur JSON-Verarbeitung sind ein ganzes Stück weiter. Mit JSON-B geht Java EE 8 aber einen großen Schritt in die richtige Richtung.

Github? Klar!