diff options
5 files changed, 184 insertions, 14 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java index e16bfaf4275..a292451e818 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java @@ -19,6 +19,7 @@ */ package org.sonar.batch.bootstrap; +import org.sonar.api.utils.MessageException; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -126,13 +127,13 @@ public class ServerClient { public RuntimeException handleHttpException(HttpDownloader.HttpException he) { if (he.getResponseCode() == 401) { - return new IllegalStateException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD), he); + return MessageException.of(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD), he); } if (he.getResponseCode() == 403) { // SONAR-4397 Details are in response content - return new IllegalStateException(tryParseAsJsonError(he.getResponseContent()), he); + return MessageException.of(tryParseAsJsonError(he.getResponseContent()), he); } - return new IllegalStateException(String.format("Fail to execute request [code=%s, url=%s]: %s", he.getResponseCode(), he.getUri(), he.getResponseContent()), he); + return MessageException.of(String.format("Fail to execute request [code=%s, url=%s]: %s", he.getResponseCode(), he.getUri(), he.getResponseContent()), he); } private static String tryParseAsJsonError(String responseContent) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java index 62bde940a11..3bc8ea8c24a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java @@ -19,6 +19,9 @@ */ package org.sonar.batch.bootstrapper; +import org.sonar.api.utils.MessageException; + +import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -95,8 +98,12 @@ public final class Batch { } configureLogging(); - bootstrapContainer = GlobalContainer.create(bootstrapProperties, components, preferCache); - bootstrapContainer.startComponents(); + try { + bootstrapContainer = GlobalContainer.create(bootstrapProperties, components, preferCache); + bootstrapContainer.startComponents(); + } catch (RuntimeException e) { + throw handleException(e); + } this.started = true; return this; @@ -108,8 +115,11 @@ public final class Batch { public Batch executeTask(Map<String, String> analysisProperties, Object... components) { checkStarted(); configureTaskLogging(analysisProperties); - bootstrapContainer.executeTask(analysisProperties, components); - configureLogging(); + try { + bootstrapContainer.executeTask(analysisProperties, components); + } catch (RuntimeException e) { + throw handleException(e); + } return this; } @@ -119,8 +129,11 @@ public final class Batch { public Batch executeTask(Map<String, String> analysisProperties, IssueListener issueListener) { checkStarted(); configureTaskLogging(analysisProperties); - bootstrapContainer.executeTask(analysisProperties, components, issueListener); - configureLogging(); + try { + bootstrapContainer.executeTask(analysisProperties, components, issueListener); + } catch (RuntimeException e) { + throw handleException(e); + } return this; } @@ -130,6 +143,20 @@ public final class Batch { } } + private RuntimeException handleException(RuntimeException t) { + if (loggingConfig.isVerbose()) { + return t; + } + + for (Throwable y : Throwables.getCausalChain(t)) { + if (y instanceof MessageException) { + return (MessageException) y; + } + } + + return t; + } + /** * @since 5.2 */ @@ -148,7 +175,12 @@ public final class Batch { private void doStop(boolean swallowException) { checkStarted(); - bootstrapContainer.stopComponents(swallowException); + configureLogging(); + try { + bootstrapContainer.stopComponents(swallowException); + } catch (RuntimeException e) { + throw handleException(e); + } this.started = false; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java index 66c827c03b3..7efe3947f3f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java @@ -49,6 +49,7 @@ public final class LoggingConfiguration { private Map<String, String> substitutionVariables = Maps.newHashMap(); private LogOutput logOutput = null; + private boolean verbose; public LoggingConfiguration() { this(null); @@ -83,11 +84,15 @@ public final class LoggingConfiguration { public LoggingConfiguration setVerbose(boolean verbose) { return setRootLevel(verbose ? LEVEL_ROOT_VERBOSE : LEVEL_ROOT_DEFAULT); } + + public boolean isVerbose() { + return verbose; + } public LoggingConfiguration setVerbose(Map<String, String> props, @Nullable Map<String, String> fallback) { String logLevel = getFallback("sonar.log.level", props, fallback); String deprecatedProfilingLevel = getFallback("sonar.log.profilingLevel", props, fallback); - boolean verbose = "true".equals(getFallback("sonar.verbose", props, fallback)) || + verbose = "true".equals(getFallback("sonar.verbose", props, fallback)) || "DEBUG".equals(logLevel) || "TRACE".equals(logLevel) || "BASIC".equals(deprecatedProfilingLevel) || "FULL".equals(deprecatedProfilingLevel); diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/log/ExceptionHandlingMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/log/ExceptionHandlingMediumTest.java new file mode 100644 index 00000000000..c25b5f78c2d --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/log/ExceptionHandlingMediumTest.java @@ -0,0 +1,116 @@ +/* + * 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.batch.mediumtest.log; + +import java.util.Collections; + +import org.hamcrest.Matchers; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.BeforeClass; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.api.utils.MessageException; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonar.batch.protocol.input.GlobalRepositories; +import org.sonar.batch.repository.GlobalRepositoriesLoader; +import org.sonar.batch.bootstrapper.Batch; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ExceptionHandlingMediumTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Batch batch; + private static ErrorGlobalRepositoriesLoader loader; + + @BeforeClass + public static void beforeClass() { + loader = new ErrorGlobalRepositoriesLoader(); + } + + public void setUp(boolean verbose) { + Batch.Builder builder = Batch.builder() + .setEnableLoggingConfiguration(true) + .addComponents( + loader, + new EnvironmentInformation("mediumTest", "1.0")); + + if (verbose) { + builder.setBootstrapProperties(Collections.singletonMap("sonar.verbose", "true")); + } + batch = builder.build(); + } + + @Test + public void test() throws Exception { + setUp(false); + loader.withCause = false; + thrown.expect(MessageException.class); + thrown.expectMessage("Error loading repository"); + thrown.expectCause(Matchers.nullValue(Throwable.class)); + + batch.start(); + } + + @Test + public void testWithCause() throws Exception { + setUp(false); + loader.withCause = true; + + thrown.expect(MessageException.class); + thrown.expectMessage("Error loading repository"); + thrown.expectCause(new TypeSafeMatcher<Throwable>() { + @Override + public void describeTo(Description description) { + } + + @Override + protected boolean matchesSafely(Throwable item) { + return item instanceof IllegalStateException && item.getMessage().equals("Code 401"); + } + }); + + batch.start(); + } + + @Test + public void testWithVerbose() throws Exception { + setUp(true); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Unable to load component class"); + batch.start(); + } + + private static class ErrorGlobalRepositoriesLoader implements GlobalRepositoriesLoader { + boolean withCause = false; + + @Override + public GlobalRepositories load(MutableBoolean fromCache) { + if (withCause) { + IllegalStateException cause = new IllegalStateException("Code 401"); + throw MessageException.of("Error loading repository", cause); + } else { + throw MessageException.of("Error loading repository"); + } + } + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/MessageException.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/MessageException.java index 92ade0e1252..df4a6bd02a0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/MessageException.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/MessageException.java @@ -29,11 +29,17 @@ import static com.google.common.collect.Lists.newArrayList; /** * Runtime exception for "functional" error. It aims to be displayed to end-users, without any technical information - * like stack traces. It requires sonar-runner 2.4. Previous versions log stack trace. + * like stack traces. * <p/> - * Note that by design Maven still logs the stack trace when the option -e is set. + * + * It's handling depends on the versions of the sonar-batch and sonar-runner. sonar-runner 2.4 will only show the + * message associated with this exception. + * Starting from sonar-batch 5.3, this is handled in the batch side, and the main goal is to hide all wrappers of this + * exception. If this exception is created without cause, then only the message associated with this exception is shown; + * otherwise, its causes are also shown. + * Previous combinations of sonar-batch/sonar-runner log all stack trace. * <p/> - * Message should be clear and complete. Keep in mind that context is not added to the exception. + * Message should be clear and complete. Keep in mind that context might not be added to the exception. * Names of processed resource and decorator are for example not automatically added when throwing {@link MessageException} * from {@link org.sonar.api.batch.Decorator}. * @@ -54,6 +60,16 @@ public class MessageException extends RuntimeException { this.l10nParams = l10nParams == null ? Collections.emptyList() : Collections.unmodifiableCollection(newArrayList(l10nParams)); } + private MessageException(String message, Throwable cause) { + super(message, cause); + l10nKey = null; + l10nParams = Collections.emptyList(); + } + + public static MessageException of(String message, Throwable cause) { + return new MessageException(message, cause); + } + public static MessageException of(String message) { return new MessageException(message); } |