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

org.nuiton.topia.templates.sql.TopiaMetadataModelBuilder Maven / Gradle / Ivy

package org.nuiton.topia.templates.sql;

/*-
 * #%L
 * Toolkit :: Templates
 * %%
 * Copyright (C) 2017 - 2024 Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.eugene.EugeneCoreTagValues;
import org.nuiton.eugene.GeneratorUtil;
import org.nuiton.eugene.models.object.ObjectModel;
import org.nuiton.eugene.models.object.ObjectModelAttribute;
import org.nuiton.eugene.models.object.ObjectModelClass;
import org.nuiton.eugene.models.object.ObjectModelEnumeration;
import org.nuiton.eugene.models.object.ObjectModelPackage;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataEntity;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataModel;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataModelVisitor;
import org.nuiton.topia.templates.TopiaExtensionTagValues;
import org.nuiton.topia.templates.TopiaHibernateTagValues;
import org.nuiton.topia.templates.TopiaTemplateHelper;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public class TopiaMetadataModelBuilder {

    private static final Logger log = LogManager.getLogger(TopiaMetadataModelBuilder.class);

    private static TopiaMetadataModel CACHE;
    private static TopiaMetadataModel FULL_CACHE;

    private final boolean verbose;
    private final ObjectModel model;
    private final TopiaTemplateHelper templateHelper;
    private final TopiaExtensionTagValues topiaExtensionTagValues;
    private final TopiaHibernateTagValues topiaHibernateTagValues;
    /**
     * Metadata for all entities of the model.
     */
    private final Map entities;

    public static TopiaMetadataModel build(boolean verbose, ObjectModel model, TopiaTemplateHelper templateHelper) {
        if (CACHE != null) {
            return CACHE;
        }
        log.info("Build topia metadata model for: " + Objects.requireNonNull(model).getName());
        return CACHE = new TopiaMetadataModelBuilder(verbose, model, templateHelper).build0(false);
    }

    public static TopiaMetadataModel buildFull(boolean verbose, ObjectModel model, TopiaTemplateHelper templateHelper) {
        if (FULL_CACHE != null) {
            return FULL_CACHE;
        }
        log.info("Build topia metadata model for: " + Objects.requireNonNull(model).getName());
        return FULL_CACHE = new TopiaMetadataModelBuilder(verbose, model, templateHelper).build0(true);
    }

    public TopiaMetadataModelBuilder(boolean verbose, ObjectModel model, TopiaTemplateHelper templateHelper) {
        this.verbose = verbose;
        this.model = model;
        this.templateHelper = templateHelper;
        this.topiaExtensionTagValues = new TopiaExtensionTagValues();
        this.topiaHibernateTagValues = new TopiaHibernateTagValues();
        this.entities = new LinkedHashMap<>();
    }

    public TopiaMetadataModel build0(boolean full) {

        List entityClasses = templateHelper.getEntityClasses(model, true);
        Multimap> oneToManyAssociationInverses = ArrayListMultimap.create();
        Multimap> oneToOneAssociationInverses = ArrayListMultimap.create();

        // Fist round to build all entities
        for (ObjectModelClass entityClass : entityClasses) {
            buildMetadataEntity(entityClass, oneToManyAssociationInverses, oneToOneAssociationInverses);
        }

        // Second round to fill inverses
        for (TopiaMetadataEntity metadataEntity : entities.values()) {
            if (oneToManyAssociationInverses.containsKey(metadataEntity.getType())) {
                for (Pair pair : oneToManyAssociationInverses.get(metadataEntity.getType())) {
                    String typeName = pair.getRight();
                    String columnName = pair.getLeft();
                    metadataEntity.addOneToManyAssociationInverse(metadataEntity, typeName, columnName);
                }
            }
            if (oneToOneAssociationInverses.containsKey(metadataEntity.getType())) {
                for (Pair pair : oneToOneAssociationInverses.get(metadataEntity.getType())) {
                    String typeName = pair.getRight();
                    String columnName = pair.getLeft();
                    metadataEntity.addOneToOneAssociationInverse(metadataEntity, typeName, columnName);
                }
            }
        }

        TopiaMetadataModel metadataModel = new TopiaMetadataModel(entities);
        if (full) {
            metadataModel.applyInheritance();
        }
        if (verbose || log.isDebugEnabled()) {
            TopiaMetadataModelVisitor.PrintVisitor visitor = new TopiaMetadataModelVisitor.PrintVisitor(true, "\n");
            metadataModel.accept(visitor);
            log.info("MetadataModel:\n" + visitor);
        }
        return metadataModel;
    }

    public TopiaMetadataEntity getEntity(String type) {
        return entities.get(type);
    }

    public Optional getOptionalEntity(String type) {
        return Optional.ofNullable(getEntity(type));
    }

    public TopiaMetadataEntity newEntity(String parent, String packageName, String type, boolean isAbstract, boolean entryPoint, boolean standalone, String dbSchemaName, String dbTableName) {
        String literalName = (dbSchemaName == null ? "" : (dbSchemaName + "_")) + type;
        if (entities.containsKey(literalName)) {
            throw new IllegalStateException(literalName + " already in cache");
        }
        TopiaMetadataEntity clazz = new TopiaMetadataEntity(parent, literalName, packageName + "." + type, isAbstract, entryPoint, standalone, dbSchemaName, dbTableName);
        entities.put(literalName, clazz);
        log.debug("create new entity: " + clazz.getType());
        return clazz;
    }

    private TopiaMetadataEntity buildMetadataEntity(ObjectModelClass entityClass, Multimap> oneToManyAssociationInverses, Multimap> oneToOneAssociationInverses) {

        TopiaMetadataEntity metadataEntity;

        String entityClassName = entityClass.getName();

        String literalName = templateHelper.getEntityEnumLiteralName(entityClass);

        Optional optionalClazz = getOptionalEntity(literalName);
        if (optionalClazz.isPresent()) {

            metadataEntity = optionalClazz.get();

        } else {

            if (verbose) {
                log.info("Start " + literalName);
            }

            ObjectModelPackage aPackage = model.getPackage(entityClass);
            String dbSchemaName = templateHelper.getSchema(entityClass);
            boolean entryPoint = topiaExtensionTagValues.isEntryPoint(entityClass);
            boolean standalone = topiaExtensionTagValues.isStandalone(model, aPackage, entityClass);
            if (entryPoint && standalone) {
                throw new IllegalStateException("An entity " + entityClassName + " can't be an entry point and standalone at the same time, make your choice");
            }
            String dbTableName = templateHelper.getDbName(entityClass);
            boolean isAbstract = entityClass.isAbstract();
            boolean haveSuper = entityClass.getSuperclasses().size() > 0;

            String parent = null;
            if (haveSuper) {
                ObjectModelClass superClass = entityClass.getSuperclasses().iterator().next();
                parent = templateHelper.getEntityEnumLiteralName(superClass);
            }
            metadataEntity = newEntity(parent, aPackage.getName(), entityClassName, isAbstract, entryPoint, standalone, dbSchemaName, dbTableName);

            Collection attributes = entityClass.getAttributes();
            for (ObjectModelAttribute attr : attributes) {

                ObjectModelAttribute reverse = attr.getReverseAttribute();

                if (!attr.isNavigable()
                        && !templateHelper.hasUnidirectionalRelationOnAbstractType(reverse, model)
                        && !attr.hasAssociationClass()) {
                    continue;
                }

                String name = attr.getName();
                String attrColumn = templateHelper.getDbName(attr);

                if (attr.getClassifier() == null || !templateHelper.isEntity(attr.getClassifier())) {

                    String attrType = attr.getType();
                    Integer precision = null;
                    Boolean useEnumerationName = null;
                    switch (attrType) {
                        case "String":
                            attrType = "java.lang.String";
                            break;
                        case "Boolean":
                            attrType = "java.lang.Boolean";
                            break;
                        case "Byte":
                            attrType = "java.lang.Byte";
                            break;
                        case "Character":
                            attrType = "java.lang.Character";
                            break;
                        case "Short":
                            attrType = "java.lang.Short";
                            break;
                        case "Integer":
                            attrType = "java.lang.Integer";
                            break;
                        case "Long":
                            attrType = "java.lang.Long";
                            break;
                        case "Float":
                        case "java.lang.Float":
                            attrType = "java.lang.Float";
                            precision = topiaExtensionTagValues.getDigits(aPackage, entityClass, attr);
                            Objects.requireNonNull(precision, String.format("No digits found on decimal attribute %s.%s", entityClass.getQualifiedName(), attr.getName()));
                            break;
                        case "Double":
                        case "java.lang.Double":
                            attrType = "java.lang.Double";
                            precision = topiaExtensionTagValues.getDigits(aPackage, entityClass, attr);
                            Objects.requireNonNull(precision, String.format("No digits found on decimal attribute %s.%s", entityClass.getQualifiedName(), attr.getName()));
                            break;
                        case "java.util.Date":
                            String type = Objects.requireNonNull(topiaHibernateTagValues.getHibernateAttributeType(attr, entityClass, aPackage, model));
                            switch (type) {
                                case "time":
                                    attrType = Time.class.getName();
                                    break;
                                case "timestamp":
                                    attrType = Timestamp.class.getName();
                                    break;
                                case "date":
                                    attrType = Date.class.getName();
                                    break;
                            }
                            break;
                        default:
                            useEnumerationName = topiaHibernateTagValues.hasUseEnumerationNameTagValue(attr, entityClass, aPackage, model);
                            if (useEnumerationName) {
                                // check we are on an enumeration
                                ObjectModelEnumeration enumeration = model.getEnumeration(attrType);
                                if (enumeration==null) {
                                    throw new IllegalStateException(String.format("Using tag-value useEnumerationName on %s.%s which is not an enumeration", entityClass.getQualifiedName(), attr.getName()));
                                }
                            }
                            break;
                    }
                    if (GeneratorUtil.isNMultiplicity(attr)) {
                        String tableName = templateHelper.getManyToManyTableName(attr);
                        metadataEntity.addManyAssociation(name, attrColumn, tableName, attr.getReverseAttributeName(), attrType);
                    } else {
                        metadataEntity.addProperty(name, attrType, attrColumn, precision, useEnumerationName);
                    }
                    continue;
                }

                ObjectModelClass attributeClass = model.getClass(attr.getType());

                boolean skipModelNavigation = topiaExtensionTagValues.isSkipModelNavigation(model.getPackage(attributeClass), attributeClass, attr);

                if (skipModelNavigation) {
                    metadataEntity.addSkipNavigation(name);
                }
                Optional optionalAttributeClass = getOptionalEntity(templateHelper.getEntityEnumLiteralName(attributeClass));

                TopiaMetadataEntity attributeClazz = optionalAttributeClass.orElseGet(() -> buildMetadataEntity(attributeClass, oneToManyAssociationInverses, oneToOneAssociationInverses));

                String reverseColumnName = templateHelper.getReverseDbName(attr);

                if (GeneratorUtil.isNMultiplicity(attr)) {


                    if (GeneratorUtil.isNMultiplicity(attr.getReverseMaxMultiplicity()) && !attr.hasAssociationClass()) {

                        // many to many
                        String tableName = templateHelper.getManyToManyTableName(attr);

                        metadataEntity.addManyToManyAssociation(attributeClazz, name, attrColumn, tableName, reverseColumnName);
                    } else {

                        // one to many
                        if (reverseColumnName == null) {
                            reverseColumnName = entityClassName;
                        }

                        boolean ordered = EugeneCoreTagValues.isOrdered(attr);
                        if (ordered) {
                            String extraColumn = reverseColumnName + "_idx";
                            if (verbose) {
                                log.info(String.format("Found ordered %s -> %s", attr, extraColumn));
                            }
                            attributeClazz.addExtraColumn(extraColumn);
                        }
                        metadataEntity.addOneToManyAssociation(attributeClazz, name, attrColumn);
                        oneToManyAssociationInverses.put(attributeClazz.getType(), Pair.of(reverseColumnName, metadataEntity.getType()));
                    }

                } else {
                    if (GeneratorUtil.isNMultiplicity(attr.getReverseMaxMultiplicity()) && !attr.hasAssociationClass()) {

                        // many to one
                        metadataEntity.addManyToOneAssociation(attributeClazz, name, attrColumn);

                    } else {

                        // one to one
                        if (attr.isComposite()) {
                            // one to one composition (child table has foreign key to parent)
                            if (reverseColumnName == null) {
                                reverseColumnName = entityClassName;
                            }
                            metadataEntity.addOneToOneAssociation(attributeClazz, name, attrColumn);
                            oneToOneAssociationInverses.put(attributeClazz.getType(), Pair.of(reverseColumnName, metadataEntity.getType()));
                        } else {

                            // one to one association (parent table has foreign key to child)
                            metadataEntity.addReversedAssociation(attributeClazz, name, attrColumn);
                        }
                    }
                }
            }
            if (verbose) {
                log.info(String.format("End %s", entityClassName));
            }
        }

        return metadataEntity;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy