/*
*
* * JBoss, Home of Professional Open Source.
* * Copyright 2011, Red Hat, Inc., and individual contributors
* * as indicated by the @author tags. See the copyright.txt file in the
* * distribution for a full listing of individual contributors.
* *
* * This is free software; you can redistribute it and/or modify it
* * under the terms of the GNU Lesser General Public License as
* * published by the Free Software Foundation; either version 2.1 of
* * the License, or (at your option) any later version.
* *
* * This software is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* * Lesser General Public License for more details.
* *
* * You should have received a copy of the GNU Lesser General Public
* * License along with this software; if not, write to the Free
* * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.jboss.capedwarf.memcache;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.memcache.ErrorHandler;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.InvalidValueException;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.Stats;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
import org.jboss.capedwarf.common.app.Application;
import org.jboss.capedwarf.common.infinispan.InfinispanUtils;
import org.jboss.capedwarf.common.infinispan.WrapperTxCallable;
import org.jboss.capedwarf.shared.config.CacheName;
/**
* @author <a href="mailto:marko.luksa@gmail.com">Marko Luksa</a>
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
*/
public class CapedwarfMemcacheService implements MemcacheService {
protected static final Logger log = Logger.getLogger(CapedwarfMemcacheService.class.getName());
private static final SetPolicy DEFAULT_SET_POLICY = SetPolicy.SET_ALWAYS;
protected final Cache<NamespacedKey, Object> cache;
private String namespace;
private ErrorHandler errorHandler;
public CapedwarfMemcacheService() {
this(null);
}
public CapedwarfMemcacheService(String namespace) {
setNamespace(namespace);
this.cache = InfinispanUtils.getCache(Application.getAppId(), CacheName.MEMCACHE);
}
protected NamespacedMarker toMarker(Object key) {
String namespace = getNamespace() == null ? NamespaceManager.get() : getNamespace();
return new NamespacedMarker(namespace == null ? "" : namespace, key);
}
protected void putMarker(Object key, Object value, long millisNoReAdd) {
cache.put(toMarker(key), value, millisNoReAdd, TimeUnit.MILLISECONDS);
}
protected boolean hasMarker(Object key) {
return cache.containsKey(toMarker(key));
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
if (namespace != null) {
NamespaceManager.validateNamespace(namespace);
}
this.namespace = namespace;
}
public Object get(Object key) {
return cache.get(namespacedKey(key));
}
public IdentifiableValue getIdentifiable(final Object key) {
return (contains(key)) ? new MyIdentifiableValue(get(key)) : null;
}
public <T> Map<T, IdentifiableValue> getIdentifiables(Collection<T> keys) {
Map<T, IdentifiableValue> map = new HashMap<T, IdentifiableValue>();
for (T key : keys) {
final IdentifiableValue identifiable = getIdentifiable(key);
if (identifiable != null) {
map.put(key, identifiable);
}
}
return map;
}
public boolean contains(Object key) {
return cache.containsKey(namespacedKey(key));
}
public <T> Map<T, Object> getAll(Collection<T> keys) {
// TODO: batching
Map<T, Object> map = new HashMap<T, Object>();
for (T key : keys) {
Object value = get(key);
if (value != null) {
map.put(key, value);
}
}
return map;
}
public void put(Object key, Object value) {
put(key, value, null);
}
public void put(Object key, Object value, Expiration expiration) {
put(key, value, expiration, DEFAULT_SET_POLICY);
}
public boolean put(Object key, Object value, Expiration expiration, SetPolicy policy) {
NamespacedKey namespacedKey = namespacedKey(key);
switch (policy) {
case SET_ALWAYS: {
cache.getAdvancedCache()
.withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP)
.put(namespacedKey, value, toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
return true;
}
case ADD_ONLY_IF_NOT_PRESENT: {
if (hasMarker(key)) {
return false;
} else {
Object previousValue = cache.putIfAbsent(namespacedKey, value, toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
return previousValue == null;
}
}
case REPLACE_ONLY_IF_PRESENT: {
Object previousValue = cache.replace(namespacedKey, value, toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
return previousValue != null;
}
default:
throw new IllegalArgumentException("Unsupported policy " + policy);
}
}
public <T> Set<T> putIfUntouched(Map<T, CasValues> values) {
return putIfUntouched(values, null);
}
public <T> Set<T> putIfUntouched(Map<T, CasValues> values, Expiration expiration) {
// TODO: cache.startBatch();
Set<T> set = new HashSet<T>();
for (Map.Entry<T, CasValues> entry : values.entrySet()) {
T key = entry.getKey();
CasValues casValues = entry.getValue();
Expiration actualExpiration = casValues.getExipration() == null ? expiration : casValues.getExipration();
boolean stored = putIfUntouched(key, casValues.getOldValue(), casValues.getNewValue(), actualExpiration);
if (stored) {
set.add(key);
}
}
return set;
}
public boolean putIfUntouched(Object key, IdentifiableValue oldValue, Object newValue) {
return putIfUntouched(key, oldValue, newValue, null);
}
public boolean putIfUntouched(Object key, IdentifiableValue oldValue, Object newValue, Expiration expiration) {
return cache.replace(namespacedKey(key), oldValue.getValue(), newValue, toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
}
public void putAll(Map<?, ?> map) {
putAll(map, null);
}
public void putAll(Map<?, ?> map, Expiration expiration) {
putAll(map, expiration, DEFAULT_SET_POLICY);
}
public <T> Set<T> putAll(Map<T, ?> map, Expiration expiration, SetPolicy policy) {
// TODO: cache.startBatch();
switch (policy) {
case SET_ALWAYS:
cache.getAdvancedCache()
.withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP)
.putAll(toNamespacedMap(map), toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
return map.keySet();
case ADD_ONLY_IF_NOT_PRESENT:
Set<T> addedKeys = new HashSet<T>();
for (Map.Entry<T, ?> entry : map.entrySet()) {
final Object key = entry.getKey();
if (hasMarker(key) == false) {
Object previousValue = cache.putIfAbsent(namespacedKey(key), entry.getValue(), toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
if (previousValue == null) {
addedKeys.add(entry.getKey());
}
}
}
return addedKeys;
case REPLACE_ONLY_IF_PRESENT:
Set<T> replacedKeys = new HashSet<T>();
for (Map.Entry<T, ?> entry : map.entrySet()) {
Object previousValue = cache.replace(namespacedKey(entry.getKey()), entry.getValue(), toLifespanMillis(expiration), TimeUnit.MILLISECONDS);
if (previousValue != null) {
replacedKeys.add(entry.getKey());
}
}
return replacedKeys;
default:
throw new IllegalArgumentException("Unsupported policy " + policy);
}
}
public boolean delete(Object key) {
return delete(key, 0L);
}
public boolean delete(Object key, long millisNoReAdd) {
Object removedObject = cache.remove(namespacedKey(key));
if (millisNoReAdd > 0) {
putMarker(key, removedObject, millisNoReAdd);
}
return removedObject != null;
}
public <T> Set<T> deleteAll(Collection<T> keys) {
return deleteAll(keys, 0L);
}
public <T> Set<T> deleteAll(Collection<T> keys, long millisNoReAdd) {
Set<T> deletedKeys = new HashSet<T>();
for (T key : keys) {
Object previousValue = cache.remove(namespacedKey(key));
if (previousValue != null) {
deletedKeys.add(key);
}
if (millisNoReAdd > 0) {
putMarker(key, previousValue, millisNoReAdd);
}
}
return deletedKeys;
}
protected void lock(Object key) {
final AdvancedCache<NamespacedKey, Object> ac = cache.getAdvancedCache();
if (ac.lock(namespacedKey(key)) == false)
throw new IllegalArgumentException("Cannot lock key: " + key);
}
protected long castToLong(Object value) {
if (value instanceof Number) {
return ((Number) value).longValue();
} else if (value instanceof String) {
String string = (String) value;
try {
return Long.parseLong(string);
} catch (NumberFormatException e) {
throw new InvalidValueException("Cannot increment. Value was " + value);
}
}
throw new InvalidValueException("Cannot increment. Value was " + value);
}
private Long incrementInternal(final Object key, final long delta, final Long initialValue) {
lock(key);
long newValue;
Object value = get(key);
if (value == null) {
if (initialValue == null) {
return null;
}
value = initialValue;
newValue = initialValue + delta;
} else {
newValue = Math.max(0, castToLong(value) + delta);
}
if (value instanceof String) {
put(key, String.valueOf(newValue));
} else if (value instanceof Byte) {
put(key, ((Number)newValue).byteValue());
} else if (value instanceof Short) {
put(key, ((Number)newValue).shortValue());
} else if (value instanceof Integer) {
put(key, ((Number)newValue).intValue());
} else if (value instanceof Long) {
put(key, ((Number)newValue).longValue());
} else {
throw new IllegalArgumentException("Unsupported value type: " + value.getClass());
}
return newValue;
}
public Long increment(Object key, long delta) {
return increment(key, delta, null);
}
public Long increment(final Object key, final long delta, final Long initialValue) {
final Callable<Long> callable = new Callable<Long>() {
public Long call() throws Exception {
return incrementInternal(key, delta, initialValue);
}
};
return new WrapperTxCallable<NamespacedKey, Object, Long>(cache, callable).call();
}
public <T> Map<T, Long> incrementAll(Collection<T> keys, long delta) {
return incrementAll(keys, delta, null);
}
public <T> Map<T, Long> incrementAll(final Collection<T> keys, final long delta, final Long initialValue) {
final Callable<Map<T, Long>> callable = new Callable<Map<T, Long>>() {
public Map<T, Long> call() throws Exception {
Map<T, Long> map = new HashMap<T, Long>();
for (T key : keys) {
Long newValue = incrementInternal(key, delta, initialValue);
map.put(key, newValue);
}
return map;
}
};
return new WrapperTxCallable<NamespacedKey, Object, Map<T, Long>>(cache, callable).call();
}
public <T> Map<T, Long> incrementAll(Map<T, Long> offsets) {
return incrementAll(offsets, null);
}
public <T> Map<T, Long> incrementAll(final Map<T, Long> offsets, final Long initialValue) {
final Callable<Map<T, Long>> callable = new Callable<Map<T, Long>>() {
public Map<T, Long> call() throws Exception {
Map<T, Long> map = new HashMap<T, Long>();
for (Map.Entry<T, Long> entry : offsets.entrySet()) {
T key = entry.getKey();
Long delta = entry.getValue();
Long newValue = incrementInternal(key, delta, initialValue);
map.put(key, newValue);
}
return map;
}
};
return new WrapperTxCallable<NamespacedKey, Object, Map<T, Long>>(cache, callable).call();
}
public void clearAll() {
cache.clear();
}
public Stats getStatistics() {
return new InfinispanStatistics(cache.getAdvancedCache());
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
public ErrorHandler getErrorHandler() {
return errorHandler;
}
private long toLifespanMillis(Expiration expiration) {
if (expiration == null) {
return -1;
} else {
return expiration.getMillisecondsValue() - System.currentTimeMillis();
}
}
private static class MyIdentifiableValue implements IdentifiableValue {
private final Object value;
public MyIdentifiableValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
private NamespacedKey namespacedKey(Object key) {
String namespace = getNamespace() == null ? NamespaceManager.get() : getNamespace();
return new NamespacedKey(namespace == null ? "" : namespace, key);
}
private <T> Map<? extends NamespacedKey, ?> toNamespacedMap(Map<T, ?> map) {
HashMap<NamespacedKey, Object> namespacedKeyMap = new HashMap<NamespacedKey, Object>();
for (Map.Entry<T, ?> entry : map.entrySet()) {
namespacedKeyMap.put(namespacedKey(entry.getKey()), entry.getValue());
}
return namespacedKeyMap;
}
}