I am trying to create a rest api server on top of my quartz scheduler. I want to be able to return the org.quartz.Trigger and org.quartz.JobDetail objects as JSON. The problem is that I cannot add the #XmlRootElement to these classes without having to recompile the jar and this causes problems with future upgrades etc. I have tested and am able to serialize a list of classes when adding the #XmlRootElement but when I try to return a List I get the error "A message body writer for Java class java.util.ArrayList, and Java type java.util.List, and MIME media type application/json was not found". I have tried adding a custom MessageBodyWriter but that does not seem to fix the problem either. Is there a way to marshal the quartz classes to JSON without having to modify the quartz code to add the #XmlRootElement. I am using this in an embedded web server with jetty btw.
#Path("/jobs")
public class JobsResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Trigger> listScheduledJobs() throws SchedulerException {
return TaskEngine.getInstance().listScheduledJobs();
}
}
Web server configuration
public class TaskEngineWebServer {
private static final Logger logger = Logger.getLogger(TaskEngineWebServer.class.getName());
private Server server;
public TaskEngineWebServer() {
this(8585);
}
public TaskEngineWebServer(Integer port) {
server = new Server(port);
logger.info("Configuring rest service to start at url /r");
ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.NO_SECURITY);
//handler.getInitParams().put("com.sun.jersey.api.json.POJOMappingFeature", "true");
PackagesResourceConfig packagesResourceConfig = new PackagesResourceConfig("com.hp.vf.scheduler.server.rest");
ServletContainer servletContainer = new ServletContainer(packagesResourceConfig);
handler.addServlet(new ServletHolder(servletContainer), "/r/*");
server.setHandler(handler);
logger.info("Done configuring rest service");
}
public void start() throws Exception {
server.start();
}
public void stop() throws Exception {
server.stop();
}
public boolean isStarted() {
return server.isStarted();
}
public boolean isStopped() {
return server.isStopped();
}
}
I dont think you can return a List as JSON directly. You need to have a wrapper class which contains this list. For eg try something like this
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class TriggerWrapper{
private List<Triggers> triggers;
public List<Triggers> getTriggers(){
if(triggers==null){
triggers = new ArrayList<Triggers>();
}
return triggers;
}
}
Then in your rest service class :
#Path("/jobs")
public class JobsResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public TriggerWrapper listScheduledJobs() throws SchedulerException {
TriggerWrapper response = new TriggerWrapper();
List<Triggers> triggers = TaskEngine.getInstance().listScheduledJobs();
response.getTriggers.addAll(triggers);
return response;
}
}
Your json would something like this :
{
"triggerwrapper": {
"triggers": [
{
"triggerid": 1
},
{
"triggerid": 2
}
]
}
}
And ofcourse if you want you can drop the root element tag from your json its configurable in jersey.
I finally figured out a clean solution, it involves creating my own MediaBodyWriter class and adding it as a provider. You have to make sure you are not using the jersey-bundle jar as the default jaxb to json provider will override yours.
jars required
jersey-core
jersey-servlet
jersey-server
jackson-annotations
jackson-databind
jackson-core
I found this MediaWriter example on the web somewhere. Sorry for not having the url but thanks to whoever write it.
#Provider
#Produces({ MediaType.APPLICATION_JSON })
public class JacksonWriter implements MessageBodyWriter<Object> {
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public long getSize(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) {
try {
MAPPER.writeValue(entityStream, value);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
When it loads you will see a log message that your provider was loaded.
This gave me the json output I was expecting as it does not rely on the JAXB annotations and simply uses the object mapper/ reflection. Probably less efficient but for my case it does not matter.
Related
Given the following basic domain model:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
I want to write a Spring MVC controller endpoint thus ...
#PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(#RequestBody BaseData baseData) { ... }
The required concrete type of baseData can be inferred from the typeOfData in the path.
This allows me to have a single method that can handle multiple URLs with different body payloads. I would have a concrete type for each payload but I don't want to have to create multiple controller methods that all do the same thing (albeit each would do very little).
The challenge that I am facing is how to "inform" the deserialization process so that the correct concrete type is instantiated.
I can think of two ways to do this.
First use a custom HttpMessageConverter ...
#Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
#Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
... which gives me the challenge to determine the subType based on the HttpInputMessage. Possibly I could use a Filter to set a custom header earlier when the URL is available to me, or I could use a ThreadLocal also set via a Filter. Neither sounds ideal to me.
My second approach would be to again use a Filter and this time wrap the incoming payload in an outer object which would then provide the type in a way that enables Jackson to do the work via #JsonTypeInfo. At the moment this is probably my preferred approach.
I have investigated HandlerMethodArgumentResolver but if I try to register a custom one it is registered AFTER the RequestResponseBodyMethodProcessor and that class takes priority.
Hmm, so after typing all of that out I had a quick check of something in the RequestResponseBodyMethodProcessor before posting the question and found another avenue to explore, which worked neatly.
Excuse the #Configuration / #RestController / WebMvcConfigurer mash-up and public fields, all for brevity. Here's what worked for me and achieved exactly what I wanted:
#Configuration
#RestController
#RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
#Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
#PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(#BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
The key to this is that I stopped using #RequestBody because that is what was preventing me overriding the built-in behaviour. By using #BaseData instead I get a HandlerMethodArgumentResolver that uniquely supports the parameter.
Other than that it was a case of assembling the two objects that already did what I needed, so autowire a MappingJackson2HttpMessageConverter and instantiate a RequestResponseBodyMethodProcessor with that one converter. Then pick the right method to override so that I could control what parameter type was used at a point that I had access to the URI.
Quick test. Given the following payload for both requests ...
{
"a": "A",
"b": "B"
}
POST http://localhost:8081/dummy/data-a
... gives a response of ...
{
"a": "A"
}
POST http://localhost:8081/dummy/data-b
... gives a response of ...
{
"b": "B"
}
In our real-world example this means that we will be able to write one method each that supports the POST / PUT. We need to build the objects and configure the validation possibly - or alternatively if we use OpenAPI 3.0 which we are investigating we could generate the model and validate without writing any further code ... but that's a separate task ;)
I'm marshalling my objects with Jackson (annotations) to JSON & XML and it works really great but there's a problem with XML.I want to add a DTD-File.I searched a little bit and found the #XmlHeader-Annotation (org.glassfish.jersey.message.XmlHeader) to add a header :
#Path("resources/xml/hashtagstatistic")
#GET
#XmlHeader("<!DOCTYPE note SYSTEM \"test.dtd\">")
#Produces(MediaType.APPLICATION_XML)
public Database getStatisticAsXml(){
return serviceController.getDatabase();
}
But it's not working. I tried the same with jaxb and there the header was added to my XML-Output.But I want to use Jackson because it returns easy my wanted structure (I don't like/want adapters). Is there a possibility to fix this or are there other solutions to handle this problem with the header?
Yeah it's a Jersey specific annotation, so Jackson won't know anything about it. I see a couple options. You could use a WriterInterceptor, and just write the header yourself.
#Provider
public class XmlHeaderWriterInterceptor implements WriterInterceptor {
#Context
private ResourceInfo info;
#Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
XmlHeader anno = info.getResourceMethod().getAnnotation(XmlHeader.class);
if (anno != null) {
String value = anno.value();
writeToStream(outputStream, value);
}
context.proceed();
}
}
Or you could create a MessageBodyWriter. But instead of implementing your own from scratch, you could just extend the one from Jackson (assuming this is what you're currently using)
#Provider
public class MyJackonXmlProvier extends JacksonJaxbXMLProvider {
#Context
private ResourceInfo info;
#Override
public void writeTo(Object value, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String,Object> httpHeaders,
OutputStream entityStream) {
// do same thing as example above
super.writeTo(pass, all, arguments)
}
Which ever one you use, just make sure to register it with the application.
jersey 2.21.
I have a resource file like below
……
#POST
#Path("/userReg")
#Produces("application/json;charset=UTF-8")
public JsonResp userReg(UserRegReq userRegReq) throws LoginNameExists {
HttpHeaderUtils.parseHeaders(userRegReq, headers);
//JsonResp is a custom java class.
JsonResp result = new JsonResp();
//will throw LoginNameExists
User user = userManager.register(userRegReq.getLoginName(), userRegReq.getPassword());
//success
result.setResult(0);
result.setData(user.getId);
return result;
}
……
To return the result to client, I implement a custom MessageBodyWriter like below
#Produces("application/json")
public class MyRespWriter implements MessageBodyWriter<JsonResp> {
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return type == JsonResp.class;
}
#Override
public long getSize(JsonResp jsonResp, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return 0;
}
#Override
public void writeTo(JsonResp jsonResp, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
//if these no exception in userReg(),
//the parameter annotations contains the annotations
//such as POST, Path, Produces;
//but if there is an exception in userReg(),
//the parameter annotations contains none of POST, Path, Produces;
//So, is there any way to retrieve the original annotations all along?
//JsonUtils is a custom java class.
String data = JsonUtils.toJsonString(jsonResp);
Writer osWriter = new OutputStreamWriter(outputStream, "UTF-8");
osWriter.write(data);
osWriter.flush();
}
}
And to handle the exceptions, I implement a ExceptionMapper like this:
public class MyExceptionMapper implements ExceptionMapper<Exception> {
public Response toResponse(Exception e) {
JsonResp result = new JsonResp();
//error
result.setResult(-1);
result.setErrMsg("System error.");
return Response.ok(result, MediaType.APPLICATION_JSON_TYPE).status(Response.Status.OK).build();
}
}
Now, if everything is ok and there’s no exception, the code execution router is userReg() -> MyRespWriter.writeTo(), the parameter "annotations" of MyRespWriter.writeTo() contains the correct annotations of method userReg(), such as POST, Path, Produces.
But if userReg() throws exception, the code execution router is userReg() -> MyExceptionMapper.toResponse() -> MyRespWriter.writeTo(), the parameter "annotations" of method MyRespWriter.writeTo() has none of the annotations of method userReg().
I want to know, is there any way that MyRespWriter.writeTo() can retrieve the original annotations all along?
You can inject ResourceInfo, then get the Method with ri.getResourceMethod(), then call method.getAnnotations() to get the annotations.
public class MyRespWriter implements MessageBodyWriter<JsonResp> {
#Context
ResourceInfo ri;
...
Annotations[] annos = ri.getResourceMethod().getAnnotations();
This may be a strange question, although I wonder why it hasn't been asked or proposed before... so please correct me if any ignorance.
First off, I am using Jackson in conjunction with Spring and the #ResponseBody annotation.
Currently, for every request handler I am returning a "Response" wrapper object, as that is what the client expects. This wrapper is quite simple:
{ "response": { "data" : ACTUAL_DATA } }
Thing is, I'm not a fan of explicitly wrapping each return value for all my request handlers. I also do not like having to unwrap these response wrappers in my unit tests.
Rather, I wonder if it were possible to return the ACTUAL_DATA as it were, and to intercept and wrap this data elsewhere.
If this is in fact possible, would it then be possible to read the annotations attached to the intercepted request handler? This way I can use custom annotations to decide how to wrap the data.
For example something like this would be amazing (note that #FetchResponse and #ResponseWrapper are made up proposed annotations):
#RequestMapping(...)
#FetchResponse
#ResponseBody
public List<User> getUsers() {
...
}
#ResponseWrapper(FetchResponse.class)
public Object wrap(Object value) {
ResponseWrapper rw = new ResponseWrapper();
rw.setData(value);
return rw;
}
Anyone familiar with this territory? Or alternatively, and reasons why this might be bad practice?
Well, looks like I'm looking for Spring's "ResponseBodyAdvice" and "AbstractMappingJacksonResponseBodyAdvice".
For anybody looking on more info on this topic: I was facing the same issue as well, and thanks to the tip of kennyg, i managed to come up with the following solution:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
#ControllerAdvice
public class JSendAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof JSendResponse) {
return body;
}
return new JSendResponse<>().success(body);
}
}
This solution wraps all the objects returned in your controllers inside a (for this example) JSendResponse class, which saves you the hassle of returning JSendResponses in all of your controller methods.
I know it's been a while since the answer was accepted but I recently stumbled onto an issue with Jackson that allowed me to discover a problem with using ResponseBodyAdvice.
Jackson will not correctly serialize your polymorphic types that use #JsonTypeInfo / #JsonSubTypes if during runtime the values of your types are not known: i.e. for example if you have a generic container type like class ResponseWrapper<T> { List<T> objects; }. That is unless you provide Jackson with specialization of that generic type before you ask it to serialize your value, refer to Why does Jackson polymorphic serialization not work in lists? . Spring does this for you when you return say a list of T and that T is known because it's provided explicitly in the method return type (as in public List<MyEntity> getAllEntities();).
If you simply implement ResponseBodyAdvice and return a new, wrapped value from beforeBodyWrite() then Spring will no longer know your full generic type with its specialization, and it will serialize your response as ResponseWrapper<?> instead of ResponseWrapper<MyEntity>.
The only way around this is to both extend from AbstractJackson2HttpMessageConverter and override writeInternal(). See how the method treats the type here: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L437
And you also need to implement a Controller advice using AbstractMappingJacksonResponseBodyAdvice and your own custom MappingJacksonValue that includes Type targetType that custom HttpMessageConverter will use.
ResponseWrapper
public class ResponseWrapper<T> {
#Nullable Error error;
T result;
public ResponseWrapper(T result) {
this.result = result;
}
}
WrappingAdvice
#Component
public class WrappingAdvice extends AbstractMappingJacksonResponseBodyAdvice {
#Override
protected MappingJacksonValue getOrCreateContainer(Object body) {
MappingJacksonValue cnt = super.getOrCreateContainer(body);
if (cnt instanceof MyMappingJacksonValue) {
return cnt;
}
return new MyMappingJacksonValue(cnt);
}
#Override
protected void beforeBodyWriteInternal(
MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
MyMappingJacksonValue cnt = (MyMappingJacksonValue) bodyContainer;
Type targetType = getTargetType(bodyContainer.getValue(), returnType);
cnt.setValue(new ResponseWrapper(cnt.getValue()));
cnt.setTargetType(TypeUtils.parameterize(
ResponseWrapper.class,
targetType));
}
/**
* This is derived from AbstractMessageConverterMethodProcessor
*/
private Type getTargetType(Object value, MethodParameter returnType) {
if (value instanceof CharSequence) {
return String.class;
}
Type genericType;
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
genericType = ResolvableType.forType(returnType.getGenericParameterType()).getGeneric().getType();
} else {
genericType = returnType.getGenericParameterType();
}
return GenericTypeResolver.resolveType(genericType, returnType.getContainingClass());
}
public static class MyMappingJacksonValue extends MappingJacksonValue {
private Type targetType;
public MyMappingJacksonValue(MappingJacksonValue other) {
super(other.getValue());
setFilters(other.getFilters());
setSerializationView(other.getSerializationView());
}
public Type getTargetType() {
return targetType;
}
public void setTargetType(Type targetType) {
this.targetType = targetType;
}
}
}
JsonHttpMessageBodyConverter
#Component
public class JsonHttpMessageBodyConverter extends AbstractJackson2HttpMessageConverter {
// omitted all constructors
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (object instanceof WrapAPIResponseAdvice.MyMappingJacksonValue) {
type = ((WrapAPIResponseAdvice.MyMappingJacksonValue) object).getTargetType();
}
super.writeInternal(object, type, outputMessage);
}
}
I suspect this can't be done, but maybe there's a trick I'm missing. I want to use to different methods that take the same MediaType, but have different parameters to the mediatype. Perhaps this is abusing MediaType parameters...
#POST
#Consumes("application/json;internal=true")
public Response handleInternal(String request) {
}
#POST
#Consumes("application/json;internal=false")
public Response handleExternal(String request) {
}
Jersey complains I have two methods consuming the same MediaType, which is true. I was hoping it'd go on to pick the right one by the parameter. Is there some trick to making this work? In a nutshell, I have two use cases for how to treat the information coming in (specifically, domain level validation) and this seemed like a decent way to distinguish between those two.
You could use a MessageBodyReader along with two user types, one for internal json and the other for external json
1- Create two types than extends String (via delegation -using lombok is easier-):
#RequiredArgsConstructor
public class InternalJSON {
#Delegate
private final String _theJSONStr;
}
#RequiredArgsConstructor
public class ExternalJSON {
#Delegate
private final String _theJSONStr;
}
2- Create the MessageBodyReader type
#Provider
public class MyRequestTypeMapper
implements MessageBodyReader<Object> {
#Override
public boolean isReadable(final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
// this matches both application/json;internal=true and application/json;internal=false
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}
#Override
public Object readFrom(final Class<Object> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String,String> httpHeaders,
final InputStream entityStream) throws IOException,
WebApplicationException {
if (mediaType.getSubType().equals("internal=true") {
// Build an InternalJSON instance parsing entityStream
// ... perhaps using JACKSON or JAXB by hand
} else if (mediaType.getSubType().equals("internal=false") {
// Build an ExternalJSON instance parsing entityStream
// ... perhaps using JACKSON or JAXB by hand
}
}
}
3- Register your MessageBodyReader at the Application (this is optional since jersey will scan the classpath for #Provider annotated types
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
...
s.add(MyRequestTypeMapper .class);
return s;
}
4- Reformat your rest methods usgin the two user types for internal and external json