org.batoo.jpa.core.impl.jdbc.EntityTable.java Source code

Java tutorial

Introduction

Here is the source code for org.batoo.jpa.core.impl.jdbc.EntityTable.java

Source

/*
 * Copyright (c) 2012 - Batoo Software ve Consultancy Ltd.
 * 
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.batoo.jpa.core.impl.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.batoo.jpa.core.impl.instance.ManagedInstance;
import org.batoo.jpa.core.impl.instance.Status;
import org.batoo.jpa.core.impl.jdbc.dbutils.QueryRunner;
import org.batoo.jpa.core.impl.jdbc.dbutils.SingleValueHandler;
import org.batoo.jpa.core.impl.model.type.EntityTypeImpl;
import org.batoo.jpa.core.jdbc.IdType;
import org.batoo.jpa.core.jdbc.adapter.JdbcAdaptor;
import org.batoo.jpa.parser.metadata.TableMetadata;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;

/**
 * Table representing an entity persistent storage.
 * 
 * @author hceylan
 * @since $version
 */
public class EntityTable extends AbstractTable {

    private final EntityTypeImpl<?> entity;
    private final Map<String, AbstractColumn> pkColumns = Maps.newHashMap();

    private final JdbcAdaptor jdbcAdaptor;
    private PkColumn identityColumn;
    private final Map<String, BasicColumn[]> indexes = Maps.newHashMap();

    private final HashMap<Integer, String> removeSqlMap = Maps.newHashMap();
    private AbstractColumn[] removeColumns;

    /**
     * @param entity
     *            the entity
     * @param metadata
     *            the table metadata
     * 
     * @since $version
     * @author hceylan
     */
    public EntityTable(EntityTypeImpl<?> entity, TableMetadata metadata) {
        super(entity.getName(), metadata);

        this.entity = entity;

        this.jdbcAdaptor = entity.getMetamodel().getJdbcAdaptor();
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public void addColumn(AbstractColumn column) {
        super.addColumn(column);

        if (column instanceof PkColumn) {
            final PkColumn pkColumn = (PkColumn) column;

            this.pkColumns.put(pkColumn.getMappingName(), pkColumn);

            if (pkColumn.getIdType() == IdType.IDENTITY) {
                this.identityColumn = (PkColumn) column;
            }
        } else if (column instanceof JoinColumn) {
            final JoinColumn joinColumn = (JoinColumn) column;
            if (joinColumn.isPrimaryKey()) {
                this.pkColumns.put(column.getMappingName(), joinColumn);
            }
        }
    }

    /**
     * Adds the index to table.
     * 
     * @param name
     *            the name of the index
     * @param columns
     *            the columns
     * @return true if an index with the <code>name</code> already existed, false otherwise
     * 
     * @since $version
     * @author hceylan
     */
    public boolean addIndex(String name, BasicColumn... columns) {
        if (this.indexes.containsKey(name)) {
            return true;
        }

        this.indexes.put(name, columns);

        return false;
    }

    /**
     * Returns the entity of the EntityTable.
     * 
     * @return the entity of the EntityTable
     * 
     * @since $version
     * @author hceylan
     */
    public EntityTypeImpl<?> getEntity() {
        return this.entity;
    }

    /**
     * Returns the indexes of the table.
     * 
     * @return the indexes of the table
     * 
     * @since $version
     * @author hceylan
     */
    public Map<String, BasicColumn[]> getIndexes() {
        return this.indexes;
    }

    /**
     * Returns the jdbcAdaptor of the EntityTable.
     * 
     * @return the jdbcAdaptor of the EntityTable
     * 
     * @since $version
     * @author hceylan
     */
    protected JdbcAdaptor getJdbcAdaptor() {
        return this.jdbcAdaptor;
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public Set<String> getPkColumnNames() {
        return this.pkColumns.keySet();
    }

    /**
     * Returns the pkColumns of the EntityTable.
     * 
     * @return the pkColumns of the EntityTable
     * 
     * @since $version
     * @author hceylan
     */
    public Collection<AbstractColumn> getPkColumns() {
        return this.pkColumns.values();
    }

    private String getRemoveSql(int size) {
        String sql = this.removeSqlMap.get(size);
        if (sql != null) {
            return sql;
        }

        synchronized (this) {
            sql = this.removeSqlMap.get(size);
            if (sql != null) {
                return sql;
            }

            this.removeColumns = new AbstractColumn[this.pkColumns.size()];
            this.pkColumns.values().toArray(this.removeColumns);

            String restriction;
            if (this.pkColumns.size() > 1) {
                final Collection<String> restrictions = Collections2.transform(this.pkColumns.values(),
                        new Function<AbstractColumn, String>() {

                            @Override
                            public String apply(AbstractColumn input) {
                                return input.getName() + " = ?";
                            }
                        });
                final String singleRestriction = Joiner.on(" AND ").join(restrictions);
                restriction = StringUtils.repeat(singleRestriction, " OR ", size);
            } else if (size == 1) {
                restriction = this.removeColumns[0].getName() + " = ?";
            } else {
                restriction = this.removeColumns[0].getName() + " IN (" + StringUtils.repeat("?", ", ", size) + ")";
            }

            sql = "DELETE FROM " + this.getQName() + " WHERE " + restriction;
            this.removeSqlMap.put(size, sql);

            return sql;
        }
    }

    /**
     * Performs inserts to the table for the managed instance or joins.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstances
     *            the managed instances to perform insert for
     * @param size
     *            the size of the batch
     * @throws SQLException
     *             thrown in case of underlying SQLException
     * 
     * @since $version
     * @author hceylan
     */
    public void performInsert(Connection connection, final ManagedInstance<?>[] managedInstances, int size)
            throws SQLException {
        final EntityTypeImpl<?> entityType = managedInstances[0].getType();

        // Do not inline, generation of the insert SQL will initialize the insertColumns!
        final String insertSql = this.getInsertSql(entityType, size);
        final AbstractColumn[] insertColumns = this.getInsertColumns(entityType, size);

        // prepare the parameters
        final Object[] params = new Object[insertColumns.length * size];

        for (int i = 0; i < size; i++) {
            final ManagedInstance<?> managedInstance = managedInstances[i];
            final Object instance = managedInstance.getInstance();

            for (int j = 0; j < insertColumns.length; j++) {
                final AbstractColumn column = insertColumns[j];
                if (column instanceof DiscriminatorColumn) {
                    params[(i * insertColumns.length) + j] = entityType.getDiscriminatorValue();
                } else {
                    params[(i * insertColumns.length) + j] = column.getValue(instance);
                }
            }

            managedInstance.setStatus(Status.MANAGED);
        }

        new QueryRunner(this.jdbcAdaptor.isPmdBroken()).update(connection, insertSql, params);

        // if there is an identity column, extract the identity and set it back to the instance
        if (this.identityColumn != null) {
            final String selectLastIdSql = this.jdbcAdaptor.getSelectLastIdentitySql(this.identityColumn);
            final Number id = new QueryRunner(this.jdbcAdaptor.isPmdBroken()).query(connection, selectLastIdSql,
                    new SingleValueHandler<Number>());

            this.identityColumn.setValue(managedInstances[0].getInstance(), id);
        }
    }

    /**
     * Performs removes from the table for the managed instance or joins.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstances
     *            the managed instance to perform remove for
     * @param size
     *            the size of the batch
     * @throws SQLException
     *             thrown in case of underlying SQLException
     * 
     * @since $version
     * @author hceylan
     */
    public void performRemove(Connection connection, final ManagedInstance<?>[] managedInstances, int size)
            throws SQLException {
        final String removeSql = this.getRemoveSql(size);

        // prepare the parameters
        final Object[] params = new Object[size * this.removeColumns.length];
        for (int i = 0; i < size; i++) {
            final Object instance = managedInstances[i].getInstance();

            for (int j = 0; j < this.removeColumns.length; j++) {
                final AbstractColumn column = this.removeColumns[j];
                params[(i * this.removeColumns.length) + j] = column.getValue(instance);
            }
        }

        final QueryRunner runner = new QueryRunner(this.jdbcAdaptor.isPmdBroken());
        runner.update(connection, removeSql, params);
    }

    /**
     * Selects the version from the table.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstance
     *            the managed instance to perform select for
     * @return the version
     * @throws SQLException
     *             thrown in case of underlying SQLException
     * 
     * @since $version
     * @author hceylan
     */
    public Object performSelectVersion(Connection connection, final ManagedInstance<?> managedInstance)
            throws SQLException {
        final Object instance = managedInstance.getInstance();

        // Do not inline, generation of the update SQL will initialize the insertColumns!
        final String updateSql = this.getSelectVersionSql(this.pkColumns);
        final AbstractColumn[] selectVersionColumns = this.getSelectVersionColumns();

        // prepare the parameters
        final Object[] params = new Object[selectVersionColumns.length];
        for (int i = 0; i < selectVersionColumns.length; i++) {
            final AbstractColumn column = selectVersionColumns[i];
            params[i] = column.getValue(instance);
        }

        // execute the insert
        final QueryRunner runner = new QueryRunner(this.jdbcAdaptor.isPmdBroken());

        return runner.query(connection, updateSql, new SingleValueHandler<Object>(), params);
    }

    /**
     * Performs update to the table for the managed instance or joins.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstance
     *            the managed instance to perform update for
     * @throws SQLException
     *             thrown in case of underlying SQLException
     * 
     * @since $version
     * @author hceylan
     */
    public void performUpdate(Connection connection, final ManagedInstance<?> managedInstance) throws SQLException {
        final EntityTypeImpl<?> entityType = managedInstance.getType();
        final Object instance = managedInstance.getInstance();

        // Do not inline, generation of the update SQL will initialize the insertColumns!
        final String updateSql = this.getUpdateSql(entityType, this.pkColumns);
        final AbstractColumn[] updateColumns = this.getUpdateColumns(entityType);

        // prepare the parameters
        final Object[] params = new Object[updateColumns.length];
        for (int i = 0; i < updateColumns.length; i++) {
            final AbstractColumn column = updateColumns[i];
            params[i] = column.getValue(instance);
        }

        // execute the insert
        final QueryRunner runner = new QueryRunner(this.jdbcAdaptor.isPmdBroken());
        runner.update(connection, updateSql, params);
    }

    /**
     * Performs update to the table for the managed instance or joins. In addition checks if the table participate in update.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstance
     *            the managed instance to perform update for
     * @return returns true if the table is updatable
     * @throws SQLException
     *             thrown in case of underlying SQLException
     * 
     * @since $version
     * @author hceylan
     */
    public boolean performUpdateWithUpdatability(Connection connection, final ManagedInstance<?> managedInstance)
            throws SQLException {
        final EntityTypeImpl<?> entityType = managedInstance.getType();
        final Object instance = managedInstance.getInstance();

        // Do not inline, generation of the update SQL will initialize the insertColumns!
        final String updateSql = this.getUpdateSql(entityType, this.pkColumns);
        final AbstractColumn[] updateColumns = this.getUpdateColumns(entityType);

        // prepare the parameters
        final Object[] params = new Object[updateColumns.length];
        for (int i = 0; i < updateColumns.length; i++) {
            final AbstractColumn column = updateColumns[i];

            // if the first column is primary key then the table is not updatable
            if ((i == 0) && column.isPrimaryKey()) {
                return false;
            }

            params[i] = column.getValue(instance);
        }

        // execute the insert
        final QueryRunner runner = new QueryRunner(this.jdbcAdaptor.isPmdBroken());
        runner.update(connection, updateSql, params);

        return true;
    }

    /**
     * Performs version update to the table.
     * 
     * @param connection
     *            the connection to use
     * @param managedInstance
     *            the managed instance to perform version update for
     * @throws SQLException
     *             thrown in case of underlying SQLException
     * 
     * @since $version
     * @author hceylan
     */
    public void performVersionUpdate(Connection connection, final ManagedInstance<?> managedInstance)
            throws SQLException {
        final Object instance = managedInstance.getInstance();

        // Do not inline, generation of the update SQL will initialize the insertColumns!
        final String updateSql = this.getVersionUpdateSql(this.pkColumns);
        final AbstractColumn[] versionUpdateColumns = this.getVersionUpdateColumns();

        // prepare the parameters
        final Object[] params = new Object[versionUpdateColumns.length];
        for (int i = 0; i < versionUpdateColumns.length; i++) {
            final AbstractColumn column = versionUpdateColumns[i];
            params[i] = column.getValue(instance);
        }

        // execute the insert
        final QueryRunner runner = new QueryRunner(this.jdbcAdaptor.isPmdBroken());
        runner.update(connection, updateSql, params);
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    public String toString() {
        final String columns = Joiner.on(", ")
                .join(Collections2.transform(this.getColumns(), new Function<AbstractColumn, String>() {

                    @Override
                    public String apply(AbstractColumn input) {
                        final StringBuffer out = new StringBuffer();
                        out.append(input instanceof PkColumn ? "ID [" : "COL [");

                        out.append("name=");
                        out.append(input.getName());
                        out.append(", type=");
                        out.append(input.getSqlType());

                        out.append("]");
                        return out.toString();
                    }
                }));

        return "Table [owner=" + this.entity.getName() //
                + ", name=" + this.getQName() //
                + ", columns=[" + columns + "]]";
    }
}