package org.molgenis.data.cache.l3; import org.molgenis.data.*; import org.molgenis.data.support.QueryImpl; import org.molgenis.data.transaction.TransactionInformation; import org.slf4j.Logger; import java.util.List; import java.util.stream.Stream; import static com.google.common.collect.Lists.newArrayList; import static java.util.Objects.requireNonNull; import static org.molgenis.data.RepositoryCapability.CACHEABLE; import static org.slf4j.LoggerFactory.getLogger; /** * Retrieves identifiers from the {@link L3Cache} based on a {@link Query} * if {@link RepositoryCapability#CACHEABLE}. * <p> * Delegates to the underlying {@link Repository} */ public class L3CacheRepositoryDecorator extends AbstractRepositoryDecorator<Entity> { private static final Logger LOG = getLogger(L3CacheRepositoryDecorator.class); private final L3Cache l3Cache; private final boolean cacheable; private final Repository<Entity> decoratedRepository; private final TransactionInformation transactionInformation; private static final int MAX_PAGE_SIZE = 1000; public L3CacheRepositoryDecorator(Repository<Entity> decoratedRepository, L3Cache l3Cache, TransactionInformation transactionInformation) { this.decoratedRepository = requireNonNull(decoratedRepository); this.l3Cache = requireNonNull(l3Cache); this.cacheable = decoratedRepository.getCapabilities().containsAll(newArrayList(CACHEABLE)); this.transactionInformation = requireNonNull(transactionInformation); } @Override protected Repository<Entity> delegate() { return decoratedRepository; } /** * Retrieves a {@link List} of identifiers from the {@link L3Cache} if the * {@link Repository} is cacheable and the {@link Query} is * limited (i.e. contains a pageSize) between 0 and MAX_PAGE_SIZE * * @param query The {@link Query} * @return A stream of {@link Entity} */ @Override public Stream<Entity> findAll(Query<Entity> query) { if (transactionInformation.isRepositoryCompletelyClean(getName())) { // FIXME page size for metadata is always 0, and batching is done by the postgres repository // FIXME Only superusers are able to use the L3 cache for metadata if (cacheable && query.getPageSize() > 0 && query.getPageSize() <= MAX_PAGE_SIZE) { List<Object> ids = l3Cache.get(delegate(), query); return delegate().findAll(ids.stream(), query.getFetch()); } } else { LOG.debug("Repository is dirty: {}", getName()); } return delegate().findAll(query); } /** * Retrieves a single identifier from the {@link L3Cache} if the * {@link Repository} is cacheable and hasn't been touched in this transaction. * * @param query The {@link Query} * @return A single {@link Entity} or null if not found */ @Override public Entity findOne(Query<Entity> query) { if (transactionInformation.isRepositoryCompletelyClean(getName()) && cacheable) { // pageSize is irrelevant for findOne, would be a waste to cache them in different entries // sort may affect which of the results is the first result, so cannot ignore that. QueryImpl<Entity> cacheKey = new QueryImpl<>(query).setPageSize(1); List<Object> ids = l3Cache.get(delegate(), cacheKey); if (ids.isEmpty()) { return null; } return delegate().findOneById(ids.get(0), query.getFetch()); } return delegate().findOne(query); } }