001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006
007package org.fcrepo.kernel.impl.cache;
008
009import static java.util.stream.Collectors.toList;
010import static org.apache.jena.vocabulary.RDF.type;
011
012import java.net.URI;
013import java.util.List;
014import java.util.Map;
015import java.util.concurrent.ConcurrentHashMap;
016import java.util.concurrent.TimeUnit;
017import java.util.function.Supplier;
018
019import org.fcrepo.config.FedoraPropsConfig;
020import org.fcrepo.kernel.api.RdfStream;
021import org.fcrepo.kernel.api.ReadOnlyTransaction;
022import org.fcrepo.kernel.api.cache.UserTypesCache;
023import org.fcrepo.kernel.api.identifiers.FedoraId;
024
025import org.apache.jena.graph.Triple;
026import org.springframework.stereotype.Component;
027
028import com.github.benmanes.caffeine.cache.Cache;
029import com.github.benmanes.caffeine.cache.Caffeine;
030
031/**
032 * Default UserTypesCache implementation
033 *
034 * @author pwinckles
035 */
036@Component
037public class UserTypesCacheImpl implements UserTypesCache {
038
039    private final Cache<FedoraId, List<URI>> globalCache;
040    private final Map<String, Cache<FedoraId, List<URI>>> sessionCaches;
041
042    public UserTypesCacheImpl(final FedoraPropsConfig config) {
043        this.globalCache = Caffeine.newBuilder()
044                .maximumSize(config.getUserTypesCacheSize())
045                .expireAfterAccess(config.getUserTypesCacheTimeout(), TimeUnit.MINUTES)
046                .build();
047        this.sessionCaches = new ConcurrentHashMap<>();
048    }
049
050    /**
051     * {@inheritDoc}
052     */
053    @Override
054    public List<URI> getUserTypes(final FedoraId resourceId,
055                                  final String sessionId,
056                                  final Supplier<RdfStream> rdfProvider) {
057        if (isNotReadOnlySession(sessionId)) {
058            final var sessionCache = getSessionCache(sessionId);
059
060            return sessionCache.get(resourceId, k -> {
061                return globalCache.get(resourceId, k2 -> {
062                    return extractRdfTypes(rdfProvider.get());
063                });
064            });
065        } else {
066            return globalCache.get(resourceId, k -> {
067                return extractRdfTypes(rdfProvider.get());
068            });
069        }
070    }
071
072    /**
073     * {@inheritDoc}
074     */
075    @Override
076    public void cacheUserTypes(final FedoraId resourceId,
077                               final RdfStream rdf,
078                               final String sessionId) {
079        if (isNotReadOnlySession(sessionId)) {
080            getSessionCache(sessionId).put(resourceId, extractRdfTypes(rdf));
081        }
082    }
083
084    /**
085     * {@inheritDoc}
086     */
087    @Override
088    public void cacheUserTypes(final FedoraId resourceId,
089                               final List<URI> userTypes,
090                               final String sessionId) {
091        if (isNotReadOnlySession(sessionId)) {
092            getSessionCache(sessionId).put(resourceId, userTypes);
093        }
094    }
095
096    /**
097     * {@inheritDoc}
098     */
099    @Override
100    public void mergeSessionCache(final String sessionId) {
101        if (isNotReadOnlySession(sessionId)) {
102            final var sessionCache = getSessionCache(sessionId);
103            globalCache.putAll(sessionCache.asMap());
104            dropSessionCache(sessionId);
105        }
106    }
107
108    /**
109     * {@inheritDoc}
110     */
111    @Override
112    public void dropSessionCache(final String sessionId) {
113        if (isNotReadOnlySession(sessionId)) {
114            sessionCaches.remove(sessionId);
115        }
116    }
117
118    private Cache<FedoraId, List<URI>> getSessionCache(final String sessionId) {
119        return sessionCaches.computeIfAbsent(sessionId, k -> {
120            return Caffeine.newBuilder()
121                    .maximumSize(1024)
122                    .build();
123        });
124    }
125
126    private List<URI> extractRdfTypes(final RdfStream rdf) {
127        return rdf.filter(t -> t.predicateMatches(type.asNode()))
128                .map(Triple::getObject)
129                .map(t -> URI.create(t.toString()))
130                .collect(toList());
131    }
132
133    private boolean isNotReadOnlySession(final String sessionId) {
134        return !ReadOnlyTransaction.READ_ONLY_TX_ID.equals(sessionId);
135    }
136}