IllegalStateException: STREAMED when I try to read the POST request body using OSGI's JAX-RS Whiteboard Resource

49 views Asked by At

I have created a JAX-RS Whiteboard resource for a REST API, a Jaxrs Extension which uses Jackson to serialize/deserialize data, and I want to read some data which will be sent using POST method.

In the following code, the @GET method works, but I just get an exception with the @POST method. If I comment the try..catch block then there is no error and I can see the response text.

How can I get it to work?

The error:

WARNING: Exception in handleFault on interceptor org.apache.cxf.jaxrs.interceptor.JAXRSDefaultFaultOutInterceptor@7103319f
org.apache.cxf.interceptor.Fault: STREAMED
    at org.apache.cxf.service.invoker.AbstractInvoker.createFault(AbstractInvoker.java:162)
...
Caused by: java.lang.IllegalStateException: STREAMED
    at org.eclipse.jetty.server.Request.getReader(Request.java:1153)

The JaxrsResource

//The jax-rs path annotation for this service.
@Path("/api/v1")
//The JAX-RS annotation to specify that JSON is produced.
@Produces(MediaType.APPLICATION_JSON)
//Mark this class as a OSGi DS component. 
@Component(service=MyApiImpl.class)
//Mark this class as a JAX-RS resource type that should be processed by the JAX-RS whiteboard.
@JaxrsResource
//Mark this class to requiring a serializer capable of supporting JSON.
@JSONRequired
public class MyApiImpl {

    @Reference(service=LoggerFactory.class)
    private Logger logger;

    @GET
    @Path("/test")
    public String getTest() {
        return "Hello World, GET-test ok! --> ";
    }

    @POST
    @Path("/resources")
    @Consumes(MediaType.APPLICATION_JSON)
    public String postFoo(@Context HttpServletRequest request) {
        try {
            BufferedReader reader = request.getReader();
        } catch (IOException e) {
            logger.error("error on input data", e);
            return e.getLocalizedMessage();
        }
        return "Hello World, POST ok! --> ";
    }
}

The JaxrsExtension:

/**
 * Very simple converter implementation to convert List of Strings to a JSON String.
 */
// OSGi DS component annotation with prototype scope to ensure that multiple instances can be requested
@Component(scope = ServiceScope.PROTOTYPE)
// Marks the service as a JAX-RS extension type that should be processed by the JAX-RS whiteboard.
@JaxrsExtension
// Marks the component as providing a serializer capable of supporting the named media type, in this case the standard media type for JSON.
@JaxrsMediaType(MediaType.APPLICATION_JSON)
public class JacksonJsonConverter<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {

    @Reference(service=LoggerFactory.class)
    private Logger logger;

    private final Converter converter = Converters.newConverterBuilder()
            .rule(String.class, this::toJson)
            .rule(this::toObject)
            .build();

    private ObjectMapper mapper = new ObjectMapper();

    private String toJson(Object value, Type targetType) {
        try {
            String retval = mapper.writeValueAsString(value);
            System.out.println("trying 'toJson': " + retval);
            return retval;
            //return mapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            logger.error("error on JSON creation", e);
            return e.getLocalizedMessage();
        }
    }

    private Object toObject(Object o, Type t) {
        if(List.class.getName().equals(t.getTypeName())) {
            try {
                System.out.println("trying 'toObject'");
                return this.mapper.readValue((String) o, List.class);
            } catch (IOException e) {
                logger.error("error on JSON parsing", e);
            }
        }
        return ConverterFunction.CANNOT_HANDLE;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        //return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType) || mediaType.getSubtype().endsWith("+json");
        boolean retval = MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType) || mediaType.getSubtype().endsWith("+json");
        System.out.println("'isWriteable' returns: " + retval);
        return retval;
    }

    @Override
    public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException {
        String json = converter.convert(t).to(String.class);
        entityStream.write(json.getBytes());
        System.out.println("'writeTo' was called");
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
//        return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType) || mediaType.getSubtype().endsWith("+json");
        boolean retval = MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType) || mediaType.getSubtype().endsWith("+json");
        System.out.println("'isReadable' returns: " + retval);
        return retval;
    }

    @Override
    public T readFrom(Class<T> type, Type genericType, Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
            throws IOException, WebApplicationException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(entityStream));
        System.out.println("'readFrom' was called");
        return (T) converter.convert(reader.readLine()).to(genericType);
    }

}

I would expect to get the body contents to apply the business logic.

0

There are 0 answers