From 4a95525684a89432186b8a881480beb18a976ae1 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sat, 4 Aug 2018 03:02:41 +0200 Subject: [PATCH] Add MappingCheckedFuture This class is moved here from MD-SAL, as CheckedFutures are not used there anymore. Change-Id: I84cda1d543a5bebdb17249943ee26ca8fe3f97bf JIRA: MDSAL-229 Signed-off-by: Robert Varga --- .../impl/AbstractForwardedTransaction.java | 2 +- .../sal/common/api/MappingCheckedFuture.java | 95 +++++++ .../api/data/AsyncWriteTransaction.java | 4 +- .../common/api/MappingCheckedFutureTest.java | 240 ++++++++++++++++++ .../compat/LegacyDOMStoreAdapter.java | 2 +- .../DOMStoreReadTransactionAdapter.java | 2 +- .../compat/LegacyDOMDataBrokerAdapter.java | 2 +- 7 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFuture.java create mode 100644 opendaylight/md-sal/sal-common-api/src/test/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFutureTest.java diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedTransaction.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedTransaction.java index ad8b60517c..86c57a3607 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedTransaction.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedTransaction.java @@ -11,11 +11,11 @@ import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; +import org.opendaylight.controller.md.sal.common.api.MappingCheckedFuture; import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction; -import org.opendaylight.mdsal.common.api.MappingCheckedFuture; import org.opendaylight.yangtools.concepts.Delegator; import org.opendaylight.yangtools.concepts.Identifiable; import org.opendaylight.yangtools.yang.binding.DataObject; diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFuture.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFuture.java new file mode 100644 index 0000000000..a847807b59 --- /dev/null +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFuture.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.common.api; + +import static java.util.Objects.requireNonNull; + +import com.google.common.util.concurrent.AbstractCheckedFuture; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import javax.annotation.Nonnull; + +/** + * An implementation of CheckedFuture that provides similar behavior for the get methods + * that the checkedGet methods provide. + * + *

For {@link CancellationException} and {@link InterruptedException}, the specified exception mapper + * is invoked to translate them to the checked exception type. + * + *

For {@link ExecutionException}, the mapper is invoked to translate the cause to the checked exception + * and a new ExecutionException is thrown with the translated cause. + * + * @author Thomas Pantelis + * + * @param The result type returned by this Future's get method + * @param The checked exception type + */ +public final class MappingCheckedFuture extends AbstractCheckedFuture { + private final Function mapper; + + private MappingCheckedFuture(final ListenableFuture delegate, final Function mapper) { + super(delegate); + this.mapper = requireNonNull(mapper); + } + + /** + * Creates a new MappingCheckedFuture that wraps the given {@link ListenableFuture} + * delegate. + * + * @param delegate the {@link ListenableFuture} to wrap + * @param mapper the mapping {@link Function} used to translate exceptions from the delegate + * @return a new MappingCheckedFuture + */ + public static MappingCheckedFuture create( + final ListenableFuture delegate, final Function mapper) { + return new MappingCheckedFuture<>(delegate, mapper); + } + + @Override + @SuppressWarnings("checkstyle:parameterName") + protected X mapException(@Nonnull final Exception e) { + return mapper.apply(e); + } + + private ExecutionException wrapInExecutionException(final String message, final Exception ex) { + return new ExecutionException(message, mapException(ex)); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + try { + return super.get(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw wrapInExecutionException("Operation was interrupted", e); + } catch (final CancellationException e) { + throw wrapInExecutionException("Operation was cancelled", e); + } catch (final ExecutionException e) { + throw wrapInExecutionException(e.getMessage(), e); + } + } + + @Override + public V get(final long timeout, @Nonnull final TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + try { + return super.get(timeout, unit); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw wrapInExecutionException("Operation was interrupted", e); + } catch (final CancellationException e) { + throw wrapInExecutionException("Operation was cancelled", e); + } catch (final ExecutionException e) { + throw wrapInExecutionException(e.getMessage(), e); + } + } +} diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java index 852c4e4886..bc18e91582 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java @@ -13,8 +13,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import javax.annotation.CheckReturnValue; import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.controller.md.sal.common.api.MappingCheckedFuture; import org.opendaylight.mdsal.common.api.CommitInfo; -import org.opendaylight.mdsal.common.api.MappingCheckedFuture; import org.opendaylight.yangtools.concepts.Path; import org.opendaylight.yangtools.util.concurrent.ExceptionMapper; @@ -384,7 +384,7 @@ public interface AsyncWriteTransaction

, D> extends AsyncTransa ExceptionMapper SUBMIT_EXCEPTION_MAPPER = new ExceptionMapper("submit", TransactionCommitFailedException.class) { @Override - protected TransactionCommitFailedException newWithCause(String message, Throwable cause) { + protected TransactionCommitFailedException newWithCause(final String message, final Throwable cause) { return new TransactionCommitFailedException(message, cause); } }; diff --git a/opendaylight/md-sal/sal-common-api/src/test/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFutureTest.java b/opendaylight/md-sal/sal-common-api/src/test/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFutureTest.java new file mode 100644 index 0000000000..bee3060a2e --- /dev/null +++ b/opendaylight/md-sal/sal-common-api/src/test/java/org/opendaylight/controller/md/sal/common/api/MappingCheckedFutureTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.common.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.SettableFuture; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Test; +import org.opendaylight.yangtools.util.concurrent.ExceptionMapper; + +/** + * Unit tests for MappingCheckedFuture. + * + * @author Thomas Pantelis + */ +public class MappingCheckedFutureTest { + + interface FutureInvoker { + void invokeGet(CheckedFuture future) throws Exception; + + Throwable extractWrappedTestEx(Exception from); + } + + static class TestException extends Exception { + private static final long serialVersionUID = 1L; + + TestException(final String message, final Throwable cause) { + super(message, cause); + } + } + + static final ExceptionMapper MAPPER = new ExceptionMapper( + "Test", TestException.class) { + + @Override + protected TestException newWithCause(final String message, final Throwable cause) { + return new TestException(message, cause); + } + }; + + static final FutureInvoker GET = new FutureInvoker() { + @Override + public void invokeGet(final CheckedFuture future) throws Exception { + future.get(); + } + + @Override + public Throwable extractWrappedTestEx(final Exception from) { + if (from instanceof ExecutionException) { + return from.getCause(); + } + + return from; + } + }; + + static final FutureInvoker TIMED_GET = new FutureInvoker() { + @Override + public void invokeGet(final CheckedFuture future) throws Exception { + future.get(1, TimeUnit.HOURS); + } + + @Override + public Throwable extractWrappedTestEx(final Exception from) { + if (from instanceof ExecutionException) { + return from.getCause(); + } + + return from; + } + }; + + static final FutureInvoker CHECKED_GET = new FutureInvoker() { + @Override + public void invokeGet(final CheckedFuture future) throws Exception { + future.checkedGet(); + } + + @Override + public Throwable extractWrappedTestEx(final Exception from) { + return from; + } + }; + + static final FutureInvoker TIMED_CHECKED_GET = new FutureInvoker() { + @Override + public void invokeGet(final CheckedFuture future) throws Exception { + future.checkedGet(50, TimeUnit.MILLISECONDS); + } + + @Override + public Throwable extractWrappedTestEx(final Exception from) { + return from; + } + }; + + @Test + public void testGet() throws Exception { + SettableFuture delegate = SettableFuture.create(); + MappingCheckedFuture future = MappingCheckedFuture.create(delegate, MAPPER); + delegate.set("test"); + assertEquals("get", "test", future.get()); + } + + @Test + public void testGetWithExceptions() throws Exception { + testExecutionException(GET, new RuntimeException()); + testExecutionException(GET, new TestException("mock", null)); + testCancellationException(GET); + testInterruptedException(GET); + } + + @Test + public void testTimedGet() throws Exception { + SettableFuture delegate = SettableFuture.create(); + MappingCheckedFuture future = MappingCheckedFuture.create(delegate, MAPPER); + delegate.set("test"); + assertEquals("get", "test", future.get(50, TimeUnit.MILLISECONDS)); + } + + @Test + public void testTimedGetWithExceptions() throws Exception { + testExecutionException(TIMED_GET, new RuntimeException()); + testCancellationException(TIMED_GET); + testInterruptedException(TIMED_GET); + } + + @Test + public void testCheckedGetWithExceptions() throws Exception { + testExecutionException(CHECKED_GET, new RuntimeException()); + testCancellationException(CHECKED_GET); + testInterruptedException(CHECKED_GET); + } + + @Test + public void testTimedCheckedWithExceptions() throws Exception { + testExecutionException(TIMED_CHECKED_GET, new RuntimeException()); + testCancellationException(TIMED_CHECKED_GET); + testInterruptedException(TIMED_CHECKED_GET); + } + + @SuppressWarnings("checkstyle:illegalCatch") + private static void testExecutionException(final FutureInvoker invoker, final Throwable cause) { + SettableFuture delegate = SettableFuture.create(); + MappingCheckedFuture mappingFuture = MappingCheckedFuture.create(delegate, MAPPER); + + delegate.setException(cause); + + try { + invoker.invokeGet(mappingFuture); + fail("Expected exception thrown"); + } catch (Exception e) { + Throwable expectedTestEx = invoker.extractWrappedTestEx(e); + assertNotNull("Expected returned exception is null", expectedTestEx); + assertEquals("Exception type", TestException.class, expectedTestEx.getClass()); + + if (cause instanceof TestException) { + assertNull("Expected null cause", expectedTestEx.getCause()); + } else { + assertSame("TestException cause", cause, expectedTestEx.getCause()); + } + } + } + + @SuppressWarnings("checkstyle:illegalCatch") + private static void testCancellationException(final FutureInvoker invoker) { + SettableFuture delegate = SettableFuture.create(); + MappingCheckedFuture mappingFuture = MappingCheckedFuture.create(delegate, MAPPER); + + mappingFuture.cancel(false); + + try { + invoker.invokeGet(mappingFuture); + fail("Expected exception thrown"); + } catch (Exception e) { + Throwable expectedTestEx = invoker.extractWrappedTestEx(e); + assertNotNull("Expected returned exception is null", expectedTestEx); + assertEquals("Exception type", TestException.class, expectedTestEx.getClass()); + assertEquals("TestException cause type", CancellationException.class, expectedTestEx.getCause().getClass()); + } + } + + @SuppressWarnings("checkstyle:illegalCatch") + private static void testInterruptedException(final FutureInvoker invoker) throws Exception { + SettableFuture delegate = SettableFuture.create(); + final MappingCheckedFuture mappingFuture = MappingCheckedFuture.create(delegate, MAPPER); + + final AtomicReference assertError = new AtomicReference<>(); + final CountDownLatch doneLatch = new CountDownLatch(1); + Thread thread = new Thread() { + @Override + public void run() { + try { + doInvoke(); + } catch (AssertionError e) { + assertError.set(e); + } finally { + doneLatch.countDown(); + } + } + + void doInvoke() { + try { + invoker.invokeGet(mappingFuture); + fail("Expected exception thrown"); + } catch (Exception e) { + Throwable expectedTestEx = invoker.extractWrappedTestEx(e); + assertNotNull("Expected returned exception is null", expectedTestEx); + assertEquals("Exception type", TestException.class, expectedTestEx.getClass()); + assertEquals("TestException cause type", InterruptedException.class, + expectedTestEx.getCause().getClass()); + } + } + }; + thread.start(); + + thread.interrupt(); + assertTrue("get call completed", doneLatch.await(5, TimeUnit.SECONDS)); + + if (assertError.get() != null) { + throw assertError.get(); + } + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/LegacyDOMStoreAdapter.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/LegacyDOMStoreAdapter.java index ae1c848656..981be07821 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/LegacyDOMStoreAdapter.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/LegacyDOMStoreAdapter.java @@ -16,6 +16,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import javax.annotation.Nonnull; import org.opendaylight.controller.cluster.datastore.DistributedDataStoreInterface; +import org.opendaylight.controller.md.sal.common.api.MappingCheckedFuture; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.sal.core.compat.ReadFailedExceptionAdapter; import org.opendaylight.controller.sal.core.spi.data.DOMStore; @@ -24,7 +25,6 @@ import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransactio import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransactionChain; import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; -import org.opendaylight.mdsal.common.api.MappingCheckedFuture; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; diff --git a/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/DOMStoreReadTransactionAdapter.java b/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/DOMStoreReadTransactionAdapter.java index 0e5efa2b3b..3c1e1bae12 100644 --- a/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/DOMStoreReadTransactionAdapter.java +++ b/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/DOMStoreReadTransactionAdapter.java @@ -14,9 +14,9 @@ import com.google.common.collect.ForwardingObject; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; +import org.opendaylight.controller.md.sal.common.api.MappingCheckedFuture; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction; -import org.opendaylight.mdsal.common.api.MappingCheckedFuture; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; diff --git a/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/LegacyDOMDataBrokerAdapter.java b/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/LegacyDOMDataBrokerAdapter.java index 6ae611675e..56981e9ad1 100644 --- a/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/LegacyDOMDataBrokerAdapter.java +++ b/opendaylight/md-sal/sal-dom-compat/src/main/java/org/opendaylight/controller/sal/core/compat/LegacyDOMDataBrokerAdapter.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.SettableFuture; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.common.api.MappingCheckedFuture; import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction; import org.opendaylight.controller.md.sal.common.api.data.DataStoreUnavailableException; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; @@ -41,7 +42,6 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain; import org.opendaylight.mdsal.common.api.CommitInfo; -import org.opendaylight.mdsal.common.api.MappingCheckedFuture; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; -- 2.36.6