All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.palantir.conjure.java.undertow.annotations.DefaultSerDe Maven / Gradle / Ivy

/*
 * (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.palantir.conjure.java.undertow.annotations;

import com.palantir.conjure.java.undertow.lib.BinaryResponseBody;
import com.palantir.conjure.java.undertow.lib.BodySerDe;
import com.palantir.conjure.java.undertow.lib.Deserializer;
import com.palantir.conjure.java.undertow.lib.Endpoint;
import com.palantir.conjure.java.undertow.lib.Serializer;
import com.palantir.conjure.java.undertow.lib.TypeMarker;
import com.palantir.conjure.java.undertow.lib.UndertowRuntime;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.exceptions.SafeIllegalArgumentException;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.StatusCodes;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Optional;
import javax.annotation.Nullable;

/** Default serializer and deserializer factory which produces behavior equivalent to conjure. */
public enum DefaultSerDe implements SerializerFactory, DeserializerFactory {
    INSTANCE;

    private static final TypeMarker INPUT_STREAM = new TypeMarker<>() {};

    @Override
    @SuppressWarnings("unchecked")
    public  Deserializer deserializer(TypeMarker marker, UndertowRuntime runtime, Endpoint endpoint) {
        if (INPUT_STREAM.getType().equals(marker.getType())) {
            return (Deserializer) new BinaryDeserializer(runtime.bodySerDe());
        }
        return runtime.bodySerDe().deserializer(marker, endpoint);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Serializer serializer(TypeMarker marker, UndertowRuntime runtime, Endpoint endpoint) {
        Type type = marker.getType();
        Serializer maybeSpecialSerializer = (Serializer) maybeSpecialSerializer(type, runtime);
        if (maybeSpecialSerializer != null) {
            return maybeSpecialSerializer;
        }
        Serializer delegateSerializer = runtime.bodySerDe().serializer(marker, endpoint);
        return isOptional(marker)
                ? (Serializer) new OptionalDelegatingSerializer<>((Serializer>) delegateSerializer)
                : delegateSerializer;
    }

    private Serializer maybeSpecialSerializer(Type type, UndertowRuntime runtime) {
        if (Void.class.equals(type)) {
            return VoidSerializer.INSTANCE;
        }
        Class clazz = asClass(type);
        if (clazz != null) {
            if (BinaryResponseBody.class.isAssignableFrom(clazz)) {
                return new BinaryResponseBodySerializer(runtime.bodySerDe());
            }
            if (InputStream.class.isAssignableFrom(clazz)) {
                return new InputStreamBodySerializer(runtime.bodySerDe());
            }
        }
        Type optionalValueType = unwrapOptional(type);
        if (optionalValueType != null) {
            Serializer maybeDelegate = maybeSpecialSerializer(optionalValueType, runtime);
            if (maybeDelegate != null) {
                return new OptionalValueDelegatingSerializer<>(maybeDelegate);
            }
        }
        return null;
    }

    @Nullable
    private static Class asClass(Type type) {
        if (type instanceof Class) {
            return (Class) type;
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType param = (ParameterizedType) type;
            return asClass(param.getRawType());
        }
        if (type instanceof WildcardType) {
            WildcardType wild = (WildcardType) type;
            // Only check upper bounded e.g. '? extends Type' as opposed to lower bounded '? super Type'
            Type[] upperBounds = wild.getUpperBounds();
            if (upperBounds != null && upperBounds.length == 1) {
                return asClass(upperBounds[0]);
            }
        }
        return null;
    }

    private static boolean isOptional(TypeMarker marker) {
        return unwrapOptional(marker.getType()) != null;
    }

    private static Type unwrapOptional(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            if (parameterizedType.getRawType().equals(Optional.class)) {
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                if (typeArguments.length != 1) {
                    throw new SafeIllegalArgumentException(
                            "Expected Optional to have exactly one type argument",
                            SafeArg.of("typeArguments", typeArguments));
                }
                return typeArguments[0];
            }
        }
        return null;
    }

    private enum VoidSerializer implements Serializer {
        INSTANCE;

        @Override
        public void serialize(Void _value, HttpServerExchange exchange) {
            exchange.setStatusCode(StatusCodes.NO_CONTENT);
        }
    }

    private static final class BinaryDeserializer implements Deserializer {
        private final BodySerDe bodySerDe;

        BinaryDeserializer(BodySerDe bodySerDe) {
            this.bodySerDe = bodySerDe;
        }

        @Override
        public InputStream deserialize(HttpServerExchange exchange) {
            return bodySerDe.deserializeInputStream(exchange);
        }
    }

    private static final class BinaryResponseBodySerializer implements Serializer {
        private final BodySerDe bodySerDe;

        BinaryResponseBodySerializer(BodySerDe bodySerDe) {
            this.bodySerDe = bodySerDe;
        }

        @Override
        public void serialize(BinaryResponseBody value, HttpServerExchange exchange) throws IOException {
            bodySerDe.serialize(value, exchange);
        }
    }

    private static final class InputStreamBodySerializer implements Serializer {
        private final BodySerDe bodySerDe;

        InputStreamBodySerializer(BodySerDe bodySerDe) {
            this.bodySerDe = bodySerDe;
        }

        @Override
        public void serialize(InputStream value, HttpServerExchange exchange) throws IOException {
            bodySerDe.serialize(
                    responseBody -> {
                        try (value) {
                            value.transferTo(responseBody);
                        }
                    },
                    exchange);
        }
    }

    private static final class OptionalDelegatingSerializer implements Serializer> {
        private final Serializer> delegate;

        OptionalDelegatingSerializer(Serializer> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void serialize(Optional value, HttpServerExchange exchange) throws IOException {
            if (value.isPresent()) {
                delegate.serialize(value, exchange);
            } else {
                exchange.setStatusCode(StatusCodes.NO_CONTENT);
            }
        }
    }

    private static final class OptionalValueDelegatingSerializer implements Serializer> {
        private final Serializer delegate;

        OptionalValueDelegatingSerializer(Serializer delegate) {
            this.delegate = delegate;
        }

        @Override
        public void serialize(Optional value, HttpServerExchange exchange) throws IOException {
            if (value.isPresent()) {
                delegate.serialize(value.get(), exchange);
            } else {
                exchange.setStatusCode(StatusCodes.NO_CONTENT);
            }
        }
    }
}