/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.cache.query;
import javax.cache.Cache;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryUpdatedListener;
import javax.cache.event.EventType;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.CacheEntryEventSerializableFilter;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.lang.IgniteAsyncCallback;
/**
* API for configuring continuous cache queries.
* <p>
* Continuous queries allow to register a remote filter and a local listener
* for cache updates. If an update event passes the filter, it will be sent to
* the node that executed the query and local listener will be notified.
* <p>
* Additionally, you can execute initial query to get currently existing data.
* Query can be of any type (SQL, TEXT or SCAN) and can be set via {@link #setInitialQuery(Query)}
* method.
* <p>
* Query can be executed either on all nodes in topology using {@link IgniteCache#query(Query)}
* method, or only on the local node, if {@link Query#setLocal(boolean)} parameter is set to {@code true}.
* Note that in case query is distributed and a new node joins, it will get the remote
* filter for the query during discovery process before it actually joins topology,
* so no updates will be missed.
* <h1 class="header">Example</h1>
* As an example, suppose we have cache with {@code 'Person'} objects and we need
* to query all persons with salary above 1000.
* <p>
* Here is the {@code Person} class:
* <pre name="code" class="java">
* public class Person {
* // Name.
* private String name;
*
* // Salary.
* private double salary;
*
* ...
* }
* </pre>
* <p>
* You can create and execute continuous query like so:
* <pre name="code" class="java">
* // Create new continuous query.
* ContinuousQuery<Long, Person> qry = new ContinuousQuery<>();
*
* // Initial iteration query will return all persons with salary above 1000.
* qry.setInitialQuery(new ScanQuery<>((id, p) -> p.getSalary() > 1000));
*
*
* // Callback that is called locally when update notifications are received.
* // It simply prints out information about all created persons.
* qry.setLocalListener((evts) -> {
* for (CacheEntryEvent<? extends Long, ? extends Person> e : evts) {
* Person p = e.getValue();
*
* System.out.println(p.getFirstName() + " " + p.getLastName() + "'s salary is " + p.getSalary());
* }
* });
*
* // Continuous listener will be notified for persons with salary above 1000.
* qry.setRemoteFilter(evt -> evt.getValue().getSalary() > 1000);
*
* // Execute query and get cursor that iterates through initial data.
* QueryCursor<Cache.Entry<Long, Person>> cur = cache.query(qry);
* </pre>
* This will execute query on all nodes that have cache you are working with and
* listener will start to receive notifications for cache updates.
* <p>
* To stop receiving updates call {@link QueryCursor#close()} method:
* <pre name="code" class="java">
* cur.close();
* </pre>
* Note that this works even if you didn't provide initial query. Cursor will
* be empty in this case, but it will still unregister listeners when {@link QueryCursor#close()}
* is called.
* <p>
* {@link IgniteAsyncCallback} annotation is supported for {@link CacheEntryEventFilter}
* (see {@link #setRemoteFilterFactory(Factory)}) and {@link CacheEntryUpdatedListener}
* (see {@link #setLocalListener(CacheEntryUpdatedListener)}).
* If filter and/or listener are annotated with {@link IgniteAsyncCallback} then annotated callback
* is executed in async callback pool (see {@link IgniteConfiguration#getAsyncCallbackPoolSize()})
* and notification order is kept the same as update order for given cache key.
*
* @see IgniteAsyncCallback
* @see IgniteConfiguration#getAsyncCallbackPoolSize()
*/
public final class ContinuousQuery<K, V> extends Query<Cache.Entry<K, V>> {
/** */
private static final long serialVersionUID = 0L;
/**
* Default page size. Size of {@code 1} means that all entries
* will be sent to master node immediately (buffering is disabled).
*/
public static final int DFLT_PAGE_SIZE = 1;
/** Maximum default time interval after which buffer will be flushed (if buffering is enabled). */
public static final long DFLT_TIME_INTERVAL = 0;
/**
* Default value for automatic unsubscription flag. Remote filters
* will be unregistered by default if master node leaves topology.
*/
public static final boolean DFLT_AUTO_UNSUBSCRIBE = true;
/** Initial query. */
private Query<Cache.Entry<K, V>> initQry;
/** Local listener. */
private CacheEntryUpdatedListener<K, V> locLsnr;
/** Remote filter. */
private CacheEntryEventSerializableFilter<K, V> rmtFilter;
/** Remote filter factory. */
private Factory<? extends CacheEntryEventFilter<K, V>> rmtFilterFactory;
/** Time interval. */
private long timeInterval = DFLT_TIME_INTERVAL;
/** Automatic unsubscription flag. */
private boolean autoUnsubscribe = DFLT_AUTO_UNSUBSCRIBE;
/** Whether to notify about {@link EventType#EXPIRED} events. */
private boolean includeExpired;
/**
* Creates new continuous query.
*/
public ContinuousQuery() {
setPageSize(DFLT_PAGE_SIZE);
}
/**
* Sets initial query.
* <p>
* This query will be executed before continuous listener is registered
* which allows to iterate through entries which already existed at the
* time continuous query is executed.
*
* @param initQry Initial query.
* @return {@code this} for chaining.
*/
public ContinuousQuery<K, V> setInitialQuery(Query<Cache.Entry<K, V>> initQry) {
this.initQry = initQry;
return this;
}
/**
* Gets initial query.
*
* @return Initial query.
*/
public Query<Cache.Entry<K, V>> getInitialQuery() {
return initQry;
}
/**
* Sets local callback. This callback is called only in local node when new updates are received.
* <p>
* The callback predicate accepts ID of the node from where updates are received and collection
* of received entries. Note that for removed entries value will be {@code null}.
* <p>
* If the predicate returns {@code false}, query execution will be cancelled.
* <p>
* <b>WARNING:</b> all operations that involve any kind of JVM-local or distributed locking (e.g.,
* synchronization or transactional cache operations), should be executed asynchronously without
* blocking the thread that called the callback. Otherwise, you can get deadlocks.
* <p>
* If local listener are annotated with {@link IgniteAsyncCallback} then it is executed in async callback pool
* (see {@link IgniteConfiguration#getAsyncCallbackPoolSize()}) that allow to perform a cache operations.
*
* @param locLsnr Local callback.
* @return {@code this} for chaining.
* @see IgniteAsyncCallback
* @see IgniteConfiguration#getAsyncCallbackPoolSize()
*/
public ContinuousQuery<K, V> setLocalListener(CacheEntryUpdatedListener<K, V> locLsnr) {
this.locLsnr = locLsnr;
return this;
}
/**
* Gets local listener.
*
* @return Local listener.
*/
public CacheEntryUpdatedListener<K, V> getLocalListener() {
return locLsnr;
}
/**
* Sets optional key-value filter. This filter is called before entry is sent to the master node.
* <p>
* <b>WARNING:</b> all operations that involve any kind of JVM-local or distributed locking
* (e.g., synchronization or transactional cache operations), should be executed asynchronously
* without blocking the thread that called the filter. Otherwise, you can get deadlocks.
* <p>
* If remote filter are annotated with {@link IgniteAsyncCallback} then it is executed in async callback
* pool (see {@link IgniteConfiguration#getAsyncCallbackPoolSize()}) that allow to perform a cache operations.
*
* @param rmtFilter Key-value filter.
* @return {@code this} for chaining.
*
* @deprecated Use {@link #setRemoteFilterFactory(Factory)} instead.
* @see IgniteAsyncCallback
* @see IgniteConfiguration#getAsyncCallbackPoolSize()
*/
@Deprecated
public ContinuousQuery<K, V> setRemoteFilter(CacheEntryEventSerializableFilter<K, V> rmtFilter) {
this.rmtFilter = rmtFilter;
return this;
}
/**
* Gets remote filter.
*
* @return Remote filter.
*/
public CacheEntryEventSerializableFilter<K, V> getRemoteFilter() {
return rmtFilter;
}
/**
* Sets optional key-value filter factory. This factory produces filter is called before entry is
* sent to the master node.
* <p>
* <b>WARNING:</b> all operations that involve any kind of JVM-local or distributed locking
* (e.g., synchronization or transactional cache operations), should be executed asynchronously
* without blocking the thread that called the filter. Otherwise, you can get deadlocks.
* <p>
* If remote filter are annotated with {@link IgniteAsyncCallback} then it is executed in async callback
* pool (see {@link IgniteConfiguration#getAsyncCallbackPoolSize()}) that allow to perform a cache operations.
*
* @param rmtFilterFactory Key-value filter factory.
* @return {@code this} for chaining.
* @see IgniteAsyncCallback
* @see IgniteConfiguration#getAsyncCallbackPoolSize()
*/
public ContinuousQuery<K, V> setRemoteFilterFactory(
Factory<? extends CacheEntryEventFilter<K, V>> rmtFilterFactory) {
this.rmtFilterFactory = rmtFilterFactory;
return this;
}
/**
* Gets remote filter.
*
* @return Remote filter.
*/
public Factory<? extends CacheEntryEventFilter<K, V>> getRemoteFilterFactory() {
return rmtFilterFactory;
}
/**
* Sets time interval.
* <p>
* When a cache update happens, entry is first put into a buffer. Entries from buffer will
* be sent to the master node only if the buffer is full (its size can be provided via {@link #setPageSize(int)}
* method) or time provided via this method is exceeded.
* <p>
* Default time interval is {@code 0} which means that
* time check is disabled and entries will be sent only when buffer is full.
*
* @param timeInterval Time interval.
* @return {@code this} for chaining.
*/
public ContinuousQuery<K, V> setTimeInterval(long timeInterval) {
if (timeInterval < 0)
throw new IllegalArgumentException("Time interval can't be negative.");
this.timeInterval = timeInterval;
return this;
}
/**
* Gets time interval.
*
* @return Time interval.
*/
public long getTimeInterval() {
return timeInterval;
}
/**
* Sets automatic unsubscribe flag.
* <p>
* This flag indicates that query filters on remote nodes should be
* automatically unregistered if master node (node that initiated the query) leaves topology. If this flag is
* {@code false}, filters will be unregistered only when the query is cancelled from master node, and won't ever be
* unregistered if master node leaves grid.
* <p>
* Default value for this flag is {@code true}.
*
* @param autoUnsubscribe Automatic unsubscription flag.
* @return {@code this} for chaining.
*/
public ContinuousQuery<K, V> setAutoUnsubscribe(boolean autoUnsubscribe) {
this.autoUnsubscribe = autoUnsubscribe;
return this;
}
/**
* Gets automatic unsubscription flag value.
*
* @return Automatic unsubscription flag.
*/
public boolean isAutoUnsubscribe() {
return autoUnsubscribe;
}
/**
* Sets the flag value defining whether to notify about {@link EventType#EXPIRED} events.
* If {@code true}, then the remote listener will get notifications about entries
* expired in cache. Otherwise, only {@link EventType#CREATED}, {@link EventType#UPDATED}
* and {@link EventType#REMOVED} events will be fired in the remote listener.
* <p>
* This flag is {@code false} by default, so {@link EventType#EXPIRED} events are disabled.
*
* @param includeExpired Whether to notify about {@link EventType#EXPIRED} events.
*/
public void setIncludeExpired(boolean includeExpired) {
this.includeExpired = includeExpired;
}
/**
* Gets the flag value defining whether to notify about {@link EventType#EXPIRED} events.
*
* @return Whether to notify about {@link EventType#EXPIRED} events.
*/
public boolean isIncludeExpired() {
return includeExpired;
}
/** {@inheritDoc} */
@Override public ContinuousQuery<K, V> setPageSize(int pageSize) {
return (ContinuousQuery<K, V>)super.setPageSize(pageSize);
}
/** {@inheritDoc} */
@Override public ContinuousQuery<K, V> setLocal(boolean loc) {
return (ContinuousQuery<K, V>)super.setLocal(loc);
}
}