/*
* 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.cache.impl;
import com.hazelcast.cache.ICache;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.HazelcastInstanceAware;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.nio.serialization.IdentifiedDataSerializable;
import com.hazelcast.spi.EventRegistration;
import com.hazelcast.spi.ListenerWrapperEventFilter;
import com.hazelcast.spi.NotifiableEventListener;
import com.hazelcast.spi.serialization.SerializationService;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryCreatedListener;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryExpiredListener;
import javax.cache.event.CacheEntryListener;
import javax.cache.event.CacheEntryRemovedListener;
import javax.cache.event.CacheEntryUpdatedListener;
import javax.cache.event.EventType;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
/**
* This implementation of {@link CacheEventListener} uses the adapter pattern for wrapping all cache event listener
* types into a single listener.
* <p>JCache has multiple {@link CacheEntryListener} sub-interfaces for each event type. This adapter
* implementation delegates to the correct subtype using the event type.</p>
* <p/>
* <p>Another responsibility of this implementation is filtering events by using the already configured
* event filters.</p>
*
* @param <K> the type of key.
* @param <V> the type of value.
* @see javax.cache.event.CacheEntryCreatedListener
* @see javax.cache.event.CacheEntryUpdatedListener
* @see javax.cache.event.CacheEntryRemovedListener
* @see javax.cache.event.CacheEntryExpiredListener
* @see javax.cache.event.CacheEntryEventFilter
*/
public class CacheEventListenerAdaptor<K, V>
implements CacheEventListener,
CacheEntryListenerProvider<K, V>,
NotifiableEventListener<CacheService>,
ListenerWrapperEventFilter,
IdentifiedDataSerializable {
// all fields are effectively final
private transient CacheEntryListener<K, V> cacheEntryListener;
private transient CacheEntryCreatedListener cacheEntryCreatedListener;
private transient CacheEntryRemovedListener cacheEntryRemovedListener;
private transient CacheEntryUpdatedListener cacheEntryUpdatedListener;
private transient CacheEntryExpiredListener cacheEntryExpiredListener;
private transient CacheEntryEventFilter<? super K, ? super V> filter;
private boolean isOldValueRequired;
private transient SerializationService serializationService;
private transient ICache<K, V> source;
public CacheEventListenerAdaptor() {
}
public CacheEventListenerAdaptor(ICache<K, V> source,
CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration,
SerializationService serializationService,
HazelcastInstance hazelcastInstance) {
this.source = source;
this.serializationService = serializationService;
this.cacheEntryListener = createCacheEntryListener(cacheEntryListenerConfiguration, hazelcastInstance);
if (cacheEntryListener instanceof CacheEntryCreatedListener) {
this.cacheEntryCreatedListener = (CacheEntryCreatedListener) cacheEntryListener;
} else {
this.cacheEntryCreatedListener = null;
}
if (cacheEntryListener instanceof CacheEntryRemovedListener) {
this.cacheEntryRemovedListener = (CacheEntryRemovedListener) cacheEntryListener;
} else {
this.cacheEntryRemovedListener = null;
}
if (cacheEntryListener instanceof CacheEntryUpdatedListener) {
this.cacheEntryUpdatedListener = (CacheEntryUpdatedListener) cacheEntryListener;
} else {
this.cacheEntryUpdatedListener = null;
}
if (cacheEntryListener instanceof CacheEntryExpiredListener) {
this.cacheEntryExpiredListener = (CacheEntryExpiredListener) cacheEntryListener;
} else {
this.cacheEntryExpiredListener = null;
}
injectDependencies(cacheEntryListener, hazelcastInstance);
Factory<CacheEntryEventFilter<? super K, ? super V>> filterFactory =
cacheEntryListenerConfiguration.getCacheEntryEventFilterFactory();
if (filterFactory != null) {
this.filter = filterFactory.create();
} else {
this.filter = null;
}
injectDependencies(filter, hazelcastInstance);
this.isOldValueRequired = cacheEntryListenerConfiguration.isOldValueRequired();
}
private CacheEntryListener<K, V> createCacheEntryListener(
CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration,
HazelcastInstance hazelcastInstance) {
Factory<CacheEntryListener<? super K, ? super V>> cacheEntryListenerFactory =
cacheEntryListenerConfiguration.getCacheEntryListenerFactory();
injectDependencies(cacheEntryListenerFactory, hazelcastInstance);
return (CacheEntryListener<K, V>) cacheEntryListenerFactory.create();
}
private void injectDependencies(Object obj, HazelcastInstance hazelcastInstance) {
if (obj instanceof HazelcastInstanceAware) {
((HazelcastInstanceAware) obj).setHazelcastInstance(hazelcastInstance);
}
}
@Override
public CacheEntryListener<K, V> getCacheEntryListener() {
return cacheEntryListener;
}
@Override
public void handleEvent(Object eventObject) {
if (eventObject instanceof CacheEventSet) {
CacheEventSet cacheEventSet = (CacheEventSet) eventObject;
try {
if (cacheEventSet.getEventType() != CacheEventType.COMPLETED) {
handleEvent(cacheEventSet.getEventType().getType(), cacheEventSet.getEvents());
}
} finally {
((CacheSyncListenerCompleter) source).countDownCompletionLatch(cacheEventSet.getCompletionId());
}
}
}
private void handleEvent(int type, Collection<CacheEventData> keys) {
final Iterable<CacheEntryEvent<? extends K, ? extends V>> cacheEntryEvent = createCacheEntryEvent(keys);
CacheEventType eventType = CacheEventType.getByType(type);
switch (eventType) {
case CREATED:
if (this.cacheEntryCreatedListener != null) {
this.cacheEntryCreatedListener.onCreated(cacheEntryEvent);
}
break;
case UPDATED:
if (this.cacheEntryUpdatedListener != null) {
this.cacheEntryUpdatedListener.onUpdated(cacheEntryEvent);
}
break;
case REMOVED:
if (this.cacheEntryRemovedListener != null) {
this.cacheEntryRemovedListener.onRemoved(cacheEntryEvent);
}
break;
case EXPIRED:
if (this.cacheEntryExpiredListener != null) {
this.cacheEntryExpiredListener.onExpired(cacheEntryEvent);
}
break;
default:
throw new IllegalArgumentException("Invalid event type: " + eventType.name());
}
}
private Iterable<CacheEntryEvent<? extends K, ? extends V>> createCacheEntryEvent(Collection<CacheEventData> keys) {
HashSet<CacheEntryEvent<? extends K, ? extends V>> evt = new HashSet<CacheEntryEvent<? extends K, ? extends V>>();
for (CacheEventData cacheEventData : keys) {
final EventType eventType = CacheEventType.convertToEventType(cacheEventData.getCacheEventType());
final K key = toObject(cacheEventData.getDataKey());
final V newValue = toObject(cacheEventData.getDataValue());
final V oldValue;
if (isOldValueRequired) {
oldValue = toObject(cacheEventData.getDataOldValue());
} else {
oldValue = null;
}
final CacheEntryEventImpl<K, V> event =
new CacheEntryEventImpl<K, V>(source, eventType, key, newValue, oldValue);
if (filter == null || filter.evaluate(event)) {
evt.add(event);
}
}
return evt;
}
private <T> T toObject(Data data) {
return serializationService.toObject(data);
}
public void handle(int type, Collection<CacheEventData> keys, int completionId) {
try {
if (CacheEventType.getByType(type) != CacheEventType.COMPLETED) {
handleEvent(type, keys);
}
} finally {
((CacheSyncListenerCompleter) source).countDownCompletionLatch(completionId);
}
}
@Override
public void onRegister(CacheService cacheService, String serviceName,
String topic, EventRegistration registration) {
CacheContext cacheContext = cacheService.getOrCreateCacheContext(topic);
cacheContext.increaseCacheEntryListenerCount();
}
@Override
public void onDeregister(CacheService cacheService, String serviceName,
String topic, EventRegistration registration) {
CacheContext cacheContext = cacheService.getOrCreateCacheContext(topic);
cacheContext.decreaseCacheEntryListenerCount();
}
@Override
public boolean eval(Object event) {
return true;
}
@Override
public Object getListener() {
return this;
}
@Override
public int getFactoryId() {
return CacheDataSerializerHook.F_ID;
}
@Override
public int getId() {
return CacheDataSerializerHook.CACHE_EVENT_LISTENER_ADAPTOR;
}
@Override
public void writeData(ObjectDataOutput out)
throws IOException {
}
@Override
public void readData(ObjectDataInput in)
throws IOException {
}
}