au.net.causal.shoelaces.jersey.common.JacksonJsonStringWriterProvider Maven / Gradle / Ivy
package au.net.causal.shoelaces.jersey.common;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Providers;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
/**
* A provider that makes Jersey use Jackson to write String responses with application/json media type in proper JSON string form with quotes as opposed to the incorrect
* plaintext form that it does by default.
*
*
* Jersey/Jackson by default writes String responses as plaintext even when the response is annotated with the application/json media type.
* This was an intentional design decision by them, but can be worked around by registering this provider which will fix this problem.
*
*
* Jersey chooses its provider by firstly looking at the response Java type finding the closest matching handled Java type by registered writers, and after finding that set of
* writers looks at the supported media types of those writers, finding the closest match (see MessageBodyFactory.WorkerComparator class). Jersey's StringMessageProvider
* handles String.class and * / * media type, whose first match is closer than the JacksonJsonProvider which matches Object.class and application/json. This means that Jackson's
* provider will never be chosen for the String return type by default. Another issue with Jackson's provider is that it for some reason blacklists String as one of its handled
* types, so even with a better match it will still refuse to handle it by default. This class's solution is to create a wrapper provider that delegates to the Jackson one
* for Strings, registering itself to handle String which will match as close as the plaintext one, and for application/json media type which will match closer than the
* plain text one. Before it delegates to the Jackson provider after matching, it removes String from the Jackson type blacklist. The Jackson provider itself is looked up
* from the provider context, which means the Jackson provider must be registered with Jersey as well as this one for this one to work.
*/
@Produces(MediaType.APPLICATION_JSON)
public class JacksonJsonStringWriterProvider implements MessageBodyWriter
{
@Context
protected Providers workers;
/**
* Finds an already-registered delegate for handling the specified media type for arbitrary objects.
* This should typically find the Jackson provider, however if another has been registered then this will
* be picked up.
*
* @param annotations an array of the annotations attached to the message entity instance.
* @param mediaType the media type of the HTTP entity. This will usually be {@link MediaType#APPLICATION_JSON_TYPE} since this is what this class is annotated
* to handle, but subclasses may change this.
*
* @return a delegate writer for the given media type.
*
* @throws RuntimeException if no writer could be found for the given media type. This provider should only be used when there is a known provider for handled
* media type already registered, such as Jackson for application/json.
*/
protected MessageBodyWriter