All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.deephacks.tools4j.config.internal.core.admin.AdminCoreContext Maven / Gradle / Ivy

There is a newer version: 0.15.0
Show newest version
/**
 * 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.tools4j.config.internal.core.admin;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import org.deephacks.tools4j.config.admin.AdminContext;
import org.deephacks.tools4j.config.admin.query.BeanQuery;
import org.deephacks.tools4j.config.internal.core.ConfigCore;
import org.deephacks.tools4j.config.internal.core.DefaultSchemaManager;
import org.deephacks.tools4j.config.internal.core.Lookup;
import org.deephacks.tools4j.config.internal.core.runtime.BeanToObjectConverter;
import org.deephacks.tools4j.config.internal.core.runtime.ClassToSchemaConverter;
import org.deephacks.tools4j.config.internal.core.runtime.DefaultBeanManager;
import org.deephacks.tools4j.config.internal.core.runtime.FieldToSchemaPropertyConverter;
import org.deephacks.tools4j.config.internal.core.runtime.ObjectToBeanConverter;
import org.deephacks.tools4j.config.model.AbortRuntimeException;
import org.deephacks.tools4j.config.model.Bean;
import org.deephacks.tools4j.config.model.Bean.BeanId;
import org.deephacks.tools4j.config.model.BeanUtils;
import org.deephacks.tools4j.config.model.Schema;
import org.deephacks.tools4j.config.model.Schema.SchemaPropertyRef;
import org.deephacks.tools4j.config.spi.BeanManager;
import org.deephacks.tools4j.config.spi.CacheManager;
import org.deephacks.tools4j.config.spi.Conversion;
import org.deephacks.tools4j.config.spi.SchemaManager;

import javax.inject.Inject;
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 static org.deephacks.tools4j.config.internal.core.admin.SchemaValidator.validateSchema;
import static org.deephacks.tools4j.config.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 {
    /** API class for JSR 303 1.0 bean validation */
    public static final String JSR303_1_0_CLASSNAME = "javax.validation.Validation";
    private static final Conversion conversion;

    static {
        conversion = Conversion.get();
        conversion.register(new BeanToObjectConverter());
        conversion.register(new ObjectToBeanConverter());
        conversion.register(new ClassToSchemaConverter());
        conversion.register(new FieldToSchemaPropertyConverter());
    }

    @Inject
    private ConfigCore core;
    private static final Lookup lookup = Lookup.get();
    private BeanManager beanManager = lookup.lookup(BeanManager.class, DefaultBeanManager.class);
    private SchemaManager schemaManager = lookup.lookup(SchemaManager.class, DefaultSchemaManager.class);
    private Optional validator;
    private Optional cacheManager;

    @Override
    public List list(String schemaName) {
        Preconditions.checkNotNull(schemaName);
        doLookup();
        Map beans = beanManager.list(schemaName);
        core.setSchema(schemaManager.getSchemas(), beans);
        return new ArrayList<>(beans.values());
    }

    @Override
    public List list(String schemaName, Collection instanceIds) {
        Preconditions.checkNotNull(schemaName);
        if (instanceIds == null || instanceIds.isEmpty()) {
            return new ArrayList<>();
        }
        doLookup();
        Map beans = beanManager.list(schemaName, instanceIds);
        Map result = new HashMap<>();
        for (String instanceId : instanceIds) {
            Bean b = beans.get(BeanId.create(instanceId, schemaName));
            result.put(b.getId(), b);
        }
        Map schemas = schemaManager.getSchemas();
        core.setSchema(schemas, result);
        return new ArrayList<>(result.values());
    }

    @Override
    public Optional get(BeanId beanId) {
        Preconditions.checkNotNull(beanId);
        doLookup();
        Optional bean = beanManager.getEager(beanId);
        if (!bean.isPresent()) {
            return bean;
        }
        Map schemas = schemaManager.getSchemas();
        core.setSchema(bean.get(), schemas);
        setSingletonReferences(bean.get(), schemas);
        return bean;
    }

    @Override
    public void create(Bean bean) {
        Preconditions.checkNotNull(bean);
        create(Arrays.asList(bean));
        core.cache(bean);
    }

    @Override
    public void createObject(Object object) throws AbortRuntimeException {
        createObjects(Arrays.asList(object));
    }


    @Override
    public void create(Collection beans) {
        if (beans == null || beans.isEmpty()) {
            return;
        }
        doLookup();
        core.setSchema(schemaManager.getSchemas(), beans);
        validateSchema(beans);
        if (validator.isPresent()) {
            initReferences(beans);
            validator.get().validate(beans);
        }
        beanManager.create(beans);
        core.cache(beans);
    }

    @Override
    public void createObjects(Collection objects) throws AbortRuntimeException {
        if (objects == null || objects.isEmpty()) {
            return;
        }
        Collection beans = convertToBeans(objects);
        create(beans);
    }

    @Override
    public void set(Bean bean) {
        Preconditions.checkNotNull(bean);
        set(Arrays.asList(bean));
    }

    @Override
    public void setObject(Object object) throws AbortRuntimeException {
        setObjects(Arrays.asList(object));
    }

    @Override
    public void set(Collection beans) {
        if (beans == null || beans.isEmpty()) {
            return;
        }
        doLookup();
        core.setSchema(schemaManager.getSchemas(), beans);
        validateSchema(beans);

        if (validator.isPresent()) {
            initReferences(beans);
            validateSet(beans);
        }
        beanManager.set(beans);
        core.cache(beans);
    }

    @Override
    public void setObjects(Collection objects) throws AbortRuntimeException {
        if (objects == null || objects.isEmpty()) {
            return;
        }
        Collection beans = convertToBeans(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 beans) {
        if (beans == null || beans.isEmpty()) {
            return;
        }
        doLookup();
        core.setSchema(schemaManager.getSchemas(), beans);
        validateSchema(beans);
        // ok to not have validation manager available
        if (validator.isPresent()) {
            validateMerge(beans);
        }
        beanManager.merge(beans);
        for (Bean bean : beans) {
            // must refresh the bean from storage since it is merged.
            Optional refreshed = beanManager.getEager(bean.getId());
            if (refreshed.isPresent()) {
                core.cache(refreshed.get());
            }
        }
    }

    @Override
    public void mergeObjects(Collection objects) throws AbortRuntimeException {
        if (objects == null || objects.isEmpty()) {
            return;
        }
        Collection beans = convertToBeans(objects);
        merge(beans);
    }



    @Override
    public void delete(BeanId beanId) {
        Preconditions.checkNotNull(beanId);
        doLookup();
        beanManager.delete(beanId);
        core.cacheRemove(beanId);
    }

    @Override
    public void delete(String name, Collection instances) {
        Preconditions.checkNotNull(name);
        if (instances == null || instances.isEmpty()) {
            return;
        }
        doLookup();
        beanManager.delete(name, instances);
        core.cacheRemove(name, instances);
    }

    @Override
    public Map getSchemas() {
        doLookup();
        return schemaManager.getSchemas();
    }

    @Override
    public BeanQuery newQuery(String schemaName) {
        doLookup();
        Schema schema = schemaManager.getSchema(schemaName);
        Preconditions.checkNotNull(schema, "Schema " + schemaName + " does not exist.");
        return beanManager.newQuery(schema);
    }

    private void initReferences(Collection beans) {
        Map indexed = BeanUtils.uniqueIndex(beans);
        for (Bean bean : beans) {
            for (String name : bean.getReferenceNames()) {
                List ids = bean.getReference(name);
                if (ids == null) {
                    continue;
                }
                for (BeanId id : ids) {
                    Bean ref = indexed.get(id);
                    if (ref == null) {
                        // TODO: investigate if eager is really needed
                        Optional optionalRef = beanManager.getEager(id);
                        if (optionalRef.isPresent()) {
                            core.setSchema(optionalRef.get(), schemaManager.getSchemas());
                            ref = optionalRef.get();
                        }
                    }
                    id.setBean(ref);
                }
            }
        }
    }

    private void validateMerge(Collection mergebeans) {

        Map beansToValidate = beanManager.getBeanToValidate(mergebeans);
        core.setSchema(schemaManager.getSchemas(), beansToValidate);
        // 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 mergeBeanReferences = new ArrayList<>();
            ArrayList checked = new ArrayList<>();
            findReferences(mergebean.getId(), beansToValidate.values(), mergeBeanReferences,
                    checked);
            // merge list references
            merge(mergeBeanReferences, mergebean);
        }
        // ready to validate
        validator.get().validate(beansToValidate.values());

    }

    private void validateSet(Collection setbeans) {
        Map beansToValidate = beanManager.getBeanToValidate(setbeans);
        core.setSchema(schemaManager.getSchemas(), beansToValidate);
        // 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 setBeanReferences = new ArrayList<>();
            ArrayList 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);
        }
        validator.get().validate(beansToValidate.values());
    }

    /**
     * Does a recursive check if predecessor have a particular reference and if
     * so return those predecessor references.
     */
    private List findReferences(BeanId reference, Collection predecessors,
            ArrayList matches, ArrayList checked) {

        for (Bean predecessor : predecessors) {
            findReferences(reference, predecessor, matches, checked);
        }
        return matches;
    }

    private void findReferences(BeanId reference, Bean predecessor, ArrayList matches,
            ArrayList 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 sources, Bean mergeBean) {
        HashMap cache = new HashMap<>();
        for (Bean source : sources) {
            for (String name : mergeBean.getPropertyNames()) {
                List values = mergeBean.getValues(name);
                if (values == null || values.size() == 0) {
                    continue;
                }
                source.setProperty(name, values);
            }
            for (String name : mergeBean.getReferenceNames()) {
                List 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 optional = beanManager.getLazy(beanId);
                        if (!optional.isPresent()) {
                            throw CFG301_MISSING_RUNTIME_REF(beanId);
                        }
                        bean = optional.get();
                        core.setSchema(bean, schemaManager.getSchemas());
                        cache.put(beanId, bean);
                    }
                    beanId.setBean(bean);
                }
                source.setReferences(name, refs);
            }

        }
    }

    /**
     * Used for setting or creating a single bean.
     */
    @SuppressWarnings("unused")
    private void initalizeReferences(Bean bean) {
        for (String name : bean.getReferenceNames()) {
            List values = bean.getReference(name);
            if (values == null) {
                continue;
            }
            for (BeanId beanId : values) {
                Optional ref = beanManager.getEager(beanId);
                if (ref.isPresent()) {
                    beanId.setBean(ref.get());
                }
                core.setSchema(beanId.getBean(), schemaManager.getSchemas());
            }
        }
    }

    /**
     * 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 beans) {
        Map userProvided = BeanUtils.uniqueIndex(beans);
        for (Bean bean : beans) {
            for (String name : bean.getReferenceNames()) {
                List 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 optional = beanManager.getEager(beanId);
                        if (optional.isPresent()) {
                            ref = optional.get();
                        }
                    }
                    beanId.setBean(ref);
                    core.setSchema(beanId.getBean(), schemaManager.getSchemas());
                }
            }
        }
    }

    private void setSingletonReferences(Bean bean, Map schemas) {
        Schema s = bean.getSchema();
        for (SchemaPropertyRef ref : s.get(SchemaPropertyRef.class)) {
            if (ref.isSingleton()) {
                Schema singletonSchema = schemas.get(ref.getSchemaName());
                Optional 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(), schemas);
                    bean.setReference(ref.getName(), singletonId);
                }
            }
        }
    }

    private void doLookup() {
        // core would already be injected in a cdi environment
        if (core == null) {
            core = new ConfigCore();
        }
        if (cacheManager == null) {
            cacheManager = core.lookupCacheManager();
        }
        if (validator == null) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            try {
                cl.loadClass(JSR303_1_0_CLASSNAME);
                validator = Optional.of(new Jsr303Validator());
            } catch (Exception e) {
                validator = Optional.absent();
            }
        }
    }

    private Collection convertToBeans(Collection objects) {
        ArrayList beans = new ArrayList<>();
        for (Object object : objects) {
            if(object instanceof Bean) {
                beans.add((Bean) object);
            } else {
                Bean bean = conversion.convert(object, Bean.class);
                beans.add(bean);
            }
        }
        return beans;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy