Commit 47ebc80e authored by Jens Ehlers's avatar Jens Ehlers
Browse files

#1 Added several line breaks in listings for PDF print view

parent cbd51a1b
Pipeline #7675 passed with stage
in 53 seconds
......@@ -155,7 +155,7 @@ class Board {
return cells[row][0].value == player && cells[row][1].value == player && cells[row][2].value == player // 3 in a row
|| cells[0][col].value == player && cells[1][col].value == player && cells[2][col].value == player // 3 in a col
|| cells[0][0].value == player && cells[1][1].value == player && cells[2][2].value == player // 3 in a diagonal
|| cells[0][2].value == player && cells[1][1].value == player && cells[2][0].value == player; // 3 in other diagonal
|| cells[0][2].value == player && cells[1][1].value == player && cells[2][0].value == player; // other diagonal
}
}</code></pre>
</div>
......@@ -393,7 +393,7 @@ class Board {
return cells[row][0].value == player && cells[row][1].value == player && cells[row][2].value == player // 3 in a row
|| cells[0][col].value == player && cells[1][col].value == player && cells[2][col].value == player // 3 in a col
|| cells[0][0].value == player && cells[1][1].value == player && cells[2][2].value == player // 3 in a diagonal
|| cells[0][2].value == player && cells[1][1].value == player && cells[2][0].value == player; // 3 in other diagonal
|| cells[0][2].value == player && cells[1][1].value == player && cells[2][0].value == player; // other diagonal
}
}</code></pre>
</div>
......
......@@ -58,7 +58,7 @@ class MyCompletableFuture {
CompletableFuture&lt;Integer> f = CompletableFuture.supplyAsync(supplier, exec); // start execution of task A
System.out.println(f.isDone()); // prints 'false'
CompletableFuture&lt;Integer> f2 = f.thenApplyAsync(plusOne, exec); // start task B when task A is done, i.e. chaining of tasks
CompletableFuture&lt;Integer> f2 = f.thenApplyAsync(plusOne, exec); // start task B when task A is done >> chaining of tasks
System.out.println(f2.get()); // waits until both tasks are done, then prints '2'
exec.shutdown();
......@@ -140,6 +140,6 @@ Diese Teilaufgaben können in der Fork-Phase rekursiv in noch kleinere Teilaufga
<p>In 2004 prägte eine wissenschaftliche Veröffentlichung von Google Research maßgeblich den Begriff <a href="https://ai.google/research/pubs/pub62">MapReduce</a>, dem wir insbesondere im Umfeld der Massendatenverarbeitung in verteilten Systemen (z.B. mittels <a href="https://hadoop.apache.org/">Hadoop</a> und <a href="https://spark.apache.org/">Spark</a>) und allgemein in vielen APIs zur funktionalen Programmierung begegnen. MapReduce beschreibt ein Programmiermodell, das ebenfalls auf dem Divide & Conquer-Prinzip aufbaut, und daher dem Fork-Join-Framework sehr ähnlich ist, wobei die <i>Map</i>-Phase der Fork-Phase entspricht und die <i>Reduce</i>-Phase der Join-Phase. Die Ausführung der parallelen Threads ist bei MapReduce-Anwendungen aber i.d.R. nicht auf eine Maschine beschränkt, sondern verteilt sich auf ein ganzes Cluster von Maschinen.</p>
<h4>Reaktive Streams</h4>
<h4 class="page-break">Reaktive Streams</h4>
<p>In Java sind reaktive Streams seit Java 9 (2017) durch die sogenannte <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html">Flow API</a> standardisiert. Es werden hier insbesondere die Interfaces <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.Publisher.html"><code>Publisher</code></a> und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.Subscriber.html"><code>Subscriber</code></a> bereitgestellt, über die eine asynchrone Stream-Verarbeitung mit nicht-blockierenden Gegendruck (engl. <i>Backpressure</i>) realisiert werden kann. Eine Datenquelle (<i>Publisher</i>) informiert seine Beobachter (<i>Subscriber</i>) über neue Nachrichten bzw. Zustandsänderungen, wobei sämtliche Publisher und Subscriber in nebenläufigen Threads ausgeführt werden. Der Gegendruck entsteht für einen Subscriber dadurch, dass er auch bei hoher Frequenz neu eingehender Nachrichten vom Publisher, diese verarbeiten können muss. Der max. mögliche Durchsatz zur Nachrichtenverarbeitung auf der Seite des Subscriber muss bestimmt werden und schränkt ggf. ein, wie viele eingehende Nachrichten aus den beobachteten Datenquellen akzeptiert werden können. Ein Code-Beispiel für die Verwendung des <code>Publisher</code>- und <code>Subscriber</code>-Interface ist bereits am Ende des Kapitels zum <a href="#unit-observer" class="navigate">Entwurfsmuster Beobachter</a> dargestellt worden. Bemerkenswert ist, dass die Flow API einen Konsens darstellt, der im Rahmen der Initiative <a href="http://www.reactive-streams.org/">Reactive Streams for the JVM</a> entstanden ist. Die Interfaces der Flow API (<code>Publisher</code>, <code>Subscriber</code>, <code>Subscription</code> und <code>Processor</code>) werden von verschiedenen beliebten Stream-Bibliotheken wie <a href="https://github.com/ReactiveX/RxJava/wiki/Reactive-Streams">RxJava</a>, <a href="https://akka.io/docs/">Akka</a> und <a href="http://vertx.io/">Vert.x</a> implementiert. Dadurch können Datenströme, die auf Basis dieser Bibliotheken entwickelt worden sind, unter Aufrechterhaltung des Gegendrucks miteinander verbunden werden.</p>
\ No newline at end of file
......@@ -332,10 +332,10 @@ class BoundedBufferSync implements BoundedBuffer {
<p>Der wechselseitige Ausschluss der Threads beim Einfahren und Ausfahren der Parkgarage ist sicherzustellen, d.h. die Methoden <code>enter</code> und <code>leave</code> der gemeinsamen Ressource <code>ParkingGarage</code> müssen in der jeweiligen Implementierung des Interface mittels <code>synchronized</code> als kritische Abschnitte gekennzeichnet werden (siehe unten Zeilen 11+25 in der Klasse <code>UnfairParkingGarage</code>). Im folgenden Code-Beispiel werden 2 alternative Implementierungen des Interface <code>ParkingGarage</code> dargestellt. In der Implementierung <code>UnfairParkingGarage</code> benachrichtigt ein Auto, das die Parkgarage verlässt, zufällig eines der wartenden Autos vor der Schranke und ermöglicht ihm das Einfahren in die Parkgarage. Dies geschieht durch den Aufruf der Methode <code>notify</code> in Zeile 28. Wenn die nummerierte Ankunft an der Schranke vor der Parkgarage nicht der ebenfalls nummerierten Einfahrt in die Parkgarage entspricht – also das FIFO-Prinzip verletzt ist, wird dies durch eine Ausgabe über <code>System.err</code> in Zeile 20 kenntlich gemacht. Dieser Fall wird bei der Ausführung des Programms relativ selten auftreten, kann aber beobachtet werden.</p>
<span>Die Implementierung <code>FairParkingGarage</code> unterscheidet sich leicht, um die Fairness beim Warten abzubilden – insbesondere in den Zeilen 16 und 31.</span>
<span>Die Implementierung <code>FairParkingGarage</code> unterscheidet sich leicht, um die Fairness beim Warten abzubilden – insbesondere in den Zeilen 16 und 32.</span>
<ul>
<li>In Zeile 31 werden mittels der Methode <code>notifyAll</code> alle der wartenden Autos statt nur ein zufälliges benachrichtigt.</li>
<li>In Zeile 16 wird die Bedingung für das Warten erweitert. Es muss nicht nur gewartet werden, wenn keine Stellplätze frei sind (<code>places == 0</code>), sondern auch wenn das Auto noch nicht an vorderster Position der Warteschlange angelangt ist. Es wird dazu gezählt, wie viele Autos bereits an der Schranke vor der Parkgarage angekommen sind (<code>arrivalCount</code>, Zeile 12) und wie viele Autos bereits in die Parkgarage eingefahren sind (<code>entranceCount</code>, Zeile 21). Die Wertzuweisung in die Variable <code>arrival</code> (Zeile 12) entspricht daher der Vergabe einer laufend inkrementierten Wartenummer – ähnlich des Wartebereichs in vielen Ämtern in Deutschland. Ein vor der Schranke wartendes Auto wird zwar jedes Mal erweckt, wenn ein anderes Auto die Parkgarage verlässt, kann aber nur vorrücken und nicht einfahren (<code>arrival - 1 != entranceCount</code>, Zeile 16), bis alle übrigen wartenden Autos mit niedrigerer Wartenummer eingefahren sind. Auf diese Weise wird die Fairness beim Zugriff auf die gemeinsame Ressource eingehalten.</li>
<li>In Zeile 32 werden mittels der Methode <code>notifyAll</code> alle der wartenden Autos statt nur ein zufälliges benachrichtigt.</li>
<li>In Zeile 16 wird die Bedingung für das Warten erweitert. Es muss nicht nur gewartet werden, wenn keine Stellplätze frei sind (<code>places == 0</code>), sondern auch wenn das Auto noch nicht an vorderster Position der Warteschlange angelangt ist. Es wird dazu gezählt, wie viele Autos bereits an der Schranke vor der Parkgarage angekommen sind (<code>arrivalCount</code>, Zeile 12) und wie viele Autos bereits in die Parkgarage eingefahren sind (<code>entranceCount</code>, Zeile 22). Die Wertzuweisung in die Variable <code>arrival</code> (Zeile 12) entspricht daher der Vergabe einer laufend inkrementierten Wartenummer – ähnlich des Wartebereichs in vielen Ämtern in Deutschland. Ein vor der Schranke wartendes Auto wird zwar jedes Mal erweckt, wenn ein anderes Auto die Parkgarage verlässt, kann aber nur vorrücken und nicht einfahren (<code>arrival - 1 != entranceCount</code>, Zeile 16), bis alle übrigen wartenden Autos mit niedrigerer Wartenummer eingefahren sind. Auf diese Weise wird die Fairness beim Zugriff auf die gemeinsame Ressource eingehalten.</li>
</ul>
<ul class="nav nav-tabs" id="sync-fairness2-tabs" role="tablist">
......@@ -363,7 +363,8 @@ class BoundedBufferSync implements BoundedBuffer {
} catch (InterruptedException e) { }
}
int entrance = ++entranceCount;
log(car.getName() + " enters parking garage [arrival = " + arrival + ", entrance = " + entrance + "].", arrival == entrance ? System.out : System.err);
log(car.getName() + " enters parking garage [arrival = " + arrival + ", entrance = " + entrance + "].",
arrival == entrance ? System.out : System.err);
places--;
}
......@@ -401,7 +402,8 @@ class BoundedBufferSync implements BoundedBuffer {
} catch (InterruptedException e) { }
}
int entrance = ++entranceCount;
log(car.getName() + " enters parking garage [arrival = " + arrival + ", entrance = " + entrance + "].", arrival == entrance ? System.out : System.err);
log(car.getName() + " enters parking garage [arrival = " + arrival + ", entrance = " + entrance + "].",
arrival == entrance ? System.out : System.err);
places--;
notifyAll();
}
......@@ -420,7 +422,7 @@ class BoundedBufferSync implements BoundedBuffer {
</div>
</div>
<p>Es stellt sich beim genauen Hinsehen die Frage, warum auch am Ende der Methode <code>enter</code> die Methode <code>notifyAll</code> aufgerufen wird (Zeile 24). Dazu müssen wir uns vorstellen, dass die Parkgarage voll belegt ist, während mehrere Autos vor der Schranke warten. Nun verlassen direkt hintereinander mehrere Autos die Parkgarage, so dass wieder einige Stellplätze frei werden. Der gemeinsame kritische Abschnitt der Methoden <code>enter</code> und <code>leave</code> sichert zu, dass während des Ausfahrens eines Autos nicht gleichzeitig ein anderes Auto einfahren kann. Anschließend gelingt es dem vordersten Auto in der Warteschlange in die Parkgarage einzufahren. Nach dem Einfahren muss es die anderen wartenden Autos mittels <code>notifyAll</code> benachrichtigen (Zeile 24), da noch weitere Plätze frei sind und ansonsten nicht wieder belegt werden würden.</p>
<p>Es stellt sich beim genauen Hinsehen die Frage, warum auch am Ende der Methode <code>enter</code> die Methode <code>notifyAll</code> aufgerufen wird (Zeile 25). Dazu müssen wir uns vorstellen, dass die Parkgarage voll belegt ist, während mehrere Autos vor der Schranke warten. Nun verlassen direkt hintereinander mehrere Autos die Parkgarage, so dass wieder einige Stellplätze frei werden. Der gemeinsame kritische Abschnitt der Methoden <code>enter</code> und <code>leave</code> sichert zu, dass während des Ausfahrens eines Autos nicht gleichzeitig ein anderes Auto einfahren kann. Anschließend gelingt es dem vordersten Auto in der Warteschlange in die Parkgarage einzufahren. Nach dem Einfahren muss es die anderen wartenden Autos mittels <code>notifyAll</code> benachrichtigen (Zeile 25), da noch weitere Plätze frei sind und ansonsten nicht wieder belegt werden würden.</p>
<p>Im Package <code>java.util.concurrent</code> finden wir heute lange etablierte Interfaces und Klassen, die eine entwicklerfreundlichere API für die grundlegenden Muster zur Synchronisation von Threads bieten. Dazu zählen z.B. die Interfaces <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/Lock.html"><code>Lock</code></a> als Alternative zu <code>synchronized</code> und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/Condition.html"><code>Condition</code></a> als Alternative zu den Methoden <code>wait</code>, <code>notify</code> und <code>notifyAll</code>. Blockierende FIFO-Warteschlangen bieten sämtliche Implementierungen des Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/BlockingQueue.html"><code>BlockingQueue</code></a>. Das folgende Code-Beispiel demonstriert wie der oben dargestellte, begrenzte und blockierende Speicher in der Klasse <code>BoundedBufferSync</code> alternativ über <code>Lock</code>- und <code>Condition</code>-Objekte (siehe <code>BoundedBufferLock</code>) bzw. über eine <code>ArrayBlockingQueue</code> (siehe <code>BoundedBufferBlockingQueue</code>) realisiert werden kann.</p>
......
<i>Dependency Injection</i> lässt sich am besten durch "Einbringen von Abhängigkeiten" übersetzen und ist in der Software-Entwicklung ein bekannter Begriff, bei dem es wie bei der Fabrikmethode um die Auslagerung von Konstruktoraufrufen zur Objekterzeugung geht. Die Ziel der Dependency Injection ist es, die Abhängigkeiten zwischen Klassen, die durch <code>import</code>-Anweisungen entstehen, zu reduzieren, und die Klassen dadurch zu entkoppeln. <a href="https://en.wikipedia.org/wiki/Loose_coupling">Lose Kopplung</a> von Klassen (oder Komponenten im Allgemeinen) hat den Vorteil, dass sich Änderungen einfacher durchführen lassen, da diese sich nur lokal auswirken.</p>
<p><i>Dependency Injection</i> lässt sich am besten durch "Einbringen von Abhängigkeiten" übersetzen und ist in der Software-Entwicklung ein bekannter Begriff, bei dem es wie bei der Fabrikmethode um die Auslagerung von Konstruktoraufrufen zur Objekterzeugung geht. Die Ziel der Dependency Injection ist es, die Abhängigkeiten zwischen Klassen, die durch <code>import</code>-Anweisungen entstehen, zu reduzieren, und die Klassen dadurch zu entkoppeln. <a href="https://en.wikipedia.org/wiki/Loose_coupling">Lose Kopplung</a> von Klassen (oder Komponenten im Allgemeinen) hat den Vorteil, dass sich Änderungen einfacher durchführen lassen, da diese sich nur lokal auswirken.</p>
<p>Dependency Injection ist kein GoF-Muster. Den Begriff hat Martin Fowler in seinem Artikel <a href="https://martinfowler.com/articles/injection.html">"Inversion of Control Containers and the Dependency Injection pattern"</a> geprägt <a href="#cite-Fow04">[Fow04]</a>. Er suchte nach einem Begriff für die Entkopplung bei der Objekterzeugung, der sich von dem allgemeinen Begriff <i>Inversion of Control (IoC)</i> (s. Kapitel <a href="#unit-spring">Spring</a>) abgrenzen lässt. Dependency Injection folgt dem <a href="https://en.wikipedia.org/wiki/Single_responsibility_principle">Prinzip der eindeutigen Verantwortlichkeit (engl. <i>Single-Responsibility-Prinzip</i>)</a> <a href="#cite-Mar18">[Mar18]</a>, das nach Robert Martin besagt, dass jede Klasse in der objekt-orientierten Programmierung nur eine wesentliche Aufgabe erfüllen soll und entsprechend dieser Aufgabe weiterentwickelt wird:</p>
<div class="cite">"There should never be more than one reason for a class to change." (Robert Martin)</div>
......
......@@ -48,7 +48,7 @@
}
}</code></pre>
<p>Das folgende Code-Beispiel demonstriert die Verwendung eines <code>PropertyChangeListener</code> als neuere Alternative zu <code>java.util.Observer</code>. Anstatt <code>java.util.Observable</code> zu erweitern, kann die Liste der registrierten Beobachter und die Methode <code>notifyObservers</code> auch eigenständig implementiert werden (Zeile 3-7). Die Beobachter können in diesem Fall das funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/beans/PropertyChangeListener.html"><code>java.beans.PropertyChangeListener</code></a> implementieren und über dessen Methode ein <code>PropertyChangeEvent</code> empfangen (Zeile 15-16). </p>
<p>Das folgende Code-Beispiel demonstriert die Verwendung eines <code>PropertyChangeListener</code> als neuere Alternative zu <code>java.util.Observer</code>. Anstatt <code>java.util.Observable</code> zu erweitern, kann die Liste der registrierten Beobachter und die Methode <code>notifyObservers</code> auch eigenständig implementiert werden (Zeilen 3-7). Die Beobachter können in diesem Fall das funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/beans/PropertyChangeListener.html"><code>java.beans.PropertyChangeListener</code></a> implementieren und über dessen Methode ein <code>PropertyChangeEvent</code> empfangen (Zeilen 15-17). </p>
<pre><code class="language-java line-numbers">class MySecondObservable {
Object state;
......@@ -65,7 +65,8 @@
public static void main(String[] args) {
// Observer is implemented as a lambda expression
PropertyChangeListener o1 = (PropertyChangeEvent e) ->
System.out.printf("Received new state = %s [previous state = %s] from %s\n", e.getNewValue(), e.getOldValue(), e.getSource());
System.out.printf("Received new state = %s [previous state = %s] from %s\n",
e.getNewValue(), e.getOldValue(), e.getSource());
MySecondObservable source = new MySecondObservable();
source.listeners.add(o1);
......
......@@ -95,7 +95,8 @@
MediaView mediaView = new MediaView();
List&lt;String> filenames = List.of("BigBuckBunny.mp4", "ElephantsDream.mp4", "ForBiggerBlazes.mp4", "ForBiggerEscapes.mp4");
List&lt;Video> videos = filenames.stream().map(filename -> new ProxyVideo(VIDEO_URL + filename)).collect(Collectors.toList());
List&lt;Video> videos = filenames.stream().map(filename -> new ProxyVideo(VIDEO_URL + filename))
.collect(Collectors.toList());
Button b = new Button("Play next video");
b.setOnAction(e -> {
......@@ -111,7 +112,7 @@
</div>
</div>
<p>Der Client startet eine einfache JavaFX-Anwendung, die jedes Mal, wenn der Anwender auf einen Button klickt, ein zufälliges Video abspielt (Zeilen 14-18 in <code>Client</code>). Zuvor wird eine Liste mit <code>ProxyVideo</code>-Objekten befüllt (Zeile 12). In dieser Zeile könnte der Konstruktoraufruf <code>new ProxyVideo</code> durch <code>new RealVideo</code> ersetzt werden, um den Proxy nicht zu verwenden, und demzufolge die Videos bereits vorab zu laden und die kurzen Werbevideos abzuspielen anstatt sie zu überspringen.</p>
<p>Der Client startet eine einfache JavaFX-Anwendung, die jedes Mal, wenn der Anwender auf einen Button klickt, ein zufälliges Video abspielt (Zeilen 15-19 in <code>Client</code>). Zuvor wird eine Liste mit <code>ProxyVideo</code>-Objekten befüllt (Zeile 12). In dieser Zeile könnte der Konstruktoraufruf <code>new ProxyVideo</code> durch <code>new RealVideo</code> ersetzt werden, um den Proxy nicht zu verwenden, und demzufolge die Videos bereits vorab zu laden und die kurzen Werbevideos abzuspielen anstatt sie zu überspringen.</p>
<p>Das folgende UML-Klassendiagramm entspricht dem Code-Beispiel für das Proxy-Muster.</p>
<img src="media/patterns_proxy_example.png" style="width:500px">
......
......@@ -63,7 +63,8 @@ conn.commit();</code></pre>
<p>Das folgende Code-Beispiel zeigt ein <code>PreparedStatement</code>, das zum einen bei wiederholter Ausführung effizient ist, da das SQL-Statement vorab geparst und validiert werden kann, und zum anderen mehr Sicherheit bietet, da durch die typsichere Parameterwertzuweisung SQL-Injections verhindert werden.</p>
<pre><code class="language-java line-numbers">PreparedStatement prepStmt = conn.prepareStatement("SELECT p.id, p.name FROM person p JOIN department d ON p.dept_id = d.id WHERE d.label = ?");
<pre><code class="language-java line-numbers">PreparedStatement prepStmt = conn.prepareStatement(
"SELECT p.id, p.name FROM person p JOIN department d ON p.dept_id = d.id WHERE d.label = ?");
prepStmt.setString(1, "R&D");
ResultSet res = prepStmt.executeQuery();</code></pre>
......
......@@ -388,7 +388,8 @@ public class UserResource {
InputStream is = new ByteArrayInputStream(user.image);
String type = URLConnection.guessContentTypeFromStream(is); // get MIME type
String ext = type.substring(type.lastIndexOf("/") + 1); // get file extension
return Response.ok(is).type(type).header("Content-Disposition", "inline; filename=\"" + user.name + "." + ext + "\"").build();
return Response.ok(is).type(type)
.header("Content-Disposition", "inline; filename=\"" + user.name + "." + ext + "\"").build();
}
private User getUserById(int id) {
......
......@@ -96,23 +96,26 @@ Content-type: text/xml; charset=utf-8
<div class="tab-content" id="wsdl-tabs-content">
<div class="tab-pane show active" id="wsdl-tabs-doc" role="tabpanel">
<pre><code class="language-xml line-numbers"><!--<!-​- Generated by JAX-WS RI (http://javaee.github.io/metro-jax-ws). RI's version is JAX-WS RI 2.3.1 -​->
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://de.oncampus.patterns.soap/" name="AccountServiceFactory" ...>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://de.oncampus.patterns.soap/" name="AccountServiceFactory" ...>
<types> <xsd:schema> <xsd:import schemaLocation="http://localhost:8080/AccountService?xsd=1"/> </xsd:schema> </types>
<types> <xsd:schema> <xsd:import schemaLocation="http://localhost:8080/AccountService?xsd=1"/> </xsd:schema> </types>
<message name="createAccount"> <part name="owner" type="xsd:string"/> </message>
<message name="createAccountResponse"> <part name="return" type="xsd:string"/> </message>
<message name="createAccount"> <part name="owner" type="xsd:string"/> </message>
<message name="createAccountResponse"> <part name="return" type="xsd:string"/> </message>
<message name="addAccountEntry"> <part name="accountId" type="xsd:string"/> <part name="entry" type="tns:accountEntry"/> </message>
<message name="addAccountEntryResponse"/>
<message name="addAccountEntry">
<part name="accountId" type="xsd:string"/>
<part name="entry" type="tns:accountEntry"/>
</message>
<message name="addAccountEntryResponse"/>
<message name="getAccountEntries"> <part name="accountId" type="xsd:string"/> </message>
<message name="getAccountEntriesResponse"> <part name="return" type="tns:accountEntryArray"/> </message>
<message name="getAccountEntries"> <part name="accountId" type="xsd:string"/> </message>
<message name="getAccountEntriesResponse"> <part name="return" type="tns:accountEntryArray"/> </message>
<message name="getAccountBalance"> <part name="accountId" type="xsd:string"/> </message>
<message name="getAccountBalanceResponse"> <part name="return" type="xsd:int"/> </message>
...
<message name="getAccountBalance"> <part name="accountId" type="xsd:string"/> </message>
<message name="getAccountBalanceResponse"> <part name="return" type="xsd:int"/> </message>
...
</definitions> --></code></pre>
</div>
<div class="tab-pane" id="wsdl-tabs-types" role="tabpanel">
......@@ -139,7 +142,7 @@ Content-type: text/xml; charset=utf-8
<ul>
<li>Jedes WSDL-Dokument ist ein wohlgeformtes und gültiges XML-Dokument. Der Bezug zum zugehörigen XML-Schema zur Validierung ist zu Beginn angegeben (Zeile 2: <a href="http://schemas.xmlsoap.org/wsdl/"><code>xmlns="http://schemas.xmlsoap.org/wsdl/"</code></a>).</li>
<li>Eigene komplexe Datentypen werden in einem separaten XML-Schema deklariert und eingebettet (Zeile 5). Das XML-Schema, das die eigenen Typen (z.B. <code>AccounEntry</code>) deklariert, ist oben im zweiten Tab dargestellt.</li>
<li>Für jede Methode werden die Input-Argumente und das Return-Argument spezifiziert (Zeilen 7-17). Die Bezeichner der Methoden und Argumente können vom Anbieter des Webservice sinnvoll gewählt werden.</li>
<li>Für jede Methode werden die Input-Argumente und das Return-Argument spezifiziert (Zeilen 7-20). Die Bezeichner der Methoden und Argumente können vom Anbieter des Webservice sinnvoll gewählt werden.</li>
</ul>
<h4>JAX-WS (Java API for XML Web Services)</h4>
......@@ -193,7 +196,8 @@ public class AccountServiceImpl {
}
@WebMethod
public void addAccountEntry(@WebParam(name = "accountId") String id, @WebParam(name = "entry") AccountEntry entry) throws AccountNotFoundException {
public void addAccountEntry(@WebParam(name = "accountId") String id, @WebParam(name = "entry") AccountEntry entry)
throws AccountNotFoundException {
getAccountById(id).entries.add(entry);
}
......@@ -214,7 +218,8 @@ public class AccountServiceImpl {
}
Account getAccountById(String id) throws AccountNotFoundException {
return accounts.stream().filter(a -> a.id.toString().equals(id)).findFirst().orElseThrow(() -> new AccountNotFoundException());
return accounts.stream().filter(a -> a.id.toString().equals(id)).findFirst()
.orElseThrow(() -> new AccountNotFoundException());
}
}</code></pre></div>
<div class="tab-pane" id="soapserver-tabs-account" role="tabpanel">
......@@ -264,8 +269,8 @@ public class AccountNotFoundException extends Exception { }</code></pre>
<li><code>AccountServiceImpl</code>: Die Klasse implementiert den SOAP-Webservice. Im Gegensatz zu RMI ist ein zugehöriges Interface, das dem Client zur Verfügung gestellt werden muss, nicht unbedingt erforderlich. Jede Klasse, die als Webservice instantiiert werden soll, benötigt die Annotation <code>Webservice</code> (Zeile 8).
<ul>
<li>Die optionale Annotation <code>@SOAPBinding(style = SOAPBinding.Style.RPC)</code> sorgt u.a. dafür, dass im generierten WSDL-Dokument Basisdatentypen wie Integer und String auf die entsprechenden Basisdatentypen von XML-Schema abgebildet werden, also z.B. <code>xsd:int</code> und <code>xsd:string</code>.</li>
<li>Jede öffentliche Methode (Sichtbarkeit <i>public</i>) wird in die Schnittstelle zum Client, also in das WSDL-Dokument, aufgenommen – außer sie ist mit <code>@WebMethod(exclude = true)</code> annotiert. Private Methoden werden nicht in das WSDL-Dokument aufgenommen. Aus eben diesen Gründen sind die Methoden <code>deleteAccount</code> (Zeilen 37-40) und <code>getAccountById</code> (Zeilen 42-44) nicht im obigen WSDL-Dokument wiederzufinden, während alle anderen Methoden der Klasse <code>AccountServiceImpl</code> im WSDL-Dokument enthalten sind.</li>
<li>Mittels der Annotationen <code>@WebMethod(operationName = "...")</code> und <code>@WebParam(name = "...")</code> können Methoden bzw. ihre Argumente gezielt nach außen gerichtet bezeichnet werden. Im obigen WSDL-Dokument wird z.B. die Methode <code>getEntries</code> exemplarisch in <code>getAccountEntries</code> umbenannt (Zeilen 26-27). Die Methodenargumente explizit zu bezeichnen, ist bei JAX-WS RI sinnvoll, da diese im WSDL-Dokument andernfalls schlicht <code>arg0</code>, <code>arg1</code>, ... heißen.</li>
<li>Jede öffentliche Methode (Sichtbarkeit <i>public</i>) wird in die Schnittstelle zum Client, also in das WSDL-Dokument, aufgenommen – außer sie ist mit <code>@WebMethod(exclude = true)</code> annotiert. Private Methoden werden nicht in das WSDL-Dokument aufgenommen. Aus eben diesen Gründen sind die Methoden <code>deleteAccount</code> (Zeilen 38-41) und <code>getAccountById</code> (Zeilen 43-46) nicht im obigen WSDL-Dokument wiederzufinden, während alle anderen Methoden der Klasse <code>AccountServiceImpl</code> im WSDL-Dokument enthalten sind.</li>
<li>Mittels der Annotationen <code>@WebMethod(operationName = "...")</code> und <code>@WebParam(name = "...")</code> können Methoden bzw. ihre Argumente gezielt nach außen gerichtet bezeichnet werden. Im obigen WSDL-Dokument wird z.B. die Methode <code>getEntries</code> exemplarisch in <code>getAccountEntries</code> umbenannt (Zeilen 27-28). Die Methodenargumente explizit zu bezeichnen, ist bei JAX-WS RI sinnvoll, da diese im WSDL-Dokument andernfalls schlicht <code>arg0</code>, <code>arg1</code>, ... heißen.</li>
</li>
<li><code>Account</code>, <code>AccountEntry</code> und <code>AccountNotFoundException</code>: Die serverseitigen DTO-Klassen haben sich im Vergleich zum RMI-Anwendungsbeispiel nicht grundlegend geändert. Das Interface <code>java.io.Serializable</code> muss nicht mehr implementiert werden, da die Serialisierung und Deserialisierung in das XML-Format nun über die Bibliothek <a href="https://javaee.github.io/jaxb-v2/">JAXB</a> erfolgt.</li>
</ul>
......
......@@ -158,21 +158,21 @@ Request URL: http://localhost:8080/bookings/1?projection=inlineProduct
---[HTTP response 200]---
Content-type: application/hal+json; charset=utf-8
{
"id": 1,
"date": "2019-12-31",
"product": {
"title": "Azzam",
"description": "Die Azzam ist mit einer Länge von 180 Meter die längste private Mega-Yacht der Welt. [...]",
"price": 181,
"instock": true
},
"bookingPrice": 300,
"_links": {
"self": { "href": "http://localhost:8080/bookings/1" },
"booking": { "href": "http://localhost:8080/bookings/1{?projection}", "templated": true },
"user": { "href": "http://localhost:8080/bookings/1/user" },
"product": { "href": "http://localhost:8080/bookings/1/product" }
}
"id": 1,
"date": "2019-12-31",
"product": {
"title": "Azzam",
"description": "Die Azzam ist mit einer Länge von 180 m die längste private Mega-Yacht der Welt. [...]",
"price": 181,
"instock": true
},
"bookingPrice": 300,
"_links": {
"self": { "href": "http://localhost:8080/bookings/1" },
"booking": { "href": "http://localhost:8080/bookings/1{?projection}", "templated": true },
"user": { "href": "http://localhost:8080/bookings/1/user" },
"product": { "href": "http://localhost:8080/bookings/1/product" }
}
} --></code></pre></div>
</div>
......@@ -216,29 +216,29 @@ Request URL: http://localhost:8080/products?sort=title&page=0&size=3
---[HTTP response 200]---
Content-type: application/hal+json; charset=utf-8
{
"_embedded": {
"products": [
{
"title": "Al Mirqab", "description": "Die Al Mirqab wurde 2004 in Auftrag [...]", "price": 133, "instock": true,
"_links": { "self": { "href": "http://localhost:8080/products/13" } }
},
{
"title": "Al Said", "description": "Die Al Said ist mit einer Länge von 155 Metern [...]", "price": 155, "instock": true,
"_links": { "self": { "href": "http://localhost:8080/products/4" } }
},
{
"title": "Al Salamah", "description": "Die Al Salamah ist eine 139 Meter lange Mega-Yacht [...]", "price": 139, "instock": false,
"_links": { "self": { "href": "http://localhost:8080/products/6" } }
}
]
},
"_links": {
"first": { "href": "http://localhost:8080/products?page=0&size=3&sort=title,asc" },
"next": { "href": "http://localhost:8080/products?page=1&size=3&sort=title,asc" },
"last": { "href": "http://localhost:8080/products?page=4&size=3&sort=title,asc" },
"search": { "href": "http://localhost:8080/products/search" }
"_embedded": {
"products": [
{
"title": "Al Mirqab", "description": "Die Al Mirqab wurde 2004 in Auftrag [...]", "price": 133, "instock": true,
"_links": { "self": { "href": "http://localhost:8080/products/13" } }
},
{
"title": "Al Said", "description": "Die Al Said ist mit einer Länge von 155 m [...]", "price": 155, "instock": true,
"_links": { "self": { "href": "http://localhost:8080/products/4" } }
},
{
"title": "Al Salamah", "description": "Die Al Salamah ist eine 139 m lange Yacht [...]", "price": 139, "instock": false,
"_links": { "self": { "href": "http://localhost:8080/products/6" } }
}
]
},
"_links": {
"first": { "href": "http://localhost:8080/products?page=0&size=3&sort=title,asc" },
"next": { "href": "http://localhost:8080/products?page=1&size=3&sort=title,asc" },
"last": { "href": "http://localhost:8080/products?page=4&size=3&sort=title,asc" },
"search": { "href": "http://localhost:8080/products/search" }
},
"page": { "size": 3, "totalElements": 15, "totalPages": 5, "number": 0 }
"page": { "size": 3, "totalElements": 15, "totalPages": 5, "number": 0 }
} --></code></pre>
<p>Die automatisch von Spring generierten Ressourcen-Methoden für ein <code>CrudRepository</code> können über verschiedene Wege angepasst werden. Es kann eine Klasse angelegt werden, die das Interface implementiert und die Methoden überschreibt. Alternativ kann eine Klasse versehen mit der Annotation <code>@RestController</code> angelegt und auf dem Pfad der Ressource registriert wird. Beide Wege erlauben das Verhalten der HTTP-Methoden individuell anzupassen. Im folgenden Code-Beispiel überschreibt die Klasse <code>UserController</code> die auf dem Pfad <code>/users</code> registrierten Methoden für POST und GET des Interface <code>UserRepository</code> (Zeilen 4-5). Bei einer GET-Anfrage werden z.B. nur noch die Namen der vorhandenen User ohne weitere Attribute der Klasse ausgegeben (Zeilen 22-26). Die Methode <code>findByName</code> im Interface <code>UserRepository</code> ist ohnehin nicht Teil der REST-API, da sie nicht mit einer Annotation <code>@RestResource</code> versehen ist. Im <code>UserRepository</code> werden explizit noch die Methoden GET und DELETE für den Pfad <code>/users/&lt;id></code> deaktiviert.</p>
......@@ -461,7 +461,8 @@ public class TweetStarter implements CommandLineRunner {
// create a capped collection which supports tailable cursors, limited to 5 documents here
reactiveMongoTemplate.dropCollection(Tweet.COLLECTION_NAME).block();
reactiveMongoTemplate.createCollection(Tweet.COLLECTION_NAME, CollectionOptions.empty().capped().size(4096).maxDocuments(5)).block();
reactiveMongoTemplate.createCollection(Tweet.COLLECTION_NAME,
CollectionOptions.empty().capped().size(4096).maxDocuments(5)).block();
// prepare HTTP request for new lorem ipsum tweet
HttpClient client = HttpClient.newHttpClient();
......@@ -508,7 +509,7 @@ public class TweetStarter implements CommandLineRunner {
<ul>
<li><b>TweetController</b>: Als Teil der API wird ein Datenstrom unter dem Pfad <code>/tweets</code> registriert (Zeilen 10-13). Es wird eine Methode aus dem zugehörigen <code>TweetRepository</code> aufgerufen, um auf die Datenbank zuzugreifen.</li>
<li><b>TweetRepository</b>: Das Interface erweitert <code>ReactiveMongoRepository&lt;Tweet, String></code> und ist damit ein spezielles reaktives Repository. JDBC-Datenquellen können nicht als reaktives Repository eingesetzt werden. Bei einer Anfrage an die Datenbank gibt die Java-API von MongoDB einen Cursor vom Typ <a href="http://api.mongodb.com/java/current/com/mongodb/DBCursor.html"><code>com.mongodb.DBCursor</code></a> zurück, über den ähnlich einem <code>java.sql.ResultSet</code> iteriert werden kann, um das Ergebnis der Anfrage zu verarbeiten. Per Default schließt MongoDB diesen Cursor, wenn über alle seine Objekte iteriert worden ist. Damit würde aber auch der Stream beendet.<br>MongoDB erlaubt es für sogenannte <a href="https://docs.mongodb.com/manual/core/capped-collections/"><i>Capped Collections</i></a> einen <a href="https://docs.mongodb.com/manual/core/tailable-cursors/"><i>Tailable Cursor</i></a> zu verwenden, der geöffnet bleibt, nachdem alle ursprünglich zurückgegebenen Objekte verarbeitet worden sind. Die Methoden eines <code>ReactiveMongoRepository</code> aus <a href="https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.reactive.repositories.infinite-streams"><i>Spring Data MongoDB</i></a> können mit der Annotation <a href="https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.reactive.repositories.infinite-streams"><code>@Tailable</code></a> versehen werden, damit sie einen deartigen nicht abgeschlossenen, unendlichen Datenstrom zurückgeben, d.h. das zurückgegebene <code>Flux</code>-Objekt ist noch nicht beendet. Nur in diesem Fall wird über die API eine SSE-EventSource veröffentlicht. Die Bezeichnung der Annotation <code>@Tailable</code> ist übrigens an den Linux-Befehl <a href="https://en.wikipedia.org/wiki/Tail_(Unix)#File_monitoring"><code>tail -f</code></a> angelehnt.</li>
<li><b>TweetStarter</b>: In der überschriebenen Methode <code>run</code> wird zuerst die verwendete Collection in eine auf max. 5 Dokumente begrenzte <i>Capped Collection</i> gewandelt (Zeilen 18-19). Anschließend wird ein HTTP-Request vorbereitet (Zeilen 22-24), der in einer Endlosschleife alle 3 Sekunden ausgeführt wird (Zeile 35), um den Text für einen <a href="https://baconipsum.com/">fleischlastigen Lorem Ipsum-Tweet</a> anzufragen (Zeile 29) und diesen in der <i>Capped Collection</i> zu speichern (Zeilen 30-31).</li>
<li><b>TweetStarter</b>: In der überschriebenen Methode <code>run</code> wird zuerst die verwendete Collection in eine auf max. 5 Dokumente begrenzte <i>Capped Collection</i> gewandelt (Zeilen 18-20). Anschließend wird ein HTTP-Request vorbereitet (Zeilen 23-25), der in einer Endlosschleife alle 3 Sekunden ausgeführt wird (Zeile 36), um den Text für einen <a href="https://baconipsum.com/">fleischlastigen Lorem Ipsum-Tweet</a> anzufragen (Zeile 30) und diesen in der <i>Capped Collection</i> zu speichern (Zeilen 31-32).</li>
<li><b>tweets.html</b>: Der Client in <code>tweets.html</code> baut eine Verbindung zur EventSource auf (Zeile 6). Die EventSource emittiert neue Tweets an den Client über die EventHandler-Funktion (Zeilen 7-11), innerhalb derer die Tweets dem DOM hinzugefügt werden.</li>
</ul>
......
......@@ -19,7 +19,7 @@ Die Studierenden lernen bewährte Entwurfs- und Architekturmuster sowie aktuelle
<li><b>Parallelverarbeitung</b>: Die Studierenden können komplexe Verarbeitungsprozesse in Java aufteilen, effizient parallelisieren und synchronisieren. Sie können diesbezüglich Vor- und Nachteile unterschiedlicher Ansätze erörtern.</li>
</ul>
<h4>Literatur</h4>
<h4 class="page-break">Literatur</h4>
<p>Auf die folgenden Einträge des Literaturverzeichnisses wird an den entsprechenden Stellen im Modul über die hier definierten Kürzel verwiesen.</p>
<ul>
<li><a id="cite-Bal11">[Bal11] Helmut Balzert: Lehrbuch der Softwaretechnik - Entwurf, Implementierung, Installation und Betrieb, Spektrum, 3. Aufl., 2011.</a></li>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment