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