6. Contexts and Dependency Injection • klassisch Dependency Injection • was kennen wir bereits • CDI im Detail – Aktivierung – Qualifier – Event Handling – Objekte injizieren – Produzenten – Alternativen – Interception • Fazit Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 569 Einschub: aktueller Stand • Sprinter ist eine klassisch aufgebaute JEE-Applikation • wir arbeiten mit Klassen und Objekten davon • kritisch betrachtet sind wesentliche Teile der Software nicht objektorientiert – oftmals gibt es (pro Nutzer) genau ein Objekt von Steuerungsklassen (alles Singleton, Ausnahme Datenschicht) – Teilprogramme rufen Methoden (?? Prozeduren) in anderen Teilprogrammen auf • (Oracle Application Express (APEX) nutzt PL/SQL zur Erstellung von Web-Applikationen) • generell nichts Schlechtes daran! • kritisch, Objekte müssen sich genau kennen Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 570 Ausblick auf weitere Themen Browser 3 JSF Scope Bean Validation 2 RESTful WebService Web Sockets 1 CDI EJB JPA Datenbank Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 571 Dependency Injection klassisch (1/2) woher kommen Objekte für Exemplarvariablen? • Variante 1: Werte werden als Parameter übergeben, aus denen Objekte gebaut werden (Objekt baut sich benötigte Objekte selber) • Variante 2: Objekte werden als Referenzen übergeben – Optimierung: Typen der Objektvariablen sind Interfaces; so konkrete Objekte leicht austauschbar • Variante 2 heißt Dependency Injection mit get- und setMethoden oder über Konstruktoren Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 572 Dependency Injection klassisch (2/2) Nutzer nutzer = new Nutzer(new Inter1RealA(42) , new Inter2RealC(43) , new Inter3RealD("Hallo")); eng verknüpft mit Factory Pattern Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 573 JSF nutzt bereits Dependency Injection @Named @SessionScoped public class SprintController implements Serializable @Inject PersistenzService pers; • @Named: Objekt steht und festen Namen zur Oberflächengestaltung zur Verfügung • @SessionScoped: Objekt steht für die gesamte Session (auch allen anderen Objekten) zur Verfügung (Context) • @Inject: „Umgebung gib (injiziere) mir ein zum Typen passendes Objekt“ • @PostConstruct: garantiert nach Erzeugung, vor Nutzung • Hinweis: Annotationen stammen aus dem CDI-Paket; es gibt sehr ähnliche in JSF-Paketen (JSF ohne CDI machbar) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 574 Ziele CDI • Entkopplung von Objekten voneinander – @Inject bestes Beispiel, es wird nur ein passendes Objekt „irgendwoher“ benötigt; dies besorgt CDIRealisierung • Vereinfachte Objekt-Kommunikation – Beispiel: Informationen abonnieren (bekannt als Publish Subscribe oder Observer Observable) • Vereinfachung von querschnittlich in mehreren Objekten benötigter Funktionalität – Beispiel: Logging • Hinzufügen von Funktionalität zu bestimmten Ereignissen ohne betroffene Methoden zu verändern – Beispiel: Konsistenzprüfung, Benachrichtigung (Aspektorientierung) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 575 Informationsquellen • CDI 1.1 gehört zu JEE 7 • JSR 299: Contexts and Dependency Injection for the JavaTM EE platform, https://jcp.org/en/jsr/detail?id=299 • JSR 346: Contexts and Dependency Injection for JavaTM EE 1.1, https://jcp.org/en/jsr/detail?id=346 • JSR 365: Contexts and Dependency Injection for JavaTM 2.0 (Draft) • Spezifikation http://www.cdi-spec.org/ – http://docs.jboss.org/cdi/spec/1.1/cdi-spec.pdf – http://docs.jboss.org/cdi/spec/1.2/cdi-spec-1.2.pdf • Referenzimplementierung Weld 2.0 (JBoss) • gute Einführung: http://docs.jboss.org/weld/reference/latest/enUS/html/intro.html Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 576 NetBeans: kein Deploy on Save gerade bei CDI ist ein vollständiges Clean & Build mit Deploy fast immer sinnvoll, da Server sonst Probleme z. B. mit noch laufenden Sessions hat Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 577 Aktivierung • explizit mit beans.xml in WEB-INF –Ordner; für reine EJB-Module oder jar-Dateien im Ordner META-INF <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.1" bean-discovery-mode="all"> </beans> • implizit, ohne beans.xml oder mit und bean-discoverymode="annotated", werden nur Beans mit Scope gefunden; typischerweise @Dependent für im Scope des Nutzers • in beans.xml können (und müssen teilweise) weitere Eigenschaften spezifiziert werden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 578 zentrales Hilfsmittel Qualifier • Qualifier sind einfache Annotationen, mit denen gewünschte bzw. geforderte Eigenschaften spezifiziert werden können • Normale neue Annotation mit Zusatzannotation @Qualifier ... import javax.inject.Qualifier; @Qualifier @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Info{} • folgende Folien: Qualifier nur erwähnt, haben dann die hier angegebene Form Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 579 Event-Model (Observer – Observable) • auch Publish-Subscribe • Observer sagt Bescheid, dass er vom Observable informiert werden möchte • Observable schickt Informationen an alle Abonnenten • Beispielaufgabe (Balkon): Informiere alle Interessierten, dass gerade ein Objekt persistiert werden soll Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 580 Event Model Übersicht (müssen nicht EJBs sein) @Qualifier … public @interface Info{} beliebige POJO-Klasse Qualifier class MeinEvent @Stateless public class PersistenceService { @Inject @Info Event<MeinEvent> event … MeinEvent me = … event.fire(me); Observable Observer @Stateless public class EventConsumer { public void empfangeMeinEvent( @Observes @Info MeinEvent event) {… Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 581 Definition des Event-Objekts • POJO mit Inhalten, die übertragen werden sollen package cdi.eventing; public class MeinEvent { // POJO private Object obj; public MeinEvent(){ } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 582 Observable – Benachrichtigen (Ausschnitt) @Stateless public class PersistenzService { @Inject @Info Event<MeinEvent> event; @Inject private EntityManager em; public void persist(Object object) { MeinEvent e = new MeinEvent(); e.setObj(object); event.fire(e); em.persist(object); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 583 Observer 1 • Durch Annotation @Info können verschiedenartige Events unterschieden werden • beteiligte Objekte sind EJBs oder haben einen Scope (oder übernehmen Scope des nutzenden Objekts) @Stateless public class EventConsumer { public void empfangeMeinEvent( @Observes @Info MeinEvent event) { System.out.println(event.getObj()); } } // wenn Konsument nicht existiert, wird er erzeugt! // nicht gewünscht: @Observes(notifyObserver=Recption.IF_EXISTS) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 584 Observer 2 @Stateless public class EventConsumer2 { // Methode zeigt Machbarkeit des Ansatzes, ob dies hier // sinnvoll, ist fraglich public void empfangeMeinEvent( @Observes @Info MeinEvent event) { System.out.println(event.getObj().getClass()); if (event.getObj() instanceof Mitarbeit){ Mitarbeit m = (Mitarbeit)event.getObj(); if (m.getTaetigkeit().isEmpty()){ m.setTaetigkeit("intern"); } } } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 585 Beispiel: neue Mitarbeit persistieren INFO: Mitarbeit [id=0, version=0, geplanteStunden=8, verbrauchteStunden=0, fertigstellungsgrad=0] INFO: class entity.Mitarbeit Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 586 Konkretisierung injizierter Objekte • bisher war immer eindeutig, welches Objekt bei @Inject genutzt wird • häufig wird aber eine bestimmte Variante eines Objekts benötigt • Ausgangspunkt: gibt Interface (oder abstrakte Klasse) mit mehreren Realisierungen • der konkret gewünschte Objekttyp wird dann durch @Inject und die zusätzliche Angabe von Qualifiern festgelegt • Beispiel: es gibt zwei verschiedene Ausgabemöglichkeiten, unterschieden durch Qualifier @LogQualifier und @SystemQualifier Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 587 Konkretisierung Übersicht @Qualifier … public @interface SystemQualifier{} interface Ausgabe {… @Qualifier … public @interface LogQualifier{} Interface Qualifier Qualifier @LogQualifier public class AusgabeLog implements Ausgabe {… Realisierung @SystemQualifier public class AusgabeSys implements Ausgabe {… Realisierung @Stateless public class EventConsumer { @Inject @LogQualifier Ausgabe aus; Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker Angabe gewünschter Variante 588 Beispiel (1/4): Realisierungen 1/2 public interface Ausgabe { public void ausgeben(String s); } @SystemQualifier public class AusgabeSystem implements Ausgabe{ @Override public void ausgeben(String s) { System.out.println(s); } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 589 Beispiel (2/4): Realisierungen 2/2 @LogQualifier public class AusgabeLog implements Ausgabe{ private final static Logger LOGGER = Logger .getLogger(AusgabeLog.class.getSimpleName()); @Override public void ausgeben(String s) { LOGGER.log(Level.INFO, "AusgabeLog: {0}", s); } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 590 Beispiel (3/4): Auswahl der Realisierung @Stateless public class EventConsumer { @Inject @LogQualifier Ausgabe aus; public void empfangeMeinEvent( @Observes @Info MeinEvent event) { //System.out.println(event.getObj()); aus.ausgeben(event.getObj().toString()); } } • ein zentrales Logging kann man mit CDI besser durch Interceptors realisieren • es können auch mehrere Qualifier angegeben werden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 591 Beispiel (4/4): Nutzung INFO: INFO: AusgabeLog: (0) CDI einbauen class entity.Sprint Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 592 Auswahlregeln • Wenn Bean keinen Qualifier hat, wird er automatisch als @Default gesetzt (kann man auch hinschreiben) • ohne benötigten Qualifier kann @Any genutzt werden • Klassen haben ohne Scope-Angabe den Scope @Dependent, der sich dem Scope des nutzenden Objekts anpasst Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 593 Produzenten • bisher wurde bei @Inject nach passenden Klassen gesucht und ein Objekt per Default-Konstruktor erzeugt • Objekt-Erzeugung kann aber auch durch mit @Produces annotierte Konstruktoren, Methoden (Rückgabe-Objekt) oder direkt Exemplarvariablen erfolgen • Konkretisierung des erzeugten Objekts wieder durch Qualifier (@Starttext, @Meldung im nächsten Beispiel) • Beispiel zeigt kritische „Wiederverwendung“ von Qualifier • javax.enterprise.inject.Produces nutzen Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 594 Statische Produzenten Übersicht (public vergessen) @Qualifier … @interface Info{} @Qualifier … @interface Meldung{} Qualifier Qualifier Produktionsvarianten class Produzent { @Produces @Meldung private String meldung; @Produces @Starttext public String getLogtext() {… Qualifier class AusgabeLog {… @Inject @Starttext @Info private String start; @Inject @Meldung private String text; … @Produces @Starttext @Info public String getLogtext2() {… Komponentenbasierte SoftwareEntwicklung @Qualifier … @interface Starttext{} Prof. Dr. Stephan Kleuker Beispielnutzungen 595 Beispiel (1/3): Produzenten-Klasse public class Produzent implements Serializable { private String logtext = "logtext"; @Produces @Meldung private String meldung; public Produzent() { this.meldung = "Hai";} @Produces @Starttext public String getLogtext() { return this.logtext; } @Produces @Starttext @Info public String getLogtext2() { System.out.println("getLogtext2"); return "2: " + this.logtext; } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 596 Beispiel (2/3): Nutzer der Produzenten @LogQualifier public class AusgabeLog implements Ausgabe{ private final static Logger LOGGER = Logger .getLogger(AusgabeLog.class.getSimpleName()); @Inject @Starttext @Info private String start; @Inject @Meldung private String text; @Override public void ausgeben(String s) { //LOGGER.log(Level.INFO, "AusgabeLog: {0}", s); LOGGER.log(Level.INFO, "{0} {1}: {2}" , new Object[]{this.text, this.start, s}); } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 597 Beispiel (3/3): Beispielnutzung INFO: INFO: INFO: getLogtext2 Hai 2: logtext: (0) Qualifier überlegen class entity.BacklogElement Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 598 Dynamische Produzenten Übersicht @Qualifier … public @interface Aktuell{} Qualifier Produktionsmethode public class Produzent{ @Produces @Aktuell public String mach(){… Komponentenbasierte SoftwareEntwicklung Beispielnutzung public class AusgabeLog { @Inject @Aktuell Instance<String> datum; … public void ausgeben(…) { … datum.get()}); … Prof. Dr. Stephan Kleuker 599 Dynamische Produktion (1/3) : Erzeugung • wieder Produzenten-Methode mit üblichen Qualifier (also nichts Neues hier) public class Produzent implements Serializable { @Produces @Aktuell public String mach(){ return new Date().toString(); } ... Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 600 Dynamische Produktion (2/3): Aufruf @LogQualifier public class AusgabeLog implements Ausgabe{ @Inject @Aktuell Instance<String> ... datum; @Override public void ausgeben(String s) { LOGGER.log(Level.INFO, "AusgabeLog: {0} - {1}" , new Object[]{s, datum.get()}); // LOGGER.log(Level.INFO, "{0} {1}: {2}" // , new Object[]{this.text, this.start, s}); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 601 Dynamische Produktion (3/3): Beispielnutzung INFO: AusgabeLog: (7) Kimi Räikkönen - Wed May 21 20:14:24 CEST 2014 INFO: AusgabeLog: (14) Fernando Alonso - Wed May 21 20:15:42 CEST 2014 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 602 Inject-Varianten • statt @Inject an Exemplarvariablen zu schreiben, ist dies auch möglich: @Inject public Konstruktor(Typ ichWerdeInjected){ … @Inject public void methode(Typ ichWerdeInjected){ … • natürlich wieder Qualifier nutzbar • weiterführend: mit @Typed an Klasse einschränken, für welche Klassen und Interfaces diese eingesetzt werden kann Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 603 Alternativen • über Qualifier können passende Klassen genau ausgewählt werden • z. B. zu Testzwecken, sollte diese Auswahl aber einfach änderbar sein – hierzu wird Klasse mit @Alternative markiert – UND muss in der beans.xml als ausgewählte Alternative angegeben werden • durch Änderung der beans.xml sehr einfach Klassenauswahl auf Testphase oder länderspezifische Auswahlen änderbar • mehrere Alternativen bei mehreren genutzten jars angebbar, dann Auswahl so steuerbar: @Priority(Interceptor.Priority.APPLICATION + 10) komplexes, sehr flexibles Auswahlsystem, wann welche Klasse genutzt wird (@Specialization) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 604 Nutzung der Alternative (1/3): weitere Klasse import cdi.qualifier.ausgabe.LogQualifier; import javax.enterprise.inject.Alternative; @Alternative @LogQualifier public class AusgabeLogMock implements Ausgabe{ @Override public void ausgeben(String s) { System.out.println("LogMock: " + s ); } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 605 Nutzung der Alternative (2/3): bean.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all"> <alternatives> <class>cdi.AusgabeLogMock</class> </alternatives> </beans> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 606 Nutzung der Alternative (3/3): Nutzung INFO: INFO: LogMock: (0) Alternativen realisieren class entity.BacklogElement Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 607 Interceptor Übersicht @Inherited @InterceptorBinding … public @interface InterceptQualifier{} unterbreche alle Methoden von @Stateless @InterceptQualifier public class BspKlasse { public void meth1(…) {… public void meth2(…) {… … <interceptors> <class>cdi.MeinInterceptor </class> Komponentenbasierte Software</interceptors> Entwicklung InterceptorBenennung bei Unterbrechung zu nutzen @InterceptQualifier @Interceptor public class MeinInterceptor { @AroundInvoke public Object logCall( InvocationContext ctx) throws Exception { … Method meth = ctx.getMethod(); … for (Object o: ctx.getParameters()) … return ctx.proceed(); }Prof. … Dr. 608 Stephan Kleuker Interception • Ansatz: sich in verschiedenen Methoden wiederholende Aufgaben zentral auslagern (Aspekt-Orientierung) • (einziger) Klassiker: Logging • Ansatz: Werden markierte Methoden oder Methoden in markierten Klassen ausgeführt, wird zunächst zum Interceptor gehörende Methode durchgeführt • Interceptor kann auf Methode und Parameter zugreifen • Interceptor muss Methodenausführung starten (proceed()) • Interceptor muss über bean.xml eingeschaltet werden (nur in diesem Archiv aktiv) oder @Priority-Annotation besitzen • Interceptor benötigt eigene Art von Qualifier • folgendes Beispiel zeigt ungewöhnliche Nutzung (auch Verstoß, dass möglichst wenig beobachtet werden soll) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 609 Nutzung von Interception (1/5): Annotation import import import import import import java.lang.annotation.ElementType; java.lang.annotation.Inherited; java.lang.annotation.Retention; java.lang.annotation.RetentionPolicy; java.lang.annotation.Target; javax.interceptor.InterceptorBinding; //@Qualifier @Inherited @InterceptorBinding @Target({ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface InterceptQualifier{} Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 610 Nutzung von Interception (2/5): Bereich festlegen • Hier werden alle Methoden der Klasse beobachtet, man kann die Annotation auch nur für einzelne Methoden nutzen @Stateless @InterceptQualifier public class EventConsumer2 { public void empfangeMeinEvent( @Observes @Info MeinEvent event) { System.out.println(event.getObj().getClass()); if (event.getObj() instanceof Mitarbeit){ Mitarbeit m = (Mitarbeit)event.getObj(); if (m.getTaetigkeit().isEmpty()){ m.setTaetigkeit("intern"); } } } Komponentenbasierte SoftwareProf. Dr. 611 Entwicklung Stephan Kleuker } Nutzung von Interception (3/5): Realisierung 1/2 @InterceptQualifier @Interceptor public class MeinInterceptor { @AroundInvoke // gibt auch @AroundConstruct, @PostConstruct public Object logCall(InvocationContext context) throws Exception { Method meth = context.getMethod(); System.out.println("Methode: " + meth); /* for (Object o : context.getParameters()) { System.out.print(o + " "); } */ Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 612 Nutzung von Interception (4/5): Realisierung 2/2 MeinEvent event = (MeinEvent) context.getParameters()[0]; if (event.getObj() instanceof Mitarbeiter) { Mitarbeiter m = (Mitarbeiter) event.getObj(); if (m.getMinr() == 999) { m.setMinr((int) (100000 + System.nanoTime()%900000)); // m.setMinr((int) m.getId()); kann nicht gehen, da 0 } } return context.proceed(); // wichtig irgendwann aufrufen } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 613 Nutzung von Interception (3/4): beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all"> <interceptors> <class>cdi.MeinInterceptor</class> </interceptors> </beans> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 614 Nutzung von Interception (4/4): Nutzung INFO: getLogtext2 INFO: Hai 2: logtext: (999) Clark Kent INFO: Methode: public void cdi.eventing.EventConsumer2.empfangeMeinEvent(cdi.eventing. MeinEvent) INFO: class entity.Mitarbeiter Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 615 Stereotype • mit CDI können große Mengen von Annotationen entstehen • häufiger haben ähnliche Klassen die gleichen Annotationen • diese können als neue Annotation zusammengefasst werden @RequestScoped @Named @MeineAnnotation @Stereotype @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Aktion {} • Klassen können auch mit mehreren Stereotypes (auch überlappend) annotiert werden • Beispiel: @Model vereint @Named und @RequestScoped Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 616 Weiterführende Themen • @Decorator, verwandt mit @Interceptor, ermöglicht Ergänzung von Funktionalität zu bestimmten Methoden • CDI auch zu eigener Transaktionssteuerung nutzbar Meinung: wenn JEE und EJB genutzt werden, spricht wenig für diesen Ansatz (Transaktion über mehrere Methoden) interessant: [Mül 14] B. Müller, JSF und JPA im Tandem, Teil 1, in: Javamagazin, 5/2014, Seiten 98-102, Software & Support Media GmbH, Frankfurt a. M. , 2014 • Auf Spezifikationsseite kann man sich über Version 1.2 informieren Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 617 Fazit • CDI ermöglicht eine sehr große Entkopplung der Klassen voneinander • Klassen werden so flexibler einsetzbar, evtl. Programmiermodel intuitiver • im Beispiel wird „Balkon“ an Projekt programmiert, da @Inject und @...Scope ausreichen; nicht untypisch für klassisches JEE-Projekt • CDI macht SW zur Zeit noch langsamer • Annotations-Warfare-Area wird drastisch vergrößert Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 618 7. (RESTful) Web Services • • • • • • • • • • • • • JavaScript Object Notation JSONP Idee: Web-Services Idee: RESTful erste Services GET, POST Clients Response Path-Parameter Aufrufparameter Architektur von REST-Applikationen Fallstudie (GET, POST, PUT, DELETE) Ausblick Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 619 Ausblick auf weitere Themen Browser 3 JSF Scope Bean Validation 2 RESTful WebService Web Sockets 1 CDI EJB JPA Datenbank Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 620 Einstieg JSON • JavaScript Object Notation (http://json.org/) • textuelles Austauschformat, abgeleitet aus JavaScript { "name": "Tony Stark", "alter": 42, "firma": { "name": "Stark Industries", "ort": "New York, N.Y" }, "freunde":["Steve Rogers", "Bruce Banner"] } • Sammlung von – (Name: Wert)-Paaren – Arrays von Werten • Werte können wieder aus beiden Elementen bestehen Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 621 Vereinheitlichung von JSON in Java in JEE 7 ergänzt: • JSR 353: JavaTM API for JSON Processing (23.5.2013), https://jcp.org/en/jsr/detail?id=353 • Referenzimplementierung jsonp https://jsonp.java.net/ • in Glassfish seit 4.0 enthalten zwei zentrale APIs • Object Model API; sehr analog zum DOM API für XML parsing • Streaming API; sehr analog zum StAX API • unabhängig von Programmiersprachen nutzbar • kompakter als XML (ähnlich gut/schlecht menschenlesbar) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 622 Beispiel: JSON-Object lesen (1/2) public static void main(String[] args) { String daten = "{ \"name\": \"Tony Stark\"," + " \"alter\": 42," + " \"firma\": { \"name\": \"Stark Industries\"," + " \"ort\": \"New York, N.Y\"" + "}," + "\"freunde\":[\"Steve Rogers\", \"Bruce Banner\", 42]" + "}"; JsonReader reader = Json.createReader(new StringReader(daten)); JsonObject tony = reader.readObject(); reader.close(); //Set<String> namen = tony.keySet(); // geht auch Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 623 Beispiel: JSON-Objekt lesen (2/2) System.out.println("Name : " + tony.getString("name")); System.out.println("Alter : " + tony.getInt("alter")); JsonObject firma = tony.getJsonObject("firma"); System.out.println("Firmenname : " + firma.getString("name")); System.out.println("Umsatz : " + firma.getInt("umsatz", 20)); JsonArray freunde = tony.getJsonArray("freunde"); Default, wenn for (JsonValue freund : freunde) { nicht da System.out.println(freund + " * " + freund.getValueType()); } Name : Tony Stark Alter : 42 } Firmenname : Stark Industries Umsatz : 20 Steve Rogers * STRING Bruce Banner * STRING 42 * NUMBER Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 624 Beispiel: JSON-Objekt von Hand erstellen public static void main(String[] args) { JsonObject personObject = Json.createObjectBuilder() .add("name", "Bruce Banner") .add("alter", 44) .add("firma", Json.createObjectBuilder() Object: .add("name", "Shield") {"name":"Bruce Banner","alter":44,"f .add("ort", "unbekannt") irma":{"name":"Shield .build()) ","ort":"unbekannt"}, .add("freunde", "freunde":["James Json.createArrayBuilder() Howlett","Ben .add("James Howlett") Grimm"]} .add("Ben Grimm") .build()) .build(); System.out.println("Object: " + personObject); Komponentenbasierte Software} Entwicklung Prof. Dr. Stephan Kleuker 625 Ausschnitt Klassendiagramm Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 626 Beispiel: Stream-Bearbeitung von JSON START_OBJECT: // daten: siehe JSON lesen KEY_NAME: name JsonParser parser = Json VALUE_STRING: .createParser(new StringReader(daten)); KEY_NAME: alter while (parser.hasNext()) { VALUE_NUMBER: 42 Event event = parser.next(); KEY_NAME: firma System.out.print(event + ": "); START_OBJECT: switch (event) { KEY_NAME: name VALUE_STRING: case KEY_NAME: System.out.print(parser.getString()); KEY_NAME: ort VALUE_STRING: break; END_OBJECT: case VALUE_NUMBER: KEY_NAME: freunde System.out.print(parser.getInt()); START_ARRAY: break; VALUE_STRING: } VALUE_STRING: System.out.println(""); VALUE_NUMBER: 42 } END_ARRAY: Komponentenbasierte SoftwareProf. Dr. 627 END_OBJECT: Entwicklung Stephan Kleuker Binding • Binding schafft automatische Umwandlungsmöglichkeit von A nach B und von B nach A • ohne Binding muss die Umwandlung (marshalling) manuell erfolgen, bei Netztransport ggfls. Rückumwandlung notwendig (unmarshalling) • Java-Objekt von und nach XML löst JAXB • JSR 222: JavaTM Architecture for XML Binding (JAXB) 2.0, https://jcp.org/en/jsr/detail?id=222 • wichtig Umwandlungsprozess konfigurierbar • Java-Objekt von und nach JSON noch nicht standardisiert (für JEE 8 angekündigt) • Referenzimplementierung für Glassfish (Stand Ende 2013) ist MOXy (übersetzt JAXB-Annotationen nach JSON) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 628 Beispiel: Vorbereitung einer Entitäts-Klasse für JSON @XmlRootElement public class Punkt implements Serializable { private int x; private int y; public Punkt() {} // wichtig public Punkt(int x, int y) {this.x = x; this.y = y;} public int getX() {return x;} public int getY() {return y;} public void setX(int x) {this.x = x;} public void setY(int y) {this.y = y;} @Override public String toString() {return "[" + x + "," + y + "]";} } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 629 Annotationen zur Steuerung der Übersetzung @XmlElement(name=“rufname") // Key-Umbenennung public String name; @XmlTransient // nicht übertragen public int alter; • man beachte, dass man erhaltenes Objekt auch noch mit vorherigen Methoden modifizieren kann • Übersetzung noch nicht standardisiert (aktuell MOXy, Teil von EclipseLink) • da manuelle JsonObject-Erzeugung nicht sehr aufwändig und sehr flexibel, wird es gute Alternative bleiben Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 630 Hintergrund Web Services • zentraler Wunsch: einfache Nutzung von Software über das Netz • unabhängig wo sich ein Rechner befindet • unabhängig von der Programmiersprache SOAP-basierte WebServices • jeder Service hat eindeutige Kennung (URI, Uniform Resource Identifier) • Schnittstellenbeschreibung WSDL • typisch: XML-basierte Kommunikationsprotokolle • typisch: Verbindung mit SOA • hier nicht wichtig, aber SOA ≠ SOAP ≠ Web Service Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 631 Hintergrund: Service Oriented Architecture UDDI ServiceVerzeichnis WSDL ServiceNutzer 3. Anfragen SOAP 4. Antworten ServiceAnbieter HTTP Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 632 Zwischenfazit SOA • Vision: auf Grundlage von Geschäftsprozessmodellierungen kann individuelle Software für ein Unternehmen entstehen • Realität: machbar, wenn alles auf einem Hersteller basiert • Realität: UDDI hat in fast allen Projekten nicht stattgefunden (SOA ist auch Super Overhyped Acronym) • aber: WebServices basierend auf SOAP haben als Kommunikationskonzept zentrale Bedeutung bekommen • gilt als relativ langsam • aber: Unternehmen nutzen es um MS-basierte Oberfläche mit JEE-realisiertem Server zu verbinden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 633 RESTful (Representational State Transfer) • Idee von der Interaktion zwischen Rechnern bleibt • REST ist ein Architekturstil für verteilte HypermediaSysteme • Protokoll: nutze Möglichkeiten von HTTP – GET: lese Information (SELECT) – POST: neue Information (INSERT) – PUT: ändere Information (UPDATE) – DELETE: lösche Information (DELETE) • Klammern deuten Ähnlichkeit zu Datenbankoperationen an • Grundlage: Dissertation Roy Fielding „Architectural Styles and the Design of Network-based Software Architectures “ • http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 634 Woher kommt „ Representational State Transfer“ http://www.scrumsprinter.de/sprint/42 Resource Client { “id”: 42, “name”: “Prototyp”, “elemente”: [ … Client fordert Information mit Hilfe einer URL an. Eine Repräsentation der Information wird als Ergebnis zurückgegeben (z. B. in Form eines JSON-Objekts), Client hat Informationszustand. Client nutzt Hyperlink in Ergebnis um weitere Informationen anzufordern. Neues Ergebnis versetzt Client in einen neuen Informationszustand. Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 635 HATEOAS – saubere REST-Architektur „Hypermedia as the Engine of Application State“ • Client kennt nur die Basis-URI des Dienstes • Server leitet durch Informationszustände der Anwendung durch Bekanntgabe von Wahlmöglichkeiten (Hyperlinks) • Der vollständige Informationszustand kann beim Client oder beim Server liegen, oder auch über beide verteilt sein • HTTP-Kommunikationsprotokoll selbst bleibt zustandslos • Grundregel: GET, PUT, DELETE sind idempotent; führen zum gleichen Ergebnis, egal wie oft sie im gleichen Informationszustand aufgerufen werden • häufig genutzter Trick: POST auch zur partiellen Aktualisierung Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 636 Wer nutzt es (Beispiele)? • Hinweis: Öfter wird gegen die geforderte Reinform von RESTful WebServices verstoßen, und normale Anfragemöglichkeit mit GET als RESTful bezeichnet • • • • Google Maps Google AJAX Search API Yahoo Search API Amazon WebServices Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 637 Standardisierung in Java viele Implementierungen • Restlet http://www.restlet.org/ • Apache CXF http://cxf.apache.org/ • Project Zero http://www.projectzero.org • GlassFish Jersey https://jersey.dev.java.net/ (Referenz) • JBoss RESTeasy http://www.jboss.org/resteasy/ Standardisierung für Java: • JSR 311: JAX-RS: The JavaTM API for RESTful Web Services, https://jcp.org/en/jsr/detail?id=311 (10.10.2008) • JSR 339: JAX-RS 2.0: The Java API for RESTful Web Services, https://jcp.org/en/jsr/detail?id=339 (24.5.2013) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 638 JAX-RS aktivieren • in JEE-aware Servern reicht theoretisch folgendes aus import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("resources") public class ApplicationConfig extends Application { } • ist generell im .war-File • sonst Konfiguration als Servlet nötig • Beschreibung in web.xml Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 639 JAX-RS aktivieren (Alternative) @ApplicationPath("resources") public class ApplicationConfig extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> resources = new java.util.HashSet<>(); try { // customize Jersey 2.0 JSON provider: Class jsonProvider = Class .forName("org.glassfish.jersey.moxy.json.MoxyJsonFeature"); resources.add(jsonProvider); } catch (ClassNotFoundException ex) {} addRestResourceClasses(resources); return resources; } private void addRestResourceClasses(Set<Class<?>> resources) { resources.add(hello.HelloWorld.class); Anbieter von } Services } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 640 erste RESTful-WebServices @Path("/helloworld") public class HelloWorld { public HelloWorld() { } @GET @Produces("text/html") public String getHtml() { return "<html><body><h1>Hello, World!!</h1></body></html>"; } @GET @Produces(MediaType.TEXT_PLAIN) public String getText() { return "Tach Welt"; } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 641 detaillierte Analyse @Path("/helloworld") • • • • Gibt Aufrufpfad an, hier resources/helloworld Pfad wird an Projektpfad, z. B. /vlRESTAnfang, angehängt könnte auch nur an einzelnen Methoden stehen kann auch zusätzlich an Methoden stehen, so dass sich der Pfad verlängert @GET @Produces("text/html") • Annotationen aus javax.ws.rs • HTTP-Befehl und Ergebnistyp (mögliche Ergebnistypen, mehrere MIME-Typen [Multipurpose Internet Mail Extension]) • nachfolgender Methodenname spielt keine Rolle! Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 642 direkter Aufruf • bei GET ist direkter Aufruf im Browser möglich • aber, das ist ein sehr sehr untypisches Szenario • typisch: – Aufruf direkt aus einer Web-Seite, meist mit JavaScript – Aufruf aus anderer Software heraus mit Mitteln der jeweiligen Programmiersprache (z. B. java.net.URL) • NetBeans: kein Haken bei „Display Browser on Run“ Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 643 Detaillierte Analyse mit cURL • generell jedes Programm zur Erzeugung von HTTP-Aufrufen und Analyse der Ergebnisse geeignet • Kommando-Zeile mit cURL http://curl.haxx.se/download.html • Für etwaige Parameter muss auch URL in Anführungsstrichen stehen • viele Browser unterstützen direkt bei solchen Tests Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 644 Nutzung automatischen Marshallings - GET • verschiedene Rückgabetypen bedienbar (praktisch sinnvoll?) @GET @Produces({MediaType.TEXT_XML, MediaType.APPLICATION_JSON}) public Punkt getJSon2() { return new Punkt(42,43); // war @XMLRootElement annotiert } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 645 Nutzung automatischen Unmarshallings - POST @POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.APPLICATION_JSON) public String postit(Punkt p){ System.out.println(p); return "ok"; } • weitere Parameter im JSON-Objekt führen zu Fehlern Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 646 zentrale Klassen Client und Response • RESTful Web Services werden typischerweise aus anderer Software aufgerufen • dies ist natürlich auch in Java möglich; vor JAX-RS 2.0 aber proprietäre Lösungen der Anbieter • https://jersey.java.net/download.html • jetzt Klasse javax.ws.rs.client.Client • Bei der Nutzung von RESTful Web Services können verschiedene Klassen als Typen für Parameter und Rückgabe genutzt werden • Hilfreich ist Klasse javax.ws.rs.core.Response • Server erzeugt Response-Objekt • Client kann problemlos Response-Objekt lesen • Response ist ein Stream, muss auch geschlossen werden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 647 Hilfsmethode zur genaueren Analyse von Response private void details(Response res) { System.out.println("-----------------\n" + "AllowedMethods : " + res.getAllowedMethods() + "\n" + "Entity Class: " + res.getEntity().getClass() + "\n" + "Language : " + res.getLanguage() + "\n" + "Location : " + res.getLocation() + "\n" + "Mediatype : " + res.getMediaType() + "\n" + "Links : " + res.getLinks() + "\n" + "Status : " + res.getStatus() + "\n" + "Date : " + res.getDate() + "\n" + "Class : " + res.getClass() + "\n" + "Inhalt : " + res.readEntity(String.class)); res.close(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 648 Kleine Beispiele (1/7) • Anmerkung: Zeigt Service-Nutzung, zeigt nichts von REST public class ClientAnalyse { private Client client; private WebTarget userTarget; public ClientAnalyse() { Client client = ClientBuilder.newClient(); userTarget = client .target("http://localhost:8080/vlRESTAnfang" + "/resources/helloworld"); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 649 Kleine Beispiele (2/7) public void analyse1() { Response res = userTarget.request("text/html").get(); details(res); } AllowedMethods : [] Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$2 Language : null Location : null Mediatype : text/html Links : [] Status : 200 Date : Fri Dec 18 15:39:22 CET 2015 Class : class org.glassfish.jersey.client.InboundJaxrsResponse Inhalt : <html lang="en"><body><h1>Hello, World!!</h1></body></html> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 650 Kleine Beispiele (3/7) public void analyse1() { Response res = userTarget.request(MediaType.TEXT_PLAIN).get(); details(res); } AllowedMethods : [] Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$2 Language : null Location : null Mediatype : text/plain Links : [] Status : 200 Date : Wed May 14 18:55:35 CEST 2014 Class : class org.glassfish.jersey.client.ScopedJaxrsResponse Inhalt : <html lang="en"><body><h1>Hello, World!!</h1></body></html> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 651 Kleine Beispiele (4/7) public void analyse3() { Response res = userTarget .request(MediaType.APPLICATION_JSON).get(); details(res); } AllowedMethods : [] Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$1 Language : null Location : null Mediatype : application/json Links : [] Status : 200 Date : Wed May 14 18:55:35 CEST 2014 Class : class org.glassfish.jersey.client.ScopedJaxrsResponse Inhalt : {"x":42,"y":43} Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 652 Kleine Beispiele (5/7) public void analyse4() { Response res = userTarget.request(MediaType.TEXT_XML).get(); details(res); } AllowedMethods : [] Entity Class: class org.glassfish.jersey.client.HttpUrlConnector$1 Language : null Location : null Mediatype : text/xml Links : [] Status : 200 Date : Wed May 14 19:08:13 CEST 2014 Class : class org.glassfish.jersey.client.ScopedJaxrsResponse Inhalt : <?xml version="1.0" encoding="UTF-8" standalone="yes"?><punkt><x>42</x><y>43</y></punkt> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 653 Kleine Beispiele (6/7) public void analyse5() { Builder buil = this.userTarget.request(MediaType.TEXT_PLAIN); Entity e = Entity.entity(new Punk(3, 4) , MediaType.APPLICATION_JSON); System.out.println(e + " : " + e.getEntity()); String res = buil.post(e, String.class); System.out.println(res); } javax.ws.rs.client.Entity@52aa911c : [3,4] ok • Anmerkung: Klasse Punk wie Punkt, sogar ohne XMLRootElement-Annotation , aber Serializable Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 654 Kleine Beispiele (7/7) public void analyse6() { Builder buil = this.userTarget.request(MediaType.TEXT_PLAIN); Entity e = Entity.json(new Punk(2,3)); System.out.println(e + " : " + e.getEntity()); String res = buil.post(e, String.class); System.out.println(res); } Entity{entity=[2,3], variant=Variant[mediaType=application/json, language=null, encoding=null], annotations=[]} : [2,3] ok • Klasse Entity bietet einige Marshalling-Methoden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 655 flexible Dienststrukturen • generell soll man aus Antworten auf weitere Abfragemöglichkeiten schließen können • /helloworld/kunden/ Frage nach Kunden: Sammlung der Namen aller Kunden • /helloworld/kunden/Hoeness/ Frage nach Kunden mit Namen: alle Eigenschaften des Kunden • /helloworld/kunden/Hoeness/konten Frage nach Konten eines benannten Kunden: Sammlung aller Konten des Kunden • /helloworld/kunden/Hoeness/konten/42 Frage nach Kontonummer eines benannten Kunden: alle Eigenschaften des Kontos dieses Kunden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 656 Beispiel: Umsetzung von Pfaden (1/4) @Path("helloworld") public class HelloWorld { // Kundenname, Sammlung von Konten (Nummer, Betrag) private Map<String, Map<Integer, Long> > kunden; public HelloWorld() { // zufaellige Beispieldaten Map<Integer,Long> tmp = new HashMap<>(); tmp.put(42,32000000L); kunden = new HashMap<>(); kunden.put("Hoeness", tmp); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 657 Beispiel: Umsetzung von Pfaden (2/4) @GET @Produces(MediaType.APPLICATION_JSON) @Path("/kunden/{user}/konten/{id}") public JsonObject getKontostand( @PathParam("user") String user , @PathParam("id") int id) { JsonObjectBuilder erg = Json.createObjectBuilder(); Map<Integer,Long> kunde = kunden.get(user); if(kunde == null){ return erg.add("fehler", "kein Kunde").build(); } Long summe = kunde.get(id); if(summe == null){ return erg.add("fehler", "kein Konto").build(); } return erg.add("summe", summe).build(); Komponentenbasierte SoftwareProf. Dr. } Entwicklung Stephan Kleuker 658 Beispiel: Umsetzung von Pfaden (3/4) public static void main(String[] a){ String[] verdaechtig = {"Rummenigge", "Hoeness"}; int[] nummern = {42,43}; Client client = ClientBuilder.newClient(); for(String v:verdaechtig){ for (int n:nummern){ WebTarget target = client.target("http://localhost:8080" + "/vlRESTAnfang/resources/helloworld/kunden/" + v + "/konten/" + n); JsonObject erg = target .request(MediaType.APPLICATION_JSON) .get(JsonObject.class); System.out.println(erg); } } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 659 Beispiel: Umsetzung von Pfaden (4/4) {"fehler":"kein Kunde"} {"fehler":"kein Kunde"} {"summe":32000000} {"fehler":"kein Konto"} Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 660 Umsetzung von Pfaden @Path("/kunden/{user}/konten/{id}") • Einbau von Pfadvariablen, auf die in Parameterliste mit @PathParam("user") zugegriffen werden kann • einfache Java-Typen, typischerweise int, long, String nutzbar; Konvertierung automatisch • Pfadvariablen in der Klassenannotation können dann in jedem Methodenkopf genutzt werden • Pfadvariablen können in @Path doppelt vorkommen und müssen dann gleichen Wert bei Nutzung haben • im Hinterkopf: wenn HTTPS, dann auch User-Token so übertrag- und später prüfbar (Sicherheit) • im Hinterkopf: individueller Wert für jeden Nutzer, der EMail mit so einem Link erhält Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 661 Externer Service zur Analyse von IPs (1/4) private static void zeigeJsonObjekt(JsonObject js){ for(String key:js.keySet()){ System.out.println(key+ ": " + js.get(key)); } } public static void main(String[] s){ String SERVICE = "http://freegeoip.net/json"; Client client = ClientBuilder.newClient(); WebTarget wt = client.target(SERVICE); Invocation.Builder invoc = wt.request(); JsonObject ergebnis = invoc.get(JsonObject.class); zeigeJsonObjekt(ergebnis); zeigeJsonObjekt(client.target(SERVICE+"/www.bild.de") .request().get(JsonObject.class)); } Komponentenbasierte SoftwareProf. Dr. Entwicklung Stephan Kleuker 662 Externer Service zur Analyse von IPs (2/4) ip: "84.155.86.93" country_code: "DE" country_name: "Germany" region_code: "NI" region_name: "Lower Saxony" city: "Neuenkirchen" zip_code: "49586" time_zone: "Europe/Berlin" latitude: 52.4167 longitude: 7.85 metro_code: 0 Komponentenbasierte SoftwareEntwicklung ip: "72.247.9.43" country_code: "US" country_name: "United States" region_code: "MA" region_name: "Massachusetts" city: "Cambridge" zip_code: "02142" time_zone: "America/New_York" latitude: 42.3626 longitude: -71.0843 metro_code: 506 Prof. Dr. Stephan Kleuker 663 Externer Service zur Analyse von IPs (3/4) public static void main(String[] st){ Client client = ClientBuilder.newClient(); WebTarget wt = client.target("http://freegeoip.net/json"); Invocation.Builder invoc = wt.request(); Response ergebnis = invoc.get(); System.out.println(ergebnis); ergebnis.bufferEntity(); // sonst Fehler bei 42 System.out.println(ergebnis.getEntity()); for(String s:ergebnis.getHeaders().keySet()){ System.out.println(s +": " + ergebnis.getHeaders().get(s)); } System.out.println(ergebnis.readEntity(JsonObject.class)); System.out.println(ergebnis.getEntity().getClass()); //42 ergebnis.close(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 664 Externer Service zur Analyse von IPs (4/4) ScopedJaxrsResponse{ClientResponse{method=GET, uri=http://freegeoip.net/json, status=200, reason=OK}} java.io.ByteArrayInputStream@6d420a24 Date: [Wed, 14 May 2014 17:48:10 GMT] Access-Control-Allow-Origin: [*] Content-Length: [222] Content-Type: [application/json] {"ip":"93.196.192.46","country_code":"DE","country_name":"Germany"," region_code":"07","region_name":"NordrheinWestfalen","city":"Hopsten","zipcode":"","latitude":52.3833,"longitu de":7.6167,"metro_code":"","area_code":""} class java.io.ByteArrayInputStream Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 665 Übergabe von Aufrufparametern (1/2) @GET @Produces(MediaType.APPLICATION_JSON) @Path("/rechnen") public JsonObject machMathe( @QueryParam("op1") int op1, @QueryParam("op2") int op2, @DefaultValue("plus") @QueryParam("operator") String operator) { JsonObjectBuilder erg = Json.createObjectBuilder(); if(operator.equals("minus")){ return erg.add("operator", operator) .add("ergebnis", (op1-op2)).build(); } return erg.add("operator", "plus") .add("ergebnis", (op1+op2)).build(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 666 Übergabe von Aufrufparametern (2/2) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 667 Dienstnutzung mit Aufrufparametern (1/2) public static void main(String[] s) { String SERVICE = "http://maps.googleapis.com/maps/api/geocode/json"; Client client = ClientBuilder.newClient(); WebTarget wt = client.target(SERVICE + "?address=Quakenbrueck&sensor=false"); Invocation.Builder invoc = wt.request(); JsonObject ergebnis = invoc.get(JsonObject.class); System.out.println(ergebnis); JsonObject details = ((JsonArray)ergebnis.get("results")) .getJsonObject(0); JsonObject position= (JsonObject) ((JsonObject)details.get("geometry")).get("location"); System.out.println(position); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 668 Dienstnutzung mit Aufrufparametern (2/2) {"results":[{"address_components":[{"long_name":"Quakenbrück"," short_name":"Quakenbrück","types":["locality","political"]},{"l ong_name":"Osnabrück","short_name":"OS","types":["administrativ e_area_level_3","political"]},{"long_name":"Lower Saxony","short_name":"NDS","types":["administrative_area_level_ 1","political"]},{"long_name":"Germany","short_name":"DE","type s":["country","political"]}],"formatted_address":"Quakenbrück, Germany","geometry":{"bounds":{"northeast":{"lat":52.6967289,"l ng":8.0344312},"southwest":{"lat":52.65917049999999,"lng":7.903 767999999999}},"location":{"lat":52.675599,"lng":7.950777699999 999},"location_type":"APPROXIMATE","viewport":{"northeast":{"la t":52.6967289,"lng":8.0344312},"southwest":{"lat":52.6591704999 9999,"lng":7.903767999999999}}},"types":["locality","political" ]}],"status":"OK"} {"lat":52.675599,"lng":7.950777699999999} Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 669 Aufgabe Sprinter soll um eine RESTful-Schnittstelle ergänzt werden, • mit der von außen auf Sprints zugegriffen werden kann, • die nur eine Teilmenge der Daten der Sprints sieht, • die neue Sprints anlegen kann, • die Sprints editieren kann, • die Sprints löschen kann • Entscheidung: Ergänze Programm um RESTful Webservices • Schnittstelle wird in neuem Projekt genutzt (das zum einfacheren Verständnis eine JSF-Oberfläche bekommt) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 670 Nutzungsszenario • Links nicht ausimplementiert Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 671 Architektur: hierarchischer Aufbau Resource POST GET (CREATE) (READ) PUT (UPDATE) DELETE (DELETE) /sprints erzeugt neuen Sprint Aktualisiere alle Sprints (oder weglassen) alle Sprints löschen /sprints/42 Fehler! Übersicht über alle Sprints Zeige Sprint wenn Sprint Lösche den mit id 42 mit id 42 Sprint mit id existiert, dann 42 aktualisieren, (sonst Fehler ?) Hinweise: noch sauberer wäre /sprint/42 (Einzahl) graue Felder nicht realisiert Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 672 Einordnung SprintRestController (Server) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 673 Client (minimal) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker SprinterRESTClient 674 Vorbereitung im Server @Stateless // oder @Singleton @Path("") public class SprintRestController implements Serializable{ @Inject private PersistenzService pers; @Context private UriInfo uriInfo; // später genauer private SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); public SprintRestController() { } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 675 Hilfsmethode zum Sprint einpacken private JsonObject jsonSprint(Sprint s, boolean einzeln) { String idzeigen = (einzeln) ? "" : "" + s.getId(); JsonObjectBuilder js = Json.createObjectBuilder(); js.add("id", s.getId()) .add("motto", s.getMotto()) .add("starttermin", formatter.format(s.getStarttermin())) .add("endtermin", formatter.format(s.getEndtermin())) .add("geplanterAufwand", s.getGeplanterAufwand()) .add("farbe", s.color()) .add("link", uriInfo.getAbsolutePathBuilder() .path(idzeigen + "/backlogElemente") .build().getPath()); return js.build(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 676 GET /sprints (1/2) @GET @Produces({MediaType.APPLICATION_JSON}) @Path("/sprints") public JsonObject getSprints( @DefaultValue("-1") @QueryParam("von") int von, @DefaultValue("-1") @QueryParam("bis") int bis) { List<Sprint> alle = pers.findAllSprint(); if (von < 0 || von >= alle.size()) { von = 0; } if (bis < 0 || bis >= alle.size()) { bis = alle.size() - 1; } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 677 GET /sprints (2/2) JsonArrayBuilder elemente = Json.createArrayBuilder(); for (int i = von; i <= bis; i++) { elemente.add(jsonSprint(alle.get(i), false)); } return Json.createObjectBuilder() .add("sprints", elemente) .build(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 678 Client – Vorbereitung (1/2) • Client braucht keine echte Datenhaltung • Ansatz: Daten lokal in SessionScope halten (für kleinere Datenmengen ok @Named @SessionScoped public class SprintController implements Serializable { private Client client; private List<Map<String, Object>> sprints; private final static String[] keys = {"id", "motto" , "starttermin", "endtermin", "geplanterAufwand" , "link", "farbe"}; private final static String SPRINTS = "http://localhost:8080/Sprinter/resources/sprints"; private final static String HOME = "index"; Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 679 Client – Vorbereitung (2/2) • eine Variable pro Eigenschaft mit get und set enum Status {BASIC, EDIT;} private long id; private String motto; private Date starttermin; private Date endtermin; private int geplanterAufwand; private Status modus; private String meldung = ""; // Statusmeldung ohne Voodoo SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); public SprintController() { } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 680 Client – Initialisierung (1/2) @PostConstruct public void init() { this.modus = Status.BASIC; this.client = ClientBuilder.newClient(); WebTarget wt = client.target(SPRINTS); Invocation.Builder buil = wt .request(MediaType.APPLICATION_JSON); JsonObject ergebnis = buil.get(JsonObject.class); JsonArray array = ergebnis.getJsonArray("sprints"); this.sprints = new ArrayList<Map<String, Object>>(); for (JsonValue val : array) { JsonObject js = (JsonObject) val; // speichert einen String als Attribut/Wert-Paar Map<String, Object> werte = new HashMap<String, Object>(); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 681 Client – Initialisierung (2/2) for (String k : keys) { werte.put(k, js.get(k)); } this.sprints.add(werte); } this.motto = ""; this.starttermin = null; this.endtermin = null; this.geplanterAufwand = 0; } in älteren Versionen überflüssige " entfernen for (String k : keys) { Object tmp = js.get(k); String txt = tmp.toString(); if(txt.startsWith("\"") && txt.endsWith("\"") && txt.length() > 1){ tmp = txt.substring(1, txt.length()-1); } werte.put(k, tmp); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 682 Erfolgloses Löschen möglich • vom anderen Nutzer gelöscht oder modifiziert • Idempotent wäre, diesen Fehler zu ignorieren (ist gelöscht) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 683 Server Action loeschen @DELETE @Path("/sprints/{id}") public JsonObject loeschen(@PathParam("id") long id) { pers.removeSprint(id); JsonObjectBuilder js = Json.createObjectBuilder(); js.add("status", "geloescht"); return js.build(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 684 Client löschen public String loeschen(Object sid) { System.out.println("loeschen: " + sid); WebTarget wb = client.target(SPRINTS + "/" + sid); Invocation.Builder build = wb .request(MediaType.APPLICATION_JSON); try { JsonObject ergebnis = build.delete(JsonObject.class); this.meldung = "loeschen erfolgreich: " + ergebnis; } catch (Exception e) { this.meldung = "loeschen gescheitert: " + e; } init(); this.modus = Status.BASIC; return HOME; } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 685 Neuer Sprint – Server (1/2) @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Path("/sprints") public JsonObject hinzufuegen(JsonObject jo) { Sprint sprint = new Sprint(); sprint.setMotto(jo.getString("motto")); sprint.setGeplanterAufwand(jo.getInt("geplanterAufwand")); SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); try { sprint.setStarttermin(formatter .parse(jo.getString("starttermin"))); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 686 Neuer Sprint – Server (2/2) sprint.setEndtermin(formatter .parse(jo.getString("endtermin"))); } catch (ParseException ex) { return null; } pers.persist(sprint); JsonObjectBuilder js = Json.createObjectBuilder(); js.add("link" , uriInfo.getAbsolutePathBuilder() .path(sprint.getId() + "/backlogElemente") .build().getPath()); return js.build(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 687 Neuer Sprint – Client Aktion uebernehmen (1/4) public String uebernehmen() { // Validerung des Clients muss dieser regeln if (this.starttermin == null || this.endtermin == null){ this.meldung = "Start- und Endtermin angeben!"; return HOME; } if (this.starttermin.compareTo(this.endtermin) > 0){ this.meldung = "Endtermin nicht vor Starttermin"; return HOME; } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 688 Neuer Sprint – Client Aktion uebernehmen (2/4) JsonObjectBuilder js = Json.createObjectBuilder(); js.add("motto", this.motto) .add("starttermin", formatter.format(this.starttermin)) .add("endtermin", formatter.format(this.endtermin)) .add("geplanterAufwand", this.geplanterAufwand); if (this.modus.equals(Status.BASIC)) { neuerSprint(js); } if (this.modus.equals(Status.EDIT)) { editiereSprint(js); } init(); this.modus = Status.BASIC; return HOME; } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 689 Neuer Sprint – Client Aktion uebernehmen (3/4) private void neuerSprint(JsonObjectBuilder js){ WebTarget wb = client.target(SPRINTS); Invocation.Builder build = wb .request(MediaType.APPLICATION_JSON); Entity entity = Entity.entity(js.build() , MediaType.APPLICATION_JSON); try { JsonObject ergebnis = build.post(entity, JsonObject.class); this.meldung = "einfuegen erfolgreich: " + ergebnis; } catch (Exception e) { this.meldung = "einfuegen gescheitert: " + e; } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 690 Sprint editieren – Server (1/2) @PUT @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Path("/sprints/{id}") public JsonObject aktualisieren( @PathParam("id") long id , JsonObject jo) { Sprint sprint = pers.findSprint(id); sprint.setMotto(jo.getString("motto")); sprint.setGeplanterAufwand(jo.getInt("geplanterAufwand")); try { sprint.setStarttermin(formatter .parse(jo.getString("starttermin"))); sprint.setEndtermin(formatter .parse(jo.getString("endtermin"))); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 691 Sprint editieren – Server (2/2) } catch (ParseException ex) { return null; } pers.merge(sprint); JsonObjectBuilder js = Json.createObjectBuilder(); js.add("link" , uriInfo.getAbsolutePathBuilder() .path("/backlogElemente").build().getPath()); return js.build(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 692 Editiere Sprint – Client Aktion uebernehmen (4/4) private void editiereSprint(JsonObjectBuilder js) { WebTarget wb = client.target(SPRINTS + "/" + this.id); Invocation.Builder build = wb .request(MediaType.APPLICATION_JSON); js.add("id", id); Entity entity = Entity.entity(js.build() , MediaType.APPLICATION_JSON); try { JsonObject ergebnis = build.put(entity, JsonObject.class); this.meldung = "aktualisieren erfolgreich: " + ergebnis; } catch (Exception e) { this.meldung = "aktualisieren gescheitert: " + e; } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 693 UriInfo (1/2) @Path("ana") @Stateless public class Analyse { @Context private UriInfo uriInfo; private final static Logger LOGGER = Logger .getLogger(Analyse.class.getSimpleName()); @GET @Produces(MediaType.TEXT_PLAIN) public String getText() { LOGGER.info("in getText"); LOGGER.info(this.uriInfo.getAbsolutePath().toString()); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 694 UriInfo (2/2) LOGGER.info(this.uriInfo.getPath()); LOGGER.info(this.uriInfo.getRequestUri().toString()); for (String s:this.uriInfo.getQueryParameters().keySet()){ LOGGER.info(s+ ": " + this.uriInfo.getQueryParameters().get(s)); } return "hai"; } INFO: INFO: INFO: INFO: INFO: INFO: in getText http://localhost:8080/resources/ana /ana http://localhost:8080/resources/ana?x=Hai&text=42 text: [42] x: [Hai] Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 695 WADL (1/3) • Web Application Description Language • XML-basierte Beschreibung angebotener Dienste • generell soll HTTP-Befehl OPTIONS genutzt werden, um Übersicht zu erhalten • Alle möglichen Dienste mit Parametern werden aufgeführt • Dienstbeschreibungen können aus Annotation generiert werden • Alternativ kann @OPTIONS-annotierte Methode realisiert werden (z. B. um Ausgabe zu verhindern) • Bedeutung eher gering, für Werkzeuge basierend auf WADL-Services interessant; erkennen so Aktualisierungen Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 696 WADL (2/3) - Beispielmethode @GET @Produces("text/html") public String getHtml() { return "<html><body>Hello, World!!</body></html>"; } <resources base="http://localhost:8080/resources/"> <resource path="helloworld"> <method id="getHtml" name="GET"> <response> <representation mediaType="text/html"/> </response> </method> ... Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 697 WADL (3/3) – Beispiel aus Sprinter <resources base="http://localhost:8080/Sprinter/resources/"> <resource path="sprints"> <method id="getSprints" name="GET"> <request> <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="von" style="query" type="xs:int" default="-1"/> <param xmlns:xs="http://www.w3.org/2001/XMLSchema" name="bis" style="query" type="xs:int" default="-1"/> </request> <response> <representation mediaType="application/json"/> </response> </method> <method id="hinzufuegen" name="POST"> <request> <representation mediaType="application/json"/> </request> <response> Komponentenbasierte SoftwareProf. Dr. <representation mediaType="application/json"/> Entwicklung Stephan Kleuker 698 @FormParam <form action="http://vfl.de/mitglieder" method="post"> <p> Vorname: <input type="text" name="vorname"><br> Nachname: <input type="text" name="nachname"><br> <input type="submit" value="Send"> </p> ermöglicht die Übernahme </form> von Parametern einer POSTAnfrage eines HTML@Path("/mitglieder") Formulars @Consumes(Mediatype.APPLICATION_FORM_URLENCODED) public class CustomerResource { @POST public void createCustomer( @FormParam(“vorname") String vorname , @FormParam(“nachname") String nachname) { ... } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 699 Response.Status (gibt evtl. passende Exceptions) public enum Status { OK(200, "OK"), CREATED(201, "Created"), ACCEPTED(202, "Accepted"), NO_CONTENT(204, "No Content"), MOVED_PERMANENTLY(301, "Moved Permanently"), SEE_OTHER(303, "See Other"), NOT_MODIFIED(304, "Not Modified"), TEMPORARY_REDIRECT(307, "Temporary Redirect"), BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, "Unauthorized"), FORBIDDEN(403, "Forbidden"), NOT_FOUND(404, "Not Found"), NOT_ACCEPTABLE(406, "Not Acceptable"), CONFLICT(409, "Conflict"), GONE(410, "Gone"), PRECONDITION_FAILED(412, "Precondition Failed"), UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), INTERNAL_SERVER_ERROR(500, "Internal Server Error"), SERVICE_UNAVAILABLE(503, "Service Unavailable"); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 700 Weiterführend (1/2) • asynchron @POST @Asynchronous public void bearbeite( @Suspended AsyncResponse ar, Daten daten) • reguläre Ausdrücke in Path, @Path("{id : .+}") komplexe Auswertungsregeln, was, wenn mehrere Möglichkeiten an Pfaden existieren • HEAD: nimmt typischerweise erste GET und gibt statt Ergebnis nur Header und Response-Code zurück • MIME-Types können sehr detailliert sein, generell type/subtype;name=value;name=value... @Consumes("application/xml;charset=utf-8") Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 701 Weiterführend (2/2) • JAX-RS-Annotationen können auch nur in Interfaces ausgelagert werden • Matrix-Parameter (Attribute) behandelbar http://beispiel.spieler.de/vfl;typ=Sturm/2015 • Nutzung von Header-Parametern @HeaderParam public String get(@HeaderParam("Referrer") String aufrufer) { public String get(@Context HttpHeaders headers) { • Cookie-Nutzung public String get(@CookieParam(“minr") int minr) • genauere Analyse vom ResponseBuilder.status(.) • Einbindung von Bean Validation • … Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 702 Literatur • (Standard-Links sind im Text) • [Bur14] B. Burke, RESTful Java with JAX-RS 2.0, O‘Reilly, Sebastopol (CA), USA, 2014 • http://www.oracle.com/technetwork/articles/java/jaxrs201929352.html Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 703 8. WebSockets • WebSockets – Verbreitung – zentrale Nachrichten – Realisierung eines Chats – Encoder – Decoder • Bedeutung von JavaScript basiert teilweise auf: [Dit14] A. Ditler, Prototypische Realisierung eines Echtzeit-Webchats als CrossplattformApplikation auf Basis von Websockets, Hochschule Osnabrück, Bachelorarbeit, 2014 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 704 WebSockets - Motivation • HTTP erlaubt nur die Beantwortung von Client-Anfragen • ohne Erweiterung keine Möglichkeit, dass der Server den Client nachträglich ohne erneute Anfrage informiert • nur mit Workaround z. B. AJAX und Long-Polling möglich • WebSockets erlauben die bidirektionale Kommunikation zwischen Client und Server • allgemein: The WebSocket API, W3C Candidate Recommendation, 20.09.2012, http://www.w3.org/TR/websockets/ • standardisiert in Java: JSR 356: JavaTM API for WebSocket, https://jcp.org/en/jsr/detail?id=356 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 705 Verbreitung (1/2) • aktuell wird Ansatz in vielen Projekten evaluiert, Nutzung hängt von Zielplattformen ab • Anmerkung: Abkündigung von Win XP-Support lässt alte IEBrowser verschwinden • genauer müssen unterstütze Prokollversionen (ab wann) und Zielplattformen zusammen evaluiert werden • Beispiel: Android-Browser erst ab Android 4.4, andere Browser für Android schon eher • Beispiel: Web-Seite soll auch zur App auf Handys werden, ein Ansatz mit Apache Cordova / Phonegap, unterstützt nur etwas ältere WebSocket-Version Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 706 Verbreitung (2/2) aktueller Stand: http://caniuse.com/websockets • IE ab 10.0, Edge ab Start • Firefox ab 11.0 • Chrome ab 16 • Safari ab 7.0 • Opera ab 12.1 • iOS Safari ab 6.1 • Android Browser ab 4.4 • Blackberry Browser ab 7.0 • Chrome for Android ab 33.0 • Firefox for Android ab 26 • IE Mobile ab 10.0 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 707 grobe Funktionsweise / Potenzial • Client und Server in Java möglich, genauso gut können aber Clients in anderen Sprachen geschrieben werden (Server auch) • Verbindung mit Server wird über HTTP hergestellt, Server dabei auf Upgrade auf WebSocket-Protokoll befragt typische Adresse: new URI("ws://localhost:1790/hallo/echo") • wenn Server Upgrade anbietet, wird bidirektional nutzbare Verbindung aufgebaut (ohne dass diese physikalisch gehalten werden muss) • wenn Server kein Upgrade anbietet, ist Ansatz gescheitert • es gibt verschiedene Protokoll-Versionen, auch hier muss sich auf eine geeinigt werden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 708 etwas Hintergrund • • • • • • • • • Auf TCP basierendes Netzwerkprotokoll Bidirektionale – Vollduplex Kommunikation Verbindung basiert auf einem einzigen Socket Datenübertragung mit geringer Latenzzeit Datenaustausch: binär, utf-8, …, nur Zeichenketten oder Byte-Buffer Websocket-Verbindung: ws:// und wss:// keine Probleme bei Firewalls und Proxy-Servern Referenzimplementierung: Tyrus https://tyrus.java.net/ Alternativen: GNU WebSocket4J, Webbit, Tootallnate Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 709 zentrale Nachrichten Server onOpen onMessage onClose onError Verbindungsaufbau senden und empfangen Verbindungsabbau Fehlerfall Client onOpen onMessage onClose onError eigentliches Protokoll, was in welcher Form ausgetauscht wird, muss von Entwicklern festgelegt werden Begriff „Socket“ kann ernst genommen werden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 710 Erstes Beispiel (1/7): Gewünscht Server Client connectToServer @OnOpen (Bestätigung) send(Hello) @OnMessage @OnMessage send(Hallo Client) send(Hello again) @OnMessage @OnMessage send(Hallo Client) (schließen) @OnClose Komponentenbasierte SoftwareEntwicklung @OnClose (Bestätigung) Prof. Dr. Stephan Kleuker 711 Erstes Beispiel (2/7): Server (1/2) @ServerEndpoint("/echo") public class EchoServer { @OnOpen public void onOpen(Session session, EndpointConfig cfg) { System.out.println("@Server Anfrage URI: " + session.getRequestURI()); } @OnMessage public void onMessage(String message, Session session) throws IOException { System.out.println("@Server Nachricht: " + message); session.getBasicRemote().sendText("Hallo Client"); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 712 Erstes Beispiel (3/7): Server (2/2) @OnClose public void onClose(Session session , CloseReason closeReason) { System.out.println("@Server CloseReason: " + closeReason); } @OnError public void onError(Session session, Throwable thr) { System.out.println("@Server Error: " + thr); } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 713 Erstes Beispiel (4/7): Client (1/3) @ClientEndpoint public class EchoClient { @OnOpen public void onOpen(Session session, EndpointConfig config) { System.out.println("Id: " + session.getId() + "\nnegotiated: " + session.getNegotiatedSubprotocol() + "\nProtocol Version: " + session.getProtocolVersion() + "\nQuery String: " + session.getQueryString() + "\nRequestURI: " + session.getRequestURI() + "\nMaxIdleTimeout:" + session.getMaxIdleTimeout()); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 714 Erstes Beispiel (5/7): Client (2/3) @OnMessage public void onMessage(String message, Session session) throws IOException { System.out.println("@Client empfangen: " + message); } @OnClose public void onClose(Session session, CloseReason closeReason) { System.out.println("@Client CloseCode: " + closeReason.getCloseCode() + "\n@Client ReasonPhrase:" + closeReason.getReasonPhrase()); } @OnError public void onError(Session session, Throwable thr) { System.out.println("@Client Error: " + thr); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 715 Erstes Beispiel (6/7): Client (3/3) public static void main(String[] args) { WebSocketContainer container = ContainerProvider .getWebSocketContainer(); try (Session session = container .connectToServer(EchoClient.class, URI.create( "ws://localhost:8080/WebSocketHelloWorld/echo"))) { session.getBasicRemote().sendText("Hello"); session.getBasicRemote().sendText("Hello again"); System.out.println("1: " + session.isOpen()); session.close(new CloseReason( CloseCodes.NORMAL_CLOSURE , "Schicht")); System.out.println("2: " + session.isOpen()); } catch (Exception e) { e.printStackTrace(); } Komponentenbasierte SoftwareProf. Dr. 716 Entwicklung Stephan Kleuker } Erstes Beispiel (7/7): Ausgabe Id: 2d80aea7-3d60-4a59-aaeb-56844cbbc25e negotiated: Protocol Version: 13 Query String: null RequestURI: ws://localhost:8080/vlWebSocketEcho/echo MaxIdleTimeout:0 1: true @Client CloseCode: NORMAL_CLOSURE @Client ReasonPhrase:Schicht 2: false @Client empfangen: Hallo Client INFO: INFO: INFO: INFO: @Server @Server @Server @Server Anfrage URI: /vlWebSocketEcho/echo Nachricht: Hello Nachricht: Hello again CloseReason: CloseReason[1000,Schicht] Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 717 Analyse des ersten Beispiels • Client und Server unterscheiden sich im Wesentlichen nur durch Annotationen @ClientEndpoint und @ServerEndpoint • in JEE-Container führt @ServerEndpoint automatisch zum Deployen (läuft) • beide nutzen @OnOpen, @OnMessage, @OnClose und gegebenenfalls @OnError • zeigt Symmetrie der Kommunikationspartner • wichtige (zu verwaltende) Objekte vom Typ Session • Beispiel zeigt, dass es vom Timing abhängt, ob Bestätigung der zweiten Nachricht noch ankommt! • keine explizite Nutzung von Threads notwendig; da paralleler Zugriff aber Synchronisation eventuell wichtig Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 718 2. Fallstudie (1/7): Realisierung eines Chats • • • • Clients können sich beim Server zum Chatten anmelden jede geschickte Nachricht wird an alle anderen verteilt Abmelden mit Nachricht „bye“ möglich (da wieder nur textbasiert, Überlappungen in Ein- und Ausgabe möglich) • Server verwaltet Client-Sessions in einer synchronisierten Collection Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 719 2. Fallstudie (2/7): Server 1/2 @ServerEndpoint("/chat") public class ChatServer { private static Set<Session> partner = Collections.synchronizedSet(new HashSet<Session>()); @OnOpen public void onOpen(Session session, EndpointConfig config) { this.partner.add(session); } @OnMessage public void onMessage(String msg, Session s) throws IOException { sende(msg); } @OnClose public void onClose(Session session, CloseReason closeReason) { this.partner.remove(session); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 720 2. Fallstudie (3/7): Server 2/2 private void sende(String nachricht) { try { // nebenbei aufraeumen List<Session> geschlossen = new ArrayList<>(); for (Session s : this.partner) { if (!s.isOpen()) { System.err.println("Geschlossen: " + s.getId()); geschlossen.add(s); } else { s.getBasicRemote().sendText(nachricht); } } this.partner.removeAll(geschlossen); System.out.println("Sende " + nachricht + " an " + this.partner.size() + " Klienten"); } catch (Throwable e) { e.printStackTrace(); } Komponentenbasierte SoftwareProf. Dr. 721 Stephan Kleuker } Entwicklung 2. Fallstudie (4/7): Client 1/2 @ClientEndpoint public class ChatClient { @OnOpen public void onOpen(Session session, EndpointConfig cfg) { System.out.println("verbunden"); } @OnMessage public void onMessage(String msg, Session s) throws IOException { System.out.println("@Client empfangen: " + msg); } @OnClose public void onClose(Session session, CloseReason closeReason) { System.out.println("abgemeldet"); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 722 2. Fallstudie (5/7): Client 2/2 public static void main(String[] args) { WebSocketContainer container = ContainerProvider .getWebSocketContainer(); try (Session session = container.connectToServer(ChatClient.class , URI.create("ws://localhost:8080/WebSocketChat/chat"))) { String eingabe= ""; while (!eingabe.toLowerCase().equals("bye")){ System.out.print("Beitrag: "); eingabe = new Scanner(System.in).nextLine(); session.getBasicRemote().sendText(eingabe); } session.close(new CloseReason(CloseReason.CloseCodes .NORMAL_CLOSURE, "Schicht")); } catch (Exception e) { e.printStackTrace(); } } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 723 2. Fallstudie (6/7): Ausgabe (Eingaben markiert) Beitrag: verbunden Beitrag: verbunden Wer ist da @Client empfangen: Beitrag: @Client Wer ist da empfangen: Wer ist da ich @Client empfangen: Beitrag: @Client ich empfangen: ich @Client empfangen: @Client empfangen: ich auch ich auch @Client empfangen: @Client empfangen: bye bye @Client empfangen: bye bye @Client empfangen: bye bye @Client empfangen: abgemeldet bye abgemeldet Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker Beitrag: verbunden @Client empfangen: Wer ist da @Client empfangen: ich ich auch Beitrag: @Client empfangen: ich auch bye @Client empfangen: bye abgemeldet 724 2. Fallstudie (7/7): Ausgabe Server INFO: INFO: INFO: INFO: INFO: INFO: Sende Sende Sende Sende Sende Sende Komponentenbasierte SoftwareEntwicklung Wer ich ich bye bye bye ist da an 3 Klienten an 3 Klienten auch an 3 Klienten an 3 Klienten an 2 Klienten an 1 Klienten Prof. Dr. Stephan Kleuker 725 Ein- und Auspacken • Zum Verschicken von Objekten werden sie in einfache Strings verwandelt • hier bietet sich wieder JSON an • für benötigte Klassen werden Encoder und Decoder geschrieben, die dem Client und Server bekannt gemacht werden • folgendes Beispiel: Austausch von Sprint-Informationen mit JavaScript-Client (sehr elementar gehalten) • Auch ByteStreams übertragbar Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 726 Sprint- Fallstudie (1/14): Sprints encoden 1/2 public class SprintsEncoder implements Encoder.TextStream<List<Sprint>> { was soll codiert werden @Override public void encode(List<Sprint> sprints, Writer writer) { JsonProvider provider = JsonProvider.provider(); JsonArrayBuilder elemente = Json.createArrayBuilder(); for (Sprint s : sprints) { elemente.add(jsonSprint(s, false)); // von REST bekannt } JsonObject js = Json.createObjectBuilder() .add("sprints", elemente).build(); try (JsonWriter jsonWriter = provider.createWriter(writer)) { jsonWriter.write(js); } Komponentenbasierte SoftwareProf. Dr. 727 } Entwicklung Stephan Kleuker Sprint- Fallstudie (2/14): Sprints encoden 2/2 @Override public void init(EndpointConfig config) { } @Override public void destroy() { } //leider Copy & Paste private JsonObject jsonSprint(Sprint s, boolean einzeln) { ... } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 728 Sprint- Fallstudie (3/14): Sprint decoden 1/2 was soll decodiert werden public class SprintDecoder implements Decoder.TextStream<Sprint> { private SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); @Override public Sprint decode(Reader reader){ JsonProvider provider = JsonProvider.provider(); JsonReader jsonReader = provider.createReader(reader); JsonObject js = jsonReader.readObject(); Sprint sprint = new Sprint(); sprint.setMotto(js.getString("motto")); try { sprint.setStarttermin(formatter .parse(js.getString("starttermin"))); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 729 Sprint- Fallstudie (4/14): Sprint decoden 2/2 sprint.setEndtermin(formatter .parse(js.getString("endtermin"))); } catch (ParseException ex) {} try { sprint.setGeplanterAufwand(js.getInt("geplanterAufwand")); } catch (Exception e){ sprint.setGeplanterAufwand(Integer .parseInt(js.getString("geplanterAufwand"))); } return sprint; } @Override public void init(EndpointConfig config) {} @Override public void destroy() {} } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 730 Sprint- Fallstudie (5/14): SprintServer 1/3 @ServerEndpoint(value="/socketsprint" , encoders={SprintsEncoder.class} , decoders={SprintDecoder.class}) public class SprintServer implements Serializable{ @Inject PersistenzService pers; private static Set<Session> partner = Collections .synchronizedSet(new HashSet<Session>()); @OnOpen public void onOpen(Session session, EndpointConfig cfg) { this.partner.add(session); sende(); // besser nur an einen } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 731 Sprint- Fallstudie (6/14): SprintServer 2/3 @OnMessage public void onMessage(Sprint sprint, Session session) throws IOException { try{ this.pers.persist(sprint); hier wird Decodierung genutzt sende(); } catch (Exception e){ // Benachrichtigung an den Client fehlt } } @OnClose public void onClose(Session session, CloseReason cR) { this.partner.remove(session); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 732 Sprint- Fallstudie (7/14): SprintServer 3/3 public void sende() { try { List<Session> geschlossen = new ArrayList<>(); List<Sprint> sprints = pers.findAllSprint(); for (Session s : this.partner) { if (!s.isOpen()) { System.err.println("Geschlossen: " + s.getId()); geschlossen.add(s); } else { s.getAsyncRemote().sendObject(sprints); } } this.partner.removeAll(geschlossen); } catch (Throwable e) { e.printStackTrace(); } Komponentenbasierte SoftwareProf. Dr. Stephan Kleuker } Entwicklung 733 Sprint- Fallstudie (8/14): JavaScript-Client 1/6 <!DOCTYPE html> <html> <head> <title>WebSocket Client für Sprints</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="js/ws.js"></script> </head> <body> <form name="felder"> <div id="eingabe"> Motto : <input type="text" id="motto"><br> Starttermin: <input type="text" id="starttermin"><br> Endtermin: <input type="text" id="endtermin"><br> geplanter Aufwand: <input type="text" id="geplanterAufwand"><br> </div> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 734 Sprint- Fallstudie (9/14): JavaScript-Client 2/6 <div id="button"> <input type="button" value="absenden" onClick="sende();"><br> <input type="button" value="beenden" onClick="ws.close();"> </div> <div id="sprints" style="background-color: white; margin:5px;"> </div> </form> </body> </html> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 735 Sprint- Fallstudie (10/14): JavaScript-Client 3/6 // Zuerst ueberpruefen, ob der Browser Websocket unterstuetzt // wird am Ende des Scripts gestartet if ("WebSocket" in window) { var ws = new WebSocket("ws://localhost:8080/Sprinter/socketsprint"); //wird bei erfolgreichem Verbindungsaufbau aufgerufen ws.onopen = function() { //alert("open"); }; // wird aufgerufen, wenn Server Daten schickt ws.onmessage = function(event) { // geht ohne eval var objJSON = eval("(function() {return " + event.data + ";})()"); document.getElementById("sprints") .innerHTML = table(objJSON.sprints); Komponentenbasierte SoftwareProf. Dr. 736 }; Entwicklung Stephan Kleuker Sprint- Fallstudie (11/14): JavaScript-Client 4/6 //wird aufgerufen, wenn Verbindung geschlossen wurde ws.onclose = function() { alert("Client beendet Verbindung"); }; // Fehlermeldung ws.onerror = function(error) { alert("Ein Fehler ist aufgetretten " + error); }; } else { alert("Dein Browser ist zu alt"); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 737 Sprint- Fallstudie (12/14): JavaScript-Client 5/6 function sende() { var sprint = { motto: document.getElementById("motto").value, starttermin: document.getElementById('starttermin').value, endtermin: document.getElementById('endtermin').value, geplanterAufwand: document.getElementById('geplanterAufwand').value }; //mit stringify zum JSON Objekt kodieren und abschicken ws.send('' + JSON.stringify(sprint)); //Eingabefelder leeren document.getElementById('motto').value = ''; document.getElementById('starttermin').value = ''; document.getElementById('endtermin').value = ''; document.getElementById('geplanterAufwand').value = ''; } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 738 Sprint- Fallstudie (13/14): JavaScript-Client 6/6 function table(data) { var erg = "<table border='1'>"; erg += "<tr><th>Id</th><th>Motto</th><th>Start</th>"; erg += "<th>Ende</th><th>Geplanter Aufwand</th></tr>"; for (var i = 0; i < data.length; i++) { erg += "<tr style='background-color:" + data[i].farbe + "'>"; erg += "<td>" + data[i].id + "</td>"; erg += "<td>" + data[i].motto + "</td>"; erg += "<td>" + data[i].starttermin + "</td>"; erg += "<td>" + data[i].endtermin + "</td>"; erg += "<td>" + data[i].geplanterAufwand + "</td>"; erg += "</tr>"; } erg += "</table>"; return erg; } Komponentenbasierte SoftwareProf. Dr. 739 Entwicklung Stephan Kleuker Sprint- Fallstudie (14/14): Beispielnutzung Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 740 nächste Schritte • Anmerkung: Die Seite ist so mit Code-Injection angreifbar • HTML-Client bekommt nicht mit, wenn in JSF-Applikation Sprint bearbeitet wird • verschiedene Schritte denkbar – Methode sende() von SprintServer wird aufgerufen, wenn ein Sprint-Objekt bearbeitet wird (z. B. in Persistieren einbauen) – Persistierung erzeugt Events, wenn Sprint-Objekte bearbeitet werden, SprintServer abonniert diese • nächste Folie; nur kleine Änderungen im SprintController von JSF (Änderungen durch REST-Client werden so nicht erkannt) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 741 Verknüpfung JSF mit WebSocket-Server @Named @SessionScoped public class SprintController implements Serializable { @Inject SprintServer server; ... public String uebernehmen() { // analog loeschen ... server.sende(); ... Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 742 weitere Überlegung Löschen und Bearbeiten prinzipiell kein Problem: • es wird aber ein erweitertes Protokoll benötigt • z. B. erste Eigenschaft gibt an, was gemacht werden soll • dann würde nicht Sprint-Klasse sondern Befehlsklasse zum Dekodieren im Server genutzt Weiterführend • Übertragung von Byte-Streams, z. B, zum Verschicken von Bildern Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 743 Bedeutung von JavaScript • Ursprünglich war JavaScript nur Hilfssprache, um kleine Berechnungen und Modifikationen im Browser zu ermöglichen • mit HTML 5 wurde JavaScript zur zentralen Sprache des Internets • Software-Engineering mit JavaScript steckt noch in den Kinderschuhen • keine Klassenbibliothek, keine Standard-Frameworks • eine unübersichtliche Menge sehr kreativer Lösungen • Beispiel Varianten vom MV*-Pattern • viele gute Werkzeuge und Hilfsmittel: JQuery, Jasmine, Istanbul, Karma, Selenium, … Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 744