Zobrazují se příspěvky se štítkemMaven. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemMaven. Zobrazit všechny příspěvky

neděle 4. září 2022

Parsing XML with missing namespace

Today I needed to parse some JUnit reports, generated from some old code, so they are missing namespaces. I created a trivial XSD file, but it could not validate the XML as it did not contain matching namespace.

Just a side note - if you need current XSD file, you can find it here.

This is able to parse JUnit-like reports from old Ant-based Jakarta EE 10 tests, so I can integrate the TCK to more modern build in GlassFish (another part of my "army of zombies" which ensures I will not do any mistakes in GlassFish refactoring; these TCK tests are waiting to be refactored too).

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FileInputStream;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import xxx.generated.Testsuite;
public class JUnitResultsParser {

public Testsuite parse(final File xml) {
    try (FileInputStream inputStream = new FileInputStream(xml)) {
        JAXBContext context = JAXBContext.newInstance(Testsuite.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = sf.newSchema(getClass().getResource("/junit-results.xsd"));
        unmarshaller.setSchema(schema);
        unmarshaller.setAdapter(new ClassAdapter());
        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = new NamespaceURIFixer(xif.createXMLStreamReader(inputStream));
        Testsuite testSuite = (Testsuite) unmarshaller.unmarshal(xsr);
        return testSuite;
    } catch (Exception e) {
        throw new IllegalArgumentException("Could not process the XML file: " + xml, e);
    }
}


private static class NamespaceURIFixer extends StreamReaderDelegate {
    public XsiTypeReader(XMLStreamReader reader) {
        super(reader);
    }

    @Override
    public String getNamespaceURI() {
        String uri = super.getNamespaceURI();
        if (uri == null) {
            // same as in xsd
            return "urn:org:junit:results";
        }
        return uri;
    }
}
}

Oh, and if you want to see how I generated remaining classes, it is this:
<plugins>
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxb2-maven-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
            <execution>
                <id>xjc</id>
                <goals>
                    <goal>xjc</goal>
                </goals>
                <configuration>
                    <addGeneratedAnnotation>true</addGeneratedAnnotation>
                    <clearOutputDir>true</clearOutputDir>
                    <locale>en</locale>
                    <sources>
                        <source>src/main/resources/junit-results.xsd</source>
                    </sources>
                    <xjbSources>
                        <xjbSource>src/main/resources/junit-results.xjb</xjbSource>
                    </xjbSources>
                    <packageName>xxx.generated</packageName>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>

Ok, ok, you wanna know what I did in that XJB file ... I noticed there are Class values and I don't want to get just String. So I created a trivial XmlAdapter<String, Class<?>> using Class.forName() and Class.getName() to convert String and Class, and that's it! Btw I found a minor bug - to my surprise I could use generics in the XML, but in generated code there's missing space after the class name. And when I added it to the XML, it remained even in the generated source code. Perhaps I should create an issue for that ...
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings version="3.0"
    xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    jaxb:extensionBindingPrefixes="xjc"
>
    <jaxb:bindings schemaLocation="junit-results.xsd" node="/xsd:schema">
        <jaxb:bindings node="//xsd:simpleType[@name='javaClassName']">
            <xjc:javaType name="java.lang.Class<?> " adapter="org.glassfish.main.tests.tck.ant.xml.ClassAdapter" />
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

The javaClassName is defined this way in XSD:
    <xsd:simpleType name="javaClassName">
        <xsd:restriction base="xsd:string" />
    </xsd:simpleType>

And now comes the cake - I can run the TCK tests against GlassFish snapshot right from the Eclipse. Yeah, it is not without issues, they simply cannot run one after another as they leave some garbage behind ... but still - I can easily reproduce some issue locally, configure logging, or even attach debugger (I did not try yet, maybe it would need some settings).


Last note: it was rather for fun, because my side doesn't see test classes inside the TCK, so obviously javaClassName cannot be converted to a Class<?>, because the required class is not on my classpath. However - I did not do any XJB customizations for several years, so I had to try it :-)

středa 13. července 2022

Native2ascii Maven Plugin Is Still Used

This will be rather a short message - today I spent some time with the plugin as someone created a security issue related to obsoleted dependencies. So I did a bit more ...

  • Migrated from Travis to GitHub Actions
  • Excluded obsoleted Velocity and Struts transitive dependencies (unused, however ...)
  • Replaced deprecated usages of commons-lang3 classes
  • Replaced "closeSilently" by try-with (do you remember years where there weren't suppressed exceptions?)
Despite you can use property files with UTF-8 now, more usual is still Latin1+escapes. Then is better to write property files in human language and use this plugin to convert them to the "language of machines". Eclipse integration with this plugin works well too.

See https://github.com/mojohaus/native2ascii-maven-plugin

pátek 30. listopadu 2018

Refactoring, část IV: Odstraňování duplicit

Co je špatně?

Duplicitní kód se sice rychle a snadno generuje (podobně jako diplomky politiků), ale později se ve zlém vrátí tím spíš, čím víc "klonů" existuje. Scénář katastrofy je obvykle rutinní:
  1. Uživatel nahlásí chybu nebo je třeba implementovat novou vlastnost
  2. Chyba se opraví - čili klon se liší od svých pravzorů
  3. Jiný uživatel reklamuje chybu v jiném klonu
  4. Chyba se opraví - obvykle jinak než v prvním klonu.
  5. Ostatní klony jsou neopravené. Trpí stejným problémem? Netrpí jím? Jak to, že fungují správně? Je část klonovaného kódu v jejich případě zbytečná? Nebo nefungují správně, jen ještě nikdo v jejich případě chybu nereklamoval?
  6. Rozdíly narůstají, chyby někde mizí, jinde zůstávají nebo se dokonce mění. Technický dluh roste exponenciálně.

Konfigurace CPD, Copy And Paste Detector


CPD je součástí PMD maven pluginu, čili když už, nejspíš budete používat oba. Kontrolám PMD samotného se ale vyhnu, to si najděte jinde ;)
CPD porovnává soubory a hledá v nich opakující se sekvence "tokenů". Algoritmus lze trochu konfigurovat, viz dokumentace pluginu.

                <plugin>
                    <artifactId>maven-pmd-plugin</artifactId>
                    <version>3.11.0</version>
                    <executions>
                        <execution>
                            <id>cpd</id>
                            <goals>
                                <goal>cpd</goal>
                            </goals>
                            <phase>verify</phase>
                            <configuration>
                                <skip>${cpd.skip}</skip>
                                <minimumTokens>100</minimumTokens>
                                <excludes>
                                    <exclude>**/en/**/*</exclude>
                                    <exclude>**/generated/**/*</exclude>
                                </excludes>
                            </configuration>
                        </execution>
                        <execution>
                            <id>cpd-check</id>
                            <goals>
                                <goal>cpd-check</goal>
                            </goals>
                            <phase>verify</phase>
                            <configuration>
                                <skip>${cpd.skip}</skip>
                                <printFailingErrors>true</printFailingErrors>
                                <verbose>true</verbose>
                            </configuration>
                        </execution>
                    </executions>

Pokud plugin najde dvě stejné sekvence delší jak 100 tokenů, shodí build a vypíše nalezené duplicity. Ty pak budete muset řešit.

Řešení

Fuj: Přehodím řádky

Ano, z pohledu CPD jste z toho venku, ale po pravdě ... vážně myslíte, že jste se problému zbavili? Ve skutečnosti jste se zbavili jen jeho hlášení, problém trvá. Navíc jste si ještě zhoršili orientaci ve třídě.

Fuj: Zvýším minimumTokens

... a zbavím se hlášení. Problém trvá, ale co hůř, ono to často ani nezabere, protože těch duplicit je víc, byť i jen na málo řádcích. Pointa je právě v tom, co vlastně je ten "token". Ta stovka je docela rozumná, proto je taky jako default.

Exkluze

Ta funguje stoprocentně. Není ovšem určená k ignorování skutečných problémů, nýbrž k ignorování falešných hlášení problémů. Může být nalezená duplicita, skutečná duplicita, falešným hlášením? Je třeba si uvědomit, že cílem není zbavit se jakýchkoliv duplicit, ale zbavit se hrozících problémů s duplicitami, zlepšit architekturu, uklidit.

Do exkluze tudíž typicky dávám jednak kód generovaný, se kterým nic nenadělám, jednak třeba JPA entity, které mohou mít velmi podobné vlastnosti, přestože vzájemně nijak nesouvisí. To, že se něco stejně jmenuje, nemusí znamenat, že je to duplicitní - a CPD s oblibou hlásí stejné čtyři settery a gettery, jdoucí za sebou.

Problém může být, pokud nemáte žádnou konvenci, jak třeba JPA entity odlišit od jiných tříd. Hodně štěstí ;-)

Vyvedení do samostatné metody ve stejné třídě

Samozřejmost, pokud je duplicitní kód jediné třídy. Nebo ne? Pokud na 10 řádkách máme 8 lokálních proměnných, tohle nebude smysluplná cesta. Můžeme je zkombinovat do jednoho výpočtu, jenže ... to zásadně zhorší čitelnost, takže taky ne. Takže jiná možnost.

Vyvedení do společného rodiče

Je to možnost, ale je třeba zvážit, zda vyváděná funkcionalita do rodiče opravdu patří. Pokud ostatním potomkům rodiče je tato funkcionalita cizí a nemají s ní nic společného, nedělejte to. Jedině že byste stvořili dalšího "mezirodiče", ale pokud mu nedokážete dát smysluplný název, budu se opakovat - nedělejte to; příliš vysoká hierarchie rodičů a potomků je další problém, čili je tu riziko, že odstraněním jednoho problému vytvoříte jiný.

Vyvedení do "utility" nebo "cizí" třídy

Utilitky, helpery, atp., jsou víceméně neobjektová věc, na druhou stranu netrpí problémy s dědičností, dobře se na ně píšou testy, takže tuhle možnost vůbec nezatracujte.
Podobně je možné, že víte o třídě, kam tento kód vlastně i logicky patří a lze ho odsud sdílet na potřebná místa. Předpokladem je, že vyvedenému kódu dokážete vytvořit nějaké pěkné smysluplné API. Ale to se vlastně týká každého "vyvádění".

Vyvedení do default metody interface

Óóó, jak snadné a efektivní!
Jenže to má háček - nese to riziko. Implementace z abstraktního rodiče, implementující stejnou metodu, má vyšší prioritu než jakýkoliv interface!!!
Vážně si rozmyslete, jestli vyváděný kód může opravdu sloužit jen jako pouhý "default". V případě kolizí pak musíte metodu stejně přetížit a z těla volat Iface.super.metoda(), jinak řečeno musíte říct, kterou z dostupných implementací chcete používat.
Pokud máte ale v rodiči metodu implementovanou, complier vám nic neřekne a neporadí, předpokládá, že to vážně chcete.

Silver bullet: přečtěte si to

Překvápko: někdy není třeba duplicitní blok "vyvádět", stačí oba duplicitní bloky naprosto stejně zjednodušit. Třeba odstranit 10 opakovaných přetypování. Nebo jen něco, co se na obou místech otravně opakuje, vyvést ven a sdílet. Výsledek? Ubyde tokenů, jak prosté, už to nevypadá jako hloupý copy and paste, jen něco, co dělá stejnou věc, a to už není známka copy and paste.

Svět je zase v pořádku :-)

pátek 6. října 2017

REST Web Services with Jackson, Jersey and Payara Micro ... Level II

So ... I will continue with experiments started here and do some real work ... I continued to extend the API introduced in the recent blog, and I encountered some new problems ... for example I could not send a java.util.List in REST response with the same error as I already resolved in the request and also dates were only serialized (have you ever tried to serialize the GregorianCalendar instance?).
So i started to search on StackOverflow and found several solution - but neither one worked or at least it was not easy and nice. Then I started to think how it all works ... ;-)
Now I will stop talking, see the solution. I created new implementation of the web service to be more comprehensible what it does:

Jackson and Jersey

I am so dumb, do you know that? I mixed up these two names so long! So, what is the difference?
  • Jersey is the reference implementation of JAX-RS API. Nothing more to say. THE core. It's home is here
  • Jackson enhances the Jersey with much more stuff to be really usable. It's home is here
And this is a part of pom.xml of the web services war; versions are defined in some parent POM:
    
    <!-- to use ResourceConfig class -->
    <dependency>
      <groupid>org.glassfish.jersey.core</groupid>
      <artifactid>jersey-server</artifactid>
    </dependency>
    <!-- to use JDK8 and other features, like java.time API or automatic ISO time formatting -->
    <dependency>
      <groupid>com.fasterxml.jackson.datatype</groupid>
      <artifactid>jackson-datatype-jdk8</artifactid>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>org.apache.logging.log4j</groupid>
      <artifactid>log4j-api</artifactid>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupid>org.apache.logging.log4j</groupid>
      <artifactid>log4j-core</artifactid>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupid>org.apache.commons</groupid>
      <artifactid>commons-lang3</artifactid>
      <scope>compile</scope>
    </dependency>

    <!-- to have better javadocs than in javaee-api -->
    <dependency>
      <groupid>javax.ws.rs</groupid>
      <artifactid>javax.ws.rs-api</artifactid>
      <scope>provided</scope>
    </dependency>    

    <!-- to have all Java EE 7 specifications in one dependency, but to have also the older javadoc -->
    <dependency>
      <groupid>javax</groupid>
      <artifactid>javaee-api</artifactid>
      <scope>provided</scope>
    </dependency>
    

ResouceConfig class

This class should be in root package of all your REST services - see calling of the method named packages in the constructor, that is the magic; it enables the annotation processing. And you know, there is more magic hidden in Maven dependencies not enabled by default. What I didn't know in first blog is that even Jackson is already in Glassfish/Payara (but not everything, see the dependencies again!).

package org.dmatej.experiment.rest;

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;
 

@ApplicationPath("") // use the application root
public class RestAppConfig extends ResourceConfig {
  public RestAppConfig() {
    packages(//
        RestAppConfig.class.getPackage().getName(), // search annotations in this class's package
        "com.fasterxml.jackson"// search in jackson's package
    );
  }
}

ContextResolver interface 

We enabled Jersey and Jackson, but that was not enough! Now we have to enable also the Jackson's extension modules.

package org.dmatej.experiment.rest;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 

@Provider
public class ObjectMapperContextResolver implements ContextResolver {

  private final ObjectMapper MAPPER;

  public ObjectMapperContextResolver() {
    MAPPER = new ObjectMapper();

// This did not find the Jdk8Module
    MAPPER.findAndRegisterModules();
    // enables ISO time parsing and formatting in REST communication.
    MAPPER.registerModule(new Jdk8Module());

    MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  }

  @Override
  public ObjectMapper getContext(Class type) {
    return MAPPER;
  }
}

And now the web service

Now it is pretty trivial and it will do what I expected. No unexplainable errors.

I think this blog entry is big enough and the implementation of Measurement transfer object is trivial, also MeasurementSaveResponse is trivial - it contains list of invalid records and some monitoring data.

The problem was the list - Jersey accepted list as a parameter but had thrown an exception if the list was in the response. Now I don't even need ArrayList in parameters and I can declare an abstract List. It also seems that those transfer objects don't need to have a complete set of getters and setters; Jersey uses reflection.

package org.dmatej.experiment;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.ejb.EJB;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Path("measurement")
public class MeasurementRestService {

  private static final Logger LOG = LogManager.getLogger(MeasurementRestService.class);

  // we are stateless, you know ...
  private List refusedMeasurements = new ArrayList<>();

  @POST
  @Path("save/{id1}/{id2}")
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  public Response save(//
      @PathParam("id1") final String id1, //
      @PathParam("id2") final String id2, //
      final List measurements) {

    final long start = System.currentTimeMillis();
    LOG.trace("save(id1={}, id2={}, measurements={})", id1, id2, measurements);

// invalid elements of the collection are moved to the refusedMeasurements collection.
    process(id1, id2, measurements);

    final MeasurementSaveResponse response = new MeasurementSaveResponse();
    response.setTimeInMillis(System.currentTimeMillis() - start);
    response.setRefusedMeasurements(this.refusedMeasurements);
    final Response restResponse = Response.ok(new GenericEntity<>(response, response.getClass())).build();
    return restResponse;
  }

Payara Micro and Uber jar 

No changes since the first blog. But we did not try it!

Call the web service!


mvn clean install; mvn fish.payara.maven.plugins:payara-micro-maven-plugin:start -pl :experiment-uberjar

... and it will start:

[2017-10-05T22:33:11.850+0200] [] [INFO] [] [javax.enterprise.system.core] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1507235591850] [levelValue: 800] experiment-ws-0.0.1-SNAPSHOT was successfully deployed in 3 118 milliseconds.

[2017-10-05T22:33:11.851+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1507235591851] [levelValue: 800] Deployed 1 archive(s)

[2017-10-05T22:33:11.852+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1507235591852] [levelValue: 800] [[
 
Instance Configuration
Host: localhost
HTTP Port(s): 8080
HTTPS Port(s):
Instance Name: payara-micro
Instance Group: no-cluster
Deployed: experiment-ws-0.0.1-SNAPSHOT ( experiment-ws-0.0.1-SNAPSHOT war /experiment-ws-0.0.1-SNAPSHOT )

]]

[2017-10-05T22:33:11.865+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1507235591865] [levelValue: 800] [[
 
Payara Micro URLs
http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT

'experiment-ws-0.0.1-SNAPSHOT' REST Endpoints
POST    /experiment-ws-0.0.1-SNAPSHOT/measurement/save/{id1}/{id2}

]]

[2017-10-05T22:33:11.865+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1507235591865] [levelValue: 800] Payara Micro  4.1.2.173 #badassmicrofish (build 25) ready in 13 959 (ms)

Then open another console and run several curls:

curl -H "Content-Type: application/json" -X OPTIONS -i http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT/measurement/save/dm790321/xxx;
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Allow: POST,OPTIONS
Last-modified:  t, 05 X j 2017 11:18:02 CEST
Content-Type: application/vnd.sun.wadl+xml
Date: Thu, 05 Oct 2017 09:18:02 GMT
Content-Length: 715

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://wadl.dev.java.net/2009/02">
    <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.25.1 2017-01-19 16:23:50"/>
    <grammars/>
    <resources base="http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT/">
        <resource path="measurement/save/dm790321/xxx">
            <method id="save" name="POST">
                <request>
                    <representation mediaType="application/json"/>
                </request>
                <response>
                    <representation mediaType="*/*"/>
                </response>
            </method>
        </resource>
    </resources>
</application>

Another example:

curl -H "Content-Type: application/json" -X GET -i http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT/application.wadl?detail=true
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Last-modified:  t, 05 X j 2017 15:59:02 CEST
Content-Type: application/vnd.sun.wadl+xml
Date: Thu, 05 Oct 2017 13:59:02 GMT
Content-Length: 5491

<application xmlns="http://wadl.dev.java.net/2009/02">
    <doc jersey:generatedby="Jersey: 2.25.1 2017-01-19 16:23:50" xmlns:jersey="http://jersey.java.net/">
    <doc jersey:hint="This is full WADL including extended resources. To get simplified WADL with users resources only do not use the query parameter detail. Link: http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT/application.wadl" xmlns:jersey="http://jersey.java.net/">
    <grammars>
        <include href="application.wadl/xsd0.xsd">
            <doc title="Generated" xml:lang="en">
        </doc></include>
    </grammars>
    <resources base="http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT/">
        <resource path="measurement">
            <resource path="save/{id1}/{id2}">
                <param name="id1" type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" />
                <param name="id2" type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" />
                <method id="save" name="POST">
                    <request>
                        <representation mediatype="application/json">
                    </representation></request>
                    <response>
                        <representation mediatype="application/json">
                    </representation></response>
                </method>
                <method id="apply" name="OPTIONS">
                    <request>
                        <representation mediatype="*/*">
                    </representation></request>
                    <response>
                        <representation mediatype="application/vnd.sun.wadl+xml">
                    </representation></response>
                    <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
                </method>
                <method id="apply" name="OPTIONS">
                    <request>
                        <representation mediatype="*/*">
                    </representation></request>
                    <response>
                        <representation mediatype="text/plain">
                    </representation></response>
                    <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
                </method>
                <method id="apply" name="OPTIONS">
                    <request>
                        <representation mediatype="*/*">
                    </representation></request>
                    <response>
                        <representation mediatype="*/*">
                    </representation></response>
                    <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
                </method>
            </resource>
        </resource>
        <resource path="application.wadl">
            <method id="getWadl" name="GET">
                <response>
                    <representation mediatype="application/vnd.sun.wadl+xml">
                    <representation mediatype="application/xml">
                </representation></representation></response>
                <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
            </method>
            <method id="apply" name="OPTIONS">
                <request>
                    <representation mediatype="*/*">
                </representation></request>
                <response>
                    <representation mediatype="text/plain">
                </representation></response>
                <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
            </method>
            <method id="apply" name="OPTIONS">
                <request>
                    <representation mediatype="*/*">
                </representation></request>
                <response>
                    <representation mediatype="*/*">
                </representation></response>
                <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
            </method>
            <resource path="{path}">
                <param name="path" type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" />
                <method id="getExternalGrammar" name="GET">
                    <response>
                        <representation mediatype="application/xml">
                    </representation></response>
                    <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
                </method>
                <method id="apply" name="OPTIONS">
                    <request>
                        <representation mediatype="*/*">
                    </representation></request>
                    <response>
                        <representation mediatype="text/plain">
                    </representation></response>
                    <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
                </method>
                <method id="apply" name="OPTIONS">
                    <request>
                        <representation mediatype="*/*">
                    </representation></request>
                    <response>
                        <representation mediatype="*/*">
                    </representation></response>
                    <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
                </method>
                <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
            </resource>
            <jersey:extended xmlns:jersey="http://jersey.java.net/">true</jersey:extended>
        </resource>
    </resources>
</doc></doc></application>

And finally, let's try timestamp and lists!:

curl -H "Content-Type: application/json" -X POST -d '[{"timestamp":"2017-03-25T15:33:11.000+02:00", "channel":"15", "variable":"XXX", "value":"18"}, {"timestamp":"2017-03-25T15:33:11.000+02:00", "channel":"15", "variable":"XXX", "value":[null]}, {"timestamp":"2017-03-25T15:33:11.000+02:00", "channel":"15", "variable":[null], "value":"18"}]' -i http://localhost:8080/experiment-ws-0.0.1-SNAPSHOT/measurement/save/dm790321/fffff;
HTTP/1.1 200 OK
Server: Payara Micro #badassfish
Content-Type: application/json
Date: Thu, 05 Oct 2017 13:47:05 GMT
Content-Length: 132

{"countOfAccepted":2,"refusedMeasurements":[{"channel":15,"timestamp":"2017-03-25T15:33:11+02:00","value":18.0}],"timeInMillis":244}

úterý 12. září 2017

Experiments with the Payara Micro, level I

New company, new project, new technologies. Okay, I'm experimenting with Payara Micro. Payara team produces more and more examples in several blogs, but they are very trivial and not always usable in production environment. At this time I'm not sure if I would be able to do the evolution to the final professional system, but it is not a problem, because the application modules are simply Java EE standard modules.
The difference is only in final organisation of the modules, deployment and container configuration, so I can create the standard EAR application in parallel to UBER jar with the Payara Micro. So this style of development is perfectly safe.

Target 

Application with the following modules:
  • DAO and bussines logic service module (JPA, JTA, EJB, CDI?), created but nearly empty in this blog 
  • Web service module (JAX-RS), only one simple service method in this blog 
  • GUI module (JSF), not resolved in this blog 
And project will have also following aggregation alternatives:
  • Uber JAR with Payara Micro - experimental, responsive development 
  • EAR for standard Payara domain 
The reason for this separation of modules is that a JSF GUI application obviously have different requirements than a Web service application. It might not be a problem for some time, but it could be a problem later. This is not any premature optimization - this will force developers to keep in mind the separation line between modules and maybe to create some clean API. That will help right now and it will be simplier to split it later.

There may be even more Maven modules:
  • superpom - common Maven plugin configurations, basic dependency management 
  • project parent - aggregator of all project modules 
  • integration tests for the web service module 
  • selenium tests for the gui module

Dead-end streets and good streets 

Well, I had hard two days with the Payara Micro. Blogs helped, but I always needed more and I was always stucked in some weird state. Yes, it was always my fault, but ... okay, now you can learn from my mistakes.

LOG4J2 

I used LOG4J with SLF4J for many years, it was pretty trivial to make it work and a bit harder to grab logs of embedded Payara in integration tests. I have found a memory leak in old LOG4J's reconfiguration and I know perhaps everything about that.
Now it is worthless with LOG4J2. But finally the configuration was also simple despite I still have not found a way to merge logs of the application and Payara. Example log4j2.properties, seems like a good street:
status = info
dest = err
name = PropertiesConfig
#log4j2.debug = true

property.path = target/logs

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = info

appender.rolling.type = RollingFile
appender.rolling.name = RollingFile
appender.rolling.fileName = ${path}/experiment-ws.log
appender.rolling.filePattern = ${path}/experiment-ws-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d %p %C{2.} [%t] %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 2
appender.rolling.policies.time.modulate = true
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=1024MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 1

logger.ws.name = org.dmatej
logger.ws.level = debug
logger.ws.additivity = false
logger.ws.appenderRef.file.ref = RollingFile

rootLogger.level = debug
rootLogger.appenderRef.stdout.ref = STDOUT
rootLogger.appenderRef.file.ref = RollingFile  

JAX-RS, lists ... Jackson!

Creating the first JAX-RS web service is pretty trivial ... you need two classes, first to configure the context of services in the module, second to implement the service:

import javax.ws.rs.ApplicationPath;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rs")
public class RestAppConfig extends ResourceConfig {

  private static final Logger LOG = LogManager.getLogger(RestAppConfig.class);

  public RestAppConfig() {
    LOG.debug("RestAppConfig()");
    try {
      packages(RestAppConfig.class.getPackage().getName());
      LOG.info("REST configured!");
    } catch (final Exception e) {
      LOG.error("Cannot configure the REST web services!", e);
    }
  }
}
And the second:

import java.util.Arrays;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("user")
public class UserRestService {

  @GET
  @Path("list")
  @Produces(MediaType.APPLICATION_JSON)
  public Response list() {
    final List<String> list = Arrays.asList("Křemílek", "Vochomůrka"); // UTFG ;-)
    return Response.ok(list).build();
  }
} 
Maven command to run the application, it will be used in all following examples:
 mvn clean install;  mvn fish.payara.maven.plugins:payara-micro-maven-plugin:start -pl :experiment-uberjar
Final part of the log:

[2017-09-12T16:31:25.369+0200] [] [INFO] [AS-WEB-GLUE-00172] [javax.enterprise.web] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505226685369] [levelValue: 800] Loading application [experiment-ws-0.0.1-SNAPSHOT] at [/experiment-ws-0.0.1-SNAPSHOT]

[2017-09-12T16:31:25.681+0200] [] [INFO] [] [javax.enterprise.system.core] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505226685681] [levelValue: 800] experiment-ws-0.0.1-SNAPSHOT was successfully deployed in 12 472 milliseconds.

[2017-09-12T16:31:25.685+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505226685685] [levelValue: 800] Deployed 1 archive(s)

[2017-09-12T16:31:25.686+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505226685686] [levelValue: 800] [[
 
Instance Configuration
Host: dmatej-lenovo
HTTP Port(s): 8080
HTTPS Port(s):
Instance Name: Frail-Barracuda
Instance Group: MicroShoal
Hazelcast Member UUID cbddb9aa-5f21-4ee1-984f-78be942124d4
Deployed: experiment-ws-0.0.1-SNAPSHOT ( experiment-ws-0.0.1-SNAPSHOT war /experiment-ws-0.0.1-SNAPSHOT )

]]

[2017-09-12T16:31:25.702+0200] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505226685702] [levelValue: 800] [[
 
Payara Micro URLs
http://dmatej-lenovo:8080/experiment-ws-0.0.1-SNAPSHOT

'experiment-ws-0.0.1-SNAPSHOT' REST Endpoints
GET     /experiment-ws-0.0.1-SNAPSHOT/rs/user/list

]]
Ok, that was pretty simple. But the service does not work and ends up with HTTP 500 (try it with browser):

[2017-09-12T20:19:02.013+0200] [] [SEVERE] [] [org.glassfish.jersey.message.internal.WriterInterceptorExecutor] [tid: _ThreadID=22 _ThreadName=http-thread-pool::http-listener(3)] [timeMillis: 1505240342013] [levelValue: 1000] MessageBodyWriter not found for media type=application/json, type=class java.util.Arrays$ArrayList, genericType=class java.util.Arrays$ArrayList.

Solution 

Only add the dependency on Jackson to pom.xml and the following line as the first to the RestAppConfig's constructor:
register(JacksonFeature.class);
The final pom.xml of the ws module:

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <artifactId>experiment-ws</artifactId>
  <packaging>war</packaging>
  <name>Experiment - Web Services</name>
  <parent>
    <groupId>org.dmatej.experiment</groupId>
    <artifactId>parent-pom</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>..</relativePath>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.ws.rs</groupId>
      <artifactId>javax.ws.rs-api</artifactId>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
    </dependency>
    <!-- to use optimized JAX-RS configuration -->
    <dependency>
      <groupId>org.glassfish.jersey.core</groupId>
      <artifactId>jersey-server</artifactId>
    </dependency>
    <!-- to simply use lists in JSONs -->
    <dependency>
      <groupId>org.glassfish.jersey.media</groupId>
      <artifactId>jersey-media-json-jackson</artifactId>
    </dependency>
  </dependencies>
  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>false</filtering>
        <includes>
          <include>**/*.properties</include>
        </includes>
      </resource>
    </resources>
  </build>
</project>

JDBC Pool 

This was a real pain but solution was so simple ... do you know what I hate? NullpointerException. In fact I appreciate that it exists, because it tells "the programmer was not careful". And if the programmer was not careful, it is a bug - maybe even trivial to fix. But another exception I have seen was ClassNotFoundException ... This is the pom.xml of the UBER jar:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.dmatej.experiment</groupId>
  <artifactId>experiment-uberjar</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Experiment Uberjar</name>
  <parent>
    <groupId>org.dmatej.experiment</groupId>
    <artifactId>superpom</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>false</filtering>
        <includes>
          <include>**/*.txt</include>
        </includes>
      </resource>
    </resources>
    <plugins>
      <!-- HOWTO: https://github.com/payara/maven-plugins -->
      <plugin>
        <groupId>fish.payara.maven.plugins</groupId>
        <artifactId>payara-micro-maven-plugin</artifactId>
        <version>1.0.0</version>
        <executions>
          <execution>
            <goals>
              <goal>bundle</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <payaraVersion>4.1.2.173</payaraVersion>
          <useUberJar>true</useUberJar>
          <deployArtifacts>
            <deployArtifact>
              <groupId>org.dmatej.experiment</groupId>
              <artifactId>experiment-ws</artifactId>
              <version>${project.version}</version>
              <type>war</type>
            </deployArtifact>
          </deployArtifacts>
          <customJars>
            <customJar>
              <groupId>org.apache.logging.log4j</groupId>
              <artifactId>log4j-api</artifactId>
            </customJar>
            <customJar>
              <groupId>org.apache.logging.log4j</groupId>
              <artifactId>log4j-core</artifactId>
            </customJar>
            <customJar>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
            </customJar>
          </customJars>
          <commandLineOptions>
            <commandLineOption>
              <key>--autobindhttp</key>
              <value>true</value>
            </commandLineOption>
            <commandLineOption>
              <key>--prebootcommandfile</key>
              <value>${project.build.outputDirectory}/prepare-resources.txt</value>
            </commandLineOption>
          </commandLineOptions>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
The prepare-resources.txt was this (note that empty lines are interpreted as an error):

set configs.config.server-config.admin-service.das-config.dynamic-reload-enabled=false
set configs.config.server-config.admin-service.das-config.autodeploy-enabled=false
create-jdbc-connection-pool --datasourceclassname com.mysql.cj.jdbc.MysqlDataSource --restype javax.sql.DataSource --property user=test:password=test:DatabaseName=experiment_test:ServerName=localhost:port=3306:zeroDateTimeBehavior=convertToNull:useUnicode=true:useJDBCCompliantTimezoneShift=true:useLegacyDatetimeCode=true:serverTimezone=UTC:characterEncoding=UTF-8 experiment-mysql
set resources.jdbc-connection-pool.experiment-mysql.steady-pool-size=5
set resources.jdbc-connection-pool.experiment-mysql.max-pool-size=20
set resources.jdbc-connection-pool.experiment-mysql.connection-validation-method=auto-commit
set resources.jdbc-connection-pool.experiment-mysql.is-connection-validation-required=true
set resources.jdbc-connection-pool.experiment-mysql.fail-all-connections=true
ping-connection-pool experiment-mysql
But it did not work, server startup failed and I had no idea why ... and Payara did not help me ... there were two kind of stacktraces:

java.lang.RuntimeException: Orb initialization erorr
        at org.glassfish.enterprise.iiop.api.GlassFishORBHelper.getORB(GlassFishORBHelper.java:191)
        at com.sun.enterprise.naming.impl.SerialContext.getORB(SerialContext.java:349)
        at com.sun.enterprise.naming.impl.SerialContext.getProviderCacheKey(SerialContext.java:356)
        at com.sun.enterprise.naming.impl.SerialContext.getRemoteProvider(SerialContext.java:386)
        at com.sun.enterprise.naming.impl.SerialContext.getProvider(SerialContext.java:331)
        at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:480)
        at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:440)
        at javax.naming.InitialContext.lookup(InitialContext.java:417)
        at org.glassfish.resourcebase.resources.naming.ResourceNamingService.lookup(ResourceNamingService.java:236)
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.getConnectorConnectionPool(ConnectorConnectionPoolAdminServiceImpl.java:799)
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.obtainManagedConnectionFactory(ConnectorConnectionPoolAdminServiceImpl.java:938)
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.getUnpooledConnection(ConnectorConnectionPoolAdminServiceImpl.java:549)
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.testConnectionPool(ConnectorConnectionPoolAdminServiceImpl.java:430)
        at com.sun.enterprise.connectors.ConnectorRuntime.pingConnectionPool(ConnectorRuntime.java:1162)
        at org.glassfish.connectors.admin.cli.PingConnectionPool.execute(PingConnectionPool.java:143)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:544)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:540)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:360)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$2.execute(CommandRunnerImpl.java:539)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:570)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:562)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:360)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:561)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1469)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1300(CommandRunnerImpl.java:111)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1851)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1727)
        at com.sun.enterprise.admin.cli.embeddable.CommandExecutorImpl.executeCommand(CommandExecutorImpl.java:169)
        at com.sun.enterprise.admin.cli.embeddable.CommandExecutorImpl.run(CommandExecutorImpl.java:94)
        at fish.payara.micro.boot.runtime.BootCommand.execute(BootCommand.java:65)
        at fish.payara.micro.boot.runtime.BootCommands.executeCommands(BootCommands.java:105)
        at fish.payara.micro.boot.runtime.BootCommands.executeCommands(BootCommands.java:99)
        at fish.payara.micro.impl.PayaraMicroImpl.bootStrap(PayaraMicroImpl.java:987)
        at fish.payara.micro.impl.PayaraMicroImpl.main(PayaraMicroImpl.java:186)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at fish.payara.micro.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
        at fish.payara.micro.boot.loader.Launcher.launch(Launcher.java:107)
        at fish.payara.micro.boot.loader.Launcher.launch(Launcher.java:70)
        at fish.payara.micro.boot.PayaraMicroLauncher.main(PayaraMicroLauncher.java:79)
        at fish.payara.micro.PayaraMicro.main(PayaraMicro.java:361)
Caused by: java.lang.NullPointerException
        at org.glassfish.enterprise.iiop.api.GlassFishORBHelper.getORB(GlassFishORBHelper.java:163)
        ... 44 more
[2017-09-12T21:42:28.782+0200] [] [SEVERE] [] [javax.enterprise.resource.resourceadapter.com.sun.enterprise.connectors] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505245348782] [levelValue: 1000] RAR6001 : Class Not found : com.sun.gjc.spi.ResourceAdapterImpl


com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: Error in creating active RAR
        at com.sun.enterprise.connectors.ActiveRAFactory.createActiveResourceAdapter(ActiveRAFactory.java:111)
        at com.sun.enterprise.connectors.service.ResourceAdapterAdminServiceImpl.createActiveResourceAdapter(ResourceAdapterAdminServiceImpl.java:212)
        at com.sun.enterprise.connectors.service.ResourceAdapterAdminServiceImpl.createActiveResourceAdapter(ResourceAdapterAdminServiceImpl.java:348)
        at com.sun.enterprise.connectors.ConnectorRuntime.createActiveResourceAdapter(ConnectorRuntime.java:405)
        at com.sun.enterprise.connectors.service.ConnectorService.loadDeferredResourceAdapter(ConnectorService.java:184)
        at com.sun.enterprise.connectors.service.ConnectorService.loadResourcesAndItsRar(ConnectorService.java:148)
        at com.sun.enterprise.connectors.service.ConnectorService.checkAndLoadPool(ConnectorService.java:325)
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.getUnpooledConnection(ConnectorConnectionPoolAdminServiceImpl.java:553)
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.testConnectionPool(ConnectorConnectionPoolAdminServiceImpl.java:430)
        at com.sun.enterprise.connectors.ConnectorRuntime.pingConnectionPool(ConnectorRuntime.java:1162)
        at org.glassfish.connectors.admin.cli.PingConnectionPool.execute(PingConnectionPool.java:143)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:544)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:540)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:360)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$2.execute(CommandRunnerImpl.java:539)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:570)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:562)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:360)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:561)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1469)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1300(CommandRunnerImpl.java:111)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1851)
        at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1727)
        at com.sun.enterprise.admin.cli.embeddable.CommandExecutorImpl.executeCommand(CommandExecutorImpl.java:169)
        at com.sun.enterprise.admin.cli.embeddable.CommandExecutorImpl.run(CommandExecutorImpl.java:94)
        at fish.payara.micro.boot.runtime.BootCommand.execute(BootCommand.java:65)
        at fish.payara.micro.boot.runtime.BootCommands.executeCommands(BootCommands.java:105)
        at fish.payara.micro.boot.runtime.BootCommands.executeCommands(BootCommands.java:99)
        at fish.payara.micro.impl.PayaraMicroImpl.bootStrap(PayaraMicroImpl.java:987)
        at fish.payara.micro.impl.PayaraMicroImpl.main(PayaraMicroImpl.java:186)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at fish.payara.micro.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
        at fish.payara.micro.boot.loader.Launcher.launch(Launcher.java:107)
        at fish.payara.micro.boot.loader.Launcher.launch(Launcher.java:70)
        at fish.payara.micro.boot.PayaraMicroLauncher.main(PayaraMicroLauncher.java:79)
        at fish.payara.micro.PayaraMicro.main(PayaraMicro.java:361)
Caused by: java.lang.ClassNotFoundException: com.sun.gjc.spi.ResourceAdapterImpl
        at com.sun.enterprise.v3.server.APIClassLoaderServiceImpl$APIClassLoader.loadClass(APIClassLoaderServiceImpl.java:245)
        at com.sun.enterprise.v3.server.APIClassLoaderServiceImpl$APIClassLoader.loadClass(APIClassLoaderServiceImpl.java:237)
        at com.sun.enterprise.connectors.ActiveRAFactory.createActiveResourceAdapter(ActiveRAFactory.java:103)
        ... 40 more
]]

Dead-end street: comment out ping 

Okay, stacktraces are gone. But I don't know if the pool has been created and if it works.

Dead-end street: add dependencies 

Idea: add missing dependencies. The Payara versions are not in Maven Central, so I tried to add glassfish versions of org.glassfish.main.jdbc.jdbc-ra.jdbc-core:jdbc-core:4.1.2 and org.glassfish.main.jdbc.jdbc-ra.jdbc40:jdbc40:4.1.2 ... Result? Several warnings like this and finally exception. Dumb idea? Something similar helped us with old versions of the Embedded Payara started by JUnit integration tests but here it was only a cargo antipattern.

[2017-09-12T21:36:37.619+0200] [] [WARNING] [] [javax.enterprise.resource.resourceadapter.com.sun.enterprise.connectors.util] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505244997619] [levelValue: 900] RAR8000 : The method setLogJdbcCalls is not present in the class : com.sun.gjc.spi.DSManagedConnectionFactory

com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: Failed to create MCF for experiment-mysql
        at com.sun.enterprise.connectors.service.ConnectorConnectionPoolAdminServiceImpl.createConnectorConnectionPool(ConnectorConnectionPoolAdminServiceImpl.java:195)
        at com.sun.enterprise.connectors.ConnectorRuntime.createConnectorConnectionPool(ConnectorRuntime.java:331)
        at org.glassfish.jdbc.deployer.JdbcConnectionPoolDeployer.actualDeployResource(JdbcConnectionPoolDeployer.java:201)
        at org.glassfish.jdbc.deployer.JdbcConnectionPoolDeployer.deployResource(JdbcConnectionPoolDeployer.java:170)
        at com.sun.enterprise.connectors.service.ConnectorService.loadDeferredResources(ConnectorService.java:233)
        at com.sun.enterprise.connectors.service.ConnectorService$1.run(ConnectorService.java:153)
        at java.security.AccessController.doPrivileged(Native Method)
Those two dependencies now remove, they are not needed and more, they are not compatible.

Look into the Payara sources and think

 ... and create this issue: https://github.com/payara/Payara/issues/1967 Oh, by the way, why I did not think it before? ORB factory is simply initialized after the prebootcommandfile execution! Ok, let's move the ping to a new file postboot.txt and add another commandLineOption to pom.xml:
<commandLineOption>
  <key>--postbootcommandfile</key>
  <value>${project.build.outputDirectory}/postboot.txt</value>
</commandLineOption>

MySQL and time zones 

Pool ping was still failing with some weird error message about unknown CEST timezone. StackOverflow advices did not work, neither one about configuring the JDBC driver. I have found several bugs reported to MySQL devs: https://bugs.mysql.com/bug.php?id=86425
I tried to change the server's default-time-zone via the MySql Workbench with no success until I noted that it updates incorrect file in my user's home directory. Finally I added these lines into /etc/mysql/my.cnf and restarted the mysql service ... and it worked.
[mysqld]
default-time-zone = +00:00

Success 

Yes, that was all. But ... there is nothing interesting in the log output, no logging about asadmin commands, no logging about their success. I was lazy to create my own logging.properties and to add path as another commandLineOption so I hacked the jar in maven repository (please, don't do this, don't be lazy!) ... the nearest usable loggers and logs was these:

[2017-09-12T22:23:53.622+0200] [] [FINE] [] [javax.enterprise.resource.resourceadapter.org.glassfish.jdbcruntime] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505247833622] [levelValue: 500] [CLASSNAME: org.glassfish.jdbcruntime.JdbcRuntimeExtension] [METHODNAME: isConnectionPoolReferredInServerInstance] JDBC resource jdbc/experiment-jta refers experiment-mysql in this server instance and is enabled
 

[2017-09-12T22:23:54.398+0200] [] [FINE] [] [org.glassfish.naming] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1505247834398] [levelValue: 500] [CLASSNAME: com.sun.enterprise.naming.util.NamingUtilsImpl] [METHODNAME: makeCopyOfObject] [[
  ** makeCopyOfObject:: ConnectorConnectionPool :: experiment-mysql
steady size: 5
max pool size: 20
max wait time: 60000
pool resize qty: 2
Idle timeout: 300
failAllConnections: true
Transaction Support Level: 1
isConnectionValidationRequired_ true
preferValidateOverRecreate_ false
matchConnections_ false
associateWithThread_ false
lazyConnectionAssoc_ false
lazyConnectionEnlist_ false
maxConnectionUsage_ 0
pingPoolDuringCreation_ false
poolingOn_ true
validateAtmostOncePeriod_ 0
connectionLeakTracingTimeout_0
connectionReclaim_false
connectionCreationRetryAttempts_0
connectionCreationRetryIntervalInMilliSeconds_10
nonTransactional_ false
nonComponent_ false
ConnectorDescriptorInfo ->
rarName: __ds_jdbc_ra
resource adapter class: com.sun.gjc.spi.ResourceAdapterImpl
connection def name: javax.sql.DataSource
MCF Config properties-> ClassName:com.mysql.cj.jdbc.MysqlDataSource
ConnectionValidationRequired:true
ValidationMethod:auto-commit
ValidationTableName:
ValidationClassName:
TransactionIsolation:
GuaranteeIsolationLevel:true
StatementWrapping:true
LogJdbcCalls:false
SlowQueryThresholdInSeconds:-1
StatementTimeout:-1
PoolMonitoringSubTreeRoot:resources/experiment-mysql
PoolName:experiment-mysql
StatementCacheSize:0
StatementCacheType:
InitSql:null
SqlTraceListeners:fish.payara.jdbc.SilentSqlTraceListener
StatementLeakTimeoutInSeconds:0
StatementLeakReclaim:false
DatabaseName:iqhouse_experiment
User:test
Password:****
ServerName:localhost
DriverProperties:setcharacterEncoding#UTF-8##setport#3306##setuseJDBCCompliantTimezoneShift#true##setuseLegacyDatetimeCode#true##setuseUnicode#true##setserverTimezone#UTC##setzeroDateTimeBehavior#convertToNull##
Delimiter:#
EscapeCharacter:\
]]  
Now I can continue to level 2: Good REST services ... to be continued ;)