/* * Copyright 2012 the original author or authors. * * 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. */ package org.springframework.batch.item.data; import java.util.ArrayList; import java.util.List; import org.springframework.batch.item.ItemWriter; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * <p> * A {@link ItemWriter} implementation that writes to a MongoDB store using an implementation of Spring Data's * {@link MongoOperations}. Since MongoDB is not a transactional store, a best effort is made to persist * written data at the last moment, yet still honor job status contracts. No attempt to roll back is made * if an error occurs during writing. * </p> * * <p> * This writer is thread-safe once all properties are set (normal singleton behavior) so it can be used in multiple * concurrent transactions. * </p> * * @author Michael Minella * */ public class MongoItemWriter<T> implements ItemWriter<T>, InitializingBean { private MongoOperations template; private final Object bufferKey; private String collection; private boolean delete = false; public MongoItemWriter() { super(); this.bufferKey = new Object(); } /** * Indicates if the items being passed to the writer are to be saved or * removed from the data store. If set to false (default), the items will * be saved. If set to true, the items will be removed. * * @param delete removal indicator */ public void setDelete(boolean delete) { this.delete = delete; } /** * Set the {@link MongoOperations} to be used to save items to be written. * * @param template the template implementation to be used. */ public void setTemplate(MongoOperations template) { this.template = template; } /** * Set the name of the Mongo collection to be written to. * * @param collection the name of the collection. */ public void setCollection(String collection) { this.collection = collection; } /** * If a transaction is active, buffer items to be written just before commit. * Otherwise write items using the provided template. * * @see org.springframework.batch.item.ItemWriter#write(List) */ @Override public void write(List<? extends T> items) throws Exception { if(!transactionActive()) { doWrite(items); return; } List<T> bufferedItems = getCurrentBuffer(); bufferedItems.addAll(items); } /** * Performs the actual write to the store via the template. * This can be overridden by a subclass if necessary. * * @param items the list of items to be persisted. */ protected void doWrite(List<? extends T> items) { if(! CollectionUtils.isEmpty(items)) { if(delete) { if(StringUtils.hasText(collection)) { for (Object object : items) { template.remove(object, collection); } } else { for (Object object : items) { template.remove(object); } } } else { if(StringUtils.hasText(collection)) { for (Object object : items) { template.save(object, collection); } } else { for (Object object : items) { template.save(object); } } } } } private boolean transactionActive() { return TransactionSynchronizationManager.isActualTransactionActive(); } @SuppressWarnings("unchecked") private List<T> getCurrentBuffer() { if(!TransactionSynchronizationManager.hasResource(bufferKey)) { TransactionSynchronizationManager.bindResource(bufferKey, new ArrayList<T>()); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void beforeCommit(boolean readOnly) { List<T> items = (List<T>) TransactionSynchronizationManager.getResource(bufferKey); if(!CollectionUtils.isEmpty(items)) { if(!readOnly) { doWrite(items); } } } @Override public void afterCompletion(int status) { if(TransactionSynchronizationManager.hasResource(bufferKey)) { TransactionSynchronizationManager.unbindResource(bufferKey); } } }); } return (List<T>) TransactionSynchronizationManager.getResource(bufferKey); } @Override public void afterPropertiesSet() throws Exception { Assert.state(template != null, "A MongoOperations implementation is required."); } }