package org.molgenis.data.elasticsearch; import org.molgenis.data.*; import org.molgenis.data.QueryRule.Operator; import org.molgenis.data.aggregation.AggregateQuery; import org.molgenis.data.aggregation.AggregateResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; import static org.molgenis.data.QueryUtils.containsAnyOperator; import static org.molgenis.data.QueryUtils.containsComputedAttribute; import static org.molgenis.data.RepositoryCapability.AGGREGATEABLE; import static org.molgenis.data.RepositoryCapability.QUERYABLE; /** * Decorator for indexed repositories. Sends all queries with operators that are not supported by the decorated * repository to the index. */ public class IndexedRepositoryDecorator extends AbstractRepositoryDecorator<Entity> { private static final Logger LOG = LoggerFactory.getLogger(IndexedRepositoryDecorator.class); private static final String INDEX_REPOSITORY = "Index Repository"; private static final String DECORATED_REPOSITORY = "Decorated Repository"; private final Repository<Entity> decoratedRepo; private SearchService searchService; /** * Operators NOT supported by the decorated repository. */ private Set<Operator> unsupportedOperators; public IndexedRepositoryDecorator(Repository<Entity> decoratedRepo, SearchService searchService) { this.searchService = requireNonNull(searchService); this.decoratedRepo = requireNonNull(decoratedRepo); Set<Operator> operators = getQueryOperators(); operators.removeAll(this.decoratedRepo.getQueryOperators()); unsupportedOperators = Collections.unmodifiableSet(operators); } @Override protected Repository<Entity> delegate() { return decoratedRepo; } @Override public Entity findOne(Query<Entity> q) { if (querySupported(q)) { LOG.debug("public Entity findOne({}) entityName: [{}] repository: [{}]", q, getEntityType().getName(), DECORATED_REPOSITORY); return decoratedRepo.findOne(q); } else { LOG.debug("public Entity findOne({}) entityName: [{}] repository: [{}]", q, getEntityType().getName(), INDEX_REPOSITORY); return searchService.findOne(q, getEntityType()); } } @Override public Stream<Entity> findAll(Query<Entity> q) { if (querySupported(q)) { LOG.debug("public Entity findAll({}) entityName: [{}] repository: [{}]", q, getEntityType().getName(), DECORATED_REPOSITORY); return decoratedRepo.findAll(q); } else { LOG.debug("public Entity findAll({}) entityName: [{}] repository: [{}]", q, getEntityType().getName(), INDEX_REPOSITORY); return searchService.searchAsStream(q, getEntityType()); } } /** * Gets the capabilities of the underlying repository and adds three read capabilities provided by the index: * {@link RepositoryCapability#INDEXABLE}, {@link RepositoryCapability#QUERYABLE} and {@link RepositoryCapability#AGGREGATEABLE}. * Does not add other index capabilities like{@link RepositoryCapability#WRITABLE} because those might conflict with the underlying repository. */ @Override public Set<RepositoryCapability> getCapabilities() { Set<RepositoryCapability> capabilities = new HashSet<>(); capabilities.addAll(decoratedRepo.getCapabilities()); capabilities.addAll(EnumSet.of(QUERYABLE, AGGREGATEABLE)); return unmodifiableSet(capabilities); } @Override public Set<Operator> getQueryOperators() { return EnumSet.allOf(Operator.class); } @Override public long count(Query<Entity> q) { // TODO check if the index is stable. If index is stable you can better check index for count results if (querySupported(q)) { LOG.debug("public long count({}) entityName: [{}] repository: [{}]", q, getEntityType().getName(), DECORATED_REPOSITORY); return decoratedRepo.count(q); } else { LOG.debug("public long count({}) entityName: [{}] repository: [{}]", q, getEntityType().getName(), INDEX_REPOSITORY); return searchService.count(q, getEntityType()); } } @Override public AggregateResult aggregate(AggregateQuery aggregateQuery) { return searchService.aggregate(aggregateQuery, getEntityType()); } /** * Checks if the underlying repository can handle this query. Queries with unsupported operators or queries that use * attributes with computed values are delegated to the index. */ private boolean querySupported(Query<Entity> q) { return !containsAnyOperator(q, unsupportedOperators) && !containsComputedAttribute(q, getEntityType()); } }