--- /dev/null
+/*
+ * 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.yangtools.util.concurrent;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Utility exception mapper which translates an Exception to a specified type of Exception.
+ *
+ * This mapper is intended to be used with {@link com.google.common.util.concurrent.Futures#makeChecked(com.google.common.util.concurrent.ListenableFuture, Function)}
+ * <ul>
+ * <li>if exception is the specified type or one of its subclasses, it returns original exception.
+ * <li>if exception is {@link ExecutionException} and the cause is of the specified type, it returns the cause
+ * <li>otherwise returns an instance of the specified exception type with original exception as the cause.
+ * </ul>
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <X> the exception type
+ */
+public abstract class ExceptionMapper<X extends Exception> implements Function<Exception, X> {
+ private final Class<X> exceptionType;
+ private final String opName;
+
+ /**
+ * Constructor.
+ *
+ * @param opName the String prefix for exception messages.
+ * @param exceptionType the exception type to which to translate.
+ */
+ public ExceptionMapper(final String opName, final Class<X> exceptionType) {
+ this.exceptionType = Preconditions.checkNotNull(exceptionType);
+ this.opName = Preconditions.checkNotNull(opName);
+ }
+
+ /**
+ * Invoked to create a new exception instance of the specified type.
+ *
+ * @param message the message for the new exception.
+ * @param cause the cause for the new exception.
+ *
+ * @return an instance of the exception type.
+ */
+ protected abstract X newWithCause(String message, Throwable cause);
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public X apply(final Exception e) {
+
+ // If exception is of the specified type,return it.
+ if (exceptionType.isAssignableFrom( e.getClass())) {
+ return (X) e;
+ }
+
+ // If exception is ExecutionException whose cause is of the specified
+ // type, return the cause.
+ if (e instanceof ExecutionException && e.getCause() != null) {
+ if (exceptionType.isAssignableFrom( e.getCause().getClass())) {
+ return (X) e.getCause();
+ } else {
+ return newWithCause(opName + " execution failed", e.getCause());
+ }
+ }
+
+ // Otherwise return an instance of the specified type with the original
+ // cause.
+
+ if (e instanceof InterruptedException) {
+ return newWithCause( opName + " was interupted.", e);
+ }
+
+ if (e instanceof CancellationException ) {
+ return newWithCause( opName + " was cancelled.", e);
+ }
+
+ // We really shouldn't get here but need to cover it anyway for completeness.
+ return newWithCause(opName + " encountered an unexpected failure", e);
+ }
+}
--- /dev/null
+/*
+ * 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.yangtools.util.concurrent;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.AbstractCheckedFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * An implementation of CheckedFuture that provides similar behavior for the <code>get</code> methods
+ * that the <code>checkedGet</code> methods provide.
+ * <p>
+ * For {@link CancellationException} and {@link InterruptedException}, the specified exception mapper
+ * is invoked to translate them to the checked exception type.
+ * <p>
+ * 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 <V> The result type returned by this Future's get method
+ * @param <X> The checked exception type
+ */
+public class MappingCheckedFuture<V, X extends Exception> extends AbstractCheckedFuture<V, X> {
+
+ private final Function<Exception, X> mapper;
+
+ private MappingCheckedFuture( ListenableFuture<V> delegate, Function<Exception, X> mapper ) {
+ super( delegate );
+ this.mapper = Preconditions.checkNotNull( mapper );
+ }
+
+ /**
+ * Creates a new <code>MappingCheckedFuture</code> 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 <code>MappingCheckedFuture</code>
+ */
+ public static <V, X extends Exception> MappingCheckedFuture<V, X> create(
+ ListenableFuture<V> delegate, Function<Exception, X> mapper ) {
+ return new MappingCheckedFuture<V, X>( delegate, mapper );
+ }
+
+ @Override
+ protected X mapException( Exception e ) {
+ return mapper.apply( e );
+ }
+
+ private ExecutionException wrapInExecutionException( String message, final Exception e ) {
+ return new ExecutionException( message, mapException( e ) );
+ }
+
+ @Override
+ public V get() throws InterruptedException, ExecutionException {
+ try {
+ return super.get();
+ } catch( InterruptedException e ) {
+ Thread.currentThread().interrupt();
+ throw wrapInExecutionException( "Operation was interrupted", e );
+ } catch( CancellationException e ) {
+ throw wrapInExecutionException( "Operation was cancelled", e );
+ } catch( ExecutionException e ) {
+ throw wrapInExecutionException( e.getMessage(), e );
+ }
+ }
+
+ @Override
+ public V get( long timeout, TimeUnit unit )
+ throws InterruptedException, ExecutionException, TimeoutException {
+ try {
+ return super.get( timeout, unit );
+ } catch( InterruptedException e ) {
+ Thread.currentThread().interrupt();
+ throw wrapInExecutionException( "Operation was interrupted", e );
+ } catch( CancellationException e ) {
+ throw wrapInExecutionException( "Operation was cancelled", e );
+ } catch( ExecutionException e ) {
+ throw wrapInExecutionException( e.getMessage(), e );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.yangtools.util.concurrent;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+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 com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * Unit tests for MappingCheckedFuture.
+ *
+ * @author Thomas Pantelis
+ */
+public class MappingCheckedFutureTest {
+
+ interface FutureInvoker {
+ void invokeGet( CheckedFuture<?,?> future ) throws Exception;
+ Throwable extractWrappedTestEx( Exception from );
+ }
+
+ @SuppressWarnings("serial")
+ static class TestException extends Exception {
+ TestException( String message, Throwable cause ) {
+ super( message, cause );
+ }
+ }
+
+ static final ExceptionMapper<TestException> MAPPER = new ExceptionMapper<TestException>(
+ "Test", TestException.class ) {
+
+ @Override
+ protected TestException newWithCause( String message, Throwable cause ) {
+ return new TestException( message, cause );
+ }
+ };
+
+ static final FutureInvoker GET = new FutureInvoker() {
+ @Override
+ public void invokeGet( CheckedFuture<?,?> future ) throws Exception {
+ future.get();
+ }
+
+ @Override
+ public Throwable extractWrappedTestEx( Exception from ) {
+ if( from instanceof ExecutionException ) {
+ return ((ExecutionException)from).getCause();
+ }
+
+ return from;
+ }
+ };
+
+ static final FutureInvoker TIMED_GET = new FutureInvoker() {
+ @Override
+ public void invokeGet( CheckedFuture<?,?> future ) throws Exception {
+ future.get( 1, TimeUnit.HOURS );
+ }
+
+ @Override
+ public Throwable extractWrappedTestEx( Exception from ) {
+ if( from instanceof ExecutionException ) {
+ return ((ExecutionException)from).getCause();
+ }
+
+ return from;
+ }
+ };
+
+ static final FutureInvoker CHECKED_GET = new FutureInvoker() {
+ @Override
+ public void invokeGet( CheckedFuture<?,?> future ) throws Exception {
+ future.checkedGet();
+ }
+
+ @Override
+ public Throwable extractWrappedTestEx( Exception from ) {
+ return from;
+ }
+ };
+
+ static final FutureInvoker TIMED_CHECKED_GET = new FutureInvoker() {
+ @Override
+ public void invokeGet( CheckedFuture<?,?> future ) throws Exception {
+ future.checkedGet( 50, TimeUnit.MILLISECONDS );
+ }
+
+ @Override
+ public Throwable extractWrappedTestEx( Exception from ) {
+ return from;
+ }
+ };
+
+ @Test
+ public void testGet() throws Exception {
+
+ SettableFuture<String> delegate = SettableFuture.create();
+ MappingCheckedFuture<String,TestException> 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<String> delegate = SettableFuture.create();
+ MappingCheckedFuture<String,TestException> 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 );
+ }
+
+ private void testExecutionException( FutureInvoker invoker, Throwable cause ) {
+
+ SettableFuture<String> delegate = SettableFuture.create();
+ MappingCheckedFuture<String,TestException> 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() );
+ }
+ }
+ }
+
+ private void testCancellationException( FutureInvoker invoker ) {
+
+ SettableFuture<String> delegate = SettableFuture.create();
+ MappingCheckedFuture<String,TestException> 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() );
+ }
+ }
+
+ private void testInterruptedException( final FutureInvoker invoker ) throws Exception {
+
+ SettableFuture<String> delegate = SettableFuture.create();
+ final MappingCheckedFuture<String,TestException> mappingFuture =
+ MappingCheckedFuture.create( delegate, MAPPER );
+
+ final AtomicReference<AssertionError> 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();
+ assertEquals( "get call completed", true, doneLatch.await( 5, TimeUnit.SECONDS ) );
+
+ if( assertError.get() != null ) {
+ throw assertError.get();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.yangtools.yang.common;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A general base exception for an operation failure.
+ *
+ * @author Thomas Pantelis
+ */
+public class OperationFailedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private final List<RpcError> errorList;
+
+ /**
+ * Constructs a new instance with the specified detail message and errors.
+ *
+ * @param message the detail message
+ * @param errors {@link RpcError} instances that provide additional error information about
+ * this exception
+ */
+ public OperationFailedException(final String message, final RpcError... errors) {
+ this(message, null, errors);
+ }
+
+ /**
+ * Constructs a new instance with the specified detail message, cause and errors.
+ *
+ * @param message the detail message
+ * @param cause the cause
+ * @param errors {@link RpcError} instances that provide additional error information about
+ * this exception
+ */
+ public OperationFailedException(final String message, final Throwable cause,
+ final RpcError... errors) {
+ super(Preconditions.checkNotNull(message), cause);
+
+ if( errors != null && errors.length > 0 ) {
+ errorList = ImmutableList.<RpcError>copyOf( Arrays.asList( errors ) );
+ }
+ else {
+ // Add a default RpcError.
+ errorList = ImmutableList.of(RpcResultBuilder.newError(ErrorType.APPLICATION, null,
+ getMessage(), null, null, getCause()));
+ }
+ }
+
+ /**
+ * Returns additional error information about this exception.
+ *
+ * @return a List of RpcErrors. There is always at least one RpcError.
+ */
+ public List<RpcError> getErrorList() {
+ return errorList;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper( this ).add( "message", getMessage() )
+ .add( "errorList", errorList ).toString();
+ }
+}