blob: bd0383c711c3afa2c3474476f1c3db6ce68fe391 [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
*
* 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
* 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.Iterator;
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.JDO_Test;
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.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 List[] expectedResult = {
getTransientCompanyModelInstancesAsList(new String[]{
"emp1", "emp2", "emp3", "emp4", "emp5"}),
getTransientMylibInstancesAsList(new String[]{
"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,
new Class[]{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 (Iterator i = instances.iterator(); i.hasNext(); ) {
Object pc = i.next();
// 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 (Iterator it=employee.getReviewedProjects().iterator();
it.hasNext(); ) {
Project other = (Project) it.next();
other.removeReviewer(employee);
}
}
if (employee.getProjects() != null) {
for (Iterator it=employee.getProjects().iterator();
it.hasNext(); ) {
Project other = (Project) it.next();
other.removeMember(employee);
}
}
if (employee.getTeam() != null) {
for (Iterator it=employee.getTeam().iterator(); it.hasNext(); ) {
Employee other = (Employee) it.next();
other.setManager(null);
}
}
if (employee.getHradvisees() != null) {
for (Iterator it=employee.getHradvisees().iterator(); it.hasNext(); ) {
Employee other = (Employee) it.next();
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 Collection expectedOids = new HashSet();
/** The list of events. */
private List 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 (Iterator i = expectedPCInstances.iterator(); i.hasNext(); ) {
this.expectedOids.add(JDOHelper.getObjectId(i.next()));
}
}
/**
* 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 oidsOfDeletedInstances = new HashSet();
Collection oidsOfUpdateInstances = new HashSet();
boolean hasDeleteEventBeenPassed = false;
int size = events.size();
for (int i = 0; i < size; i++) {
InstanceLifecycleEvent event =
(InstanceLifecycleEvent) this.events.get(i);
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()));
}
}
}
}