FastJson2Provider.java

package com.alibaba.fastjson2.support.jaxrs.javax;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.support.config.FastJsonConfig;
import lombok.Getter;
import lombok.Setter;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

/**
 * Fastjson for JAX-RS Provider.
 * ���������com.alibaba.fastjson.support.jaxrs.FastJsonProvider
 *
 * @author ���������
 * @since 2024/10/16
 * @see MessageBodyReader
 * @see MessageBodyWriter
 */

@Provider
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.WILDCARD)
public class FastJson2Provider
        implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
    /**
     * These are classes that we never use for reading
     * (never try to deserialize instances of these types).
     */
    public static final Class<?>[] DEFAULT_UNREADABLES = new Class<?>[]{
            InputStream.class, Reader.class
    };

    /**
     * These are classes that we never use for writing
     * (never try to serialize instances of these types).
     */
    public static final Class<?>[] DEFAULT_UNWRITABLES = new Class<?>[]{
            InputStream.class,
            OutputStream.class, Writer.class,
            StreamingOutput.class, Response.class
    };

    /**
     * Injectable context object used to locate configured
     * instance of {@link FastJsonConfig} to use for actual
     * serialization.
     */
    @Context
    protected Providers providers;

    /**
     * with fastJson config
     */
    @Getter
    @Setter
    private FastJsonConfig fastJsonConfig = new FastJsonConfig();

    /**
     * allow serialize/deserialize types in clazzes
     */
    private Class<?>[] clazzes;

    /**
     * Can serialize/deserialize all types.
     */
    public FastJson2Provider() {
        this((Class<?>[]) null);
    }

    /**
     * Only serialize/deserialize all types in clazzes.
     */
    public FastJson2Provider(Class<?>[] clazzes) {
        this.clazzes = clazzes;
    }

    /**
     * Check some are interface/abstract classes to exclude.
     *
     * @param type the type
     * @param classes the classes
     * @return the boolean
     */
    protected boolean isNotAssignableFrom(Class<?> type, Class<?>[] classes) {
        if (type == null) {
            return true;
        }
        //  there are some other abstract/interface types to exclude too:
        for (Class<?> cls : classes) {
            if (cls.isAssignableFrom(type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check whether a class can be serialized or deserialized. It can check
     * based on packages, annotations on entities or explicit classes.
     *
     * @param type class need to check
     * @return true if valid
     */
    protected boolean isValidType(Class<?> type, Annotation[] classAnnotations) {
        if (type == null) {
            return false;
        }
        if (clazzes != null) {
            for (Class<?> cls : clazzes) {
                // must strictly equal. Don't check inheritance
                if (cls == type) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }

    /**
     * Check media type like "application/json".
     *
     * @param mediaType media type
     * @return true if the media type is valid
     */
    protected boolean hasNotMatchingMediaType(MediaType mediaType) {
        if (mediaType == null) {
            return false;
        }
        String subtype = mediaType.getSubtype();
        return !(("json".equalsIgnoreCase(subtype))
                || (subtype.endsWith("+json"))
                || ("javascript".equals(subtype))
                || ("x-javascript".equals(subtype))
                || ("x-json".equals(subtype))
                || ("x-www-form-urlencoded".equalsIgnoreCase(subtype))
                || (subtype.endsWith("x-www-form-urlencoded")));
    }

    /**
     * Method that JAX-RS container calls to try to check whether given value
     * (of specified type) can be serialized by this provider.
     */
    @Override
    public boolean isWriteable(
            Class<?> type,
            Type genericType,
            Annotation[] annotations,
            MediaType mediaType) {
        if (hasNotMatchingMediaType(mediaType)) {
            return false;
        }
        if (isNotAssignableFrom(type, DEFAULT_UNWRITABLES)) {
            return false;
        }
        return isValidType(type, annotations);
    }

    /**
     * Method that JAX-RS container calls to serialize given value.
     */
    @Override
    public void writeTo(
            Object object,
            Class<?> type,
            Type genericType,
            Annotation[] annotations,
            MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders,
            OutputStream entityStream
    ) throws IOException, WebApplicationException {
        FastJsonConfig config = locateConfigProvider(type, mediaType);
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            if (object instanceof String && JSON.isValidObject((String) object)) {
                byte[] strBytes = ((String) object).getBytes(config.getCharset());
                baos.write(strBytes, 0, strBytes.length);
            } else if (object instanceof byte[] && JSON.isValid((byte[]) object)) {
                byte[] strBytes = (byte[]) object;
                baos.write(strBytes, 0, strBytes.length);
            } else {
                JSON.writeTo(
                        baos,
                        object,
                        config.getDateFormat(),
                        config.getWriterFilters(),
                        config.getWriterFeatures()
                );
            }
            baos.writeTo(entityStream);
        }
    }

    /**
     * Method that JAX-RS container calls to try to check whether values of
     * given type (and media type) can be deserialized by this provider.
     */
    @Override
    public boolean isReadable(
            Class<?> type,
            Type genericType,
            Annotation[] annotations,
            MediaType mediaType) {
        if (hasNotMatchingMediaType(mediaType)) {
            return false;
        }
        if (isNotAssignableFrom(type, DEFAULT_UNREADABLES)) {
            return false;
        }
        return isValidType(type, annotations);
    }

    /**
     * Method that JAX-RS container calls to deserialize given value.
     */
    @Override
    public Object readFrom(
            Class<Object> type,
            Type genericType,
            Annotation[] annotations,
            MediaType mediaType,
            MultivaluedMap<String, String> httpHeaders,
            InputStream entityStream
    ) throws IOException, WebApplicationException {
        FastJsonConfig config = locateConfigProvider(type, mediaType);
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buf = new byte[1024 * 64];
            for (; ; ) {
                int len = entityStream.read(buf);
                if (len == -1) {
                    break;
                }
                if (len > 0) {
                    baos.write(buf, 0, len);
                }
            }
            byte[] bytes = baos.toByteArray();
            return JSON.parseObject(bytes, genericType, config.getDateFormat(), config.getReaderFilters(), config.getReaderFeatures());
        } catch (JSONException ex) {
            throw new WebApplicationException(ex);
        }
    }

    /**
     * Helper method that is called if no config has been explicitly configured.
     */
    protected FastJsonConfig locateConfigProvider(Class<?> type, MediaType mediaType) {
        if (providers != null) {
            ContextResolver<FastJsonConfig> resolver = providers.getContextResolver(FastJsonConfig.class, mediaType);
            if (resolver == null) {
                resolver = providers.getContextResolver(FastJsonConfig.class, null);
            }
            if (resolver != null) {
                return resolver.getContext(type);
            }
        }
        return fastJsonConfig;
    }
}