Repository: oozie
Updated Branches:
  refs/heads/master a436d9829 -> a54f7c20d


http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/main/resources/oozie-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/oozie-default.xml 
b/core/src/main/resources/oozie-default.xml
index 5629a89..832bbe1 100644
--- a/core/src/main/resources/oozie-default.xml
+++ b/core/src/main/resources/oozie-default.xml
@@ -1490,9 +1490,13 @@ will be the requeue interval for the actions which are 
waiting for a long time w
 
     <property>
         <name>oozie.service.JPAService.connection.data.source</name>
-        <value>org.apache.commons.dbcp.BasicDataSource</value>
+        <value>org.apache.oozie.util.db.BasicDataSourceWrapper</value>
         <description>
-            DataSource to be used for connection pooling.
+            DataSource to be used for connection pooling. If you want the 
property
+            openJpa.connectionProperties="DriverClassName=..." to have a real 
effect, set this to
+            org.apache.oozie.util.db.BasicDataSourceWrapper.
+            A DBCP bug (https://issues.apache.org/jira/browse/DBCP-333) 
prevents otherwise the JDBC driver
+            setting to have a real effect while using custom class loader.
         </description>
     </property>
 
@@ -1563,6 +1567,31 @@ will be the requeue interval for the actions which are 
waiting for a long time w
        </description>
     </property>
 
+    <property>
+        <name>oozie.service.JPAService.retry.initial-wait-time.ms</name>
+        <value>100</value>
+        <description>
+          Initial wait time in milliseconds between the first failed database 
operation and the re-attempted operation. The wait
+          time is doubled at each retry.
+       </description>
+    </property>
+
+    <property>
+        <name>oozie.service.JPAService.retry.maximum-wait-time.ms</name>
+        <value>30000</value>
+        <description>
+          Maximum wait time between database retry attempts.
+       </description>
+    </property>
+
+    <property>
+        <name>oozie.service.JPAService.retry.max-retries</name>
+        <value>10</value>
+        <description>
+          Maximum number of retries for a failed database operation.
+       </description>
+    </property>
+
    <!-- SchemaService -->
 
     <property>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/main/resources/oozie-log4j.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/oozie-log4j.properties 
b/core/src/main/resources/oozie-log4j.properties
index c065f3c..ba8d7b9 100644
--- a/core/src/main/resources/oozie-log4j.properties
+++ b/core/src/main/resources/oozie-log4j.properties
@@ -59,3 +59,4 @@ log4j.logger.org.apache.oozie.sla=DEBUG, test
 log4j.logger.org.apache.hadoop=INFO, test
 log4j.logger.org.mortbay=INFO, test
 log4j.logger.org.hsqldb=INFO, test
+log4j.logger.org.apache.oozie.util.db=INFO, test

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/test/java/org/apache/oozie/command/SkipCommitFaultInjection.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/oozie/command/SkipCommitFaultInjection.java 
b/core/src/test/java/org/apache/oozie/command/SkipCommitFaultInjection.java
deleted file mode 100644
index 158fbfa..0000000
--- a/core/src/test/java/org/apache/oozie/command/SkipCommitFaultInjection.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.oozie.command;
-
-import org.apache.oozie.FaultInjection;
-
-public class SkipCommitFaultInjection extends FaultInjection {
-
-    public static final String ACTION_FAILOVER_FAULT_INJECTION = 
"oozie.fault.injection.action.failover";
-
-    private static boolean ACTIVE = false;
-
-    public boolean activate() {
-        ACTIVE = 
Boolean.parseBoolean(System.getProperty(ACTION_FAILOVER_FAULT_INJECTION, 
"false"));
-        return ACTIVE;
-    }
-
-    public void deactivate() {
-        ACTIVE = false;
-    }
-
-    public boolean isActive() {
-        return ACTIVE;
-    }
-}

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/test/java/org/apache/oozie/test/XTestCase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/oozie/test/XTestCase.java 
b/core/src/test/java/org/apache/oozie/test/XTestCase.java
index 2816e5d..0ef4a4b 100644
--- a/core/src/test/java/org/apache/oozie/test/XTestCase.java
+++ b/core/src/test/java/org/apache/oozie/test/XTestCase.java
@@ -41,7 +41,8 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import javax.persistence.EntityManager;
 import javax.persistence.FlushModeType;
-import javax.persistence.Query;
+import javax.persistence.PersistenceException;
+import javax.persistence.TypedQuery;
 
 import junit.framework.TestCase;
 
@@ -96,6 +97,8 @@ import org.apache.oozie.util.IOUtils;
 import org.apache.oozie.util.ParamChecker;
 import org.apache.oozie.util.XConfiguration;
 import org.apache.oozie.util.XLog;
+import org.apache.openjpa.persistence.ArgumentException;
+import org.apache.openjpa.persistence.RollbackException;
 
 /**
  * Base JUnit <code>TestCase</code> subclass used by all Oozie testcases.
@@ -832,71 +835,29 @@ public abstract class XTestCase extends TestCase {
         entityManager.setFlushMode(FlushModeType.COMMIT);
         entityManager.getTransaction().begin();
 
-        Query q = entityManager.createNamedQuery("GET_WORKFLOWS");
-        List<WorkflowJobBean> wfjBeans = q.getResultList();
-        int wfjSize = wfjBeans.size();
-        for (WorkflowJobBean w : wfjBeans) {
-            entityManager.remove(w);
-        }
-
-        q = entityManager.createNamedQuery("GET_ACTIONS");
-        List<WorkflowActionBean> wfaBeans = q.getResultList();
-        int wfaSize = wfaBeans.size();
-        for (WorkflowActionBean w : wfaBeans) {
-            entityManager.remove(w);
-        }
-
-        q = entityManager.createNamedQuery("GET_COORD_JOBS");
-        List<CoordinatorJobBean> cojBeans = q.getResultList();
-        int cojSize = cojBeans.size();
-        for (CoordinatorJobBean w : cojBeans) {
-            entityManager.remove(w);
-        }
-
-        q = entityManager.createNamedQuery("GET_COORD_ACTIONS");
-        List<CoordinatorActionBean> coaBeans = q.getResultList();
-        int coaSize = coaBeans.size();
-        for (CoordinatorActionBean w : coaBeans) {
-            entityManager.remove(w);
-        }
-
-        q = entityManager.createNamedQuery("GET_BUNDLE_JOBS");
-        List<BundleJobBean> bjBeans = q.getResultList();
-        int bjSize = bjBeans.size();
-        for (BundleJobBean w : bjBeans) {
-            entityManager.remove(w);
-        }
-
-        q = entityManager.createNamedQuery("GET_BUNDLE_ACTIONS");
-        List<BundleActionBean> baBeans = q.getResultList();
-        int baSize = baBeans.size();
-        for (BundleActionBean w : baBeans) {
-            entityManager.remove(w);
-        }
+        final int wfjSize = getCountAndRemoveAll(entityManager, 
"GET_WORKFLOWS", WorkflowJobBean.class);
+        final int wfaSize = getCountAndRemoveAll(entityManager, "GET_ACTIONS", 
WorkflowActionBean.class);
+        final int cojSize = getCountAndRemoveAll(entityManager, 
"GET_COORD_JOBS", CoordinatorJobBean.class);
+        final int coaSize = getCountAndRemoveAll(entityManager, 
"GET_COORD_ACTIONS", CoordinatorActionBean.class);
+        final int bjSize = getCountAndRemoveAll(entityManager, 
"GET_BUNDLE_JOBS", BundleJobBean.class);
+        final int baSize = getCountAndRemoveAll(entityManager, 
"GET_BUNDLE_ACTIONS", BundleActionBean.class);
+        final int slaSize = getCountAndRemoveAll(entityManager, 
"GET_SLA_EVENTS", SLAEventBean.class);
+        final int slaRegSize = getCountAndRemoveAll(entityManager, 
"GET_ACTIONS", SLARegistrationBean.class);
+        final int ssSize = getCountAndRemoveAll(entityManager, 
"GET_SLA_SUMMARY_ALL", SLASummaryBean.class);
 
-        q = entityManager.createNamedQuery("GET_SLA_EVENTS");
-        List<SLAEventBean> slaBeans = q.getResultList();
-        int slaSize = slaBeans.size();
-        for (SLAEventBean w : slaBeans) {
-            entityManager.remove(w);
-        }
+        try {
+            if (entityManager.getTransaction().isActive()) {
+                entityManager.getTransaction().commit();
+            }
 
-        q = entityManager.createQuery("select OBJECT(w) from 
SLARegistrationBean w");
-        List<SLARegistrationBean> slaRegBeans = q.getResultList();
-        int slaRegSize = slaRegBeans.size();
-        for (SLARegistrationBean w : slaRegBeans) {
-            entityManager.remove(w);
+            if (entityManager.isOpen()) {
+                entityManager.close();
+            }
         }
-
-        q = entityManager.createQuery("select OBJECT(w) from SLASummaryBean 
w");
-        List<SLASummaryBean> sdBeans = q.getResultList();
-        int ssSize = sdBeans.size();
-        for (SLASummaryBean w : sdBeans) {
-            entityManager.remove(w);
+        catch (final RollbackException e) {
+            log.warn("Cannot commit current transaction. [e.message={0}]", 
e.getMessage());
         }
 
-        entityManager.getTransaction().commit();
-        entityManager.close();
         log.info(wfjSize + " entries in WF_JOBS removed from DB!");
         log.info(wfaSize + " entries in WF_ACTIONS removed from DB!");
         log.info(cojSize + " entries in COORD_JOBS removed from DB!");
@@ -906,7 +867,30 @@ public abstract class XTestCase extends TestCase {
         log.info(slaSize + " entries in SLA_EVENTS removed from DB!");
         log.info(slaRegSize + " entries in SLA_REGISTRATION removed from DB!");
         log.info(ssSize + " entries in SLA_SUMMARY removed from DB!");
+    }
 
+    private <E> int getCountAndRemoveAll(final EntityManager entityManager,
+                                                        final String queryName,
+                                                        final Class<E> 
entityClass) {
+        try {
+            final TypedQuery<E> getAllQuery = 
entityManager.createNamedQuery(queryName, entityClass);
+            final List<E> allEntities = getAllQuery.getResultList();
+            final int entitiesCount = allEntities.size();
+
+            for (final E w : allEntities) {
+                entityManager.remove(w);
+            }
+
+            return entitiesCount;
+        } catch (final RollbackException e) {
+            log.warn("Cannot get count or remove all entities. 
[queryName={0};entityClass.name={1}]",
+                    queryName, entityClass.getName());
+            return 0;
+        } catch (final PersistenceException | ArgumentException e) {
+            log.warn("Cannot get count or remove all entities. 
[queryName={0};entityClass.name={1}]",
+                    queryName, entityClass.getName());
+            return 0;
+        }
     }
 
     private static MiniDFSCluster dfsCluster = null;

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/test/java/org/apache/oozie/util/db/TestOozieDmlStatementPredicate.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/oozie/util/db/TestOozieDmlStatementPredicate.java
 
b/core/src/test/java/org/apache/oozie/util/db/TestOozieDmlStatementPredicate.java
new file mode 100644
index 0000000..c2d4b15
--- /dev/null
+++ 
b/core/src/test/java/org/apache/oozie/util/db/TestOozieDmlStatementPredicate.java
@@ -0,0 +1,63 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.util.db;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class TestOozieDmlStatementPredicate {
+
+    private final FailingConnectionWrapper.OozieDmlStatementPredicate 
statementPredicate =
+            new FailingConnectionWrapper.OozieDmlStatementPredicate();
+
+    @Test
+    public void testDmlNotOozieTableDoesNotApply() {
+        assertFalse("DML statement but not of an Oozie table",
+                statementPredicate.apply("SELECT * FROM wellhello"));
+    }
+
+    @Test
+    public void testNotDmlOozieTableDoesNotApply() {
+        assertFalse("not a DML statement but of an Oozie table",
+                statementPredicate.apply("CREATE TABLE WF_JOBS"));
+    }
+
+    @Test
+    public void testNotDmlNotOozieTableDoesNotApply() {
+        assertFalse("not a DML statement and not of an Oozie table",
+                statementPredicate.apply("CREATE TABLE wellhello"));
+    }
+
+    @Test
+    public void testDmlAndOozieTableAppliesIgnoreCase() {
+        assertTrue("a DML statement and of an Oozie table",
+                statementPredicate.apply("SELECT * FROM wf_jobs"));
+
+        assertTrue("a DML statement and of an Oozie table",
+                statementPredicate.apply("insert into WF_JOBS"));
+
+        assertTrue("a DML statement and of an Oozie table",
+                statementPredicate.apply("update wf_jobs"));
+
+        assertTrue("a DML statement and of an Oozie table",
+                statementPredicate.apply("DELETE FROM WF_JOBS"));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/test/java/org/apache/oozie/util/db/TestOperationRetryHandler.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/oozie/util/db/TestOperationRetryHandler.java 
b/core/src/test/java/org/apache/oozie/util/db/TestOperationRetryHandler.java
new file mode 100644
index 0000000..ae94199
--- /dev/null
+++ b/core/src/test/java/org/apache/oozie/util/db/TestOperationRetryHandler.java
@@ -0,0 +1,218 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.util.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.willAnswer;
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.Callable;
+
+import org.apache.commons.lang.mutable.MutableInt;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.google.common.base.Predicate;
+
+/**
+ * Conventions used in the tests:
+ *  <ul>
+ *      <li>{@code RuntimeException} indicates an SQL error (network failure) 
where we should retry</li>
+ *      <li>{@code IllegalStateException} is a different SQL error where we 
should not retry</li>
+ *  </ul>
+ */
+public class TestOperationRetryHandler {
+
+    private final Predicate<Throwable> testRetryPredicate = new 
Predicate<Throwable>() {
+        @Override
+        public boolean apply(Throwable input) {
+            return input.getClass() == RuntimeException.class;
+        }
+    };
+
+    private final OperationRetryHandler retryHandler = new 
OperationRetryHandler(3, 1, 1, testRetryPredicate);
+
+    @Test
+    public void testWhenConfiguredNotToRetryThenTriesOnce() throws Exception {
+        tryOnceAndAssert(new OperationRetryHandler(0, 1, 100, 
testRetryPredicate));
+        tryOnceAndAssert(new OperationRetryHandler(10, 1, 0, 
testRetryPredicate));
+        tryOnceAndAssert(new OperationRetryHandler(0, 1, 0, 
testRetryPredicate));
+    }
+
+    private void tryOnceAndAssert(final OperationRetryHandler 
tryingOnceRetryHandler) throws Exception {
+        @SuppressWarnings("unchecked")
+        final Callable<String> operation = mock(Callable.class);
+        final MutableInt callCount = new MutableInt(0);
+
+        willAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                callCount.increment();
+                return null;
+            }
+        }).given(operation).call();
+
+        tryingOnceRetryHandler.executeWithRetry(operation);
+
+        assertCallCount(1, callCount);
+        assertNoRetryAttemptsAreInProgressOrExhausted();
+    }
+
+    @Test
+    public void testNoRetry() throws Exception {
+        @SuppressWarnings("unchecked")
+        Callable<String> operation = mock(Callable.class);
+        given(operation.call()).willReturn("dummy");
+
+        retryHandler.executeWithRetry(operation);
+    }
+
+    @Test
+    public void testRetriesOnFailure() throws Exception {
+        @SuppressWarnings("unchecked")
+        Callable<String> operation = mock(Callable.class);
+        final MutableInt callCount = new MutableInt(0);
+
+        willAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                callCount.increment();
+                throw new RuntimeException();
+            }
+        }).given(operation).call();
+
+        boolean exceptionThrown = false;
+        try {
+            retryHandler.executeWithRetry(operation);
+        } catch (RuntimeException e) {
+            exceptionThrown = true;
+        }
+
+        assertTrue("Exception was not thrown", exceptionThrown);
+        assertCallCount(3, callCount);
+        assertNoRetryAttemptsAreInProgressOrExhausted();
+    }
+
+    @Test
+    public void testRetriesOnFailureRecovery() throws Exception {
+        @SuppressWarnings("unchecked")
+        Callable<String> operation = mock(Callable.class);
+        final MutableInt callCount = new MutableInt(0);
+
+        willAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                callCount.increment();
+                if (callCount.intValue() < 2) {
+                    throw new RuntimeException();
+                }
+                return null;
+            }
+        }).given(operation).call();
+
+        retryHandler.executeWithRetry(operation);
+
+        assertCallCount(2, callCount);
+        assertNoRetryAttemptsAreInProgressOrExhausted();
+    }
+
+    @Test
+    public void testNoRetriesOnNonSQLError() throws Exception {
+        @SuppressWarnings("unchecked")
+        Callable<String> operation = mock(Callable.class);
+        final MutableInt callCount = new MutableInt(0);
+
+        willAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                callCount.increment();
+                throw new IllegalStateException();
+            }
+        }).given(operation).call();
+
+        boolean exceptionThrown = false;
+        try {
+            retryHandler.executeWithRetry(operation);
+        } catch (IllegalStateException e) {
+            exceptionThrown = true;
+        }
+
+        assertTrue("Exception was not thrown", exceptionThrown);
+        assertCallCount(1, callCount);
+        assertNoRetryAttemptsAreInProgressOrExhausted();
+    }
+
+    @Test
+    public void testEmbeddedRetryHandlersWhenInnerHandlerThrowsSQLError() 
throws Exception {
+        @SuppressWarnings("unchecked")
+        final Callable<String> innerOperation = mock(Callable.class);
+        final MutableInt callCount = new MutableInt(0);
+
+        // internal operation which will be retried
+        willAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                callCount.increment();
+                throw new RuntimeException();
+            }
+        }).given(innerOperation).call();
+
+        @SuppressWarnings("unchecked")
+        Callable<String> outerOperation = mock(Callable.class);
+
+        // the outer operation which calls the inner one and this is not
+        // supposed to be retried
+        willAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                try {
+                    retryHandler.executeWithRetry(innerOperation);
+                } catch (Exception e) {
+                    throw new RuntimeException();
+                }
+                return null;
+            }
+        }).given(outerOperation).call();
+
+        boolean exceptionThrown = false;
+        try {
+            retryHandler.executeWithRetry(outerOperation);
+        } catch (Exception e) {
+            exceptionThrown = true;
+        }
+
+        assertTrue("Exception was not thrown", exceptionThrown);
+        assertCallCount(3, callCount);
+        assertNoRetryAttemptsAreInProgressOrExhausted();
+    }
+
+    private void assertCallCount(int expected, MutableInt callCount) {
+        assertEquals("Number of retries", expected, callCount.intValue());
+    }
+
+    private void assertNoRetryAttemptsAreInProgressOrExhausted() {
+        assertEquals("Nesting level", 0,
+                
OperationRetryHandler.RETRY_ATTEMPT_STATE.getInProgressCount());
+        assertEquals("Retries performed", false,
+                OperationRetryHandler.RETRY_ATTEMPT_STATE.isExhausted());
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/test/java/org/apache/oozie/util/db/TestPersistenceExceptionSubclassFilterRetryPredicate.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/oozie/util/db/TestPersistenceExceptionSubclassFilterRetryPredicate.java
 
b/core/src/test/java/org/apache/oozie/util/db/TestPersistenceExceptionSubclassFilterRetryPredicate.java
new file mode 100644
index 0000000..76d2edc
--- /dev/null
+++ 
b/core/src/test/java/org/apache/oozie/util/db/TestPersistenceExceptionSubclassFilterRetryPredicate.java
@@ -0,0 +1,98 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.util.db;
+
+import org.apache.oozie.ErrorCode;
+import org.apache.oozie.XException;
+import org.apache.oozie.executor.jpa.JPAExecutorException;
+import org.junit.Test;
+
+import javax.persistence.EntityExistsException;
+import javax.persistence.EntityNotFoundException;
+import javax.persistence.LockTimeoutException;
+import javax.persistence.NoResultException;
+import javax.persistence.NonUniqueResultException;
+import javax.persistence.OptimisticLockException;
+import javax.persistence.PersistenceException;
+import javax.persistence.PessimisticLockException;
+import javax.persistence.QueryTimeoutException;
+import javax.persistence.RollbackException;
+import javax.persistence.TransactionRequiredException;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class TestPersistenceExceptionSubclassFilterRetryPredicate {
+    private final PersistenceExceptionSubclassFilterRetryPredicate predicate =
+            new PersistenceExceptionSubclassFilterRetryPredicate();
+
+    @Test
+    public void testFilteredJPAExceptions() {
+        assertFalse(predicate.apply(new EntityExistsException()));
+        assertFalse(predicate.apply(new EntityNotFoundException()));
+        assertFalse(predicate.apply(new LockTimeoutException()));
+        assertFalse(predicate.apply(new NoResultException()));
+        assertFalse(predicate.apply(new NonUniqueResultException()));
+        assertFalse(predicate.apply(new OptimisticLockException()));
+        assertFalse(predicate.apply(new PessimisticLockException()));
+        assertFalse(predicate.apply(new QueryTimeoutException()));
+        assertFalse(predicate.apply(new TransactionRequiredException()));
+    }
+
+    @Test
+    public void testNotFilteredJPAExceptions() {
+        assertTrue(predicate.apply(new RollbackException()));
+        assertTrue(predicate.apply(new PersistenceException()));
+    }
+
+    @Test
+    public void testNonJPAExceptions() {
+        assertTrue(predicate.apply(new RuntimeException()));
+        assertTrue(predicate.apply(new Exception()));
+    }
+
+    @Test
+    public void testNestedFilteredJPAExceptions() {
+        assertFalse(predicate.apply(wrapCause(new EntityExistsException())));
+        assertFalse(predicate.apply(wrapCause(new EntityNotFoundException())));
+        assertFalse(predicate.apply(wrapCause(new LockTimeoutException())));
+        assertFalse(predicate.apply(wrapCause(new NoResultException())));
+        assertFalse(predicate.apply(wrapCause(new 
NonUniqueResultException())));
+        assertFalse(predicate.apply(wrapCause(new OptimisticLockException())));
+        assertFalse(predicate.apply(wrapCause(new 
PessimisticLockException())));
+        assertFalse(predicate.apply(wrapCause(new QueryTimeoutException())));
+        assertFalse(predicate.apply(wrapCause(new 
TransactionRequiredException())));
+    }
+
+    @Test
+    public void testNestedNotFilteredJPAExceptions() {
+        assertTrue(predicate.apply(wrapCause(new RollbackException())));
+        assertTrue(predicate.apply(wrapCause(new PersistenceException())));
+    }
+
+    @Test
+    public void testNestedNonJPAExceptions() {
+        assertTrue(predicate.apply(wrapCause(new RuntimeException())));
+        assertTrue(predicate.apply(wrapCause(new Exception())));
+    }
+
+    private JPAExecutorException wrapCause(final Throwable cause) {
+        return new JPAExecutorException(new XException(ErrorCode.E0603, new 
PersistenceException(cause)));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/core/src/test/java/org/apache/oozie/util/db/TestRetryAttemptState.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/oozie/util/db/TestRetryAttemptState.java 
b/core/src/test/java/org/apache/oozie/util/db/TestRetryAttemptState.java
new file mode 100644
index 0000000..0158538
--- /dev/null
+++ b/core/src/test/java/org/apache/oozie/util/db/TestRetryAttemptState.java
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.util.db;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class TestRetryAttemptState {
+    private final RetryAttemptState state = new RetryAttemptState();
+
+    @Test
+    public void testOneAttemptIsInProgressAndAttemptsAreNotExhausted() {
+        state.signalStart();
+
+        assertFalse("retry attempts exhausted", state.isExhausted());
+        assertEquals("retry attempt count", 1, state.getInProgressCount());
+    }
+
+    @Test
+    public void testNoAttemptIsInProgressAndAttemptsAreNotExhausted() {
+        state.signalStart();
+        state.signalEnd();
+
+        assertFalse("retry attempts exhausted", state.isExhausted());
+        assertEquals("retry attempt count", 0, state.getInProgressCount());
+    }
+
+    @Test
+    public void testExhaustedFromOneThreadRemainsNotStartedFromAnotherThread() 
throws InterruptedException {
+        final Runnable exhaustingAttempt = new ExhaustingAttempt();
+
+        final Thread exhaustingThread = new Thread(exhaustingAttempt);
+
+        exhaustingThread.start();
+
+        exhaustingThread.join();
+
+        assertFalse("retry attempts exhausted", state.isExhausted());
+        assertEquals("retry attempt count", 0, state.getInProgressCount());
+    }
+
+    @Test
+    public void testNestedAttemptsFinishSuccessfullyWhenNotExhausted() throws 
InterruptedException {
+        final NestedAttempt inner = new NestedAttempt(false);
+        final NestedAttempt outer = new NestedAttempt(inner, false);
+
+        outer.run();
+
+        assertFalse("retry attempts exhausted", state.isExhausted());
+        assertEquals("retry attempt count", 0, state.getInProgressCount());
+    }
+
+    @Test
+    public void testOuterAttemptRemainsUnfinishedWhenInnerExhausted() throws 
InterruptedException {
+        final NestedAttempt inner = new NestedAttempt(true);
+        final NestedAttempt outer = new NestedAttempt(inner, false);
+
+        outer.run();
+
+        assertTrue("retry attempts exhausted", state.isExhausted());
+        assertEquals("retry attempt count", 1, state.getInProgressCount());
+    }
+
+    @Test
+    public void testInnerAttemptRemainsUnfinishedWhenOuterExhausted() throws 
InterruptedException {
+        final NestedAttempt inner = new NestedAttempt(false);
+        final NestedAttempt outer = new NestedAttempt(inner, true);
+
+        outer.run();
+
+        assertTrue("retry attempts exhausted", state.isExhausted());
+        assertEquals("retry attempt count", 1, state.getInProgressCount());
+    }
+
+    private class ExhaustingAttempt implements Runnable {
+        @Override
+        public void run() {
+            state.signalStart();
+
+            state.signalExhausted();
+        }
+    }
+
+    private class NestedAttempt implements Runnable {
+        private final NestedAttempt nestedAttempt;
+        private final boolean shouldExhaust;
+
+        NestedAttempt(final boolean shouldExhaust) {
+            this(null, shouldExhaust);
+        }
+
+        NestedAttempt(final NestedAttempt nestedAttempt, final boolean 
shouldExhaust) {
+            this.nestedAttempt = nestedAttempt;
+            this.shouldExhaust = shouldExhaust;
+        }
+
+        @Override
+        public void run() {
+            state.signalStart();
+
+            if (nestedAttempt != null) {
+                nestedAttempt.run();
+            }
+
+            if (shouldExhaust) {
+                state.signalExhausted();
+            }
+            else {
+                state.signalEnd();
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/docs/src/site/twiki/AG_Install.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/AG_Install.twiki 
b/docs/src/site/twiki/AG_Install.twiki
index 2125442..3509266 100644
--- a/docs/src/site/twiki/AG_Install.twiki
+++ b/docs/src/site/twiki/AG_Install.twiki
@@ -198,6 +198,37 @@ following configuration properties in the oozie-site.xml:
   oozie.service.JPAService.pool.max.active.conn=10
 </verbatim>
 
+If you are interested in fine tuning how Oozie can retry database operations 
on failing database connectivity or errors, you can
+set following properties to other values. Here are the default ones:
+
+<verbatim>
+  oozie.service.JPAService.retry.initial-wait-time.ms=100
+  oozie.service.JPAService.retry.maximum-wait-time.ms=30000
+  oozie.service.JPAService.retry.max-retries=10
+</verbatim>
+
+If you set either =oozie.service.JPAService.retry.max-retries= or 
=oozie.service.JPAService.retry.maximum-wait-time.ms= to =0=,
+no retry attempts will be made on any database connectivity issues. Exact 
settings for these properties depend also on how much load
+is on Oozie regarding workflow and coordinator jobs.
+
+The database operation retry functionality kicks in when there is a 
=javax.persistence.PersistenceException= those root cause is not
+part of the normal everyday operation - filtered against a blacklist 
consisting of descendants like =NoSuchResultException=,
+=NonUniqueResultException=, and the like. This way Oozie won't retry database 
operations on errors that are more related to the
+current query, or otherwise part of the everyday life. This way it's ensured 
that this blacklist is database agnostic.
+
+It has been tested with a MySQL / failing every minute 10 seconds / an Oozie 
coordinator job of an Oozie workflow consisting of four
+workflow actions (some of them are asynchronous). On this setup Oozie was 
recovering after each and every database outage.
+
+To set up such a failing MySQL scenario following has to be performed:
+
+   * Set =oozie.service.JPAService.connection.data.source= to 
=org.apache.oozie.util.db.BasicDataSourceWrapper=
+   within =oozie-site.xml=
+   * Set =oozie.service.JPAService.jdbc.driver= to 
=org.apache.oozie.util.db.FailingMySQLDriverWrapper= within =oozie-site.xml=
+   * Restart Oozie server
+   * Submit / start some workflows, coordinators etc.
+   * See how Oozie is retrying on injected database errors by looking at the 
Oozie server logs, grepping =JPAException= instances
+   with following message prefix: <verbatim>Deliberately failing to prepare 
statement.</verbatim>
+
 ---++ Database Migration
 
 Oozie provides an easy way to switch between databases without losing any 
data. Oozie servers should be stopped during the

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/pom.xml
----------------------------------------------------------------------
diff --git a/minitest/pom.xml b/minitest/pom.xml
index 9515284..10a89b0 100644
--- a/minitest/pom.xml
+++ b/minitest/pom.xml
@@ -48,13 +48,13 @@
         <dependency>
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-mapper-asl</artifactId>
-            <version>1.5.2</version>
+            <version>1.8.8</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-core-asl</artifactId>
-            <version>1.5.2</version>
+            <version>1.8.8</version>
             <scope>test</scope>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/java/org/apache/oozie/test/TestParallelJPAOperationRetries.java
----------------------------------------------------------------------
diff --git 
a/minitest/src/test/java/org/apache/oozie/test/TestParallelJPAOperationRetries.java
 
b/minitest/src/test/java/org/apache/oozie/test/TestParallelJPAOperationRetries.java
new file mode 100644
index 0000000..5442130
--- /dev/null
+++ 
b/minitest/src/test/java/org/apache/oozie/test/TestParallelJPAOperationRetries.java
@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.test;
+
+import org.apache.oozie.WorkflowActionBean;
+import org.apache.oozie.service.JPAService;
+import org.apache.oozie.service.Services;
+import org.apache.oozie.service.XLogService;
+import org.apache.oozie.util.XLog;
+import org.apache.oozie.util.db.FailingHSQLDBDriverWrapper;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * To test that when a JDBC driver fails, {@link JPAService} continues working:
+ * <ul>
+ *     <li>use the {@link java.sql.Connection} wrapper that is used by the 
wrapper extending {@link org.hsqldb.jdbcDriver},
+ *     and set the system property {@link 
FailingHSQLDBDriverWrapper#USE_FAILING_DRIVER}</li>
+ *     <li>initialize {@link JPAService} and a JPA {@link 
javax.persistence.EntityManagerFactory}</li>
+ *     <li>issue a decent number of JPA queries w/ appropriate parallelism via 
an {@link java.util.concurrent.Executor}</li>
+ *     <li>an {@link TypedQuery#getResultList()} resulting in a {@code SELECT 
… FROM WF_JOBS WHERE ...} Oozie database read</li>
+ *     <li>followed by a {@link TypedQuery#executeUpdate()} resulting in an 
{@code UPDATE WF_ACTIONS SET … WHERE …} Oozie database
+ *     write</li>
+ * </ul>
+ */
+public class TestParallelJPAOperationRetries extends MiniOozieTestCase {
+    private static final XLog LOG = 
XLog.getLog(TestParallelJPAOperationRetries.class);
+    private static final String ORIGINAL_LOG4J_FILE = 
System.getProperty(XLogService.LOG4J_FILE);
+
+    private volatile Exception executorException;
+
+    @Override
+    protected void setUp() throws Exception {
+        executorException = null;
+        System.setProperty(XLogService.LOG4J_FILE, "oozie-log4j.properties");
+        System.setProperty(FailingHSQLDBDriverWrapper.USE_FAILING_DRIVER, 
Boolean.TRUE.toString());
+
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        if (ORIGINAL_LOG4J_FILE != null) {
+            System.setProperty(XLogService.LOG4J_FILE, ORIGINAL_LOG4J_FILE);
+        }
+    }
+
+    public void testParallelJPAOperationsOnWorkflowBeansRetryAndSucceed() 
throws Exception {
+        final ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
+        final int totalTaskCount = 10;
+        final CountDownLatch countDownLatch = new 
CountDownLatch(totalTaskCount);
+
+        for (int ixTask = 1; ixTask <= totalTaskCount; ixTask++) {
+            final int taskCount = ixTask;
+            taskExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        LOG.debug("Task #{0} started.", taskCount);
+
+                        getResultListAndExecuteUpdateOnWorkflowBeans();
+
+                        LOG.debug("Task #{0} finished.", taskCount);
+                    }
+                    catch (final SQLException e) {
+                        executorException = e;
+                    }
+                    finally {
+                        countDownLatch.countDown();
+                    }
+                }
+            });
+        }
+
+        countDownLatch.await();
+
+        if (executorException != null) {
+            fail(String.format("Should not get an SQLException while executing 
SQL operations. [e.message=%s]",
+                    executorException.getMessage()));
+        }
+
+        taskExecutor.shutdown();
+
+        try {
+            taskExecutor.awaitTermination(1, TimeUnit.MINUTES);
+        } catch (final InterruptedException e) {
+            fail("Should not get an interrupt while shutting down 
ExecutorService.");
+        }
+    }
+
+    private void getResultListAndExecuteUpdateOnWorkflowBeans() throws 
SQLException {
+        final JPAService jpaService = Services.get().get(JPAService.class);
+
+        final int checkAgeSecs = 86_400;
+        final Timestamp ts = new Timestamp(System.currentTimeMillis() - 
checkAgeSecs * 1000);
+
+        final EntityManager em = jpaService.getEntityManager();
+        final TypedQuery<WorkflowActionBean> q = 
em.createNamedQuery("GET_RUNNING_ACTIONS", WorkflowActionBean.class);
+        q.setParameter("lastCheckTime", ts);
+        final List<WorkflowActionBean> actions = q.getResultList();
+
+        assertEquals("no WorkflowActionBeans should be present", 0, 
actions.size());
+
+        if (!em.getTransaction().isActive()) {
+            em.getTransaction().begin();
+        }
+
+        final int rowsAffected = 
em.createNamedQuery("UPDATE_ACTION_FOR_LAST_CHECKED_TIME")
+                .setParameter("lastCheckTime", 0l)
+                .setParameter("id", "666")
+                .executeUpdate();
+        if (em.getTransaction().isActive()) {
+            em.getTransaction().commit();
+        }
+
+        assertEquals("no rows should be affected when updating 
WorkflowActionBeans", 0, rowsAffected);
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/java/org/apache/oozie/test/TestWorkflow.java
----------------------------------------------------------------------
diff --git a/minitest/src/test/java/org/apache/oozie/test/TestWorkflow.java 
b/minitest/src/test/java/org/apache/oozie/test/TestWorkflow.java
new file mode 100644
index 0000000..4257b60
--- /dev/null
+++ b/minitest/src/test/java/org/apache/oozie/test/TestWorkflow.java
@@ -0,0 +1,215 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.test;
+
+import com.google.common.base.Strings;
+import org.apache.oozie.action.hadoop.JavaActionExecutor;
+import org.apache.oozie.client.OozieClientException;
+import org.apache.oozie.service.XLogService;
+import org.apache.oozie.client.OozieClient;
+import org.apache.oozie.client.WorkflowJob;
+import org.apache.oozie.local.LocalOozie;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.io.OutputStreamWriter;
+import java.util.Date;
+import java.util.Properties;
+
+import static org.junit.Assume.assumeFalse;
+
+/**
+ * {@code MiniOozie} integration test for different workflow kinds.
+ */
+public class TestWorkflow extends MiniOozieTestCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        System.setProperty(XLogService.LOG4J_FILE, "oozie-log4j.properties");
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testWorkflowWithStartAndEndCompletesSuccessfully() throws 
Exception {
+        final String wfApp = "<workflow-app xmlns='uri:oozie:workflow:0.1' 
name='test-wf'>" + "    <start to='end'/>"
+                + "    <end name='end'/>" + "</workflow-app>";
+
+        final FileSystem fs = getFileSystem();
+        final Path appPath = new Path(getFsTestCaseDir(), "app");
+        fs.mkdirs(appPath);
+        fs.mkdirs(new Path(appPath, "lib"));
+
+        final Writer writer = new OutputStreamWriter(fs.create(new 
Path(appPath, "workflow.xml")));
+        writer.write(wfApp);
+        writer.close();
+
+        final OozieClient wc = LocalOozie.getClient();
+
+        final Properties conf = wc.createConfiguration();
+        conf.setProperty(OozieClient.APP_PATH, new Path(appPath, 
"workflow.xml").toString());
+        conf.setProperty(OozieClient.USER_NAME, getTestUser());
+
+
+        final String jobId = wc.submit(conf);
+        assertNotNull(jobId);
+
+        WorkflowJob wf = wc.getJobInfo(jobId);
+        assertNotNull(wf);
+        assertEquals(WorkflowJob.Status.PREP, wf.getStatus());
+
+        wc.start(jobId);
+
+        waitFor(1000, new Predicate() {
+            public boolean evaluate() throws Exception {
+                final WorkflowJob wf = wc.getJobInfo(jobId);
+                return wf.getStatus() == WorkflowJob.Status.SUCCEEDED;
+            }
+        });
+
+        wf = wc.getJobInfo(jobId);
+        assertNotNull(wf);
+        assertEquals(WorkflowJob.Status.SUCCEEDED, wf.getStatus());
+    }
+
+    public void testFsDecisionWorkflowCompletesSuccessfully() throws Exception 
{
+        final String workflowFileName = "fs-decision.xml";
+        final Properties additionalWorkflowProperties = new Properties();
+
+        runWorkflowFromFile(workflowFileName, additionalWorkflowProperties);
+    }
+
+    public void testParallelFsAndShellWorkflowCompletesSuccessfully() throws 
Exception {
+        final String workflowFileName = "parallel-fs-and-shell.xml";
+        final Properties additionalWorkflowProperties = new Properties();
+        final boolean isEvenSecond = new Date().getTime() % 2 == 0;
+        additionalWorkflowProperties.setProperty("choosePath1", 
Boolean.toString(isEvenSecond));
+
+        final String envJavaHome = System.getenv("JAVA_HOME");
+        assumeFalse("Environment variable JAVA_HOME has to be set", 
Strings.isNullOrEmpty(envJavaHome));
+
+        additionalWorkflowProperties.setProperty("oozie.launcher." + 
JavaActionExecutor.YARN_AM_ENV, envJavaHome);
+
+        runWorkflowFromFile(workflowFileName, additionalWorkflowProperties);
+    }
+
+    private void runWorkflowFromFile(final String workflowFileName, final 
Properties additionalWorkflowProperties)
+            throws IOException, OozieClientException {
+        final FileSystem fs = getFileSystem();
+        final Path appPath = new Path(getFsTestCaseDir(), "app");
+        fs.mkdirs(appPath);
+        fs.mkdirs(new Path(appPath, "lib"));
+
+        final Reader reader = getResourceAsReader(workflowFileName, -1);
+        final Writer writer = new OutputStreamWriter(fs.create(new 
Path(appPath, "workflow.xml")));
+        copyCharStream(reader, writer);
+        writer.close();
+        reader.close();
+
+        final Path path = getFsTestCaseDir();
+
+        final OozieClient oozieClient = LocalOozie.getClient();
+
+        final Properties conf = oozieClient.createConfiguration();
+        conf.setProperty(OozieClient.APP_PATH, new Path(appPath, 
"workflow.xml").toString());
+        conf.setProperty(OozieClient.USER_NAME, getTestUser());
+        conf.setProperty("nameNodeBasePath", path.toString());
+        conf.setProperty("base", path.toUri().getPath());
+        conf.setProperty("nameNode", getNameNodeUri());
+        conf.setProperty("jobTracker", getJobTrackerUri());
+
+        for (final String additionalKey : 
additionalWorkflowProperties.stringPropertyNames()) {
+            conf.setProperty(additionalKey, 
additionalWorkflowProperties.getProperty(additionalKey));
+        }
+
+        final String jobId = oozieClient.submit(conf);
+        assertNotNull(jobId);
+
+        WorkflowJob wf = oozieClient.getJobInfo(jobId);
+        assertNotNull(wf);
+        assertEquals(WorkflowJob.Status.PREP, wf.getStatus());
+
+        oozieClient.start(jobId);
+
+        waitFor(15 * 1000, new Predicate() {
+            public boolean evaluate() throws Exception {
+                final WorkflowJob wf = oozieClient.getJobInfo(jobId);
+                return wf.getStatus() == WorkflowJob.Status.SUCCEEDED;
+            }
+        });
+
+        wf = oozieClient.getJobInfo(jobId);
+        assertNotNull(wf);
+        assertEquals(WorkflowJob.Status.SUCCEEDED, wf.getStatus());
+    }
+
+    /**
+     * Return a classpath resource as a stream.
+     * <p/>
+     *
+     * @param path classpath for the resource.
+     * @param maxLen max content length allowed.
+     * @return the stream for the resource.
+     * @throws IOException thrown if the resource could not be read.
+     */
+    private InputStream getResourceAsStream(final String path, final int 
maxLen) throws IOException {
+        final InputStream is = 
Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
+        if (is == null) {
+            throw new IllegalArgumentException("resource " + path + " not 
found");
+        }
+        return is;
+    }
+
+    /**
+     * Return a classpath resource as a reader.
+     * <p/>
+     * It is assumed that the resource is a text resource.
+     *
+     * @param path classpath for the resource.
+     * @param maxLen max content length allowed.
+     * @return the reader for the resource.
+     * @throws IOException thrown if the resource could not be read.
+     */
+    private Reader getResourceAsReader(final String path, final int maxLen) 
throws IOException {
+        return new InputStreamReader(getResourceAsStream(path, maxLen));
+    }
+
+    /**
+     * Copies an char input stream into an char output stream.
+     *
+     * @param reader reader to copy from.
+     * @param writer writer to copy to.
+     * @throws IOException thrown if the copy failed.
+     */
+    private void copyCharStream(final Reader reader, final Writer writer) 
throws IOException {
+        final char[] buffer = new char[4096];
+        int read;
+        while ((read = reader.read(buffer)) > -1) {
+            writer.write(buffer, 0, read);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/java/org/apache/oozie/test/TestWorkflowRetries.java
----------------------------------------------------------------------
diff --git 
a/minitest/src/test/java/org/apache/oozie/test/TestWorkflowRetries.java 
b/minitest/src/test/java/org/apache/oozie/test/TestWorkflowRetries.java
new file mode 100644
index 0000000..5ae1bb4
--- /dev/null
+++ b/minitest/src/test/java/org/apache/oozie/test/TestWorkflowRetries.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.oozie.test;
+
+import org.apache.oozie.util.db.FailingHSQLDBDriverWrapper;
+
+/**
+ * {@code MiniOozie} integration test for different workflow kinds, using a 
HSQLDB driver that fails sometimes.
+ */
+public class TestWorkflowRetries extends TestWorkflow {
+
+    @Override
+    protected void setUp() throws Exception {
+        System.setProperty(FailingHSQLDBDriverWrapper.USE_FAILING_DRIVER, 
Boolean.TRUE.toString());
+
+        super.setUp();
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/java/org/apache/oozie/test/WorkflowTest.java
----------------------------------------------------------------------
diff --git a/minitest/src/test/java/org/apache/oozie/test/WorkflowTest.java 
b/minitest/src/test/java/org/apache/oozie/test/WorkflowTest.java
deleted file mode 100644
index 2845f0a..0000000
--- a/minitest/src/test/java/org/apache/oozie/test/WorkflowTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.oozie.test;
-
-import org.apache.oozie.service.XLogService;
-import org.apache.oozie.test.MiniOozieTestCase;
-import org.apache.oozie.client.OozieClient;
-import org.apache.oozie.client.WorkflowJob;
-import org.apache.oozie.local.LocalOozie;
-import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.Path;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.Writer;
-import java.io.OutputStreamWriter;
-import java.util.Properties;
-
-/**
- * MiniOozie unit test
- */
-public class WorkflowTest extends MiniOozieTestCase {
-
-    @Override
-    protected void setUp() throws Exception {
-        System.setProperty(XLogService.LOG4J_FILE, "oozie-log4j.properties");
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    public void testWorkflowRun() throws Exception {
-        String wfApp = "<workflow-app xmlns='uri:oozie:workflow:0.1' 
name='test-wf'>" + "    <start to='end'/>"
-                + "    <end name='end'/>" + "</workflow-app>";
-
-        FileSystem fs = getFileSystem();
-        Path appPath = new Path(getFsTestCaseDir(), "app");
-        fs.mkdirs(appPath);
-        fs.mkdirs(new Path(appPath, "lib"));
-
-        Writer writer = new OutputStreamWriter(fs.create(new Path(appPath, 
"workflow.xml")));
-        writer.write(wfApp);
-        writer.close();
-
-        final OozieClient wc = LocalOozie.getClient();
-
-        Properties conf = wc.createConfiguration();
-        conf.setProperty(OozieClient.APP_PATH, new Path(appPath, 
"workflow.xml").toString());
-        conf.setProperty(OozieClient.USER_NAME, getTestUser());
-
-
-        final String jobId = wc.submit(conf);
-        assertNotNull(jobId);
-
-        WorkflowJob wf = wc.getJobInfo(jobId);
-        assertNotNull(wf);
-        assertEquals(WorkflowJob.Status.PREP, wf.getStatus());
-
-        wc.start(jobId);
-
-        waitFor(1000, new Predicate() {
-            public boolean evaluate() throws Exception {
-                WorkflowJob wf = wc.getJobInfo(jobId);
-                return wf.getStatus() == WorkflowJob.Status.SUCCEEDED;
-            }
-        });
-
-        wf = wc.getJobInfo(jobId);
-        assertNotNull(wf);
-        assertEquals(WorkflowJob.Status.SUCCEEDED, wf.getStatus());
-
-    }
-
-    public void testWorkflowRunFromFile() throws Exception {
-        FileSystem fs = getFileSystem();
-        Path appPath = new Path(getFsTestCaseDir(), "app");
-        fs.mkdirs(appPath);
-        fs.mkdirs(new Path(appPath, "lib"));
-
-        Reader reader = getResourceAsReader("wf-test.xml", -1);
-        Writer writer = new OutputStreamWriter(fs.create(new Path(appPath, 
"workflow.xml")));
-        copyCharStream(reader, writer);
-        writer.close();
-        reader.close();
-
-        Path path = getFsTestCaseDir();
-
-        final OozieClient wc = LocalOozie.getClient();
-
-        Properties conf = wc.createConfiguration();
-        conf.setProperty(OozieClient.APP_PATH, new Path(appPath, 
"workflow.xml").toString());
-        conf.setProperty(OozieClient.USER_NAME, getTestUser());
-        conf.setProperty("nnbase", path.toString());
-        conf.setProperty("base", path.toUri().getPath());
-
-
-
-        final String jobId = wc.submit(conf);
-        assertNotNull(jobId);
-
-        WorkflowJob wf = wc.getJobInfo(jobId);
-        assertNotNull(wf);
-        assertEquals(WorkflowJob.Status.PREP, wf.getStatus());
-
-        wc.start(jobId);
-
-        waitFor(15 * 1000, new Predicate() {
-            public boolean evaluate() throws Exception {
-                WorkflowJob wf = wc.getJobInfo(jobId);
-                return wf.getStatus() == WorkflowJob.Status.SUCCEEDED;
-            }
-        });
-
-        wf = wc.getJobInfo(jobId);
-        assertNotNull(wf);
-        assertEquals(WorkflowJob.Status.SUCCEEDED, wf.getStatus());
-
-    }
-
-    /**
-     * Return a classpath resource as a stream.
-     * <p/>
-     *
-     * @param path classpath for the resource.
-     * @param maxLen max content length allowed.
-     * @return the stream for the resource.
-     * @throws IOException thrown if the resource could not be read.
-     */
-    public InputStream getResourceAsStream(String path, int maxLen) throws 
IOException {
-        InputStream is = 
Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
-        if (is == null) {
-            throw new IllegalArgumentException("resource " + path + " not 
found");
-        }
-        return is;
-    }
-
-    /**
-     * Return a classpath resource as a reader.
-     * <p/>
-     * It is assumed that the resource is a text resource.
-     *
-     * @param path classpath for the resource.
-     * @param maxLen max content length allowed.
-     * @return the reader for the resource.
-     * @throws IOException thrown if the resource could not be read.
-     */
-    public Reader getResourceAsReader(String path, int maxLen) throws 
IOException {
-        return new InputStreamReader(getResourceAsStream(path, maxLen));
-    }
-
-    /**
-     * Copies an char input stream into an char output stream.
-     *
-     * @param reader reader to copy from.
-     * @param writer writer to copy to.
-     * @throws IOException thrown if the copy failed.
-     */
-    public void copyCharStream(Reader reader, Writer writer) throws 
IOException {
-        char[] buffer = new char[4096];
-        int read;
-        while ((read = reader.read(buffer)) > -1) {
-            writer.write(buffer, 0, read);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/resources/fs-decision.xml
----------------------------------------------------------------------
diff --git a/minitest/src/test/resources/fs-decision.xml 
b/minitest/src/test/resources/fs-decision.xml
new file mode 100644
index 0000000..72ac07a
--- /dev/null
+++ b/minitest/src/test/resources/fs-decision.xml
@@ -0,0 +1,51 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you 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.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.1" name="fs-decision">
+    <start to="fs1"/>
+    <action name="fs1">
+        <fs>
+            <mkdir path="${nameNodeBasePath}/p1"/>
+        </fs>
+        <ok to="fs2"/>
+        <error to="k"/>
+    </action>
+    <action name="fs2">
+        <fs>
+            <move source="${nameNodeBasePath}/p1" target="${base}/p2"/>
+        </fs>
+        <ok to="dec3"/>
+        <error to="k"/>
+    </action>
+    <decision name="dec3">
+        <switch>
+            <case to="fs4">${fs:exists(concat(nameNodeBasePath, '/p2'))}</case>
+            <default to="k"/>
+        </switch>
+    </decision>
+    <action name="fs4">
+        <fs>
+            <move source="${nameNodeBasePath}/p2" target="${base}/p3"/>
+        </fs>
+        <ok to="end"/>
+        <error to="k"/>
+    </action>
+    <kill name="k">
+        <message>kill</message>
+    </kill>
+    <end name="end"/>
+</workflow-app>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/resources/hsqldb-oozie-site.xml
----------------------------------------------------------------------
diff --git a/minitest/src/test/resources/hsqldb-oozie-site.xml 
b/minitest/src/test/resources/hsqldb-oozie-site.xml
index fa5fe9c..2cdce74 100644
--- a/minitest/src/test/resources/hsqldb-oozie-site.xml
+++ b/minitest/src/test/resources/hsqldb-oozie-site.xml
@@ -19,8 +19,12 @@
 -->
 <configuration>
     <property>
+        <name>oozie.service.JPAService.connection.data.source</name>
+        <value>org.apache.oozie.util.db.BasicDataSourceWrapper</value>
+    </property>
+    <property>
         <name>oozie.service.JPAService.jdbc.driver</name>
-        <value>org.hsqldb.jdbcDriver</value>
+        <value>org.apache.oozie.util.db.FailingHSQLDBDriverWrapper</value>
     </property>
     <property>
         <name>oozie.service.JPAService.jdbc.url</name>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/resources/oozie-log4j.properties
----------------------------------------------------------------------
diff --git a/minitest/src/test/resources/oozie-log4j.properties 
b/minitest/src/test/resources/oozie-log4j.properties
index c142d72..2503851 100644
--- a/minitest/src/test/resources/oozie-log4j.properties
+++ b/minitest/src/test/resources/oozie-log4j.properties
@@ -34,3 +34,4 @@ log4j.logger.org.apache.openjpa=INFO, test
 log4j.logger.org.apache.hadoop=INFO, test
 log4j.logger.org.mortbay=INFO, test
 log4j.logger.org.hsqldb=INFO, test
+log4j.logger.org.apache.oozie.util.db=TRACE, test
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/resources/parallel-fs-and-shell.xml
----------------------------------------------------------------------
diff --git a/minitest/src/test/resources/parallel-fs-and-shell.xml 
b/minitest/src/test/resources/parallel-fs-and-shell.xml
new file mode 100644
index 0000000..7118fad
--- /dev/null
+++ b/minitest/src/test/resources/parallel-fs-and-shell.xml
@@ -0,0 +1,73 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you 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.
+-->
+<workflow-app name="parallel-fs-and-shell" xmlns="uri:oozie:workflow:0.5">
+    <start to="decision"/>
+    <kill name="kill">
+        <message>Action failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]</message>
+    </kill>
+    <action name="fs1">
+        <fs>
+            <delete path='${nameNodeBasePath}/user/${wf:user()}/fs1'/>
+            <mkdir path='${nameNodeBasePath}/user/${wf:user()}/fs1'/>
+            <touchz path='${nameNodeBasePath}/user/${wf:user()}/fs1/fs1.out'/>
+        </fs>
+        <ok to="shell1"/>
+        <error to="kill"/>
+    </action>
+    <action name="fs2">
+        <fs>
+            <delete path='${nameNodeBasePath}/user/${wf:user()}/fs2'/>
+            <mkdir path='${nameNodeBasePath}/user/${wf:user()}/fs2'/>
+            <touchz path='${nameNodeBasePath}/user/${wf:user()}/fs2/fs2.out'/>
+        </fs>
+        <ok to="shell2"/>
+        <error to="kill"/>
+    </action>
+    <decision name="decision">
+        <switch>
+            <case to="fs1">
+                ${ choosePath1 }
+            </case>
+            <case to="fs2">
+                ${ !choosePath1 }
+            </case>
+            <default to="End"/>
+        </switch>
+    </decision>
+    <action name="shell1">
+        <shell xmlns="uri:oozie:shell-action:0.1">
+            <job-tracker>${jobTracker}</job-tracker>
+            <name-node>${nameNode}</name-node>
+            <exec>ls</exec>
+            <capture-output/>
+        </shell>
+        <ok to="End"/>
+        <error to="kill"/>
+    </action>
+    <action name="shell2">
+        <shell xmlns="uri:oozie:shell-action:0.1">
+            <job-tracker>${jobTracker}</job-tracker>
+            <name-node>${nameNode}</name-node>
+            <exec>ls</exec>
+            <capture-output/>
+        </shell>
+        <ok to="End"/>
+        <error to="kill"/>
+    </action>
+    <end name="End"/>
+</workflow-app>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/minitest/src/test/resources/wf-test.xml
----------------------------------------------------------------------
diff --git a/minitest/src/test/resources/wf-test.xml 
b/minitest/src/test/resources/wf-test.xml
deleted file mode 100644
index 20c4946..0000000
--- a/minitest/src/test/resources/wf-test.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you 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.
--->
-<workflow-app xmlns="uri:oozie:workflow:0.1" name="recovery-wf">
-    <start to="fs1"/>
-    <action name="fs1">
-        <fs>
-            <mkdir path="${nnbase}/p1"/>
-        </fs>
-        <ok to="fs2"/>
-        <error to="k"/>
-    </action>
-    <action name="fs2">
-        <fs>
-            <move source="${nnbase}/p1" target="${base}/p2"/>
-        </fs>
-        <ok to="dec3"/>
-        <error to="k"/>
-    </action>
-    <decision name="dec3">
-        <switch>
-            <case to="fs4">${fs:exists(concat(nnbase, '/p2'))}</case>
-            <default to="k"/>
-        </switch>
-    </decision>
-    <action name="fs4">
-        <fs>
-            <move source="${nnbase}/p2" target="${base}/p3"/>
-        </fs>
-        <ok to="end"/>
-        <error to="k"/>
-    </action>
-    <kill name="k">
-        <message>kill</message>
-    </kill>
-    <end name="end"/>
-</workflow-app>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 16c5137..0d1105b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1282,7 +1282,7 @@
             <dependency>
                 <groupId>mysql</groupId>
                 <artifactId>mysql-connector-java</artifactId>
-                <version>5.1.6</version>
+                <version>5.1.34</version>
             </dependency>
 
             <dependency>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a54f7c20/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index cf5956a..7443f2c 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
 -- Oozie 5.0.0 release (trunk - unreleased)
 
+OOZIE-2854 Oozie should handle transient database problems (andras.piros via 
gezapeti)
 OOZIE-2371 Add docs for state transitions for WF Action states (daniel.becker 
via gezapeti)
 OOZIE-2911 Re-add test testWfActionKillChildJob and adapt it to OYA (gezapeti)
 OOZIE-2918 Delete LauncherMapper and its test (asasvari via pbacsko)

Reply via email to