Enterprise Java Development@TOPIC@
Web content is shared using many standardized MIME Types
We will address only 2 of them -- both now required by JAX-RS
XML - JAXB support was part of original JAX-RS spec
JSON - requirement for JSONB support was added to JAX-RS in 2.1 as a part of JavaEE 8
No other MIME Types are required by JAX-RS
Any can be added thru standard JAX-RS marshaling/demarshaling framework
Many are immediately available thru vendor extensions
We will show manual approaches to marshaling/demarshaling first
However, content is automatically marshaled/demarshaled by JAX-RS provider
Manual marshaling/demarshaling approaches mainly useful within debug
Sample DTO Class
public class MessageDTO implements Serializable {
private String text;
public MessageDTO() {}
public MessageDTO(String message) {
this.text = message;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
}
Implements Serializable to be legal for @Remote interfaces
Typical JavaBean
Default CTOR
public setters/getters
JSON is content type most preferred by Javascript UIs
{"text":"sample text"}
Default marshaling/demarshaling is supported without class modifications
We will look at two JSON marshaling/demarshaling frameworks; JSONB and Jackson
Standard added to JavaEE 8
Start by creating an instance of Jsonb
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
JsonbConfig config=new JsonbConfig();
//config.setProperty(JsonbConfig.FORMATTING, true);
//config.setProperty(JsonbConfig.PROPERTY_NAMING_STRATEGY, PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES);
//config.setProperty(JsonbConfig.NULL_VALUES, true); //helps us spot fields we don't want
Jsonb builder = JsonbBuilder.create(config);
Use Jsonb instance to marshal DTO instance to JSON
protected <T> String marshal(T object) {
if (object==null) { return ""; }
String buffer = builder.toJson(object);
return buffer;
}
Use Jsonb to demarshal DTO instance from JSON
protected <T> T demarshal(Class<T> type, String buffer) {
T result = (T) builder.fromJson(buffer, type);
return result;
}
Direct dependency on API modules useful for pure DTO libraries
Jsonb API
<properties>
<javax.json.bind-api.version>1.0</javax.json.bind-api.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.json.bind</groupId>
<artifactId>javax.json.bind-api</artifactId>
<version>${javax.json.bind-api.version}</version>
</dependency>
<dependencies>
<!-- defines JSON-B entry point e.g., Jsonb and @Annotations -->
<dependency>
<groupId>javax.json.bind</groupId>
<artifactId>javax.json.bind-api</artifactId>
<scope>provided</scope>
</dependency>
Jsonb Provider
Direct dependency on provider modules useful for pure DTO modules with marshaling tests. JAX-RS provider will automatically bring in these dependencies if part of IT tests.
<properties>
<glassfish-json.version>1.1.2</glassfish-json.version>
<yasson.version>1.0.1</yasson.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>${yasson.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>${glassfish-json.version}</version>
</dependency>
<dependencies>
<!-- JSON-B - reference implementation for JSON-B (e.g., JsonBindingProvider) -->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<scope>test</scope>
</dependency>
<!-- JSON provider for JSON-B reference implementation - needed by yasson -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<scope>test</scope>
</dependency>
JBoss/Wildfly default JSON marshaler prior to JavaEE 8
Some inconsistencies with JSON-B relative to collections
Safest to use same marshaler as server
Start by creating an instance of ObjectMapper
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
Use ObjectMapper instance to marshal DTO instance to JSON
protected <T> String marshal(T object)
throws JsonGenerationException, JsonMappingException, IOException {
StringWriter buffer = new StringWriter();
mapper.writeValue(buffer, object);
return buffer.toString();
}
Use ObjectMapper to demarshal DTO instance from JSON
protected <T> T demarshal(Class<T> type, String buffer)
throws JsonParseException, JsonMappingException, IOException {
T result = mapper.readValue(buffer, type);
return result;
}
Direct dependency on API modules useful for pure DTO libraries
Jackson API
<properties>
<jackson.version>2.9.5</jackson.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
Jackson Provider
Direct dependency on provider modules useful for pure DTO modules with marshaling tests. JAX-RS provider will automatically bring in these dependencies if part of IT tests.
<properties>
<jackson.version>2.9.5</jackson.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependencies>
<!-- core Jackson2 libraries -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<message>
<text>sample text</text>
</message>
JAX-RS JAX-B support has existed since the start of JAX-RS
XML is much more precise than JSON -- but with precision and tuning comes complication
Root objects in stream must be annotated as @XmlRootElement
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="message")
public class MessageDTO implements Serializable {
Element namespaces are very common is XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:message xmlns:ns2="urn:ejava.jaxrs.todos">
<text>sample text</text>
</ns2:message>
@XmlRootElement(name="message", namespace="urn:ejava.jaxrs.todos")
public class MessageDTO implements Serializable {
Start by creating an instance of JAXBContext appropriate for the specific type
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
JAXBContext jbx = JAXBContext.newInstance(object.getClass());
JAXBContext jbx = JAXBContext.newInstance(type);
Use JAXBContext instance obtain a Marshaller to marshal DTO instance to XML
import javax.xml.bind.Marshaller;
protected <T> String marshal(T object) throws JAXBException {
if (object==null) { return ""; }
JAXBContext jbx = JAXBContext.newInstance(object.getClass());
Marshaller marshaller = jbx.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter buffer = new StringWriter();
marshaller.marshal(object, buffer);
return buffer.toString();
}
Use JAXBContext instance to obtain an Unmarshaller to demarshal DTO instance from XML
import javax.xml.bind.Unmarshaller;
protected <T> T demarshal(Class<T> type, String buffer) throws JAXBException {
if (buffer==null) { return null; }
JAXBContext jbx = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jbx.createUnmarshaller();
ByteArrayInputStream bis = new ByteArrayInputStream(buffer.getBytes());
@SuppressWarnings("unchecked")
T result = (T) unmarshaller.unmarshal(bis);
return result;
}
Ignore property
@XmlTransient
Represent as XML attribute (versus a child element)
@XmlAttribute
JAX-RS defines an encoding/decoding framework to marshal/demarshal content
Provider will automatically perform this work for JSON, XML, and vendor-specific supported content types
Pass result of Entity.entity() with target mediaType and annotations to guide marshaling choices
# client
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
# client
Response response = target.request(mediaType)
.buildPost(Entity.entity(todoList, mediaType, todoList.getClass().getAnnotations()))
.invoke();
Provider selects matching URI path method that can accept content type and passes as method parameter
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@POST
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createTodoList(TodoListDTO todoList) {
Set Response.entity to response payload
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@POST
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createTodoList(TodoListDTO todoList) {
TodoListDTO entity = todosMgmt.createTodoList(todoList);
URI location = ...
ResponseBuilder rb = Response.created(location)
.contentLocation(location)
.entity(entity);
return rb.build();
Pass Response.readEntity the type of object expected and annotations to guide demarshaling choices
# client
import javax.ws.rs.core.Response;
# client
static <T> T getEntity(Response response, Class<T> type) {
if (Response.Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily())) {
return response.readEntity(type, type.getAnnotations());
} else {
throw new IllegalStateException(String.format("error response[%d %s]: %s",
response.getStatus(),
response.getStatusInfo(),
response.readEntity(String.class))
);
}
}
Content providers only necessary for IT tests. Application server will have all API and implementation modules server-side.
Content Provider Maven modules
<properties>
<resteasy.version>3.5.1.Final</resteasy.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-json-binding-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependencies>
<!-- JSON-B JSON wiring for RESTEasy JAX-RS provider (javaee8; highest JSON priority) -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-json-binding-provider</artifactId>
<scope>test</scope>
</dependency>
<!-- JSON wiring for RESTEasy JAX-RS provider (javaee7) -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<scope>test</scope>
</dependency>
<!-- XML impl for RESTEasy JAX-RS provider -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<scope>test</scope>
</dependency>
If you taking a minimalist approach of least configuration and using JavaEE 7 Wildfly -- you will want to use Jackson for JSON marshaling. If you are using JavaEE 8, then you will want to use Jsonb. The two can be used together -- but you must be more aware of the tweaks required to have the provider chose one over the other.