| /* |
| * 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 + "'."); |
| } |
| } |
| } |