JEE – Ende mit Schrecken oder Schrecken ohne Ende?

Die Geschichte von Oracle und Jakarta EE

Im Herbst 2017 entschloss sich Oracle zur Überführung der Java EE in ein Open-Source-Projekt unter dem Dach der Eclipse Foundation. In meinem Beitrag https://javaeeblog.wordpress.com/2017/09/13/oracle-schlaegt-verlagerung-von-java-ee-in-die-eclipse-foundation-vor/ von damals war ich zuversichtlich, dass dieser Schritt Java EE aus dem starren Korsett des JCP befreien und der Plattform die Modernität und Agilität ermöglichen würde, die neben der Verlässlichkeit und Stabilität in aktuellen Projekten benötigt wird.

Erste Zweifel kamen mir bei der Nachricht, dass Oracle die Rechte am Namen „Java EE“ nicht abgeben würde. Als Folge davon müssen alle Vorkommen von „Java EE“ durch den neuen Namen „Jakarta EE“ ersetzt werden. Nun ist Textersatz nicht wirklich kompliziert, also was soll’s?

Es verbleibt aber genauso das geistige Eigentum an den bislang von Oracle geführten Spezifikationen in den Händen von Oracle, so dass sich nach meiner Beobachtung nun viele Monate lang JEE-Experten damit beschäftigen durften, Spezifikationen so umzuschreiben, dass sie zwar inhaltlich kompatibel bleiben, aber keine schützenswerte Originalformulierung mehr enthalten.

Diese Zeit hätte man sehr gut in die Weiterentwicklung der Plattform stecken können. Glücklicherweise gibt es das Projekt MicroProfile (https://microprofile.io/), das in der letzten Zeit ganz und garnicht paralysiert herumgelegen hat, sondern mit wirklich hoher Innovationsgeschwindigkeit JEE-Erweiterungen entwickelt hat, die dringend notwendig sind, um JEE in der Greifvogelvoliere von immer kleineren Services irgendwo in den Wolken flugfähig zu halten.

Nun kommt aber noch der dicke Brocken, dass man sich nicht auf eine Weiterentwicklung des Paketnamensraums javax einigen konnte: Die bisherigen Pakete dürfen nur unverändert weiter genutzt werden, Neu- und Weiterentwicklungen müssen in neuen Paketen (jakarta) geschehen. Einfacher Textersatz ist hier natürlich wiederum möglich, bricht aber die Kompatibilität bestehender Anwendungen und Bibliotheken mit der neuen, paketumbenannten Plattform.

Wer sich nun mit Häme zu Aussagen wie „JEE ist eh Sch…, habe ich doch schon lange gesagt. Mit Spring ist alles besser!“ versteigt, sollte mal nachschauen, wieviele javax er in seinem Code findet.

Mir erschließt sich nicht, welchen Wert Oracle in den zurückgehaltenen Rechten sieht. Für mich als Entwickler und Berater scheint der Wert zukünftig bei 0 $ zu liegen. Und dafür erzeugt man große Verärgerung und Kosten bei Kunden und Anwendern? Nun bin ich kein Jurist, insbesondere kein Markenrechtler. Ich muss das also nicht verstehen.

Blick nach vorne

Was kann man nun in dieser verfahrenen Situation tun? Man könnte eine Koexistenz von javax und jakarta anstreben, Neuerungen wie gefordert in jakarta einführen und javax als Basis nutzen. Basis im Sinne von Basisklasse bzw. Basisinterface bedeutet aber eine (zu) enge Kopplung an die alten Versionen: API-Änderungen müssten sich i. W. auf Erweiterungen beschränken und man würde die ganzen alten Zöpfe weiter mit sich herumschleppen.

Es wird also darauf hinauslaufen, dass betroffene Pakete von javax.xyz in jakarta.xyz umbenannt werden. Das bedeutet zwar breaking Changes, bietet aber die Möglichkeit, veraltete APIs abzustoßen. Die erste Version, die dies betrifft, ist Jakarta EE 9, denn Jakarta EE 8 ist vollständig kompatibel zu Java EE 8, nutzt also weiterhin die bestehenden javax-Pakete. Es wird derzeit diskutiert, ob die Umbenennung der Pakete in einem Big Bang geschehen soll oder inkrementell immer dann, wenn Änderungen an einer Teilspezifikation durcheführt werden sollen. Ersteres hätte den Charme, mit einem – allerdings großen – Schritt sofort Unabhängigkeit zu erreichen. Bei der inkrementellen Vorgehensweise könnte man darauf spekulieren, dass sich manche Teile der Plattform zumindest in näherer Zukunft gar nicht ändern werden und man den Aufwand in diesem Bereich also sparen könnte. Ein Festlegung der Marschrichtung sollte noch im Juni 2019 geschehen.

Die Kompatibilität von Anwendungen älterer Versionen zu neuen Laufzeitplattformen ist einer der großen Werte der JEE. Ihn gilt es auf jeden Fall zu erhalten. Ich bin mir sicher, dass die Anbieter der modernen JEE-Server wie WildFly, OpenLiberty, Payara oder TomEE Mittel finden werden, z. B. durch Bytecode-Manipulation zur Deployment-Zeit, Altanwendungen mit javax-Paketen auf Jakarta-EE-9+-Servern lauffähig zu erhalten.

Das Ganze erzeugt nicht gerade unerheblichen Aufwand. Er ist es aber wert, angenommen zu werden. Kaum eine Plattform für Enterprise-Anwendungen hat ein ähnliches Gewicht, eine ähnliche Verbreitung in Projekten bei gleichzeitiger Flexibilität hinsichtlich der Laufzeitplattform – ob klassische Server wie die oben genannten oder Micro-Frameworks wie KumuluzEE, Meecrowave oder Quarkus. Wenn nun juristische Verkrustungen entfallen und die weitere Entwicklung ohne Korsett geschehen, kann’s doch nur super werden!

Bis bald – vielleicht auch in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
https://gedoplan-it-training.de/

Werbeanzeigen

OpenCSV

XML und JSON sind fantastische, leichtgewichtige und technisch gut zu verarbeitende Schnittstellen-Formate. Es gibt da aber ein weiteres Format, welches den Beiden den Rang abläuft, zumindest wenn es um die inflationäre Verwendung geht: CSV. Wenn wir einmal nicht darum herum kommen CSV Dateien zu lesen oder zu schreiben, sollten wir zumindest versuchen dies auf eine Art und Weise zu tun die unsere Nerven schont. Ein solcher Weg: OpenCSV

Die Grundidee hinter OpenCSV ist weder neu noch kompliziert. Ähnlich wie bei JPA, JSON oder XML versehen wir unser Model oder DTO-Klassen mit Annotationen die später für das Lesen / Schreiben der Daten sorgen soll. Später im Programm übergeben wir den entsprechenden Funktionen nur einen InputStream und eine so annotierte Klasse und für jede Zeile innerhalb unserer CSV Datei erhalten wir eine Objekte-Instanz ( bzw. beim Schhreiben: für jede Objektinstanz eine Zeile in der CSV Datei).

Für die einfachste Form dieser Annotationen existieren zwei Varianten:

    @CsvBindByPosition(position = 0)
    private Long id;

    @CsvBindByName(column = "customerid")
    private Long customerid;

Hier wird festgelegt ob das Mapping basierend auf der Position (Variante 1) oder basierend auf einer Kopfzeile innerhalb der CSV Datei (Variante 2) geschehen soll. Das Einlesen einer CSV Datei ist dann schnell erledigt:

demo.csv

customerid;firstname;lastname;registerdate;discount
1;Max;Muster;01.05.2010;5
2;Adam;Bean;20.06.2011;10
3;Lisa;Müller;08.08.2012;15
4;Lischen;Müller;27.09.2013;20

einlesen:

 List<Customer> beans = new CsvToBeanBuilder<Customer>(...inputstream...)
               .withSeparator(';')
               .withType(Customer.class)
               .build().parse();

Der Vorteil liegt auf der Hand. Dank der Annotationen haben wir eine sehr gut verständliche und wartbare Struktur welche die CSV Datei wiederspiegelt.

Oftmals reicht ein solches „einfaches“ Mapping natürlich nicht aus, da wir möglicherweise ganze Objektgraphen in eine flache CSV-Struktur bringen wollen. Zum einen wird beim Schreiben von CSV Dateien der entsprechende Getter aufgerufen wo ein einfaches Mapping statt finden kann. Flexibler und nicht nur für das Schreiben sondern auch für das Lesen von CSV Dateien sind Converter die registriert werden können:

public class Material {

    @CsvBindByPosition(position = 0)
    private Long id;

    @CsvBindByPosition(position = 1)
    private String description;

    @CsvBindAndSplitByPosition(position = 2, elementType = Price.class, splitOn = "\\|", converter = PriceConverter.class)
    private List<Price> prices;

    @CsvBindByPosition(position = 3)
    private Double averagePrice;

    public Double getAveragePrice() {
        return prices.stream().map(Price::getPrice)
                .mapToDouble(BigDecimal::doubleValue)
                .average()
                .orElse(0.);
    }
}

PriceConverter.java :

public class PriceConverter extends AbstractCsvConverter {

    private final DecimalFormat decimalFormat = new DecimalFormat("#,##");

    @Override
    public String convertToWrite(Object value) throws CsvDataTypeMismatchException {
        final Price price = (Price) value;
        final String priceValue = decimalFormat.format((price).getPrice());
        final String until = price.getValidTo().format(DateTimeFormatter.ISO_DATE);
        final String from = price.getValidTo().format(DateTimeFormatter.ISO_DATE);
        return String.format("%s*%s*%s|", from, until, priceValue);
    }

    @Override
    public Object convertToRead(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException {
        final String[] splits = value.trim().split("\\*");
        LocalDate from=LocalDate.parse(splits[0], DateTimeFormatter.ISO_DATE);
        LocalDate to=LocalDate.parse(splits[1], DateTimeFormatter.ISO_DATE);
        BigDecimal price = new BigDecimal(splits[2]);
        return new Price(from, to, price);
    }
}

Für die Vollständigkeit, das Schreiben:

        Writer writer = new FileWriter("out.csv");
        StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer).withSeparator(';').build();
        beanToCsv.write(materials);
        writer.close();

Es gibt noch einige mehr Annotationen ( Datumsformat, individuelles Mapping für Schreiben und Lesen…) mit dessen Hilfe wir in den Prozess eingreifen können. Die Bibliothek ist bezüglich seines Umfangs sehr übersichtlich, bietet aber genug Stellen sehr individuell auf das Schreiben und Lesen von CSV Dateien ein zu wirken. Allemal ein Blick Wert.

Github? Klar.

JSF+Bean Validation, Groups – switch

Bean Validation (JSR 380) und Jave Server Faces sind inzwischen ein eingespieltes Team und werden in den meisten Projekte eingesetzt. Ein nettes Features welches wir schon in einem älteren Blog-Beitrag beleuchtet haben ( hier ) sind Validierungs-Gruppen mit denen wir unterschiedliche Regel-Sets zusammenstellen können

Wie schon im angesprochenen Artikel erläutert arbeiten wir bei den BeanValidation Groups mit einem Marker-Interface:

public class DemoModel {

    @NotNull(groups = OnTransmit.class)
    @Size(min = 4, groups = OnTransmit.class)
    private String firstname;

    @NotNull(groups = {Default.class, OnTransmit.class})
    @Size(min = 4, groups = {Default.class, OnTransmit.class})
    private String lastname;
}

Diese Gruppen lassen sich dann entwede programmatisch bei der Validierung heranziehen oder seit JSF 2.1 mittels f:validateBean in unserem JSF Template referenzieren. Doch was tun wenn wir dynamisch entscheiden wollen welche Gruppen zur Validierung heran gezogen werden sollen? Sagen wir auf Basis des verwendeten Buttons? Das macht die Sache hier schon etwas schwieriger. JSF bietet hier allerdings eine Möglichkeit, in der faces-config.xml registrieren wir einen globalen Validator:

    <application>
        <default-validators>
            <validator-id>ConditionalBeanValidator</validator-id>
        </default-validators>
        <validator>
            <validator-id>ConditionalBeanValidator</validator-id>
            <validator-class>de.gedoplan.blog.jsf.validation.ConditionalBeanValidator</validator-class>
        </validator>
    </application>
public class ConditionalBeanValidator extends BeanValidator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) {
        setValidationGroups(retrieveValidationGroup(context));
        super.validate(context, component, value);
    }

    private String retrieveValidationGroup(FacesContext context) {
        if (context.getExternalContext().getRequestParameterValuesMap().containsKey("GEDTRANSMIT")) {
            return OnTransmit.class.getName();
        }

        return null;

    }
}

Dieser Validator ( implementiert javax.faces.validator.BeanValidator ) kann nun entscheiden/festlegen welche Bean Validation Gruppen verwendet werden sollen, ganz global, ohne das wir jedes einzelne Eingabefeld mit einem entsprechenden Binding versehen müssen. Idealerweise dient dafür ein fachliches Statusfeld, sollte so etwas nicht vorhanden sein ist auch folgendes Vorgehen über einen entsprechenden HTTP Parameter der beim Button angegeben wird denkbar:

                <h:commandButton action="#{demoController.submit()}" value="submit" id="submit"/>
                <h:commandButton action="#{demoController.transmit()}" value="transmit" id="transmit">
                    <f:param name="GEDTRANSMIT" />
                </h:commandButton>

Achtung! An dieser Stelle ist vorsicht geboten = in diesem Beispiel wird die Auswahl der Validierungs-Gruppe über einen Request-Parameter gesteuert, technisch wäre es durch Manipulation des HTTP Requests also möglich die transmit() Methode auf zu rufen ohne das die korrekte Validierung abläuft. Bei kritischen Prozessen sollte hier in der „transmit“ Methode (bzw. in einem entsprechenden Service) die korrekte Validierung (nocheinmal) durchgeführt werden oder zumindest das Vorhandensein des Parameters geprüft werden.

Github? Klaro

https://github.com/GEDOPLAN/jsf-custom-validator

Unterhaus weiterhin einig im Nicht-entscheiden – droht jetzt der harte Pexit?

Das britische Parlament hat das Austrittsabkommen zum dritten Mal abgelehnt. Bis zum 12. April muss geklärt werden, wie es weitergehen soll. Droht jetzt auch für den beliebten JEE-Server Payara der Pexit, d. h. die Abschottung vom Rest der Welt?

Der Server wird von der Payara Services Ltd in Malvern, United Kingdom, angeboten und als Open Source Software entwickelt. Könnte bei einem Pexit ohne Deal Entwicklern aus dem restlichen Europa der freie Zugang zum Quellcode entzogen werden?

Auf der Payara-Webseite finden sich Aussagen wie „Always Open Source“, „Public open source development“ und „encouraging users’ involvement“, aber lassen sich diese vor dem Hintergrund der entscheidungsunfähigen Politik noch halten?

Payara hat neben dem Hauptsitz auch ein Office in Portugal. Vielleicht lassen sich Downloads, Contributions und Service Requests zukünftig über dieses europäische Büro abwickeln.

Die letzten Entscheidungen stehen noch aus; hoffentlich kann Payara doch noch in der Community verbleiben!

Übrigens: Wir bieten unsere JEE-Kurse auch auf Payara an und das vermittelte Wissen ist garantiert nicht vom britischen Parlament abhängig. Schauen Sie doch mal vorbei: https://gedoplan-it-training.de/.

NetBeans – Ordner ausblenden

Immer mal wieder kommt es vor das wir in unseren Projekt-Ordnern Inhalte vorfinden die nicht teil unserer direkten Arbeiten sind. Das können generierte Bestandteile aus anderen Prozessen sein oder in der Web-Entwicklung so etwas wie der „node-modules“ Ordner ( Projekt Abhänigkeiten ).

Das macht unsere IDE zum einen unnötig langsam da diese Ordner / Strukturen ja genauso analysiert und ausgewertet werden wie unsere eigenen Sourcen und auf der anderen Seite meldet NetBeans hier regelmäßig Fehler auf die wir jedoch keinen Einfluss haben. Es gab einmal ( in PHP Projekten wohl immer noch ) eine Möglichkeit in NetBeans Ordner für die IDE aus zu blenden. Diese Funktion ist leider verschwunden und steht für Java- und HTML5-Projekte nicht zur Verfügung. In einem NetBeans-BugTicket zu dem Thema hat jemand eine schnelle und einfach Lösung entwickelt: https://netbeans.org/bugzilla/show_bug.cgi?id=238709#c28

Durch ein kleine Plugin welches installiert werden kann ( getestet mit NetBeans 8,9 und 10) und das Anlegen einer Datei „.nbignore“ im betreffenden Ordner wird dieser einfach ausgeblendet.

Optimal ist die Lösung sicherlich nicht: der Ordner verschwindet aus der Anzeige der IDE vollständig und kann nur über den normalen Explorer noch bearbeitet werden das muss dem Entwickler bewusst sein. Zudem ist es unschön IDE-spezifische Dateien in unseren Projekten ab zu legen. Eine einfache und prakmatische Lösung ist es trotzdem und allemal besser als falsche Fehlermeldungen. Bleibt zu hoffen das wir diese Funktion eines Tages auch nativ wieder in NetBeans finden werden…


Wie läuft’s denn so? Monitoring mit MicroProfile Metrics

In meinem Blog-Eintrag „Alles gesund? Health checking mit MicroProfile Health“ habe ich dargestellt, wie einfach man den Gesundheitszustand einer Anwendung auf Basis von MicroProfile Health veröffentlichen kann. Und wenn man nun nicht nur wissen möchte, ob ein Service läuft, sondern mehr Details über den Service-Zustand erfahren möchte? Dann unterstützt uns ein weiterer Baustein: MicroProfile Metrics.

Telemetriedaten out of the box

Über den Rest-Endpoint /metrics/base werden ohne weiteren Programmieraufwand Basis-Telemetriedaten zur Verfügung gestellt:

{
  "memory.maxHeap": 8342470656,
  "memory.usedHeap": 259349504,
  "gc.G1 Young Generation.count": 28,
  "cpu.systemLoadAverage": 0.6,
  "thread.count": 111,
  ...
}

Die Daten werden in JSON geliefert, wenn man sie mit diesem Media Type anfordert (z. B. curl -H "Accept: application/json" localhost:8080/metrics/base). Mit dem Default-Media-Type text/plain werden die Daten passend für die Verarbeitung in Prometheus geliefert.

Der Endpoint /metrics/vendor ist für herstellerspezifische Messdaten aus der genutzten Plattform vorgesehen.

Anwendungsspezifische Metriken

EE-Anwendungen können weitere Messdaten bereitstellen. So kann man bspw. Methoden von CDI Beans mit der Annotation @Gauge versehen:

  @Gauge(name = "answerToLifeUniverseAndEverything", absolute = true, unit = MetricUnits.NONE)
  public long getAnswerToLifeUniverseAndEverything() {
    return 42;
  }

Die derart zur Verfügung gestellten Werte werden im Endpoint /metrics/application veröffentlicht:

{
  "answerToLifeUniverseAndEverything": 42
}

Neben dieser Möglichkeit, eigene Messwerte zu berechnen und zu veröffentlichen, kann man auch Telemetriedaten durch das Framework aggregieren lassen:

  @GET
  @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
  @Timed(name = "personList", absolute = true)
  public List getAll() {
    return this.personRepository.findAll();
  }

@Timed bewirkt hier, dass die Ausführungszeiten der annotierten Methode gemessen und zu verschiedenen statistischen Werten aggregiert werden:

{
  "personList": {
    "p50": 4922800.0,
    "p75": 5160200.0,
    "p95": 1.00322E7,
    "p98": 1.00322E7,
    "p99": 1.00322E7,
    "p999": 1.00322E7,
    "min": 3218000.0,
    "mean": 5476794.800597489,
    "max": 1.00322E7,
    "stddev": 2340795.005960796,
    "count": 5,
    "meanRate": 0.6766077577220262,
    "oneMinRate": 1.0,
    "fiveMinRate": 1.0,
    "fifteenMinRate": 1.0
  }
}

Analog könnte mit @Counted ein Aufrufzähler für eine Methode etabliert werden.

Gesamt-Metrik

Der Endpoint /metrics fasst die bislang genannten Datengruppen in ein Gesamtergebnis zusammen.

Plattformen

MicroProfile Metrics wird u. a. von folgenden JEE-Servern unterstützt:

  • OpenLiberty (mit aktivierten Features mpMetrics-1.1 und monitor-1.0).
  • Payara.
  • WildFly (Achtung: WildFly hat unterschiedliche Ports für Applikations- und Management-Zugriffe, im Default 8080 und 9990. Der o. a. REST-Endpoint /metrics steht über den Management-Port zur Verfügung).

Die genannten Klassen befinden sich bspw. in der Maven-Dependency org.eclipse.microprofile:microprofile.

Demo

In https://github.com/GEDOPLAN/metrics-demo finden Sie ein Beispielprojekt mit einem REST-Service, dessen GET– und POST-Methoden so annotiert sind, dass Zeiten bzw. Aufrufzahlen gemessen werden. Der o. a. Service, der die legendäre 42 als Ergebnis liefert, ist ebenfalls im Beispielprojekt enthalten. Die Anwendung kann als WAR-File auf einen der genannten Server deployt werden. Alternativ können über vorkonfigurierte Maven-Profile Docker-Images zum direkten Ausprobieren erstellt werden.

Bis bald – vielleicht auch in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
https://gedoplan-it-training.de/

JEE Klassen > TypeScript

Ein JEE Backend ist mit wenig Aufwand über eine JSON basierte REST-Schnittstelle zur Verfügung gestellt. An diesem Punkt kann sich der Java Entwickler zurücklehnen und die Angular Entwickler machen lassen. Als Fullstackentwickler ( oder freundlicher Java Entwickler ) können wir uns ( oder unseren Kollegen ) das Leben leicht leichter machen.

In aller Regel werden wir unsere Angular Anwendung mittels TypeScript schreiben und hier immer wo es geht auf ein typsicheres Vorgehen setzen. Das erfordert es allerdings eigene TypeScript Definitionsdateien zu schreiben welche unsere Datenmodelle ( die wir ja bereits in Java geschrieben haben ) in die TypeScript-Welt bringt. Ein Maven-Plugin welches hier Hilft:

cz.habarta.typescript-generator # typescript-generator-maven-plugin

Dieses registrieren wir in unserer pom.xml, versehen mit einigen zusätzlichen Konfigurationen und wir erhalten eine *.d.ts Datei welche wir in die Angular Entwicklung mit einfließen lassen können:

    <build>
        <plugins>
            <plugin>
                <groupId>cz.habarta.typescript-generator</groupId>
                <artifactId>typescript-generator-maven-plugin</artifactId>
                <version>2.12.476</version>
                <executions>
                    <execution>
                        <id>generate</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>process-classes</phase>
                    </execution>
                </executions>
                <configuration>
                    <jsonLibrary>jackson2</jsonLibrary>
                
                    <classesFromAutomaticJaxrsApplication>true</classesFromAutomaticJaxrsApplication>
                    <outputKind>module</outputKind>
                </configuration>
            </plugin>
        </plugins>
    </build>

Das funktioniert relativ gut, scheitert natürlich wenn wir mittels JSON-Mapper in die Struktur der generierten JSON-Nachricht eingreifen. Zum Beispiel übermitteln wir bei Relationen (User hat eine Liste von Projekten) sehr häufig lediglich die IDs der Projekte. In unseren TypeScript – Modelklassen werden wir aber gemäß Java-Klassen ein Objekt vom Typ „Projekt“ vorfinden. Hier müssen wir entweder manuell eingreifen, die generierten Klassen erweitern / überschreiben. Oder im Programm durch entsprechende Casts gegensteuern.