Implement PATCH endpoint with JAX-RS and Liferay (Apache CXF) - java

I am trying to implement a PATCH endpoint with JAX-RS in a Liferay OSGi module. The GET, POST and PUT endpoints are working fine, but I am stuck with the PATCH endpoint. As I don't know any better, I am trying to use the example implementation of Daan Scheerens.
My (simplified) implementations so far, beginning with the controller:
#Path("/resources")
public class ResourceController {
#PATCH
#Path("/{resourceId}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
Response patchResource(#PathParam("resourceId") long resourceId, ObjectPatch objectPatch) {
// Get the resource
Resource resource = getResource(resourceId);
// Apply the patch
objectPatch.apply(resource);
// Return the resource
return Response.ok(resource).build();
}
}
So I need an ObjectPatch interface that I did exactly like in Daan's example:
public interface ObjectPatch {
<T> T apply(T target) throws ObjectPatchException;
}
Next step is to implement the MessageBodyReader:
#Provider
public class PartialJsonObjectPatchReader implements MessageBodyReader<ObjectPatch> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return ObjectPatch.class == type && MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
}
#Override
public ObjectPatch readFrom(Class<ObjectPatch> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException {
JsonNode patch = OBJECT_MAPPER.readTree(entityStream);
return new PartialJsonObjectPatch(OBJECT_MAPPER, patch);
}
}
The only difference to the example implementation is that I added the #Provider annotation. As far as I understood this registers the MessageBodyReader implementation automatically to the JAX-RS runtime, like it is described here and here. From the latter:
A class wishing to provide such a service implements the MessageBodyReader interface and may be annotated with #Provider for automatic discovery.
I just have the feeling that this automatic discovery does not happen.
The last important class is the implementation of the ObjectPatch interface:
public class PartialJsonObjectPatch implements ObjectPatch {
private final ObjectMapper objectMapper;
private final JsonNode patch;
public PartialJsonObjectPatch(ObjectMapper objectMapper, JsonNode patch) {
this.objectMapper = objectMapper;
this.patch = patch;
}
#Override
public <T> T apply(T target) throws ObjectPatchException {
ObjectReader reader = objectMapper.readerForUpdating(target);
try {
return reader.readValue(patch);
} catch (IOException e) {
throw new ObjectPatchException(e);
}
}
}
If I now do a PATCH request to the endpoint, it gives me this error message:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.ObjectPatch (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I kind of understand the error message as an interface can not be instantiated, but I do not understand why I get this message. Shouldn't it try to instantiate a PartialJsonObjectPatch instead of an ObjectPatch? If I change the parameter class of the patchResource() method to PartialJsonObjectPatch (which I shouldn't), I get this error message:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.PartialJsonObjectPatch (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
The error messages lead me to this, but adding a default constructor doesn't help.
What am I missing?

The #Provider annotation is not sufficient, I had to add my MessageBodyReader implementation to the singletons in the Application class:
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
// All your other Providers, Readers, Writers, e.g:
singletons.add(new JacksonJaxbJsonProvider());
// My MessageBodyReader implementation
singletons.add(new PartialJsonObjectPatchReader());
return singletons;
}

Related

Spring Boot: Optional mapping of request parameters to POJO

I'm trying to map request parameters of a controller method into a POJO object, but only if any of its fields are present. However, I can't seem to find a way to achieve this. I have the following POJO:
public class TimeWindowModel {
#NotNull
public Date from;
#NotNull
public Date to;
}
If none of the fields are specified, I'd like to get an empty Optional, otherwise I'd get an Optional with a validated instance of the POJO. Spring supports mapping request parameter into POJO objects by leaving them unannotated in the handler:
#GetMapping("/shop/{shopId}/slot")
public Slice<Slot> getSlots(#RequestAttribute("staff") Staff staff,
#PathVariable("shopId") Long shopId, #Valid TimeWindowModel timeWindow) {
// controller code
}
With this, Spring will map request parameters "from" and "to" to an instance of TimeWindowModel. However, I want to make this mapping optional. For POST requests you can use #RequestBody #Valid Optional<T>, which will give you an Optional<T> containing an instance of T, but only if a request body was provided, otherwise it will be empty. This makes #Valid work as expected.
When not annotated, Optional<T> doesn't appear to do anything. You always get an Optional<T> with an instance of the POJO. This is problematic when combined with #Valid because it will complain that "from" and "to" are not set.
The goal is to get either (a) an instance of the POJO where both "from" and "to" are not null or (b) nothing at all. If only one of them is specified, then #Valid should fail and report that the other is missing.
I came up with a solution with a custom HandlerMethodArgumentResolver, Jackson and Jackson Databind.
The annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestParamBind {
}
The resolver:
public class RequestParamBindResolver implements HandlerMethodArgumentResolver {
private final ObjectMapper mapper;
public RequestParamBindResolver(ObjectMapper mapper) {
this.mapper = mapper.copy();
this.mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestParamBind.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mav, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// take the first instance of each request parameter
Map<String, String> requestParameters = webRequest.getParameterMap()
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()[0]));
// perform the actual resolution
Object resolved = doResolveArgument(parameter, requestParameters);
// *sigh*
// see: https://stackoverflow.com/questions/18091936/spring-mvc-valid-validation-with-custom-handlermethodargumentresolver
if (parameter.hasParameterAnnotation(Valid.class)) {
String parameterName = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, resolved, parameterName);
// DataBinder constructor unwraps Optional, so the target could be null
if (binder.getTarget() != null) {
binder.validate();
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.getErrorCount() > 0)
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
return resolved;
}
private Object doResolveArgument(MethodParameter parameter, Map<String, String> requestParameters) {
Class<?> clazz = parameter.getParameterType();
if (clazz != Optional.class)
return mapper.convertValue(requestParameters, clazz);
// special case for Optional<T>
Type type = parameter.getGenericParameterType();
Class<?> optionalType = (Class<?>)((ParameterizedType)type).getActualTypeArguments()[0];
Object obj = mapper.convertValue(requestParameters, optionalType);
// convert back to a map to find if any fields were set
// TODO: how can we tell null from not set?
if (mapper.convertValue(obj, new TypeReference<Map<String, String>>() {})
.values().stream().anyMatch(Objects::nonNull))
return Optional.of(obj);
return Optional.empty();
}
}
Then, we register it:
#Configuration
public class WebConfig implements WebMvcConfigurer {
//...
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestParamBindResolver(new ObjectMapper()));
}
}
Finally, we can use it like so:
#GetMapping("/shop/{shopId}/slot")
public Slice<Slot> getSlots(#RequestAttribute("staff") Staff staff,
#PathVariable("shopId") Long shopId,
#RequestParamBind #Valid Optional<TimeWindowModel> timeWindow) {
// controller code
}
Which works exactly as you'd expect.
I'm sure it's possible to accomplish this by using Spring's own DataBind classes in the resolver. However, Jackson Databind seemed like the most straight-forward solution. That said, it's not able to distinguish between fields that are set to null and fields that just not set. This is not really an issue for my use-case, but it's something that should be noted.
To implement logic (a) both not null or (b) both are nulls you need to implement custom validation.
Examples are here:
https://blog.clairvoyantsoft.com/spring-boot-creating-a-custom-annotation-for-validation-edafbf9a97a4
https://www.baeldung.com/spring-mvc-custom-validator
Generally, you create a new annotation, it's just a stub, and then you create a validator which implements ConstraintValidator where you provide your logic and then you put your new annotation to your POJO.

Java jax-rs client response entity generic

I have a problem with a generic function. I want to use a function to which I assign a certain class / type to first generate the corresponding result from a rest response and then return it.
public class TimerService {
[...]
public <T extends myObjInterface> RestMessageContainer<T> send(String endpointPath, Map<String, String> parameters, Class<T> clazz) {
[...]
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get();
RestMessageContainer<T> container = response.readEntity(new GenericType<RestMessageContainer<T>>() {});
return container;
}
}
public class RestMessageContainer<T extends myObjInterface> {
[...]
#XmlAttribute(name = "data")
private List<T> data;
[...]
}
I get the following error message at runtime.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.test.myObjInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
14:47:41,982 ERROR [stderr] (EJB default - 2) at [Source: (org.jboss.resteasy.client.jaxrs.internal.ClientResponse$InputStreamWrapper); line: 3, column: 14] (through reference chain: com.test.RestMessageContainer["data"]->java.util.ArrayList[0])
The error is output for the line RestMessageContainer<T> container = response.readEntity(new GenericType<RestMessageContainer<T>>() {});
Is my approach correct at all or how should I solve my problem?
Thanks for your advice,
I have several subclasses. There is no information about the type in the JSON strings. The type results from the reqeust address. I couldn't configure Jackson to recognize the subtype. There is no unique field in the JSON strings that I could use as a type.
I am not able to change the web service that delivers the JSON strings.
[UPDATE]
I have found a solution. I no longer let the JAX-RS client convert the JSON string. I have the JSON string returned to me as a string and convert it independently using Jackson.
public <T extends myObjInterface> RestMessageContainer<T> send(String endpointPath, Map<String, String> parameters, Class<T> clazz) {
[...]
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get();
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
RestMessageContainer<T> container = mapper.readValue(response.readEntity(String.class), mapper.getTypeFactory().constructParametricType(RestMessageContainer.class, clazz));
return container;
}
You cannot create an instance of an abstract class. However, you can solve the problem with a simple annotation – #JsonDeserialize on the abstract class:
#JsonDeserialize(as = Cat.class)
abstract class Animal {...}
In your case, the abstract class would be myObjInterface.
Note: If you have more than one subtype of the abstract class, then you should consider including subtype information as shown in this post.

Can you configure Spring controller specific Jackson deserialization?

I need to add a custom Jackson deserializer for java.lang.String to my Spring 4.1.x MVC application. However all answers (such as this) refer to configuring the ObjectMapper for the complete web application and the changes will apply to all Strings across all #RequestBody in all controllers.
I only want to apply the custom deserialization to #RequestBody arguments used within particular controllers. Note that I don't have the option of using #JsonDeserialize annotations for the specific String fields.
Can you configure custom deserialization for specific controllers only?
To have different deserialization configurations you must have different ObjectMapper instances but out of the box Spring uses MappingJackson2HttpMessageConverter which is designed to use only one instance.
I see at least two options here:
Move away from MessageConverter to an ArgumentResolver
Create a #CustomRequestBody annotation, and an argument resolver:
public class CustomRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapperResolver objectMapperResolver;
public CustomRequestBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
this.objectMapperResolver = objectMapperResolver;
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(CustomRequestBody.class) != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
ObjectMapper objectMapper = objectMapperResolver.getObjectMapper();
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
return objectMapper.readValue(request.getInputStream(), methodParameter.getParameterType());
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
#CustomRequestBody annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomRequestBody {
boolean required() default true;
}
ObjectMapperResolver is an interface we will be using to resolve actual ObjectMapper instance to use, I will discuss it below. Of course if you have only one use case where you need custom mapping you can simply initialize your mapper here.
You can add custom argument resolver with this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public CustomRequestBodyArgumentResolver customBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
return new CustomRequestBodyArgumentResolver(objectMapperResolver)
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(customBodyArgumentResolver(objectMapperResolver()));
}
}
Note: Do not combine #CustomRequestBody with #RequestBody, it will be ignored.
Wrap ObjectMapper in a proxy that hides multiple instances
MappingJackson2HttpMessageConverter is designed to work with only one instance of ObjectMapper. We can make that instance a proxy delegate. This will make working with multiple mappers transparent.
First of all we need an interceptor that will translate all method invocations to an underlying object.
public abstract class ObjectMapperInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return ReflectionUtils.invokeMethod(invocation.getMethod(), getObject(), invocation.getArguments());
}
protected abstract ObjectMapper getObject();
}
Now our ObjectMapper proxy bean will look like this:
#Bean
public ObjectMapper objectMapper(ObjectMapperResolver objectMapperResolver) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(ObjectMapper.class);
factory.addAdvice(new ObjectMapperInterceptor() {
#Override
protected ObjectMapper getObject() {
return objectMapperResolver.getObjectMapper();
}
});
return (ObjectMapper) factory.getProxy();
}
Note: I had class loading issues with this proxy on Wildfly, due to its modular class loading, so I had to extend ObjectMapper (without changing anything) just so I can use class from my module.
It all tied up together using this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(objectMapper(objectMapperResolver()));
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
}
}
ObjectMapperResolver implementations
Final piece is the logic that determines which mapper should be used, it will be contained in ObjectMapperResolver interface. It contains only one look up method:
public interface ObjectMapperResolver {
ObjectMapper getObjectMapper();
}
If you do not have a lot of use cases with custom mappers you can simply make a map of preconfigured instances with ReqeustMatchers as keys. Something like this:
public class RequestMatcherObjectMapperResolver implements ObjectMapperResolver {
private final ObjectMapper defaultMapper;
private final Map<RequestMatcher, ObjectMapper> mapping = new HashMap<>();
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper, Map<RequestMatcher, ObjectMapper> mapping) {
this.defaultMapper = defaultMapper;
this.mapping.putAll(mapping);
}
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper) {
this.defaultMapper = defaultMapper;
}
#Override
public ObjectMapper getObjectMapper() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
for (Map.Entry<RequestMatcher, ObjectMapper> entry : mapping.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return defaultMapper;
}
}
You can also use a request scoped ObjectMapper and then configure it on a per-request basis. Use this configuration:
#Bean
public ObjectMapperResolver objectMapperResolver() {
return new ObjectMapperResolver() {
#Override
public ObjectMapper getObjectMapper() {
return requestScopedObjectMapper();
}
};
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ObjectMapper requestScopedObjectMapper() {
return new ObjectMapper();
}
This is best suited for custom response serialization, since you can configure it right in the controller method. For custom deserialization you must also use Filter/HandlerInterceptor/ControllerAdvice to configure active mapper for current request before the controller method is triggered.
You can create interface, similar to ObjectMapperResolver:
public interface ObjectMapperConfigurer {
void configureObjectMapper(ObjectMapper objectMapper);
}
Then make a map of this instances with RequstMatchers as keys and put it in a Filter/HandlerInterceptor/ControllerAdvice similar to RequestMatcherObjectMapperResolver.
P.S. If you want to explore dynamic ObjectMapper configuration a bit further I can suggest my old answer here. It describes how you can make dynamic #JsonFilters at run time. It also contains my older approach with extended MappingJackson2HttpMessageConverter that I suggested in comments.
Probably this would help, but it ain't pretty. It would require AOP. Also I did not validate it.
Create a #CustomAnnotation.
Update your controller:
void someEndpoint(#RequestBody #CustomAnnotation SomeEntity someEntity);
Then implemment the AOP part:
#Around("execution(* *(#CustomAnnotation (*)))")
public void advice(ProceedingJoinPoint proceedingJoinPoint) {
// Here you would add custom ObjectMapper, I don't know another way around it
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String body = request .getReader().lines().collect(Collectors.joining(System.lineSeparator()));
SomeEntity someEntity = /* deserialize */;
// This could be cleaner, cause the method can accept multiple parameters
proceedingJoinPoint.proceed(new Object[] {someEntity});
}
You can create custom deserializer for your String data.
Custom Deserializer
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String str = p.getText();
//return processed String
}
}
Now suppose the String is present inside a POJO use #JsonDeserialize annotation above the variable:
public class SamplePOJO{
#JsonDeserialize(using=CustomStringDeserializer.class)
private String str;
//getter and setter
}
Now when you return it as a response it will be Deserialized in the way you have done it in CustomDeserializer.
Hope it helps.
You could try Message Converters.
They have a context about http input request (for example, docs see here, JSON). How to customize you could see here.
Idea that you could check HttpInputMessage with special URIs, which used in your controllers and convert string as you want.
You could create special annotation for this, scan packages and do it automatically.
Note
Likely, you don't need implementation of ObjectMappers. You can use simple default ObjectMapper to parse String and then convert string as you wish.
In that case you would create RequestBody once.
You can define a POJO for each different type of request parameter that you would like to deserialize. Then, the following code will pull in the values from the JSON into the object that you define, assuming that the names of the fields in your POJO match with the names of the field in the JSON request.
ObjectMapper mapper = new ObjectMapper();
YourPojo requestParams = null;
try {
requestParams = mapper.readValue(JsonBody, YourPOJO.class);
} catch (IOException e) {
throw new IOException(e);
}

Remove "type" from JSON output jersey moxy

How to remove the type from the JSON output that I have. I have a class/bean that contains output of a REST service.I'm using jersey-media-moxy to do the conversion.
The service
#Resource
public interface MyBeanResource
{
#GET
#Path("/example")
#Produces( MediaType.APPLICATION_JSON )
public Bean getBean();
}
The Bean
#XmlRootElement
class Bean
{
String a;
}
I want to add some functionality (for initializing the bean using the constructor)
class BeanImpl extends Bean
{
BeanImpl(OtherClass c)
{
a = c.toString()
}
}
The outputted JSON is:
{type:"beanImpl", a:"somevalue"}
I do not want the type in my JSON. How can I configure this?
I get the same error when I extend a class and generate JSON -- but only for a top-level (root) class. As a workaround, I annotate my subclass with #XmlType(name=""), which prevents the generated type property from appearing in my JSON.
Blaise, I'm not sure why this works. Any thoughts?
MOXy will add a type indicator to differentiate between the different subtypes. This however will only happen if MOXy is aware of the subtypes (it isn't by default).
Demo Code
Demo
Below is the equivalent code that Jersey will call on MOXy.
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class Demo {
public static void main(String[] args) throws Exception {
MOXyJsonProvider mjp = new MOXyJsonProvider();
BeanImpl beanImpl = new BeanImpl(new OtherClass());
mjp.writeTo(beanImpl, Bean.class, Bean.class, null, null, null, System.out);
}
}
Output
{}
Possible Problem?
Do you potentially have an #XmlSeeAlso annotation on your real Bean class?
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlSeeAlso(BeanImpl.class)
class Bean
{
String a;
}
Then the output will be (assuming BeanImpl also has a no-arg constructor):
{"type":"beanImpl"}
You can build a custom message body writer .
#Provider
#Produces({
MediaType.APPLICATION_JSON
})
public class BeanBodyWriter implements MessageBodyWriter<Bean> {
#Override
public long getSize(Bean t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// Called before isWriteable by Jersey. Return -1 if you don't the size yet.
return -1;
}
#Override
public boolean isWriteable(Class<?> clazz, Type genericType, Annotation[] annotations, MediaType mediaType) {
// Check that the passed class by Jersey can be handled by our message body writer
return Bean.class.isAssignableFrom(clazz);
}
#Override
public void writeTo(Bean t, Class<?> clazz, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException, WebApplicationException {
// Call your favorite JSON library to generate the JSON code and remove the unwanted fields...
String json = "...";
out.write(json.getBytes("UTF-8"));
}
}
Use this to generate JSON and you won't have that problem:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.3.3</version>
</dependency>
I used a Jersey specific Jackson package in a slightly different case, it worked. Detailed configuration is described in Jersy document. In my case, I used a generic type field in an #XmlRootElement class. MOXy added a type in the JSON output. I can see why MOXy does it. If the JSON output needs to be unmarshalled back to a Java object, MOXy needs to know the type to create the correct object. However, in my case, the type is unsightly in the JSON output.

How to create a MessageBodyWriter to return a custom object as HTML in RestEasy?

I'm using RestEasy together with Spring in Tomcat. I have a simple controller method which I want to use via Ajax (with JSON or XML response) and via a standard browser request (Using HTML as a response). It works when I use simple return data types like a string but I need to return a custom object:
#POST
#Path("fooBar")
public RequestResult fooBar()
{
return new RequestResult();
}
This is the RequestResult object (Just a dummy implementation for demonstration):
#XmlRootElement(name = "result")
public final class RequestResult
{
#XmlAttribute
public boolean getWhatever()
{
return "whatever";
}
}
It works when requesting it as JSON or XML but when requesting it as HTML I get the error message Could not find JAXBContextFinder for media type: text/html. It's clear that it can't work because RestEasy doesn't know how to convert this object to HTML. So I added this test MessageBodyWriter:
#Provider
#Produces("text/html")
public class ResultProvider implements MessageBodyWriter<RequestResult>
{
#Override
public boolean isWriteable(final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType)
{
return true;
}
#Override
public long getSize(final RequestResult t, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType)
{
return 4;
}
#Override
public void writeTo(final RequestResult t, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType,
final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
throws IOException, WebApplicationException
{
final PrintWriter writer = new PrintWriter(entityStream);
writer.println("Test");
}
}
But this doesn't change anything. No method of this provider is ever called. I'm not sure if I have to register it somewhere. All other classes are found automatically by classpath scanning so I guess this also happens for providers.
I'm pretty sure I made something wrong or I forgot something. Any hints?
Try adding adding a #Produces annotation that includes "text/html" to your fooBar() method (I included JSON and XML because it sounded like you wanted all three). When I did that, your ResultProvider was called. Let me know if that works for you!
#POST
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_HTML })
#Path("fooBar")
public RequestResult fooBar()
{
return new RequestResult();
}

Categories

Resources