org.projectforge.continuousdb.TableAttribute.java Source code

Java tutorial

Introduction

Here is the source code for org.projectforge.continuousdb.TableAttribute.java

Source

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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 http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.continuousdb;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

import org.apache.commons.lang.StringUtils;
import org.projectforge.common.BeanHelper;

/**
 * Represents one attribute of a table (e. g. for creation).
 *
 * @author Kai Reinhard (k.reinhard@micromata.de)
 */
public class TableAttribute implements Serializable {
    private static final long serialVersionUID = -8369835632981780449L;

    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TableAttribute.class);

    private static List<TableAttributeHook> hooks = new LinkedList<TableAttributeHook>();

    private boolean nullable = true;

    private TableAttributeType type;

    // For debug messages.
    private Class<?> entityClass;

    private String property;

    private Class<?> propertyType;

    /**
     * Only set for List and Sets.
     */
    private Class<?> genericType;

    private Boolean generated;

    private String name;

    private int length = 255;

    private int precision = 0;

    private int scale = 0;

    private boolean primaryKey;

    private boolean unique;

    private String foreignTable;

    private String foreignAttribute;

    private String defaultValue;

    private List<Annotation> annotations;

    public static void register(final TableAttributeHook hook) {
        hooks.add(hook);
    }

    /**
     *
     * @param clazz
     * @param property
     * @return null if getter method not found or getter method is transient.
     */
    public static TableAttribute createTableAttribute(final Class<?> clazz, final String property) {
        final Method getterMethod = BeanHelper.determineGetter(clazz, property, false);
        if (getterMethod == null) {
            throw new IllegalStateException("Can't determine getter: " + clazz + "." + property);
        }
        if (JPAHelper.isTransientAnnotationPresent(getterMethod) == true) {
            // Transient property.
            return null;
        }
        final TableAttribute attr = new TableAttribute(clazz, property);
        return attr;
    }

    /**
     * Creates a property and gets the information from the entity class. The JPA annotations Column, JoinColumn, Entity,
     * Table and ID are supported.
     *
     * @param clazz
     * @param property
     */
    public TableAttribute(final Class<?> clazz, final String property) {
        final Method getterMethod = BeanHelper.determineGetter(clazz, property, false);
        if (getterMethod == null) {
            throw new IllegalStateException("Can't determine getter: " + clazz + "." + property);
        }
        this.entityClass = clazz;
        this.property = property;
        this.name = property;
        this.propertyType = BeanHelper.determinePropertyType(getterMethod);
        // final boolean typePropertyPresent = false;
        // final String typePropertyValue = null;
        for (final TableAttributeHook hook : hooks) {
            this.type = hook.determineType(getterMethod);
            if (this.type != null) {
                break;
            }
        }
        final boolean primitive = this.propertyType.isPrimitive();
        if (JPAHelper.isPersistenceAnnotationPresent(getterMethod) == false) {
            log.warn(
                    "************** ProjectForge schema updater expect JPA annotations at getter method such as @Column for proper functioning!");
        }
        if (this.type != null) {
            // Type is already determined.
        } else if (Boolean.class.isAssignableFrom(this.propertyType) == true
                || Boolean.TYPE.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.BOOLEAN;
        } else if (Integer.class.isAssignableFrom(this.propertyType) == true
                || Integer.TYPE.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.INT;
        } else if (Long.class.isAssignableFrom(this.propertyType) == true
                || Long.TYPE.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.LONG;
        } else if (Short.class.isAssignableFrom(this.propertyType) == true
                || Short.TYPE.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.SHORT;
        } else if (String.class.isAssignableFrom(this.propertyType) == true || this.propertyType.isEnum() == true) {
            this.type = TableAttributeType.VARCHAR;
        } else if (BigDecimal.class.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.DECIMAL;
        } else if (java.sql.Date.class.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.DATE;
        } else if (java.util.Date.class.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.TIMESTAMP;
        } else if (java.util.Locale.class.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.LOCALE;
        } else if (java.util.List.class.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.LIST;
            this.setGenericReturnType(getterMethod);
        } else if (java.util.Set.class.isAssignableFrom(this.propertyType) == true) {
            this.type = TableAttributeType.SET;
            this.setGenericReturnType(getterMethod);
            // } else if (typePropertyPresent == true && "binary".equals(typePropertyValue) == true) {
            // type = TableAttributeType.BINARY;
        } else {
            final Entity entity = this.propertyType.getAnnotation(Entity.class);
            if (entity != null) {
                final javax.persistence.Table table = this.propertyType
                        .getAnnotation(javax.persistence.Table.class);
                if (table != null) {
                    this.foreignTable = table.name();
                } else {
                    this.foreignTable = new Table(this.propertyType).getName();
                }
                // if (entity != null && table != null && StringUtils.isNotEmpty(table.name()) == true) {
                final String idProperty = JPAHelper.getIdProperty(this.propertyType);
                if (idProperty == null) {
                    log.info("Id property not found for class '" + this.propertyType + "'): " + clazz + "."
                            + property);
                }
                this.foreignAttribute = idProperty;
                final Column column = JPAHelper.getColumnAnnotation(this.propertyType, idProperty);
                if (column != null && StringUtils.isNotEmpty(column.name()) == true) {
                    this.foreignAttribute = column.name();
                }
            } else {
                log.info("Unsupported property (@Entity expected for the destination class '" + this.propertyType
                        + "'): " + clazz + "." + property);
            }
            this.type = TableAttributeType.INT;
        }
        this.annotations = JPAHelper.getPersistenceAnnotations(getterMethod);
        final Id id = JPAHelper.getIdAnnotation(clazz, property);
        if (id != null) {
            this.primaryKey = true;
            this.nullable = false;
        }
        if (primitive == true) {
            this.nullable = false;
        }
        final Column column = JPAHelper.getColumnAnnotation(clazz, property);
        if (column != null) {
            if (this.isPrimaryKey() == false && primitive == false) {
                this.nullable = column.nullable();
            }
            if (StringUtils.isNotEmpty(column.name()) == true) {
                this.name = column.name();
            }
            if (this.type.isIn(TableAttributeType.VARCHAR, TableAttributeType.CHAR) == true) {
                this.length = column.length();
            }
            if (this.type == TableAttributeType.DECIMAL) {
                this.precision = column.precision();
                this.scale = column.scale();
            }
            this.unique = column.unique();
        }
        if (this.type == TableAttributeType.DECIMAL && this.scale == 0 && this.precision == 0) {
            throw new UnsupportedOperationException(
                    "Decimal values should have a precision and scale definition: " + clazz + "." + property);
        }
        final JoinColumn joinColumn = JPAHelper.getJoinColumnAnnotation(clazz, property);
        if (joinColumn != null) {
            if (StringUtils.isNotEmpty(joinColumn.name()) == true) {
                this.name = joinColumn.name();
            }
            if (joinColumn.nullable() == false) {
                this.nullable = false;
            }
        }
    }

    /**
     * @param method
     * @return
     */
    private void setGenericReturnType(final Method method) {

        Type returnType = method.getGenericReturnType();
        if ((returnType instanceof ParameterizedType) == false) {
            return;
        }
        final ParameterizedType type = (ParameterizedType) returnType;
        OneToMany oneToMany = method.getAnnotation(OneToMany.class);
        if (oneToMany != null && oneToMany.targetEntity() != null && oneToMany.targetEntity() != Void.TYPE) {
            if (type.getRawType() instanceof Class) {
                if (List.class.isAssignableFrom((Class) type.getRawType()) == true) {
                    genericType = oneToMany.targetEntity();
                    return;
                }
            }
        }
        final Type[] typeArguments = type.getActualTypeArguments();
        if (typeArguments.length == 0) {
            return;
        }
        if (typeArguments[0] instanceof ParameterizedType) {
            final ParameterizedType nt = (ParameterizedType) typeArguments[0];
            final Type[] nst = nt.getActualTypeArguments();
            if (nst.length > 0) {
                final Class<?> typeArgClass = (Class<?>) nst[0];
                if (log.isDebugEnabled() == true) {
                    log.debug("Generic type found for '" + entityClass + "." + property + "': '" + typeArgClass
                            + "'.");
                }
                genericType = typeArgClass;
            }
        }
        if ((typeArguments[0] instanceof Class) == false) {
            // opps
            final Class<?> thclas = typeArguments[0].getClass();
            log.error("Cannot determine entity type: " + thclas.getName() + " in method: " + method);
        } else {
            final Class<?> typeArgClass = (Class<?>) typeArguments[0];
            if (log.isDebugEnabled() == true) {
                log.debug("Generic type found for '" + entityClass + "." + property + "': '" + typeArgClass + "'.");
            }
            genericType = typeArgClass;
        }

    }

    public TableAttribute(final String name, final TableAttributeType type) {
        this.name = name;
        this.type = type;
    }

    public TableAttribute(final String name, final TableAttributeType type, final boolean nullable) {
        this(name, type);
        this.nullable = nullable;
    }

    public TableAttribute(final String name, final TableAttributeType type, final int length) {
        this(name, type);
        if (type != TableAttributeType.VARCHAR && type != TableAttributeType.CHAR) {
            throw new UnsupportedOperationException("Length not supported for attributes of type '" + type + "'.");
        }
        this.length = length;
    }

    public TableAttribute(final String name, final TableAttributeType type, final int length,
            final boolean nullable) {
        this(name, type, length);
        this.nullable = nullable;
    }

    public TableAttribute(final String name, final TableAttributeType type, final int precision, final int scale) {
        this(name, type);
        if (type != TableAttributeType.DECIMAL) {
            throw new UnsupportedOperationException(
                    "Precision and scale not supported for attributes of type '" + type + "'.");
        }
        this.precision = precision;
        this.scale = scale;
    }

    public TableAttribute(final String name, final TableAttributeType type, final int precision, final int scale,
            final boolean nullable) {
        this(name, type, precision, scale);
        this.nullable = nullable;
    }

    public boolean isNullable() {
        return nullable;
    }

    public TableAttribute setNullable(final boolean nullable) {
        this.nullable = nullable;
        return this;
    }

    /**
     * Not yet supported.
     */
    public boolean isUnique() {
        return unique;
    }

    public TableAttributeType getType() {
        return type;
    }

    public TableAttribute setType(final TableAttributeType type) {
        this.type = type;
        return this;
    }

    /**
     * @return The name of the property or if not exist the database identifier of the attribute.
     */
    public String getProperty() {
        return property != null ? property : name;
    }

    /**
     * The generic type is set if the property class is a list or set.
     *
     * @return the genericType
     */
    public Class<?> getGenericType() {
        return genericType;
    }

    /**
     * @return the propertyType
     */
    public Class<?> getPropertyType() {
        return propertyType;
    }

    /**
     * @return The database identifier of the attribute.
     */
    public String getName() {
        return name;
    }

    public TableAttribute setName(final String name) {
        this.name = name;
        return this;
    }

    /**
     * Length of CHAR and VARCHAR.
     *
     * @return
     */
    public int getLength() {
        return length;
    }

    public TableAttribute setLength(final int length) {
        this.length = length;
        return this;
    }

    /**
     * Precision of numerical (decimal) values.
     */
    public int getPrecision() {
        return precision;
    }

    public TableAttribute setPrecision(final int precision) {
        this.precision = precision;
        return this;
    }

    /**
     * Scale of numerical (decimal) values.
     */
    public int getScale() {
        return scale;
    }

    public TableAttribute setScale(final int scale) {
        this.scale = scale;
        return this;
    }

    public boolean isPrimaryKey() {
        return primaryKey;
    }

    /**
     * @param primaryKey
     * @return
     */
    public TableAttribute setPrimaryKey(final boolean primaryKey) {
        this.primaryKey = primaryKey;
        return this;
    }

    /**
     * True if the primary key should be generated by the database (if annotation {@link GeneratedValue} is specified).
     * May be set manually by {@link #setGenerated(Boolean)}.
     */
    public boolean isGenerated() {
        if (generated != null) {
            return generated;
        }
        return getAnnotation(GeneratedValue.class) != null;
    }

    /**
     * @param generated the generated to set
     * @return this for chaining.
     */
    public TableAttribute setGenerated(final Boolean generated) {
        this.generated = generated;
        return this;
    }

    public String getForeignTable() {
        return foreignTable;
    }

    /**
     * Sets foreign table. The primary key is set to default ("pk") if not set before.
     *
     * @param foreignTable
     * @return this for chaining.
     */
    public TableAttribute setForeignTable(final String foreignTable) {
        this.foreignTable = foreignTable;
        if (this.foreignAttribute == null) {
            this.foreignAttribute = "pk";
        }
        return this;
    }

    /**
     * Sets foreign table. The primary key is set to default ("pk") if not set before.
     *
     * @param foreignTableEntity
     * @return this for chaining.
     */
    public TableAttribute setForeignTable(final Class<?> foreignTableEntity) {
        return setForeignTable(new Table(foreignTableEntity));
    }

    /**
     * Sets foreign table. The primary key is set to default ("pk") if not set before.
     *
     * @param foreignTable
     * @return this for chaining.
     */
    public TableAttribute setForeignTable(final Table foreignTable) {
        return setForeignTable(foreignTable.getName());
    }

    public String getForeignAttribute() {
        return foreignAttribute;
    }

    public TableAttribute setForeignAttribute(final String foreignAttribute) {
        this.foreignAttribute = foreignAttribute;
        return this;
    }

    /**
     * @since 3.3.46 (didn't work before).
     */
    public String getDefaultValue() {
        return defaultValue;
    }

    public TableAttribute setDefaultValue(final String defaultValue) {
        this.defaultValue = defaultValue;
        return this;
    }

    /**
     * @return the annotations
     */
    public List<Annotation> getAnnotations() {
        return annotations;
    }

    @SuppressWarnings("unchecked")
    public <T extends Annotation> T getAnnotation(final Class<T> annotationClass) {
        if (annotations == null) {
            return null;
        }
        for (final Annotation annotation : annotations) {
            if (annotation.annotationType().isAssignableFrom(annotationClass) == true) {
                return (T) annotation;
            }
        }
        return null;
    }

    /**
     * @param annotations the annotations to set
     */
    public void setAnnotations(final List<Annotation> annotations) {
        this.annotations = annotations;
    }
}