I am putting together a short little REST demo of OSGi R7, OpenAPI, and jsonSchema. Everything works fine, except Postman is giving the following error:

No message body writer has been found for class com.ovloop.masterdata.party.pojo.provider.Person, ContentType: application/json

My class for implementing Restful services is written as follows. BTW, I can run swagger-maven-plugin on this class and get the corresponding Swagger spec.

package com.xyz.masterdata.party.rest;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
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.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardResource;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.xyz.masterdata.party.pojo.provider.Person;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;

@JaxrsResource
@HttpWhiteboardResource(pattern = { "" }, prefix = "")
@Path("/person")
@Tags(@Tag(name = "test", description = ""))
@OpenAPIDefinition(info = @Info(description = "Person Service", version = "1.0", title = ""))
@Component(service = PersonRestImpl.class, immediate = true, property = { "service.exported.interfaces=*",
        "service.intents=osgi.async", "service.intents=jaxrs", "osgi.basic.timeout=50000" })
public class PersonRestImpl {

    @GET
    @Path("/person")
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Get person with key", description = "Get person with key")
    public Person getPerson(@PathParam("person") Long personId) {
        Person person = new Person();
        person.setPersonId("3245234");
        person.setFirstName("Tom");
        person.setLastName("Petty");
        person.setAge(65);

        return person;
    }

    @GET
    @Path("/persons")
    @Operation(summary = "Get persons", description = "Get list of persons")
    public Response getPersons() {
        Person person = new Person();
        person.setPersonId("3245234");
        person.setFirstName("Tom");
        person.setLastName("Petty");
        person.setAge(65);

        List<Person> persons = new ArrayList<Person>();
        persons.add(person);

        Gson jsonConverter = new GsonBuilder().create();
        return Response.status(Status.ACCEPTED).entity(jsonConverter.toJson(persons)).build();
    }

    @DELETE
    @Path("/delete")
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "delete person", description = "delete person")
    public boolean deletePerson(@PathParam("person") long personId) {
        return true;
    }

    @POST
    @Path("/create")
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Create Person", description = "Create Person")
    public Person postPerson(Person person) {
//      if (person.personId > 0) {
//          personDao.update(person);
//          return person;
//      } else {
//          long id = personDao.save(person);
//          person.personId = 1;
//          return person;
//      }

        return person;

    }
}

My POJO, which is generated from a jsonSchema using jsonschema2pojo-maven-plugin, is as follows:

package com.xyz.masterdata.party.pojo.provider;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;


/**
 * Person
 * <p>
 * 
 * 
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
    "personId",
    "age",
    "fullName",
    "firstName",
    "lastName"
})
public class Person {

    /**
     * 
     * (Required)
     * 
     */
    @JsonProperty("personId")
    private String personId;
    /**
     * Age in years
     * 
     */
    @JsonProperty("age")
    @JsonPropertyDescription("Age in years")
    private Integer age;
    /**
     * 
     * (Required)
     * 
     */
    @JsonProperty("fullName")
    private String fullName;
    @JsonProperty("firstName")
    private String firstName;
    @JsonProperty("lastName")
    private String lastName;
    @JsonIgnore
    private Map<String, Object> additionalProperties = new HashMap<String, Object>();

    /**
     * 
     * (Required)
     * 
     */
    @JsonProperty("personId")
    public String getPersonId() {
        return personId;
    }

    /**
     * 
     * (Required)
     * 
     */
    @JsonProperty("personId")
    public void setPersonId(String personId) {
        this.personId = personId;
    }

    public Person withPersonId(String personId) {
        this.personId = personId;
        return this;
    }

    /**
     * Age in years
     * 
     */
    @JsonProperty("age")
    public Integer getAge() {
        return age;
    }

    /**
     * Age in years
     * 
     */
    @JsonProperty("age")
    public void setAge(Integer age) {
        this.age = age;
    }

    public Person withAge(Integer age) {
        this.age = age;
        return this;
    }

    /**
     * 
     * (Required)
     * 
     */
    @JsonProperty("fullName")
    public String getFullName() {
        return fullName;
    }

    /**
     * 
     * (Required)
     * 
     */
    @JsonProperty("fullName")
    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public Person withFullName(String fullName) {
        this.fullName = fullName;
        return this;
    }

    @JsonProperty("firstName")
    public String getFirstName() {
        return firstName;
    }

    @JsonProperty("firstName")
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public Person withFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    @JsonProperty("lastName")
    public String getLastName() {
        return lastName;
    }

    @JsonProperty("lastName")
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Person withLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    @JsonAnyGetter
    public Map<String, Object> getAdditionalProperties() {
        return this.additionalProperties;
    }

    @JsonAnySetter
    public void setAdditionalProperty(String name, Object value) {
        this.additionalProperties.put(name, value);
    }

    public Person withAdditionalProperty(String name, Object value) {
        this.additionalProperties.put(name, value);
        return this;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(Person.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('[');
        sb.append("personId");
        sb.append('=');
        sb.append(((this.personId == null)?"<null>":this.personId));
        sb.append(',');
        sb.append("age");
        sb.append('=');
        sb.append(((this.age == null)?"<null>":this.age));
        sb.append(',');
        sb.append("fullName");
        sb.append('=');
        sb.append(((this.fullName == null)?"<null>":this.fullName));
        sb.append(',');
        sb.append("firstName");
        sb.append('=');
        sb.append(((this.firstName == null)?"<null>":this.firstName));
        sb.append(',');
        sb.append("lastName");
        sb.append('=');
        sb.append(((this.lastName == null)?"<null>":this.lastName));
        sb.append(',');
        sb.append("additionalProperties");
        sb.append('=');
        sb.append(((this.additionalProperties == null)?"<null>":this.additionalProperties));
        sb.append(',');
        if (sb.charAt((sb.length()- 1)) == ',') {
            sb.setCharAt((sb.length()- 1), ']');
        } else {
            sb.append(']');
        }
        return sb.toString();
    }

    @Override
    public int hashCode() {
        int result = 1;
        result = ((result* 31)+((this.firstName == null)? 0 :this.firstName.hashCode()));
        result = ((result* 31)+((this.lastName == null)? 0 :this.lastName.hashCode()));
        result = ((result* 31)+((this.fullName == null)? 0 :this.fullName.hashCode()));
        result = ((result* 31)+((this.personId == null)? 0 :this.personId.hashCode()));
        result = ((result* 31)+((this.additionalProperties == null)? 0 :this.additionalProperties.hashCode()));
        result = ((result* 31)+((this.age == null)? 0 :this.age.hashCode()));
        return result;
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if ((other instanceof Person) == false) {
            return false;
        }
        Person rhs = ((Person) other);
        return (((((((this.firstName == rhs.firstName)||((this.firstName!= null)&&this.firstName.equals(rhs.firstName)))&&((this.lastName == rhs.lastName)||((this.lastName!= null)&&this.lastName.equals(rhs.lastName))))&&((this.fullName == rhs.fullName)||((this.fullName!= null)&&this.fullName.equals(rhs.fullName))))&&((this.personId == rhs.personId)||((this.personId!= null)&&this.personId.equals(rhs.personId))))&&((this.additionalProperties == rhs.additionalProperties)||((this.additionalProperties!= null)&&this.additionalProperties.equals(rhs.additionalProperties))))&&((this.age == rhs.age)||((this.age!= null)&&this.age.equals(rhs.age))));
    }

}

My bndrun file, on the application project, appears as follows:

-runfw: org.eclipse.osgi;version=3.13
-runee: JavaSE-1.8
-runprovidedcapabilities: ${native_capability}

-resolve.effective: active

-runproperties: \
    osgi.console=,\
    osgi.console.enable.builtin=false

-runrequires: \
    osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\
    osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.command)',\
    osgi.identity;filter:='(osgi.identity=com.xyz.masterdata.application.services)',\
    bnd.identity;id='com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider',\
    bnd.identity;id='com.fasterxml.jackson.jaxrs.jackson-jaxrs-base',\
    bnd.identity;id='com.fasterxml.jackson.core.jackson-databind',\
    bnd.identity;id='com.fasterxml.jackson.core.jackson-core',\
    bnd.identity;id='com.xyz.masterdata.party.rest',\
    bnd.identity;id='com.xyz.masterdata.party.pojo.provider'
-runbundles: \
    com.xyz.masterdata.application.services;version=snapshot,\
    com.xyz.masterdata.party.rest;version=snapshot,\
    com.xyz.masterdata.party.pojo.provider;version=snapshot,\
    org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
    org.apache.felix.configurator;version='[1.0.6,1.0.7)',\
    org.apache.felix.scr;version='[2.1.10,2.1.11)',\
    ch.qos.logback.classic;version='[1.2.3,1.2.4)',\
    ch.qos.logback.core;version='[1.2.3,1.2.4)',\
    org.apache.aries.javax.jax.rs-api;version='[1.0.0,1.0.1)',\
    org.apache.aries.jax.rs.whiteboard;version='[1.0.1,1.0.2)',\
    org.apache.felix.gogo.runtime;version='[1.0.10,1.0.11)',\
    org.apache.felix.http.jetty;version='[4.0.6,4.0.7)',\
    org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
    org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\
    org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\
    org.osgi.util.function;version='[1.1.0,1.1.1)',\
    org.osgi.util.promise;version='[1.1.0,1.1.1)',\
    slf4j.api;version='[1.7.25,1.7.26)',\
    org.apache.felix.gogo.command;version='[1.0.2,1.0.3)',\
    org.apache.felix.gogo.shell;version='[1.0.0,1.0.1)',\
    io.swagger.core.v3.swagger-annotations;version='[2.0.7,2.0.8)',\
    com.google.gson;version='[2.8.5,2.8.6)',\
    com.fasterxml.jackson.core.jackson-annotations;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.core.jackson-core;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.core.jackson-databind;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.jaxrs.jackson-jaxrs-base;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider;version='[2.9.8, 2.9.9)'

As mentioned above, however, when running this simple app, Postman is giving me the following error when invoking the getPerson method:

No message body writer has been found for class com.ovloop.masterdata.party.pojo.provider.Person, ContentType: application/json

But when I replace the response type from Person to Response, and hand-generate the JSON, all works well:

@GET
    @Path("/person")
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Get person with key", description = "Get person with key")
    public Response getPerson(@PathParam("person") Long personId) {
        Person person = new Person();
        person.setPersonId("3245234");
        person.setFirstName("Tom");
        person.setLastName("Petty");
        person.setAge(65);

        Gson jsonConverter = new GsonBuilder().create();
        return Response.status(Status.ACCEPTED).entity(jsonConverter.toJson(person)).build();
    }

Unfortunately, this leaves the Person data type out of my generated swagger spec, which isn't good.

So my question is how do I register a Message Body Writer for custom POJO's within an OSGi application?

Thanks in advance for your feedback,

Randy

1 Answers

2
Randy Leonard On Best Solutions

Paul had mentioned registering a JacksonJsonProvider. I had the impression adding fasterxml's jackson provider as a required bundle would do the trick. I instead swapped this out for org.apache.aries.jax.rs.jackson, and it all worked.

Below is my updated bndrun file:

-runfw: org.eclipse.osgi;version=3.13
-runee: JavaSE-1.8
-runprovidedcapabilities: ${native_capability}

-resolve.effective: active

-runproperties: \
    osgi.console=,\
    org.osgi.service.http.port=6000,\
    osgi.console.enable.builtin=false

-runrequires: \
    osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\
    osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.command)',\
    bnd.identity;id='org.apache.aries.jax.rs.jackson',\
    bnd.identity;id='org.apache.aries.jax.rs.jaxb.json',\
    bnd.identity;id='com.xyz.masterdata.party.rest',\
    bnd.identity;id='com.xyz.masterdata.party.pojo.provider',\
    bnd.identity;id='com.xyz.masterdata.application.services'
-runbundles: \
    com.xyz.masterdata.party.rest;version=snapshot,\
    com.xyz.masterdata.party.pojo.provider;version=snapshot,\
    org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
    org.apache.felix.scr;version='[2.1.10,2.1.11)',\
    ch.qos.logback.classic;version='[1.2.3,1.2.4)',\
    ch.qos.logback.core;version='[1.2.3,1.2.4)',\
    org.apache.aries.javax.jax.rs-api;version='[1.0.0,1.0.1)',\
    org.apache.aries.jax.rs.whiteboard;version='[1.0.1,1.0.2)',\
    org.apache.felix.gogo.runtime;version='[1.0.10,1.0.11)',\
    org.apache.felix.http.jetty;version='[4.0.6,4.0.7)',\
    org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
    org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\
    org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\
    org.osgi.util.function;version='[1.1.0,1.1.1)',\
    org.osgi.util.promise;version='[1.1.0,1.1.1)',\
    slf4j.api;version='[1.7.25,1.7.26)',\
    org.apache.felix.gogo.command;version='[1.0.2,1.0.3)',\
    org.apache.felix.gogo.shell;version='[1.0.0,1.0.1)',\
    io.swagger.core.v3.swagger-annotations;version='[2.0.7,2.0.8)',\
    com.fasterxml.jackson.core.jackson-annotations;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.core.jackson-core;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.core.jackson-databind;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.jaxrs.jackson-jaxrs-base;version='[2.9.8,2.9.9)',\
    com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider;version='[2.9.8,2.9.9)',\
    com.xyz.masterdata.application.services;version=snapshot,\
    org.apache.felix.configurator;version='[1.0.6,1.0.7)',\
    com.fasterxml.jackson.module.jackson-module-jaxb-annotations;version='[2.9.8,2.9.9)',\
    org.apache.aries.jax.rs.jackson;version='[1.0.2,1.0.3)',\
    javax.json-api;version='[1.1.4,1.1.5)',\
    org.apache.cxf.cxf-core;version='[3.3.1,3.3.2)',\
    org.apache.cxf.cxf-rt-frontend-jaxrs;version='[3.3.1,3.3.2)',\
    org.apache.cxf.cxf-rt-security;version='[3.3.1,3.3.2)',\
    org.apache.cxf.cxf-rt-transports-http;version='[3.3.1,3.3.2)',\
    org.apache.ws.xmlschema.core;version='[2.2.4,2.2.5)',\
    org.apache.aries.jax.rs.jaxb.json;version='[1.0.0,1.0.1)'