gaffer.store.schema.SchemaElementDefinition.java Source code

Java tutorial

Introduction

Here is the source code for gaffer.store.schema.SchemaElementDefinition.java

Source

/*
 * Copyright 2016 Crown Copyright
 *
 * 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 gaffer.store.schema;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import gaffer.data.TransformIterable;
import gaffer.data.element.ElementComponentKey;
import gaffer.data.element.IdentifierType;
import gaffer.data.element.function.ElementAggregator;
import gaffer.data.element.function.ElementFilter;
import gaffer.data.elementdefinition.ElementDefinition;
import gaffer.data.elementdefinition.exception.SchemaException;
import gaffer.function.FilterFunction;
import gaffer.function.IsA;
import gaffer.function.context.ConsumerFunctionContext;
import gaffer.function.context.PassThroughFunctionContext;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * A <code>SchemaElementDefinition</code> is the representation of a single group in a
 * {@link Schema}.
 * Each element needs identifiers and can optionally have properties, an aggregator and a validator.
 *
 * @see SchemaElementDefinition.Builder
 */
public abstract class SchemaElementDefinition implements ElementDefinition {
    private static final Map<String, Class<?>> CLASSES = new HashMap<>();

    /**
     * A validator to validate the element definition
     */
    private final SchemaElementDefinitionValidator elementDefValidator;

    /**
     * Property map of property name to accepted type.
     */
    private LinkedHashMap<String, String> properties;

    /**
     * Identifier map of identifier type to accepted type.
     */
    private LinkedHashMap<IdentifierType, String> identifiers;

    private ElementFilter validator;

    /**
     * The <code>TypeDefinitions</code> provides the different element identifier value types and property value types.
     *
     * @see TypeDefinitions
     */
    private TypeDefinitions typesLookup;

    public SchemaElementDefinition() {
        this.elementDefValidator = new SchemaElementDefinitionValidator();
        properties = new LinkedHashMap<>();
        identifiers = new LinkedHashMap<>();
    }

    /**
     * Uses the element definition validator to validate the element definition.
     *
     * @return true if the element definition is valid, otherwise false.
     */
    public boolean validate() {
        return elementDefValidator.validate(this);
    }

    @Override
    public void merge(final ElementDefinition elementDef) {
        if (elementDef instanceof SchemaElementDefinition) {
            merge(((SchemaElementDefinition) elementDef));
        } else {
            throw new IllegalArgumentException(
                    "Cannot merge a schema element definition with a " + elementDef.getClass());
        }
    }

    public void merge(final SchemaElementDefinition elementDef) {
        for (Entry<String, String> entry : elementDef.getPropertyMap().entrySet()) {
            final String newProp = entry.getKey();
            final String newPropTypeName = entry.getValue();
            if (!properties.containsKey(newProp)) {
                properties.put(newProp, newPropTypeName);
            } else {
                final String typeName = properties.get(newProp);
                if (!typeName.equals(newPropTypeName)) {
                    throw new SchemaException("Unable to merge schemas. Conflict of types with property " + newProp
                            + ". Type names are: " + typeName + " and " + newPropTypeName);
                }
            }
        }

        for (Entry<IdentifierType, String> entry : elementDef.getIdentifierMap().entrySet()) {
            final IdentifierType newId = entry.getKey();
            final String newIdTypeName = entry.getValue();
            if (!identifiers.containsKey(newId)) {
                identifiers.put(newId, newIdTypeName);
            } else {
                final String typeName = identifiers.get(newId);
                if (!typeName.equals(newIdTypeName)) {
                    throw new SchemaException("Unable to merge schemas. Conflict of types with identifier " + newId
                            + ". Type names are: " + typeName + " and " + newIdTypeName);
                }
            }
        }

        if (null == validator) {
            validator = elementDef.validator;
        } else if (null != elementDef.getOriginalValidateFunctions()) {
            validator.addFunctions(Arrays.asList(elementDef.getOriginalValidateFunctions()));
        }
    }

    public Set<String> getProperties() {
        return properties.keySet();
    }

    public boolean containsProperty(final String propertyName) {
        return properties.containsKey(propertyName);
    }

    @JsonGetter("properties")
    public Map<String, String> getPropertyMap() {
        return Collections.unmodifiableMap(properties);
    }

    @JsonSetter("properties")
    protected void setPropertyMap(final LinkedHashMap<String, String> properties) {
        this.properties = properties;
    }

    @JsonIgnore
    public Collection<IdentifierType> getIdentifiers() {
        return identifiers.keySet();
    }

    @JsonIgnore
    public Map<IdentifierType, String> getIdentifierMap() {
        return identifiers;
    }

    public boolean containsIdentifier(final IdentifierType identifierType) {
        return identifiers.containsKey(identifierType);
    }

    public String getPropertyTypeName(final String propertyName) {
        return properties.get(propertyName);
    }

    public String getIdentifierTypeName(final IdentifierType idType) {
        return identifiers.get(idType);
    }

    @JsonIgnore
    public Collection<String> getPropertyTypeNames() {
        return properties.values();
    }

    @JsonIgnore
    public Collection<String> getIdentifierTypeNames() {
        return identifiers.values();
    }

    public Class<?> getClass(final ElementComponentKey key) {
        if (key.isId()) {
            return getIdentifierClass(key.getIdentifierType());
        }

        return getPropertyClass(key.getPropertyName());
    }

    public Class<?> getClass(final String className) {
        if (null == className) {
            return null;
        }

        Class<?> clazz = CLASSES.get(className);
        if (null == clazz) {
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Class could not be found: " + className, e);
            }
            CLASSES.put(className, clazz);
        }

        return clazz;
    }

    /**
     * @return a cloned instance of {@link ElementAggregator} fully populated with all the
     * {@link gaffer.function.AggregateFunction}s defined in this
     * {@link SchemaElementDefinition} and also the
     * {@link gaffer.function.AggregateFunction}s defined in the corresponding property value
     * {@link TypeDefinition}s.
     */
    @JsonIgnore
    public ElementAggregator getAggregator() {
        final ElementAggregator aggregator = new ElementAggregator();
        for (Map.Entry<String, String> entry : getPropertyMap().entrySet()) {
            addTypeAggregateFunctions(aggregator, new ElementComponentKey(entry.getKey()), entry.getValue());
        }

        return aggregator;
    }

    /**
     * @return a cloned instance of {@link gaffer.data.element.function.ElementFilter} fully populated with all the
     * {@link gaffer.function.FilterFunction}s defined in this
     * {@link SchemaElementDefinition} and also the
     * {@link SchemaElementDefinition} and also the
     * {@link gaffer.function.FilterFunction}s defined in the corresponding identifier and property value
     * {@link TypeDefinition}s.
     */
    @JsonIgnore
    public ElementFilter getValidator() {
        return getValidator(true);
    }

    public ElementFilter getValidator(final boolean includeIsA) {
        final ElementFilter fullValidator = null != validator ? validator.clone() : new ElementFilter();
        for (Map.Entry<IdentifierType, String> entry : getIdentifierMap().entrySet()) {
            final ElementComponentKey key = new ElementComponentKey(entry.getKey());
            if (includeIsA) {
                addIsAFunction(fullValidator, key, entry.getValue());
            }
            addTypeValidatorFunctions(fullValidator, key, entry.getValue());
        }
        for (Map.Entry<String, String> entry : getPropertyMap().entrySet()) {
            final ElementComponentKey key = new ElementComponentKey(entry.getKey());
            if (includeIsA) {
                addIsAFunction(fullValidator, key, entry.getValue());
            }
            addTypeValidatorFunctions(fullValidator, key, entry.getValue());
        }

        return fullValidator;
    }

    @JsonSetter("validator")
    private void setValidator(final ElementFilter validator) {
        this.validator = validator;
    }

    @SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", justification = "null is only returned when the validator is null")
    @JsonGetter("validateFunctions")
    public ConsumerFunctionContext<ElementComponentKey, FilterFunction>[] getOriginalValidateFunctions() {
        if (null != validator) {
            final List<ConsumerFunctionContext<ElementComponentKey, FilterFunction>> functions = validator
                    .getFunctions();
            return functions.toArray(new ConsumerFunctionContext[functions.size()]);
        }

        return null;
    }

    @JsonSetter("validateFunctions")
    public void addValidateFunctions(
            final ConsumerFunctionContext<ElementComponentKey, FilterFunction>... functions) {
        if (null == validator) {
            validator = new ElementFilter();
        }
        validator.addFunctions(Arrays.asList(functions));
    }

    public void setTypesLookup(final TypeDefinitions newTypes) {
        if (null != typesLookup && null != newTypes) {
            newTypes.merge(typesLookup);
        }

        typesLookup = newTypes;
    }

    @JsonIgnore
    public Iterable<TypeDefinition> getPropertyTypeDefs() {
        return new TransformIterable<String, TypeDefinition>(getPropertyMap().values()) {
            @Override
            protected TypeDefinition transform(final String typeName) {
                return getTypeDef(typeName);
            }
        };
    }

    public TypeDefinition getPropertyTypeDef(final String property) {
        if (containsProperty(property)) {
            return getTypeDef(getPropertyMap().get(property));
        }

        return null;
    }

    public Class<?> getPropertyClass(final String propertyName) {
        final String typeName = getPropertyTypeName(propertyName);
        return null != typeName ? getTypeDef(typeName).getClazz() : null;
    }

    public Class<?> getIdentifierClass(final IdentifierType idType) {
        final String typeName = getIdentifierTypeName(idType);
        return null != typeName ? getTypeDef(typeName).getClazz() : null;
    }

    @JsonIgnore
    protected TypeDefinitions getTypesLookup() {
        if (null == typesLookup) {
            setTypesLookup(new TypeDefinitions());
        }

        return typesLookup;
    }

    private void addTypeValidatorFunctions(final ElementFilter fullValidator, final ElementComponentKey key,
            final String classOrTypeName) {
        final TypeDefinition type = getTypeDef(classOrTypeName);
        if (null != type.getValidator()) {
            for (ConsumerFunctionContext<ElementComponentKey, FilterFunction> function : type.getValidator().clone()
                    .getFunctions()) {
                final List<ElementComponentKey> selection = function.getSelection();
                if (null == selection || selection.isEmpty()) {
                    function.setSelection(Collections.singletonList(key));
                } else if (!selection.contains(key)) {
                    selection.add(key);
                }
                fullValidator.addFunction(function);
            }
        }
    }

    private void addTypeAggregateFunctions(final ElementAggregator aggregator, final ElementComponentKey key,
            final String typeName) {
        final TypeDefinition type = getTypeDef(typeName);
        if (null != type.getAggregateFunction()) {
            aggregator.addFunction(new PassThroughFunctionContext<>(type.getAggregateFunction().statelessClone(),
                    Collections.singletonList(key)));
        }
    }

    private void addIsAFunction(final ElementFilter fullValidator, final ElementComponentKey key,
            final String classOrTypeName) {
        fullValidator.addFunction(new ConsumerFunctionContext<ElementComponentKey, FilterFunction>(
                new IsA(getTypeDef(classOrTypeName).getClazz()), Collections.singletonList(key)));
    }

    private TypeDefinition getTypeDef(final String typeName) {
        return getTypesLookup().getType(typeName);
    }

    protected static class Builder {
        private final SchemaElementDefinition elDef;

        protected Builder(final SchemaElementDefinition elDef) {
            this.elDef = elDef;
        }

        protected Builder property(final String propertyName, final String typeName) {
            elDef.properties.put(propertyName, typeName);
            return this;
        }

        protected Builder identifier(final IdentifierType identifierType, final String typeName) {
            elDef.identifiers.put(identifierType, typeName);
            return this;
        }

        protected Builder validator(final ElementFilter validator) {
            elDef.setValidator(validator);
            return this;
        }

        protected Builder property(final String propertyName, final Class<?> clazz) {
            return property(propertyName, clazz.getName(), clazz);
        }

        protected Builder property(final String propertyName, final String typeName, final TypeDefinition type) {
            type(typeName, type);
            return property(propertyName, typeName);
        }

        protected Builder property(final String propertyName, final String typeName, final Class<?> typeClass) {
            return property(propertyName, typeName, new TypeDefinition(typeClass));
        }

        protected Builder type(final String typeName, final TypeDefinition type) {
            final TypeDefinitions types = getElementDef().getTypesLookup();
            final TypeDefinition exisitingType = types.get(typeName);
            if (null == exisitingType) {
                types.put(typeName, type);
            } else if (!exisitingType.equals(type)) {
                throw new IllegalArgumentException(
                        "The type provided conflicts with an existing type with the same name");
            }

            return this;
        }

        protected SchemaElementDefinition build() {
            return elDef;
        }

        protected SchemaElementDefinition getElementDef() {
            return elDef;
        }
    }
}