@@ -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) { |
@@ -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; | |||
} | |||
@@ -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); | |||
@@ -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"); | |||
} | |||
} | |||
} | |||
} |
@@ -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); | |||
} |