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

 

Advertisements

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

JSON Web Tokens – JWT – Java

JWT (Json Web Token) ist ein Technik zum Austausch von gesicherten Daten die Zustandslos über HTTP übertragen werden können. In dieser zweiteiligen Serie werfen wir einen kurzen Blick auf die Verwendung zur Authentifizierung zwischen einer Java EE und Angular Anwendung.

Um JWT in unserer Anwendung einzusetzen verwenden wir eine zusätzliche Bibliothek die wir mittels Maven wie gewohnt einbinden können:

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

Mit Hilfe dieser Bibliothek sind wir nun in der Lage JWT Tokens zu generieren. Hier ein Beispiel zur Erzeugung dieser Tokens und die Verwendung der API:

  public String generateJWTToken(String user) {
    String token = Jwts.builder()
        .setSubject(user)
        .claim("groups", new String[] { "admin", "customer" })
        .claim("mail", "dominik.mathmann@gedoplan.de")
        .signWith(SignatureAlgorithm.HS512, System.getProperty("JWT-KEY"))
        .compact();
    return token;
  }

Der Payload des JWT-Tokens enthält so genannte „claims“, Gruppen von Informationen die wir beliebig setzen können. Es existieren einige reservierte Claims die spezifiziert sind. Einen davon sehen wir im obigen Beispiel, so ist das „Subject“ ein solcher Claim der den Benutzer identifiziert. Es folgen dann einige eigens definierte Claims: die Gruppen des Benutzers (die natürlich normalerweise z.B. aus einer Datenbank kommen) und eine Mail Adresse. Anschließen „verpacken“ wir diese Informationen in einen HS512 Codierten Token der mit einem Schlüssel signiert wird. Es existieren noch einige weitere Möglichkeiten, so lässt sich zum Beispiel eine Gültigkeit festlegen, nach der dieser Token seine Gültigkeit verliert.

Das Ergebnis sieht für das Beispiel dann wie folgt aus:

eyJhbGciOiJIUzUxMiJ9.
eyJzdWIiOiJkZW1vIiwiZ3JvdXBzIjpbImF
kbWluIiwiY3VzdG9tZXIiXSwibWFpbCI6ImRvbWluaWsubWF0aG1hbm5AZ2Vkb3BsYW4uZGUif.
70b3xatjS8za28ekb1eQRo-wgB2Y7mKSqXSc6_IcIOmDsmR5nJbKZXqKJeegtwzk7i0rnpvgK50dgqdWrN9H6g

Durch den „.“ getrennt lässt sich hier auch der allgemeine Aufbau eines JWT-Tokens erkenne, der aus 3 Teilen besteht:

  • Header, enthält den Typ des Tokens und die Verschlüsselungs-Variante
  • Payload, enthält unsere Claims
  • Signature, zur Validierung des Tokens

Dieser Token kann nun zum Client übertragen werden und sollte bei jedem Request an den Server zurück übermittelt werden um zu prüfen ob dieser noch gültig ist und ob der identifizierte Benutzer berechtigt ist die angeforderte Resource zu erhalten. Diese Übertragung erfolgt in aller Regel im HTTP Header „Authorization“ im Format:

„Bearer [Token]“

JAX-RS mit JWT

Um einen solchen JWT Token nun zur Identifizierung und Absicherung unserer Rest-Schnittstellen zu verwenden bietet es sich an einen JAX-RS Filter zu verwenden (ähnlich den Inteceptoren von CDI). Dazu implementieren wir zuerst eine Annotation die dann mit einer entsprechenden Filter-Implementierung versehen wird:

Annotation

@javax.ws.rs.NameBinding
@Retention(RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface JWTAuthed {
}

Filter-Implementierung

@Provider
@JWTAuthed
@Priority(Priorities.AUTHENTICATION)
public class JWTAuthedFilter implements ContainerRequestFilter {

  @Inject
  private JWTService jwtService;

  @Override
  public void filter(ContainerRequestContext requestContext) throws IOException {
    String token = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

    try {
      jwtService.valid(token.split(" ")[1]);
    } catch (Exception e) {
      requestContext
      .abortWith(Response.status(Response.Status.UNAUTHORIZED)
      .build());
    }
  }

  public void valid(String token) {
    JwtParser signed = Jwts.parser().setSigningKey(System.getProperty("JWT-KEY"));

    String username = signed.parseClaimsJws(token).getBody().getSubject();
    System.out.println("Request is JWT-signed with user: " + username);
  }
}

Nun reicht es unsere Annotation einfach an die zu sichernden Methoden zu setzen und der Container führt automatisch eine entsprechende Validierung und Identifizierung unseres Tokens durch und würde in einem Fehlerfall einen entsprechenden Fehler auswerfen.

  @GET
  @JWTAuthed
  public DemoEntity getHello() {
    ...
  }

Ich will nicht mehr, Logout

Rein technisch bleiben diese Token unendlich gültig so lange der Signing-Key nicht verändert wird ( und falls kein Zeitstempel definiert wurde ab wann der Token ungültig wird ) Ein klassischer Logout ist somit erst mal nicht möglich. Hierzu gibt es diverse Möglichkeiten dies dennoch zu implementieren. Zum Beispiel  könnte der Login/Logout Status in der Datenbank festgehalten werden und zusätzlich geprüft werden, was jedoch bei jedem Request zu einer Datenbankabfrage führen würde. Ein sehr einfacher Weg, der die Zustandslosigkeit ein wenig zunichte macht wäre das Vorhalten von gültigen Token in einer Application-Scoped Bean. Sollte die Anwendung in einer geclusterten Umgebung ausgeführt werden müsste diese Information allerdings repliziert werden, außerdem führt ein Neustart des Anwendungsservers dazu das alle Benutzer sich erneut einloggen müssen

private List<String> validJWTTokens = new ArrayList();

  public String generateJWTToken(String user) {
    String token = ...

    this.validJWTTokens.add(token);
    return token;
  }

  public void valid(String token) {
    if (!this.validJWTTokens.contains(token)) {
      throw new RuntimeException("Token is not valid anymore");
    }
     ...
  }

JWT in Java ist dank JJWT kein Hexenwerk und lässt sich relativ einfach in die eigenen Anwendung einbringen um Authentifizierung durchzuführen oder verschlüsselte Informationen aus zu tauschen.

Alles Live in Farbe auf github.com/GEDOPLAN

Angular, testen mit Karma/Jasmine

Anwendungen zu testen ist ein leidiges Thema und wird gerne aus Zeitgründen vernachlässigt. Jeder der schon mal ein größeres Refactoring durchgeführt hat wird aber eine gute Testabdeckung zu schätzen wissen. Angular macht es uns einfach dank AngularCLI zu einer getesteten Anwendung zu kommen.

app.png

Projekte die mit AngularCLI erstellt und entwickelt werden liefern schon sehr viel was nötig ist um seine Anwendung zu testen. Neben einer einheitlichen Anwendungsstruktur generiert uns das Werkzeug auch Test-Hüllen die wir lediglich mit Leben füllen müssen. So kann eine solche Anwendung direkt per Befehl: ng test einem Testlauf unterzogen werden. Da wir bis hierher noch kaum einen Handschlag getan haben sind diese Tests natürlich rein technischer Natur, so wird zumindest geprüft ob die Templates der Komponenten geparst werden können.  Die Test-Dateien die während des Testings herangezogen werden erhalten bei der Generierung den Postfix „.spec.ts“. Hier ein einfaches Beispiel was, ganz ohne eigene Arbeit, von Angular generiert wird:

describe('HelloWorldComponent', () => {
  let component: HelloWorldComponent;
  let fixture: ComponentFixture<HelloWorldComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ HelloWorldComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HelloWorldComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Was wir hier sehen ist ein Jasmine-Testfile in dem Gruppen von Testfällen erstellt werden (describe) in denen dann einzelne Tests (it) definiert werden. AngularCLI fügt bereits die benötigten Initialisierung ein in der die Komponente, die es zu testen gilt, erstellt wird und auch einen entsprechenden Testfall um die erfolgreich Erstellung zu prüfen.

Komponente zum Test

Schauen wir uns also den Test einer Komponente an. Die Basis bildet eine sehr einfache Komponente die eine Liste von Benutzern entgegen nimmt und lediglich einige der Informationen darstellen soll ( aus Platzgründen sparen wir uns hier die Implementierung dieser Komponente, zu finden ist sie aber im Github )

  it('should show unser infos', () => {
    component.users = [
      {username: 'mocked', mail: 'mocked@mock.de'}
    ];

    fixture.detectChanges();
    const compiled: Element = fixture.debugElement.nativeElement;

    const firstUserName = compiled.querySelectorAll('h2').item(0).innerText;
    expect(firstUserName).toBe('mocked');
  });

Dank der Initialisierung der Testdatei haben wird die Komponente bereits für uns erzeugt. Diese könne wir nun mit Daten versorgen (Zeile: 2) um das Verhalten der Komponente zu testen. Die User-Daten werden in der „echten“ Anwendung von der übergeordneten Komponente übergeben und werden vermutlich über einen Rest-Service gelesen. An dieser Stelle ist die Datenherkunft aber irrelevant, wir konzentrieren uns beim testen der Komponente lediglich auf das Verhalten der Komponente selbst, sodass wir in aller Regel darauf verzichten werden hier irgendwelche Services auf zu rufen um Daten zu bekommen.

Nach dem setzen der benötigen Daten rufen wir fixture.detectChanges();  damit Angular seinen Change Detecetion durchläuft. Anschließend können wir auf das native Element zugreifen  (Zeile: 7 ) und mittels Queries die Ausgabe im Template auf ihre Richtigkeit prüfen.

Formular zum Test

Mit diesem Ansatz lasse sich jetzt auch Formulare testen. In unserem Fall haben wir uns für das Template-Driven Design entschieden. Um dies zu testen benötigen wir Zugriff auf das Formular, das wir in unserer Komponente per

  @ViewChild(NgForm)
  form: NgForm;

injizieren lassen können. Somit erhalten wir Zugriff auf das Fomular und können dieses über die entsprechende API mit Daten versorgen. Unser Test z.B. prüft ob die Benutzer Liste korrekt gefiltert wird wenn das entsprechende Feld mit der maximalen Anzahl der an zu zeigenden Benutzer gefüllt wird:

  it('should load limit users', async(() => {
    const maxUser = component.form.form.get('maxUser');
    const btn = fixture.nativeElement.querySelector('button[name="loadBtn"]');

    expect(maxUser).toBeTruthy();
    expect(btn).toBeTruthy();

    maxUser.setValue(1);
    btn.click();
    fixture.detectChanges();
    let elecount = fixture.nativeElement.querySelectorAll('.portfolio-item').length;
    expect(elecount).toBe(1);
  }));

Wir erhalten Zugriff auf das „maxUser“ Feld über das Formular. Den Button ermitteln wir über die bereits gesehene „querySelector“ Methode. Nun können wir das Feld mit Daten versorgen und einen Button-Klick emulieren um dann zu prüfen ob die erwartete Anzahl an Benutzer auf der Oberfläche dargestellt wird.

Komponente ohne Service

In aller Regel verwenden unserer Komponente Services um Daten z.B. von einer Rest-Schnittstelle zu beziehen. Diese Services können entweder im NgModule als Provider registriert werden oder wie hier zu sehen auf der Ebene der Komponente:

 @Component({
  ...
  providers: [UserService]
})
export class AppComponent {...}

Der Test unserer AppComponent wäre nun diekt von der korrekten Funktion des UserServices abhängig. Wollen wir unsere Komponente aber autark testen kann dieser Service durch eine Mock Implementierung ausgetauscht werden. Ein Mock zum UserService implementiert dieselbe Schnittstelle wie der „echte“ Service, führt aber zum Beispiel keine HTTP-Requests durch, sondern liefert definierte Rückgaben:

 @Injectable()
export class UserServiceMock {

  constructor(private http: Http) { }

  getUsers(): Observable<any[]> {
    return Observable.of(USER_DATA);
  }
}

export const USER_DATA = [...]

Nun muss lediglich sicher gestellt werden, dass unser Mock-Service herrangezogen wird wenn unser Test die Komponente instanziiert. Sollten wir den Service über das NgModule registriert haben reicht in unserem Test die Angabe eines eigenen Provider-Arrays (s. „Service zum Test“, Einbindung von HttpModule). In unserem Fall deklariert die Komponente über ein eigenes Provider-Array seine Abhängigkeiten die beim testen nun „überschrieben“ werden müssen. Dazu bietet uns „TestBed“ eine entsprechende Methode.

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpModule, FormsModule],
      declarations: [
        AppComponent, UserListComponent
      ]
    }).overrideComponent(AppComponent, {
      set: {
        providers: [
          { provide: UserService, useClass: UserServiceMock }
        ]
      }
    }).compileComponents();
  });

Service zum Test

Als Basis dient folgender Service der User-Daten abruft:

@Injectable()
export class UserService {

  constructor(private http: Http) { }

  getUsers(): Observable<any[]> {
    return this.http.get('http://jsonplaceholder.typicode.com/users').map(r => r.json());
  }
}

Der entsprechende Test ist ebenfalls sehr überschaubar:

  it('should load a least some users', async(inject([UserService], (service: UserService) => {
    expect(service).toBeTruthy();
    service.getUsers().subscribe(r => {
      expect(r.length).toBeGreaterThan(5);
    });
  })));

Zwei Dinge kommen hier hinzu die für den korrekte Ablauf wichtig sind. Zum einen wrappen wir unsere Test-Funktion in einen „async“-Funktion. Damit weisen wir unseren Testlauf an auf die Abarbeitung von asynchronen Methoden zu warten. Lasse wir dies weg würde unser Test immer erfolgreich durchlaufen, da unsere Prüfung asynchron über die „subsribe“ Methode implementiert ist und der Test bereits als erfolgreich deklariert worden wäre ohne auf die Antwort zu warten.

Würden wir den Test nun mit der Standard Initialisierung von Angular laufen lassen, würde wir einen Fehler erhalten: Error: No provider for Http! Um den Fehler richtig zu interpretieren ist es wichtig zu verstehen das im Falle eines Testes Angular ein eigenes Modul aufbaut und nicht das „normale“ NgModule verwendet welches wir ja für die Anwendung definieren. Damit wird klar: das HTTPModule ist war im Modul unserer Anwendung deklariert nicht aber in unserem speziellen Test-Modul. Solche Abhänigkeiten müssen hier speziell für den Test definiert werden. Dazu erweitern wir die inital von AngularCLI generierte Definition unserer beforeEach-Methode:

describe('UserService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpModule],
      providers: [UserService]
    });
  });
...

und importieren das HttpModule auch für unserern Testfall.

Streng genommen verletzten wir mit diesem Test allerdings den Gedanken eines Unit-Testes. Wir rufen einen externen Service auf der autark gepflegt wird und damit ist unserer Testfall nicht nur von der Logik unserer eigenen Komponente abhänig sondern auch von der adressierten Rest-Schnittstelle. Eine Änderung der gelieferten Daten über diese Schnittstelle würde bei uns nun zu einem Fehler führen obwoh unser Programm immer nocht korrekt arbeitet. Das sollte man bedenken wenn solche Tests imlementiert werden.

Services zum Test, aber ohne externe Aufrufe bitte!

Um generell Aufrufe über HTTP zu unterbinden und stattdessen eine definierte Antwort bereit zu stellen bietet Angular die Klasse „MockBackend“ welche das normale XHRBackend mit einer Implementierung ersetzt.

import { TestBed, inject, async } from '@angular/core/testing';
import { XHRBackend, ResponseOptions, Response } from '@angular/http';
import { MockBackend } from '@angular/http/testing';

import { UserService } from './user.service';
import { HttpModule } from '@angular/http';;

describe('UserService (with mocked Backend)', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpModule],
      providers: [
        { provide: XHRBackend, useClass: MockBackend },
        UserService
      ]
    });
  });

  it('should load a least some users', async(inject([UserService, XHRBackend], (service: UserService, mockBackend: MockBackend) => {
    mockBackend.connections.subscribe((connection) => {
      connection.mockRespond(new Response(new ResponseOptions({
        body: JSON.stringify([{"username": "mock"}])
      })));
    });

    expect(service).toBeTruthy();
    service.getUsers().subscribe(r => {
      console.log(r);
      expect(r.length).toBeGreaterThan(0);
    });
  })));
});

Wir greifen mit den „Providers“ in die Depedency Injection von Angular ein und bringen so das angesprochene MockBackend ins Spiel. Nun können wir in unseren Testfällen die Instanz des MockBackend injizieren lassen und auf eingehende Verbindungen reagieren. Hier haben wir nun die Möglichkeit definierte HTTP-Response zu schicken, ohne das eine echte Verbindung nach außen aufgebaut wird

Spies

Ein weiteres nettes Feature von Jasmine ist es so genannte „Spies“ auf bestimmte Methoden zu legen. Mit dessen Hilfe können wir Methoden-Aufrufe aufzeichnen und so prüfen ob Interaktionen mit der Komponente auch korrekt z.B. zu einem Service durchgreicht werden:

   it('should call load method on button click', async(() => {
    const userService = (<any>component).userService;
    const btn = fixture.nativeElement.querySelector('button[name="loadBtn"]');

    const spy = spyOn(userService, 'getUsers');
    btn.click();
    btn.click();
    btn.click();

    expect(spy.calls.count()).toBe(3);
  }));

Dieser Test funktioniert wie erwartet. Wichtig zu beachten ist aber folgendes: ein solcher Spy führt dazu das die Service-Methode „getUsers“ gar nicht aufgerufen wird. Wir simulieren hier also den Methodenaufruf nur. Falls wir in diesem Testfall auch Prüfungen implementieren wollen die das Ergebniss dieser Methoden-Aufrufe berücksichtigt muss dies mit folgendem Methoden-Aufruf festgelegt werden:

spy.and.callThrough();

Test-Code zu schreiben und vor allem aktuell zu halten stellt immer ein Overhead da der im Projekt berücksichtigt werden muss. Für komplexe Anwendungen ist das Testen jedoch ein wichtiger Baustein für eine stabile Software. Angular tut sein Bestes die Arbeit mit Tests so einfach zu machen wie möglich, nur das Schreiben der Testfälle ist und bleibt eine Aufgabe des Entwicklers.

Wie immer. Alles. Live. In Farbe. Bei Github