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

io.github.pustike.persist.metadata.Schema Maven / Gradle / Ivy

/*
 * Copyright (C) 2016-2019 the original author or authors.
 *
 * 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
 *
 * https://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 io.github.pustike.persist.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import io.github.pustike.persist.Column;
import io.github.pustike.persist.EntityListener;
import io.github.pustike.persist.Id;
import io.github.pustike.persist.Lob;
import io.github.pustike.persist.Table;
import io.github.pustike.persist.Version;
import io.github.pustike.persist.utils.PersistUtils;

/**
 * The entity schema metadata.
 */
public final class Schema {
    private final String name;
    private final EntityListener listener;
    private Map entityDataMap;

    private Schema(String name, EntityListener listener) {
        this.name = name;
        this.listener = listener;
        this.entityDataMap = new LinkedHashMap<>();
    }

    /**
     * Create the schema builder.
     * @return a new instance of schema builder.
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Get the schema name, can be {@code null} as it is optional.
     * @return the name of the schema
     */
    public String getName() {
        return name;
    }

    /**
     * Get the entity listener if configured, can be {@code null}.
     * @return the configured entity listener
     */
    public EntityListener getEntityListener() {
        return listener;
    }

    /**
     * Get the table name with schema for the given entity metadata.
     * @param entityData the entity data
     * @return the schemaName.tableName that can be used in sql query
     */
    public String toSchemaTableName(EntityData entityData) {
        String tableName = entityData.getTableName();
        return name == null ? tableName : name + '.' + tableName;
    }

    /**
     * Get the collection of all entity metadata configured in this schema.
     * @return entity metadata collection
     */
    public Collection getEntityData() {
        return Collections.unmodifiableCollection(entityDataMap.values());
    }

    /**
     * Get the entity metadata for the given class.
     * @param entityClass the entity class
     * @return the entity metadata
     */
    public EntityData getEntityData(Class entityClass) {
        return getEntityData(entityClass.getName());
    }

    /**
     * Get the entity metadata for the given class name.
     * @param className the entity class name
     * @return the entity metadata
     */
    public EntityData getEntityData(String className) {
        EntityData entityData = entityDataMap.get(className);
        if (entityData == null) {
            throw new RuntimeException("Entity data is not available for class: " + className);
        }
        return entityData;
    }

    private void registerEntity(Class entityClass) {
        EntityData entityData = createEntityData(entityClass);
        Class superClass = entityClass.getSuperclass();
        EntityData currentEntityData = entityData;
        while (superClass != null && superClass != Object.class) {
            EntityData parentEntity = entityDataMap.get(superClass.getName());
            if (parentEntity == null) {
                parentEntity = createEntityData(superClass);
                entityDataMap.put(superClass.getName(), parentEntity);
            }
            currentEntityData.setParentEntity(parentEntity);
            currentEntityData = parentEntity;
            superClass = superClass.getSuperclass();
        }
        entityDataMap.put(entityClass.getName(), entityData);
    }

    private EntityData createEntityData(Class entityClass) {
        String tableName = null;
        if (!Modifier.isAbstract(entityClass.getModifiers())) {
            Table table = entityClass.getDeclaredAnnotation(Table.class);
            if (table == null) {
                throw new IllegalStateException("@Table annotation must be specified for an Entity: " + entityClass);
            }
            tableName = PersistUtils.getSqlName(entityClass.getSimpleName(), table.name());
        }
        return new EntityData(entityClass, tableName);
    }

    private void configureMetadata() {
        for (EntityData entityData : entityDataMap.values()) {
            Class entityClass = entityData.getEntityClass();
            for (Field field : entityClass.getDeclaredFields()) {
                addFieldData(entityData, field);
            }
            entityData.validate();
        }
    }

    private void addFieldData(EntityData entityData, Field field) {
        if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())
            || Modifier.isFinal(field.getModifiers())) {
            return;
        } // else, createFieldData by looking for @Column, @Id, @Version, @Lob etc.
        FieldData fieldData = new FieldData(field);
        Annotation[] annotations = field.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(Column.class)) {
                fieldData.setColumn((Column) annotation);
            } else if (annotation.annotationType().equals(Id.class)) {
                fieldData.setColumnType(ColumnType.Id);
            } else if (annotation.annotationType().equals(Version.class)) {
                fieldData.setColumnType(ColumnType.Version);
                fieldData.setOptional(false);
            } else if (annotation.annotationType().equals(Lob.class)) {
                fieldData.setColumnType(ColumnType.Lob);
            }
        }
        // NOTE: should be called after @Column annotation has been read!
        fieldData.setColumnName(PersistUtils.getSqlName(field.getName(), fieldData.getColumnName()));
        if (entityDataMap.containsKey(field.getType().getName())) {
            fieldData.setColumnType(ColumnType.ForeignKey);
        }
        entityData.addField(fieldData);
    }

    /**
     * The schema metadata builder.
     */
    public static final class Builder {
        private String name;
        private Set> entitySet;
        private EntityListener listener;

        Builder() {
            this.entitySet = new LinkedHashSet<>();
        }

        /**
         * Set the name of the schema.
         * @param name the schema name
         * @return this builder instance
         */
        public Builder named(String name) {
            if (name == null || name.isBlank()) {
                throw new IllegalArgumentException("Invalid schema name: " + name);
            }
            this.name = name.toLowerCase(Locale.ROOT);
            return this;
        }

        /**
         * Add an entity class to the schema.
         * @param entityClass the entity class
         * @return this builder instance
         */
        public Builder add(Class entityClass) {
            Objects.requireNonNull(entityClass);
            if (!entitySet.add(entityClass)) {
                throw new IllegalArgumentException("This entity is already added: " + entityClass);
            }
            return this;
        }

        /**
         * Set the entity listener which gets notified before insert or update.
         * @param listener the entity listener
         * @return this builder instance
         */
        public Builder having(EntityListener listener) {
            this.listener = Objects.requireNonNull(listener);
            return this;
        }

        /**
         * Build the fully configured schema instance.
         * @return the schema instance
         */
        public Schema build() {
            if (entitySet.isEmpty()) {
                throw new IllegalStateException("This schema doesn't contain any entity!");
            }
            Schema schema = new Schema(name, listener);
            for (Class entityClass : entitySet) {
                schema.registerEntity(entityClass);
            }
            schema.configureMetadata();
            entitySet.clear();
            return schema;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy