/** * 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.admin; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import org.deephacks.confit.ConfigChanges; import org.deephacks.confit.admin.AdminContext; import org.deephacks.confit.admin.query.BeanQuery; import org.deephacks.confit.model.AbortRuntimeException; import org.deephacks.confit.model.Bean; import org.deephacks.confit.model.BeanId; import org.deephacks.confit.model.BeanUtils; import org.deephacks.confit.model.Events; import org.deephacks.confit.model.Schema; import org.deephacks.confit.model.Schema.SchemaPropertyRef; import org.deephacks.confit.spi.BeanManager; import org.deephacks.confit.spi.CacheManager; import org.deephacks.confit.spi.NotificationManager; 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.Collection; 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.CFG301_MISSING_RUNTIME_REF; /** * AdminCoreContext is responsible for separating the admin and config * context so that no dependencies (compile nor config) exist between them. */ @Singleton public final class AdminCoreContext extends AdminContext { private AtomicBoolean LOOKUP_DONE = new AtomicBoolean(false); private BeanManager beanManager; private SchemaManager schemaManager; private NotificationManager notificationManager; private Optional<CacheManager> cacheManager; private Optional<ValidationManager> validationManager; @Override public List<Bean> list(String schemaName) { Preconditions.checkNotNull(schemaName); doLookup(); schemaManager.getSchema(schemaName); Map<BeanId, Bean> beans = beanManager.list(schemaName); schemaManager.setSchema(beans.values()); return new ArrayList<>(beans.values()); } @Override public <T> Collection<T> list(Class<T> configurable) throws AbortRuntimeException { doLookup(); final Schema schema = schemaManager.getSchema(configurable); Collection<Bean> beans = list(schema.getName()); return (Collection<T>) schemaManager.convertBeans(beans); } @Override public List<Bean> list(String schemaName, Collection <String> instanceIds) { Preconditions.checkNotNull(schemaName); if (instanceIds == null || instanceIds.isEmpty()) { return new ArrayList<>(); } doLookup(); schemaManager.getSchema(schemaName); Map<BeanId, Bean> beans = beanManager.list(schemaName, instanceIds); schemaManager.setSchema(beans.values()); return new ArrayList<>(beans.values()); } @Override public Optional<Bean> get(BeanId beanId) { Preconditions.checkNotNull(beanId); doLookup(); Optional<Bean> bean = beanManager.getEager(beanId); if (!bean.isPresent()) { return bean; } schemaManager.setSchema(Arrays.asList(bean.get())); setSingletonReferences(bean.get()); return bean; } @Override public <T> Optional<T> get(Class<T> configurable) throws AbortRuntimeException { doLookup(); Schema schema = schemaManager.getSchema(configurable); Optional<Bean> bean = get(BeanId.createSingleton(schema.getName())); if (!bean.isPresent()) { return Optional.absent(); } return (Optional<T>) Optional.of(schemaManager.convertBean(bean.get())); } @Override public <T> Optional<T> get(Class<T> configurable, String instanceId) throws AbortRuntimeException { doLookup(); Schema schema = schemaManager.getSchema(configurable); Optional<Bean> bean = get(BeanId.create(instanceId, schema.getName())); if (!bean.isPresent()) { return Optional.absent(); } Object object = schemaManager.convertBean(bean.get()); return Optional.of((T) object); } @Override public void create(Bean bean) { doLookup(); Preconditions.checkNotNull(bean); create(Arrays.asList(bean)); } @Override public void createObject(Object object) throws AbortRuntimeException { createObjects(Arrays.asList(object)); } @Override public void create(Collection<Bean> beans) { if (beans == null || beans.isEmpty()) { return; } doLookup(); beanManager.initializeReferences(beans); schemaManager.setSchema(beans); schemaManager.validateSchema(beans); if (validationManager.isPresent()) { // ready to validate Collection<Object> objects = schemaManager.convertBeans(beans); validationManager.get().validate(objects); } beanManager.create(beans); if (cacheManager.isPresent()) { cacheManager.get().putAll(beans); } notificationManager.fireCreate(beans); } @Override public void createObjects(Collection <?> objects) throws AbortRuntimeException { doLookup(); if (objects == null || objects.isEmpty()) { return; } Collection<Bean> beans = schemaManager.convertObjects((Collection<Object>) objects); create(beans); } @Override public void set(Bean bean) { doLookup(); Preconditions.checkNotNull(bean); set(Arrays.asList(bean)); } @Override public void setObject(Object object) throws AbortRuntimeException { setObjects(Arrays.asList(object)); } @Override public void set(Collection<Bean> beans) { if (beans == null || beans.isEmpty()) { return; } doLookup(); beanManager.initializeReferences(beans); schemaManager.setSchema(beans); schemaManager.validateSchema(beans); if (validationManager.isPresent()) { validateSet(Bean.copy(beans)); } schemaManager.setSchema(beans); ConfigChanges changes = notificationManager.updated(beans); beanManager.set(beans); if (cacheManager.isPresent()) { cacheManager.get().putAll(beans); } notificationManager.fire(changes); } @Override public void setObjects(Collection<?> objects) throws AbortRuntimeException { if (objects == null || objects.isEmpty()) { return; } doLookup(); Collection<Bean> beans = schemaManager.convertObjects((Collection<Object>) objects); set(beans); } @Override public void merge(Bean bean) { Preconditions.checkNotNull(bean); merge(Arrays.asList(bean)); } @Override public void mergeObject(Object object) throws AbortRuntimeException { mergeObjects(Arrays.asList(object)); } @Override public void merge(Collection<Bean> beans) { if (beans == null || beans.isEmpty()) { return; } doLookup(); schemaManager.setSchema(beans); schemaManager.validateSchema(beans); // ok to not have validation manager available if (validationManager.isPresent()) { validateMerge(Bean.copy(beans)); } schemaManager.setSchema(beans); ConfigChanges changes = notificationManager.updated(beans); beanManager.merge(beans); if (cacheManager.isPresent()) { for (Bean bean : beans) { // must refresh the bean from storage since it is merged. Optional<Bean> refreshed = beanManager.getEager(bean.getId()); if (refreshed.isPresent()) { cacheManager.get().put(refreshed.get()); } } } notificationManager.fire(changes); } @Override public void mergeObjects(Collection<?> objects) throws AbortRuntimeException { if (objects == null || objects.isEmpty()) { return; } doLookup(); Collection<Bean> beans = schemaManager.convertObjects((Collection<Object>) objects); merge(beans); } @Override public void delete(BeanId beanId) { Preconditions.checkNotNull(beanId); doLookup(); Optional<Bean> before = get(beanId); if (!before.isPresent()) { throw Events.CFG304_BEAN_DOESNT_EXIST(beanId); } Bean bean = beanManager.delete(beanId); if (bean == null) { throw Events.CFG304_BEAN_DOESNT_EXIST(beanId); } if (cacheManager.isPresent()) { cacheManager.get().remove(beanId); } notificationManager.fireDelete(Arrays.asList(before.get())); } @Override public void deleteObject(Object instance) throws AbortRuntimeException { Bean bean = schemaManager.convertObject(instance); delete(bean.getId()); } @Override public void delete(String name, Collection<String> instances) { Preconditions.checkNotNull(name); if (instances == null || instances.isEmpty()) { return; } doLookup(); ConfigChanges changes = notificationManager.deleted(name, instances); Collection<Bean> beans = beanManager.delete(name, instances); schemaManager.setSchema(beans); if (cacheManager.isPresent()) { cacheManager.get().remove(name, instances); } notificationManager.fire(changes); } @Override public void deleteObjects(Class<?> configurable, Collection<String> instanceIds) throws AbortRuntimeException { Schema schema = schemaManager.getSchema(configurable); for (String instanceId : instanceIds) { delete(BeanId.create(instanceId, schema.getName())); } } @Override public Map<String, Schema> getSchemas() { doLookup(); return schemaManager.getSchemas(); } @Override public Optional<Schema> getSchema(String schemaName) { doLookup(); try { Schema schema = schemaManager.getSchema(schemaName); return Optional.of(schema); } catch (Exception e) { return Optional.absent(); } } @Override public BeanQuery newQuery(String schemaName) { doLookup(); Schema schema = schemaManager.getSchema(schemaName); return beanManager.newQuery(schema); } private void validateMerge(Collection<Bean> mergebeans) { Map<BeanId, Bean> beansToValidate = beanManager.getBeanToValidate(mergebeans); schemaManager.setSchema(beansToValidate.values()); // since we are validating mergebean predecessors, we need to make sure // that they see a merged reference (not unmerged reference currently in storage) // before validation can proceed. for (Bean mergebean : mergebeans) { ArrayList<Bean> mergeBeanReferences = new ArrayList<>(); ArrayList<Bean> checked = new ArrayList<>(); findReferences(mergebean.getId(), beansToValidate.values(), mergeBeanReferences, checked); // merge list references merge(mergeBeanReferences, mergebean); } // ready to validate Collection<Object> objects = schemaManager.convertBeans(beansToValidate.values()); validationManager.get().validate(objects); } private void validateSet(Collection<Bean> setbeans) { Map<BeanId, Bean> beansToValidate = beanManager.getBeanToValidate(setbeans); schemaManager.setSchema(beansToValidate.values()); // since we are validating setbean predecessors, we need to make sure // that they see a replaced/set reference (not old reference currently in storage) // before validation can proceed. for (Bean setbean : setbeans) { ArrayList<Bean> setBeanReferences = new ArrayList<>(); ArrayList<Bean> checked = new ArrayList<>(); findReferences(setbean.getId(), beansToValidate.values(), setBeanReferences, checked); for (Bean ref : setBeanReferences) { // clearing and then merging have same // effect as a 'set' operation ref.clear(); } merge(setBeanReferences, setbean); } // ready to validate Collection<Object> objects = schemaManager.convertBeans(beansToValidate.values()); validationManager.get().validate(objects); } /** * Does a recursive check if predecessor have a particular reference and if * so return those predecessor references. */ private List<Bean> findReferences(BeanId reference, Collection<Bean> predecessors, ArrayList<Bean> matches, ArrayList<Bean> checked) { for (Bean predecessor : predecessors) { findReferences(reference, predecessor, matches, checked); } return matches; } private void findReferences(BeanId reference, Bean predecessor, ArrayList<Bean> matches, ArrayList<Bean> checked) { if (checked.contains(predecessor)) { return; } checked.add(predecessor); if (reference.equals(predecessor.getId()) && !matches.contains(predecessor)) { matches.add(predecessor); } for (BeanId ref : predecessor.getReferences()) { if (ref.getBean() == null) { continue; } if (matches.contains(ref.getBean())) { continue; } if (ref.equals(reference)) { matches.add(ref.getBean()); } findReferences(reference, ref.getBean(), matches, checked); } } private void merge(List<Bean> sources, Bean mergeBean) { HashMap<BeanId, Bean> cache = new HashMap<>(); for (Bean source : sources) { for (String name : mergeBean.getPropertyNames()) { List<String> values = mergeBean.getValues(name); if (values == null || values.size() == 0) { continue; } source.setProperty(name, values); } for (String name : mergeBean.getReferenceNames()) { List<BeanId> refs = mergeBean.getReference(name); if (refs == null || refs.size() == 0) { source.setReferences(name, refs); continue; } for (BeanId beanId : refs) { Bean bean = cache.get(beanId); if (bean == null) { Optional<Bean> optional = beanManager.getLazy(beanId); if (!optional.isPresent()) { throw CFG301_MISSING_RUNTIME_REF(beanId); } bean = optional.get(); schemaManager.setSchema(Arrays.asList(bean)); cache.put(beanId, bean); } beanId.setBean(bean); } source.setReferences(name, refs); } } } /** * Used for setting or creating a multiple beans. * * We must consider that the operation may include beans that have * references betewen eachother. User provided beans are * prioritized and the storage is secondary for looking up references. */ @SuppressWarnings("unused") private void initalizeReferences(Collection<Bean> beans) { Map<BeanId, Bean> userProvided = BeanUtils.uniqueIndex(beans); for (Bean bean : beans) { for (String name : bean.getReferenceNames()) { List<BeanId> values = bean.getReference(name); if (values == null) { continue; } for (BeanId beanId : values) { // the does not exist in storage, but may exist in the // set of beans provided by the user. Bean ref = userProvided.get(beanId); if (ref == null) { Optional<Bean> optional = beanManager.getEager(beanId); if (optional.isPresent()) { ref = optional.get(); } } beanId.setBean(ref); schemaManager.setSchema(Arrays.asList(beanId.getBean())); } } } } 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()) { singleton.get().set(singletonSchema); BeanId singletonId = singleton.get().getId(); singletonId.setBean(singleton.get()); // recursive call. setSingletonReferences(singleton.get()); bean.setReference(ref.getName(), singletonId); } } } } private void doLookup() { if (LOOKUP_DONE.get()) { return; } beanManager = BeanManager.lookup(); schemaManager = SchemaManager.lookup(); notificationManager = NotificationManager.lookup(); cacheManager = CacheManager.lookup(); validationManager = ValidationManager.lookup(); LOOKUP_DONE.set(true); } }