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