Package org.jpox.store.mapped.expression

Source Code of org.jpox.store.mapped.expression.ObjectExpression

/**********************************************************************
Copyright (c) 2003 Erik Bengtson 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:
2003 Andy Jefferson - coding standards
2005 Andy Jefferson - added handling for joins to related fields
2005 Andy Jefferson - added fix for use of "this" to avoid LEFT OUTER JOIN
    ...
**********************************************************************/
package org.jpox.store.mapped.expression;

import java.util.Iterator;

import org.jpox.ClassLoaderResolver;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.jdo.exceptions.ClassNotPersistenceCapableException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.DiscriminatorMetaData;
import org.jpox.metadata.DiscriminatorStrategy;
import org.jpox.metadata.InheritanceStrategy;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.mapping.BigDecimalMapping;
import org.jpox.store.mapped.mapping.BigIntegerMapping;
import org.jpox.store.mapped.mapping.BooleanMapping;
import org.jpox.store.mapped.mapping.ByteMapping;
import org.jpox.store.mapped.mapping.CharacterMapping;
import org.jpox.store.mapped.mapping.DiscriminatorMapping;
import org.jpox.store.mapped.mapping.DoubleMapping;
import org.jpox.store.mapped.mapping.EmbeddedMapping;
import org.jpox.store.mapped.mapping.EmbeddedPCMapping;
import org.jpox.store.mapped.mapping.FloatMapping;
import org.jpox.store.mapped.mapping.IntegerMapping;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.LongMapping;
import org.jpox.store.mapped.mapping.ObjectIdClassMapping;
import org.jpox.store.mapped.mapping.PersistenceCapableMapping;
import org.jpox.store.mapped.mapping.ReferenceMapping;
import org.jpox.store.mapped.mapping.ShortMapping;
import org.jpox.store.mapped.mapping.SqlDateMapping;
import org.jpox.store.mapped.mapping.SqlTimeMapping;
import org.jpox.store.mapped.mapping.SqlTimestampMapping;
import org.jpox.store.mapped.mapping.StringMapping;
import org.jpox.util.JPOXLogger;

/**
* Representation of an Object expression in a Query.
* Let's take an example :-
* <PRE>
* We have classes A and B, and A contains a reference to B "b".
* If we do a JDOQL query for class A of "b.someField == value" then
* "b" is interpreted first and an ObjectExpression is created to represent
* that object (of type B).
* </PRE>
* The expression has an associated TableExpression, and a list of
* expressions which represent the datastore columns identifying the
* object (the PK fields/columns).
*
* @version $Revision: 1.59 $
**/
public class ObjectExpression extends ScalarExpression
{
    protected ScalarExpression conditionExpr;
    private Class castType;

    /** Name of the field that this object represents. Null typically means that this is the candidate object. */
    private String fieldName;

    /** Type of the field that this object represents. */
    private String fieldType;

    /** Flag for whether we are using a related table and so dont need to join again in field access. */
    // TODO This is always true!!!! Why ?????????
    private boolean usingRelatedTable = true;

    protected ObjectExpression(QueryExpression qs)
    {
        super(qs);
    }

    /**
     * Constructor for an object expression, using the mapping of the field, and the expression for the table.
     * @param qs The Query Statement
     * @param mapping The mapping for the field whose object we are expressing
     * @param te The expression for the table of the object.
     */
    public ObjectExpression(QueryExpression qs, JavaTypeMapping mapping, LogicSetExpression te)
    {
        super(qs, mapping, te);
    }

    /**
     * Constructor for an object expression, using the mapping of the field (which has no datastore columns),
     * the expression for its table, the mapping for a field in another table to join to, and the expression
     * for the other table. This is for use in 2 situations :-
     * <UL>
     * <LI>a 1-1 bidirectional single-FK relation where one side has its FK stored in the other object,
     * so we need to left outer join to get that field.</LI>
     * <LI>a 1-N bidirectional join table relation where the element side has to navigate via the
     * join table to get to the owner object.</LI>
     * </UL>
     * @param qs The Query Statement
     * @param mapping The mapping for the field whose object we are expressing
     * @param te The expression for the table of the object
     * @param refMapping The mapping of the field in another table that we join to
     * @param teTarget The expression for the other table that we are joining to.
     * @param selectMapping The mapping that we should select in the other table
     */
    public ObjectExpression(QueryExpression qs, JavaTypeMapping mapping, LogicSetExpression te,
                            JavaTypeMapping refMapping, LogicSetExpression teTarget, JavaTypeMapping selectMapping)
    {
        this(qs);

        // Join from the ID field of this table to the related field of the related table
        ScalarExpression sourceExpr = mapping.getDatastoreContainer().getIDMapping().newScalarExpression(qs, te);
        ScalarExpression targetExpr = refMapping.newScalarExpression(qs, teTarget);
        qs.leftOuterJoin(sourceExpr, targetExpr, teTarget, true, true);

        this.mapping = selectMapping;
        this.te = teTarget;
        for (int i = 0; i < this.mapping.getNumberOfDatastoreFields(); i++)
        {
            expressionList.addExpression(new DatastoreFieldExpression(qs, this.mapping.getDataStoreMapping(i).getDatastoreField(), teTarget));
        }
        st.append(expressionList.toString());
        usingRelatedTable = true;
    }

    /**
     * Construct an object expression conditioned to a boolean expression
     * If this expression is an operand of an operation with result type Boolean has the following semantic
     *
     * if (conditionExpr == null )
     *    return (otherExpression op expr);
     * else
     *    return (otherExpression op expr) & conditionExpr;
     *
     * @param qs the QueryExpression
     * @param expr the expression
     * @param conditionExpr the conditional boolean expression
     * @param te the TableExpression
     */
    public ObjectExpression(QueryExpression qs, ScalarExpression expr, ScalarExpression conditionExpr, LogicSetExpression te)
    {
        super(qs);

        this.te = te;
        this.mapping = expr.mapping;
        this.st.append(expr.st.toString());
        this.expressionList = expr.expressionList;
        this.conditionExpr = conditionExpr;
    }

    /**
     * Method to change the expression to use only the first datastore field.
     * This is used where we want to use the expression in an aggregate and
     * only can use one datastore field. Package permission to prevent external access.
     */
    void useFirstDatastoreFieldOnly()
    {
        if (mapping.getNumberOfDatastoreFields() <= 1)
        {
            // Do nothing
            return;
        }

        // Replace the expressionList and SQL as if starting from scratch
        expressionList = new ExpressionList();
        expressionList.addExpression(new DatastoreFieldExpression(qs, mapping.getDataStoreMapping(0).getDatastoreField(), te));
        st.clearStatement();
        st.append(expressionList.toString());
    }

    /**
     * Convenience method for the case where the mapping being used is a PersistenceCapableMapping
     * and where we want to represent the identity instead of the object represented by that
     * mapping. This changes the mapping to use the id.
     */
    public void useIdentityFormOfPCMapping()
    {
        if (mapping instanceof PersistenceCapableMapping)
        {
            mapping = new ObjectIdClassMapping((PersistenceCapableMapping) mapping);
        }
    }

    /**
     * Convenience method to add an outer join suffix.
     * Particularly for use with Oracle (8) which uses syntax like table1.col1 = table2.col2 (+),
     * @param suffix The suffix
     */
    public void addOuterJoinSuffix(String suffix)
    {
        if (suffix != null)
        {
            st.append(suffix);
        }
    }

    /**
     * Cast operator. Called when the query contains "(type)obj" where "obj" is this object.
     * @param castType The type we cast this object to
     * @return Scalar expression representing the cast object.
     */
    public ScalarExpression cast(Class castType)
    {
        ObjectExpression objectCast;
        LogicSetExpression te = qs.getTableExpression(this.qs.getStoreManager().getDatastoreClass(castType.getName(), qs.getClassLoaderResolver()).getIdentifier());
        DatastoreClass dc = this.qs.getStoreManager().getDatastoreClass(castType.getName(), qs.getClassLoaderResolver());
        if (te == null)
        {
            IdentifierFactory idFactory = qs.getStoreManager().getIdentifierFactory();
            String jtIdentifier = this.te.getAlias().getIdentifier();
            if (castType != null && !castType.getName().equals(mapping.getType()))
            {
                String castTypeName = castType.getName();
                jtIdentifier = idFactory.newIdentifier(this.te.getAlias(), castTypeName.substring(castTypeName.lastIndexOf('.') + 1)).getIdentifier();
            }

            DatastoreIdentifier jtRangeVar = idFactory.newIdentifier(IdentifierFactory.TABLE, jtIdentifier);
            LogicSetExpression jtTblExpr = qs.getTableExpression(jtRangeVar);

            if (jtTblExpr == null)
            {
                jtTblExpr = qs.newTableExpression(dc, jtRangeVar);
            }
            te = jtTblExpr;
            qs.leftOuterJoin(this, dc.getIDMapping().newScalarExpression(qs,jtTblExpr), jtTblExpr, true, true);
        }
        objectCast = new ObjectExpression(qs,dc.getIDMapping(),te);
        objectCast.conditionExpr = this.conditionExpr;
        return objectCast;
    }

    /**
     * Equals operator. Called when the query contains "obj == value" where "obj" is this object.
     * @param expr The expression we compare with (the right-hand-side in the query)
     * @return Boolean expression representing the comparison.
     */
    public BooleanExpression eq(ScalarExpression expr)
    {
        BooleanExpression bExpr = null;
        if (expr instanceof NullLiteral)
        {
            for (int i=0; i<this.expressionList.size(); i++)
            {
                if (bExpr == null)
                {
                    bExpr = expr.eq(this.expressionList.getExpression(i));
                }
                else
                {
                    bExpr = bExpr.and(expr.eq(this.expressionList.getExpression(i)));
                }
            }
        }
        else if (literalIsValidForSimpleComparison(expr))
        {
            if (this.expressionList.size() > 1)
            {
                // More than 1 value to compare with a literal!
                bExpr = super.eq(expr);
            }
            else
            {
                // Just do a direct comparison with the basic literals
                bExpr = new BooleanExpression(this, OP_EQ, expr);
            }
        }
        else if (expr instanceof ObjectLiteral)
        {
            bExpr = expr.eq(this);
        }
        else if (expr instanceof ObjectExpression)
        {
            for (int i=0; i<this.expressionList.size(); i++)
            {
                ScalarExpression source = this.expressionList.getExpression(i);
                ScalarExpression target = expr.expressionList.getExpression(i);
                if (bExpr == null)
                {
                    bExpr = source.eq(target);
                }
                else
                {
                    bExpr = bExpr.and(source.eq(target));
                }
            }
        }
        else if (expr instanceof UnboundVariable)
        {
            if (((UnboundVariable)expr).getVariableType() == null)
            {
                // Set the variable type to this objects type
                ((UnboundVariable)expr).setVariableType(qs.getClassLoaderResolver().classForName(fieldType));
            }
            bExpr = expr.eq(this);
        }
        else if (expr instanceof BooleanBitColumnExpression)
        {
            bExpr = null;
        }
        else
        {
            bExpr = super.eq(expr);
        }

        if (conditionExpr != null)
        {
            return new BooleanExpression(conditionExpr, OP_AND, bExpr);
        }

        return bExpr;
    }

    /**
     * Not equals operator. Called when the query contains "obj != value" where "obj" is this object.
     * @param expr The expression we compare with (the right-hand-side in the query)
     * @return Boolean expression representing the comparison.
     */
    public BooleanExpression noteq(ScalarExpression expr)
    {
        BooleanExpression bExpr = null;
        if (expr instanceof NullLiteral)
        {
            for (int i=0; i<this.expressionList.size(); i++)
            {
                if (bExpr == null)
                {
                    bExpr = expr.eq(this.expressionList.getExpression(i));
                }
                else
                {
                    bExpr = bExpr.and(expr.eq(this.expressionList.getExpression(i)));
                }
            }
            bExpr = new BooleanExpression(OP_NOT, bExpr.encloseWithInParentheses());
        }
        else if (literalIsValidForSimpleComparison(expr))
        {
            if (this.expressionList.size() > 1)
            {
                // More than 1 value to compare with a literal!
                bExpr = super.noteq(expr);
            }
            else
            {
                // Just do a direct comparison with the basic literals
                bExpr = new BooleanExpression(this, OP_NOTEQ, expr);
            }
        }
        else if (expr instanceof ObjectLiteral)
        {
            bExpr = expr.noteq(this);
        }
        else if (expr instanceof ObjectExpression)
        {
            for (int i=0; i<this.expressionList.size(); i++)
            {
                ScalarExpression source = this.expressionList.getExpression(i);
                ScalarExpression target = expr.expressionList.getExpression(i);
                if (bExpr == null)
                {
                    bExpr = source.eq(target);
                }
                else
                {
                    bExpr = bExpr.and(source.eq(target));
                }
            }
            bExpr = new BooleanExpression(OP_NOT, bExpr.encloseWithInParentheses());
        }
        else if (expr instanceof UnboundVariable)
        {
            if (((UnboundVariable)expr).getVariableType() == null)
            {
                // Set the variable type to this objects type
                ((UnboundVariable)expr).setVariableType(qs.getClassLoaderResolver().classForName(fieldType));
            }
            bExpr = expr.noteq(this);
        }
        else if (expr instanceof BooleanBitColumnExpression)
        {
            if (conditionExpr != null)
            {
                bExpr = new BooleanExpression(ScalarExpression.OP_NOT, conditionExpr);
            }
        }       
        else
        {
            bExpr = super.noteq(expr);
        }

        if (conditionExpr != null)
        {
            return new BooleanExpression(conditionExpr, OP_AND, bExpr);
        }

        return bExpr;
    }

    /**
     * Convenience method to return if this object is valid for simple comparison
     * with the passed expression. Performs a type comparison of the object and the expression
     * for compatibility. The expression must be a literal of a suitable type for simple
     * comparison (e.g where this object is a String, and the literal is a StringLiteral).
     * @param expr The expression
     * @return Whether a simple comparison is valid
     */
    private boolean literalIsValidForSimpleComparison(ScalarExpression expr)
    {
        // Our mapping is a single field type and is of the same basic type as the expression
        if ((expr instanceof BooleanLiteral && (mapping instanceof BooleanMapping)) ||
            (expr instanceof ByteLiteral && (mapping instanceof ByteMapping)) ||
            (expr instanceof CharacterLiteral && (mapping instanceof CharacterMapping)) ||
            (expr instanceof FloatingPointLiteral &&
             (mapping instanceof FloatMapping || mapping instanceof DoubleMapping || mapping instanceof BigDecimalMapping)) ||
            (expr instanceof IntegerLiteral &&
             (mapping instanceof IntegerMapping || mapping instanceof LongMapping || mapping instanceof BigIntegerMapping) || mapping instanceof ShortMapping) ||
            (expr instanceof SqlDateLiteral && (mapping instanceof SqlDateMapping)) ||
            (expr instanceof SqlTimeLiteral && (mapping instanceof SqlTimeMapping)) ||
            (expr instanceof SqlTimestampLiteral && (mapping instanceof SqlTimestampMapping)) ||
            (expr instanceof StringLiteral && (mapping instanceof StringMapping || mapping instanceof CharacterMapping)))
        {
            return true;
        }

        return false;
    }

    public BooleanExpression in(ScalarExpression expr)
    {
        return new BooleanExpression(this, OP_IN, expr);
    }

    /**
     * Access a field in the object that this expression represents.
     * If the field is contained in a different table then will use the "innerJoin" input parameter
     * and make a join to the required table. If the field is a 1-1 relation and the current table holds the FK
     * then no join will be made.
     * @param subfieldName the field to be accessed in this object
     * @param innerJoin whether to inner join
     * @return The field expression representing the required field of this object
     */
    public ScalarExpression accessField(String subfieldName, boolean innerJoin)
    {
        DatastoreContainerObject table;
        ClassLoaderResolver clr = qs.getClassLoaderResolver();
        try
        {
            if (mapping instanceof EmbeddedMapping)
            {
                // Any embedded fields can go straight to the main table if embedded there
                table = mapping.getDatastoreContainer();
                if (te.getMainTable().equals(table))
                {
                    // Provide the full field name so we can allow for nested embeddings
                    return te.newFieldExpression(fieldName + "." + subfieldName);
                }
            }
            else if (mapping instanceof PersistenceCapableMapping || mapping instanceof ReferenceMapping)
            {
                AbstractClassMetaData otherCmd = qs.getStoreManager().getOMFContext().getMetaDataManager().getMetaDataForClass(
                    mapping.getType(), clr);
                if (otherCmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE)
                {
                    // Field is a PC class that uses "subclass-table" inheritance strategy (and so has multiple possible tables to join to)
                    AbstractClassMetaData[] cmds = qs.getStoreManager().getClassesManagingTableForClass(otherCmd, clr);
                    if (cmds != null)
                    {
                        // Join to the first table
                        // TODO Allow for all possible tables. Can we do an OR of the tables ? How ?
                        if (cmds.length > 1)
                        {
                            JPOXLogger.QUERY.warn(LOCALISER.msg("037006",
                                mapping.getFieldMetaData().getFullFieldName(), cmds[0].getFullClassName()));
                        }
                        table = qs.getStoreManager().getDatastoreClass(cmds[0].getFullClassName(), clr);
                    }
                    else
                    {
                        // No subclasses with tables to join to, so throw a user error
                        throw new JPOXUserException(LOCALISER.msg("037005",
                            mapping.getFieldMetaData().getFullFieldName()));
                    }
                }
                else
                {
                    // Class of the field will have its own table
                    table = qs.getStoreManager().getDatastoreClass(mapping.getType(), clr);
                    ApiAdapter api = qs.getStoreManager().getApiAdapter();

                    if (fieldName != null && subfieldName != null)
                    {
                        AbstractMemberMetaData subfieldMetaData = otherCmd.getMetaDataForMember(subfieldName);
                        if (subfieldMetaData != null && subfieldMetaData.isPrimaryKey() &&
                            !api.isPersistable(subfieldMetaData.getType()))
                        {
                            // Selecting a non-PC field in the other class that is part of its PK mapping (so we have a column here for it)
                            // Avoids the extra join to the other table
                            JavaTypeMapping[] subMappings = ((PersistenceCapableMapping)mapping).getJavaTypeMapping();
                            if (subMappings.length == 1)
                            {
                                // TODO Cater for a field of a composite PK being selected
                                return subMappings[0].newScalarExpression(qs, te);
                            }
                        }
                    }
                }
            }
            else
            {
                table = qs.getStoreManager().getDatastoreClass(mapping.getType(), clr);
            }
        }
        catch (ClassNotPersistenceCapableException cnpce)
        {
            return te.newFieldExpression(subfieldName);
        }

        if (fieldType != null && !fieldType.equals(mapping.getType()))
        {
            // The field relation is to a table that allows multiple types to be stored (and has a discriminator)
            // and the type we want is not the base type, so we need to restrict the values of the discriminator.
            DiscriminatorMetaData dismd = table.getDiscriminatorMetaData();
            DiscriminatorMapping discriminatorMapping = (DiscriminatorMapping)table.getDiscriminatorMapping(false);
            if (dismd != null && dismd.getStrategy() != DiscriminatorStrategy.NONE)
            {
                // Start with the required class
                BooleanExpression discrExpr = booleanConditionForClassInDiscriminator(qs, fieldType, dismd, discriminatorMapping, te);

                // Add "or" condition for any of its possible subclasses (if any)
                Iterator subclassIter = qs.getStoreManager().getSubClassesForClass(fieldType, true, clr).iterator();
                while (subclassIter.hasNext())
                {
                    String subCandidateType = (String)subclassIter.next();
                    discrExpr.ior(booleanConditionForClassInDiscriminator(qs, subCandidateType, dismd, discriminatorMapping, te));
                }

                discrExpr.encloseWithInParentheses();

                // Add the discriminator restrictions as an "and" condition to the Query Statement
                qs.andCondition(discrExpr);
            }
        }

        if (te.getMainTable().equals(table) && usingRelatedTable && fieldName==null)
        {
            //TODO fieldname==null, QUESTION is it "<candidateAlias>" namespace? debug and see
            // We are already in the same table (left outer join in the constructor) and it isn't a self reference so just return
            // the field expression. This can happen when we have a 1-1 bidir single FK and to generate the ObjectExpression we
            // had to join across to the related field
            return te.newFieldExpression(subfieldName);
        }

        // jt... = "joined table"
        String jtIdentifier = te.getAlias().getIdentifier();
        if (fieldName != null)
        {
            jtIdentifier += '.' + fieldName;
        }
        if (!subfieldName.equals("this")) // TODO Use qs.getCandidateAlias()
        {
            jtIdentifier += '.' + subfieldName;
        }

        if (castType != null && !castType.getName().equals(mapping.getType()))
        {
            String castTypeName = castType.getName();
            jtIdentifier += '.' + castTypeName.substring(castTypeName.lastIndexOf('.') + 1);
        }

        DatastoreIdentifier jtRangeVar = qs.getStoreManager().getIdentifierFactory().newIdentifier(IdentifierFactory.TABLE, jtIdentifier);
        LogicSetExpression jtTblExpr = qs.getTableExpression(jtRangeVar);
        if (jtTblExpr == null)
        {
            // We can't join further (this subfield is not an object with a table expression)
            if (te.getAlias().getIdentifier().equalsIgnoreCase("this") && // TODO Use qs.getCandidateAlias()
                table == qs.getMainTableExpression().getMainTable() &&
                fieldName == null)
            {
                // Query contains "this.field" so just provide the associated field expression for that field
                return qs.getMainTableExpression().newFieldExpression(subfieldName);
            }

            jtTblExpr = qs.newTableExpression(table, jtRangeVar);

            ScalarExpression jtExpr = table.getIDMapping().newScalarExpression(qs, jtTblExpr);
            ScalarExpression expr = mapping.newScalarExpression(qs, te);
            if (mapping.isNullable())
            {
                if (innerJoin)
                {
                    qs.innerJoin(jtExpr, expr, jtTblExpr, true, true);
                }
                else
                {
                    qs.leftOuterJoin(jtExpr, expr, jtTblExpr, true, true);
                }
            }
            else
            {
                qs.innerJoin(jtExpr, expr, jtTblExpr, true, true);
            }
        }

        if (mapping instanceof EmbeddedPCMapping)
        {
            return jtTblExpr.newFieldExpression(fieldName + "." + subfieldName);
        }
        else
        {
            return jtTblExpr.newFieldExpression(subfieldName);
        }
    }

    /**
     * Method to return a constraint for restricting the field to just instances of a particular class.
     * @param expr Expression for the class that we want instances of (a ClassExpression).
     * @return The expression for the instanceof clause
     */
    public BooleanExpression instanceOf(ScalarExpression expr)
    {
        if (expr instanceof ClassExpression)
        {
            ClassLoaderResolver clr = qs.getClassLoaderResolver();
            Class instanceofClass = ((ClassExpression)expr).getCls();
            Class fieldClass = clr.classForName(mapping.getType());
            if (!fieldClass.isAssignableFrom(instanceofClass) && !instanceofClass.isAssignableFrom(fieldClass))
            {
                // Field type and instanceof type are totally incompatible, so just return false
                return new BooleanLiteral(qs, mapping, true).eq(new BooleanLiteral(qs, mapping, false));
            }

            DatastoreContainerObject table;
            try
            {
                if (mapping instanceof EmbeddedMapping)
                {
                    // Field is embedded in this table
                    // TODO Enable instanceof on non-PC fields (currently just return "true")
                    return new BooleanLiteral(qs, mapping, true).eq(new BooleanLiteral(qs, mapping, true));
                }
                else if (mapping instanceof PersistenceCapableMapping || mapping instanceof ReferenceMapping)
                {
                    // Field has its own table, so join to it
                    AbstractClassMetaData fieldCmd = qs.getStoreManager().getOMFContext().getMetaDataManager().getMetaDataForClass(
                        mapping.getType(), clr);
                    if (fieldCmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE)
                    {
                        // Field is a PC class that uses "subclass-table" inheritance strategy (and so has multiple possible tables to join to)
                        AbstractClassMetaData[] cmds = qs.getStoreManager().getClassesManagingTableForClass(fieldCmd, clr);
                        if (cmds != null)
                        {
                            // Join to the first table
                            // TODO Allow for all possible tables. Can we do an OR of the tables ? How ?
                            if (cmds.length > 1)
                            {
                                JPOXLogger.QUERY.warn(LOCALISER.msg("037006",
                                    mapping.getFieldMetaData().getFullFieldName(), cmds[0].getFullClassName()));
                            }
                            table = qs.getStoreManager().getDatastoreClass(cmds[0].getFullClassName(), clr);
                        }
                        else
                        {
                            // No subclasses with tables to join to, so throw a user error
                            throw new JPOXUserException(LOCALISER.msg("037005",
                                mapping.getFieldMetaData().getFullFieldName()));
                        }
                    }
                    else
                    {
                        // Class of the field will have its own table
                        table = qs.getStoreManager().getDatastoreClass(mapping.getType(), clr);
                    }
                }
                else
                {
                    table = qs.getStoreManager().getDatastoreClass(mapping.getType(), clr);
                }
            }
            catch (ClassNotPersistenceCapableException cnpce)
            {
                // Field is not PersistenceCapable
                // TODO Enable instanceof on non-PC fields (currently just return "true")
                return new BooleanLiteral(qs, mapping, true).eq(new BooleanLiteral(qs, mapping, true));
            }

            // Check if the table of the field has a discriminator
            IdentifierFactory idFactory = qs.getStoreManager().getIdentifierFactory();
            DiscriminatorMetaData dismd = table.getDiscriminatorMetaData();
            DiscriminatorMapping discriminatorMapping = (DiscriminatorMapping)table.getDiscriminatorMapping(false);
            if (discriminatorMapping != null)
            {
                // Has a discriminator so do a join to the table of the field and apply a constraint on its discriminator
                LogicSetExpression fieldTblExpr = null;
                if (fieldName == null)
                {
                    // Using THIS so use default table expression
                    fieldTblExpr = qs.getMainTableExpression();
                }
                else
                {
                    // Using field, so our real table will have an identifier of "THIS_{fieldName}" via INNER JOIN
                    String fieldIdentifier = te.getAlias().getIdentifier();
                    fieldIdentifier += '.' + fieldName;
                    DatastoreIdentifier fieldRangeVar = idFactory.newIdentifier(IdentifierFactory.TABLE, fieldIdentifier);
                    fieldTblExpr = qs.getTableExpression(fieldRangeVar);
                    if (fieldTblExpr == null)
                    {
                        fieldTblExpr = qs.newTableExpression(table, fieldRangeVar);
                    }
                    ScalarExpression fieldExpr = table.getIDMapping().newScalarExpression(qs, fieldTblExpr);
                    expr = mapping.newScalarExpression(qs, te);
                    qs.innerJoin(fieldExpr, expr, fieldTblExpr, true, true);
                }

                // Return a constraint on the discriminator for this table to get the right instances
                // This allows all discriminator values for the instanceof class and all of its subclasses
                // DISCRIM = 'baseVal' OR DISCRIM = 'sub1Val' OR DISCRIM = 'sub2Val' ... etc
                BooleanExpression discrExpr =
                    booleanConditionForClassInDiscriminator(qs, instanceofClass.getName(), dismd,
                        discriminatorMapping, fieldTblExpr);
                Iterator subclassIter = qs.getStoreManager().getSubClassesForClass(instanceofClass.getName(),
                    true, clr).iterator();
                while (subclassIter.hasNext())
                {
                    String subCandidateType = (String)subclassIter.next();
                    discrExpr.ior(booleanConditionForClassInDiscriminator(qs, subCandidateType, dismd,
                        discriminatorMapping, fieldTblExpr));
                }
                discrExpr.encloseWithInParentheses();

                return discrExpr;
            }
            else
            {
                // No discriminator so maybe union, or just a SELECT
                // Need to join to the instanceof class (where appropriate)
                // TODO RDBMS-71 Only join on the UNION select that it is applicable to
                if (table instanceof DatastoreClass)
                {
                    DatastoreClass ct = (DatastoreClass)table;
                    if (ct.managesClass(instanceofClass.getName()))
                    {
                        // This type is managed in this table so must be an instance
                        return new BooleanLiteral(qs, mapping, true).eq(new BooleanLiteral(qs, mapping, true));
                    }
                    else
                    {
                        // The instanceof type is not managed here
                        DatastoreClass instanceofTable = qs.getStoreManager().getDatastoreClass(
                            instanceofClass.getName(), clr);
                        String fieldIdentifier = te.getAlias().getIdentifier();
                        if (fieldName == null)
                        {
                            // Using THIS, so our real table will have an identifier of "THIS_INST"
                            fieldIdentifier += ".INST";
                        }
                        else
                        {
                            // Using field, so our real table will have an identifier of "THIS_{fieldName}"
                            fieldIdentifier += '.' + fieldName;
                        }
                        DatastoreIdentifier fieldRangeVar = idFactory.newIdentifier(IdentifierFactory.TABLE, fieldIdentifier);
                        LogicSetExpression fieldTblExpr = qs.newTableExpression(instanceofTable, fieldRangeVar);
                        ScalarExpression fieldExpr = table.getIDMapping().newScalarExpression(qs, te);
                        if (fieldName == null)
                        {
                            expr = instanceofTable.getIDMapping().newScalarExpression(qs, fieldTblExpr);
                        }
                        else
                        {
                            expr = mapping.newScalarExpression(qs, fieldTblExpr);
                        }
                        qs.innerJoin(fieldExpr, expr, fieldTblExpr, true, true);
                        return new BooleanLiteral(qs, mapping, true).eq(new BooleanLiteral(qs, mapping, true));
                    }
                }
                else
                {
                    // Assumed to be in the right class
                    return new BooleanLiteral(qs, mapping, true).eq(new BooleanLiteral(qs, mapping, true));
                }
            }
        }
        else
        {
            // Invalid to use "XX instanceof YY" where YY is not a class.
            throw new JPOXUserException(LOCALISER.msg("037007", expr.getClass().getName()));
        }
    }

    /**
     * Set the field which this expression was created from.
     * @param fieldName The fieldName to set.
     * @param fieldType The fieldType to set
     */
    public void setFieldDefinition(String fieldName, String fieldType)
    {
        this.fieldName = fieldName;
        this.fieldType = fieldType;
    }

    /**
     * Convenience method to generate a BooleanExpression for a value of the
     * discriminator for the provided table expression.
     * @param stmt The Query Statement to be updated
     * @param className The possible class name
     * @param dismd MetaData for the discriminator
     * @param discriminatorMapping Mapping for the discriminator
     * @param tableExpr Table Expression
     * @return BooleanExpression for this discriminator value
     */
    private BooleanExpression booleanConditionForClassInDiscriminator(QueryExpression stmt,
            String className,
            DiscriminatorMetaData dismd,
            JavaTypeMapping discriminatorMapping,
            LogicSetExpression tableExpr)
    {
        // Default to the "class-name" discriminator strategy
        String discriminatorValue = className;
        if (dismd.getStrategy() == DiscriminatorStrategy.VALUE_MAP)
        {
            // Get the MetaData for the target class since that holds the "value"
            AbstractClassMetaData targetCmd = stmt.getStoreManager().getOMFContext().getMetaDataManager().getMetaDataForClass(className, stmt.getClassLoaderResolver());
            discriminatorValue = targetCmd.getInheritanceMetaData().getDiscriminatorMetaData().getValue();
        }
        ScalarExpression discrExpr = discriminatorMapping.newScalarExpression(stmt, tableExpr);
        ScalarExpression discrVal = discriminatorMapping.newLiteral(stmt, discriminatorValue);
        return discrExpr.eq(discrVal);
    }
}
TOP

Related Classes of org.jpox.store.mapped.expression.ObjectExpression

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.