2

I have an application running on Payara 4 using a custom GSON JSON adapter. I would like to migrate to Payara 5 (5.191) and start using JSON-B. In our current application we can control the JSON output using annotations on a resource.

For example using @Summarize:

@GET
@Path("summary/{encryptedId}")
@Produces(MediaType.APPLICATION_JSON)
@Summarize
public Address findSummarized(@PathParam("encryptedId") String encryptedId) {
  return super.find(encryptedId);
}

it will cause a different GSON configuration to be used in our @Provider:

@Provider
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class GsonProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {

  public GsonProvider() {
    gson = getGson(EntityAdapter.class);
    gsonSummary = getGson(EntitySummaryAdapter.class);
  }

  ...

  @Override
  public void writeTo(T object,
                      Class<?> type,
                      Type genericType,
                      Annotation[] annotations,
                      MediaType mediaType,
                      MultivaluedMap<String, Object> httpHeaders,
                      OutputStream entityStream)
  throws IOException, WebApplicationException {
    boolean summarize = contains(annotations, Summarize.class);
    try (PrintWriter printWriter = new PrintWriter(entityStream)) {
      printWriter.write((summarize ? gsonSummary : gson).toJson(object));
      printWriter.flush();
    }
  }

}

I want to do something similar in the new JSON-B setup. I annotated our entities with @JsonbTypeSerializer(MySerializer.class), so I would like to be able to detect from within the serializer what it should do: either create a full serialized JSON object, or a summary.

What I hoped to do is set a property in the JsonbConfig, like so:

JsonbConfig config = new JsonbConfig()
        .setProperty("com.myCompany.jsonb.summarize", true);

and read it in the serializer using @Context (just guessing that this might work here), like so:

@Context
private JsonbConfiguration config;

.. but that's not. Is there any way to access JAX-RS resource annotations from a JsonbSerializer?

Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
  • would it work to create an `AddressSummary` class to represent the summarized data, and then you could have an `AddressSummary.from(Address)` static creator method and your `findSummarized` JAX-RS method could return an `AddressSumamry` instead of an `Address` object – Andy Guibert Dec 20 '19 at 17:55
  • I'd prefer to just have one or two (de)serializers. Our model consists of close to 200 entities. But it might be an option. – Jasper de Vries Dec 21 '19 at 08:46

2 Answers2

0

You could accomplish a similar goal using two separate Jsonb instances in your JAX-RS provider class like so:

@Provider
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class JsonbProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {

  private static final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
                                       .withAdapters(new EntityAdapter()));
  private static final Jsonb jsonbSummary = JsonbBuilder.create(new JsonbConfig()
                                       .withAdapters(new EntitySummaryAdapter()));

  ...

  @Override
  public void writeTo(T object,
                      Class<?> type,
                      Type genericType,
                      Annotation[] annotations,
                      MediaType mediaType,
                      MultivaluedMap<String, Object> httpHeaders,
                      OutputStream entityStream)
  throws IOException, WebApplicationException {
    boolean summarize = contains(annotations, Summarize.class);
    try (PrintWriter printWriter = new PrintWriter(entityStream)) {
      printWriter.write((summarize ? jsonbSummary : jsonb).toJson(object));
      printWriter.flush();
    }
  }

}
Andy Guibert
  • 41,446
  • 8
  • 38
  • 61
  • The key thing here is using a `JsonbAdapter`. I like that concept. Unfortunately it does not work as put in your answer (using a builder `withAdapters`). I had to annotate my entity with `@JsonbTypeAdapter(EntityAdapter)` to get the adapter working... which only allows to use me one single adapter. – Jasper de Vries Dec 22 '19 at 13:54
0

In the end I opted to create summaries from within my entities and drop the annotation on my REST resources. It was a bit of work, but I think it has been worth it.

I created a Summarizable interface and added a default method there to create a simple map summary of any entity, based on a extended version of the PropertyVisibilityStrategy we created for the full version of the entities.

public interface Summarizable {

  public default Map<String, Object> toSummary() {
    SummaryPropertyVisibilityStrategy summaryStrategy = new SummaryPropertyVisibilityStrategy();
    Map<String, Object> summary = new LinkedHashMap<>();
    ReflectionUtils.getFields(this.getClass())
            .stream()
            .filter(summaryStrategy::isVisible)
            .map(f -> new AbstractMap.SimpleEntry<>(f.getName(), summarize(f)))
            .filter(e -> e.getValue() != null)
            .forEach(e -> summary.put(e.getKey(), e.getValue()));
    return summary;
  }

  public default Object summarize(final Field field) {
    Object value = ReflectionUtils.getValueJsonb(this, field);
    return value != null && Stream.of(ManyToOne.class, OneToOne.class).anyMatch(field::isAnnotationPresent)
                   ? value.toString()
                   : value;
  }

}
  public static Object getValueJsonb(final Object object, final Field field) {
    field.setAccessible(true);
    JsonbTypeAdapter adapterAnnotation = field.getAnnotation(JsonbTypeAdapter.class);
    try {
      Object value = field.get(object);
      return adapterAnnotation == null
             ? value
             : adapterAnnotation.value().newInstance().adaptToJson(value);
    }
    catch (Exception ex) {
      throw new IllegalStateException(ex);
    }
  }
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102