/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * tware - initial implementation ******************************************************************************/ package org.eclipse.persistence.internal.expressions; import java.io.BufferedWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; 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.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.queries.ContainerPolicy; import org.eclipse.persistence.internal.queries.InterfaceContainerPolicy; import org.eclipse.persistence.mappings.CollectionMapping; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.querykeys.ForeignReferenceQueryKey; import org.eclipse.persistence.mappings.querykeys.QueryKey; import org.eclipse.persistence.queries.ReadQuery; public class MapEntryExpression extends QueryKeyExpression { protected boolean returnMapEntry = false; public MapEntryExpression(Expression base) { this(); this.baseExpression = base; } public MapEntryExpression() { this.shouldQueryToManyRelationship = true; this.hasQueryKey = true; this.hasMapping = false; } /** * INTERNAL: * Find the alias for a given table. A TableEntry is a place holder and its base expression holds * all the relevant information. Get the alias from the baseExpression */ @Override public DatabaseTable aliasForTable(DatabaseTable table) { return ((DataExpression)getBaseExpression()).aliasForTable(table); } /** * Set this expression to represent a Map.Entry rather than the Map's key */ public void returnMapEntry(){ returnMapEntry = true; } /** * INTERNAL: * This expression is built on a different base than the one we want. Rebuild it and * return the root of the new tree */ @Override public Expression rebuildOn(Expression newBase) { Expression newLocalBase = getBaseExpression().rebuildOn(newBase); Expression result = null; if (returnMapEntry){ result = newLocalBase.mapEntry(); } else { result = newLocalBase.mapKey(); } result.setSelectIfOrderedBy(selectIfOrderedBy()); return result; } /** * INTERNAL: * A special version of rebuildOn where the newBase need not be a new * ExpressionBuilder but any expression. * <p> * For nested joined attributes, the joined attribute query must have * its joined attributes rebuilt relative to it. */ @Override public Expression rebuildOn(Expression oldBase, Expression newBase) { if (this == oldBase) { return newBase; } Expression newLocalBase = ((QueryKeyExpression)getBaseExpression()).rebuildOn(oldBase, newBase); Expression result = null; if (returnMapEntry){ result = newLocalBase.mapEntry(); } else { result = newLocalBase.mapKey(); } result.setSelectIfOrderedBy(selectIfOrderedBy()); return result; } /** * INTERNAL: * Used for debug printing. */ @Override public String descriptionOfNodeType() { if (returnMapEntry){ return "MapEntry"; } else { return "MapKey"; } } /** * INTERNAL: */ @Override public Expression existingDerivedTable(DatabaseTable table) { if (baseExpression.isDataExpression()){ return ((DataExpression)baseExpression).existingDerivedTable(table); } return super.existingDerivedTable(table); } /** * Exclude any tables defined by base. */ @Override public List<DatabaseTable> getOwnedTables() { return null; } @Override public ClassDescriptor getDescriptor() { if (isAttribute()) { return null; } if (descriptor == null) { // Look first for query keys, then mappings. Ultimately we should have query keys // for everything and can dispense with the mapping part. ForeignReferenceQueryKey queryKey = (ForeignReferenceQueryKey)getQueryKeyOrNull(); if (queryKey != null) { descriptor = getSession().getDescriptor(queryKey.getReferenceClass()); return descriptor; } if (getMapping() == null) { throw QueryException.invalidQueryKeyInExpression(this); } // We assume this is either a foreign reference or an aggregate mapping descriptor = getMapping().getContainerPolicy().getDescriptorForMapKey(); if (getMapping().isVariableOneToOneMapping()) { throw QueryException.cannotQueryAcrossAVariableOneToOneMapping(getMapping(), descriptor); } } return descriptor; } /** * INTERNAL: */ @Override public DatabaseField getField() { if (!isAttribute()) { return null; } DatabaseField field = getInterfaceContainerPolicy().getDirectKeyField(getMapping()); return field; } /** * INTERNAL: * Return all the fields */ @Override public Vector getFields() { Vector result = new Vector(); InterfaceContainerPolicy icp = getInterfaceContainerPolicy(); // if this is a map entry get all the fields for both the key and the value if (returnMapEntry || !icp.isMappedKeyMapPolicy()){ result.addAll(getBaseExpression().getFields()); } else if (isAttribute()) { DatabaseField field = getField(); if (field != null) { result.add(field); } } else { result.addAll(getInterfaceContainerPolicy().getAdditionalFieldsForJoin(getMapping())); } return result; } /** * INTERNAL: */ @Override public List<DatabaseField> getSelectionFields(ReadQuery query) { ArrayList<DatabaseField> result = new ArrayList<DatabaseField>(); InterfaceContainerPolicy icp = getInterfaceContainerPolicy(); // if this is a map entry get all the fields for both the key and the value if (returnMapEntry || !icp.isMappedKeyMapPolicy()){ result.addAll(getBaseExpression().getSelectionFields(query)); } else if (isAttribute()) { DatabaseField field = getField(); if (field != null) { result.add(field); } } else { result.addAll(getInterfaceContainerPolicy().getAdditionalFieldsForJoin(getMapping())); } return result; } @Override public CollectionMapping getMapping() { return (CollectionMapping)((QueryKeyExpression)getBaseExpression()).getMapping(); } @Override public QueryKey getQueryKeyOrNull() { if (!hasQueryKey) { return null; } InterfaceContainerPolicy cp = getInterfaceContainerPolicy(); if (queryKey == null) { if (returnMapEntry){ return null; } else { queryKey = cp.createQueryKeyForMapKey(); } } return queryKey; } /** * INTERNAL: * Return if the expression is for a direct mapped attribute. */ @Override public boolean isAttribute() { if (isAttributeExpression == null) { if (returnMapEntry){ return false; } InterfaceContainerPolicy containerPolicy = getInterfaceContainerPolicy(); return containerPolicy.isMapKeyAttribute(); } return isAttributeExpression.booleanValue(); } /** * INTERNAL: */ @Override public boolean isMapEntryExpression(){ return true; } private InterfaceContainerPolicy getInterfaceContainerPolicy(){ DatabaseMapping mapping = getMapping(); if (mapping == null) { throw QueryException.noMappingForMapEntryExpression(getBaseExpression()); } if (!mapping.isCollectionMapping()){ throw QueryException.mapEntryExpressionForNonCollection(getBaseExpression(), getMapping()); } InterfaceContainerPolicy cp = null; try{ cp = (InterfaceContainerPolicy)getMapping().getContainerPolicy(); } catch (ClassCastException e){ throw QueryException.mapEntryExpressionForNonMap(getBaseExpression(), getMapping()); } return cp; } /** * INTERNAL: * Mapping criteria will be provided by the base expression */ @Override public Expression mappingCriteria(Expression base) { return null; } public boolean shouldReturnMapEntry(){ return returnMapEntry; } /** * Do any required validation for this node. Throw an exception if it's incorrect. */ @Override public void validateNode() { if ((getQueryKeyOrNull() == null) && (getMapping() == null)) { throw QueryException.invalidQueryKeyInExpression(getName()); } if (!getMapping().isCollectionMapping()) { throw QueryException.mapEntryExpressionForNonCollection(getBaseExpression(), getMapping()); } ContainerPolicy cp = getMapping().getContainerPolicy(); if ((cp == null) || !cp.isMapPolicy()) { throw QueryException.mapEntryExpressionForNonMap(getBaseExpression(), getMapping()); } } /** * INTERNAL: * Used to print a debug form of the expression tree. */ @Override public void writeDescriptionOn(BufferedWriter writer) throws IOException { writer.write(descriptionOfNodeType()); } }