/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* 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 io.janusproject.kernel.services.hazelcast;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.base.Objects;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Multiset;
import com.google.common.util.concurrent.Service;
import com.google.inject.Inject;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.MapEvent;
import com.hazelcast.core.MultiMap;
import com.hazelcast.map.listener.MapListener;
import io.janusproject.services.AbstractDependentService;
import io.janusproject.services.distributeddata.DMap;
import io.janusproject.services.distributeddata.DMapListener;
import io.janusproject.services.distributeddata.DMultiMap;
import io.janusproject.services.distributeddata.DistributedDataStructureService;
/**
* Service based on Hazelcast that permits to manage data structures that are shared over a network.
*
* <p>This service is thread-safe.
*
* @author $Author: srodriguez$
* @author $Author: ngaud$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class HazelcastDistributedDataStructureService extends AbstractDependentService
implements DistributedDataStructureService {
@Inject
private HazelcastInstance hazelcastInstance;
@Override
public final Class<? extends Service> getServiceType() {
return DistributedDataStructureService.class;
}
/**
* Change the hazelcast instance used by this factory.
*
* @param hazelcastInstance - reference to the Hazelcast engine.
*/
void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
this.hazelcastInstance = hazelcastInstance;
}
@Override
protected void doStart() {
notifyStarted();
}
@Override
protected void doStop() {
notifyStopped();
}
@Override
public <K, V> DMap<K, V> getMap(String name) {
final IMap<K, V> map = this.hazelcastInstance.getMap(name);
if (map != null) {
return new MapView<>(name, map);
}
return null;
}
@Override
public <K, V> DMap<K, V> getMap(String name, Comparator<? super K> comparator) {
final IMap<K, V> map = this.hazelcastInstance.getMap(name);
if (map != null) {
return new MapView<>(name, map);
}
return null;
}
@Override
public <K, V> DMultiMap<K, V> getMultiMap(String name) {
final MultiMap<K, V> map = this.hazelcastInstance.getMultiMap(name);
if (map != null) {
return new MultiMapView<>(name, map);
}
return null;
}
@Override
public <K, V> DMultiMap<K, V> getMultiMap(String name, Comparator<? super K> comparator) {
final MultiMap<K, V> map = this.hazelcastInstance.getMultiMap(name);
if (map != null) {
return new MultiMapView<>(name, map);
}
return null;
}
/**
* @param <K> - type of the keys.
* @param <V> - type of the values.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static final class MapView<K, V> implements DMap<K, V> {
private final String name;
private final IMap<K, V> map;
MapView(String name, IMap<K, V> map) {
assert map != null;
this.name = name;
this.map = map;
}
@Override
public boolean isBackedCollection() {
return false;
}
@Override
public String getName() {
return this.name;
}
@Override
public void clear() {
this.map.clear();
}
@Override
public boolean containsKey(Object arg0) {
return this.map.containsKey(arg0);
}
@Override
public boolean containsValue(Object arg0) {
return this.map.containsValue(arg0);
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return this.map.entrySet();
}
@Override
public V get(Object arg0) {
return this.map.get(arg0);
}
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
@Override
public Set<K> keySet() {
return this.map.keySet();
}
@Override
public V put(K arg0, V arg1) {
return this.map.put(arg0, arg1);
}
@Override
public void putAll(Map<? extends K, ? extends V> arg0) {
this.map.putAll(arg0);
}
@Override
public V remove(Object arg0) {
return this.map.remove(arg0);
}
@Override
public int size() {
return this.map.size();
}
@Override
public Collection<V> values() {
return this.map.values();
}
@Override
public V putIfAbsent(K key, V value) {
return this.map.putIfAbsent(key, value);
}
@Override
public void addDMapListener(DMapListener<? super K, ? super V> listener) {
final EntryListenerWrapper<K, V> w = new EntryListenerWrapper<>(listener);
final String k = this.map.addEntryListener((MapListener) w, true);
w.setHazelcastListener(k);
}
@Override
public void removeDMapListener(DMapListener<? super K, ? super V> listener) {
if (listener instanceof EntryListenerWrapper) {
final String k = ((EntryListenerWrapper<?, ?>) listener).getHazelcastListener();
if (k != null) {
this.map.removeEntryListener(k);
}
}
}
}
/**
* @param <K> - type of the keys.
* @param <V> - type of the values.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@SuppressWarnings("unchecked")
private static final class MultiMapView<K, V> implements DMultiMap<K, V> {
private final String name;
private final MultiMap<K, V> map;
MultiMapView(String name, MultiMap<K, V> map) {
this.name = name;
assert map != null;
this.map = map;
}
@Override
public boolean isBackedCollection() {
return false;
}
@Override
public String getName() {
return this.name;
}
@Override
public boolean put(K key, V value) {
return this.map.put(key, value);
}
@Override
public Collection<V> get(K key) {
return this.map.get(key);
}
@Override
public boolean remove(Object key, Object value) {
return this.map.remove(key, value);
}
@Override
public Collection<V> removeAll(Object key) {
return this.map.remove(key);
}
@Override
public Set<K> keySet() {
return this.map.keySet();
}
@Override
public Multiset<K> keys() {
return new SetMultiset();
}
@Override
public Collection<V> values() {
return this.map.values();
}
@Override
public Collection<Entry<K, V>> entries() {
return this.map.entrySet();
}
@Override
public boolean containsKey(Object key) {
try {
return this.map.containsKey((K) key);
} catch (ClassCastException exception) {
return false;
}
}
@Override
public boolean containsValue(Object value) {
return this.map.containsValue(value);
}
@Override
public boolean containsEntry(Object key, Object value) {
try {
return this.map.containsEntry((K) key, (V) value);
} catch (ClassCastException exception) {
return false;
}
}
@Override
public int size() {
return this.map.size();
}
@Override
public void clear() {
this.map.clear();
}
@Override
public int valueCount(K key) {
return this.map.valueCount(key);
}
@Override
public void addDMapListener(DMapListener<? super K, ? super V> listener) {
final EntryListenerWrapper<K, V> w = new EntryListenerWrapper<>(listener);
final String k = this.map.addEntryListener(w, true);
w.setHazelcastListener(k);
}
@Override
public void removeDMapListener(DMapListener<? super K, ? super V> listener) {
if (listener instanceof EntryListenerWrapper) {
final String k = ((EntryListenerWrapper<?, ?>) listener).getHazelcastListener();
if (k != null) {
this.map.removeEntryListener(k);
}
}
}
@Override
public boolean isEmpty() {
return this.map.size() == 0;
}
@Override
public Collection<V> replaceValues(K key, Iterable<? extends V> values) {
final Collection<V> oldValues = this.map.remove(key);
for (final V value : values) {
this.map.put(key, value);
}
return oldValues;
}
@Override
public boolean putAll(Multimap<? extends K, ? extends V> multimap) {
boolean changed = false;
for (final Entry<? extends K, ? extends V> value : multimap.entries()) {
changed = this.map.put(value.getKey(), value.getValue()) && changed;
}
return changed;
}
@Override
public boolean putAll(K key, Iterable<? extends V> values) {
boolean changed = false;
for (final V value : values) {
changed = this.map.put(key, value) && changed;
}
return changed;
}
@Override
public Map<K, Collection<V>> asMap() {
return Multimaps.asMap(this);
}
/**
* Internal implementation of a multiset.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class SetMultiset implements Multiset<K> {
/**
* Construct.
*/
SetMultiset() {
//
}
@Override
public int size() {
return MultiMapView.this.size();
}
@Override
public boolean isEmpty() {
return MultiMapView.this.isEmpty();
}
@Override
public Object[] toArray() {
final Object[] tab = new Object[MultiMapView.this.size()];
int i = 0;
for (final Map.Entry<K, ?> e : MultiMapView.this.entries()) {
tab[i] = e.getKey();
++i;
}
return tab;
}
@Override
public <T> T[] toArray(T[] array) {
T[] tab = array;
if (tab == null || tab.length < MultiMapView.this.size()) {
tab = (T[]) new Object[MultiMapView.this.size()];
int i = 0;
for (final Map.Entry<K, ?> e : MultiMapView.this.entries()) {
tab[i] = (T) e.getKey();
++i;
}
}
return tab;
}
@Override
public void clear() {
MultiMapView.this.clear();
}
@Override
public int count(Object element) {
int c = 0;
for (final Map.Entry<K, ?> e : MultiMapView.this.entries()) {
if (Objects.equal(element, e.getKey())) {
++c;
}
}
return c;
}
@Override
public int add(K element, int occurrences) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(K element) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object element) {
final Collection<?> values = MultiMapView.this.removeAll(element);
return values != null && !values.isEmpty();
}
@Override
public int remove(Object element, int occurrences) {
if (occurrences < 0) {
throw new IllegalArgumentException();
}
try {
final Collection<?> values = MultiMapView.this.get((K) element);
final int old = values.size();
final Iterator<?> iterator = values.iterator();
for (int i = 0; i < occurrences && iterator.hasNext(); ++i) {
iterator.next();
iterator.remove();
}
return old;
} catch (ClassCastException exception) {
return 0;
}
}
@Override
public int setCount(K element, int count) {
if (count < 0) {
throw new IllegalArgumentException();
}
final Collection<?> values = MultiMapView.this.get(element);
final int old = values.size();
if (count > old) {
throw new UnsupportedOperationException();
}
try {
final Iterator<?> iterator = values.iterator();
final int toRemove = old - count;
for (int i = 0; i < toRemove && iterator.hasNext(); ++i) {
iterator.next();
iterator.remove();
}
return old;
} catch (ClassCastException exception) {
return 0;
}
}
@Override
public boolean setCount(K element, int oldCount, int newCount) {
if (oldCount < 0 || newCount < 0) {
throw new IllegalArgumentException();
}
final Collection<?> values = MultiMapView.this.get(element);
final int old = values.size();
if (oldCount == old) {
if (newCount > old) {
throw new UnsupportedOperationException();
}
try {
final Iterator<?> iterator = values.iterator();
final int toRemove = old - newCount;
if (toRemove > 0) {
for (int i = 0; i < toRemove && iterator.hasNext(); ++i) {
iterator.next();
iterator.remove();
}
return true;
}
} catch (ClassCastException exception) {
//
}
}
return false;
}
@Override
public boolean addAll(Collection<? extends K> collection) {
throw new UnsupportedOperationException();
}
@Override
public Set<K> elementSet() {
return MultiMapView.this.keySet();
}
@Override
public Set<com.google.common.collect.Multiset.Entry<K>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<K> iterator() {
final Iterator<Map.Entry<K, V>> entries = MultiMapView.this.entries().iterator();
return new Iterator<K>() {
@Override
public boolean hasNext() {
return entries.hasNext();
}
@Override
public K next() {
return entries.next().getKey();
}
@Override
public void remove() {
entries.remove();
}
};
}
@Override
public boolean contains(Object element) {
return MultiMapView.this.containsKey(element);
}
@Override
public boolean containsAll(Collection<?> elements) {
return MultiMapView.this.keySet().containsAll(elements);
}
@Override
public boolean removeAll(Collection<?> collection) {
return MultiMapView.this.keySet().removeAll(collection);
}
@Override
public boolean retainAll(Collection<?> collection) {
return MultiMapView.this.keySet().retainAll(collection);
}
}
}
/**
* Listener on the Hazelcast map events.
*
* @param <K> type of the keys.
* @param <V> type of the values.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static class EntryListenerWrapper<K, V> implements EntryListener<K, V> {
private final DMapListener<? super K, ? super V> dmapListener;
private String key;
EntryListenerWrapper(DMapListener<? super K, ? super V> listener) {
this.dmapListener = listener;
}
/**
* Replies the Hazelcast listener associated to this object.
*
* @return the hazelcast listener.
*/
public String getHazelcastListener() {
return this.key;
}
/**
* Replies the Hazelcast listener associated to this object.
*
* @param hazelcastListener - the hazelcast listener.
*/
public void setHazelcastListener(String hazelcastListener) {
this.key = hazelcastListener;
}
@Override
public void entryAdded(EntryEvent<K, V> event) {
this.dmapListener.entryAdded(event.getKey(), event.getValue());
}
@Override
public void entryEvicted(EntryEvent<K, V> event) {
if (event.getValue() != null) {
this.dmapListener.entryRemoved(event.getKey(), event.getValue());
} else {
this.dmapListener.entryRemoved(event.getKey(), event.getOldValue());
}
}
@Override
public void entryRemoved(EntryEvent<K, V> event) {
if (event.getValue() != null) {
this.dmapListener.entryRemoved(event.getKey(), event.getValue());
} else {
this.dmapListener.entryRemoved(event.getKey(), event.getOldValue());
}
}
@Override
public void entryUpdated(EntryEvent<K, V> event) {
this.dmapListener.entryUpdated(event.getKey(), event.getValue());
}
@Override
public void mapCleared(MapEvent event) {
this.dmapListener.mapCleared(true);
}
@Override
public void mapEvicted(MapEvent event) {
this.dmapListener.mapCleared(false);
}
}
}