/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.map.impl.event;
import com.hazelcast.core.EntryEventType;
import com.hazelcast.map.impl.EntryEventFilter;
import com.hazelcast.map.impl.EventListenerFilter;
import com.hazelcast.map.impl.MapPartitionLostEventFilter;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.query.QueryEventFilter;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.spi.EventFilter;
import com.hazelcast.spi.impl.eventservice.impl.TrueEventFilter;
import com.hazelcast.spi.serialization.SerializationService;
import java.util.Collection;
import java.util.Collections;
import static com.hazelcast.core.EntryEventType.EVICTED;
import static com.hazelcast.core.EntryEventType.EXPIRED;
import static com.hazelcast.core.EntryEventType.REMOVED;
/**
* This entry event filtering strategy models the default backwards compatible Hazelcast behaviour.
* In particular, when processing {@code UPDATED} events, the predicate is evaluated against the new value; if the new value
* matches the predicate, then the {@code UPDATED} event will be published to the registered listeners.
* <p>
* Note that when trying to build a continuous query cache, this filtering strategy is flawed, as the listener will not be
* notified for updated entries whose old value matched the predicate while new value does not match the predicate. This has
* been addressed in {@link QueryCacheNaturalFilteringStrategy}.
* </p>
*
* @see QueryCacheNaturalFilteringStrategy
*/
public class DefaultEntryEventFilteringStrategy extends AbstractFilteringStrategy {
public DefaultEntryEventFilteringStrategy(SerializationService serializationService, MapServiceContext mapServiceContext) {
super(serializationService, mapServiceContext);
}
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException if the provided {@code filter} is not of a known type
*/
// This code has been moved from MapEventPublisherImpl.doFilter and
// provides the default backwards compatible filtering strategy implementation.
@SuppressWarnings("checkstyle:npathcomplexity")
@Override
public int doFilter(EventFilter filter, Data dataKey, Object dataOldValue, Object dataValue, EntryEventType eventType,
String mapNameOrNull) {
if (filter instanceof MapPartitionLostEventFilter) {
return FILTER_DOES_NOT_MATCH;
}
// the order of the following ifs is important!
// QueryEventFilter is instance of EntryEventFilter
if (filter instanceof EventListenerFilter) {
if (!filter.eval(eventType.getType())) {
return FILTER_DOES_NOT_MATCH;
} else {
filter = ((EventListenerFilter) filter).getEventFilter();
}
}
if (filter instanceof TrueEventFilter) {
return eventType.getType();
}
if (filter instanceof QueryEventFilter) {
return processQueryEventFilter(filter, eventType, dataKey, dataOldValue, dataValue, mapNameOrNull)
? eventType.getType() : FILTER_DOES_NOT_MATCH;
}
if (filter instanceof EntryEventFilter) {
return processEntryEventFilter(filter, dataKey) ? eventType.getType() : FILTER_DOES_NOT_MATCH;
}
throw new IllegalArgumentException("Unknown EventFilter type = [" + filter.getClass().getCanonicalName() + "]");
}
@Override
public EntryEventDataCache getEntryEventDataCache() {
return new DefaultEntryEventDataCache();
}
@Override
public String toString() {
return "DefaultEntryEventFilteringStrategy";
}
/**
* Evaluate if the filter matches the map event. In case of a remove, evict or expire event
* the old value will be used for evaluation, otherwise we use the new value.
* The filter must be of {@link QueryEventFilter} type.
*
* @param filter a {@link QueryEventFilter} filter
* @param eventType the event type
* @param dataKey the entry key
* @param dataOldValue the entry value before the event
* @param dataValue the entry value after the event
* @param mapNameOrNull the map name. May be null if this is not a map event (e.g. cache event)
* @return {@code true} if the entry matches the query event filter
*/
private boolean processQueryEventFilter(EventFilter filter, EntryEventType eventType,
Data dataKey, Object dataOldValue, Object dataValue, String mapNameOrNull) {
Object testValue;
if (eventType == REMOVED || eventType == EVICTED || eventType == EXPIRED) {
testValue = dataOldValue;
} else {
testValue = dataValue;
}
return evaluateQueryEventFilter(filter, dataKey, testValue, mapNameOrNull);
}
/**
* Cache for 2 different {@link EntryEventData} objects - one for including values and one for excluding values.
*/
private class DefaultEntryEventDataCache implements EntryEventDataCache {
EntryEventData eventDataIncludingValues;
EntryEventData eventDataExcludingValues;
@Override
public EntryEventData getOrCreateEventData(String mapName, Address caller, Data dataKey, Object newValue, Object oldValue,
Object mergingValue, int eventType, boolean includingValues) {
if (includingValues && eventDataIncludingValues != null) {
return eventDataIncludingValues;
} else if (!includingValues && eventDataExcludingValues != null) {
return eventDataExcludingValues;
} else {
EntryEventData entryEventData = new EntryEventData(getThisNodesAddress(), mapName, caller, dataKey,
includingValues ? mapServiceContext.toData(newValue) : null,
includingValues ? mapServiceContext.toData(oldValue) : null,
includingValues ? mapServiceContext.toData(mergingValue) : null, eventType);
if (includingValues) {
eventDataIncludingValues = entryEventData;
} else {
eventDataExcludingValues = entryEventData;
}
return entryEventData;
}
}
@Override
public boolean isEmpty() {
return eventDataIncludingValues == null && eventDataExcludingValues == null;
}
@Override
public Collection<EntryEventData> eventDataIncludingValues() {
return eventDataIncludingValues == null ? null : Collections.singleton(eventDataIncludingValues);
}
@Override
public Collection<EntryEventData> eventDataExcludingValues() {
return eventDataExcludingValues == null ? null : Collections.singleton(eventDataExcludingValues);
}
}
}