/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* 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.*; | |
/** | |
* <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 objectClass = objectToBeMutated.getClass(); | |
ClassDescriptor descriptor = getSession().getProject().getClassDescriptor(objectClass); | |
java.util.Vector mappings = descriptor.getMappings(); | |
if (isInUOW) { | |
mutationString += "U"; | |
} | |
java.util.Enumeration 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-->M, Female-->F, Unknown-->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 = (DatabaseMapping) 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; | |
} | |
public void reset() { | |
if (bindAllParametersOriginal != null) { | |
getSession().getLogin().setShouldBindAllParameters(bindAllParametersOriginal.booleanValue()); | |
} | |
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 = Boolean.valueOf(value); | |
} | |
protected void setup() { | |
if (shouldBindAllParameters() != null) { | |
bindAllParametersOriginal = Boolean.valueOf(getSession().getLogin().shouldBindAllParameters()); | |
getSession().getLogin().setShouldBindAllParameters(shouldBindAllParameters().booleanValue()); | |
} | |
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. | |
*/ | |
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. | |
*/ | |
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 + "'."); | |
} | |
} | |
} |