package org.infinispan.client.hotrod.event;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
import org.infinispan.client.hotrod.annotation.ClientCacheEntryExpired;
import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified;
import org.infinispan.client.hotrod.annotation.ClientCacheEntryRemoved;
import org.infinispan.client.hotrod.annotation.ClientListener;
import org.infinispan.client.hotrod.filter.Filters;
import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller;
import org.infinispan.protostream.ProtobufUtil;
import org.infinispan.protostream.SerializationContext;
import org.infinispan.query.api.continuous.ContinuousQuery;
import org.infinispan.query.api.continuous.ContinuousQueryListener;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.impl.BaseQuery;
import org.infinispan.query.remote.client.ContinuousQueryResult;
/**
* A container of continuous query listeners for a cache.
* <p>This class is not threadsafe.
*
* @author anistor@redhat.com
* @since 8.2
* @private
*/
public final class ContinuousQueryImpl<K, V> implements ContinuousQuery<K, V> {
private final RemoteCache<K, V> cache;
private final SerializationContext serializationContext;
private final List<ClientEntryListener<K, ?>> listeners = new ArrayList<>();
public ContinuousQueryImpl(RemoteCache<K, V> cache) {
if (cache == null) {
throw new IllegalArgumentException("cache parameter cannot be null");
}
this.cache = cache;
serializationContext = ProtoStreamMarshaller.getSerializationContext(cache.getRemoteCacheManager());
}
@Override
public <C> void addContinuousQueryListener(String queryString, ContinuousQueryListener<K, C> listener) {
addContinuousQueryListener(queryString, null, listener);
}
@Override
public <C> void addContinuousQueryListener(String queryString, Map<String, Object> namedParameters, ContinuousQueryListener<K, C> listener) {
ClientEntryListener<K, ?> eventListener = new ClientEntryListener<>(serializationContext, listener);
Object[] factoryParams = Filters.makeFactoryParams(queryString, namedParameters);
cache.addClientListener(eventListener, factoryParams, null);
listeners.add(eventListener);
}
/**
* Registers a continuous query listener that uses a query DSL based filter. The listener will receive notifications
* when a cache entry joins or leaves the matching set defined by the query.
*
* @param listener the continuous query listener instance
* @param query the query to be used for determining the matching set
*/
public <C> void addContinuousQueryListener(Query query, ContinuousQueryListener<K, C> listener) {
BaseQuery baseQuery = (BaseQuery) query;
addContinuousQueryListener(baseQuery.getQueryString(), baseQuery.getParameters(), listener);
}
public void removeContinuousQueryListener(ContinuousQueryListener<K, ?> listener) {
for (Iterator<ClientEntryListener<K, ?>> it = listeners.iterator(); it.hasNext(); ) {
ClientEntryListener l = it.next();
if (l.listener == listener) {
cache.removeClientListener(l);
it.remove();
break;
}
}
}
public List<ContinuousQueryListener<K, ?>> getListeners() {
List<ContinuousQueryListener<K, ?>> queryListeners = new ArrayList<>(listeners.size());
for (ClientEntryListener<K, ?> l : listeners) {
queryListeners.add(l.listener);
}
return queryListeners;
}
public void removeAllListeners() {
for (ClientEntryListener l : listeners) {
cache.removeClientListener(l);
}
listeners.clear();
}
@ClientListener(filterFactoryName = Filters.CONTINUOUS_QUERY_FILTER_FACTORY_NAME,
converterFactoryName = Filters.CONTINUOUS_QUERY_FILTER_FACTORY_NAME,
useRawData = true, includeCurrentState = true)
private static final class ClientEntryListener<K, C> {
private final SerializationContext serializationContext;
private final ContinuousQueryListener<K, C> listener;
ClientEntryListener(SerializationContext serializationContext, ContinuousQueryListener<K, C> listener) {
this.serializationContext = serializationContext;
this.listener = listener;
}
@ClientCacheEntryCreated
@ClientCacheEntryModified
@ClientCacheEntryRemoved
@ClientCacheEntryExpired
public void handleEvent(ClientCacheEntryCustomEvent<byte[]> event) throws IOException {
byte[] eventData = event.getEventData();
ContinuousQueryResult cqr = ProtobufUtil.fromWrappedByteArray(serializationContext, eventData);
Object key = ProtobufUtil.fromWrappedByteArray(serializationContext, cqr.getKey());
Object value = cqr.getValue() != null ? ProtobufUtil.fromWrappedByteArray(serializationContext, cqr.getValue()) : cqr.getProjection();
switch (cqr.getResultType()) {
case JOINING:
listener.resultJoining((K) key, (C) value);
break;
case UPDATED:
listener.resultUpdated((K) key, (C) value);
break;
case LEAVING:
listener.resultLeaving((K) key);
break;
default:
throw new IllegalStateException("Unexpected result type : " + cqr.getResultType());
}
}
}
}