/*
* 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.querycache.subscriber;
import com.hazelcast.core.IMapEvent;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.map.EventLostEvent;
import com.hazelcast.map.impl.EntryEventFilter;
import com.hazelcast.map.impl.ListenerAdapter;
import com.hazelcast.map.impl.MapService;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.event.EventData;
import com.hazelcast.map.impl.querycache.QueryCacheEventService;
import com.hazelcast.map.impl.querycache.QueryCacheListenerAdapter;
import com.hazelcast.map.impl.querycache.event.LocalCacheWideEventData;
import com.hazelcast.map.impl.querycache.event.LocalEntryEventData;
import com.hazelcast.map.listener.MapListener;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.query.impl.QueryEntry;
import com.hazelcast.query.impl.QueryableEntry;
import com.hazelcast.query.impl.getters.Extractors;
import com.hazelcast.spi.EventFilter;
import com.hazelcast.spi.EventRegistration;
import com.hazelcast.spi.EventService;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.impl.eventservice.impl.Registration;
import com.hazelcast.spi.impl.eventservice.impl.TrueEventFilter;
import com.hazelcast.spi.serialization.SerializationService;
import java.util.Collection;
import static com.hazelcast.map.impl.querycache.ListenerRegistrationHelper.generateListenerName;
import static com.hazelcast.map.impl.querycache.subscriber.QueryCacheEventListenerAdapters.createQueryCacheListenerAdaptor;
import static com.hazelcast.util.Preconditions.checkHasText;
import static com.hazelcast.util.Preconditions.checkNotNull;
/**
* Node side event service implementation for query cache.
*
* @see QueryCacheEventService
*/
public class NodeQueryCacheEventService implements QueryCacheEventService<EventData> {
private final MapServiceContext mapServiceContext;
public NodeQueryCacheEventService(MapServiceContext mapServiceContext) {
this.mapServiceContext = mapServiceContext;
}
// TODO not used order key
@Override
public void publish(String mapName, String cacheName, EventData eventData, int orderKey) {
checkHasText(mapName, "mapName");
checkHasText(cacheName, "cacheName");
checkNotNull(eventData, "eventData cannot be null");
publishLocalEvent(mapName, cacheName, eventData);
}
@Override
public String addListener(String mapName, String cacheName, MapListener listener) {
return addListener(mapName, cacheName, listener, null);
}
@Override
public String listenPublisher(String mapName, String cacheName, ListenerAdapter listenerAdapter) {
String listenerName = generateListenerName(mapName, cacheName);
return mapServiceContext.addListenerAdapter(listenerName, listenerAdapter);
}
@Override
public boolean removePublisherListener(String mapName, String listenerId) {
return mapServiceContext.removeEventListener(mapName, listenerId);
}
@Override
public String addListener(String mapName, String cacheName, MapListener listener, EventFilter filter) {
checkHasText(mapName, "mapName");
checkHasText(cacheName, "cacheName");
checkNotNull(listener, "listener cannot be null");
ListenerAdapter queryCacheListenerAdaptor = createQueryCacheListenerAdaptor(listener);
ListenerAdapter listenerAdaptor = new SimpleQueryCacheListenerAdapter(queryCacheListenerAdaptor);
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
EventService eventService = nodeEngine.getEventService();
String listenerName = generateListenerName(mapName, cacheName);
EventRegistration registration;
if (filter == null) {
registration = eventService.registerLocalListener(MapService.SERVICE_NAME,
listenerName, listenerAdaptor);
} else {
registration = eventService.registerLocalListener(MapService.SERVICE_NAME,
listenerName, filter, listenerAdaptor);
}
return registration.getId();
}
@Override
public boolean removeListener(String mapName, String cacheName, String id) {
String listenerName = generateListenerName(mapName, cacheName);
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
EventService eventService = nodeEngine.getEventService();
return eventService.deregisterListener(MapService.SERVICE_NAME, listenerName, id);
}
@Override
public boolean hasListener(String mapName, String cacheName) {
String listenerName = generateListenerName(mapName, cacheName);
Collection<EventRegistration> eventRegistrations = getRegistrations(listenerName);
if (eventRegistrations.isEmpty()) {
return false;
}
for (EventRegistration eventRegistration : eventRegistrations) {
Registration registration = (Registration) eventRegistration;
Object listener = registration.getListener();
if (listener instanceof QueryCacheListenerAdapter) {
return true;
}
}
return false;
}
// TODO needs refactoring.
private void publishLocalEvent(String mapName, String cacheName, Object eventData) {
String listenerName = generateListenerName(mapName, cacheName);
Collection<EventRegistration> eventRegistrations = getRegistrations(listenerName);
if (eventRegistrations.isEmpty()) {
return;
}
for (EventRegistration eventRegistration : eventRegistrations) {
Registration registration = (Registration) eventRegistration;
Object listener = registration.getListener();
if (!(listener instanceof QueryCacheListenerAdapter)) {
continue;
}
Object eventDataToPublish = eventData;
int orderKey = -1;
if (eventDataToPublish instanceof LocalCacheWideEventData) {
orderKey = listenerName.hashCode();
} else if (eventDataToPublish instanceof LocalEntryEventData) {
LocalEntryEventData localEntryEventData = (LocalEntryEventData) eventDataToPublish;
if (localEntryEventData.getEventType() != EventLostEvent.EVENT_TYPE) {
EventFilter filter = registration.getFilter();
if (!canPassFilter(localEntryEventData, filter)) {
continue;
} else {
boolean includeValue = isIncludeValue(filter);
eventDataToPublish = includeValue ? localEntryEventData : localEntryEventData.cloneWithoutValue();
Data keyData = localEntryEventData.getKeyData();
orderKey = keyData == null ? -1 : keyData.hashCode();
}
}
}
publishEventInternal(registration, eventDataToPublish, orderKey);
}
}
private boolean canPassFilter(LocalEntryEventData localEntryEventData, EventFilter filter) {
if (filter == null || filter instanceof TrueEventFilter) {
return true;
}
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
SerializationService serializationService = nodeEngine.getSerializationService();
Data keyData = localEntryEventData.getKeyData();
Object value = getValueOrOldValue(localEntryEventData);
QueryableEntry entry = new QueryEntry((InternalSerializationService) serializationService,
keyData, value, Extractors.empty());
return filter.eval(entry);
}
private boolean isIncludeValue(EventFilter filter) {
if (filter instanceof EntryEventFilter) {
return ((EntryEventFilter) filter).isIncludeValue();
}
return true;
}
private Object getValueOrOldValue(LocalEntryEventData localEntryEventData) {
Object value = localEntryEventData.getValue();
return value != null ? value : localEntryEventData.getOldValue();
}
private Collection<EventRegistration> getRegistrations(String mapName) {
MapServiceContext mapServiceContext = this.mapServiceContext;
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
EventService eventService = nodeEngine.getEventService();
return eventService.getRegistrations(MapService.SERVICE_NAME, mapName);
}
private void publishEventInternal(EventRegistration registration, Object eventData, int orderKey) {
MapServiceContext mapServiceContext = this.mapServiceContext;
NodeEngine nodeEngine = mapServiceContext.getNodeEngine();
EventService eventService = nodeEngine.getEventService();
eventService.publishEvent(MapService.SERVICE_NAME, registration, eventData, orderKey);
}
@Override
public void sendEventToSubscriber(String name, Object eventData, int orderKey) {
Collection<EventRegistration> eventRegistrations = getRegistrations(name);
if (eventRegistrations.isEmpty()) {
return;
}
for (EventRegistration eventRegistration : eventRegistrations) {
Registration registration = (Registration) eventRegistration;
Object listener = registration.getListener();
if (listener instanceof QueryCacheListenerAdapter) {
continue;
}
publishEventInternal(registration, eventData, orderKey);
}
}
/**
* Listener for a {@link com.hazelcast.map.QueryCache QueryCache}.
*
* @see com.hazelcast.core.IMap#getQueryCache(String, MapListener, com.hazelcast.query.Predicate, boolean)
*/
private static class SimpleQueryCacheListenerAdapter implements QueryCacheListenerAdapter<IMapEvent> {
private final ListenerAdapter listenerAdapter;
SimpleQueryCacheListenerAdapter(ListenerAdapter listenerAdapter) {
this.listenerAdapter = listenerAdapter;
}
@Override
public void onEvent(IMapEvent event) {
listenerAdapter.onEvent(event);
}
}
}