/*
* Copyright 2016 requery.io
*
* 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.requery.cache;
import io.requery.EntityCache;
import io.requery.meta.Attribute;
import io.requery.meta.EntityModel;
import io.requery.meta.Type;
import io.requery.proxy.CompositeKey;
import io.requery.util.ClassMap;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.Factory;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.EternalExpiryPolicy;
import javax.cache.expiry.ExpiryPolicy;
import java.util.Map;
import java.util.Set;
/**
* Cache using the JSR-107 API.
*
* @author Nikhil Purushe
*/
@SuppressWarnings("unchecked")
public class SerializableEntityCache implements EntityCache {
private final EntityModel model;
private final CacheManager cacheManager;
private final ClassMap<Cache<?, ?>> caches;
private final Factory<ExpiryPolicy> expiryPolicyFactory;
public SerializableEntityCache(EntityModel model, CacheManager cacheManager) {
if (cacheManager == null) {
throw new IllegalArgumentException();
}
this.model = model;
this.cacheManager = cacheManager;
this.expiryPolicyFactory = EternalExpiryPolicy.factoryOf();
this.caches = new ClassMap<>();
}
protected String getCacheName(Type<?> type) {
return type.getName();
}
protected <K, V> void configure(MutableConfiguration<K, V> configuration) {
configuration.setExpiryPolicyFactory(expiryPolicyFactory);
}
private <T> Class getKeyClass(Type<T> type) {
Set<Attribute<T, ?>> keys = type.getKeyAttributes();
Class keyClass;
if (keys.isEmpty()) {
// use hash code
return Integer.class;
}
if (keys.size() == 1) {
Attribute<?, ?> attribute = keys.iterator().next();
if (attribute.isAssociation()) {
attribute = attribute.getReferencedAttribute().get();
}
keyClass = attribute.getClassType();
if (keyClass.isPrimitive()) {
if (keyClass == int.class) {
keyClass = Integer.class;
} else if (keyClass == long.class) {
keyClass = Long.class;
}
}
} else {
keyClass = CompositeKey.class;
}
return keyClass;
}
protected <K, T> Cache<K, SerializedEntity<T>> createCache(String cacheName, Type<T> type) {
Class keyClass = getKeyClass(type);
if (keyClass == null) {
throw new IllegalStateException();
}
MutableConfiguration<K, SerializedEntity<T>> configuration =
new MutableConfiguration<>();
configuration.setTypes(keyClass, (Class) SerializedEntity.class);
configure(configuration);
return cacheManager.createCache(cacheName, configuration);
}
private <K, T> Cache<K, SerializedEntity<T>> tryCreateCache(Class<T> type) {
Type<T> declaredType = model.typeOf(type);
String cacheName = getCacheName(declaredType);
Cache<K, SerializedEntity<T>> cache = cacheManager.getCache(cacheName);
if (cache == null) {
// try creating it, if failed see if it was already created
try {
cache = createCache(cacheName, declaredType);
} catch (CacheException ce) {
cache = cacheManager.getCache(cacheName);
if (cache == null) {
throw ce;
}
}
}
return cache;
}
private Cache getCache(Class<?> type) {
Cache cache;
synchronized (caches) {
cache = caches.get(type);
if (cache == null) {
Type declaredType = model.typeOf(type);
String cacheName = getCacheName(declaredType);
Class keyClass = getKeyClass(declaredType);
cache = cacheManager.getCache(cacheName, keyClass, SerializedEntity.class);
}
}
return cache;
}
@Override
public <T> T get(Class<T> type, Object key) {
Cache cache = getCache(type);
if (cache != null && cache.isClosed()) {
cache = null;
}
if (cache != null) {
SerializedEntity container = (SerializedEntity) cache.get(key);
if (container != null) {
return type.cast(container.getEntity());
}
}
return null;
}
@Override
public <T> void put(Class<T> type, Object key, T value) {
Cache<Object, SerializedEntity<T>> cache;
synchronized (caches) {
cache = getCache(type);
if (cache == null) {
cache = tryCreateCache(type);
}
}
cache.put(key, new SerializedEntity<>(type, value));
}
@Override
public boolean contains(Class<?> type, Object key) {
Cache cache = getCache(type);
return cache != null && !cache.isClosed() && cache.containsKey(key);
}
@Override
public void invalidate(Class<?> type) {
Cache cache = getCache(type);
if (cache != null) {
cache.clear();
String cacheName = getCacheName(model.typeOf(type));
cacheManager.destroyCache(cacheName);
synchronized (caches) {
caches.remove(type);
}
cache.close();
}
}
@Override
public void invalidate(Class<?> type, Object key) {
Cache cache = getCache(type);
if (cache != null && !cache.isClosed()) {
cache.remove(key);
}
}
@Override
public void clear() {
synchronized (caches) {
for (Map.Entry<Class<?>, Cache<?, ?>> entry : caches.entrySet()) {
invalidate(entry.getKey());
}
}
}
}