Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Prof. Dr. Stephan Kleuker
549
6.11 Testen von Web-Applikationen - Selenium
• Web-Browser nutzen schwerpunktmäßig HTML zur Darstellung
• Capture & Replay-Werkzeuge, die hardgecoded Pixel und Klicks verarbeiten, eignen sich meist auch für diese Programme
• Einfaches Werkzeug für Web-Applikationen und Firefox ist Selenium IDE [hier nicht gezeigt] (http://seleniumhq.org/)
– erlaubt Capture & Replay von Nutzereingaben
– ermöglicht Tests von Elementen, Identifizierung über Idsaber auch Beschriftungen
– erlaubt den Export der aufgezeichneten Tests u. a. in JUnit (Variante: Browsersteuerung aus JUnit heraus [nächste Folien], ähnlich FEST)
– basiert auf JavaScript und Iframes
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
550
Hinweise zum Testen
• Erinnerung: White Box – Grey Box – Black Box
• Basisfunktionalität mit JUnit testen
• einige Funktionalität ohne Server testbar
• gibt einfache Server nur zur Testausführung
• Selenium WebDriver ermöglicht Test der (Web-)Oberfläche
• niemals alle Tests durch Oberflächentests ersetzen
• es gibt nie das ultimative Werkzeug; aber Familie von Werkzeugen hilft oft
• nur automatisieren, wenn sinnvoll
• Ziel: Continuous Integration
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
551
Selenium WebDriver
• Selenium steuert Browser von Java (C#, Python, Ruby) aus
• Installation als jar-Dateien
• flexible Möglichkeiten zum Finden von GUI-Komponenten
• ideal für Regressionstests, bei wenig sich ändernden GUIs
• in fast allen Unternehmen genutzt, die Web-Applikationen herstellen
• kontinuierliche Weiterentwicklung (nicht immer alles übertragbar, Selenium -> Selenium 2)
• Grundregel: nur automatisieren, was sinnvoll und machbar ist, Rest manuell
• http://docs.seleniumhq.org/docs/
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
552
Selenium WebDriver - Konfiguration
• unterstützt alle gängigen Browser
• bei neuen Browsern muss eventuell auf Aktualisierung von WebDriver gewartet werden
• Oft kleine zusätzliche Programme pro Browser benötigt
– Firefox: geckodriver (https://github.com/mozilla/geckodriver/releases) Proxy für Marionette
• über Nutzer-Profile nachdenken
• generell Browser-Version nutzen, der vor der genutzten Selenium-Version fertig war
• generell notwendig: eigene Testumgebung
• typisch: Testserver mit fester Konfiguration; nicht notwendigerweise neuestem Browser
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
553
Beispielprogramm (1/3)
• Spezifikation: Zu entwickeln ist eine Applikation mit der geheime Nachrichten an einen Server übergeben und dort wieder abgefragt werden können. Genauer gibt der Nutzer eine Nachricht zusammen mit zwei Codewörtern und der Anzahl, wie oft die Nachricht abgerufen werden kann, ein. Weiterhin kann ein Nutzer über die Eingabe zweier Codewörter an gespeicherte Nachrichten kommen. Ist die Codewortkombination nicht vorhanden, wird ein Standardtext ausgegeben.
• Realisierung: Glassfish, JSF (Nutzung des Application Scope)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
554
Beispielprogramm (2/3)
Server starten
Applikation starten
http://localhost:8080/SecretSafe/
Komponentenbasierte Software-Entwicklung
vergebene Ids:
main:verfassen
main:lesen
Prof. Dr. Stephan Kleuker
555
Beispielprogramm (3/3)Nachricht verfassen Nachricht lesen
Komponentenbasierte Software-Entwicklung
eingabe:c1
eingabe:c2
eingabe:geheim
eingabe:ab
eingabe:verschicken
abfrage:c1
abfrage:c2
abfrage:abfragen
antwort:Nachricht
Prof. Dr. Stephan Kleuker
556
Einblick in Nutzungsmöglichkeiten (1/14)public class SafeMoeglichkeitenTest {
private WebDriver driver;
private int linie = 0; // nur Ausgabespielerei
@BeforeClass
public static void setUpOnce() {
System.setProperty("webdriver.gecko.driver"
, "F:\\Programme\\geckodriver\\geckodriver.exe");
}
@Before
public void setUp() {
// DesiredCapabilities ieCapabilities =
// DesiredCapabilities.internetExplorer();
driver = new FirefoxDriver();
// driver = new HtmlUnitDriver();
// driver = new ChromeDriver();
// driver = new InternetExplorerDriver(ieCapabilities);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
557
Einblick in Nutzungsmöglichkeiten (2/14)
• Klasse WebDriver als zentrale Steuerungsmöglichkeit
• Erzeugt neue Browser-Instanz
• Browser muss auf dem System installiert sein, nutzt keine weiteren Einstellungen des aktuellen Nutzers (leeres Profil)
• werden kontinuierlich weiterentwickelt
• (früher reichte driver = new InternetExplorerDriver(); )
• bisheriges Angebot (unterschiedliche Qualität): HtmlUnitDriver(), FirefoxDriver(), InternetExplorerDriver(), ChromeDriver(),
• OperaDriver durch andere Entwickler, IPhoneDriver nur zusammen mit Apple-XCode-Umgebung, AndroidDriver mit Android-Entwicklungsunterstützung
• Hintergrund: Selenium lange Zeit nur mit Firefox nutzbar
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
558
Einblick in Nutzungsmöglichkeiten (3/14)
• Zentrale Hilfsklasse für GUI-Komponenten: WebElement• nur zur Veranschaulichung: Ausgabemöglichkeit
private void zeigeElemente(List<WebElement> liste){
System.out.println("----"+(++linie));
if (liste != null) {
for (WebElement w : liste) {
System.out.println(" " + w.getTagName()
+ "::" + w.getAttribute("type")
+ "::" + w.getAttribute("name")
+ "::" + w.getAttribute("value")
+ "::" + w.getText()
+ "::" + w.getLocation() + "::" +w.isEnabled());
}
}
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
559
Einblick in Nutzungsmöglichkeiten (4/14)
• Überblick über generierte HTML-Seite
• In Entwicklung sinnvolle Ids/Namen vergeben
• JSF: Ids eindeutig
• Zugriff auch ohne Ids machbar („drittes Input-Element“)Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
560
Einblick in Nutzungsmöglichkeiten (5/14)
@Test
public void testBeispielvarianten()
throws InterruptedException, IOException {
// Seite aufrufen
driver.get("http://localhost:8080/SecretSafe/");
List<WebElement> liste =
driver.findElements(By.tagName("input"));
zeigeElemente(liste);
----1
input::hidden::main::main::::(0, 0)::true
input::submit::main:verfassen::Nachricht verfassen::::(8, 129)::true
input::submit::main:lesen::Nachricht lesen::::(8, 153)::true
input::hidden::javax.faces.ViewState::2158484851038199978:-
1608245938470041174::::(0, 0)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
561
Einblick in Nutzungsmöglichkeiten (6/14)
List<WebElement> inp = driver.findElements(
By.xpath("//input"));
zeigeElemente(inp);
----2
input::hidden::main::main::::(0, 0)::true
input::submit::main:verfassen::Nachricht verfassen::::(8, 129)::true
input::submit::main:lesen::Nachricht lesen::::(8, 153)::true
input::hidden::javax.faces.ViewState::2158484851038199978:-
1608245938470041174::::(0, 0)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
562
Einblick in Nutzungsmöglichkeiten (7/14)
• Viele weitere LokalisierungsmöglichkeitenMethod Summary
static By className(java.lang.String className)
static By cssSelector(java.lang.String selector)
WebElement findElement(SearchContext context)
List<WebElement> findElements(SearchContext context)
static By id(java.lang.String id)
static By linkText(java.lang.String linkText)
static By name(java.lang.String name)
static By partialLinkText(java.lang.String linkText)
static By tagName(java.lang.String name)
static By xpath(java.lang.String xpathExpression)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
563
Einblick in Nutzungsmöglichkeiten (8/14)
• Steuerungsmöglichkeiten mit submit(), click(), weiteren Eingabemöglichkeiten
WebElement element =
driver.findElement(By.name("main:verfassen"));
System.out.println(element.getTagName()
+ "::" + element.getAttribute("type")
+ "::" + element.getAttribute("name")
+ "::" + element.getAttribute("value"));
element.click();
input::submit::main:verfassen::Nachricht verfassen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
564
Einblick in Nutzungsmöglichkeiten (9/14)(new WebDriverWait(driver, 10)).until(
new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.id("eingabe"));
}
});
System.out.println(driver.findElement(By.tagName("body")).getText());
Codewort 1:
Codewort 2:
geheime Nachricht:
wie oft abrufbar:
Zur Startseite
// Hilfsvariable für folgende Berechnung
List<WebElement> labels =
driver.findElements(By.tagName("input"));
Komponentenbasierte Software-Entwicklung
Warten bis Element erscheint
Prof. Dr. Stephan Kleuker
565
Einblick in Nutzungsmöglichkeiten (10/14)
• es besteht die Möglichkeit JavaScript direkt auszuführen
• Mächtige Möglichkeiten, z. B. um Skripte zu starten oder Seite zu analysieren
• hier mit Übergabe und Rückgabe
@SuppressWarnings("unchecked")
List<WebElement> inputs2 = (List<WebElement>)
((JavascriptExecutor)driver).executeScript(
"var lbls = arguments[0]; "
+ "var inputs = []; "
+ "for (var i=0; i < lbls.length; i++){"
+ " inputs.push(document.getElementById("
+ " lbls[i].getAttribute('name'))); "
+ "} "
+ "return inputs;„ , labels);
zeigeElemente(inputs2);
Komponentenbasierte Software-Entwicklung
Aufrufparameter
Parameter für Aufruf
Prof. Dr. Stephan Kleuker
566
Einblick in Nutzungsmöglichkeiten (11/14)
• Ausgabe zur letzten Folie
form::null::eingabe::null::Codewort 1:
Codewort 2:
geheime Nachricht:
wie oft abrufbar:
Zur Startseite::(8, 109)::true
input::text::eingabe:c1::::::(92, 109)::true
input::text::eingabe:c2::::::(92, 131)::true
input::text::eingabe:geheim::::::(138, 153)::true
input::text::eingabe:ab::0::::(120, 175)::true
input::submit::eingabe:verschicken::Verschicken::::(8,
197)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
567
Einblick in Nutzungsmöglichkeiten (12/14)Object[] werte = {"input", "text"};
@SuppressWarnings("unchecked")
List<WebElement> inputs3 = (List<WebElement>)
((JavascriptExecutor) driver).executeScript(
"var tmp = document.getElementsByTagName(arguments[0]); "
+ "var erg = []; "
+ "for (var i=0; i<tmp.length; i++){"
+ " if(tmp[i].type==arguments[1]){"
+ " erg.push(tmp[i])"
+ " }"
+ "}; "
+ "return erg;", werte);
input::text::eingabe:c1::::::(92, 109)::true
input::text::eingabe:c2::::::(92, 131)::true
input::text::eingabe:geheim::::::(138, 153)::true
input::text::eingabe:ab::0::::(120, 175)::true
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
568
Einblick in Nutzungsmöglichkeiten (13/14)
// Gibt Seitentitel auf Konsole aus
System.out.println("Titel der Seite ist: "
+ driver.getTitle());
// Bildschirmfoto
File screenshot = ((TakesScreenshot)driver)
.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(screenshot,
new File("bild"+new Date().getTime()+".png"));
Assert.assertTrue(driver.getTitle().contains("Pssst"));
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
569
Einblick in Nutzungsmöglichkeiten (14/14)
• nach mehren Testläufen
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
570
Projektaufbau
• zentral benötigte Bibliotheken im Selenium-Download
Komponentenbasierte Software-Entwicklung
(alle) benötigt; evtl.
testng weglassen
benötigt (aktuelle
Variante)
Prof. Dr. Stephan Kleuker
571
Weitere Funktionalität
• Wechsel zwischen Fenstern und zwischen Frames
• Möglichkeit vorwärts und zurück zu navigieren
• Nutzung von Cookies
• (Versuch der) Unterstützung von Drag und Drop
• Proxy-Nutzung
• Einstellung von Wartezeiten
• Warten auf das Erscheinen von HTML-Elementen (wichtig in Richtung AJAX und HTML5)
• Zusammenspiel mit Selenium IDE zur Testaufzeichnung
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
572
Achtung: Viele Einstiegsfallen
• generell gute Einarbeitungsmöglichkeit durch gute Dokumentation
• trotzdem viele kleine Fehlerquellen, die Entwicklungsprozess bremsen können
• Beispiel: Tests ziehen auf anderen Rechner um
• wichtiges Detail aus der Doku "The browser zoom level must be set to 100% so that the native mouse events can be set to the correct coordinates." (nicht erster Google-Treffer)
• teilweise weitere Browser-Einstellungen beachten
• Browser-Updates benötigen teilweise Selenium-Updates
• Fazit: Testrechner nie zu anderen Zwecken nutzen, Konfiguration sichern
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
573
Weitere Steuerungsvarianten
• Warten, bis Element vorhanden ist(new WebDriverWait(driver, 10)).until(
new ExpectedCondition<WebElement>(){
@Override
public WebElement apply(WebDriver d) {
return d.findElement( By.name("j_idt8:j_idt10"));
}});
• Steuerungsvariante mit JavaScriptWebElement but =
driver.findElement(By.name("j_idt8:j_idt10"));
((IJavaScriptExecutor)driver)
.executeScript("arguments[0].fireEvent('onclick');", but);
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
574
Test des Beispiels (1/7)
public class SecretSafeTest {
private WebDriver driver;
private static String text1;
private static String text2;
private File f = new File("C:\\Users\\x\\AppData\\"
+ "Roaming\\Mozilla\\Firefox\\Profiles\\zm12egmo.default");
private FirefoxProfile profile = new FirefoxProfile(f);
@BeforeClass
public static void setupClass() {
text1 = "" + Math.random(); // nicht ganz QS-sauber
text2 = "" + Math.random(); // zufaellige Texte
System.setProperty("webdriver.gecko.driver"
, "F:\\Programme\\geckodriver\\geckodriver.exe");
}
@Before
public void setUp() {
driver = new FirefoxDriver(profile);
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
575
Test des Beispiels (2/7)@After
public void tearDown() {
driver.quit(); // dauert, sonst nur am Ende aller Tests
}
// zur Erkennung, ob Seite bereits geladen ist
private void warteAufSeiteMitId(String id) {
(new WebDriverWait(driver, 10)).until(
new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.id(id));
}
});
}
private void startSeite(){
driver.get("http://localhost:8080/SecretSafe");
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
576
Test des Beispiels (3/7)
private void eingabeSeite(){
startSeite();
warteAufSeiteMitId("main");
driver.findElement(By.name("main:verfassen")).click();
warteAufSeiteMitId("eingabe");
}
private void ausgabeSeite(){
startSeite();
warteAufSeiteMitId("main");
driver.findElement(By.name("main:lesen")).click();
warteAufSeiteMitId("abfrage");
}
private void feldFuellen(String name, String wert){
driver.findElement(By.name(name)).clear();
driver.findElement(By.name(name)).sendKeys(wert);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
577
Test des Beispiels (4/7)private void textEingeben(String text1, String text2,
String geheim, int versuche){
eingabeSeite();
feldFuellen("eingabe:c1",text1);
feldFuellen("eingabe:c2",text2);
feldFuellen("eingabe:geheim",geheim);
feldFuellen("eingabe:ab",""+versuche);
driver.findElement(By.name("eingabe:verschicken")).click();
warteAufSeiteMitId("erfolg");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Eintrag erfolgreich"));
}
private void textEingeben(String geheim, int versuche){
textEingeben(this.text1, this.text2, geheim, versuche);
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
578
Test des Beispiels (5/7)
private void textErfolgreichSuchen(String text1,
String text2, String geheim){
ausgabeSeite();
feldFuellen("abfrage:c1",text1);
feldFuellen("abfrage:c2",text2);
driver.findElement(By.name("abfrage:abfragen")).click();
warteAufSeiteMitId("antwort");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains(geheim));
}
private void textErfolglosSuchen(String text1, String text2){
ausgabeSeite();
feldFuellen("abfrage:c1",text1);
feldFuellen("abfrage:c2",text2);
driver.findElement(By.name("abfrage:abfragen")).click();
warteAufSeiteMitId("antwort");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Treffen um 730 in KN2"));
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
579
Test des Beispiels (6/7)private void textErfolgreichSuchen(String geheim){
textErfolgreichSuchen(text1, text2, geheim);
}
private void textErfolglosSuchen(){
textErfolglosSuchen(text1, text2);
}
@Test
public void testErfolglos(){
textErfolglosSuchen();
}
@Test
public void testEintragenUndLesen(){
textEingeben("TextText", 3);
textErfolgreichSuchen("TextText");
textErfolgreichSuchen("TextText");
textErfolgreichSuchen("TextText");
textErfolglosSuchen();
}Komponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
580
Test des Beispiels (7/7)
@Test
public void testLinkLesen(){
ausgabeSeite();
driver.findElement(By.linkText("Zur Startseite")).click();
warteAufSeiteMitId("main");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Was wollen Sie machen?"));
}
@Test
public void testLinkSchreiben(){
eingabeSeite();
driver.findElement(By.linkText("Zur Startseite")).click();
warteAufSeiteMitId("main");
Assert.assertTrue(driver.findElement(By.tagName("body"))
.getText().contains("Was wollen Sie machen?"));
}
// weitere Tests sinnvoll
}Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
581
Testarchitektur
• Tests müssen für Änderbarkeit konzipiert werden
• häufig: viele Tests für eine konkrete Version geschrieben, nach leichten Änderungen der zu testenden Software werden Tests als unwartbar weggelegt
• Problem: ähnlich wie Software-Architektur wird Test-Architektur benötigt
• ein Ansatz: jeweils eigene Steuerungsklasse für eine Web-Seite, Tests arbeiten nur auf diesem Abstraktionslevel
• kleine Änderungen führen zu kleinen Änderungen in der Steuerungsklasse und keinen Änderungen bei deren Nutzern
• durch Abstraktion muss nicht jeder Tester Selenium können
• -> Tests müssen von qualifizierten Software-Entwicklern geschrieben werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
582Komponentenbasierte Software-Entwicklung
Design-Tests
• Browser stellen identische Inhalte leicht verändert da
• technisch überflüssig, aber wichtig für den Zeitgeist: modische Design-Anpassungen
• Für IT-Profi: Sisyphos-Arbeit; Test mit unterschiedlichen Browsern
• Direkte Hilfsmittel:
– Lunascape: ein Browser, bei dem man zwischen drei Maschinen umschalten kann IE (Trident)+Firefox (Gecko)+Chrome, Safari (Webkit)
– Windows: USB-portable Browser ohne Installationsnotwendigkeit (verändern keine Einstellungen): Firefox, Chrome, Opera, …
• evtl. auch Capture & Replay mit Selenium zum inhaltlichen Test
Prof. Dr. Stephan Kleuker
583Komponentenbasierte Software-Entwicklung
6.12 JSF-Erweiterungen
• graphische Möglichkeiten in JSF recht schlicht (wesentlich besser als gezeigt!), Erweiterungsbibliotheken sinnvoll
• wesentliche frei nutzbare Komponentenframeworks:– RichFaces: http://www.jboss.org/richfaces– Primefaces: http://www.primefaces.org/
• alternative JSF-Implementierung (Standard: Mojarra):– Apache MyFaces: http://myfaces.apache.org/
• kritisch: Frameworks nur teilweise kompatibel, gilt auch für Framework mit Implementierung
• kritisch: in Details teilweise deutliche Darstellungsunterschiede in verschiedenen Browsern
• Hinweis: hier nur Ideen und notwendige Konfiguration• NetBeans bietet Nachlademöglichkeit; hier nicht genutzt um
einfacher IDE wechseln zu können
Prof. Dr. Stephan Kleuker
584Komponentenbasierte Software-Entwicklung
Nutzung von PrimeFaces
• unterstützt JSF 2, Projekt ab November 2008
• Dokumentation war mal kostenpflichtig
• aktuell mit Support (Elite Downloads) und ohne (Community Downloads)
• Installation durch Einbinden einer einzelnen jar-Datei
• sehr viele Gestaltungsmöglichkeiten, mit Showcases gut dokumentiert
• Elemente haben oft sehr viele Parameter für Konfigurationsmöglichkeiten
• Ansatz: zu GUI-Element gehört Java-Objekt mit Konfigurationsmöglichkeiten in Java (set)
Prof. Dr. Stephan Kleuker
585Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (1/4) - Managed Bean
@Named
@SessionScoped
public class Text implements Serializable {
private String inhalt;
public String getInhalt() {
return inhalt;
}
public void setInhalt(String inhalt) {
this.inhalt = inhalt;
}
}
Prof. Dr. Stephan Kleuker
586Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (2/4) - index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Editor</title>
</h:head>
<h:body>
<h:form >
<p:editor id="ed" width="600px" height="120px"
value="#{text.inhalt}" widgetVar="editor"/>
<p:commandButton update="o1,o2" async="true"
value="p Uebernehmen" onclick="editor.saveHTML();"/><br/>
<h:commandButton value="h Uebernehmen"/><br/>
<h:outputText id="o1" escape="true"
value="Inhalt: #{text.inhalt}"/><br/>
<h:outputText id="o2" escape="false"
value="Inhalt: #{text.inhalt}"/>
</h:form>
</h:body>
</html>
Prof. Dr. Stephan Kleuker
587Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (3/4) - h: (Seite lädt neu)
Prof. Dr. Stephan Kleuker
588Komponentenbasierte Software-Entwicklung
Beispiel: Editorspielerei (4/4) - p: (Aktualisierung)
Prof. Dr. Stephan Kleuker
589Komponentenbasierte Software-Entwicklung
Anmerkungen zum Beispiel
• Normalerweise unterstützt p:commandButton Ajax direkt (nur bei p:editor Problem, deshalb Workaround mit widgetVar)
• <h:head> immer benötigt
• Generell: Genaue Analyse der oft sehr vielen Attribute einer Komponente notwendig
• Mischung von h: und p: oft (nicht immer) möglich
• Man muss mit Eigenarten der Komponenten leben (bei Großprojekten und Auswahlentscheidungen schwierig)
• detaillierte Dokumentation als PDF-Download http://www.primefaces.org/documentation
Prof. Dr. Stephan Kleuker
590
Versionsänderung - Beispiel
• Versionswechsel 2.0.2 zu 2.2.1 führt zu anderer Darstellung• Ungleichheit p: und h: korrigiert• andere Attribute statt height="120px" nun height="120"
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
591Komponentenbasierte Software-Entwicklung
JSF - es geht noch weiter
• JSF Expression Language– einfacher Zugriff auf Maps, die Umgebungsdaten, wie
param, requests, cookies und initParam haben– Nutzung von flash-Variable als Zwischenspeicher
• Erweiterung der Expression Language • Möglichkeit GET zu nutzen (und damit Bookmarks)
– <h:button> und <h:link>• JSF hat klare Regeln wie Ressourcen verwaltet werden
(Bilder, Templates, ...) • integrierte, nutzbare JavaScript-API• Viele weitere JavaScript-Möglichkeiten (u. a. h:outputScript)• weitere Möglichkeiten des Event-Handlings, z. B. eigene
Klasse reagiert auf Event (z. B. Phasenwechsel)• Ergänzung/Erweiterung des Exception Handlings• Lokalisierung, Mehrsprachlichkeit
Prof. Dr. Stephan Kleuker
592
7. 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
593
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 zurErstellung von Web-Applikationen)
• generell nichts Schlechtes daran!
• kritisch, Objekte müssen sich genau kennenKomponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
594
Ausblick auf weitere Themen
Komponentenbasierte Software-Entwicklung
Browser
Datenbank
JPA
EJBBean
Validation
CDIScope
JSFRESTful
WebService
Web Sockets
21
3
Prof. Dr. Stephan Kleuker
595
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 set-Methoden oder über Konstruktoren
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
596
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
597
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
598
Ziele CDI
• Entkopplung von Objekten voneinander
– @Inject bestes Beispiel, es wird nur ein passendes Objekt „irgendwoher“ benötigt; dies besorgt CDI-Realisierung
• Vereinfachte Objekt-Kommunikation
– Beispiel: Informationen abonnieren (bekannt als PublishSubscribe 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
599
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/en-US/html/intro.html
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
600
NetBeans: kein Deploy on Save
gerade bei CDI ist ein vollständiges Clean & Build mit Deployfast immer sinnvoll, da Server sonst Probleme z. B. mit noch laufenden Sessions hat (Haken wegnehmen)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
601
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-discovery-mode="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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
602
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
603
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
604
Event Model Übersicht (müssen nicht EJBs sein)
Komponentenbasierte Software-Entwicklung
class MeinEvent
beliebige POJO-Klasse
@Stateless
public class PersistenceService {
@Inject @Info Event<MeinEvent> event
…
MeinEvent me = …
event.fire(me);
Observable
@Qualifier
…
public @interface Info{}
Qualifier
@Stateless
public class EventConsumer {
public void empfangeMeinEvent(
@Observes @Info MeinEvent event)
{…
Observer
Prof. Dr. Stephan Kleuker
605
Definition des Event-Objekts
• POJO mit Inhalten, die übertragen werden sollenpackage 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
606
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
607
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
608
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
609
Beispiel: neue Mitarbeit persistieren
INFO: Mitarbeit [id=0, version=0, geplanteStunden=8,
verbrauchteStunden=0, fertigstellungsgrad=0]
INFO: class entity.Mitarbeit
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
610
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
611
Konkretisierung Übersicht
Komponentenbasierte Software-Entwicklung
interface Ausgabe {…
Angabe gewünschter Variante
@LogQualifier
public class AusgabeLog implements Ausgabe {…
@Qualifier
…
public @interface SystemQualifier{}
Qualifier
@Stateless
public class EventConsumer {
@Inject @LogQualifier Ausgabe aus;
@Qualifier
…
public @interface LogQualifier{}
Qualifier
@SystemQualifier
public class AusgabeSys implements Ausgabe {…
Interface
Realisierung
Realisierung
Prof. Dr. Stephan Kleuker
612
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
613
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
614
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 werdenKomponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
615
Beispiel (4/4): Nutzung
INFO: AusgabeLog: (0) CDI einbauen
INFO: class entity.Sprint
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
616
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
617
Produzenten
• bisher wurde bei @Inject nach passenden Klassen gesucht und ein Objekt per Default-Konstruktor erzeugt
• Objekt-Erzeugung kann aber auch durch mit @Producesannotierte 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
618
Statische Produzenten Übersicht (public vergessen)
Komponentenbasierte Software-Entwicklung
Produktions-varianten
@Qualifier
…
@interface Info{}
Qualifier
class Produzent {
@Produces @Meldung
private String meldung;
@Produces @Starttext
public String getLogtext() {…
@Produces @Starttext @Info
public String getLogtext2() {…
@Qualifier
…
@interface Meldung{}
Qualifier
class AusgabeLog {…
@Inject @Starttext @Info
private String start;
@Inject @Meldung
private String text;
…
Beispielnutzungen
@Qualifier
…
@interface Starttext{}
Qualifier
Prof. Dr. Stephan Kleuker
619
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
620
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
621
Beispiel (3/3): Beispielnutzung
INFO: getLogtext2
INFO: Hai 2: logtext: (0) Qualifier überlegen
INFO: class entity.BacklogElement
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
622
Dynamische Produzenten Übersicht
Komponentenbasierte Software-Entwicklung
Produktions-methode
Qualifier
public class Produzent{
@Produces @Aktuell
public String mach(){…
@Qualifier
…
public @interface Aktuell{}
public class AusgabeLog {
@Inject @Aktuell
Instance<String> datum;
…
public void ausgeben(…) {
…
datum.get()});
…
Beispielnutzung
Prof. Dr. Stephan Kleuker
623
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
624
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
625
Dynamische Produktion (3/3): Beispielnutzung
INFO: AusgabeLog: (14) Fernando Alonso - Wed May 21 20:15:42 CEST 2014
INFO: AusgabeLog: (7) Kimi Räikkönen - Wed May 21 20:14:24 CEST 2014
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
626
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
627
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
628
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
629
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
630
Nutzung der Alternative (3/3): Nutzung
INFO: LogMock: (0) Alternativen realisieren
INFO: class entity.BacklogElement
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
631
Interceptor Übersicht
Komponentenbasierte Software-Entwicklung
unterbreche alle Methoden von
Interceptor-Benennung
@Stateless
@InterceptQualifier
public class BspKlasse {
public void meth1(…) {…
public void meth2(…) {…
…
@Inherited
@InterceptorBinding
…
public @interface InterceptQualifier{}
@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();
} …
bei Unterbrechung zu nutzen
<interceptors>
<class>cdi.MeinInterceptor
</class>
</interceptors>
Prof. Dr. Stephan Kleuker
632
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
633
Nutzung von Interception (1/5): Annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
//@Qualifier
@Inherited
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface InterceptQualifier{}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
634
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
635
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
636
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
637
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
638
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
639
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 @RequestScopedKomponentenbasierte Software-
Entwicklung
Prof. Dr. Stephan Kleuker
640
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
641
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
642
8. (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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
643
Ausblick auf weitere Themen
Komponentenbasierte Software-Entwicklung
Browser
Datenbank
JPA
EJBBean
Validation
CDIScope
JSFRESTful
WebService
Web Sockets
21
3
Prof. Dr. Stephan Kleuker
644
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
645
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
646
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
647
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");
for (JsonValue freund : freunde) {
System.out.println(freund + " * " + freund.getValueType());
}
}
Name : Tony Stark
Alter : 42
Firmenname : Stark Industries
Umsatz : 20
Steve Rogers * STRING
Bruce Banner * STRING
42 * NUMBER
Default, wenn nicht da
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
648
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()
.add("name", "Shield")
.add("ort", "unbekannt")
.build())
.add("freunde",
Json.createArrayBuilder()
.add("James Howlett")
.add("Ben Grimm")
.build())
.build();
System.out.println("Object: " + personObject);
}
Object:
{"name":"Bruce
Banner","alter":44,"f
irma":{"name":"Shield
","ort":"unbekannt"},
"freunde":["James
Howlett","Ben
Grimm"]}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
649
Ausschnitt Klassendiagramm
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
650
Beispiel: Stream-Bearbeitung von JSON
// daten: siehe JSON lesen
JsonParser parser = Json
.createParser(new StringReader(daten));
while (parser.hasNext()) {
Event event = parser.next();
System.out.print(event + ": ");
switch (event) {
case KEY_NAME:
System.out.print(parser.getString());
break;
case VALUE_NUMBER:
System.out.print(parser.getInt());
break;
}
System.out.println("");
}
START_OBJECT:
KEY_NAME: name
VALUE_STRING:
KEY_NAME: alter
VALUE_NUMBER: 42
KEY_NAME: firma
START_OBJECT:
KEY_NAME: name
VALUE_STRING:
KEY_NAME: ort
VALUE_STRING:
END_OBJECT:
KEY_NAME: freunde
START_ARRAY:
VALUE_STRING:
VALUE_STRING:
VALUE_NUMBER: 42
END_ARRAY:
END_OBJECT:Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
651
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
652
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
653
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
654
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
655
Hintergrund: Service Oriented Architecture
Service-
Verzeichnis
Service-
Anbieter
Service-
Nutzer
3. Anfragen
4. Antworten
SOAP
WSDL
HTTP
UDDI
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
656
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
657
RESTful (Representational State Transfer)
• Idee von der Interaktion zwischen Rechnern bleibt
• REST ist ein Architekturstil für verteilte Hypermedia-Systeme
• 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
658
Woher kommt „ Representational State Transfer“
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.
ResourceClient
http://www.scrumsprinter.de/sprint/42
{ “id”: 42,
“name”: “Prototyp”,
“elemente”: [ …
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
659
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
660
Wer nutzt es (Beispiele)?
• Hinweis: Öfter wird gegen die geforderte Reinform von RESTful WebServices verstoßen, und normale Anfragemöglichkeit mit GET als RESTful oder REST-basiert bezeichnet
• Google Maps
• Google AJAX Search API
• Yahoo Search API
• Amazon WebServices
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
661
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
662
JAX-RS aktivieren
• in JEE-aware Servern reicht theoretisch folgendes ausimport 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
663
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
664
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
665
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
666
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
667
Detaillierte Analyse mit cURL
• generell jedes Programm zur Erzeugung von HTTP-Aufrufen und Analyse der Ergebnisse geeignet
• Kommando-Zeile mit cURLhttp://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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
668
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
669
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
670
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
671
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
672
Kleine Beispiele (1/7)
• Anmerkung: Zeigt Service-Nutzung, zeigt nichts von RESTpublic class ClientAnalyse {
private Client client;
private WebTarget userTarget;
public ClientAnalyse() {
Client client = ClientBuilder.newClient();
userTarget = client
.target("http://localhost:8080/vlRESTAnfang"
+ "/resources/helloworld");
}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
673
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
674
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
675
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
676
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
677
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
678
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
679
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
680
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
681
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
682
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
683
Beispiel: Umsetzung von Pfaden (4/4)
{"fehler":"kein Kunde"}
{"fehler":"kein Kunde"}
{"summe":32000000}
{"fehler":"kein Konto"}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
684
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 E-Mail mit so einem Link erhält
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
685
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
686
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
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
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
687
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
688
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":"Nordrhein-
Westfalen","city":"Hopsten","zipcode":"","latitude":52.3833,"longitu
de":7.6167,"metro_code":"","area_code":""}
class java.io.ByteArrayInputStream
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
689
Ü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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
690
Übergabe von Aufrufparametern (2/2)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
691
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
692
Dienstnutzung mit Aufrufparametern (2/2)
{"results":[{"address_components":[{"long_name":"Quakenbrück","
short_name":"Quakenbrück","types":["locality","political"]},{"l
ong_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}}},"place_id":"ChIJqfqve3Zpt0cRIqf
jZXu8LGw","types":["locality","political"]}],"status":"OK"
{"lat":52.675599,"lng":7.950777699999999}
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
693
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
694
Nutzungsszenario
• Links nicht ausimplementiert
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
695
Architektur: hierarchischer Aufbau
Resource POST(CREATE)
GET(READ)
PUT(UPDATE)
DELETE(DELETE)
/sprints erzeugt neuen Sprint
Übersicht über alle Sprints
Aktualisiere alle Sprints (oder weglassen)
alle Sprints löschen
/sprints/42 Fehler! Zeige Sprint mit id 42
wenn Sprint mit id 42 existiert, dann aktualisieren, (sonst Fehler ?)
Lösche den Sprint mit id42
Hinweise: noch sauberer wäre /sprint/42 (Einzahl)graue Felder nicht realisiert
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
696
Einordnung SprintRestController (Server)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
697
Client (minimal)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
698
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
699
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
700
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
701
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
702
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
703
Client – Vorbereitung (2/2)
• eine Variable pro Eigenschaft mit get und setenum 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
704
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
705
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
706
Erfolgloses Löschen möglich
• vom anderen Nutzer gelöscht oder modifiziert
• Idempotent wäre, diesen Fehler zu ignorieren (ist gelöscht)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
707
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
708
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
709
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
710
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
711
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
712
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
713
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
714
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
715
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
716
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
717
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
718
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: in getText
INFO: http://localhost:8080/resources/ana
INFO: /ana
INFO: http://localhost:8080/resources/ana?x=Hai&text=42
INFO: text: [42]
INFO: x: [Hai]
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
719
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
720
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
721
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>
<representation mediaType="application/json"/>
</response>
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
722
@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>
</form>
@Path("/mitglieder")
@Consumes(Mediatype.APPLICATION_FORM_URLENCODED)
public class CustomerResource {
@POST
public void createCustomer(
@FormParam(“vorname") String vorname
, @FormParam(“nachname") String nachname) {
...
}
ermöglicht die Übernahme von Parametern einer POST-Anfrage eines HTML-Formulars
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
723
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
724
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, generelltype/subtype;name=value;name=value...
@Consumes("application/xml;charset=utf-8")
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
725
Weiterführend (2/2)
• JAX-RS-Annotationen können auch nur in Interfaces ausgelagert werden
• Matrix-Parameter (Attribute) behandelbarhttp://beispiel.spieler.de/vfl;typ=Sturm/2015
• Nutzung von Header-Parametern @HeaderParampublic 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
726
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/jaxrs20-1929352.html
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
727
9. 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 Crossplattform-Applikation auf Basis von Websockets, Hochschule Osnabrück, Bachelorarbeit, 2014
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
728
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
729
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 IE-Browser 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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
730
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
731
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
732
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
733
zentrale Nachrichten
Server Client
onOpen Verbindungsaufbau onOpen
onMessage senden und empfangen onMessage
onClose Verbindungsabbau onClose
onError Fehlerfall onError
eigentliches Protokoll, was in welcher Form ausgetauscht wird, muss von Entwicklern festgelegt werden
Begriff „Socket“ kann ernst genommen werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
734
Erstes Beispiel (1/7): Gewünscht
Client Server
connectToServer
@OnOpen (Bestätigung)
send(Hello) @OnMessage
@OnMessage send(Hallo Client)
send(Hello again) @OnMessage
@OnMessage send(Hallo Client)
(schließen) @OnClose
@OnClose (Bestätigung)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
735
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
736
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
737
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
738
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
739
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
740
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: @Server Anfrage URI: /vlWebSocketEcho/echo
INFO: @Server Nachricht: Hello
INFO: @Server Nachricht: Hello again
INFO: @Server CloseReason: CloseReason[1000,Schicht]
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
741
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
742
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
743
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
744
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
745
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
746
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
747
2. Fallstudie (6/7): Ausgabe (Eingaben markiert)
Beitrag: verbunden
Wer ist da
Beitrag: @Client
empfangen: Wer ist da
@Client empfangen:
ich
@Client empfangen:
ich auch
@Client empfangen:
bye
@Client empfangen:
bye
bye
@Client empfangen:
bye
abgemeldet
Beitrag: verbunden
@Client empfangen:
Wer ist da
ich
Beitrag: @Client
empfangen: ich
@Client empfangen:
ich auch
@Client empfangen:
bye
bye
@Client empfangen:
bye
abgemeldet
Beitrag: verbunden
@Client empfangen:
Wer ist da
@Client empfangen:
ich
ich auch
Beitrag: @Client
empfangen: ich auch
bye
@Client empfangen:
bye
abgemeldet
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
748
2. Fallstudie (7/7): Ausgabe Server
INFO: Sende Wer ist da an 3 Klienten
INFO: Sende ich an 3 Klienten
INFO: Sende ich auch an 3 Klienten
INFO: Sende bye an 3 Klienten
INFO: Sende bye an 2 Klienten
INFO: Sende bye an 1 Klienten
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
749
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
750
Sprint- Fallstudie (1/14): Sprints encoden 1/2
public class SprintsEncoder
implements Encoder.TextStream<List<Sprint>> {
@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);
}
}
was soll codiert werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
751
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
752
Sprint- Fallstudie (3/14): Sprint decoden 1/2
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")));
was soll decodiert werden
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
753
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
754
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
755
Sprint- Fallstudie (6/14): SprintServer 2/3
@OnMessage
public void onMessage(Sprint sprint, Session session)
throws IOException {
try{
this.pers.persist(sprint);
sende();
} catch (Exception e){
// Benachrichtigung an den Client fehlt
}
}
@OnClose
public void onClose(Session session, CloseReason cR) {
this.partner.remove(session);
}
hier wird Decodierung genutzt
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
756
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
757
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
758
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
759
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
760
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
761
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
762
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 Software-
Entwicklung
Prof. Dr. Stephan Kleuker
763
Sprint- Fallstudie (14/14): Beispielnutzung
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
764
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 SprintControllervon JSF (Änderungen durch REST-Client werden so nicht erkannt)
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
765
Verknüpfung JSF mit WebSocket-Server
@Named
@SessionScoped
public class SprintController implements Serializable {
@Inject
SprintServer server;
...
public String uebernehmen() { // analog loeschen
...
server.sende();
...
Komponentenbasierte Software-Entwicklung
Prof. Dr. Stephan Kleuker
766
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 Software-Entwicklung
Prof. Dr. Stephan Kleuker
767
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 Software-Entwicklung