Lessons from developing Message Broker

II spent 3 weeks developing a pub/sub message broker whose transport layer is a ReSTful webservice layer.  The documentation at https://jersey.github.io/ is very good, but there are a few gotchas that don't really stick out when you examine the examples or read the documentation.  In this page I outline the things that threw me and left me banging my head for hours and days.

IssueRemedy

Using Jackson library <groupId>com.fasterxml.jackson.core</groupId>, <artifactId>jersey-hk2</artifactId>, and <artifactId>jersey-media-json-binding</artifactId> 

does not marshall and unmarshall java objects correctly

You must also add the following 

<dependency>

<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.27</version>

</dependency>

You must also register the jackson marshalling object with Jersey

Client client = ClientBuilder.newClient().register(JacksonFeature.class);

If you use the Jackson 2.9xx fasterxml libraries to marshall and unmarshall java objects to and from Strings and the Java object is a composite object (so you would end up with nested json objects), the translation works fine, but sending that string across the wire will cause a HTTP 400 error.

Only use simple Java objects that do not contain other objects, Strings are allowed.

e.g. 

public static ObjectMapper getGlobalObjectMapper()
{

if( itsMapper == null )
{

itsMapper = new ObjectMapper();

itsMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
itsMapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance().withFieldVisibility(JsonAutoDetect.Visibility.ANY));

}

return itsMapper;

}

// In some other method later that is going to make a ReSTful call

objectMapper mapper = GlobalConfigs.getGlobalObjectMapper();
String jsonRequest = mapper.writeValueAsString( <java object> );
Response response = resource.addSubscriber( jsonRequest );

Consider the following ReSTful endpoint definition

@Path("/services")
public interface BrokerServiceInfc
{

@GET
@Path("/sayhello")
@Produces(MediaType.TEXT_HTML)
public Response sayHtmlHelloTest();

@GET
@Path("/subscribe/{topic}/{domain}/{suburi}")
@Produces(MediaType.APPLICATION_JSON)
public Response addSubscriber( @PathParam("topic")String topic,
@PathParam("domain")String domain,
@PathParam("suburi")String suburi );

@POST
@Path("/subscribe")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response addSubscriber(String subInfo);

@POST
@Path("/subscribe")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response addSubscriber(SubscriptionRequest subInfo);

@GET
@Path("/createtopic/{topicName}")
@Produces(MediaType.APPLICATION_JSON)
public Response createTopic( @PathParam("topicName") String topicName);

@GET
@Path("/removetopic/{topicName}")
@Produces(MediaType.APPLICATION_JSON)
public Response removeTopic( @PathParam("topicName") String topicName);

@GET
@Path("/testtopic/{topicName}/{value}")
@Produces(MediaType.APPLICATION_JSON)
public Response testTopic(@PathParam("topicName") String topicName,
@PathParam("value") String value);

}

The two @Path("/subscribe") will cause a conflict with the web server.  The conflict is caused by the method names and the @Produces and @Consumes values are the same.  It will result in a HTTP 500 error.

The solution is to make the @Path() values slightly different

@POST
@Path("/subscribe")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response addSubscriber(String subInfo);

@POST
@Path("/jsubscribe")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response addSubscriber(SubscriptionRequest subInfo);


@Path("/services")
public interface BrokerServiceInfc
{

}

The interface isn't actually needed, you can place all of the methods directly on a class that don't implement any interface and it works just fine.  The interface is only needed if you want to perform RPC between applications that are going to use ReSTful services for inter processes communications.

Marshalling of Java objects fail if they are not full Java Beans e.g.

public class SecurityKey 
{

private String key;

public SecurityKey(){}

public SecurityKey( String key )
{

this.key = key;

}

public String getKey()
{

return key;

}

}

Objects of this class will transmit but unmarshalling will fail because there is no setter for the key attribute.

If you don't have a default constructor, you will get a HTTP 500 error during unmarshalling as the default constructor cannot be found.

Each class should be written as 

public class SecurityKey 
{

private String key;

public SecurityKey(){}

public SecurityKey( String key )
{

this.key = key;

}

public void setKey( String key )
{

this.key = key;

}

public String getKey()
{

return key;

}

}

This is a full fledged javabean.