package org.infinispan.objectfilter.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.infinispan.objectfilter.FilterCallback;
import org.infinispan.objectfilter.FilterSubscription;
import org.infinispan.objectfilter.Matcher;
import org.infinispan.objectfilter.ObjectFilter;
import org.infinispan.objectfilter.impl.aggregation.FieldAccumulator;
import org.infinispan.objectfilter.impl.logging.Log;
import org.infinispan.objectfilter.impl.predicateindex.MatcherEvalContext;
import org.infinispan.objectfilter.impl.syntax.ConstantBooleanExpr;
import org.infinispan.objectfilter.impl.syntax.FullTextVisitor;
import org.infinispan.objectfilter.impl.syntax.parser.IckleParsingResult;
import org.infinispan.objectfilter.impl.syntax.parser.IckleParser;
import org.infinispan.objectfilter.impl.syntax.parser.ObjectPropertyHelper;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.impl.BaseQuery;
import org.jboss.logging.Logger;
/**
* @author anistor@redhat.com
* @since 7.0
*/
//todo [anistor] make package local
public abstract class BaseMatcher<TypeMetadata, AttributeMetadata, AttributeId extends Comparable<AttributeId>> implements Matcher {
private static final Log log = Logger.getMessageLogger(Log.class, BaseMatcher.class.getName());
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
private final Lock write = readWriteLock.writeLock();
private final Map<TypeMetadata, FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId>> filtersByType = new HashMap<>();
private final Map<TypeMetadata, FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId>> deltaFiltersByType = new HashMap<>();
protected final ObjectPropertyHelper<TypeMetadata> propertyHelper;
protected BaseMatcher(ObjectPropertyHelper<TypeMetadata> propertyHelper) {
this.propertyHelper = propertyHelper;
}
public ObjectPropertyHelper<TypeMetadata> getPropertyHelper() {
return propertyHelper;
}
/**
* Executes the registered filters and notifies each one of them whether it was satisfied or not by the given
* instance.
*
* @param userContext an optional user provided object to be passed to matching subscribers along with the matching
* instance; can be {@code null}
* @param eventType on optional event type discriminator that is matched against the even type specified when the
* filter was registered; can be {@code null}
* @param instance the object to test against the registered filters; never {@code null}
*/
@Override
public void match(Object userContext, Object eventType, Object instance) {
if (instance == null) {
throw new IllegalArgumentException("instance cannot be null");
}
read.lock();
try {
MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> ctx = startMultiTypeContext(false, userContext, eventType, instance);
if (ctx != null) {
// try to match
ctx.process(ctx.getRootNode());
// notify
ctx.notifySubscribers();
}
} finally {
read.unlock();
}
}
@Override
public void matchDelta(Object userContext, Object eventType, Object instanceOld, Object instanceNew, Object joiningEvent, Object updatedEvent, Object leavingEvent) {
if (instanceOld == null && instanceNew == null) {
throw new IllegalArgumentException("instances cannot be both null");
}
read.lock();
try {
MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> ctx1 = null;
MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> ctx2 = null;
if (instanceOld != null) {
ctx1 = startMultiTypeContext(true, userContext, eventType, instanceOld);
if (ctx1 != null) {
// try to match
ctx1.process(ctx1.getRootNode());
}
}
if (instanceNew != null) {
ctx2 = startMultiTypeContext(true, userContext, eventType, instanceNew);
if (ctx2 != null) {
// try to match
ctx2.process(ctx2.getRootNode());
}
}
if (ctx1 != null) {
// notify
ctx1.notifyDeltaSubscribers(ctx2, joiningEvent, updatedEvent, leavingEvent);
} else if (ctx2 != null) {
// notify
ctx2.notifyDeltaSubscribers(null, leavingEvent, updatedEvent, joiningEvent);
}
} finally {
read.unlock();
}
}
@Override
public ObjectFilter getObjectFilter(Query query) {
return getObjectFilter(((BaseQuery) query).getQueryString(), null);
}
@Override
public ObjectFilter getObjectFilter(String queryString) {
return getObjectFilter(queryString, null);
}
@Override
public ObjectFilter getObjectFilter(String queryString, List<FieldAccumulator> acc) {
final IckleParsingResult<TypeMetadata> parsingResult = IckleParser.parse(queryString, propertyHelper);
disallowGroupingAndAggregations(parsingResult);
// if the query is a contradiction just return an ObjectFilter that rejects everything
if (parsingResult.getWhereClause() == ConstantBooleanExpr.FALSE) {
return new RejectObjectFilter<>(null, parsingResult);
}
final MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> metadataAdapter = createMetadataAdapter(parsingResult.getTargetEntityMetadata());
// if the query is a tautology or there is no query at all and there is no sorting or projections just return a special instance that accepts anything
// in case we have sorting and projections we cannot take this shortcut because the computation of projections or sort projections is a bit more involved
if ((parsingResult.getWhereClause() == null || parsingResult.getWhereClause() == ConstantBooleanExpr.TRUE)
&& parsingResult.getSortFields() == null && parsingResult.getProjectedPaths() == null) {
return new AcceptObjectFilter<>(null, this, metadataAdapter, parsingResult);
}
FieldAccumulator[] accumulators = acc != null ? acc.toArray(new FieldAccumulator[acc.size()]) : null;
return new ObjectFilterImpl<>(this, metadataAdapter, parsingResult, accumulators);
}
@Override
public ObjectFilter getObjectFilter(FilterSubscription filterSubscription) {
FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId> filterSubscriptionImpl = (FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId>) filterSubscription;
ObjectFilter objectFilter = getObjectFilter(filterSubscriptionImpl.getQueryString());
return filterSubscriptionImpl.getNamedParameters() != null ? objectFilter.withParameters(filterSubscriptionImpl.getNamedParameters()) : objectFilter;
}
@Override
public FilterSubscription registerFilter(Query query, FilterCallback callback, Object... eventType) {
return registerFilter(query, callback, false, eventType);
}
@Override
public FilterSubscription registerFilter(Query query, FilterCallback callback, boolean isDeltaFilter, Object... eventType) {
BaseQuery baseQuery = (BaseQuery) query;
return registerFilter(baseQuery.getQueryString(), baseQuery.getParameters(), callback, isDeltaFilter, eventType);
}
@Override
public FilterSubscription registerFilter(String queryString, FilterCallback callback, Object... eventType) {
return registerFilter(queryString, callback, false, eventType);
}
@Override
public FilterSubscription registerFilter(String queryString, FilterCallback callback, boolean isDeltaFilter, Object... eventType) {
return registerFilter(queryString, null, callback, isDeltaFilter, eventType);
}
@Override
public FilterSubscription registerFilter(String queryString, Map<String, Object> namedParameters,
FilterCallback callback, Object... eventType) {
return registerFilter(queryString, namedParameters, callback, false, eventType);
}
@Override
public FilterSubscription registerFilter(String queryString, Map<String, Object> namedParameters,
FilterCallback callback, boolean isDeltaFilter, Object... eventType) {
IckleParsingResult<TypeMetadata> parsingResult = IckleParser.parse(queryString, propertyHelper);
disallowGroupingAndAggregations(parsingResult);
disallowFullText(parsingResult);
Map<TypeMetadata, FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId>> filterMap = isDeltaFilter ? deltaFiltersByType : filtersByType;
write.lock();
try {
FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId> filterRegistry = filterMap.get(parsingResult.getTargetEntityMetadata());
if (filterRegistry == null) {
filterRegistry = new FilterRegistry<>(createMetadataAdapter(parsingResult.getTargetEntityMetadata()), true);
filterMap.put(filterRegistry.getMetadataAdapter().getTypeMetadata(), filterRegistry);
}
return filterRegistry.addFilter(queryString, namedParameters, parsingResult.getWhereClause(), parsingResult.getProjections(), parsingResult.getProjectedTypes(), parsingResult.getSortFields(), callback, isDeltaFilter, eventType);
} finally {
write.unlock();
}
}
private void disallowFullText(IckleParsingResult<TypeMetadata> parsingResult) {
if (parsingResult.getWhereClause() != null) {
if (parsingResult.getWhereClause().acceptVisitor(FullTextVisitor.INSTANCE)) {
throw log.getFiltersCannotUseFullTextSearchException();
}
}
}
private void disallowGroupingAndAggregations(IckleParsingResult<TypeMetadata> parsingResult) {
if (parsingResult.hasGroupingOrAggregations()) {
throw log.getFiltersCannotUseGroupingOrAggregationException();
}
}
@Override
public void unregisterFilter(FilterSubscription filterSubscription) {
FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId> filterSubscriptionImpl = (FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId>) filterSubscription;
Map<TypeMetadata, FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId>> filterMap = filterSubscription.isDeltaFilter() ? deltaFiltersByType : filtersByType;
write.lock();
try {
FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId> filterRegistry = filterMap.get(filterSubscriptionImpl.getMetadataAdapter().getTypeMetadata());
if (filterRegistry != null) {
filterRegistry.removeFilter(filterSubscription);
} else {
throw new IllegalStateException("No filter was found for type " + filterSubscriptionImpl.getMetadataAdapter().getTypeMetadata());
}
if (filterRegistry.getFilterSubscriptions().isEmpty()) {
filterMap.remove(filterRegistry.getMetadataAdapter().getTypeMetadata());
}
} finally {
write.unlock();
}
}
/**
* Creates a new {@link MatcherEvalContext} capable of dealing with multiple filters. The context is created only if
* the given instance is recognized to be of a type that has some filters registered. If there are no filters, {@code
* null} is returned to signal this condition and make the evaluation faster. This method must be called while
* holding the internal write lock.
*
* @param isDelta indicates if this is a delta match of not
* @param userContext an opaque value, possibly null, the is received from the caller and is to be handed to the
* {@link FilterCallback} in case a match is detected
* @param eventType on optional event type discriminator
* @param instance the instance to filter; never {@code null}
* @return the MatcherEvalContext or {@code null} if no filter was registered for the instance
*/
protected abstract MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> startMultiTypeContext(boolean isDelta, Object userContext, Object eventType, Object instance);
/**
* Creates a new {@link MatcherEvalContext} capable of dealing with a single filter for a single type. The context is
* created only if the given instance is recognized to be of a type that has some filters registered. If there are no
* filters, {@code null} is returned to signal this condition and make the evaluation faster. This method must be
* called while holding the internal write lock.
*
* @param userContext an opaque value, possibly null, the is received from the caller and is to be handed to the
* {@link FilterCallback} in case a match is detected
* @param instance the instance to filter; never {@code null}
* @param metadataAdapter the metadata adapter of expected instance type
* @return the MatcherEvalContext or {@code null} if no filter was registered for the instance
*/
protected abstract MatcherEvalContext<TypeMetadata, AttributeMetadata, AttributeId> startSingleTypeContext(Object userContext, Object eventType, Object instance, MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> metadataAdapter);
protected abstract MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> createMetadataAdapter(TypeMetadata entityType);
protected final FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId> getFilterRegistryForType(boolean isDeltaFilter, TypeMetadata entityType) {
return isDeltaFilter ? deltaFiltersByType.get(entityType) : filtersByType.get(entityType);
}
/**
* Decorates a matching instance before it is presented to the caller of the {@link ObjectFilter#filter(Object)}.
*
* @param instance never null
* @return the converted/decorated instance
*/
protected Object convert(Object instance) {
return instance;
}
}