I’m attempting to use the MicroProfile RestClient available in Payara 5 Full, but getting a StackOverflow
with the microprofile framework when it attempts to build the rest client.
Here part of the error:
Caused by: java.lang.StackOverflowError
at java.base/java.security.AccessController.doPrivileged(Native Method)
at org.glassfish.jersey.model.AnnotatedMethod.findAnnotatedMethod(AnnotatedMethod.java:276)
at org.glassfish.jersey.model.AnnotatedMethod.findAnnotatedMethod(AnnotatedMethod.java:267)
at org.glassfish.jersey.model.AnnotatedMethod.<init>(AnnotatedMethod.java:92)
at org.glassfish.jersey.model.Parameter.createList(Parameter.java:381)
at org.glassfish.jersey.model.Parameter.create(Parameter.java:359)
at org.glassfish.jersey.microprofile.restclient.MethodModel.parameterModels(MethodModel.java:501)
at org.glassfish.jersey.microprofile.restclient.MethodModel.access$1200(MethodModel.java:85)
at org.glassfish.jersey.microprofile.restclient.MethodModel$Builder.build(MethodModel.java:645)
at org.glassfish.jersey.microprofile.restclient.MethodModel.from(MethodModel.java:110)
at org.glassfish.jersey.microprofile.restclient.RestClientModel.parseMethodModels(RestClientModel.java:91)
at org.glassfish.jersey.microprofile.restclient.RestClientModel.from(RestClientModel.java:48)
at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:125)
at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:85)
at org.glassfish.jersey.microprofile.restclient.MethodModel$Builder.build(MethodModel.java:662)
The following lines are endless repeated:
at org.glassfish.jersey.microprofile.restclient.MethodModel.from(MethodModel.java:110)
at org.glassfish.jersey.microprofile.restclient.RestClientModel.parseMethodModels(RestClientModel.java:91)
at org.glassfish.jersey.microprofile.restclient.RestClientModel.from(RestClientModel.java:48)
at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:125)
at org.glassfish.jersey.microprofile.restclient.MethodModel.<init>(MethodModel.java:85)
at org.glassfish.jersey.microprofile.restclient.MethodModel$Builder.build(MethodModel.java:662)
According to the docs, 5.2022.5 supports the following:
- Jakarta EE 8
- MicroProfile 4.1
- Jersey 2.37
- Jackson 2.13.4
My dev server is using OpenJDK 1.8.0_412, but I’ll eventually build a docker image using the official Payara 5.2022.5 image.
Here is my code.
First, the data classes used for client interface:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import java.io.Serializable;
import java.util.Objects;
@JsonAutoDetect(
creatorVisibility = JsonAutoDetect.Visibility.NONE,
fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
setterVisibility = JsonAutoDetect.Visibility.NONE)
public class DataRecord implements Serializable {
public static final String PREFIX = "SOME_PREFIX:";
private String key;
public DataRecord() {
}
public DataRecord(DataRecord o) {
this(o.key);
}
public DataRecord(String key) {
this.key = key;
}
public String getDataKey() {
return buildLockKey(this.getKey());
}
public static String buildLockKey(String lockKey) {
return PREFIX.concat(lockKey);
}
public String getKey() {
return key;
}
@Override
public boolean equals(Object o) {
if (this.key == null || !(o instanceof DataRecord)) {
return false;
}
DataRecord other = (DataRecord) o;
return this.key.equals(other.key);
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.key);
return hash;
}
}
@JsonAutoDetect(
creatorVisibility = JsonAutoDetect.Visibility.NONE,
fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
setterVisibility = JsonAutoDetect.Visibility.NONE)
public class DataList implements Serializable {
private final String responseId;
private Collection<DataRecord> data;
public DataList() {
this.responseId = UUID.randomUUID().toString();
}
public DataList(Collection<DataRecord> data) {
this();
this.data = data;
}
public Collection<DataRecord> getdata() {
return data;
}
public String getResponseId() {
return responseId;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DataList)) {
return false;
}
DataList other = (DataList) o;
return this.responseId.equals(other.responseId);
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.responseId);
return hash;
}
}
Next, an exception mapper:
public class DataResponseExceptionMapper implements ResponseExceptionMapper<ServiceClientException> {
@Override
public boolean handles(int statusCode, MultivaluedMap<String, Object> headers) {
return statusCode == Response.Status.UNAUTHORIZED.getStatusCode()
|| statusCode == Response.Status.NOT_FOUND.getStatusCode()
|| statusCode == Response.Status.CONFLICT.getStatusCode()
|| statusCode == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
}
@Override
public ServiceClientException toThrowable(Response response) {
if (Response.Status.UNAUTHORIZED.getStatusCode() == response.getStatus()) {
return new UnauthorizedException("User is unauthorized");
} else if (Response.Status.CONFLICT.getStatusCode() == response.getStatus()) {
DataRecord existing = null;
if (response.hasEntity()) {
existing = response.readEntity(DataRecord.class);
}
return new DataClientException(Response.Status.CONFLICT, existing, "Some message here");
} else if (response.getStatus() >= 500) {
return new ServiceErrorException(Response.Status.fromStatusCode(response.getStatus()), "Service failure");
}
return null;
}
}
The REST Client interface:
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
@RegisterProvider(DataResponseExceptionMapper.class)
@Path("/data")
public interface DataServiceClient {
@GET
@Produces(MediaType.APPLICATION_JSON)
public DataList getAllData() throws UnauthorizedException, ServiceErrorException;
@GET
@Path("/{key}")
@Produces(MediaType.APPLICATION_JSON)
public DataRecord getDataStatus(@PathParam("key") String key) throws UnauthorizedException, ServiceErrorException;
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public DataRecord postData(DataRecord Data) throws DataClientException, UnauthorizedException, ServiceErrorException;
@DELETE
@Path("/{key}")
@Produces(MediaType.APPLICATION_JSON)
public DataRecord getReleaseData(@PathParam("key") String key) throws DataClientException, UnauthorizedException, ServiceErrorException;
@DELETE
@Produces(MediaType.APPLICATION_JSON)
public DataList getReleaseAllData() throws DataClientException, UnauthorizedException, ServiceErrorException;
}
And finally, the producer and injection point.
@ApplicationScoped
public class ApplicationBean {
@Produces
public DataServiceClient DataService() {
try {
return RestClientBuilder.newBuilder()
.baseUri(getDataURI())
.build(DataServiceClient.class);
} catch (URISyntaxException ex) {
logger.fire(new LogEvent<>(Level.SEVERE, "Could not initialize Data Client Service", ex));
}
return null;
}
}
@Singleton
@ConcurrencyManagement
@AccessTimeout(value = 60, unit = TimeUnit.SECONDS)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class DataManagerBean implements DataManagerLocal {
@Inject
private DataServiceClient dataService; <--- ERROR
@Override
public DataRecord get(String key) throws UnauthorizedException, ServiceErrorException {
return dataService.getData(key);
}
@Override
public DataRecord post(DataRecord data) throws DataClientException, UnauthorizedException, ServiceErrorException {
return dataService.postData(data);
}
I made sure to annotate the DataRecord
so that only the properties are serialized/deserialized, and there is not cyclic dependencies on the two data classes I created.
What could be the problem? A bug with the version of microprofile bundled with Payara 5.2022.5?