Aktuelles zu Java 10

Nach Java 9 wird uns Java 10 beglücken. Die Zeitleiste sieht so aus:

2017/12/14 Rampdown Phase One
2018/01/11 All Tests Run
2018/01/18 Rampdown Phase Two
2018/02/22 Final Release Candidate
2018/03/20 General Availability

Java 10 ist laut http://openjdk.java.net/projects/jdk/10/ ab heute in der „Rampdown Phase One“ (http://openjdk.java.net/projects/jdk8/milestones#Rampdown_start):

  • Rampdown — Phases in which increasing levels of scrutiny are applied to incoming changes. In phase 1, only P1-P3 bugs can be fixed. In phase 2 only showstopper bugs can be fixed.

Neuerungen dir wir für Java 10 erwarten können, beschrieben im JEP:

286: Local-Variable Type Inference
296: Consolidate the JDK Forest into a Single Repository
304: Garbage-Collector Interface
307: Parallel Full GC for G1
310: Application Class-Data Sharing
312: Thread-Local Handshakes
313: Remove the Native-Header Generation Tool (javah)
314: Additional Unicode Language-Tag Extensions
316: Heap Allocation on Alternative Memory Devices
317: Experimental Java-Based JIT Compiler
319: Root Certificates
322: Time-Based Release Versioning

Es wird auch Java 10, Java 11, usw. heißen und nicht, wie vorher angedacht, Java 18, etc. nach Jahreszahlen. Aus dem http://openjdk.java.net/jeps/322:

  • $FEATURE is incremented every six months: The March 2018 release is JDK 10, the September 2018 release is JDK 11, and so forth.

 

Inselraus: Zugriff auf SMB-Server mit jCIFS

Microsoft Windows nutzt zur Datei- und Verzeichnisfreigabe, zur Freigabe von Druckern und Kommunikationsschnittstellen das Protokoll SMB (Server Message Block). Es ist weit verbreitet, und jede aktuelle Windows-Version lässt sich als Client und Server konfigurieren – gleichzeitig gibt es unter Unix das populäre Samba, einen SMB-Server unter Open Source, der von Andrew Tridgell und Kollegen entwickelt wurde.

Mithilfe der jCIFS-SMB-Bibliothek (http://jcifs.samba.org/) kann ein Java-Programm auf Datei- und Verzeichnisfreigaben zugreifen und Freigaben auflisten. jCIFS ist eine erweiterte Implementierung von CIFS und unterstützt Unicode, Batching, verschlüsselte Authentifizierung, Transactions, das Remote Access Protocol (RAP) und Weiteres. Die Bibliothek steht unter der LGPL.

Die Klassen jcifs.smb.SmbFile, SmbFileInputStream und SmbFileOutputStream verhalten sich ähnlich wie java.io.File, FileInputStream und FileOutputStream. Sie werden mit einem Dateipfad (URL) parametrisiert, der mit smb:// beginnt. Um eine Datei zu beziehen, muss vorher der Server spezifiziert werden. Dazu dienen Eigenschaften wie WINS. Sie werden mit Config.setProperty(„wins“, „IP-Adresse“); gesetzt.

Beispiel: Lies eine Datei aus, und kopiere sie um:

InputStream in =
   new SmbFileInputStream( "smb://user:passwd@host/c/My Documents/doc.txt" );
 Path target = …
 Files.copy( in, target );

Inselraus: Anwendungen für FilterReader und FilterWriter

Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. Wir wollen eine Klasse HTMLWriter entwerfen, die FilterWriter erweitert und Textausgaben in HTML konvertiert. In HTML werden Tags eingeführt, die vom Browser erkannt und besonders behandelt werden. Findet etwa der Browser im HTML-Text eine Zeile der Form <strong>Dick</strong>, so stellt er den Inhalt „Dick“ in fetter Schrift dar, da das <strong>-Element den Zeichensatz umstellt. Alle Tags werden in spitzen Klammern geschrieben. Daraus ergibt sich, dass HTML einige spezielle Zeichenfolgen (Entities genannt) verwendet. Wenn diese Zeichen auf der HTML-Seite dargestellt werden, muss dies durch spezielle Zeichensequenzen geschehen:

  • < wird zu &lt;
  • > wird zu &gt;
  • & wird zu &amp;

Kommen diese Zeichen im Quelltext vor, so muss unser HTMLWriter diese Zeichen durch die entsprechende Sequenz ersetzen. Andere Zeichen sollen nicht ersetzt werden.

Den Browsern ist die Struktur der Zeilen in einer HTML-Datei egal. Sie formatieren wiederum nach speziellen Tags. Zeilenvorschübe etwa werden mit <br/> eingeleitet. Unser HTMLWriter soll zwei leere Zeilen durch das Zeilenvorschub-Element <br/> markieren.

HTML-Dokument schreiben

Alle sauberen HTML-Dateien haben einen wohldefinierten Anfang und ein wohldefiniertes Ende. Das folgende kleine HTML-Dokument ist wohlgeformt und zeigt, was unser Programm später erzeugen soll:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
                       "http://www.w3.org/TR/html4/strict.dtd">
 <html><head><title>Superkreativer Titel</title></head>
 <body><p>
 Und eine Menge von Sonderzeichen: &lt; und &gt; und &amp;
 Zweite Zeile
 <br/>
 Leerzeile
 Keine Leerzeile danach
 </p></body></html>

Der Titel der Seite sollte im Konstruktor übergeben werden können. Hier ist nun das Programm für den HTMLWriter:

package com.tutego.insel.io.stream;
 
 import java.io.*;
 
 class HTMLWriter extends FilterWriter {
   
   private boolean newLine;
 
   /**
    * Creates a new filtered HTML writer with a title for the web page.
    *
    * @param out  a Writer object to provide the underlying stream.
    * @throws IOException if the header cannot be written
    */
   public HTMLWriter( Writer out, String title ) throws IOException {
     super( out );
 
     out.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" +
            " \"http://www.w3.org/TR/html4/strict.dtd\">\n"  );
     out.write( "<html><head><title>" + title + "</title></head>\n<body><p>\n" );
   }
 
   /**
    * Creates a new filtered HTML writer with no title for the web page.
    *
    * @param out  a Writer object to provide the underlying stream.
    */
   public HTMLWriter( Writer out ) {
     super( out );
   }
 
   /**
    * Writes a single character.
    */
   @Override
   public void write( int c ) throws IOException {
     switch ( c ) {
       case '<':
         out.write( "&lt;" );
         newLine = false;
         break;
       case '>':
         out.write( "&gt;" );
         newLine = false;
         break;
       case '&':
         out.write( "&amp;" );
         newLine = false;
         break;
       case '\n':
         if ( newLine ) {
           out.write( "<br/>\n" );
           newLine = false;
         }
         else
           out.write( "\n" );
         newLine = true;
         break;
       case '\r':
         break; // ignore
 
       default :
         out.write( c );
         newLine = false;
     }
   }
 
   /**
    * Writes a portion of an array of characters.
    *
    * @param  cbuf Buffer of characters to be written
    * @param  off  Offset from which to start reading characters
    * @param  len  Number of characters to be written
    * @exception   IOException If an I/O error occurs
    */
   @Override
   public void write( char[] cbuf, int off, int len ) throws IOException {
     for ( int i = off; i < len; i++ )
       write( cbuf[ i ] );
   }
 
   /**
    * Writes a portion of a string.
    *
    * @param  str  String to be written.
    * @param  off  Offset from which to start reading characters
    * @param  len  Number of characters to be written
    * @exception   IOException If an I/O error occurs
    */
   @Override
   public void write( String str, int off, int len ) throws IOException {
     for ( int i = off; i < len; i++ )
       write( str.charAt( i ) );
   }
 
   /**
    * Closes the stream.
    *
    * @throws IOException If the prolog can not be written or the underlying stream 
    * not be closed
    */
   @Override
   public void close() throws IOException {
     try {
       out.write( "</p></body></html>" );
     }
     finally {
       out.close();  // Ignoriere, falls out.close() und out.write() knallt
     }
   }
 }

Ein Demo-Programm soll die aufbereiteten Daten in einen StringWriter schreiben:

StringWriter sw = new StringWriter();

try ( HTMLWriter html = new HTMLWriter( sw, "Superkreativer Titel" );

      PrintWriter pw = new PrintWriter( html ) ) {

  pw.println( "Und eine Menge von Sonderzeichen: < und > und &" );

  pw.println( "Zweite Zeile" );

  pw.println();

  pw.println( "Leerzeile" );

  pw.println( "Keine Leerzeile danach" );

}

System.out.println( sw );

HTML-Tags mit einem speziellen Filter überlesen

Unser nächstes Beispiel ist eine Klasse, die den FilterReader so erweitert, dass HTML-Tags überlesen werden. Die Klasse FilterReader deklariert den notwendigen Konstruktor zur Annahme des Reader, der die wirklichen Daten liefert, und überschreibt zwei read(…)-Methoden. Die read()-Methode ohne Parameter – die ein int für ein gelesenes Zeichen zurückgibt – legt einfach ein 1 Zeichen großes Feld an und ruft dann die zweite überschriebene read(char[], int, int)-Methode auf, die die Daten in ein Feld liest. Da dieser Methode neben dem Feld auch noch die Größe übergeben werden kann, müssen wirklich so viele Zeichen gelesen werden. Es reicht einfach nicht aus, die übergebene Anzahl von Zeichen vom tiefer liegenden Reader zu lesen, sondern hier müssen wir beachten, dass eingestreute Tags nicht zählen. Die Zeichenkette <p>Hallo<p> ist ja nur fünf Zeichen lang und nicht elf!

 package com.tutego.insel.io.stream;
 
 import java.io.*;
 
 public class HTMLReader extends FilterReader {
   private boolean inTag = false;
 
   public HTMLReader( Reader in ) {
     super( in );
   }
 
   @Override
   public int read() throws IOException {
     char[] buf = new char[ 1 ];
     return read( buf, 0, 1 ) == –1 ? –1 : buf[ 0 ];
   }
 
   @Override
   public int read( char[] cbuf, int off, int len ) throws IOException {
     int numchars = 0;
 
     while ( numchars == 0 ) {
       numchars = in.read( cbuf, off, len );
 
       if ( numchars == –1 ) // EOF?
         return –1;
 
       int last = off;
 
       for ( int i = off; i < off + numchars; i++ ) {
         if ( ! inTag ) {
           if ( cbuf[ i ] == '<' )
             inTag = true;
           else
             cbuf[ last++ ] = cbuf[ i ];
         }
         else if ( cbuf[ i ] == '>' )
           inTag = false;
       }
       numchars = last – off;
     }
     return numchars;
   }
 }

Ein Beispielprogramm soll die Daten aus einem StringReader ziehen. Der HTMLReader bekommt diesen StringReader und wird selbst von Scanner genutzt, damit wir die komfortable nextLine()-Methode nutzen können. Da hier keine externen Ressourcen vorkommen, müssen wir nichts schließen, und ein try mit Ressourcen kann entfallen.

String s = "<html>Hallo! <b>Ganz schön fett.</b> "
            + "Ah, wieder normal.</html>";
 
 Reader sr = new StringReader( s );
 Reader hr = new HTMLReader( sr );
 Scanner scanner = new Scanner( hr );
 while ( scanner.hasNextLine() )
   System.out.println( scanner.nextLine() );

Es produziert dann die einfache Ausgabe:

Hallo! Ganz schön fett. Ah, wieder normal.

Inselraus: Ein LowerCaseWriter

Wir wollen im Folgenden einen Filter schreiben, der alle in den Strom geschriebenen Zeichen in Kleinbuchstaben umwandelt. Drei Dinge sind für einen eigenen FilterWriter nötig:

  • Die Klasse leitet sich von FilterWriter
  • Unser Konstruktor nimmt als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor der Oberklasse auf, also FilterWriter. Die Oberklasse speichert das übergebene Argument in der geschützten Objektvariablen out, sodass die Unterklassen darauf zugreifen können.
  • Wir überlagern die drei write(…)-Methoden und eventuell noch close() und flush(). Unsere write(…)-Methoden führen dann die Filteroperationen aus und geben die wahren Daten an den Writer
class LowerCaseWriter extends FilterWriter {
 
   public LowerCaseWriter( Writer writer ) {
     super( writer );
   }
 
   @Override
   public void write( int c ) throws IOException {
     out.write( Character.toLowerCase((char)c) );
   }
 
   @Override
   public void write( char[] cbuf, int off, int len ) throws IOException {
     write( String.valueOf( cbuf ), off, len );
   }
 
   @Override
   public void write( String s, int off, int len ) throws IOException {
     out.write( s.toLowerCase(), off, len );
   }
 }

Und die Nutzung sieht dann so aus:

StringWriter sw = new StringWriter();
PrintWriter  pw = new PrintWriter( new LowerCaseWriter( sw ) );
pw.println( "Eine Zeile für klein und groß" );
System.out.println( sw.toString() );

JAX-RS mit Jersey, Teil 2 (RESTful Web-Services)

Content-Handler, Marshaller und verschiedene MIME-Typen

JAX-RS erlaubt grundsätzlich alle MIME-Typen, und die Daten selbst können auf verschiedene Java-Datentypen übertragen werden. So ist es egal, ob bei Textdokumenten zum Beispiel der Rückgabetyp String oder OutputStream ist; selbst ein File-Objekt lässt sich zurückgeben. Für einen Parametertyp – Parameter werden gleich vorgestellt – gilt das Gleiche: JAX-RS ist hier recht flexibel und kann über einen InputStream oder Writer einen String entgegennehmen. (Reicht das nicht, können so genannte Provider angemeldet werden.)

Bei XML-Dokumenten kommt hinzu, dass JAX-RS wunderbar mit JAXB zusammenspielt.

XML mit JAXB

Dazu ein Beispiel für einen Dienst hinter der URL http://localhost:8080/api/dating/serverinfo, der eine Serverinformation im XML-Format liefert. Das XML wird automatisch von JAXB generiert.

@GET
 @Path( "serverinfo" )
 @Produces( MediaType.TEXT_XML )
 public ServerInfo serverinfo() {
   ServerInfo info = new ServerInfo();
   info.server = System.getProperty( "os.name" )+" "+System.getProperty( "os.version" );
   return info;
 }

@XmlRootElement
 class ServerInfo {
   public String server;
 }

Die Klasse ServerInfo ist eine JAXB-annotierte Klasse. In der eigenen JAX-RS-Methode serverinfo() wird dieses ServerInfo-Objekt aufgebaut, das Attribut gesetzt und dann zurückgegeben; der Rückgabetyp ist also nicht String wie vorher, sondern explizit ServerInfo. Dass der MIME-Typ XML ist, sagt @Produces(MediaType.TEXT_XML). Und noch eine Annotation nutzt das Beispiel: @Path. Lokal an der Methode bedeutet es, dass der angegebene Pfad zusätzlich zur Pfadangabe an der Klasse gilt. Also ergibt sich der komplette Pfad aus: Basispfad + „dating“ + „/“ + „serverinfo“. Wir können http://localhost:8080/api/dating/serverinfo im Browser eingeben.

JSON-Serialisierung *

Ist der Client eines REST-Aufrufs ein JavaScript-Programm in einem Webbrowser, ist es in der Regel praktischer, statt XML das Datenformat JSON zu verwenden. JAX-RS bindet drei Möglichkeiten zum Senden von JSON:

  1. JSON-Übersetzungdes Java-Objekts über die JSON-Bibliothek („POJO based JSON binding support“). Es lassen sich so ziemlich alle Java-Objekte abbilden.
  2. JAXB-basierte JSON-Übersetz Die JSON-Bibliothek liest die JAXB-annotierten Objekte aus und führt anhand der Metadaten die Umsetzung durch. JAXB wird hier nicht nur für XML verwendet, sondern auch für JSON; natürlich ergeben nicht alle Eigenschaften einen Sinn.
  3. Automatisch ohne ein Mapping arbeitet eine Low-Level-API. Sie gibt maximale Flexibilität, aber erfordert viel Handarbeit. Seit Java EE 7 ist die API im Standard, aber nicht Teil der Java SE.

Schauen wir uns die zweite Lösung an. Jersey unterstützt von Haus aus Jackson, MOXy und Jettison als JSON-Objekt-Mapper. Um MOXy einzusetzen müssen weitere Java-Archive in den Klassenpfad aufgenommen werden. Wir können die Abhängigkeiten über Maven beschreiben, dann ist folgendes in der pom.xml aufzunehmen:

<dependency>

<groupId>org.glassfish.jersey.media</groupId>

<artifactId>jersey-media-moxy</artifactId>

<version>2.25</version>

</dependency>

Oder zu Fuß müssen die folgenden JAR-Dateien geladen und dann im Klassenpfad aufgenommen werden:

  • https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-moxy/
  • https://mvnrepository.com/artifact/org.glassfish.jersey.ext/jersey-entity-filtering/
  • https://mvnrepository.com/artifact/org.eclipse.persistence/org.eclipse.persistence.moxy/
  • https://mvnrepository.com/artifact/org.eclipse.persistence/org.eclipse.persistence.core/
  • https://mvnrepository.com/artifact/org.glassfish/javax.json/
    • Um von XML auf JSON zu wechseln muss bei den REST-Methoden lediglich der APPLICATION_JSON eingesetzt werden:
@GET

@Path( "jsonserverinfo" )

@Produces( MediaType.APPLICATION_JSON )

public ServerInfo jsonserverinfo() {

  return serverinfo();

}

Das reicht schon aus, und der Server sendet ein JSON-serialisiertes Objekt.

Alles Verhandlungssache

Die JAX-RS-API bietet mit dem MIME-Typ noch eine Besonderheit, dass der Server unterschiedliche Formate liefern kann, je nachdem, was der Client verarbeiten möchte oder kann. Der Server macht das mit @Produces klar, denn dort kann eine Liste von MIME-Typen stehen. Soll der Server XML und JSON generieren können, schreiben wir:

@GET

@Path( "jsonxmlserverinfo" )

@Produces( { MediaType.TEXT_XML, MediaType.APPLICATION_JSON } )

public ServerInfo jsonxmlserverinfo() {

  return serverinfo();

}

Kommt der Client mit dem Wunsch nach XML, bekommt er XML, möchte er JSON, bekommt er JSON. Die Jersey-Client-API teilt über request(String) bzw. request(MediaType… types) mit, was ihr Wunschtyp ist. (Dieser Typwunsch ist eine Eigenschaft von HTTP und nennt sich Content Negotiation.)

WebTarget wr1 = ClientBuilder.newClient().target( "http://localhost:8080/api" );

Builder b1 = wr1.path( "dating" ).path( "jsonxmlserverinfo" )

                .request( MediaType.APPLICATION_JSON );

System.out.println( b1.get( ServerInfo.class ).getServer() );  // Windows 10 10.0




WebTarget wr2 = ClientBuilder.newClient().target( "http://localhost:8080/api" );

Builder b2 = wr2.path( "dating" ).path( "jsonxmlserverinfo" )

                .request( MediaType.TEXT_XML );

System.out.println( b2.get( ServerInfo.class ).getServer() );  // Windows 10 10.0




WebTarget wr3 = ClientBuilder.newClient().target( "http://localhost:8080/api" );

Builder b3 = wr3.path( "dating" ).path( "jsonxmlserverinfo" )

                .request( MediaType.TEXT_PLAIN );

try {

  System.out.println( b3.get( ServerInfo.class ).getServer() );

}

catch ( Exception e ) {

  System.out.println( e );

  // javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable

}

Passt die Anfrage auf den Typ von @Produces, ist alles prima, ohne Übereinstimmung gibt es einen Fehler. Bei der letzten Zeile gibt es eine Ausnahme („javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable“), da JSON und XML eben nicht purer Text sind.

REST-Parameter

Im Abschnitt „Wie sieht ein REST-URI aus?“ in Abschnitt 15.2.1 wurde ein Beispiel vorgestellt, wie Pfadangaben aussehen, wenn sie einen RESTful Service bilden:

http://www.tutego.de/blog/javainsel/category/java-7/page/2/

Als Schlüssel-Wert-Paare lassen sich festhalten: category=java-7 und page=2. Der Server wird die URL auseinanderpflücken und genau die Blog-Einträge liefern, die zur Kategorie „java-7“ gehören und sich auf der zweiten Seite befinden.

Bisher sah unser REST-Service auf dem Endpunkt /api/dating/ so aus, dass einfach ein String zurückgegeben wird. Üblicherweise gibt es aber unterschiedliche URLs, die Operationen wie „finde alle“ oder „finde alle mit der Einschränkung X“ abbilden. Bei unseren Dating-Dienst wollen wir dem Client drei Varianten zur Abfrage anbieten (mit Beispiel):

  • /api/dating/: alle Nutzer
  • /api/dating/gender/nasi: alle Nutzer mit dem Geschlecht „nasi“
  • /api/dating/gender/nasi/age/18-28: alle „nasischen“-Nutzer im Alter von 18 bis 28

Das erste Beispiel macht deutlich, dass hier ohne explizite Angabe weiterer Einschränkungskriterien alle Nachrichten erfragt werden sollen, während mit zunehmend längerer URL weitere Einschränkungen dazukommen.

Parameter in JAX-RS kennzeichnen

Die JAX-RS-API erlaubt es, dass Parameter (wie eine ID oder ein Such-String) leicht eingefangen werden können. Für die drei möglichen URLs entstehen zum Beispiel drei überladene Methoden:

@GET @Produces( MediaType.TEXT_PLAIN )
 public String meet() ...
 


@GET

@Path( "gender/{gender}" )

@Produces( MediaType.TEXT_PLAIN )

public String meet( @PathParam( "gender" ) String gender ) {

  return String.format( "Geschlecht = %s", gender );

}




@GET

@Produces( MediaType.TEXT_PLAIN )

@Path( "gender/{gender}/age/{age}" )

public String meet( @PathParam( "gender" ) String gender,

                    @PathParam( "age" ) String age ) {

  return String.format( "Geschlecht = %s, Altersbereich = %s", gender, age );

}

Die bekannte @Path-Annotation enthält nicht einfach nur einen statischen Pfad, sondern beliebig viele Platzhalter in geschweiften Klammern. Der Name des Platzhalters taucht in der Methode wieder auf, nämlich dann, wenn er mit @PathParam an einen Parameter gebunden wird. Jersey parst für uns die URL und füllt die Parametervariablen passend auf bzw. ruft die richtige Methode auf. Da die JAX-RS-Implementierung den Wert füllt, nennt sich das auch JAX-RS-Injizierung.

URL-Endung Aufgerufene Methode
/api/dating/ meet()
/api/dating/gender/nasi meet( String gender )
/api/dating/gender/nasi/age/18-28 meet( String gender, String age )

Welche URL zu welcher Methode führt

Die Implementierungen der Methoden würden jetzt an einen Daten-Service gehen und die selektierten Datensätze zurückgeben. Das zeigt das Beispiel nicht, da dies eine andere Baustelle ist.

Tipp: Wenn Parameter falsch sind, kann eine Methode eine besondere Ausnahme vom Typ javax.ws.rs.WebApplicationException (dies ist eine RuntimeException) erzeugen. Im Konstruktor von javax.ws.rs.WebApplicationException lässt sich ein Statuscode als int oder als Aufzählung vom Typ Response.Status übergeben, etwa new WebApplicationException(Response.Status. EXPECTATION_FAILED).

REST-Services mit Parametern über die Jersey-Client-API aufrufen

Wenn die URLs in dem Format schlüssel1/wert1/schlüssel2/wert2 aufgebaut sind, dann ist ein Aufbau einfach durch Kaskadierung der path(…)-Methoden umzusetzen:

System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api" )

  .path( "dating" ).path( "gender" ).path( "nasi" )

  .request().get( String.class ) ); // Geschlecht = nasi




System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api/dating" )

  .path( "gender" ).path( "nasi" ).path( "age" ).path( "18-28" )

  .request().get( String.class ) ); // Geschlecht = nasi, Altersbereich = 18-28

Multiwerte

Schlüssel-Wert-Paare lassen sich auch auf anderen Wegen übermitteln statt nur auf dem Weg über schlüssel1/wert1/schlüssel2/wert2. Besonders im Web und für Formularparameter ist die Kodierung über schlüssel1=wert1&schlüssel2=wert2 üblich. Auch das kann in JAX-RS und mit der Jersey-Client-API abgebildet werden:

  • Anstatt Parameter mit @PathParam zu annotieren, kommt bei Multiwerten @QueryParam zum Einsatz.
  • Anstatt mit path(String) zu arbeiten, wird bei dem Jersey-Client mit queryParam(String key, Object… value)

Hinweis

Es gibt eine Reihe von Dingen, die in Methoden per Annotation übermittelt werden können, und nicht nur @PathParam und @QueryParam. Dazu kommen noch Dinge wie @HeaderParam für den HTTP-Request-Header, @CookieParam für Cookies, @Context für Informationsobjekte und weitere Objekte.

PUT-Anforderungen und das Senden von Daten

Zum Senden von Daten an einen REST-Service ist die HTTP-PUT-Methode gedacht. Die Implementierung einer Java-Methode kann so aussehen:

@PUT

@Path( "message/{user}" )

@Consumes( MediaType.TEXT_PLAIN )

public Response postMessage( @PathParam( "user" ) String user, String message ) {

  System.out.printf( "%s sendet '%s'%n", user, message );

  return Response.noContent().build();

}

Zunächst gilt, dass statt @GET ein @PUT die Methode annotiert. @Consumes hält den MIME-Typ dieser gesendeten Daten fest. Ein zusätzlicher @PathParam fängt die Benutzerkennung ein, die dann mit der gesendeten PUT-Nachricht auf der Konsole ausgegeben wird.

Diese beiden Annotationen @PUT und @Consumes sind also nötig. Eine Rückgabe in dem Sinne hat die Methode nicht, und es ist umstritten, ob ein REST-PUT überhaupt neben dem Statuscode etwas zurückgeben soll. Daher ist die Rückgabe ein spezielles JAX-RS-Objekt vom Typ Response, das hier für 204, „No Content“, steht. Ein 204 kommt auch immer dann automatisch zurück, wenn eine Methode void als Rückgabe deklariert.

PUT/POST/DELETE-Sendungen mit der Jersey-Client-API absetzen

Ein Invocation.Builder bietet neben get(…) auch die anderen Java-Methoden für HTTP-Methoden, also delete(…), post(…), options(…), … und eben auch put(…) zum Schreiben.

Client client = ClientBuilder.newClient();

WebTarget target = client.target( "http://localhost:8080/api" );

Response response = target.path( "dating" ).path( "message" )

                          .path( "Ha" ).request().put( Entity.text("Hey Ha!") );

System.out.println( response );

Die put(…)-Methode erwartet als Argument den Typ Entity, und zum Objektaufbau gibt es diverse statische Methoden in Entity. Es ist Entity.text(„Hey Chris“) eine Abkürzung für Entity.entity(„Hey Chris“, MediaType.TEXT_PLAIN).

Versionierung einer REST-API

Die REST-Schnittstelle ist ein externer Endpunkt einer Software und unterliegt den gleichen Modifikationen wie übliche Software: Schnittstellen ändern sich, die ausgetauschten Objekte bekommen zusätzliche Felder, usw. Ändert sich die Schnittstelle, erzeugt das eine neue Version. Alte APIs sollte ein Server eine Zeit lang beibehalten, damit nicht von einem Tag auf dem anderen eine vielleicht große Anzahl von Klienten mit der älteren Schnittstelle kommunikationslos sind.

Prinzipiell gibt es drei Ansätze bei der Versionierung von REST-APIs; die ersten beiden Varianten nutzen die Möglichkeiten der URL bzw. des HTTP:

  1. Änderung der URL, dass etwa eine Versionskennung mit eingebaut wird. Zu finden ist das bei vielen großen Unternehmen, und zu erkennen etwa an der Versionsnummer bei https://api.twitter.com/1/ https://www.googleapis.com/youtube/v3/, https://api.pinterest.com/v1/, https://api.instagram.com/v1/.
  2. In einem HTTP-Header, wie Accepts. Dort lässt sich die Version in den Dateityp hineinkodieren. Normalerweise ist der Header mit einem MIME-Typ wie text/plain, text/html belegt, doch der RFC4288 sieht in Sektion 3.2 einen „Vendor Tree“ mit dem Präfix vor, sodass sich damit ein eigener Media-Typ inklusive Version formulieren lässt. Das kann so aussehen: application/vnd.tutego.app.api+json;version=2.1 oder application/vnd.tutego.app.api-v2.1+json.
  3. In den Rumpf einer Nachricht lässt sich ebenfalls eine Version einkodieren, zum Beispiel wenn JSON übermittelt wird: {„version“: „2.1“,…}. Nachteilig ist, dass URLs ohne Parameter und ohne Körper keinen Platz für eine Versionsnummer lassen.

Alle Lösungen lassen sich prinzipiell mit JAX-RS umsetzen, wobei die Lösung 1 am Einfachsten ist.

JAX-RS mit Jersey, Teil 1 (RESTful Web-Services)

Dieser Abschnitt stellt das Architekturprinzip REST vor und anschließend den Java-Standard JAX-RS. Es folgen Beispiele mit der JAX-RS-Referenzimplementierung Jersey.

Aus Prinzip REST

Bei RESTful Services liegt das Konzept zugrunde, dass eine Ressource über einen Webserver verfügbar ist und eindeutig über einen URI identifiziert wird.

Da unterschieden werden muss, ob eine Ressource neu angelegt, gelesen, aktualisiert, gelöscht oder aufgelistet werden soll, werden dafür die bekannten HTTP-Methoden verwendet:

HTTP-Methode Operation
GET Listet Ressourcen auf oder holt eine konkrete Ressource.
PUT Aktualisiert eine Ressource.
DELETE Löscht eine Ressource oder eine Sammlung von Ressourcen.
POST Semantik kann variieren, in der Regel aber geht es um das Anlegen einer neuen Ressource.

HTTP-Methoden und übliche assoziierte Operationen

Anders als SOAP ist REST kein entfernter Methodenaufruf, sondern eher vergleichbar mit den Kommandos INSERT, UPDATE, DELETE und SELECT in SQL.

Ursprung von REST

Die Idee, Web-Services mit dem Webstandard HTTP aufzubauen, beschrieb Roy Thomas Fielding im Jahr 2000 in seiner Dissertation „Architectural Styles and the Design of Network-based Software Architectures“. Das Architekturprinzip nennt er Representational State Transfer (REST) – der Begriff ist neu, aber das Konzept ist alt. Aber so wie im richtigen Leben setzen sich manche Dinge erst spät durch. Das liegt auch daran, dass SOAP-basierte Web-Services immer komplizierter wurden und sich die Welt nach etwas anderem sehnte. (Daher beginnen wir im Buch auch mit REST, und dann wird SOAP folgen.)

Wie sieht ein REST-URI aus?

Auf der einen Seite stehen die HTTP-Methoden GET, PUT, POST, DELETE und auf der anderen Seite die URIs, die die Ressource kennzeichnen. Ein gutes Beispiel einer REST-URL ist der Blog vom Java-Buch:

http://www.tutego.de/blog/javainsel/category/java-9/page/2/

Das Ergebnis ist ein HTML-Dokument mit ausschließlich den Beiträgen aus der Kategorie Java 9 und nur denen auf der zweiten Seite.

Fast daneben ist auch vorbei

Da auf den ersten Blick jede HTTP-Anfrage an einen Webserver wie ein REST-Aufruf aussieht, sollten wir uns im Klaren darüber sein, was denn kein REST-Aufruf ist. Eine URL wie http://www.bing.com/search?q=tutego ist erst einmal nicht der typische REST-Stil, da es keine Ressource gibt, die angesprochen wird. Da REST als leichtgewichtig und cool gilt, geben sich natürlich gerne Dienste ein bisschen RESTig. Ein Beispiel ist Flickr, ein Fotoservice von Yahoo. Das Unternehmen wirbt mit einer REST-API, aber es ist alles andere als REST und kein gutes Beispiel.[1] Das Gleiche gilt auch für Twitter, das lediglich einen rudimentären REST-Ansatz hat, aber in der Öffentlichkeit als REST pur wahrgenommen wird.

Jersey

Für Java gibt es mit JAX-RS einen Standard zum Deklarieren von REST-basierten Web-Services. Er wurde in der JSR-311, „JAX-RS: The Java API for RESTful Web Services“, definiert. Es fehlt noch eine Implementierung, damit wir Beispiele ausprobieren können:

  • Jeder Applikationsserver ab Java EE 6 enthält standardmäßig eine JAX-RS-Implementierung.
  • Da wir JAX-RS ohne einen Applikationsserver mit der normalen Java SE ausprobieren möchten, ist eine JAX-RS-Implementierung nötig, da das JDK die JAX-RS-API nicht mitbringt. Es ist naheliegend, die JAX-RS-Referenzimplementierung Jersey (https://jersey.github.io/) zu nutzen, die auch intern von Applikationsservern verwendet wird. Jersey x implementiert die JAX-RS 2.0 API.

Mit Jersey lässt sich entweder ein Servlet-Endpunkt definieren, sodass der RESTful Web-Service in einem Servlet-Container wie Tomcat läuft, oder der eingebaute Mini-HTTP-Server genutzt wird. Wir werden im Folgenden mit dem eingebauten Server arbeiten.

Download und JAR-Dateien

  • Beginnen wir mit dem Download der nötigen Java-Archive. Diese können wir einfach per Maven einbinden, oder per Hand laden. In Maven muss für unser Beispiel folgendes in die xml-Datei:
    • <dependency>
    • <groupId>org.glassfish.jersey.containers</groupId>
    • <artifactId>jersey-container-jdk-http</artifactId>
    • <version>2.25</version>
    • </dependency>

Wenn wir den händischen Weg nehmen ist es etwas aufwändiger, die einzelnen Jar-Dateien zusammenzusammeln. Die Jersey-Homepage https://jersey.github.io/ verweist für den Download auf https://jersey.github.io/download.html. Klicken wir dort auf

Jersey JAX-RS 2.0 RI bundle […] contains the JAX-RS 2.0 API jar, all the core Jersey module jars as well as all the required 3rd-party dependencies.

dann startet der Download von jaxrs-ri-2.x.y.zip. Das ZIP-Archiv können wir auspacken und dann folgende Archive in den Klassenpfad aufnehmen:

  • Aus dem Ordner api: ws.rs-api-2.0.1.jar. Enthält die JAX-RS-API (im Wesentlichen die Annotationen), aber keine Implementierung.
  • Aus dem Ordner lib: jersey-client.jar, jersey-common.jar, jersey-server.jar. Das ist die Jersey-Implementierung. jersey-media-jaxb.jar kann mit eingebunden werden, damit Jersey automatisch JAXB für die Konvertierung zwischen XML und Objekten verwendet.
  • Aus dem Ordner ext: Alle jar-Dateien. Enthält das, worauf die Jersey-Implementierung selbst zurückgreift; um es einfach zu machen: einfach alles.
  • Ein JAR müssen wir noch in den Klassenpfad dazusetzen, damit der Jersey-Server den im JDK eingebauten HTTP-Server nutzen kann, und zwar von http://central.maven.org/maven2/org/glassfish/jersey/containers/jersey-container-jdk-http/ das jersey-container-jdk-http-2.x.y.jar.

JAX-RS-Annotationen für den ersten REST-Service

JAX-RS definiert einige Annotationen, die zentrale Konfigurationen bei RESTful Web-Services vornehmen, etwa Pfadangaben, Ausgabeformate oder Parameter. In den nächsten Abschnitten werden diese Annotationen an unterschiedlichen Beispielen vorgestellt.

Die Annotationen @Path und @GET

Beginnen wir mit einem REST-Service, der einen simplen Text-String liefert:

package com.tutego.insel.rest;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;


 @Path( "dating" )
 public class DatingResource {
 
   @GET
   @Produces( MediaType.TEXT_PLAIN )
   public String meet() {
     return "Afra, Ange, Ceri, Dara, Ha, Jun, Sevan";

  }
 }

Die ersten beiden Annotationen sind zentral:

  • @Path: Ist die Pfadangabe, die auf den Basispfad gesetzt wird. Die Annotation ist gültig für einen Typ und eine Methode.
  • @GET: Die HTTP-Methode. Hier gibt es auch die Annotationen für die anderen HTTP-Methoden POST, PUT, …
  • @Produces: Die Annotation kann zwar grundsätzlich auch entfallen, aber besser ist es, deutlich einen MIME-Typ zu setzen. Es gibt String-Konstanten für die wichtigsten MIME-Typen, wie TEXT_XML oder MediaType.TEXT_HTML, und auch Strings wie „application/pdf“ können direkt gesetzt werden. Neben @Produces gibt es das symmetrische @Consumes.

Test-Server starten

Ein Java EE-Application-Server würde die Klasse aufgrund der Annotationen gleich einlesen und als REST-Service anmelden. Da wir die Klasse jedoch in der Java SE ohne Application-Server verwenden, muss ein REST-Server von Hand aufgebaut werden. Das ist aber problemlos, denn dafür haben wir jersey-container-jdk-http-2.x.y.jar eingebunden, sodass sich Jersey in den im JDK eingebauten HTTP-Server integriert.

ResourceConfig rc = new ResourceConfig().packages( "com.tutego.insel.rest" );
 HttpServer server = JdkHttpServerFactory.createHttpServer( 
    URI.create( "http://localhost:8080/api" ), rc );
 JOptionPane.showMessageDialog( null, "Ende" );
 server.stop( 0 );

Nach dem Start des Servers scannt Jersey die Klassen im genannten Paket daraufhin ab, ob sie passende JAX-RS-Annotationen tragen. Das Class-Scanning wurde aktiviert mit der package(…)-Methode beim ResourceConfig, es ist aber auch möglich, im Konstruktor von ResourceConfig direkt die JAW-RS-Ressourcen über ihre Class-Objekte aufzuzählen.

Die statische Methode JdkHttpServerFactory.createHttpServer(…) liefert ein Objekt vom Typ com.sun.net.httpserver.HttpServer, das Teil der internen Java-API ist. Die JdkHttpServerFactory stammt aus dem internen Jersey-Paket.

REST-Services konsumieren

Bei createHttpServer(…) ist als Testpfad „http://localhost:8080/api“ eingetragen, und zusammen mit @Path(„dating“) an der Klasse ergibt sich der Pfad http://localhost:8080/api/dating für die Ressource. Wir können die URL im Browser eintragen.

REST-Client mit Jersey

Sich im Browser das Ergebnis eines REST-Aufrufs anzuschauen, ist nicht das übliche Szenario. Oft sind es JavaScript-Programme im Browser, die REST-Aufrufe starten und die konsumierten Daten auf einer Webseite integrieren.

Ist es ein Java-Programm, das einen REST-Dienst anzapft, so kann dies mit einer einfachen URLConnection erledigt werden, was eine Standardklasse aus dem java.net-Paket ist, um auf HTTP-Ressourcen zuzugreifen. Doch Jersey definiert eine standardisierte API zum einfachen Zugriff. Es reicht ein Einzeiler:

System.out.println(
   ClientBuilder.newClient().target( "http://localhost:8080/api" )
                .path( "dating" ).request().get( String.class )
 );

Der API-Stil, den die Bibliotheksautoren hier verwenden, nennt sich Fluent-API; Methodenaufrufe werden verkettet, um alle Parameter wie URL oder MIME-Typ für die Anfrage zu setzen. Das ist eine Alternative zu den bekannten Settern auf JavaBeans bzw. einen überladenen Konstruktor mit unübersichtlichen Parameterlisten.

Um die Typen bei der javax.ws.rs.client-API etwas besser zu verstehen, schreiben wir alles ganz vollständig aus:

Client client = ClientBuilder.newClient();
 WebTarget target = client.target( "http://localhost:8080/api" );
 WebTarget resourceTarget = target.path( "dating" );
 Invocation.Builder request = resourceTarget.request( MediaType.TEXT_PLAIN );
 Response response = request.get();
 System.out.println( response.readEntity( String.class ) );

Das Response-Objekt hat auch eine Methode getStatus() für den HTTP-Statuscode und hasEntry(), um zu prüfen, ob es ein Ergebnis gibt und keinen Fehler. Apropos Fehler: Ist die URL falsch, gibt es eine RuntimeException – geprüfte Ausnahmen verwendet Jersey nicht.

Exception in thread „main“ javax.ws.rs.NotFoundException: HTTP 404 Not Found

[1]   Roy Fielding meint dazu nur: „Flickr obviously don’t have a clue what REST means since they just use it as an alias for HTTP. Perhaps that is because the Wikipedia entry is also confused. I don’t know.“ (Quelle: http://roy.gbiv.com/untangled/2008/no-rest-in-cmis).

Eclipse Oxygen (4.7) ist fertig

https://projects.eclipse.org/releases/oxygen

Alle Neuerungen unter https://www.eclipse.org/oxygen/noteworthy/.

 

Unter Java 9 bedarf es Vorbereitungen für den Start:

Launching on Java 9

If you are launching eclipse with a Java 9 build (either via system or via -vm option in eclipse.ini file) then, in eclipse.ini file, after -vmargs line add --add-modules=ALL-SYSTEM. This should launch eclipse. If you get an IllegalAccessException or InaccessibleObjectException, you can add --permit-illegal-access additionally after the -vmargs.

e.g.

...
--launcher.appendVmargs
-vmargs
--add-modules=ALL-SYSTEM
--permit-illegal-access
...

The switches can be entered on the command line after -vmargs, e.g.

$ ./eclipse -vm *[path to jdk9]*/bin -vmargs --add-modules=ALL-SYSTEM --permit-illegal-access

Weiterhin:

Support for Building Java 9 Applications

The Java™ 9 specification has not been released yet and so support has not yet been integrated into our standard download packages. You can add an early access preview to the Eclipse IDE, Oxygen Edition (4.7).

The Eclipse Java™ 9 Support (BETA) contains the following:

  • ability to add JRE and JDK 9 as installed JRE;
  • support for JavaSE-9 execution environment;
  • ability to create Java and Plug-in projects that use a JRE or JDK 9; and
  • ability to compile modules that are part of a Java project
  • For up-to-date information, please see Java 9 Support (BETA) for Oxygen in the Eclipse Marketplace.

Install the beta by dragging the install button onto your running Eclipse IDE, Oxygen Edition instance.

JSON-Verarbeitung mit der Java API for JSON Processing

Zum Verarbeiten von JSON-Dokumenten gibt es in der Java SE keine Standardklassen, sodass sich eine Reihe von Open-Source-Bibliotheken herausgeprägt haben; Jackson (http://tutego.de/go/jackson) gehört zu den populärsten Lösungen. 2013 wurde dann die JSR 353, „Java API for JSON Processing“ verabschiedet, die Teil der Java EE 7 ist.

Wir können die JSON-API in unseren Java SE-Programmen nutzen, müssen dafür aber Java-Archive im Klassenpfad einbinden. Die Referenzimplementierung befindet sich unter https://javaee.github.io/jsonp/. Am Einfachsten haben es Maven-Nutzer, sie binden in ihre POM folgende Abhängigkeiten ein:

<dependency>

  <groupId>javax.json</groupId>

  <artifactId>javax.json-api</artifactId>

  <version>1.1</version>

</dependency>

<dependency>

  <groupId>org.glassfish</groupId>

  <artifactId>javax.json</artifactId>

  <version>1.1</version>

</dependency>

Aufbauen von JSON-Objekten, Formatieren und Parsen

Der Typ JsonObject ist in der API zentral, denn er definiert ein hierarchisches Model mit den Schlüssel-Wertepaaren eines JSON-Objekts. Um ein JsonObject aufzubauen können wir über den JsonObjectBuilder gehen, oder wir lassen den Parser JsonReader aus einer String-Repräsentation ein JsonObject erzeugen. Zum formatierten Schreiben in einen Ausgabestrom können wir einen einfachen JsonWriter von der Json-Klasse holen – es geht aber noch einfacher über toString() – oder über die JsonWriterFactory arbeiten, falls wir eine hübsche eingerückte Ausgabe wünschen. Ein Beispiel:

Point p = new Point( 10, 20 );

JsonObjectBuilder objBuilder = Json.createObjectBuilder()

                                   .add( "x", p.x )

                                   .add( "y", p.y );

JsonObject jsonObj = objBuilder.build();


System.out.println( jsonObj );  // {"x":10,"y":20}


Json.createWriter( System.out ).write( jsonObj ); // {"x":10,"y":20}


Map<String, Boolean> config = new HashMap<>();

config.put( JsonGenerator.PRETTY_PRINTING, true );

JsonWriterFactory writerFactory = Json.createWriterFactory( config );


StringWriter out = new StringWriter();

writerFactory.createWriter( out ).write( jsonObj );

System.out.println( out );  // {\n    "x": 10, ...


JsonReader reader = Json.createReader( new StringReader( out.toString() ) );

System.out.println( reader.readObject().getInt( "x" ) ); // 10

Soll der assoziierte Wert ein Array sein, so wird dieser mit Json.createArrayBuilder().add(..).add(..) aufgebaut und gefüllt.

JSON-Streaming API

So wie es für XML eine Pull-API gibt, existiert diese auch für JSON-Dokumente; das ist von Vorteil, wenn die Daten sehr groß sind. Ein Beispiel zeigt das sehr gut:

URL url = new URL( "https://data.cityofnewyork.us/api/views/25th-nujf/rows.json?accessType=DOWNLOAD" );



try ( JsonParser parser = Json.createParser( url.openStream() ) ) {

  while ( parser.hasNext() ) {

    switch ( parser.next() ) {

      case KEY_NAME:

      case VALUE_STRING:

        System.out.println( parser.getString() );

        break;

      case VALUE_NUMBER:

        System.out.println( parser.getBigDecimal() );

        break;

      case VALUE_TRUE:

        System.out.println( true );

        break;

      case VALUE_FALSE:

        System.out.println( false );

        break;

      case VALUE_NULL:

      case START_ARRAY:

      case END_ARRAY:

      case START_OBJECT:

      case END_OBJECT:

        // Ignore

    }

  }

}

JAXB-Beans aus XML-Schema-Datei generieren

Da es für existierende XML-Dateien mühselig ist, die annotierten JavaBeans von Hand aufzubauen, gibt es einen Generator, genannt Java Architecture for XML Binding Compiler, kurz xjc. Er kann von der Kommandozeile, von Maven, aus einem Ant-Skript oder auch von Entwicklungsumgebungen aus aufgerufen werden. Er nimmt eine XML-Schema-Datei und generiert die Java-Klassen und eine ObjectFactory, die als – wie der Name schon sagt – Fabrik für die gemappten Objekte aus den XML-Elementen fungiert.

xjc aufrufen

Im ersten Schritt wechseln wir auf die Kommandozeile und testen entweder, ob das bin-Verzeichnis vom JDK im Suchpfad ist, oder wir wechseln in das bin-Verzeichnis, sodass wir xjc direkt aufrufen können, und folgende Ausgabe erscheint:

$ xjc -help

Verwendung: xjc [-options ...] <schema file/URL/dir/jar> ... [-b <bindinfo>] ...

Wenn dir angegeben wird, werden alle Schemadateien im Verzeichnis kompiliert.

Wenn jar angegeben wird, wird die /META-INF/sun-jaxb.episode-Binding-Datei kompiliert.

Optionen:

  -nv                :  Führt keine strikte Validierung der Eingabeschemas durch

  -extension         :  Lässt Herstellererweiterungen zu - Befolgt die

                        Kompatibilitätsregeln und App E.2 der JAXB-Spezifikation nicht strikt

  -b <file/dir>      :  Gibt externe Bindings-Dateien an (jede <file> muss ihre eigene Option -b haben)

                        Wenn ein Verzeichnis angegeben wird, wird **/*.xjb durchsucht

  -d <dir>           :  Generierte Dateien werden in diesem Verzeichnis gespeichert

  -p <pkg>           :  Gibt das Zielpackage an

  -httpproxy <proxy> :  set HTTP/HTTPS proxy. Format ist [user[:password]@]proxyHost:proxyPort

  -httpproxyfile <f> : Wird wie -httpproxy verwendet, verwendet jedoch das Argument in einer Datei zum Schutz des Kennwortes

  -classpath <arg>   :  Gibt an, wo die Benutzerklassendateien gefunden werden

  -catalog <file>    :  Gibt Katalogdateien zur Auflösung von externen Entity-Referenzen an

                        Unterstützt TR9401, XCatalog und OASIS-XML-Katalogformat.

  -readOnly          :  Generierte Dateien werden im schreibgeschützten Modus gelesen

  -npa               :  Unterdrückt die Generierung von Annotationen auf Packageebene (**/package-info.java)

  -no-header         :  Unterdrückt die Generierung eines Datei-Headers mit Zeitstempel

  -target (2.0|2.1)  :  Verhält sich wie XJC 2.0 oder 2.1 und generiert Code, der keine 2.2-Features verwendet.

  -encoding <encoding> :  Gibt Zeichencodierung für generierte Quelldateien an

  -enableIntrospection :  Aktiviert die ordnungsgemäße Generierung von booleschen Gettern/Settern zur Aktivierung von Bean Introspection-APIs

  -contentForWildcard  :  Generiert Contenteigenschaft für Typen mit mehreren von xs:any abgeleiteten Elementen

  -xmlschema         :  Behandelt Eingabe als W3C-XML-Schema (Standard)

  -relaxng           :  Behandelt Eingabe als RELAX NG (experimentell, nicht unterstützt)

  -relaxng-compact   :  Behandelt Eingabe als RELAX NG-Kompaktsyntax (experimentell, nicht unterstützt)

  -dtd               :  Behandelt Eingabe als XML DTD (experimentell, nicht unterstützt)

  -wsdl              :  Behandelt Eingabe als WSDL und kompiliert enthaltene Schemas (experimentell, nicht unterstützt)

  -verbose           :  Verwendet extra-verbose

  -quiet             :  Unterdrückt die Compilerausgabe

  -help              :  Zeigt diese Hilfemeldung an

  -version           :  Zeigt Versionsinformationen an

  -fullversion       :  Zeigt vollständige Versionsinformationen an







Erweiterungen:

  -Xinject-code      :  inject specified Java code fragments into the generated code

  -Xlocator          :  enable source location support for generated code

  -Xsync-methods     :  generate accessor methods with the 'synchronized' keyword

  -mark-generated    :  mark the generated code as @javax.annotation.Generated

  -episode <FILE>    :  generate the episode file for separate compilation

  -Xpropertyaccessors :  Use XmlAccessType PROPERTY instead of FIELD for generated classes

Eigentlich ist bis auf die Angabe der Schema-Quelle (aus einer Datei oder alternativ die URL) keine weitere Angabe nötig. Es ist aber praktisch, zwei Optionen zu setzen: -p bestimmt das Java-Paket für die generierten Klassen und -d das Ausgabeverzeichnis, in dem der Generator die erzeugten Dateien ablegt.

Bibelvers über eine Bibel-API beziehen

Für ein Beispiel wählen wir eine freie Bibel-API. Die URL sieht so aus:

http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter

Die Angabe von version ist optional, und bestimmt im gesetzten Fall die Sprache bzw. die Bibel-Ausgabe. Die Seite http://www.4-14.org.uk/xml-bible-web-service-api zeigt den Einsatz der einfachen API, bei der ganz simpel in die URL das Buch, Kapitel und Vers steht. Für unser Beispiel sieht das Ergebnis so aus:

<?xml version="1.0" encoding="UTF-8"?>

<bible>

 <title>Jn3:16</title>

 <range>

  <request>Jn3:16</request>

  <result>John 3:16</result>

  <item>

   <bookname>John</bookname>

   <chapter>3</chapter>

   <verse>16</verse>

   <text>Denn Gott hat die Welt so geliebt, daß er seinen eingeborenen Sohn gab, damit jeder, der an ihn glaubt, nicht verloren gehe, sondern ewiges Leben habe.</text>

  </item>

 </range>

 <time>0.007</time>

</bible>

Für unser Beispiel wollen wir das XML-Dokument nicht von Hand auseinanderpflücken, sondern JAXB soll uns eine gefüllte JavaBean mit allen Informationen liefern. Leider gibt es für dieses einfache XML-Format kein XML-Schema, sodass wir uns mit einem Trick behelfen: wir lassen uns von einem Dienst aus der XML-Datei ein Schema generieren, sodass wir damit zu xjc gehen können. Es gibt im Internet einige freie XML-nach-Schema-Konverter, zum Beispiel http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx. Das XML-Dokument wird in das Textfeld kopiert und nach dem Knopfdruck „Generate Schema“ folgt:

<?xml version="1.0" encoding="utf-16"?>

<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <xsd:element name="bible">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element name="title" type="xsd:string" />

        <xsd:element name="range">

          <xsd:complexType>

            <xsd:sequence>

              <xsd:element name="request" type="xsd:string" />

              <xsd:element name="result" type="xsd:string" />

              <xsd:element name="item">

                <xsd:complexType>

                  <xsd:sequence>

                    <xsd:element name="bookname" type="xsd:string" />

                    <xsd:element name="chapter" type="xsd:int" />

                    <xsd:element name="verse" type="xsd:int" />

                    <xsd:element name="text" type="xsd:string" />

                  </xsd:sequence>

                </xsd:complexType>

              </xsd:element>

            </xsd:sequence>

          </xsd:complexType>

        </xsd:element>

        <xsd:element name="time" type="xsd:decimal" />

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

Diese Ausgabe speichern wir in der Datei bible.xsd, damit sie von xjc verwendet werden kann.

Den Aufruf von xjc setzen wir in eine Batch-Datei:

PATH=%PATH%;C:\Program Files\Java\jdk-9\bin

xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd

Das Tool generiert alle Typen für den komplexen Typ aus dem XML-Schema, um eine Paket-Annotation festmachen zu können, und ObjectFactory, die einfache Fabrikmethoden enthält, um die gemappten Objekte aufbauen zu können. Die Ausgabe vom Programm ist:

…>xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd

Ein Schema wird geparst ...

Ein Schema wird kompiliert ...

com\tutego\insel\xml\jaxb\bible\Bible.java

com\tutego\insel\xml\jaxb\bible\ObjectFactory.java

TIPP: In Eclipse lässt sich xjc einfach aufrufen. Selektiere im Package-Explorer BIBLE.xsd, im Kontextmenü Generate • JAXB Classes…, dann wähle das Java-Projekt aus, anschließend Next. Im folgenden Dialog gib als Paket „com.tutego.insel.xml.jaxb.bible“ ein, dann klicke auf Finish. Wichtig! Damit xjc gefunden wird, muss das JDK aktiviert sein, nicht das JRE; nur das JDK bringt das Werkzeug xjc mit.

Dann kann ein Java-Programm den Service mit einer URL ansprechen und sich das Ergebnis-XML in eine JavaBean konvertieren lassen.

package com.tutego.insel.xml.jaxb;




import javax.xml.bind.JAXB;

import com.tutego.insel.xml.jaxb.bible.Bible;




public class BibleOnline {

  public static void main( String[] args ) {

    String url = "http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter";

    Bible bible = JAXB.unmarshal( url, Bible.class );

    System.out.println( bible.getRange().getItem().getText() );

  }

}

 

WICHTIG Das JAXB-Modul muss in Java 9 über einen Schalter auf der Kommandozeile hinzugenommen werden: –add-modules java.se.ee.

Konflikte in der Schema-Datei *

Leider haben viele XML-Schemas ein Problem, sodass sie nicht direkt vom Schema-Compiler verarbeitet werden können. Ein Beispiel zeigt das Dilemma:

<container>
  <head><content title="Titel"/></head>
  <body><content doc="doc.txt"/></body>
 </container>

In der hierarchischen Struktur heißt das in <head> und <body> vorkommende XML-Element gleich, nämlich content. Die Schema-Datei kann widerspruchslos definieren, dass die beiden XML-Elemente gleich heißen, aber unterschiedliche Attribute erlauben, sozusagen das Head-Content und das Body-Content. Allein durch ihre Hierarchie, also dadurch, dass sie einmal unter head und einmal unter body liegen, sind sie eindeutig bestimmt. Der Schema-Compiler von Java bekommt aber Probleme, da er diese hierarchische Information in eine flache bringt. Er kann einfach eine Klasse Head und Body aufbauen, aber bei <content> steht er vor einem Problem. Da die Schema-Definitionen unterschiedlich sind, müssten zwei verschiedene Java-Klassen unter dem gleichen Namen Content generiert werden. Das geht nicht, und xjc bricht mit Fehlern ab.

Fehler dieser Art gibt es leider häufig, und sie sind der Grund, warum aus vielen Schemas nicht einfach JavaBeans generiert werden können. Erfolglos ohne weitere Einstellungen sind beispielsweise DocBook, Office Open XML, SVG, MathML und weitere. Doch was könnte die Lösung sein? xjc sieht Konfigurationsdateien vor, die das Mapping anpassen können. In diesen Mapping-Dokumenten identifiziert ein XPath-Ausdruck die problematische Stelle und gibt einen Substitutionstyp an. Die Dokumentation auf der JAXB-Homepage weist Interessierte in die richtige Richtung.

JAXB-Plugins

Auf der Webseite https://github.com/javaee/jaxb2-commons gibt es eine Reihe nützlicher zusätzlicher Plugins für JAXB. Zu ihnen gehören:

  • JAXB2 Basic Plugins: Diese Plugin-Sammlung besteht aus Equals (erzeugt die equals(…)-Methode, wobei der Gleichheitstest über ein Equals-Strategie-Objekt austauschbar ist), HashCode (erzeugt hashCode(), wobei auch hier die Berechnung des Hashwerts über ein Strategie-Objekt erfolgt), ToString (erzeugt toString(), auch wieder über ein externes ToStringStrategy-Objekt), Copyable (erzeugt copyTo(…), um Objektzustände in ein anderes typkompatibles Objekt zu kopieren, und auch clone(); arbeitet dabei mit CopyStrategie), Mergeable (realisiert mergeFrom(…)-Methoden mit MergeStrategy-Objekten, die Zustände eines anderen Objekts mit den eigenen vereinen), JAXBIndex (generiert eine index-Datei, die alle generierten Schema-Klassen auflistet), Inheritance und AutoInheritance (erlauben JAXB-Beans, globale Elemente oder komplexe Typen von gewünschten Basisklassen zu erben oder Schnittstellen zu implementieren), Wildcard (spezifiziert, wie sich das Wildcard-Property verhalten soll), Setters (etwas andere Setter-Implementierung bei Sammlungen) und Simplify Plugin (komplexe Properties in Einzel-Properties aufspalten).
  • Value-Constructor Plugin: Jede JavaBean bekommt von xjc nur einen Standard-Konstruktor. Das Plugin gibt einen weiteren Konstruktor hinzu, der alle Attribute direkt initialisiert.
  • Default Value Plugin: Ein XML-Schema kann mit defaultValue vordefinierte Initialbelegungen für Attribute angeben. xjc ignoriert diese. Das Plugin wertet diese Vorbelegungen aus und initialisiert die Attribute der JavaBean gemäß den Werten.
  • Fluent API Plugin: Anstatt eine Bean nur mit Setter-Aufrufen zu initialisieren, etwa eine Person mit setName(…) und setAge(…), generiert das Fluent API Plugin kaskadierbare Methoden, sodass im Beispiel unserer Person nur new Person().withName(…).withAge(…) zu schreiben ist.

Parsen und Formatieren von Datumszeitwerten der Java Date Time API

Alle temporalen Typen überschreiben standardmäßig die toString()-Methode und liefern eine standardisierte Ausgabe. Weiterhin lassen sich die temporalen Typen auch bei String.format(…) und dem java.util.Formatter einsetzen.

format(…)-Methode

Daneben bieten die Typen eine format(…)-Methode, der ein Formatierungsobjekt übergeben wird, sodass individuelle Ausgaben nach einem Muster möglich sind.

Klasse Formatierungsmethode
LocalDateTime format(DateTimeFormatter formatter)
LocalDate format(DateTimeFormatter formatter)
LocalTime format(DateTimeFormatter formatter)
ZonedDateTime format(DateTimeFormatter formatter)
OffsetDateTime format(DateTimeFormatter formatter)

Temporale Klassen nutzen den gleiche Parametertyp DateTimeFormatter

Die format(…)-Methode nimmt einen DateTimeFormatter an, der die Ausgabe beschreibt. Wie kommen wir an einen DateTimeFormatter? Die Klasse hat keinen öffentlichen Konstruktor, sondern Konstanten und statische Fabrikmethoden. Über drei Varianten kommen wir zum konkreten Objekt:

  • Es gibt vordefinierte Konstanten für standardisierte ISO/RFC-Ausgaben,
  • Methoden für vordefinierte lang/kurz-Formate (auch lokalisiert) und
  • Methoden für komplett selbst zusammengestellte Ausgaben.

Die API-Dokumentation zählt die Konstanten mit ihren Formaten aus, Beispiele sind ISO_LOCAL_DATE oder ISO_ZONED_DATE_TIME. Wichtig zu bedenken ist, dass der temporale Typ die Felder auch haben muss! Ein ISO_ZONED_DATE_TIME ist zum Beispiel bei einem LocalDate nicht möglich.

Praktischer sind die statischen ofLocalizedXXX(…)-Methoden, die eine Aufzählung FormatStyle (FULL, LONG, MEDIUM, SHORT) annehmen:

  • DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle)
  • DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle)
  • DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle)
  • DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle)

Einen so aufgebauten DateTimeFormatter lässt sich Zusatzinformation geben mit diversen withXXX(…)-Methoden, etwa withLocale(Locale locale) oder withZone(ZoneId zone).

Beispiel

LocalDateTime now = LocalDateTime.now();

out.println( now.format( BASIC_ISO_DATE ) );

out.println( now.format( ISO_LOCAL_DATE_TIME ) );

out.println( now.format( ofLocalizedDate( SHORT ) ) );

out.println( now.format( ofLocalizedDateTime( MEDIUM ) ) );

out.println( now.format( ofLocalizedDateTime( FULL ).withLocale( FRANCE )

                                                    .withZone( ZoneId.of( "America/Cayenne" ) ) ) );

Die Ausgaben sehen so aus:

20170616

2017-06-16T20:39:11.2114379

16.06.17

16.06.2017, 20:39:11

vendredi 16 juin 2017 à 20:39:11 heure de la Guyane française

Eine völlig flexible Ausgabe ermöglicht ein Muster.

Beispiel

LocalDate now = LocalDate.now();
System.out.println( now );          // 2014-03-21
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( „d. MMMM yyyy“ );
String nowAsString = now.format( formatter );
System.out.println( nowAsString );  // 21. März 2014
LocalDate nowAgain = LocalDate.parse( nowAsString, formatter );
System.out.println( nowAgain );     // 2014-03-21

Die API-Dokumentation zu DateTimeFormatter zeigt einige vordefinierte Formatierungsobjekte und listet alle Formatspezifizierer auf.

parse(…)-Methode

Neben dem Formatieren bieten die Typen auch eine statische parse(…)-Methode, die einmal mit einem String-Parameter das Format erwartet, was toString() liefert, und eine Version mit zwei Parametern, wobei dann ein Formatierungsobjekt erlaubt ist, um genau das Format anzugeben, nach dem geparst werden soll.

Asynchrones Programmieren mit CompletableFuture (CompletionStage)

So schöne an Future-Objekten ist die Abarbeitung im Hintergrund und die Möglichkeit, später abzufragen, ob das Ergebnis schon da ist. Allerdings fehlt der Future-Schnittstelle eine Methode, automatisch nach der Fertigstellung einen Folgeauftrag abzuarbeiten. Dafür bietet die Java-Bibliothek eine spezielle Unterklasse CompletableFuture. Die Klasse implementiert die Schnittstelle CompletionStage, die vermutlich die größte Anzahl Operationen in der gesamten Java SE hat. Der Typname drückt aus, das es um die Fertigstellung (engl. completion) von Abschnitten (engl. stage) geht.

Ein Beispiel für einen trinkfesten mutigen Piraten:

package com.tutego.insel.concurrent;




import java.time.LocalTime;

import java.util.concurrent.CompletableFuture;

import java.util.concurrent.TimeUnit;

import java.util.logging.Logger;




class Pirate {




  public static void main( String[] arg ) throws Throwable {




    String result = CompletableFuture.supplyAsync( Pirate::newName )

                                     .thenApply( Pirate::swears )

                                     .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks )

                                     .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks )

                                     .get();

    System.out.println( result ); // Pirat Guybrush flucht und trinkt dann 10 Flaschen Rum und trinkt dann 11 Flaschen Rum

  }




  static String newName() {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    return "Pirat Guybrush";

  }




  static String swears( String pirate ) {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    return pirate + " flucht";

  }




  static CompletableFuture<Integer> drinkRum() {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    try { TimeUnit.SECONDS.sleep( 1 ); } catch ( Exception e ) { }

    return CompletableFuture.supplyAsync( () -> LocalTime.now().getSecond() );

  }




  static String combinePiratAndDrinks( String pirat, int bottlesOfRum ) {

    Logger.getGlobal().info( "" + Thread.currentThread() );

    return pirat + " und trinkt dann " + bottlesOfRum + " Flaschen Rum";

  }

}

Die Ausgabe ist:

Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum

INFORMATION: Thread[main,5,main]

Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate newName

INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main]

Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate swears

INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main]

Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks

INFORMATION: Thread[main,5,main]

Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum

INFORMATION: Thread[main,5,main]

Juni 15, 2017 10:41:58 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks

INFORMATION: Thread[main,5,main]

Pirat Guybrush flucht und trinkt dann 57 Flaschen Rum und trinkt dann 58 Flaschen Rum

Zum Programm: Zunächst muss die Kette von Abschnitten aufgebaut werden. Das kann entweder mit dem Standardkonstruktor geschehen, oder mit statischen Methoden. In unserem Fall nutzen wir supplyAsync(Supplier<U> supplier). Die Methode nimmt sich einen freien Thread aus dem ForkJoinPool.commonPool() und lässt den Thread den supplier abarbeiten. Das Ergebnis ist über die Rückgabe, einem CompletableFuture, abrufbar. Als nächsten wenden wir thenApply(Function<? super T,? extends U> fn) an, die vergleichbar ist mit einer map(…)-Operation eines Streams. Interessant wird es bei thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn); sie verbindet das Ergebnis der eigenen CompletionStage über eine Funktion mit einer anderen CompletionStage, die wir in unserem Fall auch wieder mit supplyAsync(…) aufbauen. So kombinieren wir zwei unabhängige CompletionStage miteinander und synchronisieren das Ergebnis. Wir können das gut an der Ausgabe auslesen, dass drinkRum ganz am Anfang schon ausgeführt wird, und zwar vom Thread[main,5,main], nicht vom ForkJoinPool, weil es unabhängig von den anderen läuft.

Versionskennungen aufbauen, parsen, vergleichen

In Java 9 wurde die Kennung für die Java-Version standardisiert. Wo früher von zum Beispiel 1.9.0_31-b08 die Rede war, heißt es heute 9.1.4+8; die 1. ist also verständig verschwunden. Auch eigene Programme können Versionskennungen nutzen und die Java SE stellt eine Klasse bereit, mit der sich zum Beispiel Versionen vergleichen lassen. Details zu den sogenannten semantischen Versionierung liefert http://semver.org/lang/de/.

Versionskennung

Ein Versionskennung hat die Form $MAJOR.$MINOR.$SECURITY. Das Schema besteht aus drei Teilen, die durch Punkte voneinander getrennt sind:

  1. Hautversion (engl. major version). Wird immer bei zentralen Änderungen und Updates um eine Stelle erhöht. Oft verbunden mit inkompatiblen API-Änderungen. Dann wird die Unterversion zurück auf 0 gesetzt.
  2. Unterversion (engl. minor verson). Wird erhöht bei kleineren Änderungen, wie Bug-Fixes. Das Release selbst bleibt rückwärtskompatibel.
  3. Sicherheitsrelease/Patch. Wird erhöht nach kritischen Sicherheitsupdates. Wichtig: Die Release-Nummer wird nicht auf 0 zurückgesetzt, wenn die Unterversion erhöht wird. Ein hoher Zähler der Sicherheitsreleases weist auf viele Änderungen hin, unabhängig von der Unterversion.

Die Versionen sind rein numerisch und der Gesamtstring matcht auf den regulären Ausdruck [1-9][0-9]*((\.0)*\.[1-9][0-9]*)*. Die Kennung endet nicht mit 0 – so wird $SECURITY weggelassen, wenn sie 0 ist und $MINOR wird nicht gesetzt, wenn $MINOR und $SECURITY beide 0 sind.

Versionsstring

Ein Versionsstring ist eine Versionskennung mit einem optionalen Zusatz von Informationen wie einer Vorversion (engl. pre release) oder Build-Informationen. Den Aufbau erklärt die API-Dokumentation an der Klasse Runtime.Version.

Version-API

Die Runtime-Klasse hat unter Java 9 eine innere Klasse Version bekommen, die so einen Versionsstring aufbauen und parsen kann. Die Version der aktuellen Laufzeitumgebung liefert die statische Methode version().

Beispiel

Gib alle Informationen über den Versionsstring aus:

System.out.println( Runtime.version() ); // 9-ea+159

System.out.println( Runtime.version().major() ); // 9

System.out.println( Runtime.version().minor() ); // 0

System.out.println( Runtime.version().security() ); // 0

System.out.println( Runtime.version().pre() ); // ea

System.out.println( Runtime.version().build() ); // 159

System.out.println( Runtime.version().optional() ); // Optional.empty

Eigene Version-Objekte lassen sich aus einem Versionsstring aufbauen mit der einzigen statischen Methode der Klasse Version, und zwar parse(String).

Beispiel

Version version = Version.parse( "9.2.4+45" );

System.out.println( version.version() );  // [9, 2, 4]

An dem Beispiel ist auch version() abzulesen, die eine numerische Liste mit Versionsnummern liefert.

Neben diesen Abfragemethoden kommen weitere Methoden hinzu. Zunächst überschreibt Version die Object-Methoden equals(…), hashCode() und toString(). Und da Version die Schnittstelle Comparable<Runtime.Version> implementiert, hat die Klasse eine Methode compareTo(Runtime.Version), was die Versionsnummern in eine Ordnung bringt. Zusätzlich gibt es compareToIgnoreOptional(Runtime.Version) und equalsIgnoreOptional(Object), was so etwas wie Build-Informationen ignoriert.

Java 9 wieder verschoben um 2 Monate

Auf den vermutlich 21. September 2017.  Aus der E-Mail http://mail.openjdk.java.net/pipermail/jdk9-dev/2017-May/005864.html von Mark Reinhold:

As you probably know by now, the JCP Executive Committee (EC) recently voted [1] not to approve JSR 376, the Java Platform Module System [2], for the next stage of the process.

This vote does not mean that JSR 376 is dead, nor that Jigsaw has been rejected. It only means that the EC raised a number of concerns that it wanted the JSR 376 Expert Group (EG) to address. The JCP rules give the EG thirty days, until 7 June, to submit a revised specification for a second EC vote, which will end no later than 26 June [3].

The JSR 376 EG held a series of conference calls over the past two weeks in order discuss the EC’s concerns [4]. The net impact of those meetings on JDK 9 itself was to clarify the specification of the module system’s resolution algorithm, work on which had already begun, and to add one five-line method to the module-system API. These changes, together with additional clarifications to the JSR 376 and JSR 379 (Java SE 9) [5] Specifications, will hopefully address the EC’s concerns.

In order to be ready for all possible outcomes I suggest that here in the JDK 9 Project we continue to work toward the current goal of producing an initial Release Candidate build on 22 June [6], but adjust the GA date in order to accommodate the additional time required to move through the JCP process. To be specific, I propose that we move the GA date out by eight weeks, from 27 July to 21 September.

Comments on this proposal from JDK 9 Committers are welcome, as are reasoned objections. If no such objections are raised by 23:00 UTC next Tuesday, 6 June, or if they’re raised and satisfactorily answered, then per the JEP 2.0 process proposal [7] this will be the new schedule for JDK 9.