Package org.jpox.store.rdbms.query

Source Code of org.jpox.store.rdbms.query.ResultClassROF

/**********************************************************************
Copyright (c) 2005 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:
    ...
**********************************************************************/
package org.jpox.store.rdbms.query;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.jpox.ObjectManager;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.store.mapped.StatementExpressionIndex;
import org.jpox.store.mapped.expression.NewObjectExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.query.QueryUtils;
import org.jpox.store.query.ResultObjectFactory;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
import org.jpox.util.StringUtils;

/**
* Take a ResultSet, and for each row retrieves an object of a specified type.
* Follows the rules in JDO2 spec [14.6.12] regarding the result class.
* <P>
* The <B>resultClass</B> will be used to create objects of that type when calling
* <I>getObject()</I>. The <B>resultClass</B> can be one of the following
* </P>
* <UL>
* <LI>Simple type - String, Long, Integer, Float, Boolean, Byte, Character, Double,
* Short, BigDecimal, BigInteger, java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp</LI>
* <LI>java.util.Map - the JDO impl will choose the concrete impl of java.util.Map to use</LI>
* <LI>Object[]</LI>
* <LI>User defined type with either a constructor taking the result set fields,
* or a default constructor and setting the fields using a put(Object,Object) method, setXXX methods, or public fields</LI>
* </UL>
* </P>
* <P>
* Objects of this class are created in 2 distinct situations. The first is where a
* candidate class is available, and consequently field position mappings are
* available. The second is where no candidate class is available and so
* only the field names are available, and the results are taken in ResultSet order.
* These 2 modes have their own constructor.
*
* @version $Revision: 1.13 $
*/
public class ResultClassROF implements ResultObjectFactory
{
    protected static final Localiser LOCALISER=Localiser.getInstance("org.jpox.store.Localisation",
        RDBMSManager.class.getClassLoader());

    /** The result class that we should use. */
    private Class resultClass;

    /** The index of fields position to mapping type. */
    private final StatementExpressionIndex[] statementExpressionIndex;

    /** The result expressions. */
    private final ScalarExpression[] expressions;

    /** Names of the result field columns (in the ResultSet). */
    private final String[] resultFieldNames;

    /** Map of the ResultClass Fields, keyed by the field names (only for user-defined result classes). */
    private final Map resultClassFieldsByName = new HashMap();

    /**
     * Constructor for cases where we have a candidate class and so have mapping
     * information to base field positions on.
     * @param resultClass The result class to use
     * @param statementExpressionIndex The mapping information for field positions etc
     */
    public ResultClassROF(Class resultClass, StatementExpressionIndex[] statementExpressionIndex)
    {
        this(resultClass, statementExpressionIndex, null);
    }

    /**
     * Constructor for cases where we have a candidate class and so have mapping
     * information to base field positions on.
     * @param resultClass The result class to use
     * @param statementExpressionIndex The mapping information for field positions etc
     * @param expressions The result expressions (if known)
     */
    public ResultClassROF(Class resultClass, StatementExpressionIndex[] statementExpressionIndex,
            ScalarExpression[] expressions)
    {
        if (resultClass != null && resultClass.getName().equals("java.util.Map"))
        {
            // Spec 14.6.12 If user specifies java.util.Map, then impl chooses its own implementation Map class
            try
            {
                this.resultClass = Class.forName("java.util.HashMap", true, resultClass.getClassLoader());
            }
            catch (ClassNotFoundException cnfe)
            {
                this.resultClass = resultClass;
            }
        }
        else
        {
            this.resultClass = resultClass;
        }

        if (QueryUtils.resultClassIsUserType(resultClass.getName()))
        {
            populateDeclaredFieldsForUserType(this.resultClass);
        }

        this.statementExpressionIndex = statementExpressionIndex;
        this.expressions = expressions;
        this.resultFieldNames = new String[statementExpressionIndex.length];
        for (int i=0;i<statementExpressionIndex.length;i++)
        {
            if (statementExpressionIndex[i] != null)
            {
                resultFieldNames[i] = statementExpressionIndex[i].getColumnName();
            }
            else
            {
                resultFieldNames[i] = null;
            }
        }

        if (expressions == null && QueryUtils.resultClassIsSimple(resultClass.getName()) && statementExpressionIndex.length != 1)
        {
            String msg = LOCALISER.msg("021033", resultClass.getName());
            JPOXLogger.QUERY.error(msg);
            throw new JPOXUserException(msg);
        }
        else if (expressions != null && QueryUtils.resultClassIsSimple(resultClass.getName()) && expressions.length != 1)
        {
            String msg = LOCALISER.msg("021033", resultClass.getName());
            JPOXLogger.QUERY.error(msg);
            throw new JPOXUserException(msg);
        }
    }

    /**
     * Constructor for cases where we have no candidate class and so have no mapping information
     * to base field positions on. The fields will be retrieved in the ResultSet order.
     * @param resultClass The result class to use
     * @param resultFieldNames Names for the result fields
     */
    public ResultClassROF(Class resultClass, String[] resultFieldNames)
    {
        if (resultClass != null && resultClass.getName().equals("java.util.Map"))
        {
            // Spec 14.6.12 If user specifies java.util.Map, then impl chooses its own implementation Map class
            try
            {
                this.resultClass = Class.forName("java.util.HashMap", true, resultClass.getClassLoader());
            }
            catch (ClassNotFoundException cnfe)
            {
                this.resultClass = resultClass;
            }
        }
        else
        {
            this.resultClass = resultClass;
        }

        if (QueryUtils.resultClassIsUserType(resultClass.getName()))
        {
            populateDeclaredFieldsForUserType(this.resultClass);
        }

        this.statementExpressionIndex = null;
        this.expressions = null;
        if (resultFieldNames == null)
        {
            // We use the size of the array, so just allocate a 0-length array
            this.resultFieldNames = new String[0];
        }
        else
        {
            this.resultFieldNames = resultFieldNames;
        }
    }

    /**
     * Method to convert the ResultSet row into an Object of the ResultClass type. We have a special
     * handling for "result" expressions when they include literals or "new Object()" expression due to
     * the fact that complex literals and "new Object()" cannot be added to the SQL queries.
     * @param om The ObjectManager
     * @param rs The ResultSet from the Query.
     * @return The ResultClass object.
     */
    public Object getObject(final ObjectManager om, final Object rs)
    {
        // Retrieve the field values from the ResultSet
        Object[] fieldValues = null;
        if (statementExpressionIndex != null)
        {
            // Field mapping information available so use it to allocate our results
            if (expressions != null)
            {
                // Allow for any NewObjectExpression where we have to generate a new object using some fields
                fieldValues = new Object[expressions.length];
                StatementExpressionIterator stmtExprIterator = new StatementExpressionIterator();
                for (int i=0;i<expressions.length;i++)
                {
                    fieldValues[i] = processScalarExpression(om, rs, stmtExprIterator, expressions[i]);
                }
            }
            else
            {
                fieldValues = new Object[statementExpressionIndex.length];
                for (int i=0; i<statementExpressionIndex.length; i++)
                {
                    if (statementExpressionIndex[i] != null)
                    {
                        fieldValues[i] = statementExpressionIndex[i].getMapping().getObject(om, rs,
                            statementExpressionIndex[i].getExpressionIndex());
                    }
                    else
                    {
                        fieldValues[i] = null;
                    }
                }
            }
        }
        else
        {
            // No field mapping info, so allocate our results in the ResultSet parameter order.
            try
            {
                fieldValues = new Object[resultFieldNames.length];
                for (int i=0; i<fieldValues.length; i++)
                {
                    fieldValues[i] = getResultObject((ResultSet)rs, i+1);
                }
            }
            catch (SQLException sqe)
            {
                String msg = LOCALISER.msg("021043", sqe.getMessage());
                JPOXLogger.QUERY.error(msg);
                throw new JPOXUserException(msg);
            }
        }

        // If the user requires Object[] then just give them what we have
        if (resultClass == Object[].class)
        {
            return fieldValues;
        }

        if (QueryUtils.resultClassIsSimple(resultClass.getName()))
        {
            // User wants a single field
            if (fieldValues.length == 1 && (fieldValues[0] == null || resultClass.isAssignableFrom(fieldValues[0].getClass())))
            {
                // Simple object is the correct type so just give them the field
                return fieldValues[0];
            }
            else if (fieldValues.length == 1 && !resultClass.isAssignableFrom(fieldValues[0].getClass()))
            {
                // Simple object is not assignable to the ResultClass so throw an error
                String msg = LOCALISER.msg("021034",
                    resultClass.getName(), fieldValues[0].getClass().getName());
                JPOXLogger.QUERY.error(msg);
                throw new JPOXUserException(msg);
            }
        }
        else
        {
            // User requires creation of one of his own type of objects, or a Map

            // A. Find a constructor with the correct constructor arguments
            Object obj = QueryUtils.createResultObjectUsingArgumentedConstructor(resultClass, fieldValues);
            if (obj != null)
            {
                return obj;
            }
            else if (JPOXLogger.QUERY.isDebugEnabled())
            {
                // Give debug message that no constructor was found with the right args
                Class[] ctr_arg_types = new Class[resultFieldNames.length];
                for (int i=0;i<resultFieldNames.length;i++)
                {
                    if (fieldValues[i] != null)
                    {
                        ctr_arg_types[i] = fieldValues[i].getClass();
                    }
                    else
                    {
                        ctr_arg_types[i] = null;
                    }
                }
                JPOXLogger.QUERY.debug(LOCALISER.msg("021038",
                    resultClass.getName(), StringUtils.objectArrayToString(ctr_arg_types)));
            }

            // B. No argumented constructor exists so create an object and update fields using fields/put method/set method
            obj = QueryUtils.createResultObjectUsingDefaultConstructorAndSetters(resultClass, resultFieldNames,
                resultClassFieldsByName, fieldValues);

            return obj;
        }

        // Impossible to satisfy the resultClass requirements so throw exception
        String msg = LOCALISER.msg("021035",resultClass.getName());
        JPOXLogger.QUERY.error(msg);
        throw new JPOXUserException(msg);
    }

    /**
     * Populate a map with the declared fields of the result class and super classes.
     * @param cls the class to find the declared fields and populate the map
     */
    private void populateDeclaredFieldsForUserType(Class cls)
    {
        for (int i=0;i<cls.getDeclaredFields().length;i++)
        {
            if (resultClassFieldsByName.put(cls.getDeclaredFields()[i].getName().toUpperCase(), cls.getDeclaredFields()[i]) != null)
            {
                throw new JPOXUserException(LOCALISER.msg("021042",
                    cls.getDeclaredFields()[i].getName()));
            }
        }
        if (cls.getSuperclass() != null)
        {
            populateDeclaredFieldsForUserType(cls.getSuperclass());
        }
    }

    private Object processNewObjectExpression(ObjectManager om, Object rs, StatementExpressionIterator stmtExprIterator,
            ScalarExpression expr1)
    {
        // NewObjectExpression - so create the new object from the component field values
        NewObjectExpression newObjectExpr = (NewObjectExpression)expr1;
        int numberOfArgs = newObjectExpr.getArgumentExpressions().size();
        ArrayList argValues = new ArrayList();
        for (int j=0;j<numberOfArgs;j++)
        {
            ScalarExpression expr = (ScalarExpression)newObjectExpr.getArgumentExpressions().get(j);
            argValues.add(processScalarExpression(om, rs, stmtExprIterator, expr));
        }
        return newObjectExpr.createNewObject(argValues.toArray(new Object[argValues.size()]));
    }
   
    private Object processScalarExpression(ObjectManager om, Object rs, StatementExpressionIterator stmtExprIterator,
            ScalarExpression expr1)
    {
        if (expr1 instanceof NewObjectExpression)
        {
            return processNewObjectExpression(om, rs, stmtExprIterator, expr1);
        }
        else
        {
            try
            {
                // Extract the field value
                return stmtExprIterator.current().getMapping().getObject(om, rs, stmtExprIterator.current().getExpressionIndex());
            }
            finally
            {
                if (stmtExprIterator.hasNext())
                {
                    stmtExprIterator.next();
                }
            }
        }
    }
   
    /**
     * Basic iterator for StatementExpressionIndex
     */
    private class StatementExpressionIterator
    {
        int index = 0;
        boolean hasNext()
        {
            return( index < statementExpressionIndex.length-1 );
        }

        StatementExpressionIndex next()
        {
            index++;
            return statementExpressionIndex[index];
        }
       
        StatementExpressionIndex current()
        {
            return statementExpressionIndex[index];
        }
    }

    /**
     * Invokes a type-specific getter on given ResultSet
     */
    private static interface ResultSetGetter
    {
        Object getValue(ResultSet rs, int i) throws SQLException;
    }

    /** Map<Class, ResultSetGetter> ResultSetGetters by result classes */
    private static Map resultSetGetters = new HashMap(20);
    static
    {
        // any type specific getter from ResultSet that we can guess from the desired result class

        resultSetGetters.put(Boolean.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Boolean(rs.getBoolean(i));
            }
        });
       
        resultSetGetters.put(Byte.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Byte(rs.getByte(i));
            }
        });

        resultSetGetters.put(Short.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Short(rs.getShort(i));
            }
        });
       
        resultSetGetters.put(Integer.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Integer(rs.getInt(i));
            }
        });

        resultSetGetters.put(Long.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Long(rs.getLong(i));
            }
        });
       
        resultSetGetters.put(Float.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Float(rs.getFloat(i));
            }
        });
       
        resultSetGetters.put(Double.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return new Double(rs.getDouble(i));
            }
        });
       
        resultSetGetters.put(BigDecimal.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getBigDecimal(i);
            }
        });

        resultSetGetters.put(byte[].class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getBytes(i);
            }
        });

        ResultSetGetter timestampGetter = new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getTimestamp(i);
            }
        };
        resultSetGetters.put(java.sql.Timestamp.class, timestampGetter);
        // also use Timestamp getter for Date, so it also has time of the day
        // e.g. with Oracle
        resultSetGetters.put(java.util.Date.class, timestampGetter);

        resultSetGetters.put(java.sql.Date.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getDate(i);
            }
        });

        resultSetGetters.put(String.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getString(i);
            }
        });

        resultSetGetters.put(java.io.Reader.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getCharacterStream(i);
            }
        });

        resultSetGetters.put(java.sql.Array.class, new ResultSetGetter()
        {
            public Object getValue(ResultSet rs, int i) throws SQLException
            {
                return rs.getArray(i);
            }
        });
    }

    /**
     * Convenience method to read the value of a column out of the ResultSet.
     * @param rs ResultSet
     * @param columnNumber Number of the column (starting at 1)
     * @return Value for the column for this row.
     * @throws SQLException Thrown if an error occurs on reading
     */
    private Object getResultObject(final ResultSet rs, int columnNumber) throws SQLException
    {
        // use getter on ResultSet specific to our desired resultClass
        ResultSetGetter getter = (ResultSetGetter) resultSetGetters.get(resultClass);
        if (getter != null)
        {
            // User has specified a result type for this column so use the specific getter
            return getter.getValue(rs, columnNumber);
        }
        else
        {
            // User has specified Object/Object[] so just retrieve generically
            return rs.getObject(columnNumber);
        }
    }
}
TOP

Related Classes of org.jpox.store.rdbms.query.ResultClassROF

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.