package org.deephacks.confit.internal.core.config; import com.google.common.base.Optional; import org.deephacks.confit.admin.query.BeanQuery; import org.deephacks.confit.admin.query.BeanQueryBuilder.BeanRestriction; import org.deephacks.confit.admin.query.BeanQueryBuilder.LogicalRestriction; import org.deephacks.confit.admin.query.BeanQueryBuilder.PropertyRestriction; import org.deephacks.confit.admin.query.BeanQueryResult; import org.deephacks.confit.model.AbortRuntimeException; 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.spi.BeanManager; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static org.deephacks.confit.model.Events.*; /** * In memory BeanManager. */ public class DefaultBeanManager extends BeanManager { private static final InMemoryStorage storage = new InMemoryStorage(); @Override public Optional<Bean> getEager(BeanId id) { return getEagerly(id); } private Optional<Bean> getEagerly(BeanId id) { HashMap<BeanId, Bean> found = new HashMap<>(); getEagerly(id, found, new HashSet<BeanId>()); Bean bean = found.get(id); if (bean == null) { return Optional.absent(); } return Optional.of(bean); } private void getEagerly(BeanId id, HashMap<BeanId, Bean> found, Set<BeanId> seen) { if (seen.contains(id)) { return; } seen.add(id); Bean bean = found.get(id) == null ? storage.get(id) : found.get(id); if (bean == null) { return; } found.put(id, bean); // bean found, fetch references. for (BeanId ref : bean.getReferences()) { if (ref.getBean() != null) { continue; } Bean refBean = found.get(ref) == null ? storage.get(ref) : found.get(ref); if (refBean == null) { throw CFG301_MISSING_RUNTIME_REF(bean.getId(), ref); } ref.setBean(refBean); found.put(ref, refBean); getEagerly(ref, found, seen); } } @Override public Optional<Bean> getLazy(BeanId id) throws AbortRuntimeException { Bean bean = storage.get(id); if (bean == null) { return Optional.absent(); } for (BeanId ref : bean.getReferences()) { Bean refBean = storage.get(ref); if (refBean == null) { throw CFG301_MISSING_RUNTIME_REF(ref); } ref.setBean(refBean); } return Optional.of(bean); } /** * The direct, but no further, successors that references this bean will also be * fetched and initialized with their direct, but no further, predecessors. */ @Override public Map<BeanId, Bean> getBeanToValidate(Collection<Bean> beans) throws AbortRuntimeException { Map<BeanId, Bean> beansToValidate = new HashMap<>(); for (Bean bean : beans) { Map<BeanId, Bean> predecessors = new HashMap<>(); // beans read from xml storage will only have their basic properties initialized... // ... but we also need set the direct references/predecessors for beans to validate Map<BeanId, Bean> beansToValidateSubset = getDirectSuccessors(bean); beansToValidateSubset.put(bean.getId(), bean); for (Bean toValidate : beansToValidateSubset.values()) { predecessors.putAll(getDirectPredecessors(toValidate)); } for (Bean predecessor : predecessors.values()) { for (BeanId ref : predecessor.getReferences()) { Bean b = storage.get(ref); if (b == null) { throw CFG301_MISSING_RUNTIME_REF(predecessor.getId()); } ref.setBean(b); } } for (Bean toValidate : beansToValidateSubset.values()) { // list references of beansToValidate should now // be available in predecessors. for (BeanId ref : toValidate.getReferences()) { Bean predecessor = predecessors.get(ref); if (predecessor == null) { throw new IllegalStateException("Bug in algorithm. Reference [" + ref + "] of [" + toValidate.getId() + "] should be available in predecessors."); } ref.setBean(predecessor); } } beansToValidate.putAll(predecessors); } return beansToValidate; } private Map<BeanId, Bean> getDirectPredecessors(Bean bean) { Map<BeanId, Bean> predecessors = new HashMap<>(); for (BeanId ref : bean.getReferences()) { Bean predecessor = storage.get(ref); if (predecessor == null) { throw CFG304_BEAN_DOESNT_EXIST(ref); } predecessors.put(predecessor.getId(), predecessor); } return predecessors; } private Map<BeanId, Bean> getDirectSuccessors(Bean bean) { Map<BeanId, Bean> successors = new HashMap<>(); for (Bean b : storage.all()) { List<BeanId> refs = b.getReferences(); if (refs.contains(bean.getId())) { successors.put(b.getId(), b); } } return successors; } @Override public Optional<Bean> getSingleton(String schemaName) throws IllegalArgumentException { for (Bean bean : storage.all()) { if (bean.getId().getSchemaName().equals(schemaName)) { if (!bean.getId().isSingleton()) { throw new IllegalArgumentException("Schema [" + schemaName + "] is not a lookup."); } BeanId singletonId = bean.getId(); return getEagerly(singletonId); } } return Optional.of(Bean.create(BeanId.createSingleton(schemaName))); } @Override public Map<BeanId, Bean> list(String name) { Map<BeanId, Bean> result = new HashMap<>(); for (Bean b : storage.all()) { if (b.getId().getSchemaName().equals(name)) { Optional<Bean> bean = getEagerly(b.getId()); result.put(bean.get().getId(), bean.get()); } } return result; } @Override public Map<BeanId, Bean> list(String schemaName, Collection<String> ids) throws AbortRuntimeException { Map<BeanId, Bean> result = new HashMap<>(); for (Bean bean : storage.all()) { String schema = bean.getId().getSchemaName(); if (!schema.equals(schemaName)) { continue; } for (String id : ids) { if (bean.getId().getInstanceId().equals(id)) { result.put(bean.getId(), bean); } } } return result; } @Override public void create(Bean bean) { checkReferencesExist(bean, new ArrayList<Bean>()); checkUniquness(bean); storage.put(bean); } @Override public void create(Collection<Bean> set) { // first check uniqueness towards storage for (Bean bean : set) { checkUniquness(bean); } // references may not exist in storage, but are provided // as part of the transactions, so add them before validating references. for (Bean bean : set) { checkReferencesExist(bean, set); } for (Bean bean : set) { storage.put(bean); } } @Override public void createSingleton(BeanId singleton) { Bean bean = Bean.create(singleton); try { checkUniquness(bean); } catch (AbortRuntimeException e) { // ignore and return silently. return; } storage.put(bean); } @Override public void set(Bean bean) { Bean existing = storage.get(bean.getId()); if (existing == null) { throw CFG304_BEAN_DOESNT_EXIST(bean.getId()); } checkReferencesExist(bean, new ArrayList<Bean>()); checkInstanceExist(bean); storage.put(bean); } @Override public void set(Collection<Bean> set) { // TODO: check that provided beans are unique among themselves. // references may not exist in storage, but are provided // as part of the transactions, so add them before validating references. for (Bean bean : set) { Bean existing = storage.get(bean.getId()); if (existing == null) { throw CFG304_BEAN_DOESNT_EXIST(bean.getId()); } storage.put(bean); } for (Bean bean : set) { checkReferencesExist(bean, set); } } @Override public void merge(Bean bean) { Bean b = storage.get(bean.getId()); if (b == null) { throw CFG304_BEAN_DOESNT_EXIST(bean.getId()); } replace(b, bean); } @Override public void merge(Collection<Bean> bean) { for (Bean replace : bean) { Bean target = storage.get(replace.getId()); if (target == null) { throw Events.CFG304_BEAN_DOESNT_EXIST(replace.getId()); } replace(target, replace); storage.put(target); } } private void replace(Bean target, Bean replace) { if (target == null) { // bean did not exist in storage, create it. target = replace; } checkReferencesExist(replace, new ArrayList<Bean>()); for (String name : replace.getPropertyNames()) { List<String> values = replace.getValues(name); if (values == null || values.size() == 0) { // null/empty indicates a remove/reset-to-default op target.remove(name); } else { target.setProperty(name, replace.getValues(name)); } } for (String name : replace.getReferenceNames()) { List<BeanId> values = replace.getReference(name); if (values == null || values.size() == 0) { // null/empty indicates a remove/reset-to-default op target.remove(name); } else { target.setReferences(name, values); } } } @Override public Bean delete(BeanId id) { checkNoReferencesExist(id); Bean bean = storage.remove(id); return bean; } @Override public Collection<Bean> delete(String schemaName, Collection<String> instanceIds) { Collection<Bean> deleted = new ArrayList<>(); for (String instance : instanceIds) { checkNoReferencesExist(BeanId.create(instance, schemaName)); BeanId id = BeanId.create(instance, schemaName); if (storage.get(id) == null) { throw Events.CFG304_BEAN_DOESNT_EXIST(id); } } for (String instance : instanceIds) { BeanId id = BeanId.create(instance, schemaName); storage.remove(id); } return deleted; } @Override public BeanQuery newQuery(Schema schema) { Map<BeanId, Bean> beans = list(schema.getName()); ArrayList<Bean> sorted = new ArrayList<>(beans.values()); Collections.sort(sorted, new Comparator<Bean>() { @Override public int compare(Bean b1, Bean b2) { return b1.getId().getInstanceId().compareTo(b2.getId().getInstanceId()); } }); return new DefaultBeanQuery(schema, sorted); } private static void checkNoReferencesExist(BeanId deleted) { Collection<BeanId> hasReferences = new ArrayList<>(); for (Bean b : storage.all()) { if (hasReferences(b, deleted)) { hasReferences.add(b.getId()); } } if (hasReferences.size() > 0) { throw CFG302_CANNOT_DELETE_BEAN(Arrays.asList(deleted)); } } private static void checkReferencesExist(final Bean bean, Collection<Bean> additional) { HashMap<BeanId, Bean> additionalMap = new HashMap<>(); for (Bean b : additional) { additionalMap.put(b.getId(), b); } ArrayList<BeanId> allRefs = new ArrayList<>(); for (String name : bean.getReferenceNames()) { if (bean.getReference(name) == null) { // the reference is about to be removed. continue; } for (BeanId beanId : bean.getReference(name)) { allRefs.add(beanId); } } Collection<BeanId> missingReferences = new ArrayList<>(); for (BeanId beanId : allRefs) { if (beanId.getInstanceId() == null) { continue; } if (storage.get(beanId) == null && additionalMap.get(beanId) == null) { missingReferences.add(beanId); } } if (missingReferences.size() > 0) { throw CFG301_MISSING_RUNTIME_REF(bean.getId(), missingReferences); } } private static void checkInstanceExist(Bean bean) { Collection<Bean> beans = storage.all(); for (Bean existingBean : beans) { if (existingBean.getId().equals(bean.getId())) { return; } } throw CFG304_BEAN_DOESNT_EXIST(bean.getId()); } private static void checkUniquness(Bean bean) { Collection<Bean> beans = storage.all(); for (Bean existing : beans) { if (bean.getId().equals(existing.getId())) { throw CFG303_BEAN_ALREADY_EXIST(bean.getId()); } } } /** * Returns the a list of property names of the target bean that have * references to the bean id. */ private static boolean hasReferences(Bean target, BeanId reference) { for (String name : target.getReferenceNames()) { List<BeanId> refs = target.getReference(name); if (refs != null) { for (BeanId ref : target.getReference(name)) { if (ref.equals(reference)) { return true; } } } } return false; } public static void clear() { storage.clear(); } private static final class InMemoryStorage { private static final HashMap<BeanId, Bean> beans = new HashMap<>(); public void put(Bean bean) { // make a copy Bean store = Bean.copy(bean); beans.put(bean.getId(), store); } public Bean get(BeanId id){ Bean found = beans.get(id); return Bean.copy(found); } public Collection<Bean> all() { return beans.values(); } public void clear() { beans.clear(); } public Bean remove(BeanId id) { return beans.remove(id); } } public class DefaultBeanQuery implements BeanQuery { private final ArrayList<Bean> beans; private final Schema schema; private int maxResults = Integer.MAX_VALUE; private int firstResult; public DefaultBeanQuery(Schema schema, ArrayList<Bean> beans) { this.beans = beans; this.schema = schema; } @Override public BeanQuery add(BeanRestriction restriction) { if (restriction instanceof PropertyRestriction) { throw new UnsupportedOperationException("Not implemented yet"); } else if (restriction instanceof LogicalRestriction) { throw new UnsupportedOperationException("Not implemented yet"); } throw new UnsupportedOperationException("Could not identify restriction: " + restriction); } @Override public BeanQuery setFirstResult(String firstResult) { try { this.firstResult = Integer.parseInt(firstResult); } catch (Exception e) { throw new IllegalArgumentException("Could not parse firstResult into an integer."); } return this; } @Override public BeanQuery setMaxResults(int maxResults) { this.maxResults = maxResults; return this; } @Override public BeanQueryResult retrieve() { final ArrayList<Bean> result = new ArrayList<>(); int firstResult = 0; for (int i = 0; i < beans.size(); i++) { firstResult = i + 1; if (i >= firstResult && result.size() < maxResults) { result.add(beans.get(i)); } if (result.size() > maxResults) { break; } } final String nextFirstResult = Integer.toString(firstResult); return new BeanQueryResult() { @Override public List<Bean> get() { return result; } @Override public String nextFirstResult() { return nextFirstResult; } }; } } }