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

org.efaps.update.schema.datamodel.SQLTableUpdate Maven / Gradle / Ivy

/*
 * Copyright 2003 - 2012 The eFaps Team
 *
 * 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.
 *
 * Revision:        $Rev: 7483 $
 * Last Changed:    $Date: 2012-05-11 11:57:38 -0500 (Fri, 11 May 2012) $
 * Last Changed By: $Author: [email protected] $
 */

package org.efaps.update.schema.datamodel;

import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.efaps.ci.CIAdminDataModel;
import org.efaps.db.Context;
import org.efaps.db.Insert;
import org.efaps.db.Instance;
import org.efaps.db.InstanceQuery;
import org.efaps.db.QueryBuilder;
import org.efaps.db.databases.AbstractDatabase;
import org.efaps.db.databases.information.ColumnInformation;
import org.efaps.db.databases.information.ForeignKeyInformation;
import org.efaps.db.databases.information.TableInformation;
import org.efaps.db.databases.information.UniqueKeyInformation;
import org.efaps.db.transaction.ConnectionResource;
import org.efaps.update.AbstractUpdate;
import org.efaps.update.UpdateLifecycle;
import org.efaps.update.util.InstallationException;
import org.efaps.util.EFapsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Handles the import / update of SQL tables for eFaps read from a XML
 * configuration item file.
 *
 * @author The eFaps Team
 * @version $Id: SQLTableUpdate.java 7483 2012-05-11 16:57:38Z [email protected] $
 */
public class SQLTableUpdate
    extends AbstractUpdate
{
    /**
     * Logging instance used to give logging information of this class.
     */
    private static final Logger LOG = LoggerFactory.getLogger(SQLTableUpdate.class);

    /**
     * Flag to indicate that the SQL table was created and that the auto
     * increment of the foreign key to parent SQL table must be created in the
     * database.
     *
     * @see Definition#createSQLTable()
     */
    private boolean created;

    /**
     * Default constructor to initialize this SQL table update instance for
     * given _url.
     *
     * @param _url        URL of the file
     */
    public SQLTableUpdate(final URL _url)
    {
        super(_url, "Admin_DataModel_SQLTable");
    }

    /**
     * Creates new instance of class {@link Definition}.
     *
     * @return new definition instance
     * @see Definition
     */
    @Override
    protected AbstractDefinition newDefinition()
    {
        return new Definition();
    }

    /**
     * The class defines a column in a SQL table.
     */
    private static final class Column
    {
        /**
         * Name of the column.
         */
        private final String name;

        /**
         * Type of the column.
         */
        private final AbstractDatabase.ColumnType type;

        /**
         * Length of the Column / Precision of a decimal.
         */
        private final int length;

        /**
         * Is null allowed in the column?
         */
        private final boolean isNotNull;

        /**
         * Scale of a decimal.
         */
        private final int scale;

        /**
         * @param _name     Name
         * @param _type     column type
         * @param _length   length
         * @param _scale    scale
         * @param _notNull  not null
         */
        private Column(final String _name,
                       final AbstractDatabase.ColumnType _type,
                       final int _length,
                       final int _scale,
                       final boolean _notNull)
        {
            this.name = _name;
            this.type = _type;
            this.length = _length;
            this.scale = _scale;
            this.isNotNull = _notNull;
        }

        /**
         * Returns a string representation with values of all instance variables of
         * a column.
         *
         * @return string representation of this definition of a column
         */
        @Override
        public String toString()
        {
            return new ToStringBuilder(this)
                .append("name", this.name)
                .append("type", this.type)
                .append("isNotNull", this.isNotNull)
                .toString();
        }
    }

    /**
     * Defines an unique key in a SQL table.
     */
    private static final class UniqueKey
    {
        /**
         * Name of the unique key.
         */
        private final String name;

        /**
         * Columns of the unique key.
         */
        private final String columns;

        /**
         * Default constructor.
         *
         * @param _name     name of the unique key
         * @param _columns  SQL table columns
         */
        private UniqueKey(final String _name,
                          final String _columns)
        {
            String name = null;
            try {
                name = Context.getDbType().getConstrainName(_name);
            } catch (final IOException e) {
                e.printStackTrace();
            }
            this.name = name;
            this.columns = _columns;
        }

        /**
         * Returns a string representation with values of all instance
         * variables of a column.
         *
         * @return string representation of this definition of a column
         */
        @Override
        public String toString()
        {
            return new ToStringBuilder(this)
                .append("name", this.name)
                .append("columns", this.columns)
                .toString();
        }
    }

    /**
     * The class defines a foreign key in a sql table.
     */
    private static final class ForeignKey
    {
        /**
         * Name of the foreign key.
         */
        private final String name;

        /**
         * Key of the foreign key.
         */
        private final String key;

        /**
         * Reference of the foreign key.
         */
        private final String reference;

        /**
         * Should a delete be cascaded?
         */
        private final boolean cascade;

        /**
         *
         * @param _name         name of the foreign key
         * @param _key          key of the foreign key
         * @param _reference    reference of the foreign key
         * @param _cascade      true if cascade; otherwise false
         */
        private ForeignKey(final String _name,
                           final String _key,
                           final String _reference,
                           final boolean _cascade)
        {
            String name = null;
            try {
                name =  Context.getDbType().getConstrainName(_name);
            } catch (final IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            this.name = name;
            this.key = _key;
            this.reference = _reference;
            this.cascade = _cascade;
        }

        /**
         * Returns a string representation with values of all instance
         * variables of a column.
         *
         * @return string representation of this definition of a column
         */
        @Override
        public String toString()
        {
            return new ToStringBuilder(this)
                .append("name", this.name)
                .append("key", this.key)
                .append("reference", this.reference)
                .toString();
        }
    }

    /**
     * The class defines a check constraint in a sql table.
     */
    private static final class CheckKey
    {
        /**
         * Name of the check constraint.
         */
        private final String name;

        /**
         * Condition of the check constraint.
         */
        private final String condition;

        /**
         * Default constructor.
         *
         * @param _name         name of the check key
         * @param _condition    condition of the check key
         */
        private CheckKey(final String _name,
                         final String _condition)
        {
            this.name = _name;
            this.condition = _condition;
        }

        /**
         * Returns a string representation with values of all instance
         * variables of a column.
         *
         * @return string representation of this definition of a column
         */
        @Override
        public String toString()
        {
            return new ToStringBuilder(this)
                .append("name", this.name)
                .append("condition", this.condition)
                .toString();
        }
    }

    /**
     * Definition for SQLTable.
     */
    protected class Definition
        extends AbstractDefinition
    {
        /**
         * The SQL table name of the parent table (as name in the SQL database).
         */
        private String parentSQLTableName;

        /**
         * The SQL table name of the parent table (as internal name in eFaps).
         */
        private String parent;

        /**
         * SQL statement which is directly executed (e.g. to create a SQL view).
         *
         * @see #addSQL
         * @see #executeSQLs
         */
        private final List sqls = new ArrayList();

        /**
         * Defines columns of the SQL table.
         */
        private final List columns = new ArrayList();

        /**
         * Defined unique keys of the SQL table.
         */
        private final List uniqueKeys = new ArrayList();

        /**
         * Defined foreign keys of the table.
         */
        private final List foreignKeys = new ArrayList();

        /**
         * Defined check keys of the table.
         */
        private final List checkKeys = new ArrayList();

        /**
         * Is this table a view.
         */
        private boolean view;

        /**
         *
         * @param _tags         current path as list of single tags
         * @param _attributes   attributes for current path
         * @param _text         content for current path
         */
        @Override
        protected void readXML(final List _tags,
                               final Map _attributes,
                               final String _text)
        {
            final String value = _tags.get(0);
            if ("database".equals(value))  {
                if (_tags.size() > 1)  {
                    final String subValue = _tags.get(1);
                    if ("check".equals(subValue))  {
                        this.checkKeys.add(new CheckKey(_attributes.get("name"),
                                                        _attributes.get("condition")));
                    } else if ("column".equals(subValue))  {
                        final String lengthStr = _attributes.get("length");
                        final String scaleStr = _attributes.get("scale");
                        final int length = (lengthStr != null)
                                           ? Integer.parseInt(lengthStr)
                                           : 0;
                        final int scale = (scaleStr != null)
                                          ? Integer.parseInt(scaleStr)
                                          : 0;
                        this.columns.add(new Column(_attributes.get("name"),
                                            Enum.valueOf(AbstractDatabase.ColumnType.class,
                                                         _attributes.get("type")),
                                            length,
                                            scale,
                                            "true".equals(_attributes.get("not-null"))));
                    } else if ("foreign".equals(subValue))  {
                        this.foreignKeys.add(new ForeignKey(_attributes.get("name"),
                                                            _attributes.get("key"),
                                                            _attributes.get("reference"),
                                                            "true".equals(_attributes.get("cascade"))));
                    } else if ("parent-table".equals(subValue))  {
                        this.parentSQLTableName = _text;
                    } else if ("sql".equals(subValue))  {
                        this.sqls.add(_text);
                    } else if ("table-name".equals(subValue))  {
                        addValue("SQLTable", _text);
                        addValue("SQLColumnID", "ID");
                    } else if ("view-name".equals(subValue))  {
                        addValue("SQLTable", _text);
                        addValue("SQLColumnID", "ID");
                        this.view = true;
                    } else if ("unique".equals(subValue))  {
                        this.uniqueKeys.add(new UniqueKey(_attributes.get("name"),
                                                          _attributes.get("columns")));
                    }
                }
            } else if ("parent".equals(value))  {
                if ((_text != null) && !"".equals(_text)) {
                    this.parent = _text;
                }
            } else if ("typeid-column".equals(value))  {
                addValue("SQLColumnType", _text);
            } else  {
                super.readXML(_tags, _attributes, _text);
            }
        }

        /**
         * Appends to the update the SQL table specific attribute values to the
         * _insert.
         *
         * @param _insert   insert instance to append SQL table specific
         *                  attributes
         * @throws InstallationException if insert of the eFaps definition for
         *                               the SQL table failed
         */
        @Override
        protected void createInDB(final Insert _insert)
            throws InstallationException
        {
            try {
                _insert.add("SQLTable", getValue("SQLTable"));
            } catch (final EFapsException e) {
                throw new InstallationException("Could not add SQLTable attribute", e);
            }
            try {
                _insert.add("SQLColumnID", getValue("SQLColumnID"));
            } catch (final EFapsException e) {
                throw new InstallationException("Could not add SQLColumnID attribute", e);
            }
            super.createInDB(_insert);
        }

        /**
         * Specific update instructions for the update for SQL tables.
         * Depending on the {@link UpdateLifecycle life cycle}
         * _step following is done:
         * 
         * 
         * 
         *     
         * 
         *     
         * 
         *     
         * 
         *     
         * 
         *     
         * 
Life Cycle StepDescription
{@link UpdateLifecycle#SQL_CREATE_TABLE SQL_CREATE_TABLE} *
{@link UpdateLifecycle#SQL_UPDATE_ID SQL_UPDATE_ID}
{@link UpdateLifecycle#SQL_UPDATE_TABLE SQL_UPDATE_TABLE} *
{@link UpdateLifecycle#SQL_RUN_SCRIPT SQL_RUN_SCRIPT}
{@link UpdateLifecycle#EFAPS_UPDATE EFAPS_UPDATE}
* * @param _step current life cycle update step * @param _allLinkTypes all link types * @throws InstallationException if update failed * @see #createSQLTable() * @see #updateColIdSQLTable() * @see #updateSQLTable() * @see #executeSQLs() */ @Override public void updateInDB(final UpdateLifecycle _step, final Set _allLinkTypes) throws InstallationException { try { if (_step == UpdateLifecycle.SQL_CREATE_TABLE) { if (!this.view) { createSQLTable(); } super.updateInDB(_step, _allLinkTypes); } else if (_step == UpdateLifecycle.SQL_UPDATE_ID && !this.view) { updateColIdSQLTable(); super.updateInDB(_step, _allLinkTypes); } else if (_step == UpdateLifecycle.SQL_UPDATE_TABLE && !this.view) { updateSQLTable(); super.updateInDB(_step, _allLinkTypes); } else if (_step == UpdateLifecycle.SQL_RUN_SCRIPT) { executeSQLs(); super.updateInDB(_step, _allLinkTypes); } else if (_step == UpdateLifecycle.EFAPS_UPDATE) { if (getValue("Name") != null) { // search for the parent SQL table name instance (if defined) if (this.parent != null) { final QueryBuilder queryBldr = new QueryBuilder(CIAdminDataModel.SQLTable); queryBldr.addWhereAttrEqValue(CIAdminDataModel.SQLTable.Name, this.parent); final InstanceQuery query = queryBldr.getQuery(); query.executeWithoutAccessCheck(); if (query.next()) { final Instance instance = query.getCurrentValue(); addValue(CIAdminDataModel.SQLTable.DMTableMain.name, "" + instance.getId()); } } super.updateInDB(_step, _allLinkTypes); } } else { super.updateInDB(_step, _allLinkTypes); } } catch (final EFapsException e) { throw new InstallationException(" SQLTable can not be updated", e); } } /** * Execute defined {@link #sqls SQL statements} in the database. * * @throws InstallationException if SQL scripts could not be executed * @see #sqls * @see #updateInDB */ protected void executeSQLs() throws InstallationException { if (!this.sqls.isEmpty()) { if (SQLTableUpdate.LOG.isInfoEnabled()) { SQLTableUpdate.LOG.info(" Execute Script for DB SQL '" + getValue("SQLTable") + "'"); } ConnectionResource con = null; try { final Context context = Context.getThreadContext(); con = context.getConnectionResource(); if (this.view) { final String tableName = getValue("SQLTable"); if (Context.getDbType().existsView(con.getConnection(), tableName)) { Context.getDbType().deleteView(con.getConnection(), tableName); } } final Statement stmt = con.getConnection().createStatement(); for (final String sql : this.sqls) { if (SQLTableUpdate.LOG.isDebugEnabled()) { SQLTableUpdate.LOG.debug(" ..SQL> " + sql); } stmt.execute(sql); } con.commit(); } catch (final EFapsException e) { if (con != null) { try { con.abort(); } catch (final EFapsException e1) { throw new InstallationException("SQLTable can not be updated", e1); } } throw new InstallationException("SQLTable can not be updated", e); } catch (final SQLException e) { throw new InstallationException("SQLTable can not be updated", e); } } } /** * If the SQL table does not exists in the database, create the SQL table. * * @throws EFapsException if create of the SQL tables failed * @see #updateInDB(UpdateLifecycle, Set) */ protected void createSQLTable() throws EFapsException { final Context context = Context.getThreadContext(); ConnectionResource con = null; final String tableName = getValue("SQLTable"); try { con = context.getConnectionResource(); if (!Context.getDbType().existsTable(con.getConnection(), tableName) && !Context.getDbType().existsView(con.getConnection(), tableName)) { if (SQLTableUpdate.LOG.isInfoEnabled()) { SQLTableUpdate.LOG.info(" Create DB SQL Table '" + tableName + "'"); } Context.getDbType().createTable(con.getConnection(), tableName); SQLTableUpdate.this.created = true; } con.commit(); } catch (final SQLException e) { SQLTableUpdate.LOG.error("SQLTableUpdate.createSQLTable.EFapsException", e); throw new EFapsException("SQLTableUpdate.createSQLTable.EFapsException", e); } finally { if (con != null && con.isOpened()) { con.abort(); } } } /** * Update the column ID of SQL table in the database. The column ID * must be auto increment or defined as foreign key to another SQL * table (if {@link #parentSQLTableName parent SQL table name} is * defined in the XML configuration item file). * * @throws EFapsException if update of the SQL tables failed * @throws InstallationException if update of the SQL tables failed * @see #updateInDB(UpdateLifecycle, Set) */ protected void updateColIdSQLTable() throws EFapsException, InstallationException { if (SQLTableUpdate.this.created) { SQLTableUpdate.this.created = false; final Context context = Context.getThreadContext(); ConnectionResource con = null; final String tableName = getValue("SQLTable"); if (SQLTableUpdate.LOG.isInfoEnabled()) { if (this.parentSQLTableName != null) { SQLTableUpdate.LOG.info(" Define ID column for SQL Table '" + tableName + "' " + "(parent '" + this.parentSQLTableName + "')"); } else { SQLTableUpdate.LOG.info(" Define ID column for SQL Table '" + tableName + "'"); } } try { con = context.getConnectionResource(); if (this.parentSQLTableName != null) { Context.getDbType().defineTableParent(con.getConnection(), tableName, this.parentSQLTableName); } else { Context.getDbType().defineTableAutoIncrement(con.getConnection(), tableName); } con.commit(); } catch (final SQLException e) { SQLTableUpdate.LOG.error("SQLTableUpdate.updateSQLTable.EFapsException", e); throw new EFapsException(getClass(), "updateSQLTable.Throwable", e); } finally { if (con != null && con.isOpened()) { con.abort(); } } } } /** * Update the SQL table in the database. * * @throws InstallationException if update of the SQL tables failed * @see #updateInDB(UpdateLifecycle, Set) */ protected void updateSQLTable() throws InstallationException { ConnectionResource con = null; final String tableName = getValue("SQLTable"); if (SQLTableUpdate.LOG.isInfoEnabled()) { SQLTableUpdate.LOG.info(" Update DB SQL Table '" + tableName + "'"); } try { con = Context.getThreadContext().getConnectionResource(); final TableInformation tableInfo = Context.getDbType().getRealTableInformation(con.getConnection(), tableName); for (final Column column : this.columns) { final ColumnInformation colInfo = tableInfo.getColInfo(column.name); if (colInfo != null) { if (SQLTableUpdate.LOG.isDebugEnabled()) { SQLTableUpdate.LOG.debug("column '" + column.name + "' already defined in " + "table '" + tableName + "'"); } // TODO: check for column types, column length and isNotNull } else { Context.getDbType().addTableColumn(con.getConnection(), tableName, column.name, column.type, null, column.length, column.scale, column.isNotNull); } } // add unique keys for (final UniqueKey uniqueKey : this.uniqueKeys) { final UniqueKeyInformation ukInfo = tableInfo.getUKInfo(uniqueKey.name); if (ukInfo != null) { if (SQLTableUpdate.LOG.isDebugEnabled()) { SQLTableUpdate.LOG.debug("unique key '" + uniqueKey.name + "' already defined in " + "table '" + tableName + "'"); } // TODO: check for column names } else { // check if a unique key exists for same column names final UniqueKeyInformation ukInfo2 = tableInfo.getUKInfoByColNames(uniqueKey.columns); if (ukInfo2 != null) { SQLTableUpdate.LOG.error("unique key for columns " + uniqueKey.columns + " exists"); } else { Context.getDbType().addUniqueKey(con.getConnection(), tableName, uniqueKey.name, uniqueKey.columns); } } } // add foreign keys for (final ForeignKey foreignKey : this.foreignKeys) { final ForeignKeyInformation fkInfo = tableInfo.getFKInfo(foreignKey.name); if (fkInfo != null) { if (SQLTableUpdate.LOG.isDebugEnabled()) { SQLTableUpdate.LOG.debug("foreign key '" + foreignKey.name + "' already defined in " + "table '" + tableName + "'"); } // TODO: further updates } else { Context.getDbType().addForeignKey(con.getConnection(), tableName, foreignKey.name, foreignKey.key, foreignKey.reference, foreignKey.cascade); } } // update check keys for (final CheckKey checkKey : this.checkKeys) { Context.getDbType().addCheckKey(con.getConnection(), tableName, checkKey.name, checkKey.condition); } con.commit(); con = null; } catch (final EFapsException e) { throw new InstallationException("update of the SQL table failed", e); } catch (final SQLException e) { throw new InstallationException("update of the SQL table failed", e); } finally { if (con != null) { try { con.abort(); } catch (final EFapsException e) { throw new InstallationException("Abort failed", e); } } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy