/*
* Copyright (c) 2008-2012, Hazel Bilisim Ltd. 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.client.impl;
import com.hazelcast.client.Call;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.Packet;
import com.hazelcast.client.ProxyHelper;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.impl.ClusterOperation;
import com.hazelcast.impl.DataAwareEntryEvent;
import com.hazelcast.impl.Keys;
import com.hazelcast.nio.Data;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import static com.hazelcast.client.IOUtil.toByte;
import static com.hazelcast.client.IOUtil.toObject;
public class EntryListenerManager {
private final Object NULL_KEY = new Object();
private final ConcurrentMap<String, ConcurrentHashMap<Object, List<EntryListenerHolder>>> entryListeners =
new ConcurrentHashMap<String, ConcurrentHashMap<Object, List<EntryListenerHolder>>>();
public synchronized void registerListener(String name, Object key, boolean includeValue, EntryListener<?, ?> entryListener) {
ConcurrentHashMap<Object, List<EntryListenerHolder>> map = entryListeners.get(name);
key = toKey(key);
if (map == null) {
map = new ConcurrentHashMap<Object, List<EntryListenerHolder>>();
final ConcurrentHashMap<Object, List<EntryListenerHolder>> map2 = entryListeners.putIfAbsent(name, map);
if (map2 != null) {
map = map2;
}
}
if (!map.contains(key)) {
map.putIfAbsent(key, new CopyOnWriteArrayList<EntryListenerHolder>());
}
map.get(key).add(new EntryListenerHolder(entryListener, includeValue));
}
private Object toKey(final Object key) {
return key != null ? key : NULL_KEY;
}
private Object fromKey(final Object key) {
return key != NULL_KEY ? key : null;
}
public synchronized void removeListener(String name, Object key, EntryListener<?, ?> entryListener) {
Map<Object, List<EntryListenerHolder>> m = entryListeners.get(name);
if (m != null) {
key = toKey(key);
List<EntryListenerHolder> list = m.get(key);
if (list != null) {
for (final Iterator<EntryListenerHolder> it = list.iterator(); it.hasNext(); ) {
final EntryListenerHolder entryListenerHolder = it.next();
if (entryListenerHolder.listener.equals(entryListener)) {
list.remove(entryListenerHolder);
}
}
if (m.get(key).isEmpty()) {
m.remove(key);
}
}
if (m.isEmpty()) {
entryListeners.remove(name);
}
}
}
public synchronized Boolean noListenerRegistered(Object key, String name, boolean includeValue) {
final Map<Object, List<EntryListenerHolder>> map = entryListeners.get(name);
key = toKey(key);
if (map == null || map.get(key) == null) {
return Boolean.TRUE;
}
for (final EntryListenerHolder holder : map.get(key)) {
if (holder.includeValue == includeValue) {
return Boolean.FALSE;
} else if (includeValue) {
return null;
}
}
return Boolean.TRUE;
}
public void notifyListeners(Packet packet) {
Object keyObj = toObject(packet.getKey());
Object value = toObject(packet.getValue());
Data newValue = null;
Data oldValue = null;
if (value instanceof Keys) {
final Keys values = (Keys) value;
final Iterator<Data> it = values.getKeys().iterator();
newValue = it.hasNext() ? new Data(it.next().buffer) : null;
oldValue = it.hasNext() ? new Data(it.next().buffer) : null;
} else {
newValue = new Data(packet.getValue());
}
final DataAwareEntryEvent event = new DataAwareEntryEvent(null,
(int) packet.getLongValue(),
packet.getName(),
new Data(packet.getKey()),
newValue,
oldValue,
true);
String name = packet.getName();
Object key = toKey(keyObj);
if (entryListeners.get(name) != null) {
notifyListeners(event, entryListeners.get(name).get(NULL_KEY));
if (key != NULL_KEY) {
notifyListeners(event, entryListeners.get(name).get(key));
}
}
}
private void notifyListeners(EntryEvent event, Collection<EntryListenerHolder> collection) {
if (collection == null) {
return;
}
EntryEvent eventNoValue = event.getValue() != null ?
new EntryEvent(event.getSource(), event.getMember(), event.getEventType().getType(), event.getKey(), null, null) :
event;
switch (event.getEventType()) {
case ADDED:
for (EntryListenerHolder holder : collection) {
holder.listener.entryAdded(holder.includeValue ? event : eventNoValue);
}
break;
case UPDATED:
for (EntryListenerHolder holder : collection) {
holder.listener.entryUpdated(holder.includeValue ? event : eventNoValue);
}
break;
case REMOVED:
for (EntryListenerHolder holder : collection) {
holder.listener.entryRemoved(holder.includeValue ? event : eventNoValue);
}
break;
case EVICTED:
for (EntryListenerHolder holder : collection) {
holder.listener.entryEvicted(holder.includeValue ? event : eventNoValue);
}
break;
}
}
public Call createNewAddListenerCall(final ProxyHelper proxyHelper, final Object key, boolean includeValue) {
Packet request = proxyHelper.createRequestPacket(ClusterOperation.ADD_LISTENER, toByte(key), null);
request.setLongValue(includeValue ? 1 : 0);
return proxyHelper.createCall(request);
}
public Collection<Call> calls(final HazelcastClient client) {
final List<Call> calls = new ArrayList<Call>();
for (final Entry<String, ConcurrentHashMap<Object, List<EntryListenerHolder>>> entry : entryListeners.entrySet()) {
final String name = entry.getKey();
final ConcurrentHashMap<Object, List<EntryListenerHolder>> value = entry.getValue();
for (final Entry<Object, List<EntryListenerHolder>> anotherEntry : value.entrySet()) {
boolean includeValue = false;
final Object key = fromKey(anotherEntry.getKey());
for (final EntryListenerHolder entryListenerHolder : anotherEntry.getValue()) {
includeValue |= entryListenerHolder.includeValue;
if (includeValue) break;
}
final ProxyHelper proxyHelper = new ProxyHelper(name, client);
calls.add(createNewAddListenerCall(proxyHelper, key, includeValue));
}
}
return calls;
}
private static class EntryListenerHolder {
private final EntryListener listener;
private boolean includeValue;
public EntryListenerHolder(EntryListener listener, boolean includeValue) {
this.listener = listener;
this.includeValue = includeValue;
}
}
}