blob: 3ab71eab611667b5750c6ae3a86a923406d41a68 [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
// 14/05/2012-2.4 Guy Pelletier
// - 376603: Provide for table per tenant support for multitenant applications
package org.eclipse.persistence.mappings.structures;
import java.sql.Array;
import java.sql.Ref;
import java.sql.Struct;
import java.sql.Types;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.RelationalDescriptor;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.expressions.SQLSelectStatement;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.ValueReadQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
/**
* <p><b>Purpose:</b>
* Differentiates object-relational descriptors from normal relational descriptors.
* The object-relational descriptor describes a type not a table, (although there
* is normally a table associated with the type, unless it is aggregate).
*/
@SuppressWarnings("unchecked")
public class ObjectRelationalDataTypeDescriptor extends RelationalDescriptor {
protected String structureName;
protected Vector orderedFields;
protected Vector allOrderedFields;
public ObjectRelationalDataTypeDescriptor() {
this.orderedFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
}
/**
* INTERNAL:
* Auto-Default orderedFields to fields
*/
@Override
public void initialize(AbstractSession session) throws DescriptorException {
super.initialize(session);
if (orderedFields==null || orderedFields.size()==0){
orderedFields=getAllFields();
}
setAllOrderedFields();
}
/**
* PUBLIC:
* Order the fields in a specific
* Add the field ordering, this will order the fields in the order this method is called.
* @param fieldName the name of the field to add ordering on.
*/
public void addFieldOrdering(String fieldName) {
getOrderedFields().addElement(new DatabaseField(fieldName));
}
/**
* INTERNAL:
* Extract the direct values from the specified field value.
* Return them in a vector.
* The field value better be an Array.
*/
@Override
public Vector buildDirectValuesFromFieldValue(Object fieldValue) throws DatabaseException {
if(fieldValue == null) {
return null;
}
return Helper.vectorFromArray((Object[])fieldValue);
}
/**
* INTERNAL:
* Build the appropriate field value for the specified
* set of direct values.
* The database better be expecting an ARRAY.
*/
@Override
public Object buildFieldValueFromDirectValues(Vector directValues, String elementDataTypeName, AbstractSession session) throws DatabaseException {
Object[] fields = Helper.arrayFromVector(directValues);
try {
session.getAccessor().incrementCallCount(session);
java.sql.Connection connection = session.getAccessor().getConnection();
return session.getPlatform().createArray(elementDataTypeName, fields, session,connection);
} catch (java.sql.SQLException ex) {
throw DatabaseException.sqlException(ex, session, false);
} finally {
session.getAccessor().decrementCallCount();
}
}
/**
* INTERNAL:
* Build and return the field value from the specified nested database row.
* The database better be expecting a Struct.
*/
@Override
public Object buildFieldValueFromNestedRow(AbstractRecord nestedRow, AbstractSession session) throws DatabaseException {
java.sql.Connection connection = session.getAccessor().getConnection();
return this.buildStructureFromRow(nestedRow, session, connection);
}
/**
* INTERNAL:
* Build and return the appropriate field value for the specified
* set of nested rows.
* The database better be expecting an ARRAY.
* It looks like we can ignore inheritance here....
*/
@Override
public Object buildFieldValueFromNestedRows(Vector nestedRows, String structureName, AbstractSession session) throws DatabaseException {
Object[] fields = new Object[nestedRows.size()];
java.sql.Connection connection = session.getAccessor().getConnection();
boolean reconnected = false;
try {
if (connection == null) {
session.getAccessor().incrementCallCount(session);
reconnected = true;
connection = session.getAccessor().getConnection();
}
int i = 0;
for (Enumeration stream = nestedRows.elements(); stream.hasMoreElements();) {
AbstractRecord nestedRow = (AbstractRecord)stream.nextElement();
fields[i++] = this.buildStructureFromRow(nestedRow, session, connection);
}
return session.getPlatform().createArray(structureName, fields, session,connection);
} catch (java.sql.SQLException exception) {
throw DatabaseException.sqlException(exception, session, false);
} finally {
if (reconnected) {
session.getAccessor().decrementCallCount();
}
}
}
/**
* INTERNAL:
* Build and return the nested rows from the specified field value.
* This method allows the field value to be an ARRAY containing other structures
* such as arrays or Struct, or direct values.
*/
static public Object buildContainerFromArray(Array fieldValue, ObjectRelationalDatabaseField arrayField, AbstractSession session) throws DatabaseException {
if (arrayField.getType()==null){
return fieldValue;
}
Object[] objects = null;
try {
objects = (Object[])fieldValue.getArray();
} catch (java.sql.SQLException ex) {
throw DatabaseException.sqlException(ex, session, false);
}
if (objects == null) {
return null;
}
boolean isNestedStructure = false;
ObjectRelationalDataTypeDescriptor ord=null;
DatabaseField nestedType = null;
nestedType = arrayField.getNestedTypeField();
if ((nestedType != null) && nestedType.getSqlType()==Types.STRUCT){
ClassDescriptor descriptor = session.getDescriptor(nestedType.getType());
if ((descriptor != null) && (descriptor.isObjectRelationalDataTypeDescriptor())) {
//this is used to convert non-null objects passed through stored procedures and custom SQL to structs
ord=(ObjectRelationalDataTypeDescriptor)descriptor;
}
} else if ((nestedType != null) && (nestedType instanceof ObjectRelationalDatabaseField) ){
isNestedStructure = true;
}
//handle ARRAY conversions
ReadObjectQuery query = new ReadObjectQuery();
query.setSession(session);
ContainerPolicy cp = ContainerPolicy.buildPolicyFor(arrayField.getType());
Object container = cp.containerInstance(objects.length);
for (int i = 0; i < objects.length; i++) {
Object arrayValue = objects[i];
if (arrayValue == null) {
return null;
}
if (ord!=null){
AbstractRecord nestedRow = ord.buildRowFromStructure( (Struct)arrayValue);
ClassDescriptor descriptor = ord;
if (descriptor.hasInheritance()) {
Class newElementClass = descriptor.getInheritancePolicy().classFromRow(nestedRow, session);
if (!descriptor.getJavaClass().equals(newElementClass)) {
descriptor = session.getDescriptor(newElementClass);
if (descriptor==null){
descriptor=ord;
}
}
}
arrayValue = descriptor.getObjectBuilder().buildNewInstance();
descriptor.getObjectBuilder().buildAttributesIntoObject(arrayValue, null, nestedRow, query, null, null, false, session);
} else if (isNestedStructure && (arrayValue instanceof Array)){
arrayValue = buildContainerFromArray((Array)arrayValue, (ObjectRelationalDatabaseField)nestedType, session);
}
cp.addInto(arrayValue, container, session);
}
return container;
}
/**
* INTERNAL:
* Build and return the nested database row from the specified field value.
* The field value better be an Struct.
*/
@Override
public AbstractRecord buildNestedRowFromFieldValue(Object fieldValue) throws DatabaseException {
AbstractRecord row = new DatabaseRecord();
Object[] attributes = (Object[])fieldValue;
for (int index = 0; index < getAllOrderedFields().size(); index++) {
DatabaseField field = (DatabaseField) getAllOrderedFields().get(index);
row.put(field, attributes[index]);
}
return row;
}
/**
* INTERNAL:
* Creates allOrderedFields Vector, keeping allFields contents ordered.
*/
private void setAllOrderedFields() {
allOrderedFields = new Vector(orderedFields.size());
Vector<DatabaseField> allFieldsCopy = new Vector(getAllFields());
for (DatabaseField orderedField : (Vector<DatabaseField>) getOrderedFields()) {
Iterator<DatabaseField> iterator = allFieldsCopy.iterator();
while (iterator.hasNext()) {
DatabaseField field = iterator.next();
if (orderedField.getName().equalsIgnoreCase(field.getName())) {
allOrderedFields.add(field);
iterator.remove();
}
}
}
}
/**
* INTERNAL:
* Build and return the nested rows from the specified field value.
* The field value better be an ARRAY.
*/
@Override
public Vector buildNestedRowsFromFieldValue(Object fieldValue, AbstractSession session) throws DatabaseException {
if(fieldValue==null){
return null;
}
Object[] structs = (Object[])fieldValue;
Vector nestedRows = new Vector(structs.length);
for (int i = 0; i < structs.length; i++) {
Object[] struct = (Object[])structs[i];
if (struct == null) {
return null;
}
nestedRows.addElement(this.buildNestedRowFromFieldValue(struct));
}
return nestedRows;
}
/**
* INTERNAL:
* Build a row representation from the ADT structure field array.
* TopLink will then build the object from the row.
*/
public AbstractRecord buildRowFromStructure(Struct structure) throws DatabaseException {
Object[] attributes;
try {
attributes = structure.getAttributes();
} catch (java.sql.SQLException exception) {
throw DatabaseException.sqlException(exception);
}
if(attributes!=null){
for(int i=0;i<attributes.length;i++){
if(attributes[i] instanceof Array ){
attributes[i]=ObjectRelationalDataTypeDescriptor.buildArrayObjectFromArray(attributes[i]);
}else if(attributes[i] instanceof Struct){
attributes[i]=ObjectRelationalDataTypeDescriptor.buildArrayObjectFromStruct(attributes[i]);
}
}
}
return buildNestedRowFromFieldValue(attributes);
}
/**
* INTERNAL:
* Build a ADT structure from the row data.
*/
public Struct buildStructureFromRow(AbstractRecord row, AbstractSession session, java.sql.Connection connection) throws DatabaseException {
Struct structure;
boolean reconnected = false;
try {
if (connection == null) {
session.getAccessor().incrementCallCount(session);
reconnected = true;
connection = session.getAccessor().getConnection();
}
Object[] fields = new Object[getOrderedFields().size()];
for (int index = 0; index < getOrderedFields().size(); index++) {
DatabaseField field = (DatabaseField)getOrderedFields().elementAt(index);
fields[index] = row.get(field);
}
structure = session.getPlatform().createStruct(getStructureName(), fields, row, getOrderedFields(), session, connection);
} catch (java.sql.SQLException exception) {
throw DatabaseException.sqlException(exception, session, false);
} finally {
if (reconnected) {
session.getAccessor().decrementCallCount();
}
}
return structure;
}
/**
* INTERNAL:
* Build array of objects for Array data type.
*/
public static Object buildArrayObjectFromArray(Object array) throws DatabaseException {
Object[] objects = null;
if(array==null){
return array;
}
try {
objects = (Object[])((Array)array).getArray();
} catch (java.sql.SQLException ex) {
throw DatabaseException.sqlException(ex);
}
if (objects == null ) {
return null;
} else {
for (int i=0;i<objects.length;i++){
if (objects[i] instanceof Array){
objects[i] = buildArrayObjectFromArray(objects[i]);
}
if (objects[i] instanceof Struct){
objects[i] = buildArrayObjectFromStruct(objects[i]);
}
}
}
return objects;
}
/**
* INTERNAL:
* Build array of objects for Struct data type.
*/
public static Object buildArrayObjectFromStruct(Object structure) throws DatabaseException{
Object[] attributes = null;
if(structure==null){
return structure;
}
try {
attributes = ((Struct)structure).getAttributes();
} catch (java.sql.SQLException exception) {
throw DatabaseException.sqlException(exception);
}
if (attributes==null){
return null;
} else {
for(int i=0;i<attributes.length;i++){
if (attributes[i] instanceof Array){
attributes[i] = buildArrayObjectFromArray(attributes[i]);
}
if (attributes[i] instanceof Struct){
attributes[i] = buildArrayObjectFromStruct(attributes[i]);
}
}
}
return attributes;
}
/**
* INTERNAL:
* Aggregates use a dummy table as default.
*/
@Override
protected DatabaseTable extractDefaultTable() {
if (isAggregateDescriptor()) {
return new DatabaseTable();
}
return super.extractDefaultTable();
}
/**
* INTERNAL:
* Return the field order.
*/
public Vector getOrderedFields() {
return orderedFields;
}
/**
* INTERNAL:
* Return allFields contents ordered.
*/
private Vector getAllOrderedFields() {
return allOrderedFields;
}
/**
* INTERNAL:
* Get the ref for the object.
* This is required for use by Refs, there might be a better way to do it when objID are supported.
* (i.e. getting it from the object or identity map).
*/
public Ref getRef(Object object, AbstractSession session) {
SQLSelectStatement statement = new SQLSelectStatement();
statement.addTable(getTables().firstElement());// Assumed only one for obj-rel descriptors.
statement.getFields().addElement(new org.eclipse.persistence.expressions.ExpressionBuilder().ref());
statement.setWhereClause(getObjectBuilder().buildPrimaryKeyExpressionFromObject(object, session));
statement.setRequiresAliases(true);
statement.normalize(session, this);
ValueReadQuery valueQuery = new ValueReadQuery();
valueQuery.setSQLStatement(statement);
valueQuery.checkPrepare(session, new DatabaseRecord(), true);
// Must return unwrapped Ref on WLS.
valueQuery.getCall().setIsNativeConnectionRequired(true);
Ref ref = (Ref)session.executeQuery(valueQuery);
return ref;
}
/**
* PUBLIC:
* Return the name of the structure.
* This is the name of the user defined data type as defined on the database.
*/
public String getStructureName() {
return structureName;
}
/**
* PUBLIC:
* Return if this is an ObjectRelationalDataTypeDescriptor.
*/
@Override
public boolean isObjectRelationalDataTypeDescriptor(){
return true;
}
/**
* INTERNAL:
* Aggregates obj-rel are initialized normally as no cloning is required.
*/
@Override
public boolean requiresInitialization(AbstractSession session) {
return true;
}
@Override
protected void validateMappingType(DatabaseMapping mapping) {
//do nothing
}
/**
* INTERNAL:
* Set the field order.
*/
public void setOrderedFields(Vector orderedFields) {
this.orderedFields = orderedFields;
}
/**
* PUBLIC:
* Set the name of the structure.
* This is the name of the user defined data type as defined on the database.
*/
public void setStructureName(String structureName) {
this.structureName = structureName;
}
}