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.
<!-- 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
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:
Another example: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>
And finally, let's try timestamp and lists!: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>
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}