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

io.servicetalk.serialization.api.DefaultSerializer Maven / Gradle / Ivy

/*
 * Copyright © 2018 Apple Inc. and the ServiceTalk project authors
 *
 * 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 io.servicetalk.serialization.api;

import io.servicetalk.buffer.api.Buffer;
import io.servicetalk.buffer.api.BufferAllocator;
import io.servicetalk.concurrent.BlockingIterable;
import io.servicetalk.concurrent.BlockingIterator;
import io.servicetalk.concurrent.CloseableIterable;
import io.servicetalk.concurrent.CloseableIterator;
import io.servicetalk.concurrent.PublisherSource.Subscriber;
import io.servicetalk.concurrent.api.Publisher;
import io.servicetalk.concurrent.api.internal.SubscribablePublisher;
import io.servicetalk.concurrent.internal.AbstractCloseableIterable;

import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static io.servicetalk.concurrent.api.SourceAdapters.toSource;
import static java.lang.Integer.MAX_VALUE;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;

/**
 * Default implementation for {@link Serializer}.
 */
public final class DefaultSerializer implements Serializer {

    /**
     * This applies a somewhat arbitrary limit (around 500kb) on the auto scaling of the next buffer allocation.
     */
    private static final int MAX_READABLE_BYTES_TO_ADJUST = MAX_VALUE >>> 12;
    private static final int DEFAULT_SERIALIZATION_SIZE_BYTES_ESTIMATE = 512;

    private static final IntUnaryOperator DEFAULT_SIZE_ESTIMATOR = lastSize -> {
        // Approximate the next buffer will be ~33% bigger than the current buffer as a best effort to avoid
        // re-allocation and copy.
        return max(DEFAULT_SERIALIZATION_SIZE_BYTES_ESTIMATE, (min(MAX_READABLE_BYTES_TO_ADJUST, lastSize) << 2) / 3);
    };

    private final SerializationProvider serializationProvider;

    /**
     * New instance.
     *
     * @param serializationProvider {@link SerializationProvider} to use.
     */
    public DefaultSerializer(final SerializationProvider serializationProvider) {
        this.serializationProvider = requireNonNull(serializationProvider);
    }

    @Override
    public  Publisher serialize(final Publisher source, final BufferAllocator allocator,
                                           final Class type) {
        return serialize(source, allocator, type, DEFAULT_SIZE_ESTIMATOR);
    }

    @Override
    public  Iterable serialize(final Iterable source, final BufferAllocator allocator,
                                          final Class type) {
        return serialize(source, allocator, type, DEFAULT_SIZE_ESTIMATOR);
    }

    @Override
    public  BlockingIterable serialize(final BlockingIterable source, final BufferAllocator allocator,
                                                  final Class type) {
        return serialize(source, allocator, type, DEFAULT_SIZE_ESTIMATOR);
    }

    @Override
    public  Publisher serialize(final Publisher source, final BufferAllocator allocator,
                                           final Class type, final IntUnaryOperator bytesEstimator) {
        return new SubscribablePublisher() {
            @Override
            protected void handleSubscribe(final Subscriber subscriber) {
                applySerializer0(subscriber, allocator, bytesEstimator, serializationProvider.getSerializer(type),
                        source);
            }
        };
    }

    @Override
    public  Iterable serialize(final Iterable source, final BufferAllocator allocator,
                                          final Class type, final IntUnaryOperator bytesEstimator) {
        return applySerializer0(allocator, bytesEstimator, source, serializationProvider.getSerializer(type));
    }

    @Override
    public  BlockingIterable serialize(final BlockingIterable source, final BufferAllocator allocator,
                                                  final Class type, final IntUnaryOperator bytesEstimator) {
        return applySerializer0(allocator, bytesEstimator, source, serializationProvider.getSerializer(type));
    }

    @Override
    public  Publisher serialize(final Publisher source, final BufferAllocator allocator,
                                           final TypeHolder typeHolder) {
        return serialize(source, allocator, typeHolder, DEFAULT_SIZE_ESTIMATOR);
    }

    @Override
    public  Iterable serialize(final Iterable source, final BufferAllocator allocator,
                                          final TypeHolder typeHolder) {
        return serialize(source, allocator, typeHolder, DEFAULT_SIZE_ESTIMATOR);
    }

    @Override
    public  BlockingIterable serialize(final BlockingIterable source, final BufferAllocator allocator,
                                                  final TypeHolder typeHolder) {
        return serialize(source, allocator, typeHolder, DEFAULT_SIZE_ESTIMATOR);
    }

    @Override
    public  Publisher serialize(final Publisher source, final BufferAllocator allocator,
                                           final TypeHolder typeHolder, final IntUnaryOperator bytesEstimator) {
        return new SubscribablePublisher() {
            @Override
            protected void handleSubscribe(final Subscriber subscriber) {
                applySerializer0(subscriber, allocator, bytesEstimator, serializationProvider.getSerializer(typeHolder),
                        source);
            }
        };
    }

    @Override
    public  Iterable serialize(final Iterable source, final BufferAllocator allocator,
                                          final TypeHolder typeHolder, final IntUnaryOperator bytesEstimator) {
        final StreamingSerializer serializer = serializationProvider.getSerializer(typeHolder);
        return applySerializer0(allocator, bytesEstimator, source, serializer);
    }

    @Override
    public  BlockingIterable serialize(final BlockingIterable source, final BufferAllocator allocator,
                                                  final TypeHolder typeHolder,
                                                  final IntUnaryOperator bytesEstimator) {
        return applySerializer0(allocator, bytesEstimator, source, serializationProvider.getSerializer(typeHolder));
    }

    @Override
    public  Buffer serialize(final T toSerialize, final BufferAllocator allocator) {
        return serialize(toSerialize, allocator, DEFAULT_SERIALIZATION_SIZE_BYTES_ESTIMATE);
    }

    @Override
    public  Buffer serialize(final T toSerialize, final BufferAllocator allocator, final int bytesEstimate) {
        final Buffer destination = allocator.newBuffer(bytesEstimate);
        serializationProvider.serialize(toSerialize, destination);
        return destination;
    }

    @Override
    public  void serialize(final T toSerialize, final Buffer destination) {
        serializationProvider.serialize(toSerialize, destination);
    }

    @Override
    public  Publisher deserialize(final Publisher source, final TypeHolder typeHolder) {
        return new SubscribablePublisher() {
            @Override
            protected void handleSubscribe(final Subscriber subscriber) {
                applyDeserializer0(source, subscriber, serializationProvider.getDeserializer(typeHolder));
            }
        };
    }

    @Override
    public  CloseableIterable deserialize(final Iterable source, final TypeHolder typeHolder) {
        return applyDeserializer0(source, serializationProvider.getDeserializer(typeHolder));
    }

    @Override
    public  BlockingIterable deserialize(final BlockingIterable source,
                                               final TypeHolder typeHolder) {
        return serializationProvider.getDeserializer(typeHolder).deserialize(source);
    }

    @Override
    public  Publisher deserialize(final Publisher source, final Class type) {
        return new SubscribablePublisher() {
            @Override
            protected void handleSubscribe(final Subscriber subscriber) {
                applyDeserializer0(source, subscriber, serializationProvider.getDeserializer(type));
            }
        };
    }

    @Override
    public  CloseableIterable deserialize(final Iterable source, final Class type) {
        return applyDeserializer0(source, serializationProvider.getDeserializer(type));
    }

    @Override
    public  BlockingIterable deserialize(final BlockingIterable source, final Class type) {
        return serializationProvider.getDeserializer(type).deserialize(source);
    }

    @Override
    public  CloseableIterable deserializeAggregated(final Buffer serializedData, final Class type) {
        return deserializeAggregated0(serializedData, serializationProvider.getDeserializer(type));
    }

    @Override
    public  CloseableIterable deserializeAggregated(final Buffer serializedData, final TypeHolder typeHolder) {
        return deserializeAggregated0(serializedData, serializationProvider.getDeserializer(typeHolder));
    }

    @Override
    public  T deserializeAggregatedSingle(final Buffer serializedData, final Class type) {
        return getSingleValueOnly(deserializeAggregated(serializedData, type));
    }

    @Override
    public  T deserializeAggregatedSingle(final Buffer serializedData, final TypeHolder typeHolder) {
        return getSingleValueOnly(deserializeAggregated(serializedData, typeHolder));
    }

    private static  void applySerializer0(final Subscriber subscriber,
                                             final BufferAllocator allocator, final IntUnaryOperator bytesEstimator,
                                             final StreamingSerializer serializer, final Publisher source) {
        toSource(source.map(new SerializerFunction<>(bytesEstimator, allocator, serializer))).subscribe(subscriber);
    }

    private static  Iterable applySerializer0(final BufferAllocator allocator,
                                                         final IntUnaryOperator bytesEstimator,
                                                         final Iterable source,
                                                         final StreamingSerializer serializer) {
        return stream(source.spliterator(), false)
                .map(new SerializerFunction<>(bytesEstimator, allocator, serializer))
                .collect(toList());
    }

    @Nonnull
    private static  BlockingIterable applySerializer0(final BufferAllocator allocator,
                                                                 final IntUnaryOperator bytesEstimator,
                                                                 final BlockingIterable source,
                                                                 final StreamingSerializer serializer) {
        SerializerFunction serializerFunction = new SerializerFunction<>(bytesEstimator, allocator, serializer);
        return () -> {
            final BlockingIterator iterator = source.iterator();
            return new BlockingIterator() {
                @Override
                public boolean hasNext(final long timeout, final TimeUnit unit) throws TimeoutException {
                    return iterator.hasNext(timeout, unit);
                }

                @Override
                public Buffer next(final long timeout, final TimeUnit unit) throws TimeoutException {
                    return serializerFunction.apply(iterator.next(timeout, unit));
                }

                @Override
                public void close() throws Exception {
                    iterator.close();
                }

                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }

                @Override
                public Buffer next() {
                    return serializerFunction.apply(iterator.next());
                }
            };
        };
    }

    private static  void applyDeserializer0(final Publisher source, final Subscriber subscriber,
                                               final StreamingDeserializer deSerializer) {
        // The StreamingDeserializer will be used to buffer data in between Buffers. It is not thread safe but
        // the concatMap should ensure there is no concurrency, and will ensure visibility when transitioning
        // between Buffers.
        toSource(source.flatMapConcatIterable(deSerializer::deserialize)
                .beforeOnComplete(deSerializer::close))
                .subscribe(subscriber);
    }

    @Nonnull
    private static  CloseableIterable applyDeserializer0(final Iterable source,
                                                      final StreamingDeserializer deSerializer) {
        return deserializeAndClose(source, deSerializer::deserialize, deSerializer);
    }

    @Nonnull
    private static  CloseableIterable deserializeAggregated0(final Buffer serializedData,
                                                   final StreamingDeserializer deSerializer) {
        return deserializeAndClose(serializedData, deSerializer::deserialize, deSerializer);
    }

    private static  CloseableIterable deserializeAndClose(final S source,
                                                                   final Function> doDeserialize,
                                                                   final StreamingDeserializer deSerializer) {
        final Iterable deserialized;
        try {
            deserialized = doDeserialize.apply(source);
        } catch (Throwable throwable) {
            try {
                deSerializer.close();
            } catch (SerializationException e) {
                throwable.addSuppressed(e);
            }
            throw throwable;
        }

        return new AbstractCloseableIterable(deserialized) {
            @Override
            protected void closeIterator(final Iterator iterator) {
                deSerializer.close();
            }
        };
    }

    private static  T getSingleValueOnly(CloseableIterable iterable) {
        final CloseableIterator iterator = iterable.iterator();
        final T value = iterator.next();
        closeIterator(iterator,
                iterator.hasNext() ? new SerializationException("More than one value was deserialized.") : null);
        return value;
    }

    private static void closeIterator(CloseableIterator iterator, @Nullable SerializationException cause) {
        try {
            iterator.close(); // May throw in case of incomplete accumulated data
        } catch (Exception e) {
            if (cause != null) {
                cause.addSuppressed(e);
                throw cause;
            }
            if (e instanceof SerializationException) {
                throw (SerializationException) e;
            }
            throw new SerializationException("Failed to close iterator", e);
        }

        if (cause != null) {
            throw cause;
        }
    }

    private static class SerializerFunction implements Function {
        private final IntUnaryOperator bytesEstimator;
        private final BufferAllocator allocator;
        private final StreamingSerializer serializer;
        private int lastSize;

        SerializerFunction(final IntUnaryOperator bytesEstimator, final BufferAllocator allocator,
                           final StreamingSerializer serializer) {
            this.bytesEstimator = bytesEstimator;
            this.allocator = allocator;
            this.serializer = serializer;
        }

        @Override
        public Buffer apply(final T t) {
            lastSize = bytesEstimator.applyAsInt(lastSize);
            final Buffer destination = allocator.newBuffer(lastSize);
            serializer.serialize(t, destination);
            return destination;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy