| /* |
| * 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; |
| } |
| } |