From 26d817f7290cdbcce11292cf509c2051a2063a8c Mon Sep 17 00:00:00 2001 From: cramakri <cramakri> Date: Tue, 13 Sep 2011 08:59:48 +0000 Subject: [PATCH] LMS-2436 Added support for notifying client code of errors with secondary transactions SVN: 22903 --- ...tOmniscientTopLevelDataSetRegistrator.java | 16 +++++- .../DataSetRegistrationService.java | 8 +++ .../JythonTopLevelDataSetHandler.java | 51 +++++++++++++++++- .../v1/IDataSetRegistrationTransaction.java | 4 ++ .../api/v1/SecondaryTransactionFailure.java | 54 +++++++++++++++++++ .../api/v1/impl/AbstractTransactionState.java | 21 +++++++- .../impl/DataSetRegistrationTransaction.java | 14 +++++ 7 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/SecondaryTransactionFailure.java 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 9269f6550c0..1760346d142 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 @@ -49,6 +49,7 @@ import ch.systemsx.cisd.etlserver.ITopLevelDataSetRegistratorDelegate; import ch.systemsx.cisd.etlserver.PropertiesBasedETLServerPlugin; import ch.systemsx.cisd.etlserver.TopLevelDataSetRegistratorGlobalState; import ch.systemsx.cisd.etlserver.registrator.IDataSetOnErrorActionDecision.ErrorType; +import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure; import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.DataSetRegistrationTransaction; import ch.systemsx.cisd.etlserver.utils.PostRegistrationExecutor; import ch.systemsx.cisd.etlserver.utils.PreRegistrationExecutor; @@ -453,6 +454,18 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator<T extends Dat { } + /** + * A method called when there is an error in one of the secondary transactions. + * <p> + * Subclasses can override and implement their own handling logic. + */ + public void didEncounterSecondaryTransactionErrors( + DataSetRegistrationService<T> dataSetRegistrationService, + DataSetRegistrationTransaction<T> transaction, + List<SecondaryTransactionFailure> secondaryErrors) + { + } + /** * Rollback a failure that occurs outside of any *particular* data set registration, but with * the whole processing of the incoming folder itself. @@ -477,7 +490,8 @@ public abstract class AbstractOmniscientTopLevelDataSetRegistrator<T extends Dat final IDelegatedActionWithResult<Boolean> cleanAfterwardsAction, ITopLevelDataSetRegistratorDelegate delegate) { - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings( + { "unchecked", "rawtypes" }) DataSetRegistrationService<T> service = new DataSetRegistrationService(this, incomingDataSetFile, new DefaultDataSetRegistrationDetailsFactory(getRegistratorState(), 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 a937efa69b1..c32f960d40c 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 @@ -38,6 +38,7 @@ import ch.systemsx.cisd.etlserver.TopLevelDataSetRegistratorGlobalState; import ch.systemsx.cisd.etlserver.registrator.AbstractOmniscientTopLevelDataSetRegistrator.OmniscientTopLevelDataSetRegistratorState; import ch.systemsx.cisd.etlserver.registrator.IDataSetOnErrorActionDecision.ErrorType; import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSetRegistrationTransaction; +import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure; import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.DataSetRegistrationTransaction; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; @@ -211,6 +212,13 @@ public class DataSetRegistrationService<T extends DataSetInformation> implements registrator.didCommitTransaction(this, transaction); } + public void didEncounterSecondaryTransactionErrors( + DataSetRegistrationTransaction<T> transaction, + List<SecondaryTransactionFailure> secondaryErrors) + { + registrator.didEncounterSecondaryTransactionErrors(this, transaction, secondaryErrors); + } + /** * Create a storage algorithm for storing an individual data set. This is internally used by * transactions. Other clients may find it useful as well. 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 9ef358fcf7e..7b21e7a2ea4 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 @@ -17,6 +17,7 @@ package ch.systemsx.cisd.etlserver.registrator; import java.io.File; +import java.util.List; import org.python.core.Py; import org.python.core.PyException; @@ -30,6 +31,7 @@ import ch.systemsx.cisd.common.utilities.PropertyUtils; import ch.systemsx.cisd.etlserver.DataSetRegistrationAlgorithm; import ch.systemsx.cisd.etlserver.ITopLevelDataSetRegistratorDelegate; import ch.systemsx.cisd.etlserver.TopLevelDataSetRegistratorGlobalState; +import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure; import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.DataSetRegistrationTransaction; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; @@ -52,10 +54,17 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends private static final String ROLLBACK_TRANSACTION_FUNCTION_NAME = "rollback_transaction"; /** - * The name of the function being called after successful transaction commit. + * The name of the function called after successful transaction commit. */ private static final String COMMIT_TRANSACTION_FUNCTION_NAME = "commit_transaction"; + /** + * The name of the function called when secondary transactions, DynamicTransactionQuery objects, + * fail. + */ + private static final String DID_ENCOUNTER_SECONDARY_TRANSACTION_ERRORS_FUNCTION_NAME = + "did_encounter_secondary_transaction_errors"; + private static final String FACTORY_VARIABLE_NAME = "factory"; /** @@ -185,6 +194,16 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends invokeCommitTransactionFunction(service, transaction); } + @Override + public void didEncounterSecondaryTransactionErrors(DataSetRegistrationService<T> service, + DataSetRegistrationTransaction<T> transaction, + List<SecondaryTransactionFailure> secondaryErrors) + { + super.didEncounterSecondaryTransactionErrors(service, transaction, secondaryErrors); + + invokeDidEncounterSecondaryTransactionErrorsFunction(service, transaction, secondaryErrors); + } + private void invokeRollbackTransactionFunction(DataSetRegistrationService<T> service, DataSetRegistrationTransaction<T> transaction, DataSetStorageAlgorithmRunner<T> algorithmRunner, Throwable ex) @@ -217,6 +236,21 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends } } + private void invokeDidEncounterSecondaryTransactionErrorsFunction( + DataSetRegistrationService<T> service, DataSetRegistrationTransaction<T> transaction, + List<SecondaryTransactionFailure> secondaryErrors) + { + PythonInterpreter interpreter = getInterpreterFromService(service); + PyFunction function = + tryJythonFunction(interpreter, + DID_ENCOUNTER_SECONDARY_TRANSACTION_ERRORS_FUNCTION_NAME); + if (null != function) + { + invokeDidEncounterSecondaryTransactionErrorsFunction(function, service, transaction, + secondaryErrors); + } + } + private PyFunction tryJythonFunction(PythonInterpreter interpreter, String functionName) { try @@ -261,12 +295,25 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends Py.java2py(throwable)); } + /** + * Pulled out as a separate method so tests can hook in. + */ protected void invokeCommitTransactionFunction(PyFunction function, DataSetRegistrationService<T> service, DataSetRegistrationTransaction<T> transaction) { function.__call__(Py.java2py(service), Py.java2py(transaction)); } + /** + * Pulled out as a separate method so tests can hook in. + */ + protected void invokeDidEncounterSecondaryTransactionErrorsFunction(PyFunction function, + DataSetRegistrationService<T> service, DataSetRegistrationTransaction<T> transaction, + List<SecondaryTransactionFailure> secondaryErrors) + { + function.__call__(Py.java2py(service), Py.java2py(transaction), Py.java2py(secondaryErrors)); + } + /** * Set the factory available to the python script. Subclasses may want to override. */ @@ -301,7 +348,7 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends { return createDataSetRegistrationDetails(); } - + /** * Returns the Java class for the given class name. */ diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java index 433b859efed..4c7065faec8 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/IDataSetRegistrationTransaction.java @@ -219,6 +219,10 @@ public interface IDataSetRegistrationTransaction /** * Gets a database query object for the data source with the specified name. + * <p> + * After the rest of the transaction is committed, the queries are committed. Failures in these + * secondary queries are not fatal, but they are caught and the clients of the transaction are + * notified. * * @param dataSourceName The name of the data source to query against, as declared in the * service.properties file. diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/SecondaryTransactionFailure.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/SecondaryTransactionFailure.java new file mode 100644 index 00000000000..02c781cc3df --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/api/v1/SecondaryTransactionFailure.java @@ -0,0 +1,54 @@ +/* + * 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; + +import net.lemnik.eodsql.DynamicTransactionQuery; + +/** + * A simple bean for capturing the contextual information about a failure in a secondary transaction + * + * @author Chandrasekhar Ramakrishnan + */ +public class SecondaryTransactionFailure +{ + private final DynamicTransactionQuery query; + + private final Throwable error; + + public SecondaryTransactionFailure(DynamicTransactionQuery query, Throwable error) + { + this.query = query; + this.error = error; + } + + public DynamicTransactionQuery getQuery() + { + return query; + } + + public Throwable getError() + { + return error; + } + + @Override + public String toString() + { + return "SecondaryTransactionFailure [query=" + query + ", error=" + error + "]"; + } + +} 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 index 6654248f8af..7b3d1b8ec89 100644 --- 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 @@ -39,6 +39,7 @@ import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterial; import ch.systemsx.cisd.etlserver.registrator.api.v1.IProject; import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample; import ch.systemsx.cisd.etlserver.registrator.api.v1.ISpace; +import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure; import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.IDataSetImmutable; import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.IExperimentImmutable; @@ -484,10 +485,26 @@ abstract class AbstractTransactionState<T extends DataSetInformation> DataSetStorageAlgorithmRunner<T> runner = new DataSetStorageAlgorithmRunner<T>(algorithms, parent, parent); List<DataSetInformation> datasets = runner.prepareAndRunStorageAlgorithms(); + + // The queries are optional parts of the commit; catch any errors and inform the + // invoker + ArrayList<SecondaryTransactionFailure> encounteredErrors = + new ArrayList<SecondaryTransactionFailure>(); for (DynamicTransactionQuery query : queriesToCommit.values()) { - query.commit(); - query.close(false); + try + { + query.commit(); + query.close(false); + } catch (Throwable e) + { + encounteredErrors.add(new SecondaryTransactionFailure(query, e)); + } + } + + if (false == encounteredErrors.isEmpty()) + { + parent.invokeDidEncounterSecondaryTransactionErrors(encounteredErrors); } return datasets.isEmpty() == false; 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 df300a357c3..3876217f826 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 @@ -44,6 +44,7 @@ import ch.systemsx.cisd.etlserver.registrator.api.v1.IMaterial; import ch.systemsx.cisd.etlserver.registrator.api.v1.IProject; import ch.systemsx.cisd.etlserver.registrator.api.v1.ISample; import ch.systemsx.cisd.etlserver.registrator.api.v1.ISpace; +import ch.systemsx.cisd.etlserver.registrator.api.v1.SecondaryTransactionFailure; 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; @@ -471,4 +472,17 @@ public class DataSetRegistrationTransaction<T extends DataSetInformation> implem { return getStateAsLiveState().getDatabaseQuery(dataSourceName); } + + void invokeDidEncounterSecondaryTransactionErrors( + List<SecondaryTransactionFailure> encounteredErrors) + { + try + { + registrationService.didEncounterSecondaryTransactionErrors(this, encounteredErrors); + } catch (Throwable t) + { + DataSetRegistrationTransaction.operationLog.warn( + "Failed to invoke secondary transaction error hook:" + t.getMessage(), t); + } + } } -- GitLab