From d5dd36f773ca331080af609baba54c0a95efd22a Mon Sep 17 00:00:00 2001 From: cramakri <cramakri> Date: Tue, 8 Feb 2011 10:27:58 +0000 Subject: [PATCH] LMS-2033 Implemented improved transaction facility. SVN: 19807 --- .../cisd/etlserver/BaseDirectoryHolder.java | 14 +- .../systemsx/cisd/etlserver/FileRenamer.java | 19 +- .../etlserver/TransferredDataSetHandler.java | 2 +- ...tOmniscientTopLevelDataSetRegistrator.java | 14 + .../DataSetRegistrationService.java | 133 ++-- .../registrator/DataSetStorageAlgorithm.java | 603 ++++++++++++++++++ .../DataSetStorageAlgorithmRunner.java | 288 +++++++++ .../DefaultEntityRegistrationService.java | 79 +++ .../IEntityRegistrationService.java | 32 + .../JythonTopLevelDataSetHandler.java | 15 +- .../api/v1/impl/AbstractTransactionState.java | 376 +++++++++++ .../impl/DataSetRegistrationTransaction.java | 382 +++-------- .../dto/AtomicEntityRegistrationDetails.java | 86 +++ .../dto/AtomicEntityRegistrationResult.java | 79 +++ .../dto/DataSetRegistrationInformation.java | 42 ++ .../JythonTopLevelDataSetRegistratorTest.java | 2 +- .../DataSetRegistrationTransactionTest.java | 20 +- 17 files changed, 1759 insertions(+), 427 deletions(-) create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithm.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithmRunner.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityRegistrationService.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IEntityRegistrationService.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationDetails.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationResult.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetRegistrationInformation.java diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/BaseDirectoryHolder.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/BaseDirectoryHolder.java index fbe4029b62d..517bcf849a4 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/BaseDirectoryHolder.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/BaseDirectoryHolder.java @@ -19,12 +19,12 @@ package ch.systemsx.cisd.etlserver; import java.io.File; /** - * A tiny class which holds the <i>base directory</i> and ensures that the <i>target file</i> - * lazily gets computed only once. + * A tiny class which holds the <i>base directory</i> and ensures that the <i>target file</i> lazily + * gets computed only once. * * @author Christian Ribeaud */ -final class BaseDirectoryHolder +public final class BaseDirectoryHolder { private final File baseDirectory; @@ -35,8 +35,8 @@ final class BaseDirectoryHolder private File targetFile; - BaseDirectoryHolder(final IDataStoreStrategy dataStoreStrategy, final File baseDirectory, - final File incomingDataSetPath) + public BaseDirectoryHolder(final IDataStoreStrategy dataStoreStrategy, + final File baseDirectory, final File incomingDataSetPath) { assert dataStoreStrategy != null : "Data store strategy can not be null."; assert baseDirectory != null : "Base directory can not be null"; @@ -51,12 +51,12 @@ final class BaseDirectoryHolder return dataStoreStrategy.getTargetPath(baseDirectory, incomingDataSetPath); } - final File getBaseDirectory() + public final File getBaseDirectory() { return baseDirectory; } - final synchronized File getTargetFile() + public final synchronized File getTargetFile() { if (targetFile == null) { diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/FileRenamer.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/FileRenamer.java index f6a435d4236..598b60a68c7 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/FileRenamer.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/FileRenamer.java @@ -32,22 +32,21 @@ import ch.systemsx.cisd.common.logging.LogFactory; * * @author Franz-Josef Elmer */ -final class FileRenamer +public final class FileRenamer { - private final static Logger notificationLog = - LogFactory.getLogger(LogCategory.NOTIFY, FileRenamer.class); + private final static Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, + FileRenamer.class); - final static Logger operationLog = - LogFactory.getLogger(LogCategory.OPERATION, FileRenamer.class); + final static Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + FileRenamer.class); /** * Renames given <var>sourceFile</var> to given <var>destinationFile</var>. * <p> - * Internally uses {@link FileOperations} and notifies the administrator if the process - * failed. + * Internally uses {@link FileOperations} and notifies the administrator if the process failed. * </p> */ - static final boolean renameAndLog(final File sourceFile, final File destinationFile) + public static final boolean renameAndLog(final File sourceFile, final File destinationFile) { final String absoluteTargetPath = destinationFile.getAbsolutePath(); if (destinationFile.exists()) @@ -58,8 +57,8 @@ final class FileRenamer return false; } boolean renamedOK = - FileOperations.getMonitoredInstanceForCurrentThread().rename(sourceFile, - destinationFile); + FileOperations.getMonitoredInstanceForCurrentThread().rename(sourceFile, + destinationFile); if (renamedOK) { if (operationLog.isInfoEnabled()) diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java index 6f8ca7e277c..6f0dacee878 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java @@ -53,7 +53,7 @@ public final class TransferredDataSetHandler extends AbstractTopLevelDataSetRegi IDataSetHandler, IExtensibleDataSetHandler { - static final String TARGET_NOT_RELATIVE_TO_STORE_ROOT = + public static final String TARGET_NOT_RELATIVE_TO_STORE_ROOT = "Target path '%s' is not relative to store root directory '%s'."; static final Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java index e64e5b7fed1..e43308aa606 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/AbstractOmniscientTopLevelDataSetRegistrator.java @@ -283,6 +283,20 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator extends throwable).doRollback(); } + /** + * Rollback a failure when trying to commit a transaction. + * <p> + * Subclasses may override, but should call super. + */ + + public <T extends DataSetInformation> void rollbackTransaction( + DataSetRegistrationService dataSetRegistrationService, + DataSetRegistrationTransaction<T> transaction, + DataSetStorageAlgorithmRunner<T> algorithm, Throwable ex) + { + updateStopped(ex instanceof InterruptedExceptionUnchecked); + } + /** * Rollback a failure that occurs outside of any *particular* data set registration, but with * the whole processing of the incoming folder itself. diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java index d72695030c0..3e583a04c8f 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetRegistrationService.java @@ -53,9 +53,6 @@ public class DataSetRegistrationService implements IRollbackDelegate private final ArrayList<DataSetRegistrationAlgorithm> dataSetRegistrations = new ArrayList<DataSetRegistrationAlgorithm>(); - // Any parent services - private final DataSetRegistrationService parentServiceOrNull; - /** * The currently live child transaction. */ @@ -93,21 +90,6 @@ public class DataSetRegistrationService implements IRollbackDelegate this.registrator = registrator; this.registratorState = registrator.getRegistratorState(); this.globalCleanAfterwardsAction = globalCleanAfterwardsAction; - this.parentServiceOrNull = null; - } - - /** - * A copy constuctor. Used in the creation of transactions. Subclasses will want to override - * this. - * - * @param other - */ - public DataSetRegistrationService(DataSetRegistrationService other) - { - this.registrator = other.registrator; - this.registratorState = registrator.getRegistratorState(); - this.globalCleanAfterwardsAction = new NoOpCleanAfterwardsAction(); - this.parentServiceOrNull = other; } public OmniscientTopLevelDataSetRegistratorState getRegistratorState() @@ -130,6 +112,9 @@ public class DataSetRegistrationService implements IRollbackDelegate return future; } + /** + * Create a new transaction that atomically performs file operations and registers entities. + */ public IDataSetRegistrationTransaction transaction(File dataSetFile, IDataSetRegistrationDetailsFactory<DataSetInformation> detailsFactory) { @@ -145,27 +130,18 @@ public class DataSetRegistrationService implements IRollbackDelegate // Clone this service for the transaction to keep them independent liveTransactionOrNull = new DataSetRegistrationTransaction<DataSetInformation>(registrator.getGlobalState() - .getStoreRootDir(), workingDirectory, stagingDirectory, - this.createSubService(), detailsFactory); + .getStoreRootDir(), workingDirectory, stagingDirectory, this, + detailsFactory); return liveTransactionOrNull; } - // public <T extends DataSetInformation> DataSetStorageAlgorithm<T> createStorageAlgorithm( - // File dataSetFile, DataSetRegistrationDetails<T> dataSetDetails) - // { - // IDataStoreStrategy strategy = - // registratorState.getDataStrategyStore().getDataStoreStrategy( - // dataSetDetails.getDataSetInformation(), dataSetFile); - // DataSetStorageAlgorithm<T> algorithm = - // new DataSetStorageAlgorithm<T>(dataSetFile, dataSetDetails, strategy, - // registratorState.getStorageProcessor(), null, null, null, null); - // return algorithm; - // } - + /** + * Commit any scheduled changes. + */ public void commit() { - // If a transaction is hanging around, commit it before starting a new one + // If a transaction is hanging around, commit it commitExtantTransaction(); for (DataSetRegistrationAlgorithm registrationAlgorithm : dataSetRegistrations) @@ -175,30 +151,50 @@ public class DataSetRegistrationService implements IRollbackDelegate globalCleanAfterwardsAction.execute(); } + /** + * Abort any scheduled changes. + */ public void abort() { + rollbackExtantTransaction(); dataSetRegistrations.clear(); + } - if (null != liveTransactionOrNull) - { - liveTransactionOrNull.rollback(); - } - if (null != parentServiceOrNull) - { - if (null != parentServiceOrNull.liveTransactionOrNull) - { - parentServiceOrNull.liveTransactionOrNull.rollback(); - } - } + public void rollback(DataSetRegistrationAlgorithm algorithm, Throwable ex) + { + registrator.rollback(this, algorithm, ex); + } + + public <T extends DataSetInformation> void rollbackTransaction( + DataSetRegistrationTransaction<T> transaction, + DataSetStorageAlgorithmRunner<T> algorithm, Throwable ex) + { + registrator.rollbackTransaction(this, transaction, algorithm, ex); } /** - * Create a service derived from this one. By default, use the copy constructor. Subclasses - * should override. + * Create a storage algorithm for storing an individual data set. This is internally used by + * transactions. Other clients may find it useful as well. */ - protected DataSetRegistrationService createSubService() + public <T extends DataSetInformation> DataSetStorageAlgorithm<T> createStorageAlgorithm( + File dataSetFile, DataSetRegistrationDetails<T> dataSetDetails) + { + IDataStoreStrategy strategy = + registratorState.getDataStrategyStore().getDataStoreStrategy( + dataSetDetails.getDataSetInformation(), dataSetFile); + + TopLevelDataSetRegistratorGlobalState globalContext = registratorState.getGlobalState(); + DataSetStorageAlgorithm<T> algorithm = + new DataSetStorageAlgorithm<T>(dataSetFile, dataSetDetails, strategy, + registratorState.getStorageProcessor(), + globalContext.getDataSetValidator(), globalContext.getDssCode(), + registratorState.getFileOperations(), globalContext.getMailClient()); + return algorithm; + } + + public <T extends DataSetInformation> IEntityRegistrationService<T> getEntityRegistrationService() { - return new DataSetRegistrationService(this); + return new DefaultEntityRegistrationService<T>(registrator); } private DataSetRegistrationAlgorithm createRegistrationAlgorithm(File incomingDataSetFile, @@ -234,12 +230,28 @@ public class DataSetRegistrationService implements IRollbackDelegate details.getDataSetInformation())); } - public void rollback(DataSetRegistrationAlgorithm algorithm, Throwable ex) + /** + * If a transaction is hanging around, commit it + */ + private void commitExtantTransaction() { - registrator.rollback(this, algorithm, ex); + if (null != liveTransactionOrNull + && false == liveTransactionOrNull.isCommittedOrRolledback()) + { + // Commit the existing transaction + liveTransactionOrNull.commit(); + } + } + + private void rollbackExtantTransaction() + { + if (null != liveTransactionOrNull) + { + liveTransactionOrNull.rollback(); + } } - protected static class DefaultApplicationServerRegistrator implements + private static class DefaultApplicationServerRegistrator implements IDataSetInApplicationServerRegistrator { private final AbstractOmniscientTopLevelDataSetRegistrator registrator; @@ -260,23 +272,4 @@ public class DataSetRegistrationService implements IRollbackDelegate } } - protected static class NoOpCleanAfterwardsAction implements IDelegatedActionWithResult<Boolean> - { - public Boolean execute() - { - return true; // do nothing - } - } - - /** - * If a transaction is hanging around, commit it - */ - private void commitExtantTransaction() - { - if (null != liveTransactionOrNull) - { - // Commit the existing transaction - liveTransactionOrNull.commit(); - } - } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithm.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithm.java new file mode 100644 index 00000000000..bcb1f74068b --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithm.java @@ -0,0 +1,603 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.etlserver.registrator; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.time.StopWatch; +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked; +import ch.systemsx.cisd.common.Constants; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.filesystem.IFileOperations; +import ch.systemsx.cisd.common.logging.Log4jSimpleLogger; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.common.types.BooleanOrUnknown; +import ch.systemsx.cisd.etlserver.BaseDirectoryHolder; +import ch.systemsx.cisd.etlserver.DataStoreStrategyKey; +import ch.systemsx.cisd.etlserver.FileRenamer; +import ch.systemsx.cisd.etlserver.IDataStoreStrategy; +import ch.systemsx.cisd.etlserver.IStorageProcessor; +import ch.systemsx.cisd.etlserver.IStorageProcessor.UnstoreDataAction; +import ch.systemsx.cisd.etlserver.TransferredDataSetHandler; +import ch.systemsx.cisd.etlserver.validation.IDataSetValidator; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType; +import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData; +import ch.systemsx.cisd.openbis.generic.shared.dto.StorageFormat; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier; + +/** + * DataSetStorageAlgorithm is a state machine that executes steps to store a data set in the store + * and transitions between states. + * <p> + * The states and transitions are as follows: <br> + * + * <pre> + * /-> (Committed*) + * (Initialized) -> (Prepared) -> (Stored) -< + * \ \-> (Rolledback) -> (UndoneState*) + * \ -------------------------------------/ + * </pre> + * + * States marked with a "*" are terminal. + * <p> + * N.b. Methods invoked on states for which the method is not valid will yield a class cast + * exception since the state object will not be castable to the desired class. + * + * @author Chandrasekhar Ramakrishnan + */ +public class DataSetStorageAlgorithm<T extends DataSetInformation> +{ + static private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + DataSetStorageAlgorithm.class); + + private final File incomingDataSetFile; + + private final DataSetRegistrationDetails<T> registrationDetails; + + private final T dataSetInformation; + + private final IDataStoreStrategy dataStoreStrategy; + + private final IStorageProcessor storageProcessor; + + private final String dataStoreCode; + + private final DataSetType dataSetType; + + private final File storeRoot; + + private final IFileOperations fileOperations; + + // Used by the storage processor + private final IMailClient mailClient; + + // State that changes during execution + private DataSetStorageAlgorithmState<T> state; + + public DataSetStorageAlgorithm(File incomingDataSetFile, + DataSetRegistrationDetails<T> registrationDetails, + IDataStoreStrategy dataStoreStrategy, IStorageProcessor storageProcessor, + IDataSetValidator dataSetValidator, String dataStoreCode, + IFileOperations fileOperations, IMailClient mailClient) + { + this.incomingDataSetFile = incomingDataSetFile; + this.registrationDetails = registrationDetails; + this.dataSetInformation = registrationDetails.getDataSetInformation(); + this.dataStoreStrategy = dataStoreStrategy; + this.storageProcessor = storageProcessor; + this.dataStoreCode = dataStoreCode; + this.fileOperations = fileOperations; + this.mailClient = mailClient; + + this.storeRoot = storageProcessor.getStoreRootDirectory(); + this.dataSetType = registrationDetails.getDataSetType(); + + if (dataStoreStrategy.getKey() != DataStoreStrategyKey.IDENTIFIED) + { + throw new UserFailureException("Data set must be associated with an experiment "); + } + + dataSetValidator.assertValidDataSet(dataSetType, incomingDataSetFile); + + state = new InitializedState<T>(this); + } + + /** + * Prepare registration of a data set. + */ + public void prepare() + { + InitializedState<T> initializedState = (InitializedState<T>) state; + initializedState.prepare(); + + state = new PreparedState<T>(initializedState); + } + + /** + * Run the storage processor. + */ + public void runStorageProcessor() throws Throwable + { + PreparedState<T> preparedState = (PreparedState<T>) state; + preparedState.storeData(); + + state = new StoredState<T>(preparedState); + } + + /** + * Ask the storage processor to rollback. Used by clients of the algorithm. + */ + public void rollbackStorageProcessor(Throwable throwable) + { + // Rollback may be called on in the stored state or in the prepared state. In the prepared + // state, there is nothing to do. + if (state instanceof PreparedState) + { + return; + } + + StoredState<T> storedState = (StoredState<T>) state; + UnstoreDataAction action = storedState.rollbackStorageProcessor(throwable); + + state = new RolledbackState<T>(storedState, action, throwable); + } + + public void executeUndoStoreAction() + { + // Rollback may be called on in the stored state or in the prepared state. In the prepared + // state, there is nothing to do. + if (state instanceof PreparedState) + { + state = new UndoneState<T>((PreparedState<T>) state); + return; + } + + RolledbackState<T> rolledbackState = (RolledbackState<T>) state; + rolledbackState.executeUndoAction(); + + state = new UndoneState<T>(rolledbackState); + } + + /** + * Ask the storage processor to commit. Used by clients of the algorithm. + */ + public void commitStorageProcessor() + { + StoredState<T> storedState = (StoredState<T>) state; + storedState.commitStorageProcessor(); + + state = new CommittedState<T>(storedState); + } + + public File getIncomingDataSetFile() + { + return incomingDataSetFile; + } + + public T getDataSetInformation() + { + return dataSetInformation; + } + + protected Logger getOperationLog() + { + return operationLog; + } + + public NewExternalData createExternalData() + { + final NewExternalData data = new NewExternalData(); + data.setUserId(dataSetInformation.getUploadingUserIdOrNull()); + data.setUserEMail(dataSetInformation.tryGetUploadingUserEmail()); + data.setExtractableData(dataSetInformation.getExtractableData()); + data.setLocatorType(registrationDetails.getLocatorType()); + data.setDataSetType(registrationDetails.getDataSetType()); + data.setFileFormatType(registrationDetails.getFileFormatType()); + data.setMeasured(registrationDetails.isMeasuredData()); + data.setDataStoreCode(dataStoreCode); + + File dataFile = ((StoredState<T>) state).getDataFile(); + + final String relativePath = FileUtilities.getRelativeFile(storeRoot, dataFile); + String absolutePath = dataFile.getAbsolutePath(); + assert relativePath != null : String.format( + TransferredDataSetHandler.TARGET_NOT_RELATIVE_TO_STORE_ROOT, absolutePath, + storeRoot.getAbsolutePath()); + final StorageFormat storageFormat = storageProcessor.getStorageFormat(); + final BooleanOrUnknown isCompleteFlag = dataSetInformation.getIsCompleteFlag(); + + data.setComplete(isCompleteFlag); + data.setLocation(relativePath); + data.setStorageFormat(storageFormat); + + return data; + } + + public String getSuccessRegistrationMessage() + { + final StringBuilder buffer = new StringBuilder(); + String userID = dataSetInformation.getUploadingUserIdOrNull(); + String userEMail = dataSetInformation.tryGetUploadingUserEmail(); + if (userID != null || userEMail != null) + { + appendNameAndObject(buffer, "User", userID == null ? userEMail : userID); + } + appendNameAndObject(buffer, "Data Set Code", dataSetInformation.getDataSetCode()); + appendNameAndObject(buffer, "Data Set Type", dataSetType.getCode()); + appendNameAndObject(buffer, "Experiment Identifier", + dataSetInformation.getExperimentIdentifier()); + appendNameAndObject(buffer, "Sample Identifier", dataSetInformation.getSampleIdentifier()); + appendNameAndObject(buffer, "Producer Code", dataSetInformation.getProducerCode()); + appendNameAndObject(buffer, "Production Date", + formatDate(dataSetInformation.getProductionDate())); + final List<String> parentDataSetCodes = dataSetInformation.getParentDataSetCodes(); + if (parentDataSetCodes.isEmpty() == false) + { + appendNameAndObject(buffer, "Parent Data Sets", + StringUtils.join(parentDataSetCodes, ' ')); + } + appendNameAndObject(buffer, "Is complete", dataSetInformation.getIsCompleteFlag()); + buffer.setLength(buffer.length() - 1); + return buffer.toString(); + } + + public String getFailureRegistrationMessage() + { + return "Error when trying to register data set '" + incomingDataSetFile.getName() + "'."; + } + + private String formatDate(Date productionDate) + { + return productionDate == null ? "" : Constants.DATE_FORMAT.get().format(productionDate); + } + + private final void appendNameAndObject(final StringBuilder buffer, final String name, + final Object object) + { + if (object != null) + { + buffer.append(name).append("::").append(object).append(";"); + } + } + + protected DataSetType getDataSetType() + { + return dataSetType; + } + + protected IDataStoreStrategy getDataStoreStrategy() + { + return dataStoreStrategy; + } + + protected File getStoreRoot() + { + return storeRoot; + } + + protected IStorageProcessor getStorageProcessor() + { + return storageProcessor; + } + + protected DataSetRegistrationDetails<T> getRegistrationDetails() + { + return registrationDetails; + } + + private static abstract class DataSetStorageAlgorithmState<T extends DataSetInformation> + { + protected final DataSetStorageAlgorithm<T> storageAlgorithm; + + protected final File incomingDataSetFile; + + protected DataSetStorageAlgorithmState(DataSetStorageAlgorithm<T> storageAlgorithm) + { + this.storageAlgorithm = storageAlgorithm; + this.incomingDataSetFile = storageAlgorithm.getIncomingDataSetFile(); + } + + protected Logger getOperationLog() + { + return storageAlgorithm.getOperationLog(); + } + + protected IFileOperations getFileOperations() + { + return storageAlgorithm.fileOperations; + } + + protected final File createBaseDirectory(final IDataStoreStrategy strategy, + final File baseDir, final DataSetInformation dataSetInfo) + { + final File baseDirectory = + strategy.getBaseDirectory(baseDir, dataSetInfo, + storageAlgorithm.getDataSetType()); + baseDirectory.mkdirs(); + if (getFileOperations().isDirectory(baseDirectory) == false) + { + throw EnvironmentFailureException.fromTemplate( + "Creating data set base directory '%s' for data set '%s' failed.", + baseDirectory.getAbsolutePath(), incomingDataSetFile); + } + return baseDirectory; + } + } + + private static class InitializedState<T extends DataSetInformation> extends + DataSetStorageAlgorithmState<T> + { + protected BaseDirectoryHolder baseDirectoryHolder; + + public InitializedState(DataSetStorageAlgorithm<T> storageAlgorithm) + { + super(storageAlgorithm); + } + + /** + * Prepare registration of a data set. + */ + public void prepare() + { + IDataStoreStrategy dataStoreStrategy = storageAlgorithm.getDataStoreStrategy(); + final File baseDirectory = + createBaseDirectory(dataStoreStrategy, storageAlgorithm.getStoreRoot(), + storageAlgorithm.getDataSetInformation()); + baseDirectoryHolder = + new BaseDirectoryHolder(dataStoreStrategy, baseDirectory, incomingDataSetFile); + } + } + + private static class PreparedState<T extends DataSetInformation> extends + DataSetStorageAlgorithmState<T> + { + protected final BaseDirectoryHolder baseDirectoryHolder; + + protected final IStorageProcessor storageProcessor; + + protected final DataSetInformation dataSetInformation; + + protected File markerFile; + + protected File dataFile; + + public PreparedState(InitializedState<T> oldState) + { + super(oldState.storageAlgorithm); + this.storageProcessor = storageAlgorithm.getStorageProcessor(); + this.baseDirectoryHolder = oldState.baseDirectoryHolder; + this.dataSetInformation = storageAlgorithm.getDataSetInformation(); + } + + public void storeData() + { + markerFile = createProcessingMarkerFile(); + String entityDescription = createEntityDescription(); + if (getOperationLog().isInfoEnabled()) + { + getOperationLog().info("Start storing data set for " + entityDescription + "."); + } + final StopWatch watch = new StopWatch(); + watch.start(); + + dataFile = + storageProcessor.storeData(storageAlgorithm.getDataSetInformation(), + storageAlgorithm.getRegistrationDetails(), getMailClient(), + incomingDataSetFile, baseDirectoryHolder.getBaseDirectory()); + if (getOperationLog().isInfoEnabled()) + { + getOperationLog().info( + "Finished storing data set for " + entityDescription + ", took " + watch); + } + assert dataFile != null : "The folder that contains the stored data should not be null."; + } + + private IMailClient getMailClient() + { + return storageAlgorithm.mailClient; + } + + private final File createProcessingMarkerFile() + { + final File baseDirectory = baseDirectoryHolder.getBaseDirectory(); + final File baseParentDirectory = baseDirectory.getParentFile(); + final String processingDirName = baseDirectory.getName(); + markerFile = + new File(baseParentDirectory, Constants.PROCESSING_PREFIX + processingDirName); + try + { + getFileOperations().createNewFile(markerFile); + } catch (final IOExceptionUnchecked ex) + { + throw EnvironmentFailureException.fromTemplate(ex, + "Cannot create marker file '%s'.", markerFile.getPath()); + } + return markerFile; + } + + private String createEntityDescription() + { + SampleIdentifier sampleIdentifier = dataSetInformation.getSampleIdentifier(); + if (sampleIdentifier != null) + { + return "sample '" + sampleIdentifier + "'"; + } + return "experiment '" + dataSetInformation.getExperimentIdentifier() + "'"; + } + } + + private static class StoredState<T extends DataSetInformation> extends + DataSetStorageAlgorithmState<T> + { + protected final IStorageProcessor storageProcessor; + + protected final BaseDirectoryHolder baseDirectoryHolder; + + protected final File markerFile; + + protected final File dataFile; + + public StoredState(PreparedState<T> oldState) + { + super(oldState.storageAlgorithm); + this.storageProcessor = storageAlgorithm.getStorageProcessor(); + this.baseDirectoryHolder = oldState.baseDirectoryHolder; + + this.markerFile = oldState.markerFile; + this.dataFile = oldState.dataFile; + } + + protected File getDataFile() + { + return dataFile; + } + + /** + * Ask the storage processor to rollback. Used by clients of the algorithm. + */ + public UnstoreDataAction rollbackStorageProcessor(final Throwable throwable) + { + UnstoreDataAction action = + storageProcessor.rollback(incomingDataSetFile, + baseDirectoryHolder.getBaseDirectory(), throwable); + cleanUp(); + return action; + } + + /** + * Ask the storage processor to commit. Used by clients of the algorithm. + */ + public void commitStorageProcessor() + { + storageProcessor.commit(incomingDataSetFile, baseDirectoryHolder.getBaseDirectory()); + cleanUp(); + } + + /** + * Cleanup from the processing -- done after a commit or rollback + */ + private void cleanUp() + { + getFileOperations().delete(markerFile); + } + } + + private static class CommittedState<T extends DataSetInformation> extends + DataSetStorageAlgorithmState<T> + { + + CommittedState(StoredState<T> oldState) + { + super(oldState.storageAlgorithm); + } + + } + + private static class RolledbackState<T extends DataSetInformation> extends + DataSetStorageAlgorithmState<T> + { + private final UnstoreDataAction action; + + private final Throwable throwable; + + private final File storeRoot; + + private BaseDirectoryHolder baseDirectoryHolder; + + public RolledbackState(StoredState<T> oldState, UnstoreDataAction action, + Throwable throwable) + { + super(oldState.storageAlgorithm); + this.action = action; + this.throwable = throwable; + + this.storeRoot = oldState.storageProcessor.getStoreRootDirectory(); + + this.baseDirectoryHolder = oldState.baseDirectoryHolder; + } + + public void executeUndoAction() + { + if (action == UnstoreDataAction.MOVE_TO_ERROR) + { + final File baseDirectory = + createBaseDirectory(TransferredDataSetHandler.ERROR_DATA_STRATEGY, + storeRoot, storageAlgorithm.getDataSetInformation()); + baseDirectoryHolder = + new BaseDirectoryHolder(TransferredDataSetHandler.ERROR_DATA_STRATEGY, + baseDirectory, incomingDataSetFile); + FileRenamer.renameAndLog(incomingDataSetFile, baseDirectoryHolder.getTargetFile()); + writeThrowable(); + } else if (action == UnstoreDataAction.DELETE) + { + FileUtilities.deleteRecursively(incomingDataSetFile, new Log4jSimpleLogger( + getOperationLog())); + } + } + + private void writeThrowable() + { + final String fileName = incomingDataSetFile.getName() + ".exception"; + final File file = + new File(baseDirectoryHolder.getTargetFile().getParentFile(), fileName); + FileWriter writer = null; + try + { + writer = new FileWriter(file); + throwable.printStackTrace(new PrintWriter(writer)); + } catch (final IOException e) + { + getOperationLog().warn( + String.format("Could not write out the exception '%s' in file '%s'.", + fileName, file.getAbsolutePath()), e); + } finally + { + IOUtils.closeQuietly(writer); + } + } + } + + private static class UndoneState<T extends DataSetInformation> extends + DataSetStorageAlgorithmState<T> + { + + UndoneState(PreparedState<T> oldState) + { + super(oldState.storageAlgorithm); + } + + UndoneState(RolledbackState<T> oldState) + { + super(oldState.storageAlgorithm); + } + + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithmRunner.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithmRunner.java new file mode 100644 index 00000000000..7cb63dba030 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DataSetStorageAlgorithmRunner.java @@ -0,0 +1,288 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.etlserver.registrator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.base.exceptions.InterruptedExceptionUnchecked; +import ch.systemsx.cisd.common.exceptions.HighLevelException; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation; + +/** + * An algorithm that implements the logic running many data set storage algorithms in one logical + * transaction. + * + * @author Chandrasekhar Ramakrishnan + */ +public class DataSetStorageAlgorithmRunner<T extends DataSetInformation> +{ + + public static interface IRollbackDelegate<T extends DataSetInformation> + { + public void rollback(DataSetStorageAlgorithmRunner<T> algorithm, Throwable ex); + } + + /** + * Interface for code that is run to register a new data set. + * + * @author Chandrasekhar Ramakrishnan + */ + public static interface IDataSetInApplicationServerRegistrator<T extends DataSetInformation> + { + public void registerDataSetsInApplicationServer(List<DataSetRegistrationInformation<T>> data) + throws Throwable; + } + + static private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + DataSetStorageAlgorithmRunner.class); + + public static final String DATA_SET_REGISTRATION_FAILURE_TEMPLATE = + "Registration of data set '%s' failed."; + + public static final String DATA_SET_STORAGE_FAILURE_TEMPLATE = "Storing data set '%s' failed."; + + public static final String SUCCESSFULLY_REGISTERED = "Successfully registered data set: ["; + + private final ArrayList<DataSetStorageAlgorithm<T>> dataSetStorageAlgorithms; + + private final IDataSetInApplicationServerRegistrator<T> applicationServerRegistrator; + + private final IRollbackDelegate<T> rollbackDelegate; + + public DataSetStorageAlgorithmRunner(IEncapsulatedOpenBISService openBisService, + List<DataSetStorageAlgorithm<T>> dataSetStorageAlgorithms, + IRollbackDelegate<T> rollbackDelegate) + { + this(dataSetStorageAlgorithms, rollbackDelegate, + new DefaultApplicationServerRegistrator<T>(openBisService)); + } + + public DataSetStorageAlgorithmRunner(List<DataSetStorageAlgorithm<T>> dataSetStorageAlgorithms, + IRollbackDelegate<T> rollbackDelegate, + IDataSetInApplicationServerRegistrator<T> applicationServerRegistrator) + { + this.dataSetStorageAlgorithms = + new ArrayList<DataSetStorageAlgorithm<T>>(dataSetStorageAlgorithms); + this.rollbackDelegate = rollbackDelegate; + this.applicationServerRegistrator = applicationServerRegistrator; + } + + /** + * Prepare registration of a data set. + */ + public final void prepare() + { + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + storageAlgorithm.prepare(); + } + } + + /** + * Register the data sets. + */ + public List<DataSetInformation> runStorageAlgorithms() + { + try + { + // Runs or throws a throwable + runStorageProcessors(); + + } catch (final HighLevelException ex) + { + rollbackDuringStorageProcessorRun(ex); + return Collections.emptyList(); + } catch (final Throwable throwable) + { + rollbackDuringStorageProcessorRun(throwable); + return Collections.emptyList(); + } + + try + { + // Runs or throw a throwable + registerDataSetsInApplicationServer(); + + } catch (final HighLevelException ex) + { + rollbackDuringMetadataRegistration(ex); + return Collections.emptyList(); + } catch (final Throwable throwable) + { + rollbackDuringMetadataRegistration(throwable); + return Collections.emptyList(); + } + + try + { + // Should always succeed + commitStorageProcessors(); + + logSuccessfulRegistration(); + + ArrayList<DataSetInformation> dataSetInformationCollection = + new ArrayList<DataSetInformation>(); + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + dataSetInformationCollection.add(storageAlgorithm.getDataSetInformation()); + + } + + return dataSetInformationCollection; + } catch (final Throwable throwable) + { + // Something has gone really wrong + rollbackAfterStorageProcessorAndMetadataRegistration(throwable); + return Collections.emptyList(); + } + } + + public List<DataSetInformation> prepareAndRunStorageAlgorithms() + { + prepare(); + return runStorageAlgorithms(); + } + + private void rollbackDuringStorageProcessorRun(Throwable ex) + { + rollbackStorageProcessors(ex); + rollbackDelegate.rollback(this, ex); + } + + private void rollbackDuringMetadataRegistration(Throwable ex) + { + rollbackStorageProcessors(ex); + rollbackDelegate.rollback(this, ex); + } + + private void rollbackAfterStorageProcessorAndMetadataRegistration(Throwable ex) + { + rollbackStorageProcessors(ex); + rollbackDelegate.rollback(this, ex); + } + + private void commitStorageProcessors() + { + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + storageAlgorithm.commitStorageProcessor(); + + } + } + + private void registerDataSetsInApplicationServer() throws Throwable + { + ArrayList<DataSetRegistrationInformation<T>> registrationData = + new ArrayList<DataSetRegistrationInformation<T>>(); + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + registrationData.add(new DataSetRegistrationInformation<T>(storageAlgorithm + .getDataSetInformation(), storageAlgorithm.createExternalData())); + + } + applicationServerRegistrator.registerDataSetsInApplicationServer(registrationData); + } + + private void runStorageProcessors() throws Throwable + { + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + storageAlgorithm.runStorageProcessor(); + } + } + + private void rollbackStorageProcessors(Throwable ex) + { + // Errors which are not AssertionErrors leave the system in a state that we don't + // know and can't trust. Thus we will not perform any operations any more in this + // case. + if (ex instanceof Error && ex instanceof AssertionError == false) + { + return; + } + + // Don't rollback when this exception happens + boolean stopped = ex instanceof InterruptedExceptionUnchecked; + + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + storageAlgorithm.rollbackStorageProcessor(ex); + + if (stopped == false) + { + storageAlgorithm.executeUndoStoreAction(); + } + } + } + + private static class DefaultApplicationServerRegistrator<T extends DataSetInformation> + implements IDataSetInApplicationServerRegistrator<T> + { + private final IEncapsulatedOpenBISService openBisService; + + DefaultApplicationServerRegistrator(IEncapsulatedOpenBISService openBisService) + { + this.openBisService = openBisService; + } + + public void registerDataSetsInApplicationServer(List<DataSetRegistrationInformation<T>> data) + throws Throwable + { + for (DataSetRegistrationInformation<T> datum : data) + { + openBisService.registerDataSet(datum.getDataSetInformation(), + datum.getExternalData()); + } + } + + } + + private void logSuccessfulRegistration() + { + if (getOperationLog().isInfoEnabled()) + { + String msg = getSuccessRegistrationMessage(); + getOperationLog().info(msg); + } + } + + private final String getSuccessRegistrationMessage() + { + final StringBuilder buffer = new StringBuilder(); + + for (DataSetStorageAlgorithm<T> storageAlgorithm : dataSetStorageAlgorithms) + { + buffer.append(SUCCESSFULLY_REGISTERED); + buffer.append(storageAlgorithm.getSuccessRegistrationMessage()); + buffer.append(']'); + } + return buffer.toString(); + } + + private Logger getOperationLog() + { + return operationLog; + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityRegistrationService.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityRegistrationService.java new file mode 100644 index 00000000000..2e8f3f0f75a --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/DefaultEntityRegistrationService.java @@ -0,0 +1,79 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.etlserver.registrator; + +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityRegistrationDetails; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityRegistrationResult; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory; + +public class DefaultEntityRegistrationService<T extends DataSetInformation> implements + IEntityRegistrationService<T> +{ + private final AbstractOmniscientTopLevelDataSetRegistrator registrator; + + public DefaultEntityRegistrationService(AbstractOmniscientTopLevelDataSetRegistrator registrator) + { + this.registrator = registrator; + } + + public AtomicEntityRegistrationResult registerEntitiesInApplcationServer( + AtomicEntityRegistrationDetails<T> registrationDetails) + { + // Arrays to hold return values + ArrayList<Experiment> experimentsUpdated = new ArrayList<Experiment>(); + ArrayList<Experiment> experimentsCreated = new ArrayList<Experiment>(); + ArrayList<Sample> samplesUpdated = new ArrayList<Sample>(); + ArrayList<Sample> samplesCreated = new ArrayList<Sample>(); + ArrayList<DataSetInformation> dataSetsCreated = new ArrayList<DataSetInformation>(); + + IEncapsulatedOpenBISService openBisService = + registrator.getGlobalState().getOpenBisService(); + List<NewExperiment> experimentRegistrations = + registrationDetails.getExperimentRegistrations(); + for (NewExperiment experiment : experimentRegistrations) + { + openBisService.registerExperiment(experiment); + ExperimentIdentifier experimentIdentifier = + new ExperimentIdentifierFactory(experiment.getIdentifier()).createIdentifier(); + experimentsCreated.add(openBisService.tryToGetExperiment(experimentIdentifier)); + } + + List<DataSetRegistrationInformation<T>> dataSetRegistrations = + registrationDetails.getDataSetRegistrations(); + for (DataSetRegistrationInformation<T> dataSetRegistration : dataSetRegistrations) + { + openBisService.registerDataSet(dataSetRegistration.getDataSetInformation(), + dataSetRegistration.getExternalData()); + dataSetsCreated.add(dataSetRegistration.getDataSetInformation()); + } + + return new AtomicEntityRegistrationResult(experimentsUpdated, experimentsCreated, + samplesUpdated, samplesCreated, dataSetsCreated); + + } + +} \ No newline at end of file diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IEntityRegistrationService.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IEntityRegistrationService.java new file mode 100644 index 00000000000..9bdef2e8746 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/IEntityRegistrationService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.etlserver.registrator; + +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityRegistrationDetails; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityRegistrationResult; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; + +/** + * The inteface implemented by a service that can register openBIS entities atomically. + * + * @author Chandrasekhar Ramakrishnan + */ +public interface IEntityRegistrationService<T extends DataSetInformation> +{ + AtomicEntityRegistrationResult registerEntitiesInApplcationServer( + AtomicEntityRegistrationDetails<T> registrationDetails); +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java index b591a59b43a..193a465014e 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java @@ -244,7 +244,7 @@ public class JythonTopLevelDataSetHandler extends AbstractOmniscientTopLevelData dataSetInfo.setInstanceCode(registratorState.getHomeDatabaseInstance().getCode()); dataSetInfo.setInstanceUUID(registratorState.getHomeDatabaseInstance().getUuid()); } - + /** * Adaptor for the IDataSetRegistrationDetailsFactory interface. */ @@ -282,22 +282,9 @@ public class JythonTopLevelDataSetHandler extends AbstractOmniscientTopLevelData this.interpreter = interpreter; } - public JythonDataSetRegistrationService(JythonDataSetRegistrationService other) - { - super(other); - interpreter = other.interpreter; - } - public PythonInterpreter getInterpreter() { return interpreter; } - - @Override - protected JythonDataSetRegistrationService createSubService() - { - return new JythonDataSetRegistrationService(this); - } - } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java new file mode 100644 index 00000000000..1b8955b63a7 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/AbstractTransactionState.java @@ -0,0 +1,376 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.etlserver.registrator.api.v1.impl; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.etlserver.registrator.DataSetRegistrationDetails; +import ch.systemsx.cisd.etlserver.registrator.DataSetRegistrationService; +import ch.systemsx.cisd.etlserver.registrator.DataSetStorageAlgorithm; +import ch.systemsx.cisd.etlserver.registrator.DataSetStorageAlgorithmRunner; +import ch.systemsx.cisd.etlserver.registrator.IDataSetRegistrationDetailsFactory; +import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSet; +import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperiment; +import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample; +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityRegistrationDetails; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample; +import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO; +import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO; + +/** + * Abstract superclass for the states a DataSetRegistrationTransaction can be in. + * + * @author Chandrasekhar Ramakrishnan + */ +abstract class AbstractTransactionState<T extends DataSetInformation> +{ + protected final DataSetRegistrationTransaction<T> parent; + + protected AbstractTransactionState(DataSetRegistrationTransaction<T> parent) + { + this.parent = parent; + } + + public abstract boolean isCommitted(); + + public abstract boolean isRolledback(); + + /** + * The state where the transaction is still modifyiable. + * + * @author Chandrasekhar Ramakrishnan + */ + static class LiveTransactionState<T extends DataSetInformation> extends + AbstractTransactionState<T> + { + // Keeps track of steps that have been executed and may need to be reverted. Elements are + // kept in the order they need to be reverted. + private final RollbackStack rollbackStack; + + // The directory to use as "local" for paths + private final File workingDirectory; + + // The directory in which new data sets get staged + private final File stagingDirectory; + + // The registration service that owns this transaction + private final DataSetRegistrationService registrationService; + + // The interface to openBIS + private final IEncapsulatedOpenBISService openBisService; + + private final IDataSetRegistrationDetailsFactory<T> registrationDetailsFactory; + + private final ArrayList<DataSet<T>> registeredDataSets = new ArrayList<DataSet<T>>(); + + private final List<Experiment> experimentsToBeRegistered = new ArrayList<Experiment>(); + + public LiveTransactionState(DataSetRegistrationTransaction<T> parent, + RollbackStack rollbackStack, File workingDirectory, File stagingDirectory, + DataSetRegistrationService registrationService, + IDataSetRegistrationDetailsFactory<T> registrationDetailsFactory) + { + super(parent); + this.rollbackStack = rollbackStack; + this.workingDirectory = workingDirectory; + this.stagingDirectory = stagingDirectory; + this.registrationService = registrationService; + this.openBisService = + this.registrationService.getRegistratorState().getGlobalState() + .getOpenBisService(); + this.registrationDetailsFactory = registrationDetailsFactory; + } + + public IDataSet createNewDataSet() + { + // Create registration details for the new data set + DataSetRegistrationDetails<T> registrationDetails = + registrationDetailsFactory.createDataSetRegistrationDetails(); + + return createNewDataSet(registrationDetails); + } + + public IDataSet createNewDataSet(DataSetRegistrationDetails<T> registrationDetails) + { + // Request a code, so we can keep the staging file name and the data set code in sync + String dataSetCode = registrationDetails.getDataSetInformation().getDataSetCode(); + if (null == dataSetCode) + { + dataSetCode = generateDataSetCode(registrationDetails); + registrationDetails.getDataSetInformation().setDataSetCode(dataSetCode); + } + + // Create a directory for the data set + File stagingFolder = new File(stagingDirectory, dataSetCode); + MkdirsCommand cmd = new MkdirsCommand(stagingFolder.getAbsolutePath()); + executeCommand(cmd); + + DataSet<T> dataSet = + registrationDetailsFactory.createDataSet(registrationDetails, stagingFolder); + registeredDataSets.add(dataSet); + return dataSet; + } + + public ISample getSampleForUpdate(String sampleIdentifierString) + { + // TODO Auto-generated method stub + return null; + } + + public ISample createNewSample(String sampleIdentifierString) + { + // TODO Auto-generated method stub + return null; + } + + public IExperiment getExperimentForUpdate(String experimentIdentifierString) + { + // TODO Auto-generated method stub + return null; + } + + public IExperiment createNewExperiment(String experimentIdentifierString) + { + String permID = openBisService.createDataSetCode(); + Experiment experiment = new Experiment(experimentIdentifierString, permID); + experimentsToBeRegistered.add(experiment); + return experiment; + } + + public String moveFile(String src, IDataSet dst) + { + File srcFile = new File(src); + return moveFile(src, dst, srcFile.getName()); + } + + public String moveFile(String src, IDataSet dst, String dstInDataset) + { + @SuppressWarnings("unchecked") + DataSet<T> dataSet = (DataSet<T>) dst; + + // See if this is an absolute path + File srcFile = new File(src); + if (false == srcFile.exists()) + { + // Try it relative + srcFile = new File(workingDirectory, src); + } + + File dataSetFolder = dataSet.getDataSetStagingFolder(); + File dstFile = new File(dataSetFolder, dstInDataset); + + FileUtilities.checkInputFile(srcFile); + + MoveFileCommand cmd = + new MoveFileCommand(srcFile.getParentFile().getAbsolutePath(), + srcFile.getName(), dstFile.getParentFile().getAbsolutePath(), + dstFile.getName()); + executeCommand(cmd); + return dstFile.getAbsolutePath(); + } + + public String createNewDirectory(IDataSet dst, String dirName) + { + @SuppressWarnings("unchecked") + DataSet<T> dataSet = (DataSet<T>) dst; + File dataSetFolder = dataSet.getDataSetStagingFolder(); + File dstFile = new File(dataSetFolder, dirName); + MkdirsCommand cmd = new MkdirsCommand(dstFile.getAbsolutePath()); + executeCommand(cmd); + return dstFile.getAbsolutePath(); + } + + public String createNewFile(IDataSet dst, String fileName) + { + return createNewFile(dst, "/", fileName); + } + + public String createNewFile(IDataSet dst, String dstInDataset, String fileName) + { + @SuppressWarnings("unchecked") + DataSet<T> dataSet = (DataSet<T>) dst; + File dataSetFolder = dataSet.getDataSetStagingFolder(); + File dstFolder = new File(dataSetFolder, dstInDataset); + File dstFile = new File(dstFolder, fileName); + NewFileCommand cmd = new NewFileCommand(dstFile.getAbsolutePath()); + executeCommand(cmd); + return dstFile.getAbsolutePath(); + } + + public void deleteFile(String src) + { + // TODO Auto-generated method stub + + } + + /** + * Commit the transaction + */ + public void commit() + { + ArrayList<DataSetStorageAlgorithm<T>> algorithms = + new ArrayList<DataSetStorageAlgorithm<T>>(registeredDataSets.size()); + for (DataSet<T> dataSet : registeredDataSets) + { + File contents = dataSet.getDataSetContents(); + DataSetRegistrationDetails<T> details = dataSet.getRegistrationDetails(); + registrationService.getRegistratorState().getDataStrategyStore() + .getDataStoreStrategy(details.getDataSetInformation(), contents); + algorithms.add(registrationService.createStorageAlgorithm(contents, details)); + } + + DataSetStorageAlgorithmRunner<T> runner = + new DataSetStorageAlgorithmRunner<T>(algorithms, parent, parent); + runner.prepareAndRunStorageAlgorithms(); + } + + /** + * Rollback any commands that have been executed. Rollback is done in the reverse order of + * execution. + */ + public void rollback() + { + rollbackStack.rollbackAll(); + registeredDataSets.clear(); + } + + /** + * Execute the command and add it to the list of commands that have been executed. + */ + private void executeCommand(ITransactionalCommand cmd) + { + rollbackStack.pushAndExecuteCommand(cmd); + } + + /** + * Generate a data set code for the registration details. Just calls openBisService to get a + * data set code by default. + * + * @return A data set code + */ + private String generateDataSetCode(DataSetRegistrationDetails<T> registrationDetails) + { + return openBisService.createDataSetCode(); + } + + AtomicEntityRegistrationDetails<T> createRegistrationDetails( + List<DataSetRegistrationInformation<T>> dataSetRegistrations) + { + ArrayList<NewExperiment> experimentRegistrations = new ArrayList<NewExperiment>(); + ArrayList<ExperimentUpdatesDTO> experimentUpdates = + new ArrayList<ExperimentUpdatesDTO>(); + ArrayList<SampleUpdatesDTO> sampleUpdates = new ArrayList<SampleUpdatesDTO>(); + ArrayList<NewSample> sampleRegistrations = new ArrayList<NewSample>(); + + AtomicEntityRegistrationDetails<T> registrationDetails = + new AtomicEntityRegistrationDetails<T>(experimentUpdates, + experimentRegistrations, sampleUpdates, sampleRegistrations, + dataSetRegistrations); + return registrationDetails; + } + + @Override + public boolean isCommitted() + { + return false; + } + + @Override + public boolean isRolledback() + { + return false; + } + } + + private static abstract class TerminalTransactionState<T extends DataSetInformation> extends + AbstractTransactionState<T> + { + private final LiveTransactionState<T> liveState; + + protected TerminalTransactionState(LiveTransactionState<T> liveState) + { + super(liveState.parent); + this.liveState = liveState; + deleteStagingFolders(); + this.liveState.rollbackStack.discard(); + } + + private void deleteStagingFolders() + { + for (DataSet<T> dataSet : liveState.registeredDataSets) + { + dataSet.getDataSetStagingFolder().delete(); + } + } + + } + + /** + * State where the transaction has been committed. + * + * @author Chandrasekhar Ramakrishnan + */ + static class CommitedTransactionState<T extends DataSetInformation> extends + TerminalTransactionState<T> + { + + public CommitedTransactionState(LiveTransactionState<T> liveState) + { + super(liveState); + } + + @Override + public boolean isCommitted() + { + return true; + } + + @Override + public boolean isRolledback() + { + return false; + } + } + + static class RolledbackTransactionState<T extends DataSetInformation> extends + TerminalTransactionState<T> + { + public RolledbackTransactionState(LiveTransactionState<T> liveState) + { + super(liveState); + } + + @Override + public boolean isCommitted() + { + return false; + } + + @Override + public boolean isRolledback() + { + return true; + } + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java index f712acc6d88..1b1b9259ccb 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransaction.java @@ -18,27 +18,33 @@ package ch.systemsx.cisd.etlserver.registrator.api.v1.impl; import java.io.File; import java.io.FilenameFilter; -import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.commons.lang.time.DateFormatUtils; import org.apache.log4j.Logger; -import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.etlserver.registrator.DataSetRegistrationDetails; import ch.systemsx.cisd.etlserver.registrator.DataSetRegistrationService; +import ch.systemsx.cisd.etlserver.registrator.DataSetStorageAlgorithmRunner; import ch.systemsx.cisd.etlserver.registrator.IDataSetRegistrationDetailsFactory; +import ch.systemsx.cisd.etlserver.registrator.IEntityRegistrationService; import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSet; import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSetRegistrationTransaction; import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperiment; import ch.systemsx.cisd.etlserver.registrator.api.v1.IExperimentImmutable; import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample; import ch.systemsx.cisd.etlserver.registrator.api.v1.ISampleImmutable; +import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.AbstractTransactionState.CommitedTransactionState; +import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.AbstractTransactionState.LiveTransactionState; +import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.AbstractTransactionState.RolledbackTransactionState; import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.AtomicEntityRegistrationDetails; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetRegistrationInformation; import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier; import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory; import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier; @@ -53,7 +59,8 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFa * @author Chandrasekhar Ramakrishnan */ public class DataSetRegistrationTransaction<T extends DataSetInformation> implements - IDataSetRegistrationTransaction + IDataSetRegistrationTransaction, DataSetStorageAlgorithmRunner.IRollbackDelegate<T>, + DataSetStorageAlgorithmRunner.IDataSetInApplicationServerRegistrator<T> { private static final String ROLLBACK_QUEUE1_FILE_NAME_SUFFIX = "rollBackQueue1"; @@ -150,8 +157,8 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem IDataSetRegistrationDetailsFactory<T> registrationDetailsFactory) { state = - new LiveTransactionState<T>(rollbackStack, workingDirectory, stagingDirectory, - registrationService, registrationDetailsFactory); + new LiveTransactionState<T>(this, rollbackStack, workingDirectory, + stagingDirectory, registrationService, registrationDetailsFactory); this.registrationService = registrationService; this.openBisService = this.registrationService.getRegistratorState().getGlobalState().getOpenBisService(); @@ -159,12 +166,12 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem public IDataSet createNewDataSet() { - return ((LiveTransactionState<T>) state).createNewDataSet(); + return getStateAsLiveState().createNewDataSet(); } public IDataSet createNewDataSet(DataSetRegistrationDetails<T> registrationDetails) { - return ((LiveTransactionState<T>) state).createNewDataSet(registrationDetails); + return getStateAsLiveState().createNewDataSet(registrationDetails); } public ISampleImmutable getSample(String sampleIdentifierString) @@ -176,12 +183,12 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem public ISample getSampleForUpdate(String sampleIdentifierString) { - return ((LiveTransactionState<T>) state).getSampleForUpdate(sampleIdentifierString); + return getStateAsLiveState().getSampleForUpdate(sampleIdentifierString); } public ISample createNewSample(String sampleIdentifierString) { - return ((LiveTransactionState<T>) state).createNewSample(sampleIdentifierString); + return getStateAsLiveState().createNewSample(sampleIdentifierString); } public IExperimentImmutable getExperiment(String experimentIdentifierString) @@ -195,42 +202,42 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem public IExperiment getExperimentForUpdate(String experimentIdentifierString) { - return ((LiveTransactionState<T>) state).getExperimentForUpdate(experimentIdentifierString); + return getStateAsLiveState().getExperimentForUpdate(experimentIdentifierString); } public IExperiment createNewExperiment(String experimentIdentifierString) { - return ((LiveTransactionState<T>) state).createNewExperiment(experimentIdentifierString); + return getStateAsLiveState().createNewExperiment(experimentIdentifierString); } public String moveFile(String src, IDataSet dst) { - return ((LiveTransactionState<T>) state).moveFile(src, dst); + return getStateAsLiveState().moveFile(src, dst); } public String moveFile(String src, IDataSet dst, String dstInDataset) { - return ((LiveTransactionState<T>) state).moveFile(src, dst, dstInDataset); + return getStateAsLiveState().moveFile(src, dst, dstInDataset); } public String createNewDirectory(IDataSet dst, String dirName) { - return ((LiveTransactionState<T>) state).createNewDirectory(dst, dirName); + return getStateAsLiveState().createNewDirectory(dst, dirName); } public String createNewFile(IDataSet dst, String fileName) { - return ((LiveTransactionState<T>) state).createNewFile(dst, fileName); + return getStateAsLiveState().createNewFile(dst, fileName); } public String createNewFile(IDataSet dst, String dstInDataset, String fileName) { - return ((LiveTransactionState<T>) state).createNewFile(dst, dstInDataset, fileName); + return getStateAsLiveState().createNewFile(dst, dstInDataset, fileName); } public void deleteFile(String src) { - ((LiveTransactionState<T>) state).deleteFile(src); + getStateAsLiveState().deleteFile(src); } /** @@ -238,7 +245,12 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem */ public void commit() { - LiveTransactionState<T> liveState = (LiveTransactionState<T>) state; + // No need to commit again + if (state instanceof CommitedTransactionState) + { + return; + } + LiveTransactionState<T> liveState = getStateAsLiveState(); liveState.commit(); state = new CommitedTransactionState<T>(liveState); @@ -250,320 +262,78 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem */ public void rollback() { - LiveTransactionState<T> liveState = (LiveTransactionState<T>) state; + // No need to rollback again + if (state instanceof RolledbackTransactionState) + { + return; + } + + LiveTransactionState<T> liveState = getStateAsLiveState(); liveState.rollback(); state = new RolledbackTransactionState<T>(liveState); } /** - * Abstract superclass for the states a DataSetRegistrationTransaction can be in. - * - * @author Chandrasekhar Ramakrishnan + * Delegate method called by the {@link DataSetStorageAlgorithmRunner}. */ - private static abstract class AbstractTransactionState<T extends DataSetInformation> + public void rollback(DataSetStorageAlgorithmRunner<T> algorithm, Throwable ex) { - public abstract boolean isCommitted(); - - public abstract boolean isRolledback(); + rollback(); + registrationService.rollbackTransaction(this, algorithm, ex); } /** - * The state where the transaction is still modifyiable. - * - * @author Chandrasekhar Ramakrishnan + * Delegate method called by the {@link DataSetStorageAlgorithmRunner}. This implementation asks + * the DataSetRegistrationService to register not just the data sets, but perform any creation + * or updates of Experiments and Samples as well. */ - private static class LiveTransactionState<T extends DataSetInformation> extends - AbstractTransactionState<T> + public void registerDataSetsInApplicationServer( + List<DataSetRegistrationInformation<T>> dataSetRegistrations) throws Throwable { - // Keeps track of steps that have been executed and may need to be reverted. Elements are - // kept in the order they need to be reverted. - private final RollbackStack rollbackStack; - - // The directory to use as "local" for paths - private final File workingDirectory; - - // The directory in which new data sets get staged - private final File stagingDirectory; - - // The registration service that owns this transaction - private final DataSetRegistrationService registrationService; - - // The interface to openBIS - private final IEncapsulatedOpenBISService openBisService; - - private final IDataSetRegistrationDetailsFactory<T> registrationDetailsFactory; - - private final ArrayList<DataSet<T>> registeredDataSets = new ArrayList<DataSet<T>>(); - - private final List<Experiment> experimentsToBeRegistered = new ArrayList<Experiment>(); - - public LiveTransactionState(RollbackStack rollbackStack, File workingDirectory, - File stagingDirectory, DataSetRegistrationService registrationService, - IDataSetRegistrationDetailsFactory<T> registrationDetailsFactory) - { - this.rollbackStack = rollbackStack; - this.workingDirectory = workingDirectory; - this.stagingDirectory = stagingDirectory; - this.registrationService = registrationService; - this.openBisService = - this.registrationService.getRegistratorState().getGlobalState() - .getOpenBisService(); - this.registrationDetailsFactory = registrationDetailsFactory; - } - - public IDataSet createNewDataSet() - { - // Create registration details for the new data set - DataSetRegistrationDetails<T> registrationDetails = - registrationDetailsFactory.createDataSetRegistrationDetails(); - - return createNewDataSet(registrationDetails); - } - - public IDataSet createNewDataSet(DataSetRegistrationDetails<T> registrationDetails) - { - // Request a code, so we can keep the staging file name and the data set code in sync - String dataSetCode = registrationDetails.getDataSetInformation().getDataSetCode(); - if (null == dataSetCode) - { - dataSetCode = generateDataSetCode(registrationDetails); - registrationDetails.getDataSetInformation().setDataSetCode(dataSetCode); - } - - // Create a directory for the data set - File stagingFolder = new File(stagingDirectory, dataSetCode); - MkdirsCommand cmd = new MkdirsCommand(stagingFolder.getAbsolutePath()); - executeCommand(cmd); - - DataSet<T> dataSet = - registrationDetailsFactory.createDataSet(registrationDetails, stagingFolder); - registeredDataSets.add(dataSet); - return dataSet; - } - - public ISample getSampleForUpdate(String sampleIdentifierString) - { - // TODO Auto-generated method stub - return null; - } - - public ISample createNewSample(String sampleIdentifierString) - { - // TODO Auto-generated method stub - return null; - } - - public IExperiment getExperimentForUpdate(String experimentIdentifierString) - { - // TODO Auto-generated method stub - return null; - } - - public IExperiment createNewExperiment(String experimentIdentifierString) - { - String permID = openBisService.createDataSetCode(); - Experiment experiment = new Experiment(experimentIdentifierString, permID); - experimentsToBeRegistered.add(experiment); - return experiment; - } - - public String moveFile(String src, IDataSet dst) - { - File srcFile = new File(src); - return moveFile(src, dst, srcFile.getName()); - } - - public String moveFile(String src, IDataSet dst, String dstInDataset) - { - @SuppressWarnings("unchecked") - DataSet<T> dataSet = (DataSet<T>) dst; - - // See if this is an absolute path - File srcFile = new File(src); - if (false == srcFile.exists()) - { - // Try it relative - srcFile = new File(workingDirectory, src); - } - - File dataSetFolder = dataSet.getDataSetStagingFolder(); - File dstFile = new File(dataSetFolder, dstInDataset); - - FileUtilities.checkInputFile(srcFile); - - MoveFileCommand cmd = - new MoveFileCommand(srcFile.getParentFile().getAbsolutePath(), - srcFile.getName(), dstFile.getParentFile().getAbsolutePath(), - dstFile.getName()); - executeCommand(cmd); - return dstFile.getAbsolutePath(); - } - - public String createNewDirectory(IDataSet dst, String dirName) - { - @SuppressWarnings("unchecked") - DataSet<T> dataSet = (DataSet<T>) dst; - File dataSetFolder = dataSet.getDataSetStagingFolder(); - File dstFile = new File(dataSetFolder, dirName); - MkdirsCommand cmd = new MkdirsCommand(dstFile.getAbsolutePath()); - executeCommand(cmd); - return dstFile.getAbsolutePath(); - } - - public String createNewFile(IDataSet dst, String fileName) - { - return createNewFile(dst, "/", fileName); - } + AtomicEntityRegistrationDetails<T> registrationDetails = + getStateAsLiveState().createRegistrationDetails(dataSetRegistrations); + IEntityRegistrationService<T> entityRegistrationService = + registrationService.getEntityRegistrationService(); - public String createNewFile(IDataSet dst, String dstInDataset, String fileName) - { - @SuppressWarnings("unchecked") - DataSet<T> dataSet = (DataSet<T>) dst; - File dataSetFolder = dataSet.getDataSetStagingFolder(); - File dstFolder = new File(dataSetFolder, dstInDataset); - File dstFile = new File(dstFolder, fileName); - NewFileCommand cmd = new NewFileCommand(dstFile.getAbsolutePath()); - executeCommand(cmd); - return dstFile.getAbsolutePath(); - } - - public void deleteFile(String src) - { - // TODO Auto-generated method stub - - } - - /** - * Commit the transaction - */ - public void commit() - { - for (DataSet<T> dataSet : registeredDataSets) - { - registrationService.queueDataSetRegistration(dataSet.getDataSetContents(), - dataSet.getRegistrationDetails()); - } - registrationService.commit(); - - // for (DataSet<T> dataSet : registeredDataSets) - // { - // File contents = dataSet.getDataSetContents(); - // DataSetRegistrationDetails<T> details = dataSet.getRegistrationDetails(); - // registrationService.getRegistratorState().getDataStrategyStore() - // .getDataStoreStrategy(details.getDataSetInformation(), contents); - // DataSetStorageAlgorithm<T> algorithm = - // new DataSetStorageAlgorithm<T>(contents, details, null, null, null, null, - // null, null); - // } - // registrationService.commit(); - } - - /** - * Rollback any commands that have been executed. Rollback is done in the reverse order of - * execution. - */ - public void rollback() - { - rollbackStack.rollbackAll(); - registeredDataSets.clear(); - } - - /** - * Execute the command and add it to the list of commands that have been executed. - */ - private void executeCommand(ITransactionalCommand cmd) - { - rollbackStack.pushAndExecuteCommand(cmd); - } - - /** - * Generate a data set code for the registration details. Just calls openBisService to get a - * data set code by default. - * - * @return A data set code - */ - private String generateDataSetCode(DataSetRegistrationDetails<T> registrationDetails) - { - return openBisService.createDataSetCode(); - } - - @Override - public boolean isCommitted() - { - return false; - } - - @Override - public boolean isRolledback() - { - return false; - } + entityRegistrationService.registerEntitiesInApplcationServer(registrationDetails); } - private static abstract class TerminalTransactionState<T extends DataSetInformation> extends - AbstractTransactionState<T> + public boolean isCommittedOrRolledback() { - private final LiveTransactionState<T> liveState; - - protected TerminalTransactionState(LiveTransactionState<T> liveState) - { - this.liveState = liveState; - deleteStagingFolders(); - this.liveState.rollbackStack.discard(); - } - - private void deleteStagingFolders() - { - for (DataSet<T> dataSet : liveState.registeredDataSets) - { - dataSet.getDataSetStagingFolder().delete(); - } - } - + return isCommitted() || isRolledback(); } - private static class CommitedTransactionState<T extends DataSetInformation> extends - TerminalTransactionState<T> + public boolean isCommitted() { - - public CommitedTransactionState(LiveTransactionState<T> liveState) - { - super(liveState); - } - - @Override - public boolean isCommitted() - { - return true; - } - - @Override - public boolean isRolledback() - { - return false; - } + return state.isCommitted(); } - private static class RolledbackTransactionState<T extends DataSetInformation> extends - TerminalTransactionState<T> + public boolean isRolledback() { - public RolledbackTransactionState(LiveTransactionState<T> liveState) - { - super(liveState); - } + return state.isRolledback(); + } - @Override - public boolean isCommitted() + /** + * Return the state as live state. Throw an EnvironmentFailureException if this is not possible. + */ + private LiveTransactionState<T> getStateAsLiveState() + { + try { - return false; - } - - @Override - public boolean isRolledback() + LiveTransactionState<T> liveState = (LiveTransactionState<T>) state; + return liveState; + } catch (ClassCastException ex) { - return true; + String message; + if (state instanceof CommitedTransactionState) + { + message = "The transaction has already been committed"; + } else + { + message = "The transaction has already been rolledback"; + } + throw new EnvironmentFailureException(message, ex); } } - } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationDetails.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationDetails.java new file mode 100644 index 00000000000..a8097a5b7ad --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationDetails.java @@ -0,0 +1,86 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.generic.shared.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewExperiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewSample; +import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentUpdatesDTO; +import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO; + +/** + * An object that captures the state for performing the registration of one or many openBIS entities + * atomically. + * + * @author Chandrasekhar Ramakrishnan + */ +public class AtomicEntityRegistrationDetails<T extends DataSetInformation> implements + Serializable +{ + private static final long serialVersionUID = 1L; + + private final ArrayList<ExperimentUpdatesDTO> experimentUpdates; + + private final ArrayList<NewExperiment> experimentRegistrations; + + private final ArrayList<SampleUpdatesDTO> sampleUpdates; + + private final ArrayList<NewSample> sampleRegistrations; + + private final ArrayList<DataSetRegistrationInformation<T>> dataSetRegistrations; + + public AtomicEntityRegistrationDetails(List<ExperimentUpdatesDTO> experimentUpdates, + List<NewExperiment> experimentRegistrations, List<SampleUpdatesDTO> sampleUpdates, + List<NewSample> sampleRegistrations, + List<DataSetRegistrationInformation<T>> dataSetRegistrations) + { + this.experimentUpdates = new ArrayList<ExperimentUpdatesDTO>(experimentUpdates); + this.experimentRegistrations = new ArrayList<NewExperiment>(experimentRegistrations); + this.sampleUpdates = new ArrayList<SampleUpdatesDTO>(sampleUpdates); + this.sampleRegistrations = new ArrayList<NewSample>(sampleRegistrations); + this.dataSetRegistrations = + new ArrayList<DataSetRegistrationInformation<T>>(dataSetRegistrations); + } + + public ArrayList<ExperimentUpdatesDTO> getExperimentUpdates() + { + return experimentUpdates; + } + + public ArrayList<NewExperiment> getExperimentRegistrations() + { + return experimentRegistrations; + } + + public ArrayList<SampleUpdatesDTO> getSampleUpdates() + { + return sampleUpdates; + } + + public ArrayList<NewSample> getSampleRegistrations() + { + return sampleRegistrations; + } + + public ArrayList<DataSetRegistrationInformation<T>> getDataSetRegistrations() + { + return dataSetRegistrations; + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationResult.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationResult.java new file mode 100644 index 00000000000..1e0d8c4bb04 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/AtomicEntityRegistrationResult.java @@ -0,0 +1,79 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.generic.shared.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample; + +/** + * @author Chandrasekhar Ramakrishnan + */ +public class AtomicEntityRegistrationResult implements Serializable +{ + private static final long serialVersionUID = 1L; + + private final ArrayList<Experiment> experimentsUpdated; + + private final ArrayList<Experiment> experimentsCreated; + + private final ArrayList<Sample> samplesUpdated; + + private final ArrayList<Sample> samplesCreated; + + private final ArrayList<DataSetInformation> dataSetsCreated; + + public AtomicEntityRegistrationResult(List<Experiment> experimentsUpdated, + List<Experiment> experimentsCreated, List<Sample> samplesUpdated, + List<Sample> samplesCreated, List<DataSetInformation> dataSetsCreated) + { + this.experimentsUpdated = new ArrayList<Experiment>(experimentsUpdated); + this.experimentsCreated = new ArrayList<Experiment>(experimentsCreated); + this.samplesUpdated = new ArrayList<Sample>(samplesUpdated); + this.samplesCreated = new ArrayList<Sample>(samplesCreated); + this.dataSetsCreated = new ArrayList<DataSetInformation>(dataSetsCreated); + } + + public ArrayList<Experiment> getExperimentsUpdated() + { + return experimentsUpdated; + } + + public ArrayList<Experiment> getExperimentsCreated() + { + return experimentsCreated; + } + + public ArrayList<Sample> getSamplesUpdated() + { + return samplesUpdated; + } + + public ArrayList<Sample> getSamplesCreated() + { + return samplesCreated; + } + + public ArrayList<DataSetInformation> getDataSetsCreated() + { + return dataSetsCreated; + } + +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetRegistrationInformation.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetRegistrationInformation.java new file mode 100644 index 00000000000..3f0c9e0f3da --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/dto/DataSetRegistrationInformation.java @@ -0,0 +1,42 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.generic.shared.dto; + +import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData; + +public class DataSetRegistrationInformation<T extends DataSetInformation> +{ + private final T dataSetInformation; + + private final NewExternalData externalData; + + public DataSetRegistrationInformation(T dataSetInformation, NewExternalData externalData) + { + this.dataSetInformation = dataSetInformation; + this.externalData = externalData; + } + + public T getDataSetInformation() + { + return dataSetInformation; + } + + public NewExternalData getExternalData() + { + return externalData; + } +} \ No newline at end of file diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java index a9ef583ccae..111b0479d01 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java @@ -635,7 +635,7 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractFileSystemTest registrator.setEmail("email@email.com"); experiment.setRegistrator(registrator); - exactly(2).of(openBisService).tryToGetExperiment( + exactly(3).of(openBisService).tryToGetExperiment( new ExperimentIdentifierFactory("/SPACE/PROJECT/EXP-CODE") .createIdentifier()); will(returnValue(experiment)); diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransactionTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransactionTest.java index d1f4e047c91..ec532bd3c9c 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransactionTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/api/v1/impl/DataSetRegistrationTransactionTest.java @@ -34,7 +34,6 @@ import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; import ch.systemsx.cisd.common.filesystem.FileUtilities; import ch.systemsx.cisd.common.filesystem.QueueingPathRemoverService; import ch.systemsx.cisd.common.logging.BufferedAppender; -import ch.systemsx.cisd.common.mail.From; import ch.systemsx.cisd.common.mail.IMailClient; import ch.systemsx.cisd.common.utilities.ExtendedProperties; import ch.systemsx.cisd.common.utilities.IDelegatedActionWithResult; @@ -132,7 +131,6 @@ public class DataSetRegistrationTransactionTest extends AbstractFileSystemTestCa { setUpOpenBisExpectations(true); setUpDataSetValidatorExpectations(); - setUpMailClientExpectations(); createTransaction(); IDataSet newDataSet = tr.createNewDataSet(); @@ -214,9 +212,7 @@ public class DataSetRegistrationTransactionTest extends AbstractFileSystemTestCa context.assertIsSatisfied(); } - // The second invocation of rollback will cause a class-cast exception. - @Test(expectedExceptions = - { ClassCastException.class }) + @Test public void testDoubleRollbackNormal() { setUpOpenBisExpectations(false); @@ -371,7 +367,7 @@ public class DataSetRegistrationTransactionTest extends AbstractFileSystemTestCa project.setSpace(space); experiment.setProject(project); - exactly(2).of(openBisService).tryToGetExperiment( + exactly(3).of(openBisService).tryToGetExperiment( new ExperimentIdentifierFactory(EXPERIMENT_IDENTIFIER) .createIdentifier()); will(returnValue(experiment)); @@ -397,18 +393,6 @@ public class DataSetRegistrationTransactionTest extends AbstractFileSystemTestCa }); } - private void setUpMailClientExpectations() - { - context.checking(new Expectations() - { - { - exactly(1).of(mailClient).sendMessage(with(any(String.class)), - with(any(String.class)), with(aNull(String.class)), - with(aNull(From.class)), with(any(String[].class))); - } - }); - } - public static final class MockStorageProcessor implements IStorageProcessor { static MockStorageProcessor instance; -- GitLab