blob: 32f967935cfdea7b58ffb3e04237d3f1faecfda8 [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.testing.framework;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.sessions.server.ClientSession;
import org.eclipse.persistence.descriptors.*;
import org.eclipse.persistence.mappings.*;
import org.eclipse.persistence.mappings.foundation.*;
import java.util.Enumeration;
import java.util.Vector;
/**
* <p>
* <b>Purpose</b>: Define a generic test for writing an object to the database.
* Should originalObject contain no changes to the original object from the
* database (a TRIVIAL UPDATE), find and mutate a direct to field mapping before
* writing the object to the database. If originalObject is different from but
* has the same primary key as an object on the database, do not mutate the
* object as it has already been changed (a NON-TRIVIAL UPDATE).
* <p>
* <b>Responsibilities</b>:
* <ul>
* <li> Be independent of the class being tested.
* <li> Attempt to mutate a Direct to Field mapping if instructed to do so.
* <li> Execute the insert object query and verify no errors occurred.
* <li> Verify the object written matches the object that was written.
* </ul>
*/
public class WriteObjectTest extends TransactionalTestCase {
/** Query to read the original object from the database */
protected ReadObjectQuery query;
/** The original object that is set thru example methods from the model */
protected Object originalObject;
/** The originalObject is read from the database and stored here */
protected Object objectToBeWritten;
/** The object from the database is read in verify to compare against the
* objectToBeWritten.
*/
protected Object objectFromDatabase;
/** The following allows individual tests to be run with or
* without bind all parameters on. */
protected Boolean bindAllParameters = null;
protected Boolean bindAllParametersOriginal = null;
/** An option to test non-trivial updates. */
protected boolean makesTrivialUpdate = true;
/** This determines whether the test should attempt to mutate the object.
* This defaults to true. It can be set to false where changing the object
* will cause failures.
*/
protected boolean testShouldMutate = true;
public WriteObjectTest() {
setDescription("The test writing of the intended object from the "+
"database and checks if it was inserted properly");
}
public WriteObjectTest(Object originalObject) {
this.originalObject = originalObject;
setName(getName() + "(" + originalObject.getClass() + ")");
setDescription(
"The test writing of the intended object, '"+originalObject
+"', from the database and checks if it was inserted properly");
}
public Object getOriginalObject() {
return originalObject;
}
/**
* A method that takes an object and attempts to find a direct to field
* mapping that can be mutated. The value of the mapping is changed by
* appending a mutation string to the existing value in the mapping.
* Should no suitable mapping be found, the method simply returns the
* object passed in.
* This covers test case bug 2773036
*/
public Object findAndMutateDirectToFieldMappingInObject(Object objectToBeMutated, boolean isInUOW) {
DatabaseMapping dbMapping = null;
DatabaseMapping mutatableMapping = null;
String mutationString = "M"; // The string to append
/**
* Here the class of the object passed is used to get the corresponding
* descriptor, which is then used to find the mappings and determine if
* one exists that can be mutated
*/
Class<? extends Object> objectClass = objectToBeMutated.getClass();
ClassDescriptor descriptor = getSession().getProject().getClassDescriptor(objectClass);
Vector<DatabaseMapping> mappings = descriptor.getMappings();
if (isInUOW) {
mutationString += "U";
}
Enumeration<DatabaseMapping> en = mappings.elements();
/**
* Parse the mappings for the object's descriptor to find a suitable
* mapping that can be mutated. The mapping must meet the conditions:
* Not a primary key mapping
* Must be direct to field
* Must be a string field
* Must not have a converter (i.e. Male--{@literal >}M, Female--{@literal >}F, Unknown--{@literal >}U)
* The loop exits once an appropriate mapping is found or the list of
* mappings has been fully parsed (whichever occurs first)
*/
while (en.hasMoreElements ()) {
dbMapping = en.nextElement();
if (!dbMapping.isPrimaryKeyMapping()
&& dbMapping.isDirectToFieldMapping()
&&!((AbstractDirectMapping) dbMapping).hasConverter()
&& (dbMapping.getAttributeAccessor().getAttributeClass())
.getName().indexOf("String") != -1) {
mutatableMapping = dbMapping;
break;
}
}
/**
* If a mapping was found that can be mutated, use TopLink methods to
* modify the value stored in the object for that mapping. Otherwise
* do nothing.
*/
if (mutatableMapping != null) {
mutatableMapping.setAttributeValueInObject(
objectToBeMutated,
mutatableMapping.getAttributeValueFromObject(
objectToBeMutated) + mutationString);
}
else {
// Can't necessarily throw error/warning as some projects
// (i.e. LOB project) have descriptors that are not simple to mutate
}
return objectToBeMutated;
}
/**
* @see #setMakesTrivialUpdate(boolean)
*/
public boolean makesTrivialUpdate() {
return makesTrivialUpdate;
}
/**
* @see #setTestShouldMutate(boolean)
*/
public boolean testShouldMutate() {
return testShouldMutate;
}
@Override
public void reset() {
if (bindAllParametersOriginal != null) {
getSession().getLogin().setShouldBindAllParameters(bindAllParametersOriginal);
}
super.reset();
}
/**
* A trivial update is writing the same version of an object to the
* database as exists on the database. Normally in this test the object
* passed to the constructor is used only to read the version of itself on
* the database. This version from the database is then written to the
* database: a trivial update. If this flag is set and originalObject is
* different from but has the same primary key as an object on the database,
* then a non-trivial update will occur.
* Warning: If you modify the objects passed to this
* test, do not obtain them from the population manager, as they may become
* corrupted for other users.
*/
public void setMakesTrivialUpdate(boolean value) {
this.makesTrivialUpdate = value;
}
/**
* Some subclasses of WriteObjectTest will not return the correct results if
* the object is mutated, for example tests that pass null values and expect
* nulls to be returned. If this flag is set then the object will not be
* mutated before attempting to write to the database.
*/
public void setTestShouldMutate(boolean value) {
this.testShouldMutate = value;
}
/**
* Allows one to set bindAllParameters to true on a test by test basis.
* This works only for simple sessions, and could cause this simple test to
* run much slower than before.
*/
public void setShouldBindAllParameters(boolean value) {
bindAllParameters = value;
}
@Override
protected void setup() {
if (shouldBindAllParameters() != null) {
bindAllParametersOriginal = getSession().getLogin().shouldBindAllParameters();
getSession().getLogin().setShouldBindAllParameters(shouldBindAllParameters());
}
super.setup();
this.query = new ReadObjectQuery();
this.query.setSelectionObject(this.originalObject);
/* Must ensure that the object is from the database for updates. */
this.objectToBeWritten = getSession().executeQuery(this.query);
if (this.query.getDescriptor().shouldIsolateProtectedObjectsInUnitOfWork() && getSession().isClientSession()){
//must get entity from server session and not the isolated session.
this.objectToBeWritten = ((ClientSession)getSession()).getParent().getIdentityMapAccessor().getFromIdentityMap(this.objectToBeWritten);
}
if (this.objectToBeWritten == null) {
this.objectToBeWritten = this.originalObject;
this.query = new ReadObjectQuery();
this.query.setSelectionObject(this.originalObject);
}
if (!makesTrivialUpdate()) {
this.objectToBeWritten = this.originalObject;
}
}
public Boolean shouldBindAllParameters() {
return bindAllParameters;
}
/**
* The test() method will, if required, pass the object to the
* findAndMutateDirectToFieldMappingInObject method, and will then attempt
* to write the object to the database.
*/
@Override
protected void test() {
if (makesTrivialUpdate() && testShouldMutate()) {
// Only want to do this if the update is trivial
// Otherwise there are already changes in the object that
// will generate SQL
this.objectToBeWritten =
this.findAndMutateDirectToFieldMappingInObject(
this.objectToBeWritten, false);
}
getDatabaseSession().writeObject(this.objectToBeWritten);
}
/**
* Verify if the objects match completely through allowing the session
* to use the descriptors. This will compare the objects and all of
* their privately owned parts.
*/
@Override
protected void verify() {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
this.objectFromDatabase = getSession().executeQuery(this.query);
if (!(compareObjects(this.objectToBeWritten, this.objectFromDatabase))) {
throw new TestErrorException("The object inserted into the database, '"
+ this.objectFromDatabase + "' does not match the original, '"
+ this.objectToBeWritten + "'.");
}
}
}