/******************************************************************************* * Copyright 2013-2014 alladin-IT GmbH * * 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 at.alladin.rmbt.shared.cache; import java.io.IOException; import java.net.SocketAddress; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import net.spy.memcached.AddrUtil; import net.spy.memcached.BinaryConnectionFactory; import net.spy.memcached.ConnectionObserver; import net.spy.memcached.MemcachedClient; import net.spy.memcached.internal.BulkFuture; import net.spy.memcached.internal.GetFuture; import com.google.common.hash.Funnel; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.common.io.BaseEncoding; public class CacheHelper implements ConnectionObserver { private static final String PREFIX_TS = "ts:"; private static final CacheHelper instance = new CacheHelper(); private static final long TIMEOUT = 500; // 0.5 seconds private static HashFunction keyHash = Hashing.murmur3_128(); private static BaseEncoding keyEncoding = BaseEncoding.base64(); private final AtomicReference<MemcachedClient> memcachedClient = new AtomicReference<>(); private final AtomicBoolean memcachedActive = new AtomicBoolean(false); private final ExecutorService executor = Executors.newFixedThreadPool(4); public static CacheHelper getInstance() { return instance; } public void initMemcached(String addresses) // addresses: space sep. like 'localhost:11211 10.10.10.10:1234' { if (memcachedClient.get() != null) throw new IllegalStateException("memcached already initialized"); try { final MemcachedClient _memcachedClient = new MemcachedClient( new BinaryConnectionFactory(), AddrUtil.getAddresses(addresses)); _memcachedClient.addObserver(this); memcachedClient.set(_memcachedClient); } catch (IOException e) { e.printStackTrace(); } } public ExecutorService getExecutor() { return executor; } public static class ObjectWithTimestamp { public final Object o; public final Long ts; public final boolean stale; public ObjectWithTimestamp(Object o, Long ts, boolean stale) { this.o = o; this.ts = ts; this.stale = stale; } } public boolean isActive() { return (memcachedClient.get() != null && memcachedActive.get()); } public Object get(String key) { if (! isActive()) return null; Object result = null; final GetFuture<Object> f = memcachedClient.get().asyncGet(key); try { result = f.get(TIMEOUT, TimeUnit.MILLISECONDS); } catch (Exception e) { f.cancel(true); } return result; // return memcachedClient.get(key); // sync version } public ObjectWithTimestamp getWithTimestamp(String key, int staleExp) //staleExp: seconds { if (! isActive()) return null; final String tsKey = PREFIX_TS + key; final BulkFuture<Map<String, Object>> f = memcachedClient.get().asyncGetBulk(key, tsKey); try { final Map<String, Object> map = f.get(TIMEOUT, TimeUnit.MILLISECONDS); Object result = map.get(key); if (result == null) return null; final Long ts = (Long)map.get(tsKey); final boolean stale; if (ts == null) stale = true; // if there is no ts, we assume stale else { final long now = System.currentTimeMillis(); stale = (ts + (staleExp * 1000) <= now); } return new ObjectWithTimestamp(result, ts, stale); } catch (Exception e) { f.cancel(true); return null; } } // public MemcachedClient getMemcachedClient() // { // return memcachedClient; // } public static <T extends Funnel<T>> String getHash(T object) { return keyEncoding.encode(keyHash.hashObject(object, object).asBytes()); } public Future<Boolean> set(String key, int exp, Object o) { return set(key, exp, o, false); } public Future<Boolean> set(String key, int exp, Object o, boolean addTimestamp) { if (! isActive()) return null; if (addTimestamp) memcachedClient.get().set(PREFIX_TS + key, exp, System.currentTimeMillis()); return memcachedClient.get().set(key, exp, o); } protected void updateMemcachedActive() { final int numAvail = memcachedClient.get().getAvailableServers().size(); memcachedActive.set(numAvail >= 1); } @Override public void connectionEstablished(SocketAddress addr, int arg1) { updateMemcachedActive(); } @Override public void connectionLost(SocketAddress addr) { updateMemcachedActive(); } }