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

io.r2dbc.postgresql.codec.CachedCodecLookup Maven / Gradle / Ivy

There is a newer version: 0.8.13.RELEASE
Show newest version
/*
 * Copyright 2021 the original author or 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.r2dbc.postgresql.codec;

import io.r2dbc.postgresql.message.Format;
import io.r2dbc.postgresql.type.PostgresqlObjectId;
import io.r2dbc.postgresql.util.Assert;
import reactor.util.Logger;
import reactor.util.Loggers;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * Cache implementation of the {@link CodecLookup}. This will keep the relevant {@link Codec} for the type, format and database type cached for faster access.
 * In case the {@link Codec} can't be found in the cache, a fallback search using {@link DefaultCodecLookup} will be done.
 *
 * @since 0.8.9
 */
class CachedCodecLookup implements CodecLookup {

    private static final Logger LOG = Loggers.getLogger(CachedCodecLookup.class);

    private final Map> decodeCodecsCache = new ConcurrentHashMap<>();

    private final Map> encodeCodecsCache = new ConcurrentHashMap<>();

    private final Map> encodeNullCodecsCache = new ConcurrentHashMap<>();

    private final CodecLookup delegate;

    public CachedCodecLookup(Iterable> codecRegistry) {
        this.delegate = new DefaultCodecLookup(codecRegistry);
    }

    public CachedCodecLookup(CodecLookup delegate) {
        Assert.isTrue(!(delegate instanceof CachedCodecLookup), "delegate must not be of type CachedCodecLookup");
        this.delegate = delegate;
    }

    @Override
    public Iterator> iterator() {
        return this.delegate.iterator();
    }

    @Override
    public void afterCodecAdded() {

        this.decodeCodecsCache.clear();
        this.encodeCodecsCache.clear();
        this.encodeNullCodecsCache.clear();

        for (Codec c : this.delegate) {

            if (c instanceof CodecMetadata) {
                CodecMetadata metadata = (CodecMetadata) c;

                cacheEncode(c, metadata.type());
                for (PostgresqlObjectId identifier : metadata.getDataTypes()) {
                    for (Format format : metadata.getFormats()) {
                        cacheDecode(c, metadata.type(), identifier, format);
                    }
                }
            }
        }
        // Handle decode to Object.class support
        for (PostgresqlObjectId identifier : PostgresqlObjectId.values()) {
            for (Format format : Format.all()) {
                Codec c = this.delegate.findDecodeCodec(identifier.getObjectId(), format, Object.class);
                if (c != null) {
                    cacheDecode(c, Object.class, identifier, format);
                }
            }
        }
    }

    @Override
    public  Codec findDecodeCodec(int dataType, Format format, Class type) {
        Integer hash = generateCodecHash(dataType, format, type);
        return findCodec(hash, this.decodeCodecsCache, () -> {
            LOG.trace("[codec-finder dataType={}, format={}, type={}] Decode codec not found in cache", dataType, format, type.getName());
            Codec c = this.delegate.findDecodeCodec(dataType, format, type);
            if (c != null) {
                this.decodeCodecsCache.putIfAbsent(hash, c);
            }
            return c;
        });
    }

    @Override
    public  Codec findEncodeCodec(T value) {
        Integer hash = generateCodecHash(value.getClass());
        return findCodec(hash, this.encodeCodecsCache, () -> {
            LOG.trace("[codec-finder type={}] Encode codec not found in cache", value.getClass().getName());
            Codec c = this.delegate.findEncodeCodec(value);
            if (c != null) {
                this.encodeCodecsCache.putIfAbsent(hash, c);
            }
            return c;
        });
    }

    @Override
    public  Codec findEncodeNullCodec(Class type) {
        Integer hash = generateCodecHash(type);
        return findCodec(hash, this.encodeNullCodecsCache, () -> {
            LOG.trace("[codec-finder type={}] Encode null codec not found in cache", type.getName());
            Codec c = this.delegate.findEncodeNullCodec(type);
            if (c != null) {
                this.encodeNullCodecsCache.putIfAbsent(hash, c);
            }
            return c;
        });
    }

    private void cacheDecode(Codec c, Class type, PostgresqlObjectId identifier, Format format) {
        Integer decodeHash = generateCodecHash(identifier.getObjectId(), format, type);
        this.decodeCodecsCache.putIfAbsent(decodeHash, c);
    }

    private void cacheEncode(Codec c, Class type) {
        Integer encodeHash = generateCodecHash(type);
        this.encodeCodecsCache.putIfAbsent(encodeHash, c);
        if (c.canEncodeNull(type)) {
            this.encodeNullCodecsCache.putIfAbsent(encodeHash, c);
        }
    }

    @SuppressWarnings("unchecked")
    private synchronized  Codec findCodec(Integer codecHash, Map> cache, Supplier> fallback) {
        Codec value = (Codec) cache.get(codecHash);
        return value != null ? value : fallback.get();
    }

    private static Integer generateCodecHash(int dataType, Format format, Class type) {
        int hash = (dataType << 5) - dataType;
        hash = (hash << 5) - hash + format.hashCode();
        hash = (hash << 5) - hash + generateCodecHash(type);
        return hash;
    }

    private static Integer generateCodecHash(Class type) {
        int hash = type.hashCode();
        if (type.getComponentType() != null) {
            hash = (hash << 5) - hash + generateCodecHash(type.getComponentType());
        }
        return hash;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy