aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-07-22 14:13:57 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-07-22 14:17:16 +0200
commit5539cfffd327697a49278361901335e2d4d6e075 (patch)
tree5f6aed036ccf7266d8b6adfd0bdd69a58b9bb3ea /sonar-core
parent88b8a12589fe8c5823e3310ffd9fb9a244f0a3c0 (diff)
downloadsonarqube-5539cfffd327697a49278361901335e2d4d6e075.tar.gz
sonarqube-5539cfffd327697a49278361901335e2d4d6e075.zip
Add org.sonar.core.util.ContextException
Diffstat (limited to 'sonar-core')
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/ContextException.java170
-rw-r--r--sonar-core/src/test/java/org/sonar/core/util/ContextExceptionTest.java99
2 files changed, 269 insertions, 0 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/util/ContextException.java b/sonar-core/src/main/java/org/sonar/core/util/ContextException.java
new file mode 100644
index 00000000000..4335166fcf7
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/util/ContextException.java
@@ -0,0 +1,170 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * A runtime exception that provides contextual information as a list of key-value
+ * pairs. This information is added to the message and stack trace.
+ * <p>
+ * Example:
+ * <pre>
+ * try {
+ *
+ * } catch (Exception e) {
+ * throw ContextException.of("Unable to assign issue", e)
+ * .addContext("projectUuid", "P1")
+ * .addContext("issueUuid", "I1")
+ * .addContext("login", "felix");
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * Contexts of nested {@link ContextException}s are merged:
+ * <pre>
+ * try {
+ * throw ContextException.of("Something wrong").addContext("login", "josette");
+ * } catch (Exception e) {
+ * throw ContextException.of("Unable to assign issue", e)
+ * .addContext("issueUuid", "I1")
+ * .addContext("login", "felix");
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * The generated message, usually written to a log with stack trace, looks like:
+ * <pre>
+ * Unable to assign issue | issueUuid=I1 | login=[josette,felix]
+ * </pre>
+ * </p>
+ */
+public class ContextException extends RuntimeException {
+
+ private static final Joiner COMMA_JOINER = Joiner.on(',');
+
+ // LinkedListMultimap is used to keep order of keys and values
+ private final ListMultimap<String, Object> context = LinkedListMultimap.create();
+
+ private ContextException(Throwable t) {
+ super(t);
+ }
+
+ private ContextException(String message, Throwable t) {
+ super(message, t);
+ }
+
+ private ContextException(String message) {
+ super(message);
+ }
+
+ private ContextException addContext(ContextException e) {
+ this.context.putAll(e.context);
+ return this;
+ }
+
+ public ContextException addContext(String key, @Nullable Object value) {
+ context.put(key, value);
+ return this;
+ }
+
+ public ContextException clearContext(String key) {
+ context.removeAll(key);
+ return this;
+ }
+
+ public ContextException setContext(String key, @Nullable Object value) {
+ clearContext(key);
+ return addContext(key, value);
+ }
+
+ /**
+ * Returns the values associated with {@code key}, if any, else returns an
+ * empty list.
+ */
+ public List<Object> getContext(String key) {
+ return context.get(key);
+ }
+
+ public static ContextException of(Throwable t) {
+ if (t instanceof ContextException) {
+ return new ContextException(t.getCause()).addContext((ContextException) t);
+ }
+ return new ContextException(t);
+ }
+
+ public static ContextException of(String message, Throwable t) {
+ if (t instanceof ContextException) {
+ return new ContextException(message, t.getCause()).addContext((ContextException) t);
+ }
+ return new ContextException(message, t);
+ }
+
+ public static ContextException of(String message) {
+ return new ContextException(message);
+ }
+
+ @Override
+ @Nonnull
+ public String getMessage() {
+ return format(super.getMessage());
+ }
+
+ /**
+ * Provides the message explaining the exception without the contextual data.
+ */
+ @CheckForNull
+ public String getRawMessage() {
+ return super.getMessage();
+ }
+
+ private String format(@Nullable String baseMessage) {
+ StringBuilder sb = new StringBuilder();
+ Iterator<String> keyIt = context.keySet().iterator();
+ if (StringUtils.isNotBlank(baseMessage)) {
+ sb.append(baseMessage);
+ if (keyIt.hasNext()) {
+ sb.append(" | ");
+ }
+ }
+ while (keyIt.hasNext()) {
+ String key = keyIt.next();
+ sb.append(key).append("=");
+ List<Object> values = getContext(key);
+ if (values.size() > 1) {
+ sb.append("[").append(COMMA_JOINER.join(values)).append("]");
+ } else if (values.size() == 1) {
+ sb.append(values.get(0));
+ }
+ if (keyIt.hasNext()) {
+ sb.append(" | ");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/util/ContextExceptionTest.java b/sonar-core/src/test/java/org/sonar/core/util/ContextExceptionTest.java
new file mode 100644
index 00000000000..00ec6781f2e
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/util/ContextExceptionTest.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ContextExceptionTest {
+
+ public static final String LABEL = "something wrong";
+
+ @Test
+ public void only_label() throws Exception {
+ ContextException e = ContextException.of(LABEL);
+ assertThat(e.getMessage()).isEqualTo(LABEL);
+ assertThat(e.getRawMessage()).isEqualTo(LABEL);
+ assertThat(e.getCause()).isNull();
+ }
+
+ @Test
+ public void only_cause() throws Exception {
+ Exception cause = new Exception("cause");
+ ContextException e = ContextException.of(cause);
+ assertThat(e.getMessage()).isEqualTo("java.lang.Exception: cause");
+ assertThat(e.getRawMessage()).isEqualTo("java.lang.Exception: cause");
+ assertThat(e.getCause()).isSameAs(cause);
+ }
+
+ @Test
+ public void cause_and_message() throws Exception {
+ Exception cause = new Exception("cause");
+ ContextException e = ContextException.of(LABEL, cause);
+ assertThat(e.getMessage()).isEqualTo(LABEL);
+ assertThat(e.getRawMessage()).isEqualTo(LABEL);
+ assertThat(e.getCause()).isSameAs(cause);
+ }
+
+ @Test
+ public void addContext() throws Exception {
+ ContextException e = ContextException.of(LABEL)
+ .addContext("K1", "V1")
+ .addContext("K2", "V2");
+ assertThat(e).hasMessage(LABEL + " | K1=V1 | K2=V2");
+ }
+
+ @Test
+ public void setContext() throws Exception {
+ ContextException e = ContextException.of(LABEL)
+ .addContext("K1", "V1")
+ .setContext("K1", "V2");
+ assertThat(e).hasMessage(LABEL + " | K1=V2");
+ }
+
+ @Test
+ public void multiple_context_values() throws Exception {
+ ContextException e = ContextException.of(LABEL)
+ .addContext("K1", "V1")
+ .addContext("K1", "V2");
+ assertThat(e).hasMessage(LABEL + " | K1=[V1,V2]");
+ }
+
+ @Test
+ public void merge_ContextException() throws Exception {
+ ContextException cause = ContextException.of("cause").addContext("K1", "V1");
+ ContextException e = ContextException.of(cause)
+ .addContext("K1", "V11")
+ .addContext("K2", "V2");
+ assertThat(e.getContext("K1")).containsExactly("V1", "V11");
+ assertThat(e.getContext("K2")).containsOnly("V2");
+ assertThat(e).hasMessage("K1=[V1,V11] | K2=V2");
+ }
+
+ @Test
+ public void merge_ContextException_with_new_message() throws Exception {
+ ContextException cause = ContextException.of("cause").addContext("K1", "V1");
+ ContextException e = ContextException.of(LABEL, cause).addContext("K2", "V2");
+ assertThat(e.getContext("K1")).containsOnly("V1");
+ assertThat(e.getContext("K2")).containsOnly("V2");
+ assertThat(e).hasMessage(LABEL + " | K1=V1 | K2=V2");
+ }
+}