blob: 44c576516adc03679cb6c5aab738508f285dba0f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
<<<<<<< Updated upstream
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
=======
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
>>>>>>> Stashed changes
* limitations under the License.
*/
package org.apache.jdo.tck.query.delete;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import javax.jdo.listener.DeleteLifecycleListener;
import javax.jdo.listener.InstanceLifecycleEvent;
import javax.jdo.listener.StoreLifecycleListener;
import org.apache.jdo.tck.pc.company.CompanyModelReader;
import org.apache.jdo.tck.pc.company.Department;
import org.apache.jdo.tck.pc.company.Employee;
import org.apache.jdo.tck.pc.company.IEmployee;
import org.apache.jdo.tck.pc.company.IProject;
import org.apache.jdo.tck.pc.company.Person;
import org.apache.jdo.tck.pc.company.Project;
import org.apache.jdo.tck.pc.mylib.MylibReader;
import org.apache.jdo.tck.pc.mylib.PrimitiveTypes;
import org.apache.jdo.tck.query.QueryElementHolder;
import org.apache.jdo.tck.query.QueryTest;
import org.apache.jdo.tck.util.BatchTestRunner;
import org.apache.jdo.tck.util.ConversionHelper;
/**
* <B>Title:</B> Delete Persistent All. <br>
* <B>Keywords:</B> query <br>
* <B>Assertion ID:</B> A14.8-4 <br>
* <B>Assertion Description: </B> Dirty instances of affected classes are first flushed to the
* datastore. Instances already in the cache when deleted via these methods or brought into the
* cache as a result of these methods undergo the life cycle transitions as if deletePersistent had
* been called on them. That is, if an affected class implements the DeleteCallback interface, the
* instances to be deleted are instantiated in memory and the jdoPreDelete method is called prior to
* deleting the instance in the datastore. If any LifecycleListener instances are registered with
* affected classes, these listeners are called for each deleted instance. Before returning control
* to the application, instances of affected classes in the cache are refreshed by the
* implementation so their status in the cache reflects whether they were deleted from the
* datastore.
*/
public class DeleteCallback extends QueryTest {
/** */
private static final String ASSERTION_FAILED = "Assertion A14.8-4 (DeleteCallback) failed: ";
/**
* The array of valid queries which may be executed as single string queries and as API queries.
*/
private static final QueryElementHolder<?>[] VALID_QUERIES = {
new QueryElementHolder<>(
/*UNIQUE*/ null,
/*RESULT*/ null,
/*INTO*/ null,
/*FROM*/ Person.class,
/*EXCLUDE*/ null,
/*WHERE*/ null,
/*VARIABLES*/ null,
/*PARAMETERS*/ null,
/*IMPORTS*/ null,
/*GROUP BY*/ null,
/*ORDER BY*/ null,
/*FROM*/ null,
/*TO*/ null),
new QueryElementHolder<>(
/*UNIQUE*/ null,
/*RESULT*/ null,
/*INTO*/ null,
/*FROM*/ PrimitiveTypes.class,
/*EXCLUDE*/ null,
/*WHERE*/ null,
/*VARIABLES*/ null,
/*PARAMETERS*/ null,
/*IMPORTS*/ null,
/*GROUP BY*/ null,
/*ORDER BY*/ null,
/*FROM*/ null,
/*TO*/ null)
};
/** The expected results of valid queries. */
private final List<?>[] expectedResult = {
getTransientCompanyModelInstancesAsList(Employee.class, "emp1", "emp2", "emp3", "emp4", "emp5"),
getTransientMylibInstancesAsList(
"primitiveTypesPositive", "primitiveTypesNegative", "primitiveTypesCharacterStringLiterals")
};
/**
* The <code>main</code> is called when the class is directly executed from the command line.
*
* @param args The arguments passed to the program.
*/
public static void main(String[] args) {
BatchTestRunner.run(DeleteCallback.class);
}
/** */
public void testRelationshipsAPI() {
queryUpdateDeleteVerify(0, false, "middlename");
}
/** */
public void testRelationshipsSingleString() {
queryUpdateDeleteVerify(0, true, "middlename");
}
/** */
public void testNoRelationshipsAPI() {
queryUpdateDeleteVerify(1, false, "stringNull");
}
/** */
public void testNoRelationshipsSingleString() {
queryUpdateDeleteVerify(1, true, "stringNull");
}
/**
* @see org.apache.jdo.tck.JDO_Test#localSetUp()
*/
@Override
protected void localSetUp() {
addTearDownClass(CompanyModelReader.getTearDownClasses());
addTearDownClass(MylibReader.getTearDownClasses());
loadAndPersistCompanyModel(getPM());
loadAndPersistMylib(getPM());
}
/**
* Adds a lifecycle listener to the persistence manager.
* Converts the query element holder instance refered to by argument
* <code>index</code> to a JDO query instance based on argument
* <code>asSingleString</code>.
* Executes the query instance and marks all queried pc instances as dirty
* by calling {@link JDOHelper#makeDirty(java.lang.Object, java.lang.String)}.
* Passes argument <code>fieldName</code> to that call.
* Afterwards, calls {@link Query#deletePersistentAll()}, and
* verifies the lifecycle callbacks and the lifecycle states.
* @param index the index of the query element holder instance
* @param fieldName the field name passed as argument to
* {@link JDOHelper#makeDirty(java.lang.Object, java.lang.String)
* @param asSingleString determines if the query is executed as
* single string query or as API query.
*/
private void queryUpdateDeleteVerify(int index, boolean asSingleString, String fieldName) {
PersistenceManager pm = getPM();
Transaction transaction = pm.currentTransaction();
transaction.begin();
try {
LifecycleVerifier lifecycleVerifier;
Query<?> query =
asSingleString
? VALID_QUERIES[index].getSingleStringQuery(pm)
: VALID_QUERIES[index].getAPIQuery(pm);
Collection<?> result = executeQuery(query, index, asSingleString);
try {
lifecycleVerifier = new LifecycleVerifier(result);
pm.addInstanceLifecycleListener(
lifecycleVerifier, VALID_QUERIES[index].getCandidateClass());
updateInstances(result, fieldName);
deleteInstances(query, index, asSingleString, result.size());
} finally {
query.close(result);
}
lifecycleVerifier.verifyCallbacksAndStates();
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
/**
* Executes the given query, checks and returns the query result. Note: This mthod does not close
* the query result.
*
* @param query the query.
* @param index the index of the query element holder instance which was used to created the given
* query.
* @param asSingleString indicates if the given query was created using API methods or if it was
* created by a single string.
* @return the query result.
*/
private Collection<?> executeQuery(Query<?> query, int index, boolean asSingleString) {
if (logger.isDebugEnabled()) {
if (asSingleString) {
logger.debug("Executing single string query: " + VALID_QUERIES[index]);
} else {
logger.debug("Executing API query: " + VALID_QUERIES[index]);
}
}
Collection<?> result = (Collection<?>) query.execute();
if (logger.isDebugEnabled()) {
logger.debug("Query result: " + ConversionHelper.convertObjectArrayElements(result));
}
checkQueryResultWithoutOrder(ASSERTION_FAILED, result, expectedResult[index]);
return result;
}
/**
* Makes all instances in the given collection dirty.
* If instances are employees, then all relationships are cleared.
* @param instances the instances
* @param fieldName the field name passed as argument to
* {@link JDOHelper#makeDirty(java.lang.Object, java.lang.String)
*/
private void updateInstances(Collection<?> instances, String fieldName) {
for (Object pc : instances) {
// clear employee relationships
if (pc instanceof Employee) {
Employee employee = (Employee) pc;
if (employee.getDentalInsurance() != null) {
employee.getDentalInsurance().setEmployee(null);
}
if (employee.getMedicalInsurance() != null) {
employee.getMedicalInsurance().setEmployee(null);
}
if (employee.getDepartment() != null) {
((Department) employee.getDepartment()).removeEmployee(employee);
}
if (employee.getFundingDept() != null) {
((Department) employee.getFundingDept()).removeEmployee(employee);
}
if (employee.getManager() != null) {
((Employee) employee.getManager()).removeFromTeam(employee);
}
if (employee.getMentor() != null) {
employee.getMentor().setProtege(null);
}
if (employee.getProtege() != null) {
employee.getProtege().setMentor(null);
}
if (employee.getHradvisor() != null) {
((Employee) employee.getHradvisor()).removeAdvisee(employee);
}
if (employee.getReviewedProjects() != null) {
for (IProject iProject : employee.getReviewedProjects()) {
Project other = (Project) iProject;
other.removeReviewer(employee);
}
}
if (employee.getProjects() != null) {
for (IProject iProject : employee.getProjects()) {
Project other = (Project) iProject;
other.removeMember(employee);
}
}
if (employee.getTeam() != null) {
for (IEmployee iEmployee : employee.getTeam()) {
Employee other = (Employee) iEmployee;
other.setManager(null);
}
}
if (employee.getHradvisees() != null) {
for (IEmployee iEmployee : employee.getHradvisees()) {
Employee other = (Employee) iEmployee;
other.setHradvisor(employee);
}
}
}
// make the instance dirty.
if (logger.isDebugEnabled()) {
logger.debug("Calling JDOHelper.makeDirty(" + pc + ", \"" + fieldName + "\")");
}
JDOHelper.makeDirty(pc, fieldName);
}
}
/**
* Calls {@link Query#deletePersistentAll()} on the given query.
*
* @param query the query.
* @param index the index of the query element holder instance which was used to created the given
* query.
* @param asSingleString indicates if the given query was created using API methods or if it was
* created by a single string.
* @param expectedNumberOfDeletedInstances the expected number of deleted instances.
*/
private void deleteInstances(
Query<?> query, int index, boolean asSingleString, int expectedNumberOfDeletedInstances) {
if (logger.isDebugEnabled()) {
if (asSingleString) {
logger.debug("Deleting persistent by single string query: " + VALID_QUERIES[index]);
} else {
logger.debug("Deleting persistent by API query: " + VALID_QUERIES[index]);
}
}
long nr = query.deletePersistentAll();
if (logger.isDebugEnabled()) {
logger.debug(nr + " objects deleted.");
}
if (nr != expectedNumberOfDeletedInstances) {
fail(
ASSERTION_FAILED,
"deletePersistentAll returned "
+ nr
+ ", expected is "
+ expectedNumberOfDeletedInstances
+ ". Query: "
+ VALID_QUERIES[index]);
}
}
/**
* A lifecycle listener which may be added to persistence managers.
* Gathers delete events and store events and keeps those
* in a list.
* Method {@link LifecycleVerifier#verifyCallbacksAndStates()}
* may be called to check if the right events have been called
* on all expected instances.
* The expected instances are passed through
* {@link LifecycleVerifier#LifecycleVerifier(Collection).
*/
private class LifecycleVerifier implements DeleteLifecycleListener, StoreLifecycleListener {
/** The oids of expected pc instances. */
private final Collection<Object> expectedOids = new HashSet<>();
/** The list of events. */
private final List<InstanceLifecycleEvent> events = new ArrayList<>();
/**
* Argument <code>expectedPCInstances</code> holds pc instances which are expected to be sources
* of events.
*
* @param expectedPCInstances the pc instances which are expected to be sources of events.
*/
public LifecycleVerifier(Collection<?> expectedPCInstances) {
for (Object expectedPCInstance : expectedPCInstances) {
this.expectedOids.add(JDOHelper.getObjectId(expectedPCInstance));
}
}
/**
* Verifies if the right events have been called for all expected pc instances. All store events
* must have been fired before the first delete event has been fired. Furthermore, checks if pc
* instances kept in delete events have state persistent-deleted. The test case fails if one of
* these conditions is violated.
*/
public void verifyCallbacksAndStates() {
if (logger.isDebugEnabled()) {
logger.debug("Verifying callbacks and states.");
}
// The two collections are filled iterating through the list of
// events. Finally, they are compared against field expectedOids.
// Note: Set implementations are used instead of list
// implementations to eliminate duplicates. Duplicates may occur
// if multiple updates or deletions are executed for the same
// pc instances.
Collection<Object> oidsOfDeletedInstances = new HashSet<>();
Collection<Object> oidsOfUpdateInstances = new HashSet<>();
boolean hasDeleteEventBeenPassed = false;
int size = events.size();
for (InstanceLifecycleEvent event : events) {
Object source = event.getSource();
int eventType = event.getEventType();
if (eventType == InstanceLifecycleEvent.DELETE) {
if (logger.isDebugEnabled()) {
logger.debug("Verifying delete event on " + JDOHelper.getObjectId(source));
}
hasDeleteEventBeenPassed = true;
if (!JDOHelper.isDeleted(source)) {
fail(
ASSERTION_FAILED, "PC instance must have persistent deleted " + "state: " + source);
}
oidsOfDeletedInstances.add(JDOHelper.getObjectId(source));
} else if (eventType == InstanceLifecycleEvent.STORE) {
if (logger.isDebugEnabled()) {
logger.debug("Verifying store event on " + JDOHelper.getObjectId(source));
}
if (hasDeleteEventBeenPassed) {
fail(
ASSERTION_FAILED,
"PC instances must not be flushed " + "after delete has been executed.");
}
oidsOfUpdateInstances.add(JDOHelper.getObjectId(source));
}
}
if (!equalsCollection(oidsOfDeletedInstances, this.expectedOids)) {
String lf = System.getProperty("line.separator");
fail(
ASSERTION_FAILED,
"Got delete events for oids "
+ oidsOfDeletedInstances
+ '.'
+ lf
+ "Expected deleted events for oids "
+ this.expectedOids
+ '.');
} else if (!oidsOfUpdateInstances.containsAll(this.expectedOids)) {
String lf = System.getProperty("line.separator");
fail(
ASSERTION_FAILED,
"Got store events for oids "
+ oidsOfUpdateInstances
+ '.'
+ lf
+ "Expected store events for oids "
+ this.expectedOids
+ '.');
}
}
/**
* @see DeleteLifecycleListener#preDelete(javax.jdo.listener.InstanceLifecycleEvent)
*/
public void preDelete(InstanceLifecycleEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("preDelete event: " + JDOHelper.getObjectId(event.getSource()));
}
}
/**
* @see DeleteLifecycleListener#postDelete(javax.jdo.listener.InstanceLifecycleEvent)
*/
public void postDelete(InstanceLifecycleEvent event) {
this.events.add(event);
if (logger.isDebugEnabled()) {
logger.debug("postDelete event: " + JDOHelper.getObjectId(event.getSource()));
}
}
/**
* @see StoreLifecycleListener#preStore(javax.jdo.listener.InstanceLifecycleEvent)
*/
public void preStore(InstanceLifecycleEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("preStore event: " + JDOHelper.getObjectId(event.getSource()));
}
}
/**
* @see StoreLifecycleListener#postStore(javax.jdo.listener.InstanceLifecycleEvent)
*/
public void postStore(InstanceLifecycleEvent event) {
this.events.add(event);
if (logger.isDebugEnabled()) {
logger.debug("postStore event: " + JDOHelper.getObjectId(event.getSource()));
}
}
}
}