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

com.blazebit.domain.declarative.impl.spi.DeclarativeDomainConfigurationImpl Maven / Gradle / Ivy

/*
 * Copyright 2019 Blazebit.
 *
 * 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 com.blazebit.domain.declarative.impl.spi;

import com.blazebit.annotation.AnnotationUtils;
import com.blazebit.domain.declarative.Transient;
import com.blazebit.domain.declarative.spi.DeclarativeAttributeMetadataProcessor;
import com.blazebit.domain.declarative.spi.DeclarativeFunctionMetadataProcessor;
import com.blazebit.domain.declarative.spi.DeclarativeFunctionParameterMetadataProcessor;
import com.blazebit.domain.declarative.spi.DeclarativeMetadataProcessor;
import com.blazebit.domain.declarative.spi.TypeResolver;
import com.blazebit.domain.boot.model.DomainBuilder;
import com.blazebit.domain.boot.model.DomainFunctionBuilder;
import com.blazebit.domain.boot.model.EntityDomainTypeBuilder;
import com.blazebit.domain.boot.model.EnumDomainTypeBuilder;
import com.blazebit.domain.boot.model.MetadataDefinition;
import com.blazebit.domain.declarative.DeclarativeDomainConfiguration;
import com.blazebit.domain.declarative.DiscoverMode;
import com.blazebit.domain.declarative.DomainAttribute;
import com.blazebit.domain.declarative.DomainFunction;
import com.blazebit.domain.declarative.DomainFunctionParam;
import com.blazebit.domain.declarative.DomainFunctions;
import com.blazebit.domain.declarative.DomainType;
import com.blazebit.domain.runtime.model.DomainModel;
import com.blazebit.reflection.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

/**
 * @author Christian Beikov
 * @since 1.0.0
 */
public class DeclarativeDomainConfigurationImpl implements DeclarativeDomainConfiguration {

    private static final Logger LOG = Logger.getLogger(DeclarativeDomainConfigurationImpl.class.getName());
    private static final MetadataDefinition[] EMPTY = new MetadataDefinition[0];
    private final DomainBuilder domainBuilder;
    private final Map, List>> entityMetadataProcessors = new HashMap<>();
    private final Map, List>> attributeMetadataProcessors = new HashMap<>();
    private final Map, List>> functionMetadataProcessors = new HashMap<>();
    private final Map, List>> functionParameterMetadataProcessors = new HashMap<>();
    private TypeResolver typeResolver;

    public DeclarativeDomainConfigurationImpl(DomainBuilder domainBuilder) {
        this.domainBuilder = domainBuilder;
    }

    @Override
    public DeclarativeDomainConfiguration withMetadataProcessor(DeclarativeMetadataProcessor metadataProcessor) {
        entityMetadataProcessors.compute(metadataProcessor.getProcessingAnnotation(), (k, v) -> new ArrayList<>()).add((DeclarativeMetadataProcessor) metadataProcessor);
        return this;
    }

    @Override
    public DeclarativeDomainConfiguration withMetadataProcessor(DeclarativeAttributeMetadataProcessor metadataProcessor) {
        attributeMetadataProcessors.compute(metadataProcessor.getProcessingAnnotation(), (k, v) -> new ArrayList<>()).add((DeclarativeAttributeMetadataProcessor) metadataProcessor);
        return this;
    }

    @Override
    public DeclarativeDomainConfiguration withMetadataProcessor(DeclarativeFunctionMetadataProcessor metadataProcessor) {
        functionMetadataProcessors.compute(metadataProcessor.getProcessingAnnotation(), (k, v) -> new ArrayList<>()).add((DeclarativeFunctionMetadataProcessor) metadataProcessor);
        return this;
    }

    @Override
    public DeclarativeDomainConfiguration withMetadataProcessor(DeclarativeFunctionParameterMetadataProcessor metadataProcessor) {
        functionParameterMetadataProcessors.compute(metadataProcessor.getProcessingAnnotation(), (k, v) -> new ArrayList<>()).add((DeclarativeFunctionParameterMetadataProcessor) metadataProcessor);
        return this;
    }

    @Override
    public TypeResolver getTypeResolver() {
        return typeResolver;
    }

    @Override
    public DeclarativeDomainConfiguration setTypeResolver(TypeResolver typeResolver) {
        this.typeResolver = typeResolver;
        return this;
    }

    @Override
    public DomainModel createDomainModel() {
        return domainBuilder.build();
    }

    @Override
    public DeclarativeDomainConfiguration addDomainFunctions(Class domainFunctionsClass) {
        DomainFunctions domainFunctions = AnnotationUtils.findAnnotation(domainFunctionsClass, DomainFunctions.class);
        if (domainFunctions == null) {
            throw new IllegalArgumentException("No domain functions annotation found on type: " + domainFunctionsClass);
        }

        boolean implicitDiscovery = domainFunctions.discoverMode() != DiscoverMode.EXPLICIT;
        Set> superTypes = ReflectionUtils.getSuperTypes(domainFunctionsClass);
        Set handledMethods = new HashSet<>();

        for (Class c : superTypes) {
            for (Method method : c.getDeclaredMethods()) {
                if (Modifier.isStatic(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers()) && !method.isBridge()) {
                    final String methodName = method.getName();
                    if (handledMethods.add(methodName)) {
                        handleDomainFunctionMethod(domainFunctionsClass, method, implicitDiscovery);
                    }
                }
            }
        }

        return this;
    }

    private void handleDomainFunctionMethod(Class domainFunctionsClass, Method method, boolean implicitDiscovery) {
        DomainFunction domainFunction = AnnotationUtils.findAnnotation(method, DomainFunction.class);
        if (domainFunction == null) {
            if (implicitDiscovery && AnnotationUtils.findAnnotation(method, Transient.class) == null) {
                domainFunction = new DomainFunctionLiteral();
            } else {
                return;
            }
        }

        String name = domainFunction.value();
        if (name.isEmpty()) {
            name = method.getName();
        }

        Class type = domainFunction.collection() ? Collection.class : domainFunction.type();
        String typeName = domainFunction.collection() ? "Collection" : domainFunction.typeName();
        Class elementType = domainFunction.collection() ? domainFunction.type() : void.class;
        String elementTypeName = domainFunction.collection() ? domainFunction.typeName() : "";

        DomainFunctionBuilder function = domainBuilder.createFunction(name);

        ResolvedType resolvedType = resolveType(typeName, elementTypeName, type, elementType, domainFunctionsClass, method, null);
        if (resolvedType.collection) {
            if (resolvedType.typeName.isEmpty()) {
                function.withCollectionResultType(resolvedType.type);
            } else {
                function.withCollectionResultType(resolvedType.typeName);
            }
        } else {
            if (resolvedType.typeName.isEmpty()) {
                function.withCollectionResultType(resolvedType.type);
            } else {
                function.withCollectionResultType(resolvedType.typeName);
            }
        }

        // automatic metadata discovery via meta annotations
        for (Map.Entry, List>> entry : functionMetadataProcessors.entrySet()) {
            if (entry.getKey() == null) {
                for (DeclarativeFunctionMetadataProcessor processor : entry.getValue()) {
                    MetadataDefinition metadataDefinition = processor.process(domainFunctionsClass, method, null);
                    if (metadataDefinition != null) {
                        function.withMetadata(metadataDefinition);
                    }
                }
            } else {
                Annotation annotation = AnnotationUtils.findAnnotation(method, entry.getKey());
                if (annotation != null) {
                    for (DeclarativeFunctionMetadataProcessor processor : entry.getValue()) {
                        MetadataDefinition metadataDefinition = processor.process(domainFunctionsClass, method, annotation);
                        if (metadataDefinition != null) {
                            function.withMetadata(metadataDefinition);
                        }
                    }
                }
            }
        }

        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            handleDomainFunctionParameter(function, domainFunctionsClass, method, parameters[i]);
        }

        function.build();
    }

    private void handleDomainFunctionParameter(DomainFunctionBuilder function, Class domainFunctionsClass, Method method, Parameter parameter) {
        String parameterName = parameter.getName();

        Class type;
        String typeName;
        Class elementType;
        String elementTypeName;
        DomainFunctionParam param = parameter.getAnnotation(DomainFunctionParam.class);
        if (param == null) {
            type = parameter.getType();
            typeName = "";
            elementType = null;
            elementTypeName = "";
        } else {
            if (!param.value().isEmpty()) {
                parameterName = param.value();
            }

            type = param.collection() ? Collection.class : param.type();
            typeName = param.collection() ? "Collection" : param.typeName();
            elementType = param.collection() ? param.type() : void.class;
            elementTypeName = param.collection() ? param.typeName() : "";
        }

        // automatic metadata discovery via meta annotations
        List> metadataDefinitions = new ArrayList<>();
        for (Map.Entry, List>> entry : functionParameterMetadataProcessors.entrySet()) {
            if (entry.getKey() == null) {
                for (DeclarativeFunctionParameterMetadataProcessor processor : entry.getValue()) {
                    MetadataDefinition metadataDefinition = processor.process(domainFunctionsClass, method, parameter, null);
                    if (metadataDefinition != null) {
                        metadataDefinitions.add(metadataDefinition);
                    }
                }
            } else {
                Annotation annotation = AnnotationUtils.findAnnotation(method, entry.getKey());
                if (annotation != null) {
                    for (DeclarativeFunctionParameterMetadataProcessor processor : entry.getValue()) {
                        MetadataDefinition metadataDefinition = processor.process(domainFunctionsClass, method, parameter, annotation);
                        if (metadataDefinition != null) {
                            metadataDefinitions.add(metadataDefinition);
                        }
                    }
                }
            }
        }

        MetadataDefinition[] metadataDefinitionArray;
        if (metadataDefinitions.isEmpty()) {
            metadataDefinitionArray = EMPTY;
        } else {
            metadataDefinitionArray = metadataDefinitions.toArray(new MetadataDefinition[metadataDefinitions.size()]);
        }

        ResolvedType resolvedType = resolveType(typeName, elementTypeName, type, elementType, domainFunctionsClass, method, parameter);
        if (resolvedType.collection) {
            if (resolvedType.typeName.isEmpty()) {
                function.withArgument(parameterName, resolvedType.type, metadataDefinitionArray);
            } else {
                function.withArgument(parameterName, resolvedType.typeName, metadataDefinitionArray);
            }
        } else {
            if (resolvedType.typeName.isEmpty()) {
                function.withArgument(parameterName, resolvedType.type, metadataDefinitionArray);
            } else {
                function.withArgument(parameterName, resolvedType.typeName, metadataDefinitionArray);
            }
        }
    }

    @Override
    public DeclarativeDomainConfiguration addDomainType(Class domainTypeClass) {
        DomainType domainType = AnnotationUtils.findAnnotation(domainTypeClass, DomainType.class);
        if (domainType == null) {
            throw new IllegalArgumentException("No domain type annotation found on type: " + domainTypeClass);
        }

        String name = domainType.value();
        if (name.isEmpty()) {
            name = domainTypeClass.getSimpleName();
        }

        boolean implicitDiscovery = domainType.discoverMode() != DiscoverMode.EXPLICIT;
        Set> superTypes = ReflectionUtils.getSuperTypes(domainTypeClass);

        // automatic metadata discovery via meta annotations
        List> metadataDefinitions = new ArrayList<>();

        for (Class type : superTypes) {
            for (Map.Entry, List>> entry : entityMetadataProcessors.entrySet()) {
                if (entry.getKey() == null) {
                    for (DeclarativeMetadataProcessor processor : entry.getValue()) {
                        MetadataDefinition metadataDefinition = processor.process(domainTypeClass, null);
                        if (metadataDefinition != null) {
                            metadataDefinitions.add(metadataDefinition);
                        }
                    }
                } else {
                    Annotation annotation = AnnotationUtils.findAnnotation(type, entry.getKey());
                    if (annotation != null) {
                        for (DeclarativeMetadataProcessor processor : entry.getValue()) {
                            MetadataDefinition metadataDefinition = processor.process(domainTypeClass, annotation);
                            if (metadataDefinition != null) {
                                metadataDefinitions.add(metadataDefinition);
                            }
                        }
                    }
                }
            }
        }

        if (domainTypeClass.isEnum()) {
            Class> enumDomainTypeClass = (Class>) domainTypeClass;
            EnumDomainTypeBuilder enumType = domainBuilder.createEnumType(name, enumDomainTypeClass);
            for (int i = 0; i < metadataDefinitions.size(); i++) {
                enumType.withMetadata(metadataDefinitions.get(i));
            }
            Enum[] enumConstants = (Enum[]) domainTypeClass.getEnumConstants();
            for (int i = 0; i < (enumConstants).length; i++) {
                handleEnumConstant(enumType, enumDomainTypeClass, enumConstants[i]);
            }
        } else {
            EntityDomainTypeBuilder entityType = domainBuilder.createEntityType(name, domainTypeClass);
            for (int i = 0; i < metadataDefinitions.size(); i++) {
                entityType.withMetadata(metadataDefinitions.get(i));
            }

            Set handledMethods = new HashSet<>();

            for (Class c : superTypes) {
                for (Method method : c.getDeclaredMethods()) {
                    if (!Modifier.isPrivate(method.getModifiers()) && !method.isBridge()) {
                        final String methodName = method.getName();
                        if (handledMethods.add(methodName)) {
                            handleDomainAttributeMethod(entityType, domainTypeClass, method, implicitDiscovery);
                        }
                    }
                }
            }

            entityType.build();
        }

        return this;
    }

    private void handleEnumConstant(EnumDomainTypeBuilder enumType, Class> domainTypeClass, Enum enumConstant) {
        // automatic metadata discovery via meta annotations
        List> metadataDefinitions = new ArrayList<>();
        Class constantClass = enumConstant.getClass();
        for (Map.Entry, List>> entry : entityMetadataProcessors.entrySet()) {
            if (entry.getKey() == null) {
                for (DeclarativeMetadataProcessor processor : entry.getValue()) {
                    MetadataDefinition metadataDefinition = processor.process(constantClass, null);
                    if (metadataDefinition != null) {
                        metadataDefinitions.add(metadataDefinition);
                    }
                }
            } else {
                Annotation annotation = AnnotationUtils.findAnnotation(constantClass, entry.getKey());
                if (annotation != null) {
                    for (DeclarativeMetadataProcessor processor : entry.getValue()) {
                        MetadataDefinition metadataDefinition = processor.process(constantClass, annotation);
                        if (metadataDefinition != null) {
                            metadataDefinitions.add(metadataDefinition);
                        }
                    }
                }
            }
        }

        MetadataDefinition[] metadataDefinitionArray;
        if (metadataDefinitions.isEmpty()) {
            metadataDefinitionArray = EMPTY;
        } else {
            metadataDefinitionArray = metadataDefinitions.toArray(new MetadataDefinition[metadataDefinitions.size()]);
        }

        enumType.withValue(enumConstant.name(), metadataDefinitionArray);
    }

    private void handleDomainAttributeMethod(EntityDomainTypeBuilder entityType, Class domainTypeClass, Method method, boolean implicitDiscovery) {
        DomainAttribute domainAttribute = AnnotationUtils.findAnnotation(method, DomainAttribute.class);
        if (domainAttribute == null) {
            if (implicitDiscovery && ReflectionUtils.isGetter(method) && AnnotationUtils.findAnnotation(method, Transient.class) == null) {
                domainAttribute = new DomainAttributeLiteral();
            } else {
                return;
            }
        } else if (!ReflectionUtils.isGetter(method)) {
            throw new IllegalArgumentException("Non-getter can't be a domain type attribute: " + method);
        }

        String name = getAttributeName(method);
        Class type = domainAttribute.collection() ? Collection.class : domainAttribute.value();
        String typeName = domainAttribute.collection() ? "Collection" : domainAttribute.typeName();
        Class elementType = domainAttribute.collection() ? domainAttribute.value() : void.class;
        String elementTypeName = domainAttribute.collection() ? domainAttribute.typeName() : "";

        // automatic metadata discovery via meta annotations
        List> metadataDefinitions = new ArrayList<>();
        for (Map.Entry, List>> entry : attributeMetadataProcessors.entrySet()) {
            if (entry.getKey() == DomainAttribute.class) {
                for (DeclarativeAttributeMetadataProcessor processor : entry.getValue()) {
                    MetadataDefinition metadataDefinition = processor.process(domainTypeClass, method, domainAttribute);
                    if (metadataDefinition != null) {
                        metadataDefinitions.add(metadataDefinition);
                    }
                }
            } else if (entry.getKey() == null) {
                for (DeclarativeAttributeMetadataProcessor processor : entry.getValue()) {
                    MetadataDefinition metadataDefinition = processor.process(domainTypeClass, method, null);
                    if (metadataDefinition != null) {
                        metadataDefinitions.add(metadataDefinition);
                    }
                }
            } else {
                Annotation annotation = AnnotationUtils.findAnnotation(method, entry.getKey());
                if (annotation != null) {
                    for (DeclarativeAttributeMetadataProcessor processor : entry.getValue()) {
                        MetadataDefinition metadataDefinition = processor.process(domainTypeClass, method, annotation);
                        if (metadataDefinition != null) {
                            metadataDefinitions.add(metadataDefinition);
                        }
                    }
                }
            }
        }

        MetadataDefinition[] metadataDefinitionArray;
        if (metadataDefinitions.isEmpty()) {
            metadataDefinitionArray = EMPTY;
        } else {
            metadataDefinitionArray = metadataDefinitions.toArray(new MetadataDefinition[metadataDefinitions.size()]);
        }

        ResolvedType resolvedType = resolveType(typeName, elementTypeName, type, elementType, domainTypeClass, method, null);
        if (resolvedType.collection) {
            if (resolvedType.typeName.isEmpty()) {
                entityType.addCollectionAttribute(name, resolvedType.type, metadataDefinitionArray);
            } else {
                entityType.addCollectionAttribute(name, resolvedType.typeName, metadataDefinitionArray);
            }
        } else {
            if (resolvedType.typeName.isEmpty()) {
                entityType.addAttribute(name, resolvedType.type, metadataDefinitionArray);
            } else {
                entityType.addAttribute(name, resolvedType.typeName, metadataDefinitionArray);
            }
        }
    }

    protected ResolvedType resolveType(String typeName, String elementTypeName, Class type, Class elementType, Class baseClass, Method method, Parameter parameter) {
        if (!typeName.isEmpty()) {
            if ("Collection".equals(typeName)) {
                if (elementTypeName.isEmpty()) {
                    return ResolvedType.collection(elementType);
                } else {
                    return ResolvedType.collection(elementTypeName);
                }
            } else {
                return ResolvedType.basic(typeName);
            }
        } else {
            Class returnType;
            if (type == void.class) {
                if (typeResolver != null) {
                    Object resolvedType = typeResolver.resolve(method.getGenericReturnType());
                    if (resolvedType instanceof String) {
                        return ResolvedType.basic((String) resolvedType);
                    } else if (resolvedType instanceof Class) {
                        return ResolvedType.basic((Class) resolvedType);
                    } else if (resolvedType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) resolvedType;
                        Type rawType = parameterizedType.getRawType();
                        if ("Collection".equals(rawType.getTypeName()) || rawType instanceof Class && Collection.class.isAssignableFrom((Class) rawType)) {
                            Type[] typeArguments = parameterizedType.getActualTypeArguments();
                            if (typeArguments.length > 0) {
                                if (typeArguments[0] instanceof Class) {
                                    return ResolvedType.collection((Class) typeArguments[0]);
                                } else {
                                    return ResolvedType.collection(typeArguments[0].getTypeName());
                                }
                            }
                        } else if (rawType instanceof Class) {
                            return ResolvedType.basic((Class) rawType);
                        }
                    }
                }

                if (method == null) {
                    returnType = parameter.getType();
                    if (Collection.class.isAssignableFrom(returnType)) {
                        Type[] typeArguments = ((ParameterizedType) parameter.getParameterizedType()).getActualTypeArguments();
                        elementType = (Class) ReflectionUtils.resolve(baseClass, typeArguments[0]);
                    } else {
                        elementType = null;
                    }
                } else {
                    returnType = ReflectionUtils.getResolvedMethodReturnType(baseClass, method);
                    if (Collection.class.isAssignableFrom(returnType)) {
                        Class[] typeArguments = ReflectionUtils.getResolvedMethodReturnTypeArguments(baseClass, method);
                        elementType = typeArguments[0];
                    } else {
                        elementType = null;
                    }
                }
            } else {
                returnType = type;
                elementType = null;
            }
            if (Collection.class.isAssignableFrom(returnType)) {
                return ResolvedType.collection(elementType);
            } else {
                return ResolvedType.basic(returnType);
            }
        }
    }

    private static class ResolvedType {
        private final String typeName;
        private final Class type;
        private final boolean collection;

        public ResolvedType(String typeName, Class type, boolean collection) {
            this.typeName = typeName;
            this.type = type;
            this.collection = collection;
        }

        public static ResolvedType basic(String typeName) {
            return new ResolvedType(typeName, null, false);
        }

        public static ResolvedType basic(Class type) {
            return new ResolvedType("", type, false);
        }

        public static ResolvedType collection(String typeName) {
            return new ResolvedType(typeName, null, true);
        }

        public static ResolvedType collection(Class type) {
            return new ResolvedType("", type, true);
        }
    }

    protected static String getAttributeName(Method getterOrSetter) {
        String name = getterOrSetter.getName();
        StringBuilder sb = new StringBuilder(name.length());
        int index = name.startsWith("is") ? 2 : 3;
        char firstAttributeNameChar = name.charAt(index);
        return sb.append(Character.toLowerCase(firstAttributeNameChar))
                .append(name, index + 1, name.length())
                .toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy