| /* |
| * Copyright (c) 1998, 2019 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: |
| // 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 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<>(); |
| 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()); |
| } |
| |
| } |
| |