/******************************************************************************* | |
* Copyright (c) 1998, 2013 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: | |
* Oracle - initial API and implementation from Oracle TopLink | |
* 11/10/2011-2.4 Guy Pelletier | |
* - 357474: Address primaryKey option from tenant discriminator column | |
******************************************************************************/ | |
package org.eclipse.persistence.queries; | |
import java.io.*; | |
import java.util.*; | |
import java.lang.reflect.Constructor; | |
import java.security.AccessController; | |
import java.security.PrivilegedActionException; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.internal.expressions.MapEntryExpression; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.internal.identitymaps.CacheId; | |
import org.eclipse.persistence.internal.queries.*; | |
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.*; | |
import org.eclipse.persistence.mappings.converters.Converter; | |
import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping; | |
import org.eclipse.persistence.sessions.DatabaseRecord; | |
import org.eclipse.persistence.sessions.Session; | |
/** | |
* <b>Purpose</b>: A single row (type) result for a ReportQuery<p> | |
* | |
* <b>Description</b>: Represents a single row of attribute values (converted using mapping) for | |
* a ReportQuery. The attributes can be from various objects. | |
* | |
* <b>Responsibilities</b>:<ul> | |
* <li> Converted field values into object attribute values. | |
* <li> Provide access to values by index or item name | |
* </ul> | |
* | |
* @author Doug Clarke | |
* @since TOPLink/Java 2.0 | |
*/ | |
public class ReportQueryResult implements Serializable, Map { | |
/** Item names to lookup result values */ | |
protected List<String> names; | |
/** Actual converted attribute values */ | |
protected List<Object> 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<Object> 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(); | |
} | |
List items = query.getItems(); | |
int itemSize = items.size(); | |
List results = new ArrayList(itemSize); | |
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. | |
} | |
for (int index = 0; index < itemSize; index++) { | |
ReportItem item = (ReportItem)items.get(index); | |
if (item.isConstructorItem()) { | |
// For constructor items need to process each constructor argument. | |
ConstructorReportItem constructorItem = (ConstructorReportItem)item; | |
Class[] constructorArgTypes = constructorItem.getConstructorArgTypes(); | |
int numberOfArguments = constructorItem.getReportItems().size(); | |
Object[] constructorArgs = new Object[numberOfArguments]; | |
for (int argumentIndex = 0; argumentIndex < numberOfArguments; argumentIndex++) { | |
ReportItem argumentItem = (ReportItem)constructorItem.getReportItems().get(argumentIndex); | |
Object 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); | |
} | |
results.add(returnValue); | |
} catch (IllegalAccessException exc){ | |
throw QueryException.exceptionWhileUsingConstructorExpression(exc, query); | |
} catch (java.lang.reflect.InvocationTargetException exc){ | |
throw QueryException.exceptionWhileUsingConstructorExpression(exc, query); | |
} catch (InstantiationException exc){ | |
throw QueryException.exceptionWhileUsingConstructorExpression(exc, query); | |
} | |
} 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); | |
} | |
/** | |
* 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; | |
DatabaseMapping mapping = item.getMapping(); | |
int rowSize = row.size(); | |
int itemIndex = item.getResultIndex(); | |
ClassDescriptor descriptor = item.getDescriptor(); | |
if (!item.isPlaceHolder()) { | |
if (descriptor == null && mapping != null){ | |
descriptor = mapping.getReferenceDescriptor(); | |
} | |
if (mapping != null && (mapping.isAbstractColumnMapping() || mapping.isDirectCollectionMapping())) { | |
if (itemIndex >= rowSize) { | |
throw QueryException.reportQueryResultSizeMismatch(itemIndex + 1, rowSize); | |
} | |
// 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()); | |
} | |
if (mapping.isAbstractColumnMapping()){ | |
value = ((AbstractColumnMapping)mapping).getObjectValue(value, query.getSession()); | |
} else { | |
Converter converter = ((DirectCollectionMapping)mapping).getValueConverter(); | |
if (converter != null){ | |
value = converter.convertDataValueToObjectValue(value, query.getSession()); | |
} | |
} | |
// 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 = ((MapContainerPolicy)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. | |
*/ | |
public void clear() { | |
this.names = new ArrayList<String>(); | |
this.results = new ArrayList<Object>(); | |
} | |
/** | |
* 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. | |
*/ | |
public boolean containsKey(Object key) { | |
return getNames().contains(key); | |
} | |
/** | |
* PUBLIC: | |
* Check if the value is contained in the result. | |
*/ | |
public boolean containsValue(Object value) { | |
return getResults().contains(value); | |
} | |
/** | |
* OBSOLETE: | |
* Return an enumeration of the result values. | |
* @see #values() | |
*/ | |
public Enumeration elements() { | |
return new Vector(getResults()).elements(); | |
} | |
/** | |
* PUBLIC: | |
* Returns a set of the keys. | |
*/ | |
public Set entrySet() { | |
return new EntrySet(); | |
} | |
/** | |
* Defines the virtual entrySet. | |
*/ | |
protected class EntrySet extends AbstractSet { | |
public Iterator iterator() { | |
return new EntryIterator(); | |
} | |
public int size() { | |
return ReportQueryResult.this.size(); | |
} | |
public boolean contains(Object object) { | |
if (!(object instanceof Entry)) { | |
return false; | |
} | |
return ReportQueryResult.this.containsKey(((Entry)object).getKey()); | |
} | |
public boolean remove(Object object) { | |
if (!(object instanceof Entry)) { | |
return false; | |
} | |
ReportQueryResult.this.remove(((Entry)object).getKey()); | |
return true; | |
} | |
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; | |
} | |
public Object getKey() { | |
return key; | |
} | |
public Object getValue() { | |
return value; | |
} | |
public Object setValue(Object value) { | |
Object oldValue = this.value; | |
this.value = value; | |
return oldValue; | |
} | |
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()); | |
} | |
public int hashCode() { | |
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode()); | |
} | |
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; | |
} | |
public boolean hasNext() { | |
return this.index < ReportQueryResult.this.size(); | |
} | |
public Object next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
this.index++; | |
return new RecordEntry(getNames().get(this.index - 1), getResults().get(this.index - 1)); | |
} | |
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 { | |
public Object next() { | |
if (!hasNext()) { | |
throw new NoSuchElementException(); | |
} | |
this.index++; | |
return getNames().get(this.index - 1); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Compare if the two results are equal. | |
*/ | |
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; | |
} | |
/** | |
* PUBLIC: | |
* Return the value for given item name. | |
*/ | |
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<String> getNames() { | |
return names; | |
} | |
/** | |
* PUBLIC: | |
* Return the Id for the result or null if not requested. | |
*/ | |
public Object getId() { | |
return primaryKey; | |
} | |
/** | |
* PUBLIC: | |
* Return the PKs for the corresponding object or null if not requested. | |
* @Deprecated since 2.1, replaced by getId() | |
* @see #getId() | |
*/ | |
@Deprecated | |
public Vector<Object> getPrimaryKeyValues() { | |
if (this.primaryKey instanceof CacheId) { | |
return new Vector(Arrays.asList(((CacheId)this.primaryKey).getPrimaryKey())); | |
} | |
Vector primaryKey = new Vector(1); | |
primaryKey.add(this.primaryKey); | |
return primaryKey; | |
} | |
/** | |
* PUBLIC: | |
* Return the results. | |
*/ | |
public List<Object> getResults() { | |
return results; | |
} | |
/** | |
* PUBLIC: | |
* Return if the result is empty. | |
*/ | |
public boolean isEmpty() { | |
return getNames().isEmpty(); | |
} | |
/** | |
* OBSOLETE: | |
* Return an enumeration of the result names. | |
* @see #keySet() | |
*/ | |
public Enumeration keys() { | |
return new Vector(getNames()).elements(); | |
} | |
/** | |
* PUBLIC: | |
* Returns a set of the keys. | |
*/ | |
public Set keySet() { | |
return new KeySet(); | |
} | |
/** | |
* Defines the virtual keySet. | |
*/ | |
protected class KeySet extends EntrySet { | |
public Iterator iterator() { | |
return new KeyIterator(); | |
} | |
public boolean contains(Object object) { | |
return ReportQueryResult.this.containsKey(object); | |
} | |
public boolean remove(Object object) { | |
return ReportQueryResult.this.remove(object) != null; | |
} | |
} | |
/** | |
* ADVANCED: | |
* Set the value for given item name. | |
*/ | |
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. | |
*/ | |
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. | |
*/ | |
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<String> 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<Object> results) { | |
this.results = results; | |
} | |
/** | |
* PUBLIC: | |
* Return the number of name/value pairs in the result. | |
*/ | |
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(); | |
} | |
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. | |
*/ | |
public Collection values() { | |
return getResults(); | |
} | |
} |