blob: d08b9f0c57158a9f1cec125cdf2dc12a2d1c1613 [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:
// Oracle - initial API and implementation from Oracle TopLink
// 10/01/2018: Will Dazey
// - #253: Add support for embedded constructor results with CriteriaBuilder
package org.eclipse.persistence.queries;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.expressions.ConstantExpression;
import org.eclipse.persistence.internal.expressions.MapEntryExpression;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.StringHelper;
import org.eclipse.persistence.internal.queries.ReportItem;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
import org.eclipse.persistence.mappings.DatabaseMapping;
/**
* <b>Purpose</b>: An item specifying a class constructor method to be used in a ReportQuery's returned results.
* <p>Example:
* <blockquote><pre>
* ConstructorReportItem item = new ConstructorReportItem("Employee");
* item.setResultType(Employee.class);
* item.addAttribute("firstName", employees.get("firstName"));
* query.addConstructorReportItem(item);
* </pre></blockquote>
* <p>
* When executed will return a collection of ReportQueryResults that contain Employee objects created using
* the new Employee(firstname) constructor.
*
* @author Chris Delahunt
* @since TopLink Essentials 1.0
*/
public class ConstructorReportItem extends ReportItem {
/**
* String prefix of {@link #toString()} output.
*/
private static final String TO_STR_PREFIX = "ConstructorReportItem(";
/**
* String to separate name and items array of {@link #toString()} output.
*/
private static final String TO_STR_ARRAY = " -> [";
/**
* String suffix of {@link #toString()} output.
*/
private static final String TO_STR_SUFFIX = "])";
protected Class<?>[] constructorArgTypes;
protected List<DatabaseMapping> constructorMappings;
protected List<ReportItem> reportItems;
protected Constructor constructor;
/**
* Create a new constructor item.
*/
public ConstructorReportItem() {
}
/**
* Create a new constructor item.
* @param name string used to look up this result in the ReportQueryResult.
*/
public ConstructorReportItem(String name) {
super(name, null);
}
/**
* Method to add an expression to be used to return the parameter that is then passed into the constructor method.
* Similar to ReportQuery's addAttribute method, but name is not needed.
*/
public void addAttribute(Expression attributeExpression) {
ReportItem item = new ReportItem(getName()+getReportItems().size(), attributeExpression);
getReportItems().add(item);
}
/**
* Add the attribute with joining.
*/
public void addAttribute(String attributeName, Expression attributeExpression, List joinedExpressions) {
ReportItem item = new ReportItem(attributeName, attributeExpression);
item.getJoinedAttributeManager().setJoinedAttributeExpressions_(joinedExpressions);
getReportItems().add(item);
}
public void addItem(ReportItem item) {
getReportItems().add(item);
}
public Class<?>[] getConstructorArgTypes(){
return constructorArgTypes;
}
/**
* INTERNAL:
* Return the mappings for the items.
*/
public List<DatabaseMapping> getConstructorMappings(){
return constructorMappings;
}
/**
* INTERNAL:
* Return the constructor.
*/
public Constructor getConstructor(){
return constructor;
}
/**
* INTERNAL:
* Set the constructor.
*/
public void setConstructor(Constructor constructor){
this.constructor = constructor;
}
public List<ReportItem> getReportItems(){
if (reportItems == null) {
reportItems = new ArrayList<>();
}
return reportItems;
}
/**
* INTERNAL:
* Looks up mapping for attribute during preExecute of ReportQuery
*/
@Override
public void initialize(ReportQuery query) throws QueryException {
int size= getReportItems().size();
List<DatabaseMapping> mappings = new ArrayList<>();
for (int index = 0; index < size; index++) {
ReportItem item = reportItems.get(index);
item.initialize(query);
mappings.add(item.getMapping());
}
setConstructorMappings(mappings);
int numberOfItems = getReportItems().size();
// Arguments may be initialized depending on how the query was constructed, so types may be undefined though.
if (getConstructorArgTypes() == null) {
setConstructorArgTypes(new Class<?>[numberOfItems]);
}
Class<?>[] constructorArgTypes = getConstructorArgTypes();
for (int index = 0; index < numberOfItems; index++) {
if (constructorArgTypes[index] == null) {
ReportItem argumentItem = getReportItems().get(index);
if (mappings.get(index) != null) {
DatabaseMapping mapping = constructorMappings.get(index);
if (argumentItem.getAttributeExpression() != null && argumentItem.getAttributeExpression().isMapEntryExpression()){
if (((MapEntryExpression)argumentItem.getAttributeExpression()).shouldReturnMapEntry()){
constructorArgTypes[index] = Map.Entry.class;
} else {
constructorArgTypes[index] = (Class) mapping.getContainerPolicy().getKeyType();
}
} else {
constructorArgTypes[index] = mapping.getAttributeClassification();
}
} else if (argumentItem.getResultType() != null) {
constructorArgTypes[index] = argumentItem.getResultType();
} else if (argumentItem.getDescriptor() != null) {
constructorArgTypes[index] = argumentItem.getDescriptor().getJavaClass();
} else if (argumentItem.getAttributeExpression() != null && argumentItem.getAttributeExpression().isConstantExpression()){
constructorArgTypes[index] = ((ConstantExpression)argumentItem.getAttributeExpression()).getValue().getClass();
} else {
// Use Object.class by default.
constructorArgTypes[index] = ClassConstants.OBJECT;
}
}
}
if (getConstructor() == null) {
try {
Constructor<?> constructor = null;
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
constructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor<>(getResultType(), constructorArgTypes, true));
} catch (PrivilegedActionException exception) {
throw QueryException.exceptionWhileUsingConstructorExpression(exception.getException(), query);
}
} else {
constructor = PrivilegedAccessHelper.getConstructorFor(getResultType(), constructorArgTypes, true);
}
setConstructor(constructor);
} catch (NoSuchMethodException exception) {
throw QueryException.exceptionWhileUsingConstructorExpression(exception, query);
}
}
}
@Override
public boolean isConstructorItem(){
return true;
}
public void setConstructorArgTypes(Class<?>[] constructorArgTypes){
this.constructorArgTypes = constructorArgTypes;
}
/**
* INTERNAL:
* Return the mappings for the items.
*/
public void setConstructorMappings(List<DatabaseMapping> constructorMappings){
this.constructorMappings = constructorMappings;
}
public void setReportItems(List<ReportItem> reportItems){
this.reportItems = reportItems;
}
@Override
public String toString() {
String name = StringHelper.nonNullString(getName());
// Calculate string length
int length = TO_STR_PREFIX.length() + name.length()
+ TO_STR_ARRAY.length() + TO_STR_SUFFIX.length();
int size = reportItems != null ? reportItems.size() : 0;
String[] items = new String[size];
for (int i=0; i < size; i++) {
items[i] = StringHelper.nonNullString(reportItems.get(i).toString());
length += items[i].length();
}
// Build string
StringBuilder str = new StringBuilder(length);
str.append(TO_STR_PREFIX).append(name).append(TO_STR_ARRAY);
for (int i=0; i < size; i++) {
str.append(items[i]);
}
str.append(TO_STR_SUFFIX);
return str.toString();
}
}