Jersey 1.18.1 here. I have the following Jersey resource defined on my server:
#Path("/location")
#Produces(MediaType.APPLICATION_JSON)
public class LocationResourceImpl implements LocationResource {
private ObjectMapper mapper;
public LocationResourceImpl() {
super();
mapper = new ObjectMapper();
// TODO: Configure for JSON POJO mapping how?!?
}
#GET
#Path("address/{address_id}")
#Override
public Address getAddress(#PathParam("address_id") Long id) {
Address address;
address = new Address(
1L,
"19 ABC Dr",
"Suite 3",
"Testville",
"NY",
"US",
"12345");
return address;
}
}
My Address POJO is properly annotated with #JsonProperty annotations. I am trying to figure out how to configure my ObjectMapper instance so that the Address instance returned by getAddress(Long) returns my address as JSON.
Any ideas as to what I can do? It look like setSerializationConfig and setDeserializationConfig methods were added in Jersey 2.x, but upgrading isn't an option for me, as I'm using DropWizard 0.7.1, which depends on Jersey 1.18.x.
With Dropwizard, we don't need any special configuration for basic Jackson POJO mapping support. As explained in the Dropwizard User Guide: How it's Glued Together:
When your application starts up, it will spin up a Jetty HTTP server, see DefaultServerFactory. This server will have two handlers, one for your application port and the other for your admin port.
The application port has an HttpServlet as well, this is composed of DropwizardResourceConfig, which is an extension of Jersey’s resource configuration that performs scanning to find root resource and provider classes.
DropwizardResourceConfig is where the various ResourceMethodDispatchAdapter are registered to enable the following functionality:
Enables using Jackson to parse request entities into objects and generate response entities from objects, all while performing validation.
Related
I have a java class with uppercase field names and some of them with under scroll, like this:
public class DATADto {
private String UPPERCASE;
private String UNDER_SCROLL;
public String getUPPERCASE() { return UPPERCASE; }
public void setUPPERCASE(String s) { UPPERCASE = s; }
...//setters and getters
}
and I used this in a rest endpoint that accepts json in a spring rest controller:
#RestController
#RequestMapping({"/api/path"})
public class MyRestController {
#PostMapping(path = {"/Data"}, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> useDATADto(#RequestBody DATADto aDATADto ) {
//do something
}
}
what JSON fields do I need to send by default and why?
The story goes like this..
Spring Boot by default uses Jackson ObjectMapper to serialize and deserialize Java objects.
In this context, by serialization we mean the conversion of java objects into json, deserialization is the reverse process.
Regarding the #RequestBody annotation, the following is written in the documentation:
Annotation indicating a method parameter should be bound to the body
of the web request. The body of the request is passed through an
HttpMessageConverter to resolve the method argument depending on the
content type of the request. Optionally, automatic validation can be
applied by annotating the argument with #Valid.
In short, #RequestBody annotation tells Spring to deserialize an incoming request body into an object passed as a parameter to the handler method. Spring achieves this using MessageConverter
Since Spring Boot uses Jackson by default for serializing and deserializing request and response objects in your REST APIs, and Jackson uses MappingJackson2HttpMessageConverter, so that will be message converter implementation that spring will use. You can read more about that here.
The important thing is that Jackson uses Java Bean naming conventions to figure out the json properties in a Java class. Acutally it uses default PropertyNamingStrategy . Here is what is written in documentation:
In absence of a registered custom strategy, default Java property
naming strategy is used, which leaves field names as is, and removes
set/get/is prefix from methods (as well as lower-cases initial
sequence of capitalized characters).
So, since you didn't set any naming strategy, it will use default one.
Beacause of that, if you send payload like this :
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}
That won't work, you will get exception, since there jackson won't find under_scroll property in your class, it will look for under_SCROLL , therefore this payload:
{
"uppercase": "YOUR_VALUE",
"under_SCROLL": "YOUR_VALUE"
}
will work.
To change default PropertyNamingStrategy check
this article.
It will depend on the Jackson property naming strategy. The default is LOWER_CAMEL_CASE , so your request body should look like this:
{
"uppercase": "test",
"under_scroll": "test"
}
For all possible configurations of the naming strategy for Jackson please refer to the document «Class PropertyNamingStrategy»
If you're using Spring, you may use this property to configure the naming strategy:
spring.jackson.property-naming-strategy
Another possible way will be the bean configuration:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder jacksonMapper = new Jackson2ObjectMapperBuilder();
jacksonMapper.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
return jacksonMapper;
}
Additional note:
Your current naming approach doesn't follow the Java Code Conventions. If you need to process JSON with some specific naming format better to use the #JsonProperty annotation on the fields of your POJO.
Please see the example below:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
#Data
public class DATADto {
#JsonProperty("UPPERCASE")
private String uppercase;
#JsonProperty("UNDER_SCROLL")
private String underScroll;
}
You should send post request to /api/path/data with this request body:
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}
In the context of Jersey 2.25.1, a very simple REST API is built with a simple JAXB POJO bean:
#XmlRootElement
public class Profile {
#XmlAttribute
private String name;
//setter and getter
}
and a resource
public class ProfileResource {
#Path("/profile")
#Produce(MediaType.APPLICATION_XML)
public Profile getProfile() {
Profile p = new Profile();
p.setName("test");
return p;
}
}
and it is running on a Tomcat server.
A bug is in the POJO - Profile - deliterately so that when sending http request to localhost:8080/contextroot/profile, I would get an internal error response with STATUS 500. However, the ERROR detail is not written into the default log file in the backend Tomcat server. By comparison, with Spring framework all errors will be logged.
According to the Jersey Documentation, tracing support can be enabled by set jersey.config.server.tracing.type to ALL. So I extends the jax.ws.rs.core.Application as the following:
#ApplicationPath("contextroot")
public class ApplicationConfig extends Application {
#Override
public Map<String, Object> getProperties() {
Map<String,Object> properties = new HashMap<>();
properties.put(ServerProperties.TRACING, TracingConfig.ALL.toString());
System.out.println("Appliction is getting properties::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
return properties;
}
}
with the jaxrs-ri dependency according to How to set up JAX-RS Application using annotations only (no web.xml). So this class is indeed instantiated when starting the application on the Tomcat server. However, when debugging on the the first line in org.glassfish.jersey.server.ServerRuntime.process(final ContainerRequest request) class, i.e. TracingUtils.initTracingSupport(tracingConfig, tracingThreshold, request);, the value of property jersey.config.server.tracing.type was detected to be still OFF when sending a request, meaning the ApplicationConfig is not used at all.
Question: How to enable the tracing support of a Jersey application on the Tomcat server
NOTE! There is a rough answer in Why does jersey have no error log when the status is 500? However, this answer will turn all the errors into internal error with status 500, which is unexpected obviously
I got several classes that are annotated with meta information:
#Metadata(id="Foo",number=3)
public class Foo {
...
}
#Metadata(id="Bar",number=11)
public class SomeBar {
...
}
Now I want to publish this meta information via Jersey, returning it as a JSON structure. To do so I created a resource:
...
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response list(){
...
List<Object> result=new LinkedList<>();
for each class in (classes annotated with #Metadata) { //pseudo
Metadata md=annotatedclass.getAnnotation(Metadata.class);
result.add(md);
}
return Response.ok(result).build();
}
A browser call to this method fails with HTTP 500 / Server Error - without any further info! No exceptions in debug log, no extended info on the error page.
What actually works is translating Metadata objects "by hand". Substituting result.add(md) with ObjectMapper mapper=new ObjectMapper(); result.add(mapper.writeValueAsString(md)); works perfectly. I get all the metadata objects serialized as (JSON-)Strings.
So what is Jetty doing different in serializing my metadata annotations?
I'm building an API in java using DropWizard. However, for certain resources I also need to consume other RESTful API's. These other API's do not require any authentication.
Can DropWizard be used to consume API's? Or what are some other ways to simply consume a RESTful API in a java application? Since I'm using DropWizard I already have Jackson.
So if the REST API is something like this:
[ {"id": "0",
"name" : "Joe"
]
I'd like to have an object like this List<Foo>
I suppose you can use a DropWizard's Jersey Client. According to the documentation, it does exactly what you are looking for.
http://www.dropwizard.io/1.0.3/docs/manual/client.html
I.e.:
public class ExampleConfiguration extends Configuration {
#Valid
#NotNull
private JerseyClientConfiguration jerseyClient = new JerseyClientConfiguration();
#JsonProperty("jerseyClient")
public JerseyClientConfiguration getJerseyClientConfiguration() {
return jerseyClient;
}
}
Then, in your service’s run method, create a new JerseyClientBuilder:
#Override
public void run(ExampleConfiguration config,
Environment environment) {
final Client client = new JerseyClientBuilder(environment).using(config.getJerseyClientConfiguration())
.build(getName());
environment.jersey().register(new ExternalServiceResource(client));
}
I have 2 questions:
1. Can I create one class, annotate it with JAXB annotations(for XML support) and declare in web.xml
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
for JSON (Jackson library) support? Or I need to create separately two classes for JSON and XML?
Or may be exist some more elegant way to cause REST service to return both JSON and XML?
2. How I can programmatically choose what type to return (JSON or XML)?
Thanks.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Can I create one class, annotate it with JAXB annotations(for XML support) and declare in web.xml for JSON (Jackson library) support?
You can always use an Application class to specify a MessageBodyReader/MessageBodyWriter for the JSON binding. I believe Jackson provides an implementation in its jar. Below is an example of an Application class that specifies MOXy as the JSON provider:
package org.example;
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class CustomerApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(MOXyJsonProvider.class);
set.add(CustomerService.class);
return set;
}
}
Or I need to create separately two classes for JSON and XML?
EclipseLink JAXB (MOXy) offers native XML binding and is designed to enable you to use the same object model for both JSON and XML. You can integrate it into your JAX-RS application using the MOXyJsonProvider class:
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
How I can programmatically choose what type to return (JSON or XML)?
Server Side
You can specify that your service offers both XML and JSON messages using the #Produces annotation.
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("{id}")
public Customer read(#PathParam("id") long id) {
return entityManager.find(Customer.class, id);
}
For More Information
http://blog.bdoughan.com/2012/03/moxy-as-your-jax-rs-json-provider.html
Client Side
You can use the MediaType to indicate the type of message. Below is an example using Jersey client APIs. Note how the URL is the same, just the requested media type is different.
Client client = Client.create();
WebResource resource = client.resource("http://localhost:8080/CustomerService/rest/customers");
// Get XML response as a Customer
Customer customer = resource.path("1")
.accept(MediaType.APPLICATION_XML)
.get(Customer.class);
System.out.println(customer.getLastName() + ", "+ customer.getFirstName());
// Get JSON response as a Customer
Customer customer = resource.path("1")
.accept(MediaType.APPLICATION_JSON)
.get(Customer.class);
System.out.println(customer.getLastName() + ", "+ customer.getFirstName());
For More Information
http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-55.html
If your client wants to use a part of the URL to configure the response type, you can use a Servlet filter.
An easy way to implement overriding the representation (media type) could use a URL query parameter:
/resources/todo?format=json
The Servlet filter parses the URL query parameters, and if a format=json is present, replaces or adds the accept header "application/json".
No need for seperate classes, what you need is seperate methods:
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Todo getXML() {
Todo todo = new Todo();
todo.setSummary("This is my first todo");
todo.setDescription("This is my first todo");
return todo;
}
Then in the client side, when you request for the service, you indicate in what format you want it:
// Get XML
System.out.println(service.path("rest").path("todo").accept(MediaType.TEXT_XML).get(String.class));
// Get XML for application
System.out.println(service.path("rest").path("todo").accept(MediaType.APPLICATION_XML).get(String.class));
// Get JSON for application
System.out.println(service.path("rest").path("todo").accept(MediaType.APPLICATION_JSON).get(String.class));