2013-10-06 12:32:57 +02:00
|
|
|
/*
|
2013-12-15 13:38:36 +01:00
|
|
|
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
|
|
|
|
*
|
|
|
|
* 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.
|
2013-10-06 12:32:57 +02:00
|
|
|
*/
|
2013-10-15 22:26:58 +02:00
|
|
|
package ch.eitchnet.xmlpers.impl;
|
2013-10-06 12:32:57 +02:00
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
import java.text.MessageFormat;
|
2013-11-14 19:21:49 +01:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
2013-10-06 12:32:57 +02:00
|
|
|
import java.util.List;
|
2013-11-14 19:21:49 +01:00
|
|
|
import java.util.Map;
|
2013-10-06 12:32:57 +02:00
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
import ch.eitchnet.utils.helper.StringHelper;
|
|
|
|
import ch.eitchnet.utils.objectfilter.ObjectFilter;
|
2013-10-15 22:26:58 +02:00
|
|
|
import ch.eitchnet.xmlpers.api.FileDao;
|
|
|
|
import ch.eitchnet.xmlpers.api.IoMode;
|
|
|
|
import ch.eitchnet.xmlpers.api.MetadataDao;
|
2013-11-14 19:21:49 +01:00
|
|
|
import ch.eitchnet.xmlpers.api.ModificationResult;
|
2013-10-15 22:26:58 +02:00
|
|
|
import ch.eitchnet.xmlpers.api.ObjectDao;
|
2013-10-06 12:32:57 +02:00
|
|
|
import ch.eitchnet.xmlpers.api.PersistenceContext;
|
2013-10-15 22:26:58 +02:00
|
|
|
import ch.eitchnet.xmlpers.api.PersistenceRealm;
|
|
|
|
import ch.eitchnet.xmlpers.api.PersistenceTransaction;
|
|
|
|
import ch.eitchnet.xmlpers.api.TransactionCloseStrategy;
|
2013-11-14 19:21:49 +01:00
|
|
|
import ch.eitchnet.xmlpers.api.TransactionResult;
|
|
|
|
import ch.eitchnet.xmlpers.api.TransactionState;
|
2013-10-15 22:26:58 +02:00
|
|
|
import ch.eitchnet.xmlpers.api.XmlPersistenceException;
|
|
|
|
import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
|
2013-10-06 12:32:57 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @author Robert von Burg <eitch@eitchnet.ch>
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public class DefaultPersistenceTransaction implements PersistenceTransaction {
|
|
|
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(DefaultPersistenceTransaction.class);
|
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
private final DefaultPersistenceRealm realm;
|
|
|
|
private final boolean verbose;
|
|
|
|
|
|
|
|
private final ObjectFilter objectFilter;
|
2013-10-06 12:32:57 +02:00
|
|
|
private final ObjectDao objectDao;
|
|
|
|
private final MetadataDao metadataDao;
|
2013-10-15 22:26:58 +02:00
|
|
|
|
|
|
|
private FileDao fileDao;
|
|
|
|
private IoMode ioMode;
|
2013-10-06 18:42:55 +02:00
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
private TransactionCloseStrategy closeStrategy;
|
|
|
|
|
2013-11-14 19:21:49 +01:00
|
|
|
private TransactionState state;
|
|
|
|
private long startTime;
|
|
|
|
private Date startTimeDate;
|
|
|
|
private TransactionResult txResult;
|
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
public DefaultPersistenceTransaction(DefaultPersistenceRealm realm, boolean verbose) {
|
2013-11-14 19:21:49 +01:00
|
|
|
this.startTime = System.nanoTime();
|
|
|
|
this.startTimeDate = new Date();
|
2013-10-15 22:26:58 +02:00
|
|
|
this.realm = realm;
|
2013-10-06 12:32:57 +02:00
|
|
|
this.verbose = verbose;
|
|
|
|
this.objectFilter = new ObjectFilter();
|
2013-10-15 22:26:58 +02:00
|
|
|
this.fileDao = new FileDao(this, realm.getPathBuilder(), verbose);
|
2013-10-06 12:32:57 +02:00
|
|
|
this.objectDao = new ObjectDao(this, this.fileDao, this.objectFilter);
|
2013-10-15 22:26:58 +02:00
|
|
|
this.metadataDao = new MetadataDao(realm.getPathBuilder(), this, verbose);
|
|
|
|
|
|
|
|
this.closeStrategy = TransactionCloseStrategy.COMMIT;
|
2013-11-14 19:21:49 +01:00
|
|
|
this.state = TransactionState.OPEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setTransactionResult(TransactionResult txResult) throws IllegalStateException {
|
|
|
|
if (this.txResult != null) {
|
|
|
|
String msg = "The transaction already has a result set!"; //$NON-NLS-1$
|
|
|
|
throw new IllegalStateException(msg);
|
|
|
|
}
|
|
|
|
this.txResult = txResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TransactionResult getTransactionResult() throws IllegalStateException {
|
|
|
|
if (isOpen()) {
|
|
|
|
String msg = "The transaction is still open thus has no result yet! Either commit or rollback before calling this method"; //$NON-NLS-1$
|
|
|
|
throw new IllegalStateException(msg);
|
|
|
|
}
|
|
|
|
return this.txResult;
|
2013-10-15 22:26:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public PersistenceRealm getRealm() {
|
|
|
|
return this.realm;
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ObjectDao getObjectDao() {
|
|
|
|
return this.objectDao;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public MetadataDao getMetadataDao() {
|
|
|
|
return this.metadataDao;
|
|
|
|
}
|
|
|
|
|
2014-08-23 20:47:37 +02:00
|
|
|
@Override
|
|
|
|
public FileDao getFileDao() {
|
|
|
|
return this.fileDao;
|
|
|
|
}
|
|
|
|
|
2013-10-06 12:32:57 +02:00
|
|
|
@Override
|
2013-10-15 22:26:58 +02:00
|
|
|
public ObjectReferenceCache getObjectRefCache() {
|
|
|
|
return this.realm.getObjectRefCache();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setCloseStrategy(TransactionCloseStrategy closeStrategy) {
|
|
|
|
this.closeStrategy = closeStrategy;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void close() throws XmlPersistenceException {
|
|
|
|
this.closeStrategy.close(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void autoCloseableRollback() {
|
2013-11-14 19:21:49 +01:00
|
|
|
long start = System.nanoTime();
|
|
|
|
if (this.state == TransactionState.COMMITTED)
|
2013-10-06 18:42:55 +02:00
|
|
|
throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$
|
2013-10-15 22:26:58 +02:00
|
|
|
|
2013-11-14 19:21:49 +01:00
|
|
|
if (this.state != TransactionState.ROLLED_BACK) {
|
2013-10-20 01:14:59 +02:00
|
|
|
unlockObjectRefs();
|
2013-11-14 19:21:49 +01:00
|
|
|
this.state = TransactionState.ROLLED_BACK;
|
2013-10-06 18:42:55 +02:00
|
|
|
this.objectFilter.clearCache();
|
2013-11-14 19:21:49 +01:00
|
|
|
|
|
|
|
long end = System.nanoTime();
|
|
|
|
long txDuration = end - this.startTime;
|
|
|
|
long closeDuration = end - start;
|
|
|
|
|
|
|
|
this.txResult.clear();
|
|
|
|
this.txResult.setState(this.state);
|
|
|
|
this.txResult.setStartTime(this.startTimeDate);
|
|
|
|
this.txResult.setTxDuration(txDuration);
|
|
|
|
this.txResult.setCloseDuration(closeDuration);
|
|
|
|
this.txResult.setRealm(this.realm.getRealmName());
|
|
|
|
this.txResult.setModificationByKey(Collections.<String, ModificationResult> emptyMap());
|
2013-10-06 18:42:55 +02:00
|
|
|
}
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2013-10-15 22:26:58 +02:00
|
|
|
public void autoCloseableCommit() throws XmlPersistenceException {
|
2013-10-06 12:32:57 +02:00
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
long start = System.nanoTime();
|
2013-10-06 12:32:57 +02:00
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
try {
|
2013-10-06 12:32:57 +02:00
|
|
|
|
2013-10-15 22:26:58 +02:00
|
|
|
if (this.verbose) {
|
|
|
|
String msg = "Committing {0} operations in TX...";//$NON-NLS-1$
|
2013-10-20 01:14:59 +02:00
|
|
|
logger.info(MessageFormat.format(msg, this.objectFilter.sizeCache()));
|
2013-10-15 22:26:58 +02:00
|
|
|
}
|
|
|
|
|
2013-10-20 01:14:59 +02:00
|
|
|
Set<String> keySet = this.objectFilter.keySet();
|
2013-11-14 19:21:49 +01:00
|
|
|
Map<String, ModificationResult> modifications;
|
|
|
|
if (this.txResult == null)
|
|
|
|
modifications = null;
|
|
|
|
else
|
|
|
|
modifications = new HashMap<>(keySet.size());
|
|
|
|
|
2013-10-06 12:32:57 +02:00
|
|
|
for (String key : keySet) {
|
|
|
|
|
|
|
|
List<Object> removed = this.objectFilter.getRemoved(key);
|
|
|
|
if (removed.isEmpty()) {
|
|
|
|
if (this.verbose)
|
|
|
|
logger.info("No objects removed in this tx."); //$NON-NLS-1$
|
|
|
|
} else {
|
|
|
|
if (this.verbose)
|
|
|
|
logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$
|
|
|
|
|
|
|
|
for (Object object : removed) {
|
2013-10-19 17:43:36 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
PersistenceContext<Object> ctx = (PersistenceContext<Object>) object;
|
2013-10-15 22:26:58 +02:00
|
|
|
this.fileDao.performDelete(ctx);
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Object> updated = this.objectFilter.getUpdated(key);
|
|
|
|
if (updated.isEmpty()) {
|
|
|
|
if (this.verbose)
|
|
|
|
logger.info("No objects updated in this tx."); //$NON-NLS-1$
|
|
|
|
} else {
|
|
|
|
if (this.verbose)
|
|
|
|
logger.info(updated.size() + " objects updated in this tx."); //$NON-NLS-1$
|
|
|
|
|
|
|
|
for (Object object : updated) {
|
2013-10-19 17:43:36 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
PersistenceContext<Object> ctx = (PersistenceContext<Object>) object;
|
2013-10-15 22:26:58 +02:00
|
|
|
this.fileDao.performUpdate(ctx);
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Object> added = this.objectFilter.getAdded(key);
|
|
|
|
if (added.isEmpty()) {
|
|
|
|
if (this.verbose)
|
|
|
|
logger.info("No objects added in this tx."); //$NON-NLS-1$
|
|
|
|
} else {
|
|
|
|
if (this.verbose)
|
|
|
|
logger.info(added.size() + " objects added in this tx."); //$NON-NLS-1$
|
|
|
|
|
|
|
|
for (Object object : added) {
|
2013-10-19 17:43:36 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
PersistenceContext<Object> ctx = (PersistenceContext<Object>) object;
|
2013-10-15 22:26:58 +02:00
|
|
|
this.fileDao.performCreate(ctx);
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
}
|
2013-11-14 19:21:49 +01:00
|
|
|
|
|
|
|
if (modifications != null) {
|
|
|
|
ModificationResult result = new ModificationResult(key, added, updated, removed);
|
|
|
|
modifications.put(key, result);
|
|
|
|
}
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
|
2013-11-14 19:21:49 +01:00
|
|
|
if (this.txResult != null) {
|
|
|
|
this.txResult.clear();
|
|
|
|
this.txResult.setState(TransactionState.COMMITTED);
|
|
|
|
this.txResult.setModificationByKey(modifications);
|
|
|
|
}
|
2013-10-15 22:26:58 +02:00
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
2013-11-14 19:21:49 +01:00
|
|
|
if (this.txResult == null) {
|
|
|
|
|
|
|
|
long end = System.nanoTime();
|
|
|
|
long txDuration = end - this.startTime;
|
|
|
|
long closeDuration = end - start;
|
2013-10-15 22:26:58 +02:00
|
|
|
|
2013-11-14 19:21:49 +01:00
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.append("TX has failed after "); //$NON-NLS-1$
|
|
|
|
sb.append(StringHelper.formatNanoDuration(txDuration));
|
|
|
|
sb.append(" with close operation taking "); //$NON-NLS-1$
|
|
|
|
sb.append(StringHelper.formatNanoDuration(closeDuration));
|
|
|
|
logger.info(sb.toString());
|
|
|
|
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.txResult.clear();
|
|
|
|
this.txResult.setState(TransactionState.FAILED);
|
|
|
|
this.txResult.setModificationByKey(Collections.<String, ModificationResult> emptyMap());
|
2014-01-10 23:14:17 +01:00
|
|
|
this.txResult.setFailCause(e);
|
2013-10-06 12:32:57 +02:00
|
|
|
|
|
|
|
} finally {
|
2013-11-14 19:21:49 +01:00
|
|
|
|
2013-10-06 12:32:57 +02:00
|
|
|
// clean up
|
2013-10-20 01:14:59 +02:00
|
|
|
unlockObjectRefs();
|
2013-10-06 12:32:57 +02:00
|
|
|
this.objectFilter.clearCache();
|
2013-11-14 19:21:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
long end = System.nanoTime();
|
|
|
|
long txDuration = end - this.startTime;
|
|
|
|
long closeDuration = end - start;
|
|
|
|
|
|
|
|
if (this.txResult == null) {
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.append("TX was completed after "); //$NON-NLS-1$
|
|
|
|
sb.append(StringHelper.formatNanoDuration(txDuration));
|
|
|
|
sb.append(" with close operation taking "); //$NON-NLS-1$
|
|
|
|
sb.append(StringHelper.formatNanoDuration(closeDuration));
|
|
|
|
logger.info(sb.toString());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
this.txResult.setStartTime(this.startTimeDate);
|
|
|
|
this.txResult.setTxDuration(txDuration);
|
|
|
|
this.txResult.setCloseDuration(closeDuration);
|
|
|
|
this.txResult.setRealm(this.realm.getRealmName());
|
|
|
|
|
|
|
|
if (this.txResult.getState() == TransactionState.FAILED) {
|
|
|
|
String msg = "Failed to commit TX due to underlying exception: {0}"; //$NON-NLS-1$
|
2013-12-22 23:03:14 +01:00
|
|
|
String causeMsg = this.txResult.getFailCause() == null ? null : this.txResult.getFailCause()
|
|
|
|
.getMessage();
|
|
|
|
msg = MessageFormat.format(msg, causeMsg);
|
2013-11-14 19:21:49 +01:00
|
|
|
throw new XmlPersistenceException(msg, this.txResult.getFailCause());
|
|
|
|
}
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-20 01:14:59 +02:00
|
|
|
@SuppressWarnings("rawtypes")
|
|
|
|
private void unlockObjectRefs() {
|
|
|
|
List<PersistenceContext> lockedObjects = this.objectFilter.getAll(PersistenceContext.class);
|
|
|
|
for (PersistenceContext lockedObject : lockedObjects) {
|
|
|
|
lockedObject.getObjectRef().unlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-06 12:32:57 +02:00
|
|
|
@Override
|
2013-10-06 18:42:55 +02:00
|
|
|
public boolean isOpen() {
|
2013-11-14 19:21:49 +01:00
|
|
|
return this.state == TransactionState.OPEN;
|
2013-10-06 18:42:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2013-10-15 22:26:58 +02:00
|
|
|
public void setIoMode(IoMode ioMode) {
|
2013-10-06 18:42:55 +02:00
|
|
|
this.ioMode = ioMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2013-10-15 22:26:58 +02:00
|
|
|
public IoMode getIoMode() {
|
2013-10-06 18:42:55 +02:00
|
|
|
return this.ioMode;
|
2013-10-06 12:32:57 +02:00
|
|
|
}
|
|
|
|
}
|