blob: 7afb3cdb9b3443ae767d8cd64e8adc346d7b0d2c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// May 21, 2009-2.0 Chris Delahunt
// - TODO Bug#: Bug Description
package org.eclipse.persistence.internal.expressions;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
/**
* @author cdelahun
*
*/
public class ClassTypeExpression extends DataExpression {
/** Cache the aliased field. Only applies to attributes. */
protected DatabaseField field;
/** Cache the aliased field. Only applies to attributes. */
protected DatabaseField aliasedField;
/**
*
*/
public ClassTypeExpression(Expression base) {
super();
this.baseExpression = base;
}
public ClassTypeExpression() {
super();
}
/**
* INTERNAL:
* Used for debug printing.
*/
@Override
public String descriptionOfNodeType() {
return "Class For Inheritance";
}
/* (non-Javadoc)
* @see org.eclipse.persistence.expressions.Expression#rebuildOn(org.eclipse.persistence.expressions.Expression)
*/
@Override
public Expression rebuildOn(Expression newBase) {
Expression newLocalBase = getBaseExpression().rebuildOn(newBase);
Expression result = newLocalBase.type();
result.setSelectIfOrderedBy(selectIfOrderedBy());
return result;
}
/**
* INTERNAL
* This method returns the inheritance field value for an object to conform in an in-memory query.
* Similar to getFieldValue, but deals with an instance rather than a Class object directly
*/
public Object typeValueFromObject(Object object, AbstractSession session) {
// get the descriptor directly from the object, and use it to find the Java class
ClassDescriptor objectDescriptor = session.getClassDescriptor(object);
if (!objectDescriptor.hasInheritance()
|| objectDescriptor.getInheritancePolicy().shouldUseClassNameAsIndicator()
|| objectDescriptor.getInheritancePolicy().hasClassExtractor() ) {
return (objectDescriptor.getJavaClassName());
} else {
return objectDescriptor.getInheritancePolicy().getClassIndicatorMapping().get(objectDescriptor.getJavaClass());
}
}
@Override
public void validateNode() {
ClassDescriptor descriptor = getContainingDescriptor();
if (descriptor ==null){
throw QueryException.invalidTypeExpression(getBaseExpression());
}
if ( (!descriptor.hasInheritance()) || (!descriptor.getInheritancePolicy().hasClassIndicator()) ) {
throw QueryException.invalidTypeExpression(descriptor.getJavaClassName());
}
super.validateNode();
}
/**
* INTERNAL:
* Return the value for in memory comparison.
* This is only valid for value expressions.
* Pulled from QueryKeyExpression valueFromObject
*/
@Override
public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) {
// The expression may be across a relationship, in which case it must be traversed.
if ((!getBaseExpression().isExpressionBuilder()) && getBaseExpression().isQueryKeyExpression()) {
object = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
// toDo: Null means the join filters out the row, returning null is not correct if an inner join,
// outer/inner joins need to be fixed to filter correctly.
if (object == null) {
return null;
}
// If from an anyof the object will be a collection of values,
// A new vector must union the object values and the values extracted from it.
if (object instanceof Vector) {
Vector comparisonVector = new Vector(((Vector)object).size() + 2);
for (Enumeration valuesToIterate = ((Vector)object).elements();
valuesToIterate.hasMoreElements();) {
Object vectorObject = valuesToIterate.nextElement();
if (vectorObject == null) {
comparisonVector.addElement(null);
} else {
Object valueOrValues = typeValueFromObject(vectorObject, session);
// If a collection of values were extracted union them.
if (valueOrValues instanceof Vector) {
for (Enumeration nestedValuesToIterate = ((Vector)valueOrValues).elements();
nestedValuesToIterate.hasMoreElements();) {
comparisonVector.addElement(nestedValuesToIterate.nextElement());
}
} else {
comparisonVector.addElement(valueOrValues);
}
}
}
return comparisonVector;
}
}
return typeValueFromObject(object, session);
}
/**
* INTERNAL:
* Used to print a debug form of the expression tree.
*/
@Override
public void writeDescriptionOn(BufferedWriter writer) throws IOException {
writer.write("TYPE");
writer.write(tableAliasesDescription());
}
/**
* INTERNAL:
*/
@Override
public DatabaseField getField() {
if (field == null) {
ClassDescriptor descriptor = getContainingDescriptor();
if (!descriptor.hasInheritance() || descriptor.getInheritancePolicy().hasClassExtractor()){
throw QueryException.invalidTypeExpression(descriptor.getJavaClassName());
}
field = descriptor.getInheritancePolicy().getClassIndicatorField();
}
return field;
}
/**
* INTERNAL:
* Transform the object-level value into a database-level value
* objectValue is a Class or collection of Class objects and the returned value is the database representation
* Example: ObjectValue=LargeProject returns "L".
*/
@Override
public Object getFieldValue(Object objectValue, AbstractSession session) {
if (objectValue ==null){
return null;
}
if (objectValue instanceof Collection) {
// This can actually be a collection for IN within expressions... however it would be better for expressions to handle this.
Collection values = (Collection)objectValue;
Vector fieldValues = new Vector(values.size());
for (Iterator iterator = values.iterator(); iterator.hasNext();) {
Object value = iterator.next();
if (!(value instanceof Expression)){
value = getFieldValue(value, session);
}
fieldValues.add(value);
}
return fieldValues;
} else {
if (! (objectValue instanceof Class) ){
throw QueryException.invalidTypeExpression(objectValue.getClass().toString());
}
ClassDescriptor descriptor = session.getDescriptor((Class)objectValue);
if (descriptor == null){
throw QueryException.invalidTypeExpression(objectValue.getClass().toString());
}
if (descriptor.hasInheritance() && !descriptor.getInheritancePolicy().shouldUseClassNameAsIndicator()){
return descriptor.getInheritancePolicy().getClassIndicatorMapping().get(objectValue);
} else {
return ((Class)objectValue).getName();
}
}
}
/**
* INTERNAL:
* Like QueryKeyExpression, return the descriptor for the class type used, null if one can't be determined yet.
* Should only be called when a session is already set.
*/
@Override
public ClassDescriptor getContainingDescriptor() {
return ((ObjectExpression)getBaseExpression()).getDescriptor();
}
/**
* INTERNAL:
* Return the descriptor for the base expression. This is used in ReportItem when building the
* return value (a class), as none of the expressions will have a session.
*/
public ClassDescriptor getContainingDescriptor(ObjectLevelReadQuery query) {
Class<?> queryClass = null;
if (getBaseExpression().isExpressionBuilder()){
queryClass = ((ExpressionBuilder)getBaseExpression()).getQueryClass();
return query.getSession().getDescriptor(queryClass);
} else {
// It must be a QueryKeyExpression.
return getBaseExpression().getLeafDescriptor(query, query.getDescriptor(), query.getSession());
}
}
@Override
public boolean isClassTypeExpression(){
return true;
}
/**
* INTERNAL:
*/
@Override
public boolean isAttribute() {
return true;
}
/**
* INTERNAL:
* For CR#2456 if this is part of an objExp.equal(objExp), do not need to add
* additional expressions to normalizer both times, and the foreign key join
* replaces the equal expression.
*/
public Expression normalize(ExpressionNormalizer normalizer, Vector foreignKeyJoinPointer) {
if (hasBeenNormalized()) {
return this;
}
return super.normalize(normalizer);
}
/**
* INTERNAL:
* Alias the database field for our current environment
*/
protected void initializeAliasedField() {
DatabaseField tempField = getField().clone();
DatabaseTable aliasedTable = getAliasedTable();
aliasedField = tempField;
aliasedField.setTable(aliasedTable);
}
/**
* INTERNAL:
* Return the field appropriately aliased
*/
@Override
public DatabaseField getAliasedField() {
if (aliasedField == null) {
initializeAliasedField();
}
return aliasedField;
}
/**
* Return the alias for our table
*/
protected DatabaseTable getAliasedTable() {
DataExpression base = (DataExpression)getBaseExpression();
DatabaseTable alias = base.aliasForTable(getField().getTable());
if (alias == null) {
return getField().getTable();
} else {
return alias;
}
}
/**
* INTERNAL:
* Return all the fields
*/
@Override
public Vector getFields() {
Vector result = new Vector(1);
DatabaseField field = getField();
if (field != null) {
result.addElement(field);
}
return result;
}
@Override
public Expression twistedForBaseAndContext(Expression newBase, Expression context, Expression oldBase) {
if (oldBase == null || this.baseExpression == oldBase) {
Expression twistedBase = this.baseExpression.twistedForBaseAndContext(newBase, context, oldBase);
return twistedBase.type();
}
return this;
}
}