From aadd3c4b79b5fc9f043229e2a525f864c35e2ba9 Mon Sep 17 00:00:00 2001 From: cramakri <cramakri> Date: Tue, 11 Jan 2011 15:38:50 +0000 Subject: [PATCH] LMS-1966 Refactoring DSS to make alternate top-level data set handlers possible. SVN: 19365 --- .../DataSetRegistrationAlgorithm.java | 647 ++++++++++++++++++ .../etlserver/TransferredDataSetHandler.java | 39 +- ...etHandlerDataSetRegistrationAlgorithm.java | 365 +--------- .../etlserver/api/v1/PutDataSetExecutor.java | 8 +- 4 files changed, 700 insertions(+), 359 deletions(-) create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java new file mode 100644 index 00000000000..2351fd7d447 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DataSetRegistrationAlgorithm.java @@ -0,0 +1,647 @@ +/* + * 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; + +import java.io.File; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.concurrent.locks.Lock; + +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.exceptions.HighLevelException; +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.filesystem.IFileOperations; +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.common.utilities.IDelegatedActionWithResult; +import ch.systemsx.cisd.etlserver.IStorageProcessor.UnstoreDataAction; +import ch.systemsx.cisd.etlserver.validation.IDataSetValidator; +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; +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; + +/** + * An algorithm that implements the logic for registration of a data set. + * + * @author Chandrasekhar Ramakrishnan + */ +public class DataSetRegistrationAlgorithm +{ + + public static interface IRollbackDelegate + { + public void rollback(DataSetRegistrationAlgorithm algorithm, Throwable ex); + } + + /** + * Object for holding the state necessary for registring a data set. + * + * @author Chandrasekhar Ramakrishnan + */ + public static class DataSetRegistrationAlgorithmState + { + private final File incomingDataSetFile; + + private final IEncapsulatedOpenBISService openBisService; + + private final IDelegatedActionWithResult<Boolean> cleanAfterwardsAction; + + private final IPreRegistrationAction preRegistrationAction; + + private final IPostRegistrationAction postRegistrationAction; + + private final DataSetInformation dataSetInformation; + + private final IDataStoreStrategy dataStoreStrategy; + + private final ITypeExtractor typeExtractor; + + private final IStorageProcessor storageProcessor; + + private final IFileOperations fileOperations; + + private final IDataSetValidator dataSetValidator; + + private final IMailClient mailClient; + + private final boolean shouldDeleteUnidentified; + + private final String dataStoreCode; + + private final Lock registrationLock; + + private final boolean shouldNotifySuccessfulRegistration; + + private final DataSetType dataSetType; + + private final File storeRoot; + + private final String defaultErrorMessageTemplate; + + private final String emailSubjectTemplate; + + public DataSetRegistrationAlgorithmState(File incomingDataSetFile, + IEncapsulatedOpenBISService openBisService, + IDelegatedActionWithResult<Boolean> cleanAftrewardsAction, + IPreRegistrationAction preRegistrationAction, + IPostRegistrationAction postRegistrationAction, + DataSetInformation dataSetInformation, IDataStoreStrategy dataStoreStrategy, + ITypeExtractor typeExtractor, IStorageProcessor storageProcessor, + IFileOperations fileOperations, IDataSetValidator dataSetValidator, + IMailClient mailClient, boolean shouldDeleteUnidentified, Lock registrationLock, + String dataStoreCode, boolean shouldNotifySuccessfulRegistration) + { + this.incomingDataSetFile = incomingDataSetFile; + this.openBisService = openBisService; + this.cleanAfterwardsAction = cleanAftrewardsAction; + this.preRegistrationAction = preRegistrationAction; + this.postRegistrationAction = postRegistrationAction; + this.dataSetInformation = dataSetInformation; + this.dataStoreStrategy = dataStoreStrategy; + this.typeExtractor = typeExtractor; + this.storageProcessor = storageProcessor; + this.fileOperations = fileOperations; + this.dataSetValidator = dataSetValidator; + this.mailClient = mailClient; + this.shouldDeleteUnidentified = shouldDeleteUnidentified; + this.registrationLock = registrationLock; + this.dataStoreCode = dataStoreCode; + this.shouldNotifySuccessfulRegistration = shouldNotifySuccessfulRegistration; + + if (dataSetInformation.getDataSetCode() == null) + { + // Extractor didn't extract an externally generated data set code, so request one + // from the openBIS server. + dataSetInformation.setDataSetCode(openBisService.createDataSetCode()); + } + + this.dataSetType = typeExtractor.getDataSetType(incomingDataSetFile); + dataSetInformation.setDataSetType(dataSetType); + this.storeRoot = storageProcessor.getStoreRootDirectory(); + this.defaultErrorMessageTemplate = DATA_SET_STORAGE_FAILURE_TEMPLATE; + this.emailSubjectTemplate = EMAIL_SUBJECT_TEMPLATE; + } + + public String getErrorMessageTemplate() + { + return defaultErrorMessageTemplate; + } + + public File getIncomingDataSetFile() + { + return incomingDataSetFile; + } + + public IEncapsulatedOpenBISService getOpenBisService() + { + return openBisService; + } + + public IDelegatedActionWithResult<Boolean> getCleanAftrewardsAction() + { + return cleanAfterwardsAction; + } + + public IPreRegistrationAction getPreRegistrationAction() + { + return preRegistrationAction; + } + + public IPostRegistrationAction getPostRegistrationAction() + { + return postRegistrationAction; + } + + public DataSetInformation getDataSetInformation() + { + return dataSetInformation; + } + + public IDataStoreStrategy getDataStoreStrategy() + { + return dataStoreStrategy; + } + + public ITypeExtractor getTypeExtractor() + { + return typeExtractor; + } + + public IStorageProcessor getStorageProcessor() + { + return storageProcessor; + } + + public DataSetType getDataSetType() + { + return dataSetType; + } + + public File getStoreRoot() + { + return storeRoot; + } + + public IMailClient getMailClient() + { + return mailClient; + } + + public Lock getRegistrationLock() + { + return registrationLock; + } + } + + static private final Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, + DataSetRegistrationAlgorithm.class); + + static private final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + DataSetRegistrationAlgorithm.class); + + public static final String EMAIL_SUBJECT_TEMPLATE = "Success: data set for experiment '%s"; + + 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: ["; + + // Immutable State + private final DataSetRegistrationAlgorithmState state; + + private final IRollbackDelegate rollbackDelegate; + + private final File incomingDataSetFile; + + private final DataSetInformation dataSetInformation; + + // State that changes during execution + private BaseDirectoryHolder baseDirectoryHolder; + + private String errorMessageTemplate; + + public DataSetRegistrationAlgorithm(DataSetRegistrationAlgorithmState state, + IRollbackDelegate rollbackDelegate) + { + this.state = state; + this.rollbackDelegate = rollbackDelegate; + incomingDataSetFile = state.incomingDataSetFile; + dataSetInformation = state.dataSetInformation; + errorMessageTemplate = state.getErrorMessageTemplate(); + } + + /** + * Prepare registration of a data set. + */ + public final void prepare() + { + final File baseDirectory = + createBaseDirectory(getDataStoreStrategy(), state.storeRoot, + state.dataSetInformation); + baseDirectoryHolder = + new BaseDirectoryHolder(getDataStoreStrategy(), baseDirectory, + state.incomingDataSetFile); + } + + public boolean hasDataSetBeenIdentified() + { + return state.dataStoreStrategy.getKey() == DataStoreStrategyKey.IDENTIFIED; + } + + /** + * Register the data set. This method is only ever called for identified data sets. + */ + public final List<DataSetInformation> registerDataSet() + { + String processorID = getTypeExtractor().getProcessorType(incomingDataSetFile); + try + { + getDataSetValidator().assertValidDataSet(state.dataSetType, incomingDataSetFile); + registerDataSetAndInitiateProcessing(processorID); + logAndNotifySuccessfulRegistration(getEmail()); + if (state.fileOperations.exists(incomingDataSetFile) + && removeAndLog("clean up failed") == false) + { + getOperationLog().error( + "Cannot delete '" + incomingDataSetFile.getAbsolutePath() + "'."); + } + + clean(); + return Collections.singletonList(dataSetInformation); + } catch (final HighLevelException ex) + { + final String userEmailOrNull = dataSetInformation.tryGetUploadingUserEmail(); + boolean deleted = false; + if (userEmailOrNull != null) + { + final String errorMessage = + "Error when trying to register data set '" + incomingDataSetFile.getName() + + "'."; + state.getMailClient() + .sendMessage( + String.format(errorMessage, dataSetInformation + .getExperimentIdentifier().getExperimentCode()), + ex.getMessage(), null, null, userEmailOrNull); + if (state.shouldDeleteUnidentified) + { + deleted = removeAndLog(errorMessage + " [" + ex.getMessage() + "]"); + } + } + if (deleted == false) + { + rollback(ex); + } + return Collections.emptyList(); + } catch (final Throwable throwable) + { + rollback(throwable); + return Collections.emptyList(); + } + } + + public final File createBaseDirectory(final IDataStoreStrategy strategy, final File baseDir, + final DataSetInformation dataSetInfo) + { + final File baseDirectory = + strategy.getBaseDirectory(baseDir, dataSetInfo, state.dataSetType); + baseDirectory.mkdirs(); + if (state.fileOperations.isDirectory(baseDirectory) == false) + { + throw EnvironmentFailureException.fromTemplate( + "Creating data set base directory '%s' for data set '%s' failed.", + baseDirectory.getAbsolutePath(), state.incomingDataSetFile); + } + return baseDirectory; + } + + public DataSetInformation getDataSetInformation() + { + return dataSetInformation; + } + + public BaseDirectoryHolder getBaseDirectoryHolder() + { + return baseDirectoryHolder; + } + + public void setBaseDirectoryHolder(BaseDirectoryHolder baseDirectoryHolder) + { + this.baseDirectoryHolder = baseDirectoryHolder; + } + + public String getErrorMessageTemplate() + { + return errorMessageTemplate; + } + + public File getStoreRoot() + { + return state.getStoreRoot(); + } + + /** + * Ask the storage processor to rollback. Used by clients of the algorithm. + */ + public UnstoreDataAction rollbackStorageProcessor(final Throwable throwable) + { + UnstoreDataAction action = + getStorageProcessor().rollback(incomingDataSetFile, + baseDirectoryHolder.getBaseDirectory(), throwable); + return action; + } + + protected boolean clean() + { + return state.cleanAfterwardsAction.execute(); + } + + /** + * Contact openBis and register the data set there. Subclasses may override. + * + * @throws Throwable + */ + protected void registerDataSetInApplicationServer(NewExternalData data) throws Throwable + { + state.getOpenBisService().registerDataSet(dataSetInformation, data); + } + + private void rollback(Throwable ex) + { + rollbackDelegate.rollback(this, ex); + } + + /** + * Registers the data set. + */ + private void registerDataSetAndInitiateProcessing(final String procedureTypeCode) + throws Throwable + { + final File markerFile = createProcessingMarkerFile(); + try + { + String entityDescription = createEntityDescription(); + if (getOperationLog().isInfoEnabled()) + { + getOperationLog().info("Start storing data set for " + entityDescription + "."); + } + final StopWatch watch = new StopWatch(); + watch.start(); + NewExternalData data = createExternalData(); + state.preRegistrationAction.execute(data.getCode(), + incomingDataSetFile.getAbsolutePath()); + File dataFile = + getStorageProcessor().storeData(dataSetInformation, getTypeExtractor(), + state.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."; + final String relativePath = FileUtilities.getRelativeFile(state.storeRoot, dataFile); + String absolutePath = dataFile.getAbsolutePath(); + assert relativePath != null : String.format( + TransferredDataSetHandler.TARGET_NOT_RELATIVE_TO_STORE_ROOT, absolutePath, + state.storeRoot.getAbsolutePath()); + final StorageFormat availableFormat = getStorageProcessor().getStorageFormat(); + final BooleanOrUnknown isCompleteFlag = dataSetInformation.getIsCompleteFlag(); + // Ensure that we either register the data set and initiate the processing copy or + // do none of both. + state.getRegistrationLock().lock(); + try + { + errorMessageTemplate = DATA_SET_REGISTRATION_FAILURE_TEMPLATE; + plainRegisterDataSet(data, relativePath, availableFormat, isCompleteFlag); + state.postRegistrationAction.execute(data.getCode(), absolutePath); + clean(); + } finally + { + state.getRegistrationLock().unlock(); + } + getStorageProcessor().commit(incomingDataSetFile, + baseDirectoryHolder.getBaseDirectory()); + } finally + { + getFileOperations().delete(markerFile); + } + } + + private NewExternalData createExternalData() + { + final NewExternalData data = new NewExternalData(); + data.setUserId(dataSetInformation.getUploadingUserIdOrNull()); + data.setUserEMail(dataSetInformation.tryGetUploadingUserEmail()); + data.setExtractableData(dataSetInformation.getExtractableData()); + data.setLocatorType(getTypeExtractor().getLocatorType(incomingDataSetFile)); + data.setDataSetType(getTypeExtractor().getDataSetType(incomingDataSetFile)); + data.setFileFormatType(getTypeExtractor().getFileFormatType(incomingDataSetFile)); + data.setMeasured(getTypeExtractor().isMeasuredData(incomingDataSetFile)); + data.setDataStoreCode(state.dataStoreCode); + return data; + } + + private void logAndNotifySuccessfulRegistration(final String email) + { + String msg = null; + if (getOperationLog().isInfoEnabled()) + { + msg = getSuccessRegistrationMessage(); + getOperationLog().info(msg); + } + if (state.shouldNotifySuccessfulRegistration) + { + if (msg == null) + { + msg = getSuccessRegistrationMessage(); + } + if (getNotificationLog().isInfoEnabled()) + { + getNotificationLog().info(msg); + } + if (StringUtils.isBlank(email) == false) + { + state.getMailClient().sendMessage( + String.format(state.emailSubjectTemplate, dataSetInformation + .getExperimentIdentifier().getExperimentCode()), msg, null, null, + email); + } + } + } + + private final String getSuccessRegistrationMessage() + { + final StringBuilder buffer = new StringBuilder(); + buffer.append(SUCCESSFULLY_REGISTERED); + 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", state.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); + buffer.append(']'); + return buffer.toString(); + } + + 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(";"); + } + } + + private String getEmail() + { + Experiment experiment = dataSetInformation.tryToGetExperiment(); + if (experiment == null) + { + throw new UserFailureException("Unknown experiment of data set " + dataSetInformation); + } + return experiment.getRegistrator().getEmail(); + } + + private boolean removeAndLog(String msg) + { + final boolean ok = getFileOperations().removeRecursivelyQueueing(incomingDataSetFile); + if (getOperationLog().isInfoEnabled()) + { + getOperationLog().info("Dataset deleted in registration: " + msg); + } + return ok; + } + + private final void plainRegisterDataSet(NewExternalData data, final String relativePath, + final StorageFormat storageFormat, final BooleanOrUnknown isCompleteFlag) + throws Throwable + { + updateExternalData(data, relativePath, storageFormat, isCompleteFlag); + // Finally: register the data set in the database. + registerDataSetInApplicationServer(data); + } + + private final NewExternalData updateExternalData(NewExternalData data, + final String relativePath, final StorageFormat storageFormat, + final BooleanOrUnknown isCompleteFlag) + { + data.setComplete(isCompleteFlag); + data.setLocation(relativePath); + data.setStorageFormat(storageFormat); + return data; + } + + private final File createProcessingMarkerFile() + { + final File baseDirectory = baseDirectoryHolder.getBaseDirectory(); + final File baseParentDirectory = baseDirectory.getParentFile(); + final String processingDirName = baseDirectory.getName(); + final File 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 IFileOperations getFileOperations() + { + return state.fileOperations; + } + + private IStorageProcessor getStorageProcessor() + { + return state.storageProcessor; + } + + private IDataStoreStrategy getDataStoreStrategy() + { + return state.dataStoreStrategy; + } + + private ITypeExtractor getTypeExtractor() + { + return state.typeExtractor; + } + + private IDataSetValidator getDataSetValidator() + { + return state.dataSetValidator; + } + + private Logger getNotificationLog() + { + return notificationLog; + } + + private Logger getOperationLog() + { + return operationLog; + } +} 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 d6d1d30f3f1..7c6e2a6d4fa 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandler.java @@ -221,7 +221,8 @@ public final class TransferredDataSetHandler extends AbstractTopLevelDataSetRegi public List<DataSetInformation> handleDataSet(final File dataSet) { - final TransferredDataSetHandlerDataSetRegistrationAlgorithm registrationHelper = createRegistrationHelper(dataSet); + final TransferredDataSetHandlerDataSetRegistrationAlgorithm registrationHelper = + createRegistrationHelper(dataSet); return new DataSetRegistrationAlgorithmRunner(registrationHelper).runAlgorithm(); } @@ -298,8 +299,8 @@ public final class TransferredDataSetHandler extends AbstractTopLevelDataSetRegi } } - private TransferredDataSetHandlerDataSetRegistrationAlgorithm createRegistrationHelper(File dataSet, - DataSetInformation dataSetInformation, IDataSetRegistrator registrator) + private TransferredDataSetHandlerDataSetRegistrationAlgorithm createRegistrationHelper( + File dataSet, DataSetInformation dataSetInformation, IDataSetRegistrator registrator) { if (useIsFinishedMarkerFile) { @@ -542,12 +543,6 @@ public final class TransferredDataSetHandler extends AbstractTopLevelDataSetRegi return notifySuccessfulRegistration; } - @Override - protected String getEmailSubjectTemplate() - { - return TransferredDataSetHandlerDataSetRegistrationAlgorithm.EMAIL_SUBJECT_TEMPLATE; - } - @Override protected void rollback(final Throwable throwable) throws Error { @@ -557,11 +552,12 @@ public final class TransferredDataSetHandler extends AbstractTopLevelDataSetRegi Thread.interrupted(); // Ensure the thread's interrupted state is cleared. getOperationLog().warn( String.format("Requested to stop registration of data set '%s'", - dataSetInformation)); + algorithm.getDataSetInformation())); } else { - getNotificationLog().error(String.format(errorMessageTemplate, dataSetInformation), - throwable); + getNotificationLog().error( + String.format(algorithm.getErrorMessageTemplate(), + algorithm.getDataSetInformation()), throwable); } // 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 @@ -570,24 +566,25 @@ public final class TransferredDataSetHandler extends AbstractTopLevelDataSetRegi { throw (Error) throwable; } - UnstoreDataAction action = rollbackStorageProcessor(throwable); + UnstoreDataAction action = algorithm.rollbackStorageProcessor(throwable); if (stopped == false) { if (action == UnstoreDataAction.MOVE_TO_ERROR) { final File baseDirectory = - createBaseDirectory(TransferredDataSetHandler.ERROR_DATA_STRATEGY, - storeRoot, dataSetInformation); - baseDirectoryHolder = - new BaseDirectoryHolder(TransferredDataSetHandler.ERROR_DATA_STRATEGY, - baseDirectory, incomingDataSetFile); + algorithm.createBaseDirectory( + TransferredDataSetHandler.ERROR_DATA_STRATEGY, + algorithm.getStoreRoot(), algorithm.getDataSetInformation()); + algorithm.setBaseDirectoryHolder(new BaseDirectoryHolder( + TransferredDataSetHandler.ERROR_DATA_STRATEGY, baseDirectory, + incomingDataSetFile)); boolean moveInCaseOfErrorOk = - FileRenamer.renameAndLog(incomingDataSetFile, - baseDirectoryHolder.getTargetFile()); + FileRenamer.renameAndLog(incomingDataSetFile, algorithm + .getBaseDirectoryHolder().getTargetFile()); writeThrowable(throwable); if (moveInCaseOfErrorOk) { - clean(); + algorithm.clean(); } } else if (action == UnstoreDataAction.DELETE) { diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerDataSetRegistrationAlgorithm.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerDataSetRegistrationAlgorithm.java index 9d7e1beab61..af804280d97 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerDataSetRegistrationAlgorithm.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerDataSetRegistrationAlgorithm.java @@ -20,96 +20,67 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.concurrent.locks.Lock; 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.rinn.restrictions.Private; -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.exceptions.HighLevelException; -import ch.systemsx.cisd.common.exceptions.UserFailureException; -import ch.systemsx.cisd.common.filesystem.FileUtilities; import ch.systemsx.cisd.common.filesystem.IFileOperations; import ch.systemsx.cisd.common.mail.IMailClient; -import ch.systemsx.cisd.common.types.BooleanOrUnknown; import ch.systemsx.cisd.common.utilities.IDelegatedActionWithResult; -import ch.systemsx.cisd.etlserver.IStorageProcessor.UnstoreDataAction; +import ch.systemsx.cisd.etlserver.DataSetRegistrationAlgorithm.DataSetRegistrationAlgorithmState; import ch.systemsx.cisd.etlserver.validation.IDataSetValidator; import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; 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; -public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm +public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm implements + DataSetRegistrationAlgorithm.IRollbackDelegate { @Private - public static final String EMAIL_SUBJECT_TEMPLATE = "Success: data set for experiment '%s"; + public static final String EMAIL_SUBJECT_TEMPLATE = + DataSetRegistrationAlgorithm.EMAIL_SUBJECT_TEMPLATE; @Private static final String DATA_SET_REGISTRATION_FAILURE_TEMPLATE = - "Registration of data set '%s' failed."; + DataSetRegistrationAlgorithm.DATA_SET_REGISTRATION_FAILURE_TEMPLATE; @Private - static final String DATA_SET_STORAGE_FAILURE_TEMPLATE = "Storing data set '%s' failed."; + static final String DATA_SET_STORAGE_FAILURE_TEMPLATE = + DataSetRegistrationAlgorithm.DATA_SET_STORAGE_FAILURE_TEMPLATE; @Private - static final String SUCCESSFULLY_REGISTERED = "Successfully registered data set: ["; - - protected final IDelegatedActionWithResult<Boolean> cleanAftrewardsAction; + static final String SUCCESSFULLY_REGISTERED = + DataSetRegistrationAlgorithm.SUCCESSFULLY_REGISTERED; protected final File incomingDataSetFile; - protected final DataSetInformation dataSetInformation; - - protected final IDataStoreStrategy dataStoreStrategy; - - protected final DataSetType dataSetType; - - protected final File storeRoot; - - private final IPreRegistrationAction preRegistrationAction; - - private final IPostRegistrationAction postRegistrationAction; - - protected BaseDirectoryHolder baseDirectoryHolder; - - protected String errorMessageTemplate; + protected final DataSetRegistrationAlgorithm algorithm; public TransferredDataSetHandlerDataSetRegistrationAlgorithm(File incomingDataSetFile, IDelegatedActionWithResult<Boolean> cleanAftrewardsAction, IPreRegistrationAction preRegistrationAction, IPostRegistrationAction postRegistrationAction) { - this.preRegistrationAction = preRegistrationAction; - this.postRegistrationAction = postRegistrationAction; - this.errorMessageTemplate = TransferredDataSetHandlerDataSetRegistrationAlgorithm.DATA_SET_STORAGE_FAILURE_TEMPLATE; - this.incomingDataSetFile = incomingDataSetFile; - this.cleanAftrewardsAction = cleanAftrewardsAction; - this.dataSetInformation = extractDataSetInformation(incomingDataSetFile); - if (dataSetInformation.getDataSetCode() == null) - { - // Extractor didn't extract an externally generated data set code, so request one - // from the openBIS server. - dataSetInformation.setDataSetCode(getOpenBisService().createDataSetCode()); - } - this.dataStoreStrategy = + DataSetInformation dataSetInformation = extractDataSetInformation(incomingDataSetFile); + IDataStoreStrategy dataStoreStrategy = getDataStrategyStore() .getDataStoreStrategy(dataSetInformation, incomingDataSetFile); - this.dataSetType = getTypeExtractor().getDataSetType(incomingDataSetFile); - dataSetInformation.setDataSetType(dataSetType); - this.storeRoot = getStorageProcessor().getStoreRootDirectory(); + DataSetRegistrationAlgorithmState algorithmState = + new DataSetRegistrationAlgorithmState(incomingDataSetFile, getOpenBisService(), + cleanAftrewardsAction, preRegistrationAction, postRegistrationAction, + dataSetInformation, dataStoreStrategy, getTypeExtractor(), + getStorageProcessor(), getFileOperations(), getDataSetValidator(), + getMailClient(), shouldDeleteUnidentified(), getRegistrationLock(), + getDataStoreCode(), shouldNotifySuccessfulRegistration()); + algorithm = new DataSetRegistrationAlgorithm(algorithmState, this); + this.incomingDataSetFile = algorithmState.getIncomingDataSetFile(); } /** @@ -117,7 +88,7 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm */ public DataSetInformation getDataSetInformation() { - return dataSetInformation; + return algorithm.getDataSetInformation(); } /** @@ -125,15 +96,12 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm */ public final void prepare() { - final File baseDirectory = - createBaseDirectory(dataStoreStrategy, storeRoot, dataSetInformation); - baseDirectoryHolder = - new BaseDirectoryHolder(dataStoreStrategy, baseDirectory, incomingDataSetFile); + algorithm.prepare(); } public final boolean hasDataSetBeenIdentified() { - return dataStoreStrategy.getKey() == DataStoreStrategyKey.IDENTIFIED; + return algorithm.hasDataSetBeenIdentified(); } /** @@ -141,154 +109,7 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm */ public final List<DataSetInformation> registerDataSet() { - String processorID = getTypeExtractor().getProcessorType(incomingDataSetFile); - try - { - getDataSetValidator().assertValidDataSet(dataSetType, incomingDataSetFile); - registerDataSetAndInitiateProcessing(processorID); - logAndNotifySuccessfulRegistration(getEmail()); - if (getFileOperations().exists(incomingDataSetFile) - && removeAndLog("clean up failed") == false) - { - getOperationLog().error( - "Cannot delete '" + incomingDataSetFile.getAbsolutePath() + "'."); - } - - clean(); - return Collections.singletonList(dataSetInformation); - } catch (final HighLevelException ex) - { - final String userEmailOrNull = dataSetInformation.tryGetUploadingUserEmail(); - boolean deleted = false; - if (userEmailOrNull != null) - { - final String errorMessage = - "Error when trying to register data set '" + incomingDataSetFile.getName() - + "'."; - getMailClient() - .sendMessage( - String.format(errorMessage, dataSetInformation - .getExperimentIdentifier().getExperimentCode()), - ex.getMessage(), null, null, userEmailOrNull); - if (shouldDeleteUnidentified()) - { - deleted = removeAndLog(errorMessage + " [" + ex.getMessage() + "]"); - } - } - if (deleted == false) - { - rollback(ex); - } - return Collections.emptyList(); - } catch (final Throwable throwable) - { - rollback(throwable); - return Collections.emptyList(); - } - } - - private String getEmail() - { - Experiment experiment = dataSetInformation.tryToGetExperiment(); - if (experiment == null) - { - throw new UserFailureException("Unknown experiment of data set " + dataSetInformation); - } - return experiment.getRegistrator().getEmail(); - } - - protected UnstoreDataAction rollbackStorageProcessor(final Throwable throwable) - { - UnstoreDataAction action = - getStorageProcessor().rollback(incomingDataSetFile, - baseDirectoryHolder.getBaseDirectory(), throwable); - return action; - } - - /** - * Registers the data set. - */ - private void registerDataSetAndInitiateProcessing(final String procedureTypeCode) - throws Throwable - { - final File markerFile = createProcessingMarkerFile(); - try - { - String entityDescription = createEntityDescription(); - if (TransferredDataSetHandler.operationLog.isInfoEnabled()) - { - TransferredDataSetHandler.operationLog.info("Start storing data set for " - + entityDescription + "."); - } - final StopWatch watch = new StopWatch(); - watch.start(); - NewExternalData data = createExternalData(); - preRegistrationAction.execute(data.getCode(), incomingDataSetFile.getAbsolutePath()); - File dataFile = - getStorageProcessor().storeData(dataSetInformation, getTypeExtractor(), - 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."; - 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 availableFormat = getStorageProcessor().getStorageFormat(); - final BooleanOrUnknown isCompleteFlag = dataSetInformation.getIsCompleteFlag(); - // Ensure that we either register the data set and initiate the processing copy or - // do none of both. - getRegistrationLock().lock(); - try - { - errorMessageTemplate = - TransferredDataSetHandlerDataSetRegistrationAlgorithm.DATA_SET_REGISTRATION_FAILURE_TEMPLATE; - plainRegisterDataSet(data, relativePath, availableFormat, isCompleteFlag); - postRegistrationAction.execute(data.getCode(), absolutePath); - clean(); - } finally - { - getRegistrationLock().unlock(); - } - getStorageProcessor().commit(incomingDataSetFile, - baseDirectoryHolder.getBaseDirectory()); - } finally - { - getFileOperations().delete(markerFile); - } - } - - private String createEntityDescription() - { - SampleIdentifier sampleIdentifier = dataSetInformation.getSampleIdentifier(); - if (sampleIdentifier != null) - { - return "sample '" + sampleIdentifier + "'"; - } - return "experiment '" + dataSetInformation.getExperimentIdentifier() + "'"; - } - - private final File createProcessingMarkerFile() - { - final File baseDirectory = baseDirectoryHolder.getBaseDirectory(); - final File baseParentDirectory = baseDirectory.getParentFile(); - final String processingDirName = baseDirectory.getName(); - final File 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; + return algorithm.registerDataSet(); } /** @@ -299,10 +120,10 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm final boolean ok = shouldDeleteUnidentified() ? (removeAndLog(incomingDataSetFile.getName() + " could not be identified.")) : FileRenamer.renameAndLog( - incomingDataSetFile, baseDirectoryHolder.getTargetFile()); + incomingDataSetFile, algorithm.getBaseDirectoryHolder().getTargetFile()); if (ok) { - clean(); + algorithm.clean(); } } @@ -316,15 +137,6 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm return ok; } - private final void plainRegisterDataSet(NewExternalData data, final String relativePath, - final StorageFormat storageFormat, final BooleanOrUnknown isCompleteFlag) - throws Throwable - { - updateExternalData(data, relativePath, storageFormat, isCompleteFlag); - // Finally: register the data set in the database. - registerDataSetInApplicationServer(data); - } - /** * Contact openBis and register the data set there. Subclasses may override. * @@ -332,79 +144,7 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm */ protected void registerDataSetInApplicationServer(NewExternalData data) throws Throwable { - getOpenBisService().registerDataSet(dataSetInformation, data); - } - - private void logAndNotifySuccessfulRegistration(final String email) - { - String msg = null; - if (TransferredDataSetHandler.operationLog.isInfoEnabled()) - { - msg = getSuccessRegistrationMessage(); - TransferredDataSetHandler.operationLog.info(msg); - } - if (shouldNotifySuccessfulRegistration()) - { - if (msg == null) - { - msg = getSuccessRegistrationMessage(); - } - if (TransferredDataSetHandler.notificationLog.isInfoEnabled()) - { - TransferredDataSetHandler.notificationLog.info(msg); - } - if (StringUtils.isBlank(email) == false) - { - getMailClient().sendMessage( - String.format(getEmailSubjectTemplate(), dataSetInformation - .getExperimentIdentifier().getExperimentCode()), msg, null, null, - email); - } - } - } - - private final String getSuccessRegistrationMessage() - { - final StringBuilder buffer = new StringBuilder(); - buffer.append(TransferredDataSetHandlerDataSetRegistrationAlgorithm.SUCCESSFULLY_REGISTERED); - 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); - buffer.append(']'); - return buffer.toString(); - } - - 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(";"); - } + getOpenBisService().registerDataSet(algorithm.getDataSetInformation(), data); } /** @@ -449,48 +189,12 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm } } - protected final File createBaseDirectory(final IDataStoreStrategy strategy, final File baseDir, - final DataSetInformation dataSetInfo) - { - final File baseDirectory = strategy.getBaseDirectory(baseDir, dataSetInfo, dataSetType); - 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 final NewExternalData updateExternalData(NewExternalData data, - final String relativePath, final StorageFormat storageFormat, - final BooleanOrUnknown isCompleteFlag) - { - data.setComplete(isCompleteFlag); - data.setLocation(relativePath); - data.setStorageFormat(storageFormat); - return data; - } - - private NewExternalData createExternalData() - { - final NewExternalData data = new NewExternalData(); - data.setUserId(dataSetInformation.getUploadingUserIdOrNull()); - data.setUserEMail(dataSetInformation.tryGetUploadingUserEmail()); - data.setExtractableData(dataSetInformation.getExtractableData()); - data.setLocatorType(getTypeExtractor().getLocatorType(incomingDataSetFile)); - data.setDataSetType(getTypeExtractor().getDataSetType(incomingDataSetFile)); - data.setFileFormatType(getTypeExtractor().getFileFormatType(incomingDataSetFile)); - data.setMeasured(getTypeExtractor().isMeasuredData(incomingDataSetFile)); - data.setDataStoreCode(getDataStoreCode()); - return data; - } - protected final void writeThrowable(final Throwable throwable) { final String fileName = incomingDataSetFile.getName() + ".exception"; - final File file = new File(baseDirectoryHolder.getTargetFile().getParentFile(), fileName); + final File file = + new File(algorithm.getBaseDirectoryHolder().getTargetFile().getParentFile(), + fileName); FileWriter writer = null; try { @@ -507,9 +211,9 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm } } - protected boolean clean() + public void rollback(DataSetRegistrationAlgorithm algo, Throwable ex) { - return cleanAftrewardsAction.execute(); + rollback(ex); } // subclass responsibility @@ -546,5 +250,4 @@ public abstract class TransferredDataSetHandlerDataSetRegistrationAlgorithm protected abstract boolean shouldNotifySuccessfulRegistration(); - protected abstract String getEmailSubjectTemplate(); } \ No newline at end of file diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/api/v1/PutDataSetExecutor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/api/v1/PutDataSetExecutor.java index c74b2ea29bc..ef1b0c8e2a9 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/api/v1/PutDataSetExecutor.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/api/v1/PutDataSetExecutor.java @@ -524,12 +524,6 @@ class PutDataSetExecutor implements IDataSetHandlerRpc return service.getDataStrategyStore(); } - @Override - protected String getEmailSubjectTemplate() - { - return TransferredDataSetHandlerDataSetRegistrationAlgorithm.EMAIL_SUBJECT_TEMPLATE; - } - @Override protected IFileOperations getFileOperations() { @@ -587,7 +581,7 @@ class PutDataSetExecutor implements IDataSetHandlerRpc @Override protected void rollback(Throwable ex) { - rollbackStorageProcessor(ex); + algorithm.rollbackStorageProcessor(ex); if (ex instanceof UserFailureException) { throw (UserFailureException) ex; -- GitLab