Identity and Equality of JPA Entities

JPA entities contain an id field (or property) annotaded with @Id. For this post I will assume a single field, but compound ids are possible as well.

Entity objects are business objects, so it is generally accepted that we will have to supply equals and hashCode for entity classes. Let me point out here, that it’s very unlikely for our business code to call any of these methods directly, because we don’t normally need explicit comparisons of entity objects in our business code, but we use collections which in turn may call equals and hashcode for looking up or adding entries.

We have several expectations regarding the semantics of equals and collection operations expecially when looking at entity objects:
We have several expectations regarding the semantics of equals and collection operations expecially when looking at entity objects:

  1. Objects based on the same database entry should be the same regarding equals and objects from different db rows should be different

a) including objects from different entity managers,
b) including detached objects,
c) including new (transient) objects,
d) even if some non-id attribute has been changed (they will end up in ‚their‘ db records!).

  1. Hashcode-based collections (e. g. HashSet) should behave friendly, i. e.

a) adding multiple objects should be possible – even for new (transient) objects,
b) the collection should remain intact even if contained objects get persisted into the db.

If the entity has a business id, i. e. the id attribute has some business meaning and is set by the constructor, the expectations can easily be met by using exactly the id attribute in equals and hashCode.

If you choose to compare all fields instead of just the id, your implementation breaks expectation 1.d). If you choose to not implement equals and hashCode at all and rather use the methods derived from Object, your implementation breaks expectations 1.a) and 1.b).

So, as an intermediate conclusion, implement equals and hashCode in your entity classes and base them on just the id attribute(s):


@Entity
public class Foo {

@Id
private String id;

...

public Foo(String id, ...) {
this.id = id;
...
}

public int hashCode() {
return (this.id == null) ? 0 : this.id.hashCode();
}

public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Foo other = (Foo) obj;
if (this.id == null) {
return other.id == null;
}
return this.id.equals(other.id);
}

Things get more complicated, if you want to use generated ids. JPA supports you in this with the annotation @GeneratedValue which has to be placed on the id attribute in addition to @Id. The generator works for integral number fields and it’s best to abstain from primitive types in order to have the additional value null for unset ids. So you would use Integer, Long or BigInteger depending on the amount of data entries expected:

@Entity
public class Foo {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

The problems with generated ids looking at identity and equality arise from the fact, that the entity manager will set the ids late – they may be populated not until the commit of the inserting transaction.

To begin with you will use the id field for comparison in equals and in hashCode as you did for business ids before. But if you do that in exactly the way shown above, you break expectation 2.a), because equals will evaluate all new (transient) objects as equal, as their ids are all null.

You can fix this by modifying equals such that it returns false if any of the compared ids are still unset:

public boolean equals(Object obj) {
...
if (this.id == null) {
return false;
}
return this.id.equals(other.id);
}

While this now meets expectation 2.a), it still breaks expectation 2.b): If you add some new entities to a HashSet and persist them afterwards, the collection will be corrupt due to the changed hash codes.

And bad news: There is no way to get around this, if using generated ids! In many application this still is no issue, because the program sequence „Create multiple objects“, „add them to a hash-based collection“, „persist the objects“, „use the collection“ is not very common and can be circumvented by persisting (and flushing) the objects before they get added to the collection.

Please take into account, that the usage of a hash-based collection may not be directly visible and instead be hidden as association collection in some other entity class.

What are your options, if you want to use generated ids and your business logic suffers from the hash-based collection problem discussed above? Well, the solution is to use an id populated early, i. e. in the constructor, which can be generated easily and – most advisable for performance – without hitting the database. java.util.UUID offeres the desired functionalty. It supplies 128 bit integers commonly expressed as 36 character string (32 hex digits and 4 dashes). java.util.UUID uses random numbers as base and offers a distribution which makes duplicates very unlikely. Other implementations exist, which use MAC addresses and random numbers.

@Entity
public class Foo {

@Id
private String id;

private String description;

public Foo(String description) {
this.id = UUID.randomUUID().toString();
...

By using uuids you cherry-pick the advantages from having early set ids while still having generated values.

The size of uuids may be painful. They are roughly four times the size of an integer, but storage requirements depend on your database product. Remember that they are ids, so every foreign key in the db has the same size. If that is a problem, you can resort to having an early set uuid for supporting equals and hashCode, which is not annotated with @Id. Instead you add another integer field as JPA generated id, i. a. annotated with @Id @GeneratedValue. Now all tables contain a number as primary key as well as a non-primary uuid column, but all foreign keys are just numbers.

So, the subject of JPA ids, which seems easy at first glance, is far from that in reality. You may use the following recipe when designing entity classes:

  • If your entity contains some identifying business attribute, take this as id. Let equals and hashCode use exactly this attribute. All expectations expressed above will be met
    (-> ChemicalElement in showcase).
  • If you don’t find a suitable business id, and …
    • if the hash-based collection problem discussed above is no problem for your application, use a @Id @GeneratedValue annotated integer id. Let equals and hashCode use exactly this attribute, but modify equals such that unset ids render a false return value. All expectations expressed above will be met with the exception of 2.b)
      (-> LabExperiment3NonNullIdEquality in showcase).
    • you want / have to use early set ids, use uuids.
      • If db size does not really matter or your model contains just a few associations, use the uuids directly as JPA ids. Let equals and hashCode use exactly this attribute. All expectations expressed above will be met
        (-> LabExperiment4UUIdEquality in showcase).
      • If you care about the storage consumption of foreign keys in your database, use the uuid just for equals and hashCode and add a separate @Id @GeneratedValue id to your class. Let equals and hashCode use exactly the uuid attribute. All expectations expressed above will be met
        (-> LabExperiment5AddIdEquality in showcase).

There is a showcase on https://github.com/GEDOPLAN/jpa-equals-demo demonstrating the various options. You can find the referenced classes there.

See you in our trainings at Berlin, Bielefeld, Cologne or your site!
http://gedoplan-it-training.de/


 

Advertisements

New and noteworthy in Java EE 8

Am 18.09.2017 wurde nach langer Zeit des Wartens und auch der Unsicherheit über die Zukunft der Plattform die Version 8 der Java Enterprise Edition veröffentlicht. Diese im JSR 366 beschriebene Umbrella Specification umfasst diverse Änderungen an den in ihr enthaltenen Einzelspezifikationen, von den im Folgenden einige – die subjektiv wichtigsten – beschrieben werden:

Java API for JSON Binding 1.0 (JSON-B, JSR 367)

Durch diese neue Spezifikation enthält die Plattform erstmals die Möglichkeit der Serialisierung und Deserialisierung von Java-Objekten in bzw. aus JSON analog zu JAXB. Im Sinne von Zero Configuration gibt es Defaults für die Konvertierung von Attributen, natürlich inklusive der mit Java 8 eingeführten Datums- und Zeit-Typen. Von den Defaults kann abgewichen werden, indem Klassen und Attribute mit Annotationen versehen werden (z. B. @JsonbTransient zum Ausschluss von Attributen) oder indem eine global angepasste Konfiguration genutzt wird.

Java Servlet 4.0 (JSR 369)

Der Fokus der neuen Servlet-Spezifikation liegt auf der Unterstützung von HTTP/2. Der Datenaustausch zwischen Client (Browser) und Server kann dann mittels Header Compression, Request Multiplexing und Server Push effizienter gestaltet werden. Die API-Änderungen sind nur marginal, so dass Anwendungen in den Genuß der Optimierung einfach dadurch kommen, dass sie diese Version der Spezifikation nutzen. Die überwiegende Anzahl der Browser sind bereits HTTP/2-fähig.

JavaServer Faces 2.3 (JSF, JCP 372)

Das Paket javax.faces.bean ist nun endlich deprecated. Statt der JSF-eigenen Manage Beans setzt man nun vollständig auf CDI, was sich auch in der Injizierbarkeit von CDI Beans in JSF-Artefakte wie bspw. Konverter ausdrückt. Weitere Änderungen sind die Unterstützung von Java 8 Date/Time, Websocket Integration und die Möglichkeit feldübergreifender Validierung.

Contexts and Dependency Injection for Java 2.0 (CDI, JSR 365)

Ein CDI Container lässt sich nun auch im SE-Kontext betreiben. Dies drückt sich einerseits dadurch aus, dass die Spezifikation nun getrennte Abschnitte für SE und EE vorsieht. Andererseits ist mit Hilfe der Klasse SeContainer ein implementierungsneutraler Start und Stopp eines Containers möglich. Änderungen gibt es weiterhin bei der Verarbeitung von Events. Observer können nun eine Verarbeitungsreihenfolge erhalten oder asynchron bedient werden.

Bean Validation 2.0 (JSR 380)

Die Validierugsmöglichkeiten für Java Beans wurden um diverse neue vordefinierte Contraints ergänzt (bspw. @Email oder @Positive) und können nun auch auf Elemente von Collections angewendet werden. Schließlich werden nun auch Java-8-Klassen wie LocalDate oder Optional unterstützt.

Java API for RESTful Web Services 2.1 (JAX-RS, JSR 375)

Asynchronität ist in REST-Services schon seit der Vorversion ein Thema, das nun u. a. durch die Nutzung von CompletionStage weiter ausgebaut und resilienzfähig wird. Neu sind zudem der Support für PATCH Requests sowie Server Side Events.

Neben dieser willkürlichen Auswahl haben auch weitere Spezifikationen Änderungen erfahren.

Zu den genannten Punkten werden wir in lockerer Folge weitere Posts in diesem Blog machen. In diesem Sinne: Stay tuned!

Apache CompareToBuilder

Gleichheit von Objekten wird bekanntlich mittels „equals“ Methode implementiert in der fachliche Informationen heran gezogen werden um zu prüfen ob es sich bei zwei Objektinstanzen um ein und dasselbe fachliche Objekt handelt. Es mag aber nun Situationen geben in denen uns das nicht reicht. Denken wir an einen JUnit-Test oder ein Migrationsprogramm wo möglicherweise weitreichendere Prüfungen auf Gleichheit durchgeführt werden sollen. Die Apache Commons Lang Bibliothek hat da was in petto : der CompareToBuilder

Ich bin du?

Die einfachste Variante den CompareBuilder zu verwenden ist es über die Fluent-API alle Vergleichswerte über die „append“ Methode ein zu binden und am Ende mittels „toComparison“ Methode das entsprechende Ergebnis an zu fordern. Der CompareToBuilder führt dann null-Prüfungen durch und ruft für jedes übergeben Objekt die „equals“ Methode auf und liefert am Ende einen int-Wert zurück der die Gleichheit der beiden Objekte ausweist.

int cResultFirma = new CompareToBuilder()
.append(firma1.getFax(), firma2.getFax())
.append(firma1.getInternet(), firma2.getInternet())
.append(firma1.getMail(), firma2.getMail())
.append(firma1.getTelefon(), firma2.getTelefon())
.toComparison();

Weniger schreiben, mehr Reflection

Das gezeigte Beispiel ist für komplexe Objekte natürlich ein aufwendiges Unterfangen. Der CompareToBuilder bietet darüber hinaus aber noch eine statische Methode die es erlaubt solche Prüfungen per Reflection durchführen zu lassen. Dabei bietet die Methode auch die Möglichkeit bestimmte Felder nicht aus zu werten, transiente Felder zu ignorieren oder die Prüfung nicht auf alle Felder der Superklassen an zu wenden.


int cResultFirma = CompareToBuilder.reflectionCompare(
    firma1, firma2,   // zu vergleichende Objekte
    "id", "createTimestamp" // Felder die nicht geprüft werden sollen
);

Was auf den ersten Blick sehr einfach in der Verwendung aussieht ist allerdings leider auch in seiner Funktionalität sehr eingeschränkt. So stößt dieser Mechanismus schon auf Probleme wenn es sich bei den zu prüfenden Feldern um eine Collection von Objekten handelt. Auch ist es leider nicht möglich auf bestimmte Attribute mit einer separaten Prüfungen zu reagieren. So wäre es z.B. wünschenswert beim Vergleich unserer Firma mittels Reflection eine referenzierte Instanz „Adresse“ nicht mittels „equals“ prüfen zu lassen sondern auch hier per Reflection zu prüfen oder individuell zu prüfen. Das lässt sich somit leider nur über den Umweg realisieren solche Referenzen beim „reflectionCompare“ in die Ignore-Liste mit auf zu nehmen und diese separat zu prüfen.

Der CompareToBuilder aus der Bibliothek Apache Commons Lang ist ein sehr rudimentäres Hilfsmittel das uns hilft Vergleiche von Objekten an zu stellen. Wenn diese allerdings sehr komplexe Strukturen aufweisen, die vollständig geprüft werden sollen, stößt der CompareToBuilder sehr schnell an seine Grenzen

 

Oracle schlägt Verlagerung von Java EE in die Eclipse Foundation vor

Seit nunmehr fast 20 Jahren ist die Java EE eine verlässliche Bank für den Aufbau und Betrieb von Enterprise-Anwendungen. Oracle – zuvor Sun – hat am Erfolg der Plattform zweifellos einen wesentlichen Anteil, zusammen mit den vielen Firmen, Einzelpersonen und der Community, die sich für Standards und deren Weiterentwicklung engagiert haben.

Nun hat sich die Welt aber weiter gedreht – manchmal scheint es, mit stetig wachsender Geschwindigkeit – und neue Anforderungen (Cloud, Microservices, … – you name it) verlangen nach einer Anpassung und Weiterentwicklung der Plattform in einer Taktung, die nicht mehr so recht zu einem in gewisser Weise zentralistischen Modell passen will.

Vor diesem Hintergrund erscheint die Ankündigung von Oracle, Java EE in die Eclipse Foundation zu verschieben, sehr sinnvoll. Sie ist vielleicht sogar überfällig, wenn man die Unsicherheit und Querelen der vergangenen Monate und Jahre betrachet.

Ich bin sehr zuversichtlich, dass dieser Schritt der richtige ist, um Java EE weiter verlässlich, stabil und zukunftsorientiert auszurichten.

Spring Boot + DATA JPA + HATEOAS

Spring Boot bietet die Möglichkeit auf sehr einfachem Wege standalone Anwendungen zu schreiben, die keinerlei Applicationserver als Laufzeitumgebung benötigen. Als Erweiterungen existieren eine ganze Reihe von Möglichkeiten, darunter die Einbindung von JPA und Rest (unter Anwendung von HATEOAS). Dieser Beitrag fast das Projekt-Setup kurz zusammen und stellt die technischen Möglichkeiten vor Rest-Schnittstellen mittels Spring Data auf einfachem Wege bereitzustellen.

Aller Anfang ist leicht. Die Entwickler von Spring unterstützen neue Projekte in der Initialisierung neuer Projekte über ein Webangebot über das Basis-Projekte mit den gewünschten Features generiert werden können: https://start.spring.io/ .

Das pom.xml eines so erstellten Projektes könnte wie folgt aussehen:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M3</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!--JPA Support-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--Support für die Veröffentlichung unserer Repositories als REST-Schnittstelle-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <!--Laufzeitumgebung-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>
        <!--Testing-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--Basis-Support für Webanwendungen-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--integrierte Datenbank-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>@RepositoryRestResource
            <scope>runtime</scope>
        </dependency>
    </dependencies>

Gestartet wird das Projekt über eine klassische Main-Methode und beinhaltet neben unserer Anwendung auch einen entsprechenden WebServer und Datenbank. Die Konfiguration für diese Komponenten erfolgt in einer einzelnen Properties Datei (src/main/resources/application.properties) in der wir zum Beispiel wie im folgenden zu sehen die (standardmäßige) In-Memory H2 Datenbank auf eine File-basierte umstellen und JPA anweisen die benötigten Tabellen zu generieren.

application.properties

spring.jpa.hibernate.ddl-auto = create
spring.jpa.generate-ddl = true
spring.datasource.url=jdbc:h2:file:~/spring;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driverClassName=org.h2.Driver

Start, per main(…)

Der Start übernimmt wie angesprochen eine simple main-Methode, die lediglich Spring dazu veranlasst die benötigten Container zu initialisieren. Darüber hinaus scannt dieser Prozess alle unterliegenden Packages nach Spring Komponenten und registriert z.B. unsere Rest-Resources und aktualisiert auf Basis unserer JPA-Entitäten die Datenbank.

SpringDemoApplication.java

@SpringBootApplication
public class SpringbootDemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootDemoApplication.class, args);
  }
}

Rest, ganz einfach

Spring bietet uns die Möglichkeit in vielen Situationen auf das implementieren einer separaten Rest-Resource-Klasse zu verzichten. Dank der Annotation „@RepositoryRestResource“ ist Spring in der Lage auf Basis seines CRUD-Basis Repositories alle Operationen direkt als Rest-Resource zur Verfügung zu stellen. Um dies zu realisieren reicht es ein entsprechendes Interface bereit zu stellen und das generische CrudRepository zu erweitern:

MessageRepository

@RepositoryRestResource(path = "msg", collectionResourceRel = "msg")
public interface MessageRepository extends CrudRepository<Message, Integer> {

}

Request: http://localhost:8080/msg

{
    "_embedded": {
        "msg": [
            {
                "date": "1970-01-01T00:00:00.000+0000",
                "message": "Hello World",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/msg/1"
                    },
                    "message": {
                        "href": "http://localhost:8080/msg/1"
                    },
                    "user": {
                        "href": "http://localhost:8080/msg/1/user"
                    }
                }
            }]
    }
}

Dank unseres Projekt-Setups verwendet Spring per default ein HAL-Format um unsere Entität aus zu liefern (s. Blog Beitrag HATEOAS + HAL). Damit entfällt das lästige manuelle Handling Umwandeln von Referenzen auf andere Entitäten. Stattdessen fügt Spring entsprechende Links in die Response eins, über dessen URL der Client die referenzierten Daten bei Bedarf abholen kann.

Vom Suchen und Finden

Neben den einfachen Get-Anfragen die automatisch für uns generriert werden bietet Spring auch eine halbautomische Erzeugung von Query-Methoden an. So erübrigt sich das manuelle Schreiben von Methoden die lediglich simple Queries an die Datenbank übermitteln wenn wir uns auf die reine Definition solcher zusätzlichen Methoden im Resource-Interface stützen. Dies geschieht wahlweise durch eine Namens-Konvention oder durch die explizite Angabe einer JPQL-Query über Annotationen

  @RestResource(path = "searchMessage", rel = "searchMessage")
  List<Message> findByMessageIgnoreCaseContaining(@Param("msg") String message);
}

ermöglicht GET-Anfragen ala:

http://localhost:8080/msg/search/searchMessage?msg=Blank

Post it

Die automatische Erzeugung von Rest-Schnittstellen beschränkt sich dabei aber nicht nur auf GET-Anfragen. Auch das Speicher und Löschen von Objekten wird unterstützt. Sehr charmant dabei ist die erneute Verwendung von HAL um Referenzen zwischen unseren Entitäten vom Client aus fest zu legen.

Post Request

{
    "user": "http://localhost:8080/usr/2",
    "message": "Hello Blank",
    "date": 0
}

Dies führt nicht nur zu einer simplen Speicherung unseres Objektes sondern Spring fügt ganz automatisch, auf Basis der User-URL, die benötigte JPA-Referenz hinzu.

JAX-RS bietet uns eine sehr einfache, standatisierte Möglichkeit Rest-Schnittstellen bereit zu stellen. Dabei existieren jedoch sehr viel sich wiederholende Implemetierungen, welche die Entwicklungszeit erhöhen. Hier liefert Spring ( /Spring Data) eine extrem mächtige Möglichkeit unnötigen Ballast bei CRUD-Operationen aus der Anwendung fern zu halten.

Live? In Farbe? Auf Github !

Rest – HATEOAS + HAL

REST-Webservices sind wohl die am weitesten verbreitete Schnittstellentechnologie die im WEB zu finden ist. Dank JSON als Datenformat bietet REST eine zustandslose und leichtgewichtige Möglichkeit Daten zwischen Anwendungen aus zu tauschen. HATEOAS (Hypermedia as the Engine of Application State) ist nun im Grunde keine weitere Technologie, sondern ein Konzept wie eine REST-Schnittstelle aufgebaut werden sollte.

Der Grundgedanke hinter HATEOAS ist das Clients in der Lage sind die Funktionen der Anwendungs-API zu verwenden ohne das diese fest verdrahtet im Client-Code sein müssen. Vergleichbar ist dies mit der menschlichen Nutzung einer Webseite. Wir besuchen einen Internetauftritt, erhalten Informationen, aber auch weiterführende Links über die wir weitere Funktionen/Seiten abrufen können. Das Prinzip lässt sich nahezu eins zu eins auf HATEOAS anwenden. Anstatt lediglich die angeforderten Informationen in der Datenstruktur von JSON zu erhalten informiert uns die Anwendung ebenfalls über weiterführende Aktionen und Links.

Ein einfaches Beispiel ist z.B. die Abbildung von JPA Entitäten im JSON Format. Nehmen wir ein sehr einfaches Datenmodell aus zwei Entitäten: User und Message. Die Entitäten besitzen eine Bi-Direktionale Verbindung:

@Entity
public class Message {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private Date date;

  private String message;

  @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
  private User user;

------------------------------------------------------------------------

@Entity
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private String username;

  @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
  private List<Message> messages;

Bereits hier müssen wir uns Gedanken über die Umwandlung in ein JSON Format machen: ohne zusätzlichen Eingriff von unserer Seite könnten diese Entitäten bereits nicht in ein JSON Format umgewandelt werden, da die Bi-Direktionale Relation zu einer Endlosschleife bei der Umwandlung führen würde. Neben die Bildung von DTOs richten sich die Möglichkeiten um dieses Problem zu beheben nach dem verwendeten JSON Parser (Jackson bietet z.B. Annotationen an um Felder zu ignorieren, Sub-Views aus zu liefern,  solche Relationen zu kennzeichnen oder diese lediglich mit der entsprechenden ID aus zu liefern). Eine klassischer JSON Output für unser „Message“-Objekt könnte nach diesem Prinzip so aussehen:

{
     "date": "1970-01-01T00:00:00.000+0000",
     "message": "Hello World",
     "user": 2
}

Anstatt das Userobjekt mit aus zu liefern bietet das JSON hier nur die entsprechende ID an. Wenn der Client auch die Benutzer-Information benötigt muss dieser wissen das es sich bei dieser ID um eine User-ID handelt, zudem muss die entsprechende URL zum Abruf der User-Daten (z.B. “ http://…/user/2 „) im Client-Code festgelegt sein.

HAL

Zur Unterscheidung: HATEOAS ist der Architektur-Ansatz, HAL ist eine Spezifikation zur JSON-Syntax der Link-Relationen. Neben HAL gibt es noch einige Alternativen (Collection+JSON, Hydra…). HAL findet z.B. Verwendung in „Spring HATEOAS“. Mittels „HAL“ können die Informationen nun verlinkt werden und der Client damit in die Lage versetzt werden die benötigen zusätzlichen Informationen ab zu rufen, ohne die konkreten URLS selber zusammen zu setzen.

Unser Message-Objekt könnte mittels „HAL“ dann wie folgt aussehen:

{
    "date": "1970-01-01T00:00:00.000+0000",
    "message": "Hello World",
    "_links": {
        "self": {
            "href": "http://localhost:8080/msg/2"
        },
        "user": {
            "href": "http://localhost:8080/msg/2/user"
        }
    }
}

Header Links

Eine andere Möglichkeit besteht darin Links im HTTP Header zu verankert. Dies lässt sich auch mit JAX-RS direkt erledigen. Ein einfaches Beispiel um innerhalb der Rest-Methode einen Link der Response hinzuzufügen könnte so aussehen:

  @Context
  UriInfo uriInfo;

  @GET
  @Path("hal")
  public Response getHelloHAL() {
    DemoEntity demo = new DemoEntity(1, "Hello World");
    return Response.ok(demo).link(uriInfo.getBaseUri().resolve("login"), "login").build();
  }

Der Header wird dann um einen entsprechenden Eintrag erweitert und enthält den angegebenen Link:

...
link <http://localhost:8080/angular-jwt-1.0-SNAPSHOT/rest/login>; rel="login"
...

Im nächsten Artikel werfen wir einen Blick auf Spring-Boot und dessen Möglichkeiten HATEOAS und HAL mit sehr wenig Aufwand in der eigenen Anwendung zu verwenden.

JSON Web Tokens – JWT – Angular

Im ersten Teil haben wir einen Blick auf JWT im Zusammenspiel mit Java EE geworfen. Heute soll es um die Client-Seite gehen und die Verwendung von JWT im Kontext von Angular (v. 4)

app.png

Für Angular gibt es inzwischen einige Zusatzbibliotheken die den Umgang mit JWT wesentlich vereinfachen. Eine davon werden wir unserem Projekt wie gewohnt über NPM hinzufügen:

npm install @auth0/angular-jwt

Diese neue Version ist ab Angular 4 verfügbar und verwendet zur Implementierung die eingeführten HTTP-Interceptoren. Die anschließende Verwendung ist denkbar einfach. Wir konfigurieren JWT über unser app.module und definieren eine Methode welche den JWT Token zur Verfügung stellt. In diesem Beispiel legen wir diesen nach einem Login in den LocalStorage ab und rufen diesen bei der Initialisierung der Anwendung ab:

 

import { JwtModule } from '@auth0/angular-jwt';

export const getToken = function() {
  return localStorage.getItem('JWT-TOKEN');
}

@NgModule({
    ...
    JwtModule.forRoot({
      config: {
        tokenGetter: getToken,
        whitelistedDomains: ['localhost:8080']
      }
    })
  ]
export class AppModule { }

Hier zu sehen ist auch die benötigte Deklaration der Domains an die unser Token geschickt werden soll (da viele Anwendungen mit mehreren Servern kommunizieren und nicht alle Requests mit den zusätzlichen Headern versehen werden sollen).

In Folge dieser Deklaration erhalten alle unsere Requests an die definierten Hosts einen entsprechenden Authentifikation-Header, ganz automatisch:

request_header_jwt

Token? Her damit !

Natürlich muss dieser Token erst mal den Weg in unsere Anwendung finden. Der Token wird vom Server bereitgestellt und lässt sich mit einer sehr einfachen Login Methode implementieren:

  login(username: string, password: string): Promise<boolean> {
    return new Promise((resolve, reject) => {
      this.http
        .post('http://localhost:8080/angular-jwt-1.0-SNAPSHOT/rest/login'
           , { username, password }, { responseType: 'text' })
        .subscribe((resp: string) => {
          localStorage.setItem('JWT-TOKEN', resp);
          resolve(true);
        }, error => reject);
    });
  }

Du? Nicht. Guards

Mit dem Vorhandensein dieses Tokens lässt sich nun auch sehr leicht der Zugriff auf bestimmte Teile der Seite verhindern. Dazu verwenden wir so genannte Guards, die beim Routing greifen und prüfen ob eine bestimmte Route überhaupt verwendet werden darf:

Guard

export const APP_ROUTES: Routes = [
  ...
  { path: 'protected', component: ProtectedComponent, canActivate: [AuthGuardService] },
  { path: 'notallowed', component: NotAllowedComponent }
]

Route-Definition

@Injectable()
export class AuthGuardService implements CanActivate {

  constructor(private router: Router) { }

  canActivate(): boolean {
    const allowed = localStorage.getItem('JWT-TOKEN') !== null;

    if (!allowed) {
      this.router.navigateByUrl('notallowed');
    }
    return allowed;
  }
}

JWT ist einer der weit verbreiteten Techniken für die Zustandslose Authentifizierung und den Austausch verschlüsselter Daten. Angular bietet zusammen mit der hier gezeigten Bibliothek eine sehr einfache Möglichkeit diese Technik zu nutzen.

Live? Farbe? github/GEDOPLAN