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}