5. 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 476 Ausblick auf weitere Themen Browser 3 JSF Scope Bean Validation 1 RESTful WebService Web Sockets 2 CDI EJB JPA Datenbank Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 477 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 478 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 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 479 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 480 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 481 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 482 Ausschnitt Klassendiagramm Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 483 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. 484 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 485 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 486 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 487 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 488 Hintergrund: Service Oriented Architecture UDDI ServiceVerzeichnis WSDL ServiceNutzer 3. Anfragen SOAP 4. Antworten ServiceAnbieter HTTP Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 489 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 490 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 491 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 492 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 493 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 494 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 495 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 496 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 497 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 498 detaillierte Analyse @Path("/helloworld") • Gibt Aufrufpfad an, hier resources/helloworld • 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 499 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) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 500 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 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 501 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 502 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 503 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 • 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 504 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 505 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/resources/helloworld"); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 506 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$1 Language : null Location : null Mediatype : text/html 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 507 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$1 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 508 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 509 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 510 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 511 Kleine Beispiele (7/7) public void analyse5() { 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); } javax.ws.rs.client.Entity@49fa424c : [2,3] ok • Klasse Entity bietet einige Marshalling-Methoden Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 512 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 513 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 514 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 515 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/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 516 Beispiel: Umsetzung von Pfaden (4/4) {"fehler":"kein Kunde"} {"fehler":"kein Kunde"} {"summe":32000000} {"fehler":"kein Konto"} Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 517 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 518 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 519 Externer Service zur Analyse von IPs (2/4) ip: 93.196.192.46 country_code: DE country_name: Germany region_code: 07 region_name: Nordrhein-Westfalen city: Hopsten zipcode: latitude: 52.3833 longitude: 7.6167 metro_code: area_code: Komponentenbasierte SoftwareEntwicklung ip: 209.8.115.88 country_code: US country_name: United States region_code: TX region_name: Texas city: Dallas zipcode: latitude: 32.7831 longitude: -96.8067 metro_code: 623 area_code: 214 Prof. Dr. Stephan Kleuker 520 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 521 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 522 Ü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 523 Übergabe von Aufrufparametern (2/2) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 524 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 525 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 526 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 527 Nutzungsszenario • Links nicht ausimplementiert Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 528 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 mit id 42 mit id 42 existiert, dann aktualisieren, sonst Fehler Lösche den Sprint mit id 42 Hinweise: noch sauberer wäre /sprint/42 (Einzahl) graue Felder nicht realisiert Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 529 Einordnung SprintRestController (Server) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 530 Client (minimal) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker SprinterRESTClient 531 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 532 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 533 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 534 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 535 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 536 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 537 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 538 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 (Übung !), ü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 539 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 540 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 541 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 542 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 543 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 544 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 545 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 546 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 547 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 548 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 549 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 550 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 551 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 552 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 553 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 554 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 555 @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 556 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 557 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 558 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 559 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 560