/** * 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.core.config; import com.google.common.base.Optional; import org.deephacks.confit.ConfigContext; import org.deephacks.confit.ConfigObserver; import org.deephacks.confit.model.AbortRuntimeException; import org.deephacks.confit.model.Bean; import org.deephacks.confit.model.BeanId; import org.deephacks.confit.model.Schema; import org.deephacks.confit.model.Schema.SchemaPropertyRef; import org.deephacks.confit.query.ConfigQuery; import org.deephacks.confit.spi.BeanManager; import org.deephacks.confit.spi.CacheManager; import org.deephacks.confit.spi.NotificationManager; import org.deephacks.confit.spi.PropertyManager; import org.deephacks.confit.spi.SchemaManager; import org.deephacks.confit.spi.ValidationManager; import javax.inject.Singleton; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import static org.deephacks.confit.model.Events.CFG303; /** * ConfigCoreContext is responsible for separating the admin, config and spi * context so that no dependencies (compile nor config) exist between them. */ @Singleton public final class ConfigCoreContext extends ConfigContext { private SchemaManager schemaManager; private BeanManager beanManager; private NotificationManager notificationManager; private PropertyManager propertyManager; private Optional<ValidationManager> validationManager; private Optional<CacheManager> cacheManager; private static HashMap<BeanId, Bean> FILE_CONFIG; private static final ThreadLocal<String> RECURSION_SHORTCIRCUIT = new ThreadLocal<>(); private AtomicBoolean LOOKUP_DONE = new AtomicBoolean(false); @Override public void register(Class<?>... configurable) { doLookup(); schemaManager.register(configurable); if (cacheManager.isPresent()) { for (Class<?> cls : configurable) { Schema schema = schemaManager.getSchema(cls); cacheManager.get().registerSchema(schema); } } } @Override public void unregister(Class<?>... configurable) { doLookup(); for (Class<?> cls : configurable) { Schema schema = schemaManager.remove(cls); if (cacheManager.isPresent()) { cacheManager.get().removeSchema(schema); } } } @Override public <T> T get(Class<T> configurable) { doLookup(); Schema schema = schemaManager.getSchema(configurable); BeanId singleton = getSingletonId(schema, configurable); Optional<Bean> bean; try { if (configurable.getName().equals(RECURSION_SHORTCIRCUIT.get())) { bean = Optional.absent(); } else { RECURSION_SHORTCIRCUIT.set(configurable.getName()); bean = beanManager.getEager(singleton); } } finally { RECURSION_SHORTCIRCUIT.set(null); } if (!bean.isPresent()) { initFile(configurable); Bean fileBean = FILE_CONFIG.get(singleton); if (fileBean != null) { fileBean.set(schema); bean = Optional.of(fileBean); } if (bean.isPresent()) { T object = (T) schemaManager.convertBean(bean.get()); if (cacheManager.isPresent()) { cacheManager.get().put(bean.get()); } return object; } } if (!bean.isPresent()) { bean = Optional.of(Bean.create(BeanId.createSingleton(schema.getName()))); } schemaManager.setSchema(Arrays.asList(bean.get())); setSingletonReferences(bean.get()); T object = (T) schemaManager.convertBean(bean.get()); if (cacheManager.isPresent()) { cacheManager.get().put(bean.get()); } return object; } @Override public <T> List<T> list(Class<T> configurable) { doLookup(); Schema s = schemaManager.getSchema(configurable); initFile(configurable); Map<BeanId, Bean> beans = new HashMap<>(); Map<BeanId, Bean> found = beanManager.list(s.getName()); // only fallback to file if no beans were found. // maybe good if file and storage beans were merged? // on the other hand, that would mean that file bean instances // never can be removed, which may be annoying? if (found.isEmpty()) { for (Bean bean : FILE_CONFIG.values()) { if (bean.getId().getSchemaName().equals(s.getName())) { beans.put(bean.getId(), bean); } } } for (Bean foundBean : found.values()) { beans.put(foundBean.getId(), foundBean); } schemaManager.setSchema(beans.values()); for (Bean bean : beans.values()) { setSingletonReferences(bean); if (cacheManager.isPresent()) { cacheManager.get().put(bean); } } ArrayList<T> objects = new ArrayList<>(); for(Object object : schemaManager.convertBeans(beans.values())) { objects.add((T) object); } return objects; } @Override public <T> Optional<T> get(String id, Class<T> configurable) { doLookup(); Schema s = schemaManager.getSchema(configurable); BeanId beanId = BeanId.create(id, s.getName()); Optional<Bean> bean = beanManager.getEager(beanId); if (!bean.isPresent()) { initFile(configurable); Bean fileBean = FILE_CONFIG.get(beanId); if (fileBean != null) { bean = Optional.of(fileBean); } else { return Optional.absent(); } } schemaManager.setSchema(Arrays.asList(bean.get())); setSingletonReferences(bean.get()); T object = (T) schemaManager.convertBean(bean.get()); if (cacheManager.isPresent()) { cacheManager.get().put(bean.get()); } return Optional.of(object); } @Override public <T> ConfigQuery<T> newQuery(Class<T> configurable) { doLookup(); if(!cacheManager.isPresent()) { throw new IllegalStateException("Queries are not possible without a cache manager."); } Schema schema = schemaManager.getSchema(configurable); return cacheManager.get().newQuery(schema); } @Override public void registerObserver(ConfigObserver observer) { doLookup(); notificationManager.register(observer); } private BeanId getSingletonId(Schema s, Class<?> configurable) { return BeanId.createSingleton(s.getName()); } private void setSingletonReferences(Bean bean) { Schema s = bean.getSchema(); for (SchemaPropertyRef ref : s.get(SchemaPropertyRef.class)) { if (ref.isSingleton()) { Schema singletonSchema = schemaManager.getSchema(ref.getSchemaName()); Optional<Bean> singleton = beanManager.getSingleton(ref.getSchemaName()); if (!singleton.isPresent()) { initFile(null); Bean fileBean = FILE_CONFIG.get(BeanId.createSingleton(ref.getSchemaName())); if (fileBean != null) { singleton = Optional.of(fileBean); } } if (!singleton.isPresent()) { singleton = Optional.of(Bean.create(BeanId.createSingleton(ref.getSchemaName()))); } singleton.get().set(singletonSchema); BeanId singletonId = singleton.get().getId(); singletonId.setBean(singleton.get()); // recursive call. setSingletonReferences(singleton.get()); bean.setReference(ref.getName(), singletonId); } } } private synchronized void initFile(Class<?> configurable) { if (configurable == null) { return; } if (FILE_CONFIG == null) { FILE_CONFIG = new HashMap<>(); } Schema schema = schemaManager.getSchema(configurable); for (Bean bean : propertyManager.list(schema, schemaManager.getSchemas())) { if (validationManager.isPresent()) { Object object = schemaManager.convertBean(bean); validationManager.get().validate(object); } FILE_CONFIG.put(bean.getId(), bean); } } public void doLookup() { if (LOOKUP_DONE.get()) { return; } propertyManager = PropertyManager.lookup(); schemaManager = SchemaManager.lookup(); beanManager = BeanManager.lookup(); validationManager = ValidationManager.lookup(); notificationManager = NotificationManager.lookup(); cacheManager = CacheManager.lookup(); LOOKUP_DONE.set(true); } }