0

Problem:

Given the REST endpoint:

@Path("/companies")
@Stateless
public class CompanyService{

    @EJB
    private CompanyEjb ejb;

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Company update(Company company){
        return ejb.update(company);
    }

}

After submitting a PUT request, JAX-RS is expected to return the updated company such as

{
    "id": 1,
    "name": "Company",
    "departments": [1,2]
}

However, only the primary key, 1, is returned. A second request triggers an exception:

Warning:   StandardWrapperValve[[RestApplicationClass]: Servlet.service() for servlet RestApplicationClass threw exception
java.lang.NoSuchMethodException: java.lang.Long.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at org.eclipse.yasson.internal.ReflectionUtils.lambda$createNoArgConstructorInstance$1(ReflectionUtils.java:188)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.eclipse.yasson.internal.ReflectionUtils.createNoArgConstructorInstance(ReflectionUtils.java:186)
    at org.eclipse.yasson.internal.serializer.ObjectDeserializer.getInstance(ObjectDeserializer.java:92)
    at org.eclipse.yasson.internal.serializer.AbstractContainerDeserializer.deserialize(AbstractContainerDeserializer.java:62)
    at org.eclipse.yasson.internal.serializer.AdaptedObjectDeserializer.deserialize(AdaptedObjectDeserializer.java:94)
    at org.eclipse.yasson.internal.Unmarshaller.deserializeItem(Unmarshaller.java:57)
    at org.eclipse.yasson.internal.Unmarshaller.deserialize(Unmarshaller.java:50)
    at org.eclipse.yasson.internal.JsonBinding.deserialize(JsonBinding.java:45)
    at org.eclipse.yasson.internal.JsonBinding.fromJson(JsonBinding.java:85)

The adapter seems to be applied altough I don't have the company to be adapted

Context

Configuration is:

  • Payara 5.0.0.Alpha3 (hence Yasson 1.0)
  • Java EE 8 (hence JAX-RS 2.1, JSON-B 1.0 a JSON-P 1.1)
  • Application is packed in EAR format
  • REST request submitted with Postman

Two entities Company and Department are linked by a @OneToMany relationships. Both entities have a primary key id and a name name. To avoid JSON infinite loop, the @ManyToOne Company of Department has an adapter:

public class Department{
    // ...
    @ManyToOne // JPA stuff irrelevant here
    @JsonbTypeAdapter(CompanyAdapter.class)
    private Company company;
    // ...
}

with the (simplified) adapter:

public class CompanyAdapter implements JsonbAdapter<Company, Long>{

    @EJB
    private CompanyEjb ejb;

    @Override
    public Long adaptToJson(Company orgnl) throws Exception {
        return orgnl != null ? orgnl.getId() : null;
    }

    @Override
    public E adaptFromJson(Long adptd) throws Exception {
        return ejb.findById(adptd);
    }
}

Workaround:

Initially, I thought that once an adapter is defined, they will be applied everywhere, similarly to JPA Converters with autoApply = true. This led me nowhere

At first, I thought about using the last version of Yasson (1.0.2-SNAPSHOT) to fix the problem. To do so, I need to tell Payara to load the Yasson I want. So the glassfish-web.xml is modified as followed:

<glassfish-web-app error-url="">
    ...
    <!-- <class-loader delegate="true"/> -->
    <class-loader delegate="false"/>
    ...
</glassfish-web-app>

according to Payara documentation.

But it appears than only switching the class-loader delegation to false is enough.

=> why?

Subsidiary question:

Because of some hot-deploy issues, I need to do the following to test my changes:

  1. make the changes
  2. build & deploy
  3. restart the server after deploy

If by any chance someone knows why a hot-deploy/simple deploy modifies the JAX-RS adapter behaviour, I'm looking forward the answer

Al-un
  • 3,102
  • 2
  • 21
  • 40

1 Answers1

1

Just to post back to SO here, the OP raised a detailed GitHub issue on Yasson and the root cause is that Yasson caches the object model (so it doesn't have to re-scan object models every time). This caching causes issue in an application server environment where Yasson is provided by the container (e.g. JAX-RS runtime) and when the user updates their app, only their app classes get reloaded but Yasson and it's caches remain loaded.

Short term fix

The workaround for now is to restart the app server when making changes to your JSON-B data classes.

Long term

I'm going to see if Yasson and/or JSON-B spec can define some sort of "clear caches" hook. The idea here would be when an app server hot-updates an app, it can invoke this "clear cache" hook to flush out the stored object model from the application.

Andy Guibert
  • 41,446
  • 8
  • 38
  • 61