/** * 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 org.deephacks.confit.internal.cached; import com.google.common.base.Preconditions; import org.deephacks.cached.Cache; import org.deephacks.cached.CacheBuilder; import org.deephacks.cached.buffer.util.internal.chmv8.ConcurrentHashMapV8; import org.deephacks.confit.internal.cached.proxy.ConfigProxyGenerator; import org.deephacks.confit.internal.cached.query.ConfigIndex; import org.deephacks.confit.internal.cached.query.ConfigIndexedCollection; import org.deephacks.confit.model.Bean; import org.deephacks.confit.model.BeanId; import org.deephacks.confit.model.Events; import org.deephacks.confit.model.Schema; import org.deephacks.confit.query.ConfigQuery; import org.deephacks.confit.spi.CacheManager; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.deephacks.confit.internal.cached.proxy.ConfigProxyGenerator.PROXY_CLASS_SUFFIX; /** * Stores proxies of configurable objects into an off-heap cache. Each schema have * a separate cache. * * Configurable objects are serialized to binary form into the off-heap cache * with references to other configurable objects, not actual objects. * * This is a de-duplicating measure to save memory and simplify cache consistency. * Avoiding cache serialization of instances that potentially come from different * configurable object hierarchies. */ public class CachedCacheManager extends CacheManager<Object> { /** SchemaName -> Cache */ private static final ConcurrentHashMapV8<String, Cache<BeanId, Object>> caches = new ConcurrentHashMapV8<>(); /** generate proxies that are stored in schema-specific caches */ private static final ConfigProxyGenerator proxyGenerator = new ConfigProxyGenerator(); /** proxies are serialized into binary ByteBuf form using this serializer */ private static final DefaultCacheValueSerializer defaultSerializer = new DefaultCacheValueSerializer(); /** schemaName -> index */ private static final HashMap<String, ConfigIndex> configIndexes = new HashMap<>(); /** schemaName -> indexed collection */ private static final HashMap<String, ConfigIndexedCollection> indexCollections = new HashMap<>(); @Override public void registerSchema(Schema schema) { proxyGenerator.put(schema); defaultSerializer.put(schema); putIndex(schema); } @Override public void removeSchema(Schema schema) { String schemaName = schema.getName(); clear(schemaName); indexCollections.remove(schemaName); configIndexes.remove(schemaName); } public Object get(BeanId id) { Cache<BeanId, Object> cache = getCache(id.getSchemaName()); Object proxy = cache.get(id); validateCacheObject(proxy); return proxy; } @Override public List<Object> get(String schemaName) { Cache<BeanId, Object> cache = getCache(schemaName); List<Object> objects = new ArrayList<>(); for(BeanId id : cache.keySet()) { objects.add(cache.get(id)); } return objects; } @Override public void put(Bean bean) { Schema schema = bean.getSchema(); Preconditions.checkNotNull(schema, "Missing schema"); ConfigIndexedCollection col = indexCollections.get(bean.getId().getSchemaName()); col.add(bean); BeanId id = bean.getId(); Set<Bean> beans = flattenReferences(bean); for (Bean b : beans) { Object proxy = proxyGenerator.generateConfigProxy(b); validateCacheObject(proxy); Cache<BeanId, Object> cache = getCache(b.getId().getSchemaName()); cache.put(id, proxy); } } @Override public void putAll(Collection<Bean> configurables) { for (Bean bean : configurables) { put(bean); } } /** * Serializing anything but proxy objects into the cache will break the * API and invalidate the idea of de-duplicated cache to save memory and * simplify cache consistency */ private void validateCacheObject(Object proxy) { if(!proxy.getClass().getName().endsWith(PROXY_CLASS_SUFFIX)) { throw new IllegalArgumentException("Do not try to serialize anything " + "but proxied objects into the cache"); } } /** * Bean may, but not necessarily, have deep hierarchies of references * to other beans. Since a cache store beans per schema we must * dig out this hierarchy and flatten it out, */ private Set<Bean> flattenReferences(Bean bean) { Set<Bean> beans = new HashSet<>(); for (String referenceName : bean.getReferenceNames()) { List<BeanId> ids = bean.getReference(referenceName); for (BeanId id : ids) { if (id.getBean() == null) { continue; } beans.addAll(flattenReferences(id.getBean())); } } beans.add(bean); return beans; } @Override public void remove(BeanId beanId) { Cache<BeanId, Object> cache = getCache(beanId.getSchemaName()); if(cache != null) { cache.remove(beanId); } ConfigIndexedCollection col = indexCollections.get(beanId.getSchemaName()); col.remove(beanId); } @Override public void remove(String schemaName, Collection<String> instances) { for (String instanceId : instances) { remove(BeanId.create(instanceId, schemaName)); } } @Override public void clear(String schemaName) { Cache<BeanId, Object> cache = getCache(schemaName); if(cache != null) { cache.clear(); } } @Override public void clear() { for (String key : caches.keySet()) { caches.get(key).clear(); } } @Override public ConfigQuery newQuery(Schema schema) { ConfigIndexedCollection collection = indexCollections.get(schema.getName()); if(collection == null) { throw Events.CFG101_SCHEMA_NOT_EXIST(schema.getName()); } return new org.deephacks.confit.internal.cached.query.ConfigQuery<>(collection, this); } private void putIndex(Schema schema) { if(configIndexes.get(schema.getName()) != null) { return; } ConfigIndex index = new ConfigIndex(schema); configIndexes.put(schema.getName(), index); indexCollections.put(schema.getName(), new ConfigIndexedCollection(index)); } private Cache<BeanId, Object> getCache(String schemaName) { Cache<BeanId, Object> cache = caches.get(schemaName); if(cache == null) { synchronized (caches) { cache = CacheBuilder.<BeanId, Object>newBuilder() .serializer(defaultSerializer).build(); caches.put(schemaName, cache); } } return cache; } }