All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.queries.ReportQueryResult Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2019, 2020 IBM Corporation. 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:
//     Oracle - initial API and implementation from Oracle TopLink
//     11/10/2011-2.4 Guy Pelletier
//       - 357474: Address primaryKey option from tenant discriminator column
//     10/01/2018: Will Dazey
//       - #253: Add support for embedded constructor results with CriteriaBuilder
package org.eclipse.persistence.queries;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Vector;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.ExpressionOperator;
import org.eclipse.persistence.internal.expressions.FunctionExpression;
import org.eclipse.persistence.internal.expressions.MapEntryExpression;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.NonSynchronizedSubVector;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.queries.ReportItem;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.mappings.AggregateObjectMapping;
import org.eclipse.persistence.mappings.Association;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Session;

/**
 * Purpose: A single row (type) result for a ReportQuery

* * Description: Represents a single row of attribute values (converted using mapping) for * a ReportQuery. The attributes can be from various objects. * * Responsibilities:

    *
  • Converted field values into object attribute values. *
  • Provide access to values by index or item name *
* * @author Doug Clarke * @since TOPLink/Java 2.0 */ public class ReportQueryResult implements Serializable, Map { /** Item names to lookup result values */ protected List names; /** Actual converted attribute values */ protected List results; /** Id value if the retrievPKs flag was set on the ReportQuery. These can be used to get the actual object */ protected Object primaryKey; /** If an objectLevel distinct is used then generate unique key for this result */ // GF_ISSUE_395 protected StringBuffer key; /** * INTERNAL: * Used to create test results */ public ReportQueryResult(List results, Object primaryKeyValues) { this.results = results; this.primaryKey = primaryKeyValues; } public ReportQueryResult(ReportQuery query, AbstractRecord row, Vector toManyResults) { super(); this.names = query.getNames(); buildResult(query, row, toManyResults); } /** * INTERNAL: * Create an array of attribute values (converted from raw field values using the mapping). */ protected void buildResult(ReportQuery query, AbstractRecord row, Vector toManyData) { // GF_ISSUE_395 if (query.shouldDistinctBeUsed() && (query.shouldFilterDuplicates())) { this.key = new StringBuffer(); } if (query.shouldRetrievePrimaryKeys()) { setId(query.getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(row, query.getSession())); // For bug 3115576 this is only used for EXISTS sub-selects so no result is needed. } List results = new ArrayList<>(); for(ReportItem item: query.getItems()) { if (item.isConstructorItem()) { Object result = processConstructorItem(query, row, toManyData, (ConstructorReportItem) item); results.add(result); } else if (item.getAttributeExpression() != null && item.getAttributeExpression().isClassTypeExpression()) { Object value = processItem(query, row, toManyData, item); ClassDescriptor descriptor = ((org.eclipse.persistence.internal.expressions.ClassTypeExpression)item.getAttributeExpression()).getContainingDescriptor(query); if (descriptor != null && descriptor.hasInheritance()) { value = descriptor.getInheritancePolicy().classFromValue(value, query.getSession()); } else { value = query.getSession().getDatasourcePlatform().convertObject(value, Class.class); } results.add(value); } else { // Normal items Object value = processItem(query, row, toManyData, item); results.add(value); } } setResults(results); } private Object processConstructorItem(ReportQuery query, AbstractRecord row, Vector toManyData, ConstructorReportItem constructorItem) { // For constructor items need to process each constructor argument. Class[] constructorArgTypes = constructorItem.getConstructorArgTypes(); int numberOfArguments = constructorItem.getReportItems().size(); Object[] constructorArgs = new Object[numberOfArguments]; for (int argumentIndex = 0; argumentIndex < numberOfArguments; argumentIndex++) { ReportItem argumentItem = constructorItem.getReportItems().get(argumentIndex); Object result = null; if(argumentItem.isConstructorItem()) { result = processConstructorItem(query, row, toManyData, (ConstructorReportItem) argumentItem); } else { result = processItem(query, row, toManyData, argumentItem); } constructorArgs[argumentIndex] = ConversionManager.getDefaultManager().convertObject(result, constructorArgTypes[argumentIndex]); } try { Constructor constructor = constructorItem.getConstructor(); Object returnValue = null; if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ try { returnValue = AccessController.doPrivileged(new PrivilegedInvokeConstructor(constructor, constructorArgs)); } catch (PrivilegedActionException exception) { throw QueryException.exceptionWhileUsingConstructorExpression(exception.getException(), query); } } else { returnValue = PrivilegedAccessHelper.invokeConstructor(constructor, constructorArgs); } return returnValue; } catch (ReflectiveOperationException exc) { throw QueryException.exceptionWhileUsingConstructorExpression(exc, query); } } private Object processItemFromMapping(ReportQuery query, AbstractRecord row, DatabaseMapping mapping, ReportItem item, int itemIndex) { Object value = null; // If mapping is not null then it must be a direct mapping - see Reportitem.init. // Check for non database (EIS) records to use normal get. if (row instanceof DatabaseRecord) { value = row.getValues().get(itemIndex); } else { value = row.get(mapping.getField()); } //Bug 421056: JPA 2.1; section 4.8.5 if(item.getAttributeExpression().isFunctionExpression()) { FunctionExpression exp = (FunctionExpression) item.getAttributeExpression(); int selector = exp.getOperator().getSelector(); //a value of null for max/min implies no rows could be applied //we want to return null, per the spec, here before the mapping gets to alter the value if (value == null && ((selector == ExpressionOperator.Maximum) || (selector == ExpressionOperator.Minimum))) { return value; } } //If the mapping was set on the ReportItem, then use the mapping to convert the value if (mapping.isAbstractColumnMapping()) { value = ((AbstractColumnMapping)mapping).getObjectValue(value, query.getSession()); } else if (mapping.isDirectCollectionMapping()) { value = ((DirectCollectionMapping)mapping).getObjectValue(value, query.getSession()); } return value; } /** * INTERNAL: * Return a value from an item and database row (converted from raw field values using the mapping). */ protected Object processItem(ReportQuery query, AbstractRecord row, Vector toManyData, ReportItem item) { JoinedAttributeManager joinManager = null; if (item.hasJoining()) { joinManager = item.getJoinedAttributeManager(); if (joinManager.isToManyJoin()) { // PERF: Only reset data-result if unset, must only occur once per item, not per row (n vs n^2). if (joinManager.getDataResults_() == null) { joinManager.setDataResults(new ArrayList(toManyData), query.getSession()); } } } Object value = null; int rowSize = row.size(); int itemIndex = item.getResultIndex(); DatabaseMapping mapping = item.getMapping(); ClassDescriptor descriptor = item.getDescriptor(); if (item.getAttributeExpression() != null) { if (descriptor == null && mapping != null) { descriptor = mapping.getReferenceDescriptor(); } if (mapping != null && (mapping.isAbstractColumnMapping() || mapping.isDirectCollectionMapping())) { if (itemIndex >= rowSize) { throw QueryException.reportQueryResultSizeMismatch(itemIndex + 1, rowSize); } value = processItemFromMapping(query, row, mapping, item, itemIndex); // GF_ISSUE_395+ if (this.key != null) { this.key.append(value); this.key.append("_"); } } else if (descriptor != null) { // Item is for an object result. int size = descriptor.getAllSelectionFields(query).size(); if (itemIndex + size > rowSize) { throw QueryException.reportQueryResultSizeMismatch(itemIndex + size, rowSize); } AbstractRecord subRow = row; // Check if at the start of the row, then avoid building a subRow. if (itemIndex > 0) { Vector trimedFields = new NonSynchronizedSubVector<>(row.getFields(), itemIndex, rowSize); Vector trimedValues = new NonSynchronizedSubVector(row.getValues(), itemIndex, rowSize); subRow = new DatabaseRecord(trimedFields, trimedValues); } if (mapping != null && mapping.isAggregateObjectMapping()){ value = ((AggregateObjectMapping)mapping).buildAggregateFromRow(subRow, null, null, joinManager, query, false, query.getSession(), true); } else { //TODO : Support prefrechedCacheKeys in report query value = descriptor.getObjectBuilder().buildObject(query, subRow, joinManager); } // this covers two possibilities // 1. We want the actual Map.Entry from the table rather than the just the key // 2. The map key is extracted from the owning object rather than built with // a specific mapping. This could happen in a MapContainerPolicy if (item.getAttributeExpression().isMapEntryExpression() && mapping.isCollectionMapping()){ Object rowKey = null; if (mapping.getContainerPolicy().isMapPolicy() && !mapping.getContainerPolicy().isMappedKeyMapPolicy()){ rowKey = mapping.getContainerPolicy().keyFrom(value, query.getSession()); } else { rowKey = mapping.getContainerPolicy().buildKey(subRow, query, null, query.getSession(), true); } if (((MapEntryExpression)item.getAttributeExpression()).shouldReturnMapEntry()){ value = new Association(rowKey, value); } else { value = rowKey; } } // GF_ISSUE_395 if (this.key != null) { Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromRow(subRow, query.getSession()); if (primaryKey != null){//GF3233 NPE is caused by processing the null PK being extracted from referenced target with null values in database. this.key.append(primaryKey); } this.key.append("_"); } } else { value = row.getValues().get(itemIndex); // GF_ISSUE_395 if (this.key != null) { this.key.append(value); } } } return value; } /** * PUBLIC: * Clear the contents of the result. */ @Override public void clear() { this.names = new ArrayList<>(); this.results = new ArrayList<>(); } /** * PUBLIC: * Check if the value is contained in the result. */ public boolean contains(Object value) { return containsValue(value); } /** * PUBLIC: * Check if the key is contained in the result. */ @Override public boolean containsKey(Object key) { return getNames().contains(key); } /** * PUBLIC: * Check if the value is contained in the result. */ @Override public boolean containsValue(Object value) { return getResults().contains(value); } /** * PUBLIC: * Returns a set of the keys. */ @Override public Set entrySet() { return new EntrySet(); } /** * Defines the virtual entrySet. */ protected class EntrySet extends AbstractSet { @Override public Iterator iterator() { return new EntryIterator(); } @Override public int size() { return ReportQueryResult.this.size(); } @Override public boolean contains(Object object) { if (!(object instanceof Entry)) { return false; } return ReportQueryResult.this.containsKey(((Entry)object).getKey()); } @Override public boolean remove(Object object) { if (!(object instanceof Entry)) { return false; } ReportQueryResult.this.remove(((Entry)object).getKey()); return true; } @Override public void clear() { ReportQueryResult.this.clear(); } } /** * Entry class for implementing Map interface. */ protected static class RecordEntry implements Entry { Object key; Object value; public RecordEntry(Object key, Object value) { this.key = key; this.value = value; } @Override public Object getKey() { return key; } @Override public Object getValue() { return value; } @Override public Object setValue(Object value) { Object oldValue = this.value; this.value = value; return oldValue; } @Override public boolean equals(Object object) { if (!(object instanceof Map.Entry)) { return false; } Map.Entry entry = (Map.Entry)object; return compare(key, entry.getKey()) && compare(value, entry.getValue()); } @Override public int hashCode() { return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode()); } @Override public String toString() { return key + "=" + value; } private boolean compare(Object object1, Object object2) { return (object1 == null ? object2 == null : object1.equals(object2)); } } /** * Defines the virtual entrySet iterator. */ protected class EntryIterator implements Iterator { int index; EntryIterator() { this.index = 0; } @Override public boolean hasNext() { return this.index < ReportQueryResult.this.size(); } @Override public Object next() { if (!hasNext()) { throw new NoSuchElementException(); } this.index++; return new RecordEntry(getNames().get(this.index - 1), getResults().get(this.index - 1)); } @Override public void remove() { if (this.index >= ReportQueryResult.this.size()) { throw new IllegalStateException(); } ReportQueryResult.this.remove(getNames().get(this.index)); } } /** * Defines the virtual keySet iterator. */ protected class KeyIterator extends EntryIterator { @Override public Object next() { if (!hasNext()) { throw new NoSuchElementException(); } this.index++; return getNames().get(this.index - 1); } } /** * PUBLIC: * Compare if the two results are equal. */ @Override public boolean equals(Object anObject) { if (anObject instanceof ReportQueryResult) { return equals((ReportQueryResult)anObject); } return false; } /** * INTERNAL: * Used in testing to compare if results are correct. */ public boolean equals(ReportQueryResult result) { if (this == result) { return true; } if (!getResults().equals(result.getResults())) { return false; } // Compare PKs if (getId() != null) { if (result.getId() == null) { return false; } return getId().equals(getId()); } return true; } @Override public int hashCode() { List results = getResults(); Object id = getId(); int result = results != null ? results.hashCode() : 0; result = 31 * result + (id != null ? id.hashCode() : 0); return result; } /** * PUBLIC: * Return the value for given item name. */ @Override public Object get(Object name) { if (name instanceof String) { return get((String)name); } return null; } /** * PUBLIC: * Return the value for given item name. */ public Object get(String name) { int index = getNames().indexOf(name); if (index == -1) { return null; } return getResults().get(index); } /** * PUBLIC: * Return the indexed value from result. */ public Object getByIndex(int index) { return getResults().get(index); } /** * INTERNAL: * Return the unique key for this result */ public String getResultKey(){ if (this.key != null){ return this.key.toString(); } return null; } /** * PUBLIC: * Return the names of report items, provided to ReportQuery. */ public List getNames() { return names; } /** * PUBLIC: * Return the Id for the result or null if not requested. */ public Object getId() { return primaryKey; } /** * PUBLIC: * Return the results. */ public List getResults() { return results; } /** * PUBLIC: * Return if the result is empty. */ @Override public boolean isEmpty() { return getNames().isEmpty(); } /** * PUBLIC: * Returns a set of the keys. */ @Override public Set keySet() { return new KeySet(); } /** * Defines the virtual keySet. */ protected class KeySet extends EntrySet { @Override public Iterator iterator() { return new KeyIterator(); } @Override public boolean contains(Object object) { return ReportQueryResult.this.containsKey(object); } @Override public boolean remove(Object object) { return ReportQueryResult.this.remove(object) != null; } } /** * ADVANCED: * Set the value for given item name. */ @Override public Object put(Object name, Object value) { int index = getNames().indexOf(name); if (index == -1) { getNames().add((String)name); getResults().add(value); return null; } Object oldValue = getResults().get(index); getResults().set(index, value); return oldValue; } /** * PUBLIC: * Add all of the elements. */ @Override public void putAll(Map map) { Iterator entriesIterator = map.entrySet().iterator(); while (entriesIterator.hasNext()) { Map.Entry entry = (Map.Entry)entriesIterator.next(); put(entry.getKey(), entry.getValue()); } } /** * PUBLIC: * If the PKs were retrieved with the attributes then this method can be used to read the real object from the database. */ public Object readObject(Class javaClass, Session session) { if (getId() == null) { throw QueryException.reportQueryResultWithoutPKs(this); } ReadObjectQuery query = new ReadObjectQuery(javaClass); query.setSelectionId(getId()); return session.executeQuery(query); } /** * INTERNAL: * Remove the name key and value from the result. */ @Override public Object remove(Object name) { int index = getNames().indexOf(name); if (index >= 0) { getNames().remove(index); Object value = getResults().get(index); getResults().remove(index); return value; } return null; } protected void setNames(List names) { this.names = names; } /** * INTERNAL: * Set the Id for the result row's object. */ protected void setId(Object primaryKey) { this.primaryKey = primaryKey; } /** * INTERNAL: * Set the results. */ public void setResults(List results) { this.results = results; } /** * PUBLIC: * Return the number of name/value pairs in the result. */ @Override public int size() { return getNames().size(); } /** * INTERNAL: * Converts the ReportQueryResult to a simple array of values. */ public Object[] toArray(){ List list = getResults(); return (list == null) ? null : list.toArray(); } /** * INTERNAL: * Converts the ReportQueryResult to a simple list of values. */ public List toList(){ return this.getResults(); } @Override public String toString() { java.io.StringWriter writer = new java.io.StringWriter(); writer.write("ReportQueryResult("); for (int index = 0; index < getResults().size(); index++) { Object resultObj = getResults().get(index); writer.write(String.valueOf(resultObj)); writer.write(" <" + (resultObj == null ? "null" : resultObj.getClass().getName()) + ">"); if (index < (getResults().size() - 1)) { writer.write(", "); } } writer.write(")"); return writer.toString(); } /** * PUBLIC: * Returns an collection of the values. */ @Override public Collection values() { return getResults(); } }