/********************************************************************** Copyright (c) 2013 Andy Jefferson and others. All rights reserved. Licensed 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. Contributors: ... **********************************************************************/ package org.datanucleus.flush; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.datanucleus.ExecutionContext; import org.datanucleus.exceptions.NucleusOptimisticException; import org.datanucleus.state.ObjectProvider; import org.datanucleus.store.StorePersistenceHandler; import org.datanucleus.util.Localiser; import org.datanucleus.util.NucleusLogger; /** * Flush method for cases where the datastore doesn't use referential integrity so we can send batches * of deletes, then batches of inserts, then any updates to optimise the persistence. * This also makes use of the OperationQueue to do more intelligent handling of cascade delete when elements are removed * from Collections, checking if it is later added to a different collection. */ public class FlushNonReferential implements FlushProcess { /* (non-Javadoc) * @see org.datanucleus.FlushProcess#execute(org.datanucleus.ExecutionContext, java.util.List, java.util.List, org.datanucleus.flush.OperationQueue) */ public List<NucleusOptimisticException> execute(ExecutionContext ec, List<ObjectProvider> primaryOPs, List<ObjectProvider> secondaryOPs, OperationQueue opQueue) { // Make copy of ObjectProviders so we don't have ConcurrentModification issues Set<ObjectProvider> opsToFlush = new HashSet<ObjectProvider>(); if (primaryOPs != null) { opsToFlush.addAll(primaryOPs); primaryOPs.clear(); } if (secondaryOPs != null) { opsToFlush.addAll(secondaryOPs); secondaryOPs.clear(); } // Process all delete, insert and update of objects List<NucleusOptimisticException> excptns = flushDeleteInsertUpdateGrouped(opsToFlush, ec); if (opQueue != null) { if (!ec.getStoreManager().usesBackedSCOWrappers()) { // This ExecutionContext is not using backing store SCO wrappers, so process SCO Operations for cascade delete etc. opQueue.processOperationsForNoBackingStoreSCOs(ec); } opQueue.clearPersistDeleteUpdateOperations(); } return excptns; } /** * Method that does the flushing of the passed ObjectProviders, grouping them into all DELETEs, then all INSERTs, * finally all UPDATEs. The StorePersistenceHandler will get calls to <i>deleteObjects</i>, <i>insertObjects</i> * and <i>updateObject</i> (for each other one). Note that this is in a separate method to allow calls by * other FlushProcesses that want to take advantage of the basic flush method without * @param opsToFlush The ObjectProviders to process * @param ec ExecutionContext * @return Any optimistic verification exceptions thrown during flush */ public List<NucleusOptimisticException> flushDeleteInsertUpdateGrouped(Set<ObjectProvider> opsToFlush, ExecutionContext ec) { List<NucleusOptimisticException> optimisticFailures = null; Set<Class> classesToFlush = null; if (ec.getNucleusContext().getStoreManager().getQueryManager().getQueryResultsCache() != null) { classesToFlush = new HashSet(); } Set<ObjectProvider> opsToDelete = new HashSet<ObjectProvider>(); Set<ObjectProvider> opsToInsert = new HashSet<ObjectProvider>(); Iterator<ObjectProvider> opIter = opsToFlush.iterator(); while (opIter.hasNext()) { ObjectProvider op = opIter.next(); if (op.isEmbedded()) { op.markAsFlushed(); // Embedded have nothing to flush since the owner manages it opIter.remove(); } else { if (classesToFlush != null && op.getObject() != null) { classesToFlush.add(op.getObject().getClass()); } if (op.getLifecycleState().isNew() && !op.isFlushedToDatastore() && !op.isFlushedNew()) { // P_NEW and not yet flushed to datastore opsToInsert.add(op); opIter.remove(); } else if (op.getLifecycleState().isDeleted() && !op.isFlushedToDatastore()) { if (!op.getLifecycleState().isNew()) { // P_DELETED opsToDelete.add(op); opIter.remove(); } else if (op.getLifecycleState().isNew() && op.isFlushedNew()) { // P_NEW_DELETED already persisted opsToDelete.add(op); opIter.remove(); } } } } if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("010046", opsToDelete.size(), opsToInsert.size(), opsToFlush.size())); } StorePersistenceHandler persistenceHandler = ec.getStoreManager().getPersistenceHandler(); if (!opsToDelete.isEmpty()) { // Perform preDelete - deleteAll - postDelete, and mark all ObjectProviders as flushed // TODO This omits some parts of sm.internalDeletePersistent for (ObjectProvider op : opsToDelete) { op.setFlushing(true); ec.getCallbackHandler().preDelete(op.getObject()); } try { persistenceHandler.deleteObjects(opsToDelete.toArray(new ObjectProvider[opsToDelete.size()])); } catch (NucleusOptimisticException noe) { optimisticFailures = new ArrayList(); Throwable[] nestedExcs = noe.getNestedExceptions(); if (nestedExcs != null && nestedExcs.length > 1) { NucleusOptimisticException[] noes = (NucleusOptimisticException[])nestedExcs; for (int i=0;i<nestedExcs.length;i++) { optimisticFailures.add(noes[i]); } } else { optimisticFailures.add(noe); } } for (ObjectProvider op : opsToDelete) { ec.getCallbackHandler().postDelete(op.getObject()); op.setFlushedNew(false); op.markAsFlushed(); op.setFlushing(false); } } if (!opsToInsert.isEmpty()) { // Perform preStore - insertAll - postStore, and mark all ObjectProviders as flushed // TODO This omits some parts of sm.internalMakePersistent for (ObjectProvider op : opsToInsert) { op.setFlushing(true); ec.getCallbackHandler().preStore(op.getObject()); // TODO Make sure identity is set since user could have updated fields in preStore } persistenceHandler.insertObjects(opsToInsert.toArray(new ObjectProvider[opsToInsert.size()])); for (ObjectProvider op : opsToInsert) { ec.getCallbackHandler().postStore(op.getObject()); op.setFlushedNew(true); op.markAsFlushed(); op.setFlushing(false); ec.putObjectIntoLevel1Cache(op); // Update the object in the cache(s) now that version/id are set } } if (!opsToFlush.isEmpty()) { // Objects to update for (ObjectProvider op : opsToFlush) { try { op.flush(); } catch (NucleusOptimisticException oe) { if (optimisticFailures == null) { optimisticFailures = new ArrayList(); } optimisticFailures.add(oe); } } } if (classesToFlush != null) { // Flush any query results from cache for these types Iterator<Class> queryClsIter = classesToFlush.iterator(); while (queryClsIter.hasNext()) { Class cls = queryClsIter.next(); ec.getNucleusContext().getStoreManager().getQueryManager().evictQueryResultsForType(cls); } } return optimisticFailures; } }