Folien ab 17.12.15 - Hochschule Osnabrück

Werbung
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
Herunterladen