| /* |
| * 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())); |
| } |
| } |
| } |
| } |