Package org.jpox.store.rdbms

Source Code of org.jpox.store.rdbms.RDBMSStoreHelper

/**********************************************************************
Copyright (c) 2003 Andy Jefferson and others. All rights reserved.
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.

Contributors:
2004 David Ezzio - fix the use of the default package for the persistent class
2004 Erik Bengtson - removed Connection parameter for generateTableNameForMetaData method
2004 Andy Jefferson - added getClassForAppIdKey()
2004 Andy Jefferson - moved collection/map join table naming to generateTableName
2005 Andy Jefferson - fixed use of catalog/schema so we always query multi-schema
    ...
**********************************************************************/
package org.jpox.store.rdbms;

import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.jpox.ClassLoaderResolver;
import org.jpox.ManagedConnection;
import org.jpox.ObjectManager;
import org.jpox.StateManager;
import org.jpox.Transaction;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.ClassMetaData;
import org.jpox.metadata.DiscriminatorMetaData;
import org.jpox.metadata.DiscriminatorStrategy;
import org.jpox.metadata.InheritanceStrategy;
import org.jpox.state.StateManagerFactory;
import org.jpox.store.StoreData;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.MappedStoreData;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.MetaDataStringLiteral;
import org.jpox.store.mapped.expression.NullLiteral;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.rdbms.adapter.RDBMSAdapter;
import org.jpox.store.rdbms.columninfo.ColumnInfo;
import org.jpox.store.rdbms.columninfo.TableInfo;
import org.jpox.store.rdbms.query.DiscriminatorIteratorStatement;
import org.jpox.store.rdbms.query.RDBMSQueryUtils;
import org.jpox.store.rdbms.table.ClassTable;
import org.jpox.store.rdbms.table.Table;
import org.jpox.store.rdbms.typeinfo.ForeignKeyInfo;
import org.jpox.util.JPOXLogger;
import org.jpox.util.StringUtils;

/**
* Provides a series of uilities assisting in the datastore management process
* for RDBMS datastores.
*
* @version $Revision: 1.75 $
**/
public class RDBMSStoreHelper
{
    private static final int TABLE_IDENTIFIER_CATALOG = 0;
    private static final int TABLE_IDENTIFIER_SCHEMA = 1;
    private static final int TABLE_IDENTIFIER_TABLE = 2;
    public static final int TABLE_IDENTIFIER_COLUMN = 3;

    /**
     * Returns the type of a database table in the datastore.
     * Uses the DatabaseMetaData to extract this information.
     *
     * @param storeMgr Manager for the store
     * @param table The table/view
     * @param conn Connection to the database.
     * @return One of the TABLE_TYPE_* values from {@link Table}.
     * @see Table
     * @throws SQLException
     */
    public static int getTableType(RDBMSManager storeMgr,
                                   Table table,
                                   Connection conn)
    throws SQLException
    {
        String tableType = null;

        // Calculate the catalog/schema names since we need to search fully qualified
        String[] c = splitTableIdentifierName(((RDBMSAdapter)storeMgr.getDatastoreAdapter()).getCatalogSeparator(), table.getIdentifier().getIdentifier());
        String catalogName = table.getCatalogName();
        String schemaName = table.getSchemaName();
        String tableName = table.getIdentifier().getIdentifier();
        if (c[TABLE_IDENTIFIER_CATALOG] != null)
        {
            catalogName = c[TABLE_IDENTIFIER_CATALOG];
        }
        if (c[TABLE_IDENTIFIER_SCHEMA] != null)
        {
            schemaName = c[TABLE_IDENTIFIER_SCHEMA];
        }
        if (c[TABLE_IDENTIFIER_TABLE] != null)
        {
            tableName = c[TABLE_IDENTIFIER_TABLE];
        }
        RDBMSAdapter dba = (RDBMSAdapter)storeMgr.getDatastoreAdapter();
        catalogName = JDBCUtils.getIdentifierNameStripped(catalogName, dba);
        schemaName = JDBCUtils.getIdentifierNameStripped(schemaName, dba);
        tableName = JDBCUtils.getIdentifierNameStripped(tableName, dba);

        ResultSet rs = conn.getMetaData().getTables(catalogName, schemaName, tableName, null);
        try
        {
            while (rs.next())
            {
                if (tableName.equalsIgnoreCase(rs.getString(3)))
                {
                    tableType = rs.getString(4).toUpperCase();
                    break;
                }
            }
        }
        finally
        {
            rs.close();
        }

        if (tableType == null)
        {
            return Table.TABLE_TYPE_MISSING;
        }
        else if (tableType.equals("TABLE"))
        {
            return Table.TABLE_TYPE_TABLE;
        }
        else if (tableType.equals("VIEW"))
        {
            return Table.TABLE_TYPE_VIEW;
        }
        else
        {
            return Table.TABLE_TYPE_UNKNOWN;
        }
    }

    /**
     * Returns the column info for a database table.
     * Uses the DatabaseMetaData to extract this information.
     * @param storeMgr The RDBMSManager
     * @param table The table/view
     * @param conn Connection to the datastore
     * @return  A list of ColumnInfo objects describing the columns of the
     *          table.  The list is in the same order as was supplied by
     *          getColumns(). If no column info is found for the given table,
     *          an empty list is returned.
     * @see org.jpox.store.rdbms.columninfo.ColumnInfo
     * @throws SQLException
     */
    public static List getColumnInfoForTable(RDBMSManager storeMgr,
                                             Table table,
                                             Connection conn)
    throws SQLException
    {
        List cols = new ArrayList();

        // Calculate the catalog/schema names since we need to search fully qualified
        String[] c = splitTableIdentifierName(((RDBMSAdapter)storeMgr.getDatastoreAdapter()).getCatalogSeparator(), table.getIdentifier().getIdentifier());
        String catalogName = table.getCatalogName();
        String schemaName = table.getSchemaName();
        String tableName = table.getIdentifier().getIdentifier();
        if (c[TABLE_IDENTIFIER_CATALOG] != null)
        {
            catalogName = c[TABLE_IDENTIFIER_CATALOG];
        }
        if (c[TABLE_IDENTIFIER_SCHEMA] != null)
        {
            schemaName = c[TABLE_IDENTIFIER_SCHEMA];
        }
        if (c[TABLE_IDENTIFIER_TABLE] != null)
        {
            tableName = c[TABLE_IDENTIFIER_TABLE];
        }
        RDBMSAdapter dba = (RDBMSAdapter)storeMgr.getDatastoreAdapter();
        catalogName = JDBCUtils.getIdentifierNameStripped(catalogName, dba);
        schemaName = JDBCUtils.getIdentifierNameStripped(schemaName, dba);
        tableName = JDBCUtils.getIdentifierNameStripped(tableName, dba);

        ResultSet rs = dba.getColumns(conn, catalogName, schemaName, tableName);
        try
        {
            while (rs.next())
            {
                cols.add(((RDBMSAdapter)storeMgr.getDatastoreAdapter()).newColumnInfo(rs));
            }
        }
        finally
        {
            rs.close();
        }

        return cols;
    }

    /**
     * Returns the table info.
     * Uses the DatabaseMetaData to extract this information.
     * @param storeMgr The RDBMSManager
     * @param catalogName The catalog name
     * @param schemaName The schema name
     * @param conn Connection to the datastore
     * @return  A list of TableInfo objects describing the tables of the
     *          schema.  The list is in the same order as was supplied by
     *          getTables(). If no table, an empty list is returned.
     * @see org.jpox.store.rdbms.columninfo.TableInfo
     * @throws SQLException
     */
    public static List getTableInfo(RDBMSManager storeMgr,
                                    String catalogName,
                                    String schemaName,
                                    Connection conn)
    throws SQLException
    {
        List tables = new ArrayList();

        ResultSet rs = ((RDBMSAdapter)storeMgr.getDatastoreAdapter()).getTables(conn, catalogName, schemaName);
        try
        {
            while (rs.next())
            {
                tables.add(new TableInfo(rs));
            }
        }
        finally
        {
            rs.close();
        }

        return tables;
    }
   
    /**
     * Returns the column info for a database table.
     * Uses the DatabaseMetaData to extract this information.
     * @param storeMgr The RDBMSManager
     * @param table The table/view
     * @param conn Connection to the datastore
     * @param column The column
     * @return  A list of ColumnInfo objects describing the columns of the
     *          table.  The list is in the same order as was supplied by
     *          getColumns(). If no column info is found for the given table,
     *          an empty list is returned.
     * @see org.jpox.store.rdbms.columninfo.ColumnInfo
     * @throws SQLException
     */
    public static ColumnInfo getColumnInfoForColumnName(RDBMSManager storeMgr,
                                                        Table table,
                                                        Connection conn,
                                                        DatastoreIdentifier column)
    throws SQLException
    {
        ColumnInfo colInfo = null;

        RDBMSAdapter dba = (RDBMSAdapter)storeMgr.getDatastoreAdapter();
        // Calculate the catalog/schema names since we need to search fully qualified
        String[] t = splitTableIdentifierName(dba.getCatalogSeparator(), table.getIdentifier().getIdentifier());
        String catalogName = table.getCatalogName();
        String schemaName = table.getSchemaName();
        String tableName = table.getIdentifier().getIdentifier();
        if (t[TABLE_IDENTIFIER_CATALOG] != null)
        {
            catalogName = t[TABLE_IDENTIFIER_CATALOG];
        }
        if (t[TABLE_IDENTIFIER_SCHEMA] != null)
        {
            schemaName = t[TABLE_IDENTIFIER_SCHEMA];
        }
        if (t[TABLE_IDENTIFIER_TABLE] != null)
        {
            tableName = t[TABLE_IDENTIFIER_TABLE];
        }
        String[] c = splitColumnIdentifierName(dba.getCatalogSeparator(), column.getIdentifier());
        String columnName = column.getIdentifier();
        if (c[TABLE_IDENTIFIER_COLUMN] != null)
        {
            columnName = c[TABLE_IDENTIFIER_COLUMN];
        }
        //put in case, so JDBC driver will find the schema
        if (schemaName != null)
        {
            if (storeMgr.getIdentifierFactory().getIdentifierCase() == IdentifierFactory.IDENTIFIER_LOWER_CASE ||
                storeMgr.getIdentifierFactory().getIdentifierCase() == IdentifierFactory.IDENTIFIER_LOWER_CASE_QUOTED)
            {
                schemaName = schemaName.toLowerCase();
            }
            else if (storeMgr.getIdentifierFactory().getIdentifierCase() == IdentifierFactory.IDENTIFIER_UPPER_CASE ||
                storeMgr.getIdentifierFactory().getIdentifierCase() == IdentifierFactory.IDENTIFIER_UPPER_CASE_QUOTED)
            {
                schemaName= schemaName.toUpperCase();
            }
        }
        ResultSet rs = dba.getColumns(conn, catalogName, schemaName, tableName, columnName);
        try
        {
            if (rs.next())
            {
                colInfo = ((RDBMSAdapter)storeMgr.getDatastoreAdapter()).newColumnInfo(rs);
            }
            if (rs.next())
            {
                //TODO localise
                throw new JPOXException("Should not have returned more than one column info.").setFatal();
            }
        }
        finally
        {
            rs.close();
        }

        return colInfo;
    }

    /**
     * Returns the foreign key info for a database table.
     * Uses the DatabaseMetaData to extract this information.
     *
     * @param storeMgr Manager of the store
     * @param table The table
     * @param conn Connection to the datastore
     * @return  A list of ForeignKeyInfo objects describing the columns of the
     *          table's foreign keys. The list is in the same order as was
     *          supplied by get??portedKeys(). If no column info is found for
     *          the given table, an empty list is returned.
     * @see ForeignKeyInfo
     * @throws SQLException
     */
    public static List getForeignKeyInfoForTable(RDBMSManager storeMgr,
                                                 Table table,
                                                 Connection conn)
    throws SQLException
    {
        List fk_cols = new ArrayList();

        // Calculate the catalog/schema names since we need to search fully qualified
        String[] c = splitTableIdentifierName(((RDBMSAdapter)storeMgr.getDatastoreAdapter()).getCatalogSeparator(), table.getIdentifier().getIdentifier());
        String catalogName = table.getCatalogName();
        String schemaName = table.getSchemaName();
        String tableName = table.getIdentifier().getIdentifier();
        if (c[TABLE_IDENTIFIER_CATALOG] != null)
        {
            catalogName = c[TABLE_IDENTIFIER_CATALOG];
        }
        if (c[TABLE_IDENTIFIER_SCHEMA] != null)
        {
            schemaName = c[TABLE_IDENTIFIER_SCHEMA];
        }
        if (c[TABLE_IDENTIFIER_TABLE] != null)
        {
            tableName = c[TABLE_IDENTIFIER_TABLE];
        }
        RDBMSAdapter dba = (RDBMSAdapter)storeMgr.getDatastoreAdapter();
        catalogName = JDBCUtils.getIdentifierNameStripped(catalogName, dba);
        schemaName = JDBCUtils.getIdentifierNameStripped(schemaName, dba);
        tableName = JDBCUtils.getIdentifierNameStripped(tableName, dba);

        ResultSet rs = conn.getMetaData().getImportedKeys(catalogName, schemaName, tableName);
        try
        {
            while (rs.next())
            {
                ForeignKeyInfo fki = dba.newForeignKeyInfo(rs);

                // Some RDBMS (e.g PostgreSQL) returns duplicate rows from getImportedKeys().
                if (!fk_cols.contains(fki))
                {
                    fk_cols.add(fki);
                }
            }
        }
        finally
        {
            rs.close();
        }

        return fk_cols;
    }
    /**
     * Method to split a fully-qualified database table name into its
     * constituent parts (CATALOG.SCHEMA.TABLE). This is typically used where a user
     * has specified a table name as fully qualified in the MetaData.
     * @param separator Separator character
     * @param name The fully qualified name.
     * @return The separated parts of the name (catalog, schema, table)
     **/
    public static String[] splitTableIdentifierName(String separator, String name)
    {
        String[] result = new String[3];

        int p = name.indexOf(separator);
        if (p < 0)
        {
            // Table name specified
            result[TABLE_IDENTIFIER_TABLE] = name;
        }
        else
        {
            int p1 = name.indexOf(separator, p + separator.length());
            if (p1 < 0)
            {
                // Schema and Table name specified
                // TODO What if the RDBMS only supports catalog ... this should be the catalog here!!
                result[TABLE_IDENTIFIER_SCHEMA] = name.substring(0, p);
                result[TABLE_IDENTIFIER_TABLE] = name.substring(p + separator.length());
            }
            else
            {
                // Catalog, Schema and Table name specified
                result[TABLE_IDENTIFIER_CATALOG] = name.substring(0, p);
                result[TABLE_IDENTIFIER_SCHEMA] = name.substring(p + separator.length(), p1);
                result[TABLE_IDENTIFIER_TABLE] = name.substring(p1 + separator.length());
            }
        }
        if (result[TABLE_IDENTIFIER_SCHEMA] != null &&
            result[TABLE_IDENTIFIER_SCHEMA].length() < 1)
        {
            result[TABLE_IDENTIFIER_SCHEMA] = null;
        }
        if (result[TABLE_IDENTIFIER_CATALOG] != null &&
            result[TABLE_IDENTIFIER_CATALOG].length() < 1)
        {
            result[TABLE_IDENTIFIER_CATALOG] = null;
        }
        return result;
    }

    /**
     * Method to split a fully-qualified database column name into its
     * constituent parts (CATALOG.SCHEMA.TABLE.COLUMN). This is typically used where a user
     * has specified a column name as fully qualified in the MetaData.
     * @param separator Separator character
     * @param name The fully qualified name.
     * @return The separated parts of the name (catalog, schema, table, column)
     **/
    public static String[] splitColumnIdentifierName(String separator, String name)
    {
        String[] result = new String[4];

        int p = name.indexOf(separator);
        if (p < 0)
        {
            // Column name specified
            result[TABLE_IDENTIFIER_COLUMN] = name;
        }
        else
        {
            int p1 = name.indexOf(separator, p + separator.length());
            if (p1 < 0)
            {
                // Table and Column name specified
                result[TABLE_IDENTIFIER_TABLE] = name.substring(0, p);
                result[TABLE_IDENTIFIER_COLUMN] = name.substring(p + separator.length());
            }
            else
            {
                int p2 = name.indexOf(separator, p1 + separator.length());
                if (p2 < 0)
                {
                    // Schema, Table and Column name specified
                    result[TABLE_IDENTIFIER_SCHEMA] = name.substring(0, p);
                    result[TABLE_IDENTIFIER_TABLE] = name.substring(p + separator.length(), p1);
                    result[TABLE_IDENTIFIER_COLUMN] = name.substring(p1 + separator.length());
                }
                else
                {
                    // Schema, Table and Column name specified
                    result[TABLE_IDENTIFIER_CATALOG] = name.substring(0, p);
                    result[TABLE_IDENTIFIER_SCHEMA] = name.substring(p + separator.length(), p1);
                    result[TABLE_IDENTIFIER_TABLE] = name.substring(p1 + separator.length(), p2);
                    result[TABLE_IDENTIFIER_COLUMN] = name.substring(p2 + separator.length());
                }
            }
        }
        if (result[TABLE_IDENTIFIER_SCHEMA] != null &&
            result[TABLE_IDENTIFIER_SCHEMA].length() < 1)
        {
            result[TABLE_IDENTIFIER_SCHEMA] = null;
        }
        if (result[TABLE_IDENTIFIER_CATALOG] != null &&
            result[TABLE_IDENTIFIER_CATALOG].length() < 1)
        {
            result[TABLE_IDENTIFIER_CATALOG] = null;
        }
        if (result[TABLE_IDENTIFIER_TABLE] != null &&
                result[TABLE_IDENTIFIER_TABLE].length() < 1)
            {
                result[TABLE_IDENTIFIER_TABLE] = null;
            }
        return result;
    }

    /**
     * Utility that takes an id and a list of possible class RDBMSStoreData
     * and finds which of these classes contains the object with that id.
     * Works via a query to the datastore. Operates for all types of identity.
     * @param om Object Manager
     * @param storeMgr Store Manager
     * @param id The id
     * @param schemaDataOptions List of possible RDBMSStoreData
     * @return Name of the class with this key (or null if none found)
     **/
    public static String getClassNameForIdKeyUsingUnion(ObjectManager om,
                                                  RDBMSManager storeMgr,
                                                  Object id,
                                                  List schemaDataOptions)
    {
        String className = null;

        // Check for input error
        if (schemaDataOptions == null || id == null || schemaDataOptions.size() == 0)
        {
            return null;
        }

        // Calculate max length of class name for identifier
        int metadata_id_len = 0;
        Iterator optionsIter = schemaDataOptions.iterator();
        while (optionsIter.hasNext())
        {
            RDBMSStoreData schemaDataOption = (RDBMSStoreData)optionsIter.next();
            metadata_id_len = Math.max(schemaDataOption.getName().length(), metadata_id_len);
        }

        // Form the query to find which one of these classes has the instance with this id
        QueryExpression qs_base = null;
        optionsIter = schemaDataOptions.iterator();
        while (optionsIter.hasNext())
        {
            RDBMSStoreData schemaDataOption = (RDBMSStoreData)optionsIter.next();
            ClassMetaData cmd = (ClassMetaData)schemaDataOption.getMetaData();

            QueryExpression qs = storeMgr.getDatastoreAdapter().newQueryStatement(schemaDataOption.getDatastoreContainerObject(), om.getClassLoaderResolver());
            String classname = StringUtils.leftAlignedPaddedString(schemaDataOption.getName(), metadata_id_len);
            qs.selectScalarExpression(new MetaDataStringLiteral(qs,classname));

            // LEFT OUTER JOIN to all direct subclasses
            Iterator subclass_iter = storeMgr.getSubClassesForClass(schemaDataOption.getName(),false, om.getClassLoaderResolver()).iterator();
            int subclasses_seq_id = 0;
            while (subclass_iter.hasNext())
            {
                String subclass = (String)subclass_iter.next();
                DatastoreClass subclassTable = storeMgr.getDatastoreClass(subclass, om.getClassLoaderResolver());

                // No need to LEFT OUTER JOIN for "subclass-table" and "superclass-table" cases
                // "subclass-table" objects dont exist on their own
                // "superclass-table" are excluded using the discriminator clause
                if (subclassTable != null && !subclassTable.getIdentifier().equals(schemaDataOption.getDatastoreContainerObject().getIdentifier()))
                {
                    DatastoreIdentifier subclassTableIdentifier = storeMgr.getIdentifierFactory().newIdentifier(IdentifierFactory.TABLE, "SUBCLASS" + (subclasses_seq_id++));
                    QueryExpression st = storeMgr.getDatastoreAdapter().newQueryStatement(subclassTable, subclassTableIdentifier, om.getClassLoaderResolver());
                    LogicSetExpression table_expr_sub = st.newTableExpression(subclassTable, subclassTableIdentifier);
                    JavaTypeMapping subMapping = subclassTable.getIDMapping();
                    st.select(subclassTableIdentifier, subMapping);

                    ScalarExpression subExpr = subMapping.newScalarExpression(qs, table_expr_sub);
                    ScalarExpression schExpr =
                        (((DatastoreClass)schemaDataOption.getDatastoreContainerObject()).getIDMapping()).newScalarExpression(
                            qs,qs.getMainTableExpression());
                    qs.leftOuterJoin(subExpr, schExpr, table_expr_sub, true);
                    qs.andCondition(new NullLiteral(qs).eq(subExpr));
                }
            }

            // WHERE (object id) = ?
            JavaTypeMapping idMapping = ((DatastoreClass)schemaDataOption.getDatastoreContainerObject()).getIDMapping();

            // We have to create a StateManager here just to map fields from the AppId key object
            // to the table fields. Really the table should have some way of doing this. TODO : Refactor this
            Class pc_class = om.getClassLoaderResolver().classForName(schemaDataOption.getName());
            StateManager sm = StateManagerFactory.newStateManagerForHollow(om,pc_class,id);
            ScalarExpression fieldExpr = idMapping.newScalarExpression(qs, qs.getMainTableExpression());
            ScalarExpression fieldValue = idMapping.newLiteral(qs, sm.getObject());
            qs.andCondition(fieldExpr.eq(fieldValue), true);
            // Discriminator for this class
            JavaTypeMapping discrimMapping = schemaDataOption.getDatastoreContainerObject().getDiscriminatorMapping(false);
            DiscriminatorMetaData discrimMetaData = cmd.getInheritanceMetaData().getDiscriminatorMetaData();
            if (discrimMapping != null)
            {
                ScalarExpression discrimExpr = discrimMapping.newScalarExpression(qs, qs.getMainTableExpression());
                Object value = null;
                if (cmd.getDiscriminatorStrategy() == DiscriminatorStrategy.CLASS_NAME)
                {
                    value = schemaDataOption.getName();
                }
                else if (cmd.getDiscriminatorStrategy() == DiscriminatorStrategy.VALUE_MAP)
                {
                    value = discrimMetaData.getValue();
                }
                ScalarExpression discrimValue = discrimMapping.newLiteral(qs, value);
                qs.andCondition(discrimExpr.eq(discrimValue), true);
            }

            if (qs_base==null)
            {
                qs_base = qs;
            }
            else
            {
                qs_base.union(qs);
            }
        }

        // Perform the query
        try
        {
            Transaction tx = om.getTransaction();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            boolean useUpdateLock = ((Boolean)tx.getOptions().get("transaction.serializeReadObjects")).booleanValue();
            String statement = storeMgr.getStatementTextForQuery(qs_base, useUpdateLock);
            try
            {
                PreparedStatement ps = storeMgr.getStatementForQuery(qs_base, om, mconn, useUpdateLock, null, null);
                try
                {
                    ResultSet rs = sqlControl.executeStatementQuery(mconn, statement, ps);
                    try
                    {
                        if (rs != null)
                        {
                            while (rs.next())
                            {
                                className = RDBMSQueryUtils.getClassNameFromJPOXMetaDataResultSetRow(rs);
                            }
                        }
                    }
                    finally
                    {
                        rs.close();
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException sqe)
        {
            JPOXLogger.DATASTORE.error(sqe);
            throw new JPOXDataStoreException(sqe.toString());
        }

        return className;
    }

    /**
     * Utility that takes an id and a list of possible class RDBMSStoreData
     * and finds which of these classes contains the object with that id.
     * Works via a query to the datastore. Operates for all types of identity.
     * TODO The "schemaDataOptions" could be changed to be an array of class names.
     * @param om Object Manager
     * @param storeMgr Store Manager
     * @param id The id
     * @param schemaDataOptions List of possible RDBMSStoreData
     * @return Name of the class with this key (or null if none found)
     **/
    public static String getClassNameForIdKeyUsingDiscriminator(ObjectManager om,
                                                  RDBMSManager storeMgr,
                                                  Object id,
                                                  List schemaDataOptions)
    {
        String className = null;

        // Check for input error
        if (schemaDataOptions == null || schemaDataOptions.size() == 0 || id == null)
        {
            return null;
        }

        ClassLoaderResolver clr = om.getClassLoaderResolver();
        Class primaryClass = clr.classForName(((RDBMSStoreData)schemaDataOptions.get(0)).getName());
        Class objectClass = primaryClass;
        if (Modifier.isAbstract(primaryClass.getModifiers()))
        {
            // The primary class is abstract so find one that isnt. We only need a valid object type
            Iterator optionsIter = schemaDataOptions.iterator();
            while (optionsIter.hasNext())
            {
                RDBMSStoreData data = (RDBMSStoreData)optionsIter.next();
                Class dataClass = clr.classForName(data.getName());
                if (!Modifier.isAbstract(dataClass.getModifiers()))
                {
                    objectClass = dataClass;
                    break;
                }
            }
        }
        if (Modifier.isAbstract(objectClass.getModifiers()))
        {
            throw new JPOXException("Class "+objectClass.getName()+" is abstract, and a non abstract was expected.").setFatal();
        }

        // Form the query to find which one of these classes has the instance with this id
        DiscriminatorIteratorStatement discrimStmt = new DiscriminatorIteratorStatement(clr, new Class[] {primaryClass}, true, storeMgr, true);

        // Check all instances of this table to see if we have all possible candidates in our "options" list
        DatastoreClass primaryTable = storeMgr.getDatastoreClass(primaryClass.getName(), clr);
        StoreData[] storeData = storeMgr.getStoreDataForDatastoreContainerObject(primaryTable.getIdentifier());
        boolean haveAllCandidates = true;
        for (int i=0;i<storeData.length;i++)
        {
            if (storeData[i] instanceof MappedStoreData)
            {
                ClassTable tbl = (ClassTable)((MappedStoreData)storeData[i]).getDatastoreContainerObject();
                String[] managedClasses = tbl.getManagedClasses();
                for (int j=0;j<managedClasses.length;j++)
                {
                    boolean managedClassFound = false;
                    Iterator optionsIter = schemaDataOptions.iterator();
                    while (optionsIter.hasNext())
                    {
                        StoreData optionData = (StoreData)optionsIter.next();
                        if (optionData.getName().equals(managedClasses[j]))
                        {
                            managedClassFound = true;
                            break;
                        }
                    }
                    if (!managedClassFound)
                    {
                        haveAllCandidates = false;
                        break;
                    }
                }
                if (!haveAllCandidates)
                {
                    break;
                }
            }
        }

        if (haveAllCandidates)
        {
            // We have all possible candidates that are stored in this table so we can omit the restriction on the discriminator
            discrimStmt.setRestrictDiscriminator(false); // Just retrieve the discriminator for this id (accept any possible disc values)
        }

        // Create the statement
        QueryExpression stmt = discrimStmt.getQueryStatement(null);

        // WHERE (object id) = ?
        // We have to create a StateManager here just to map fields from the AppId key object to the table fields.
        // Really the table should have some way of doing this. TODO : Refactor this
        StateManager sm = StateManagerFactory.newStateManagerForHollow(om, objectClass, id);
        JavaTypeMapping idMapping = primaryTable.getIDMapping();
        ScalarExpression fieldExpr = idMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
        ScalarExpression fieldValue = idMapping.newLiteral(stmt, sm.getObject());
        stmt.andCondition(fieldExpr.eq(fieldValue), true);

        // Perform the query
        try
        {
            Transaction tx = om.getTransaction();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            boolean useUpdateLock = ((Boolean)tx.getOptions().get("transaction.serializeReadObjects")).booleanValue();
            String statement = storeMgr.getStatementTextForQuery(stmt, useUpdateLock);
            try
            {
                PreparedStatement ps = storeMgr.getStatementForQuery(stmt, om, mconn, useUpdateLock, null, null);
                try
                {
                    ResultSet rs = sqlControl.executeStatementQuery(mconn, statement, ps);
                    try
                    {
                        if (rs != null)
                        {
                            while (rs.next())
                            {
                                className = RDBMSQueryUtils.getClassNameFromDiscriminatorResultSetRow(primaryTable, rs, om);
                            }
                        }
                    }
                    finally
                    {
                        rs.close();
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException sqe)
        {
            JPOXLogger.DATASTORE.error(sqe);
            throw new JPOXDataStoreException(sqe.toString());
        }

        return className;
    }

    /**
     * Utility to find the ClassMetaData for the class that manages the table where the supplied
     * class stores its fields. This is for use where the current class uses an inheritance strategy
     * of "superclass-table" and we want to find the table where we should add our fields.
     * @param cmd The meta data for this class.
     * @return The metadata for the class that manages the table
     */
    public static AbstractClassMetaData getClassMetaDataManagingTableForClass(AbstractClassMetaData cmd)
    {
        if (cmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.NEW_TABLE ||
            cmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.COMPLETE_TABLE)
        {
            // We manage our own table
            return cmd;
        }

        // Go to the superclass
        AbstractClassMetaData superCmd = cmd.getSuperAbstractClassMetaData();
        if (superCmd != null)
        {
            if (superCmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.NEW_TABLE)
            {
                return superCmd;
            }
            else if (superCmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE)
            {
                return null;
            }
            else
            {
                // Go to the superclass and try again
                return getClassMetaDataManagingTableForClass(superCmd);
            }
        }
       
        // No class found that manages our fields!
        return null;
    }
}
TOP

Related Classes of org.jpox.store.rdbms.RDBMSStoreHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.