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, Author 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

Advertisements

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!

 

JPA + REST, Jackson Resolver


JAX-RS macht das Erstellen von Webservices leicht. Innerhalb von Minuten ist die Businesslogik und JPA Entitäten über REST im JSON Format verfügbar. Aber Moment! LazyLoadException? Circular Dependencies? Schnell stellen wir fest das unsere komplexen JPA Entitäten eben nicht out of the box in ein JSON Format übertragen werden können wenn Relationen und Bidirektionale Verbindungen im Spiel sind. Die Verarbeitung solche Relationen ist dann oftmals mühsam und mit viel manuellen Aufwand verbunden wenn der Umweg über DTOs gegangen wird. Jackson (JSON Parser, Standardimplementierung z.B. im Wildfly) biete hier eine interessante Möglichkeit: JsonIdentityInfo mit eigenem Resolver.

Folgendes einfaches Datenmodel führt ohne Eingriff bereits zu besagten Problemen:

 

@Entity
public class Talk {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @NotNull
    @Size(min = 5)
    private String title;

    @ManyToMany(fetch = FetchType.EAGER)
    private List speakers;
}

@Entity
public class Speaker {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    private String firstname;

    private String lastname;

    @ManyToMany(mappedBy = "speakers", fetch = FetchType.EAGER)
    private List talks;

JSON kennt keine Referenzen und würde beim Verarbeiten dieser Entitäten in eine Endlosschleife münden (Talk > Speaker > Talk > Speaker…). Um dies zu verhindern gibt es eine ganze Reihe von Möglichleiten, z.B. die Verwendung von @JSONIgnore (Referenzen beim JSON erzeugen ignorieren) oder die Entwicklung von einem DTO. Auch Denkbar ist die Verwendung von Jacksons @JsonIdentityInfo Annotation:

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    @JsonIdentityReference(alwaysAsId = true)
    @ManyToMany(fetch = FetchType.EAGER)
    private List speakers = new ArrayList();

Dies führt dazu das Jackson diese Relation immer lediglich mit dem Feld „ID“ im JSON versieht:

{
„id“: 1,
„title“: „Power Workshop Java EE“,
„speakers“: [
1,3
],
„talkType“: „WORKSHOP“,
„duration“: 480
},

Diese Varianten haben bisher einen entscheidenden Nachteil: ein einfaches Ändern der Relationen ist ohne manuelle Schritte nicht möglich:

  • @JSONIgnore: würde sogar dazu führen das die Referenzen auf Speaker entfernt werden, da sie nicht im JSON geliefert werden und bei einem „merge“ = „null“ sind
  • DTO: manueller Aufbau der Entität und „merge“

Auch @JsonIdentityInfo hilft in diesem Fall noch nicht. Optional kann dieser Annotation aber eine „resolver“ übergeben werden, der für die Umwandlung in/von JSON sorgt und der in unserem Fall die entsprechende Entitäts-Referenz aus der Datenbank holt und diese korrekt zuweist:

    @JsonIdentityInfo(
         resolver = JPAResolver.class, 
         generator = ObjectIdGenerators.PropertyGenerator.class, 
         scope = Speaker.class, 
         property = "id"
     )

Der Resolver:

public class JPAResolver extends SimpleObjectIdResolver {

    private EntityManager em;

    public JPAResolver() {
        this.em = CDI.current().select(EntityManager.class).get();
    }

    @Override
    public void bindItem(IdKey id, Object pojo) {
        super.bindItem(id, pojo);
    }

    @Override
    public Object resolveId(IdKey id) {
        Object resolved = super.resolveId(id);
        if (resolved == null) {
            resolved = loadFromDatabase(id);
            bindItem(id, resolved);
        }

        return resolved;
    }

    private Object loadFromDatabase(IdKey idKey) {
        return this.em.getReference(idKey.scope, idKey.key);
    }

    @Override
    public ObjectIdResolver newForDeserialization(Object context) {
        return new JPAResolver();
    }

    @Override
    public boolean canUseFor(ObjectIdResolver resolverType) {
        return resolverType.getClass() == JPAResolver.class;
    }
}

Dieser muss noch (zugegeben etwas umständlich) dem Context hinzugefügt werden. Dies kann z.B. mittels JaxRS Provider geschehen mittels HandlerInstantiator . Die Registrierung ist im GitHub hier: de/gedoplan/jackson/system/JacksonJPAResolverProvider.javazu finden

Das ist charmant! Rest-Clients können nun nicht nur die Entität selber (Talk) verändern, sondern in einem Abwasch auch die Referenz (>Speaker) auf Basis der ID manipulieren. Da der Resolver über das scope-Attribut mit der Referenz-Entity-Klasse versehen ist kann er in dieser Form generisch für alle Referenzen verwendet werden. Cool.

Im nächsten Artikel werfen wir einen dann einen Blick auf JSON-B, dem neuen Standard für JSON in Java EE 8

 

Live. In Farbe. Zum ausprobieren. Auf GitHub.