1   package eu.fbk.knowledgestore;
2   
3   import java.util.List;
4   
5   import com.google.common.base.Preconditions;
6   import com.google.common.collect.ImmutableList;
7   import com.google.common.collect.Iterables;
8   
9   // TODO: make OperationException unchecked?
10  
11  /**
12   * Signals the failure of a KnowledgeStore {@code Operation} invocation.
13   * <p>
14   * This exception is thrown every time an invocation of a KnowledgeStore {@link Operation} fails.
15   * The outcome attribute (see {@link #getOutcome()}) provides the error / unknown operation
16   * outcome at the root of this exception. The {@code Throwable}s causing this exception (zero or
17   * more) are also stored: method {@link #getCauses()} returns a list with all the causes, while
18   * standard method {@link #getCause()} returns only the last cause of the list (thus, only this
19   * cause will be included in the stacktrace printed by {@link #printStackTrace()}).
20   * </p>
21   */
22  public class OperationException extends Exception {
23  
24      private static final long serialVersionUID = 1L;
25  
26      private final Outcome outcome;
27  
28      private final List<Throwable> causes;
29  
30      /**
31       * Creates a new instance with the outcome and causes vararg array specified.
32       * 
33       * @param outcome
34       *            the error/unknown outcome for this exception
35       * @param causes
36       *            a vararg array with the causes of this exception, possibly empty
37       */
38      public OperationException(final Outcome outcome, final Throwable... causes) {
39          this(outcome, ImmutableList.copyOf(causes));
40      }
41  
42      /**
43       * Creates a new instance with the outcome and causes {@code Iterable} specified.
44       * 
45       * @param outcome
46       *            the error/unknown outcome for this exception
47       * @param causes
48       *            an iterable with the causes of this exception, not null, possibly empty
49       */
50      public OperationException(final Outcome outcome, final Iterable<? extends Throwable> causes) {
51          super(messageFor(Preconditions.checkNotNull(outcome), causes),
52                  Iterables.isEmpty(causes) ? null : Iterables.getLast(causes));
53          this.outcome = outcome;
54          this.causes = ImmutableList.copyOf(causes);
55      }
56  
57      /**
58       * Returns the error/unknown outcome associated to this exception.
59       * 
60       * @return the outcome
61       */
62      public Outcome getOutcome() {
63          return this.outcome;
64      }
65  
66      /**
67       * Returns all the causes of this exception. Causes are reported whenever available; in
68       * particular, {@code OperationException} on single objects are reported in case of
69       * {@code create}, {@code merge}, {@code update}, {@code delete} bulk operations.
70       * 
71       * @return the causes associated to this exception
72       */
73      public List<Throwable> getCauses() {
74          return this.causes;
75      }
76  
77      private static String messageFor(final Outcome outcome,
78              final Iterable<? extends Throwable> causes) {
79          final StringBuilder builder = new StringBuilder();
80          builder.append(outcome);
81          int index = 0;
82          for (final Throwable cause : causes) {
83              builder.append("\n(").append(++index).append(") ")
84                      .append(cause.getClass().getSimpleName()).append(" ")
85                      .append(cause.getMessage());
86          }
87          return builder.toString();
88      }
89  
90  }