blob: 97de2d59563c2a56daecd93380b9b5a05ad56821 [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
package org.eclipse.persistence.descriptors;
import java.io.Serializable;
import java.util.*;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.*;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.databaseaccess.DatabaseCall;
import org.eclipse.persistence.queries.*;
/**
* <p><b>Purpose</b>:
* Allows for INSERT or UPDATE operations to return values back into the object being written.
* This allows for table default values, trigger or stored procedures computed values to be set back
* into the object.
* This can be used with generated SQL on the Oracle platform using the RETURNING clause,
* or through stored procedures on other platforms.
*
* @since TopLink 10.1.3
*/
public class ReturningPolicy implements Serializable, Cloneable {
protected static final int INSERT = 0;
protected static final int UPDATE = 1;
protected static final int NUM_OPERATIONS = 2;
protected static final int RETURN_ONLY = 0;
protected static final int WRITE_RETURN = 1;
protected static final int MAPPED = 2;
protected static final int UNMAPPED = 3;
protected static final int ALL = 4;
protected static final int MAIN_SIZE = 5;
/** owner of the policy */
protected ClassDescriptor descriptor;
/**
* Stores an object of type Info for every call to any of addField.. methods.
* Should be filled out before initialize() is called:
* fields added after initialization are ignored.
*/
protected List<Info> infos = new ArrayList<>();
/**
* The following attributes are initialized by initialize() method.
* Contains the actual DatabaseFields to be returned.
* Populated during descriptor initialization using infos.
* Here's the order:
* <pre>
* main[INSERT][RETURN_ONLY] main[INSERT][WRITE_RETURN] main[INSERT][MAPPED] main[INSERT][UNMAPPED] main[INSERT][ALL]
* main[UPDATE][RETURN_ONLY] main[UPDATE][WRITE_RETURN] main[UPDATE][MAPPED] main[UPDATE][UNMAPPED] main[UPDATE][ALL]
* </pre>
* After initialization main[UPDATE,WRITE_RETURN] will contain all DatabaseFields that should be
* returned on Update as read-write.
* <pre>
* main[i][RETURN_ONLY] + main[i][WRITE_RETURN] = main[i][MAPPED]
* main[i][MAPPED] + main[i][UNMAPPED] = main[i][ALL]
* </pre>
*/
protected Collection<DatabaseField>[][] main;
/**
* maps ClassDescriptor's tables into Vectors of fields to be used for call generation.
* Lazily initialized array [NUM_OPERATIONS]
*/
protected Map<DatabaseTable, List<DatabaseField>>[] tableToFieldsForGenerationMap;
/** indicates whether ReturningPolicy is used for generation of the PK. */
protected boolean isUsedToSetPrimaryKey;
/** contains all default table the returning fields that are either unmapped or mapped supplied with types. */
protected Map<DatabaseField, DatabaseField> fieldsNotFromDescriptor_DefaultTable;
/** contains all the other tables returning fields that are either unmapped or mapped supplied with types. */
protected Map<DatabaseField, DatabaseField> fieldsNotFromDescriptor_OtherTables;
public ReturningPolicy() {
super();
}
/**
* PUBLIC:
* Return the owner of the policy.
*/
public ClassDescriptor getDescriptor() {
return descriptor;
}
/**
* INTERNAL:
*/
protected void fieldIsNotFromDescriptor(DatabaseField field) {
if (field.getTable().equals(getDescriptor().getDefaultTable())) {
if (this.fieldsNotFromDescriptor_DefaultTable == null) {
this.fieldsNotFromDescriptor_DefaultTable = new HashMap<>();
}
this.fieldsNotFromDescriptor_DefaultTable.put(field, field);
} else {
if (this.fieldsNotFromDescriptor_OtherTables == null) {
this.fieldsNotFromDescriptor_OtherTables = new HashMap<>();
}
this.fieldsNotFromDescriptor_OtherTables.put(field, field);
}
}
/**
* INTERNAL:
*/
public List<DatabaseField> getFieldsToGenerateInsert(DatabaseTable table) {
return getVectorOfFieldsToGenerate(INSERT, table);
}
/**
* INTERNAL:
*/
public List<DatabaseField> getFieldsToGenerateUpdate(DatabaseTable table) {
return getVectorOfFieldsToGenerate(UPDATE, table);
}
/**
* INTERNAL:
*/
public List<Info> getFieldInfos() {
return infos;
}
/**
* INTERNAL:
*/
public void setFieldInfos(List<Info> infos) {
this.infos = infos;
}
/**
* INTERNAL:
* Used for testing only
*/
public boolean hasEqualFieldInfos(ReturningPolicy returningPolicyToCompare) {
return hasEqualFieldInfos(returningPolicyToCompare.getFieldInfos());
}
/**
* INTERNAL:
* Used for testing only
*/
public boolean hasEqualFieldInfos(List<Info> infosToCompare) {
return areCollectionsEqualAsSets(getFieldInfos(), infosToCompare);
}
/**
* INTERNAL:
* Compares two Collections as sets (ignoring the order of the elements).
* Note that the passed Collections are cloned.
* Used for testing only.
*/
public static boolean areCollectionsEqualAsSets(Collection<? extends Info> col1, Collection<? extends Info> col2) {
if (col1 == col2) {
return true;
}
if (col1.size() != col2.size()) {
return false;
}
Collection<Info> c1 = new ArrayList<>(col1);
Collection<Info> c2 = new ArrayList<>(col2);
for (Iterator<Info> i = c1.iterator(); i.hasNext();) {
Info o = i.next();
c2.remove(o);
}
return c2.isEmpty();
}
/**
* INTERNAL:
*/
@SuppressWarnings({"unchecked"})
protected List<DatabaseField> getVectorOfFieldsToGenerate(int operation, DatabaseTable table) {
if (this.main[operation][ALL] == null) {
return null;
}
if (this.tableToFieldsForGenerationMap == null) {
// the method is called for the first time
tableToFieldsForGenerationMap = (Map<DatabaseTable, List<DatabaseField>>[]) new HashMap[NUM_OPERATIONS];
}
if (this.tableToFieldsForGenerationMap[operation] == null) {
// the method is called for the first time for this operation
this.tableToFieldsForGenerationMap[operation] = new HashMap<>();
}
List<DatabaseField> fieldsForGeneration = this.tableToFieldsForGenerationMap[operation].get(table);
if (fieldsForGeneration == null) {
// the method is called for the first time for this operation and this table
fieldsForGeneration = new ArrayList<>();
Iterator<DatabaseField> it = this.main[operation][ALL].iterator();
while (it.hasNext()) {
DatabaseField field = it.next();
if (field.getTable().equals(table)) {
fieldsForGeneration.add(field);
}
}
this.tableToFieldsForGenerationMap[operation].put(table, fieldsForGeneration);
}
return fieldsForGeneration;
}
/**
* INTERNAL:
*/
public Collection<DatabaseField> getFieldsToMergeInsert() {
return main[INSERT][MAPPED];
}
/**
* INTERNAL:
*/
public Collection<DatabaseField> getFieldsToMergeUpdate() {
return main[UPDATE][MAPPED];
}
/**
* INTERNAL:
* Normally cloned when not yet initialized.
* If initialized ReturningPolicy cloned then the clone should be re-initialized.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (Exception exception) {
throw new InternalError("clone failed");
}
}
/**
* INTERNAL:
*/
public void setDescriptor(ClassDescriptor descriptor) {
this.descriptor = descriptor;
}
/**
* PUBLIC:
* Define that the field will be returned from an insert operation.
*/
public void addFieldForInsert(String qualifiedName) {
addFieldForInsert(qualifiedName, null);
}
/**
* PUBLIC:
* Define that the field will be returned from an insert operation.
* The type may be required to bind the output parameter if not known by the mapping.
*/
public void addFieldForInsert(String qualifiedName, Class<?> type) {
addFieldForInsert(createField(qualifiedName, type));
}
/**
* PUBLIC:
* Define that the field will be returned from an insert operation.
*/
public void addFieldForInsert(DatabaseField field) {
addField(field, true, false, false);
}
/**
* PUBLIC:
* Define that the field will be returned from an insert operation.
* A field added with addFieldForInsertReturnOnly method
* is excluded from INSERT clause during SQL generation.
*/
public void addFieldForInsertReturnOnly(String qualifiedName) {
addFieldForInsertReturnOnly(qualifiedName, null);
}
/**
* PUBLIC:
* Define that the field will be returned from an insert operation.
* A field added with addFieldForInsertReturnOnly method
* is excluded from INSERT clause during SQL generation.
* The type may be required to bind the output parameter if not known by the mapping.
*/
public void addFieldForInsertReturnOnly(String qualifiedName, Class type) {
addFieldForInsertReturnOnly(createField(qualifiedName, type));
}
/**
* PUBLIC:
* Define that the field will be returned from an insert operation.
* A field added with addFieldForInsertReturnOnly method
* is excluded from INSERT clause during SQL generation.
*/
public void addFieldForInsertReturnOnly(DatabaseField field) {
addField(field, true, true, false);
}
/**
* PUBLIC:
* Define that the field will be returned from an update operation.
*/
public void addFieldForUpdate(String qualifiedName) {
addFieldForUpdate(qualifiedName, null);
}
/**
* PUBLIC:
* Define that the field will be returned from an update operation.
* The type may be required to bind the output parameter if not known by the mapping.
*/
public void addFieldForUpdate(String qualifiedName, Class<?> type) {
addFieldForUpdate(createField(qualifiedName, type));
}
/**
* PUBLIC:
* Define that the field will be returned from an update operation.
*/
public void addFieldForUpdate(DatabaseField field) {
addField(field, false, false, true);
}
/**
* INTERNAL:
*/
protected void addField(DatabaseField field, boolean isInsert, boolean isInsertModeReturnOnly, boolean isUpdate) {
getFieldInfos().add(new Info(field, isInsert, isInsertModeReturnOnly, isUpdate));
}
/**
* INTERNAL:
*/
public static class Info implements Cloneable {
private DatabaseField field;
private boolean isInsert;
private boolean isInsertModeReturnOnly;
private boolean isUpdate;
private Class<?> referenceClass;
private String referenceClassName;
Info() {
super();
}
Info(DatabaseField field, boolean isInsert, boolean isInsertModeReturnOnly, boolean isUpdate) {
this.field = field;
if (field != null) {
if (field.getType() != null) {
setReferenceClass(field.getType());
}
}
this.isInsert = isInsert;
this.isInsertModeReturnOnly = isInsertModeReturnOnly;
this.isUpdate = isUpdate;
}
public DatabaseField getField() {
return field;
}
public void setField(DatabaseField field) {
this.field = field;
if ((field.getType() == null) && (referenceClass != null)) {
field.setType(referenceClass);
}
}
public boolean isInsert() {
return isInsert;
}
public void setIsInsert(boolean isInsert) {
this.isInsert = isInsert;
}
public boolean isInsertModeReturnOnly() {
return isInsertModeReturnOnly;
}
public void setIsInsertModeReturnOnly(boolean isInsertModeReturnOnly) {
this.isInsertModeReturnOnly = isInsertModeReturnOnly;
}
public boolean isUpdate() {
return isUpdate;
}
public void setIsUpdate(boolean isUpdate) {
this.isUpdate = isUpdate;
}
public Class<?> getReferenceClass() {
return referenceClass;
}
public void setReferenceClass(Class<?> referenceClass) {
this.referenceClass = referenceClass;
if (referenceClass != null) {
this.referenceClassName = referenceClass.getName();
}
}
public String getReferenceClassName() {
return referenceClassName;
}
public void setReferenceClassName(String referenceClassName) {
this.referenceClassName = referenceClassName;
}
// operation is INSERT or UPDATE (0 or 1)
boolean is(int operation, int stateToCheck) {
if (operation == INSERT) {
if (isInsert) {
if (stateToCheck == RETURN_ONLY) {
return isInsertModeReturnOnly;
} else {
return !isInsertModeReturnOnly;
}
}
} else {
if (isUpdate) {
return stateToCheck == WRITE_RETURN;
}
}
return false;
}
// operation is INSERT or UPDATE (0 or 1)
boolean is(int operation) {
if (operation == INSERT) {
return isInsert;
} else {
return isUpdate;
}
}
/**
* INTERNAL:
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (Exception exception) {
throw new InternalError("clone failed");
}
}
@Override
public boolean equals(Object objectToCompare) {
if (objectToCompare instanceof Info) {
return equals((Info)objectToCompare);
} else {
return false;
}
}
boolean equals(Info infoToCompare) {
if (this == infoToCompare) {
return true;
}
if (!getField().equals(infoToCompare.getField())) {
return false;
}
if ((getField().getType() == null) && (infoToCompare.getField().getType() != null)) {
return false;
}
if ((getField().getType() != null) && !getField().getType().equals(infoToCompare.getField().getType())) {
return false;
}
if (isInsert() != infoToCompare.isInsert()) {
return false;
}
if (isInsertModeReturnOnly() != infoToCompare.isInsertModeReturnOnly()) {
return false;
}
if (isUpdate() != infoToCompare.isUpdate()) {
return false;
}
return true;
}
@Override
public int hashCode() {
DatabaseField field = getField();
Class<?> type = field != null ? field.getType() : null;
boolean isInsert = isInsert();
boolean isInsertModeReturnOnly = isInsertModeReturnOnly();
boolean isUpdate = isUpdate();
int result = field != null ? field.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (isInsert ? 1 : 0);
result = 31 * result + (isInsertModeReturnOnly ? 1 : 0);
result = 31 * result + (isUpdate ? 1 : 0);
return result;
}
}
/**
* INTERNAL:
*/
// precondition: info1.field.equals(info2.field);
static Info mergeInfos(Info info1, Info info2, AbstractSession session, ClassDescriptor descriptor) {
boolean ok = true;
DatabaseField fieldMerged = info1.getField();
if (info2.getField().getType() != null) {
if (info1.getField().getType() == null) {
fieldMerged = info2.field;
} else if (!info1.getField().getType().equals(info2.getField().getType())) {
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyFieldTypeConflict(info1.getField().getName(), info1.getField().getType().getName(), info2.getField().getType().getName(), descriptor));
ok = false;
}
}
boolean isInsertMerged = false;
boolean isInsertModeReturnOnlyMerged = false;
if (info1.isInsert() && !info2.isInsert()) {
isInsertMerged = true;
isInsertModeReturnOnlyMerged = info1.isInsertModeReturnOnly();
} else if (!info1.isInsert() && info2.isInsert()) {
isInsertMerged = true;
isInsertModeReturnOnlyMerged = info2.isInsertModeReturnOnly();
} else if (info1.isInsert() && info2.isInsert()) {
isInsertMerged = true;
isInsertModeReturnOnlyMerged = info1.isInsertModeReturnOnly();
if (info1.isInsertModeReturnOnly() != info2.isInsertModeReturnOnly()) {
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyFieldInsertConflict(info1.getField().getName(), descriptor));
ok = false;
}
}
if (ok) {
// merging
boolean isUpdateMerged = info1.isUpdate() || info2.isUpdate();
return new Info(fieldMerged, isInsertMerged, isInsertModeReturnOnlyMerged, isUpdateMerged);
} else {
// there is a problem - can't merge
return null;
}
}
/**
* INTERNAL:
*/
// used only on equal fields: field1.equals(field2)
static protected boolean isThereATypeConflict(DatabaseField field1, DatabaseField field2) {
return (field1.getType() != null) && (field2.getType() != null) && !field1.getType().equals(field2.getType());
}
/**
* INTERNAL:
*/
protected DatabaseField createField(String qualifiedName, Class type) {
DatabaseField field = new DatabaseField(qualifiedName);
field.setType(type);
return field;
}
/**
* INTERNAL:
*/
protected Collection<DatabaseField> createCollection() {
return new HashSet<>();
}
// precondition field != null
protected void addFieldToMain(int operation, int state, DatabaseField field) {
if (main[operation][state] == null) {
main[operation][state] = createCollection();
}
main[operation][state].add(field);
}
protected void addCollectionToMain(int operation, int state, Collection<? extends DatabaseField> collection) {
if ((collection == null) || collection.isEmpty()) {
return;
}
if (main[operation][state] == null) {
main[operation][state] = createCollection();
}
main[operation][state].addAll(collection);
}
protected void addMappedFieldToMain(DatabaseField field, Info info) {
for (int operation = INSERT; operation <= UPDATE; operation++) {
for (int state = RETURN_ONLY; state <= WRITE_RETURN; state++) {
if (info.is(operation, state)) {
addFieldToMain(operation, state, field);
addFieldToMain(operation, MAPPED, field);
addFieldToMain(operation, ALL, field);
}
}
}
}
protected void addUnmappedFieldToMain(DatabaseField field, Info info) {
for (int operation = INSERT; operation <= UPDATE; operation++) {
if (info.is(operation)) {
addFieldToMain(operation, UNMAPPED, field);
addFieldToMain(operation, ALL, field);
}
}
}
protected Hashtable<DatabaseField, Info> removeDuplicateAndValidateInfos(AbstractSession session) {
Hashtable<DatabaseField, Info> infoHashtable = new Hashtable<>();
for (int i = 0; i < infos.size(); i++) {
Info info1 = infos.get(i);
info1 = (Info)info1.clone();
DatabaseField descField = getDescriptor().buildField(info1.getField());
if(info1.getField().getType() == null) {
info1.setField(descField);
} else {
// keep the original type if specified
info1.getField().setName(descField.getName());
info1.getField().setTableName(getDescriptor().getDefaultTable().getQualifiedNameDelimited(session.getPlatform()));
}
Info info2 = infoHashtable.get(info1.getField());
if (info2 == null) {
infoHashtable.put(info1.getField(), info1);
} else {
Info infoMerged = mergeInfos(info1, info2, session, getDescriptor());
if (infoMerged != null) {
// substitute info2 with infoMerged
infoHashtable.put(infoMerged.getField(), infoMerged);
} else {
// couldn't merge info1 and info2 due to a conflict.
// substitute info2 with info1
infoHashtable.put(info1.getField(), info1);
}
}
}
return infoHashtable;
}
/**
* INTERNAL:
*/
@SuppressWarnings({"unchecked"})
public void initialize(AbstractSession session) {
clearInitialization();
main = (Collection<DatabaseField>[][]) new Collection[NUM_OPERATIONS][MAIN_SIZE];
// The order of descriptor initialization guarantees initialization of Parent before children.
// main array is copied from Parent's ReturningPolicy
if (getDescriptor().isChildDescriptor()) {
ClassDescriptor parentDescriptor = getDescriptor().getInheritancePolicy().getParentDescriptor();
if (parentDescriptor.hasReturningPolicy()) {
copyMainFrom(parentDescriptor.getReturningPolicy());
}
}
if (!infos.isEmpty()) {
Hashtable<DatabaseField, Info> infoHashtable = removeDuplicateAndValidateInfos(session);
Hashtable<DatabaseField, Info> infoHashtableUnmapped = (Hashtable<DatabaseField, Info>)infoHashtable.clone();
for (Enumeration<DatabaseField> fields = getDescriptor().getFields().elements();
fields.hasMoreElements();) {
DatabaseField field = fields.nextElement();
Info info = infoHashtableUnmapped.get(field);
if (info != null) {
infoHashtableUnmapped.remove(field);
if (verifyFieldAndMapping(session, field)) {
if (info.getField().getType() == null) {
addMappedFieldToMain(field, info);
} else {
addMappedFieldToMain(info.getField(), info);
fieldIsNotFromDescriptor(info.getField());
}
}
}
}
if (!infoHashtableUnmapped.isEmpty()) {
Enumeration<DatabaseField> fields = infoHashtableUnmapped.keys();
while (fields.hasMoreElements()) {
DatabaseField field = fields.nextElement();
Info info = infoHashtableUnmapped.get(field);
if (verifyField(session, field, getDescriptor())) {
if (field.getType() != null) {
addUnmappedFieldToMain(field, info);
fieldIsNotFromDescriptor(field);
session.log(SessionLog.FINEST, SessionLog.QUERY, "added_unmapped_field_to_returning_policy", info.toString(), getDescriptor().getJavaClassName());
} else {
if (getDescriptor().isReturnTypeRequiredForReturningPolicy()) {
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyUnmappedFieldTypeNotSet(field.getName(), getDescriptor()));
}
}
}
}
}
}
initializeIsUsedToSetPrimaryKey();
}
protected void copyMainFrom(ReturningPolicy policy) {
Collection<? extends DatabaseField>[][] mainToCopy = policy.main;
for (int operation = INSERT; operation <= UPDATE; operation++) {
for (int state = RETURN_ONLY; state < MAIN_SIZE; state++) {
addCollectionToMain(operation, state, mainToCopy[operation][state]);
}
}
}
/**
* INTERNAL:
* Both ReturningPolicies should be initialized
*/
public boolean hasEqualMains(ReturningPolicy policy) {
Collection<DatabaseField>[][] mainToCompare = policy.main;
if (main == mainToCompare) {
return true;
}
for (int operation = INSERT; operation <= UPDATE; operation++) {
for (int state = RETURN_ONLY; state < MAIN_SIZE; state++) {
if ((main[operation][state] == null) && (mainToCompare[operation][state] != null)) {
return false;
}
if ((main[operation][state] != null) && (mainToCompare[operation][state] == null)) {
return false;
}
if (!main[operation][state].equals(mainToCompare[operation][state])) {
return false;
}
}
}
// now compare types
Hashtable<DatabaseField, DatabaseField> allFields = new Hashtable<>();
for (int operation = INSERT; operation <= UPDATE; operation++) {
if (main[operation][ALL] != null) {
Iterator<DatabaseField> it = main[operation][ALL].iterator();
while (it.hasNext()) {
DatabaseField field = it.next();
allFields.put(field, field);
}
}
}
for (int operation = INSERT; operation <= UPDATE; operation++) {
if (mainToCompare[operation][ALL] != null) {
Iterator<DatabaseField> it = mainToCompare[operation][ALL].iterator();
while (it.hasNext()) {
DatabaseField fieldToCompare = it.next();
DatabaseField field = allFields.get(fieldToCompare);
if (!field.getType().equals(fieldToCompare.getType())) {
return false;
}
}
}
}
return true;
}
/**
* INTERNAL:
*/
public void trimModifyRowForInsert(AbstractRecord modifyRow) {
trimModifyRow(modifyRow, INSERT);
}
// operation should be either INSERT or UPDATE
protected void trimModifyRow(AbstractRecord modifyRow, int operation) {
if ((modifyRow == null) || modifyRow.isEmpty()) {
return;
}
Collection<? extends DatabaseField> fields = main[operation][RETURN_ONLY];
if ((fields == null) || fields.isEmpty()) {
return;
}
for (int i = modifyRow.size() - 1; i >= 0; i--) {
DatabaseField field = modifyRow.getFields().get(i);
if (fields.contains(field)) {
modifyRow.remove(field);
}
}
}
/**
* PUBLIC:
*/
public boolean isUsedToSetPrimaryKey() {
return isUsedToSetPrimaryKey;
}
// only infos is filled out
protected void clearInitialization() {
this.main = null;
this.tableToFieldsForGenerationMap = null;
this.fieldsNotFromDescriptor_DefaultTable = null;
this.fieldsNotFromDescriptor_OtherTables = null;
}
protected void initializeIsUsedToSetPrimaryKey() {
this.isUsedToSetPrimaryKey = false;
if ((main[INSERT][MAPPED] == null) || main[INSERT][MAPPED].isEmpty()) {
return;
}
List<DatabaseField> primaryKeys = getDescriptor().getPrimaryKeyFields();
for (int index = 0; (index < primaryKeys.size()) && !isUsedToSetPrimaryKey; index++) {
this.isUsedToSetPrimaryKey = main[INSERT][MAPPED].contains(primaryKeys.get(index));
}
}
protected boolean verifyFieldAndMapping(AbstractSession session, DatabaseField field) {
boolean ok = true;
verifyField(session, field, getDescriptor());
DatabaseMapping mapping;
List<DatabaseMapping> readOnlyMappings = getDescriptor().getObjectBuilder().getReadOnlyMappingsForField(field);
if (readOnlyMappings != null) {
for (int j = 0; j < readOnlyMappings.size(); j++) {
mapping = readOnlyMappings.get(j);
ok &= verifyFieldAndMapping(session, field, getDescriptor(), mapping);
}
}
mapping = getDescriptor().getObjectBuilder().getMappingForField(field);
if (mapping != null) {
ok &= verifyFieldAndMapping(session, field, getDescriptor(), mapping);
}
return ok;
}
protected static boolean verifyFieldAndMapping(AbstractSession session, DatabaseField field, ClassDescriptor descriptor, DatabaseMapping mapping) {
verifyField(session, field, descriptor);
while (mapping.isAggregateObjectMapping()) {
ClassDescriptor referenceDescriptor = mapping.getReferenceDescriptor();
mapping = referenceDescriptor.getObjectBuilder().getMappingForField(field);
verifyFieldAndMapping(session, field, referenceDescriptor, mapping);
}
if (!mapping.isDirectToFieldMapping() && !mapping.isTransformationMapping()) {
String mappingTypeName = Helper.getShortClassName(mapping);
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyMappingNotSupported(field.getName(), mappingTypeName, mapping));
return false;
} else {
return true;
}
}
protected static boolean verifyField(AbstractSession session, DatabaseField field, ClassDescriptor descriptor) {
boolean ok = true;
if (field.equals(descriptor.getSequenceNumberField())) {
ok = false;
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyFieldNotSupported(field.getName(), descriptor));
} else if (descriptor.hasInheritance() && field.equals(descriptor.getInheritancePolicy().getClassIndicatorField())) {
ok = false;
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyFieldNotSupported(field.getName(), descriptor));
} else if (descriptor.usesOptimisticLocking()) {
OptimisticLockingPolicy optimisticLockingPolicy = descriptor.getOptimisticLockingPolicy();
if (optimisticLockingPolicy instanceof VersionLockingPolicy) {
VersionLockingPolicy versionLockingPolicy = (VersionLockingPolicy)optimisticLockingPolicy;
if (field.equals(versionLockingPolicy.getWriteLockField())) {
ok = false;
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyFieldNotSupported(field.getName(), descriptor));
}
}
}
return ok;
}
/**
* INTERNAL:
*/
public void validationAfterDescriptorInitialization(AbstractSession session) {
Hashtable<DatabaseField, DatabaseField> mapped = new Hashtable<>();
for (int operation = INSERT; operation <= UPDATE; operation++) {
if ((main[operation][MAPPED] != null) && !main[operation][MAPPED].isEmpty()) {
Iterator<DatabaseField> it = main[operation][MAPPED].iterator();
while (it.hasNext()) {
DatabaseField field = it.next();
mapped.put(field, field);
}
}
}
if (!mapped.isEmpty()) {
for (Enumeration<DatabaseField> fields = getDescriptor().getFields().elements();
fields.hasMoreElements();) {
DatabaseField fieldInDescriptor = fields.nextElement();
DatabaseField fieldInMain = mapped.get(fieldInDescriptor);
if (fieldInMain != null) {
if (fieldInMain.getType() == null) {
if (getDescriptor().isReturnTypeRequiredForReturningPolicy()) {
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyMappedFieldTypeNotSet(fieldInMain.getName(), getDescriptor()));
}
} else if (isThereATypeConflict(fieldInMain, fieldInDescriptor)) {
session.getIntegrityChecker().handleError(DescriptorException.returningPolicyAndDescriptorFieldTypeConflict(fieldInMain.getName(), fieldInMain.getType().getName(), fieldInDescriptor.getType().getName(), getDescriptor()));
}
}
}
}
if (!(session.getDatasourcePlatform() instanceof DatabasePlatform)) {
// don't attempt further diagnostics on non-relational platforms
return;
}
WriteObjectQuery[] query = { getDescriptor().getQueryManager().getInsertQuery(), getDescriptor().getQueryManager().getUpdateQuery() };
String[] queryTypeName = { "InsertObjectQuery", "UpdateObjectQuery" };
for (int operation = INSERT; operation <= UPDATE; operation++) {
if ((main[operation][ALL] != null) && !main[operation][ALL].isEmpty()) {
// this operation requires some fields to be returned
if ((query[operation] == null) || (query[operation].getDatasourceCall() == null)) {
if (!session.getPlatform().canBuildCallWithReturning()) {
session.getIntegrityChecker().handleError(DescriptorException.noCustomQueryForReturningPolicy(queryTypeName[operation], Helper.getShortClassName(session.getPlatform()), getDescriptor()));
}
} else if (query[operation].getDatasourceCall() instanceof StoredProcedureCall) {
// SQLCall with custom SQL calculates its outputRowFields later (in prepare() method) -
// that's why SQLCall can't be verified here.
DatabaseCall customCall = (DatabaseCall)query[operation].getDatasourceCall();
Enumeration outputRowFields = customCall.getOutputRowFields().elements();
Collection<DatabaseField> notFoundInOutputRow = createCollection();
notFoundInOutputRow.addAll(main[operation][ALL]);
while (outputRowFields.hasMoreElements()) {
notFoundInOutputRow.remove(outputRowFields.nextElement());
}
if (!notFoundInOutputRow.isEmpty()) {
Iterator<DatabaseField> it = notFoundInOutputRow.iterator();
while (it.hasNext()) {
DatabaseField field = it.next();
session.getIntegrityChecker().handleError(DescriptorException.customQueryAndReturningPolicyFieldConflict(field.getName(), queryTypeName[operation], getDescriptor()));
}
}
}
}
}
}
/**
* INTERNAL:
* Returns an equal field held by ReturningPolicy, or null.
*/
public DatabaseField getField(DatabaseField field) {
DatabaseField foundField = null;
if (this.fieldsNotFromDescriptor_DefaultTable != null) {
foundField = this.fieldsNotFromDescriptor_DefaultTable.get(field);
}
if ((foundField == null) && (this.fieldsNotFromDescriptor_OtherTables != null)) {
foundField = this.fieldsNotFromDescriptor_OtherTables.get(field);
}
return foundField;
}
}