From 3125bcc453e3973298b7cf417b9ea8a158ba3b8a Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Fri, 16 Aug 2013 17:46:06 +0200 Subject: [PATCH] SONAR-4547 improve support of MessageException --- .../core/sensors/ProfileSensorTest.java | 5 +- .../bootstrap/DatabaseCompatibility.java | 8 +- .../batch/phases/DecoratorsExecutor.java | 4 + .../org/sonar/maven/ExceptionHandling.java | 39 ++++++++++ .../main/java/org/sonar/maven/SonarMojo.java | 42 ++++++----- .../sonar/maven/ExceptionHandlingTest.java | 75 +++++++++++++++++++ .../sonar/runner/impl/RunnerException.java | 26 +++++++ .../org/sonar/api/utils/MessageException.java | 26 ++----- .../sonar/api/utils/MessageExceptionTest.java | 23 +----- .../platform/DatabaseServerCompatibility.java | 2 +- 10 files changed, 187 insertions(+), 63 deletions(-) create mode 100644 sonar-maven-plugin/src/main/java/org/sonar/maven/ExceptionHandling.java create mode 100644 sonar-maven-plugin/src/test/java/org/sonar/maven/ExceptionHandlingTest.java create mode 100644 sonar-maven-plugin/src/test/java/org/sonar/runner/impl/RunnerException.java diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java index 24fbaa07b45..cce7bab1087 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java @@ -20,14 +20,15 @@ package org.sonar.plugins.core.sensors; import org.junit.Test; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.*; import org.sonar.api.batch.SensorContext; import org.sonar.api.database.DatabaseSession; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.profiles.RulesProfile; import org.sonar.api.test.IsMeasure; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.*; + public class ProfileSensorTest { @Test diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseCompatibility.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseCompatibility.java index 26f0f425015..6fe7394e040 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseCompatibility.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DatabaseCompatibility.java @@ -59,21 +59,21 @@ public class DatabaseCompatibility implements BatchComponent { message.append(" / *****)\n\t- Server side: check the configuration at "); message.append(server.getURL()); message.append("/system\n"); - throw new MessageException(message.toString()); + throw MessageException.of(message.toString()); } } private void checkDatabaseStatus() { DatabaseVersion.Status status = version.getStatus(); if (status == DatabaseVersion.Status.REQUIRES_DOWNGRADE) { - throw new MessageException("Database relates to a more recent version of SonarQube. Please check your settings (JDBC settings, version of Maven plugin)"); + throw MessageException.of("Database relates to a more recent version of SonarQube. Please check your settings (JDBC settings, version of Maven plugin)"); } if (status == DatabaseVersion.Status.REQUIRES_UPGRADE) { - throw new MessageException("Database must be upgraded. Please browse " + server.getURL() + "/setup"); + throw MessageException.of("Database must be upgraded. Please browse " + server.getURL() + "/setup"); } if (status != DatabaseVersion.Status.UP_TO_DATE) { // Support other future values - throw new MessageException("Unknown database status: " + status); + throw MessageException.of("Unknown database status: " + status); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/DecoratorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/DecoratorsExecutor.java index 7d77355ef11..539fc1e4529 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/DecoratorsExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/DecoratorsExecutor.java @@ -27,6 +27,7 @@ import org.sonar.api.batch.DecoratorContext; import org.sonar.api.batch.SonarIndex; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; +import org.sonar.api.utils.MessageException; import org.sonar.api.utils.SonarException; import org.sonar.batch.DecoratorsSelector; import org.sonar.batch.DefaultDecoratorContext; @@ -80,6 +81,9 @@ public class DecoratorsExecutor implements BatchComponent { decorator.decorate(resource, context); eventBus.fireEvent(new DecoratorExecutionEvent(decorator, false)); + } catch (MessageException e) { + throw e; + } catch (Exception e) { // SONAR-2278 the resource should not be lost in exception stacktrace. throw new SonarException("Fail to decorate '" + resource + "'", e); diff --git a/sonar-maven-plugin/src/main/java/org/sonar/maven/ExceptionHandling.java b/sonar-maven-plugin/src/main/java/org/sonar/maven/ExceptionHandling.java new file mode 100644 index 00000000000..2fcd44e55e2 --- /dev/null +++ b/sonar-maven-plugin/src/main/java/org/sonar/maven/ExceptionHandling.java @@ -0,0 +1,39 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.maven; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; + +class ExceptionHandling { + + static RuntimeException handle(Exception e, Log log) throws MojoExecutionException { + Throwable source = e; + if (e.getClass().getName().equals("org.sonar.runner.impl.RunnerException") && e.getCause() != null) { + source = e.getCause(); + } + log.error(source.getMessage()); + throw new MojoExecutionException(source.getMessage(), source); + } + + static RuntimeException handle(String message, Log log) throws MojoExecutionException { + return handle(new MojoExecutionException(message), log); + } +} diff --git a/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java b/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java index 14213720c23..d62389c6970 100644 --- a/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java +++ b/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java @@ -29,7 +29,6 @@ import org.apache.maven.execution.RuntimeInformation; import org.apache.maven.lifecycle.LifecycleExecutor; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; @@ -127,23 +126,25 @@ public final class SonarMojo extends AbstractMojo { */ RuntimeInformation runtimeInformation; - public void execute() throws MojoExecutionException, MojoFailureException { + @Override + public void execute() throws MojoExecutionException { ArtifactVersion mavenVersion = getMavenVersion(); if (mavenVersion.getMajorVersion() == 2 && mavenVersion.getMinorVersion() < 2) { - throw new MojoExecutionException("Please use at least Maven 2.2.x to perform SonarQube analysis (current version is " + mavenVersion.toString() + ")"); + ExceptionHandling.handle("Please use at least Maven 2.2.x to perform SonarQube analysis (current version is " + mavenVersion.toString() + ")", getLog()); } - EmbeddedRunner runner = EmbeddedRunner.create() + try { + EmbeddedRunner runner = EmbeddedRunner.create() .setApp("Maven", mavenVersion.toString()) .addProperties(session.getExecutionProperties()) .addProperties(project.getModel().getProperties()) // Add user properties (ie command line arguments -Dsonar.xxx=yyyy) in last position to override all other .addProperties(session.getUserProperties()); - String encoding = getSourceEncoding(project); - if (encoding != null) { - runner.setProperty(ScanProperties.PROJECT_SOURCE_ENCODING, encoding); - } - runner + String encoding = getSourceEncoding(project); + if (encoding != null) { + runner.setProperty(ScanProperties.PROJECT_SOURCE_ENCODING, encoding); + } + runner .setProperty(ScanProperties.PROJECT_KEY, getSonarKey(project)) .setProperty(RunnerProperties.WORK_DIR, getSonarWorkDir(project).getAbsolutePath()) .setProperty(ScanProperties.PROJECT_BASEDIR, project.getBasedir().getAbsolutePath()) @@ -151,25 +152,28 @@ public final class SonarMojo extends AbstractMojo { .setProperty(ScanProperties.PROJECT_NAME, toString(project.getName())) .setProperty(ScanProperties.PROJECT_DESCRIPTION, toString(project.getDescription())) .setProperty(ScanProperties.PROJECT_SOURCE_DIRS, "."); - // Exclude log implementation to not conflict with Maven 3.1 logging impl - runner.mask("org.slf4j.LoggerFactory") + // Exclude log implementation to not conflict with Maven 3.1 logging impl + runner.mask("org.slf4j.LoggerFactory") // Include slf4j Logger that is exposed by some Sonar components .unmask("org.slf4j.Logger") .unmask("org.slf4j.ILoggerFactory") - // Exclude other slf4j classes - // .unmask("org.slf4j.impl.") + // Exclude other slf4j classes + // .unmask("org.slf4j.impl.") .mask("org.slf4j.") - // Exclude logback + // Exclude logback .mask("ch.qos.logback.") .mask("org.sonar.") - // Include everything else + // Include everything else .unmask(""); - runner.addExtensions(session, getLog(), lifecycleExecutor, artifactFactory, localRepository, artifactMetadataSource, artifactCollector, + runner.addExtensions(session, getLog(), lifecycleExecutor, artifactFactory, localRepository, artifactMetadataSource, artifactCollector, dependencyTreeBuilder, projectBuilder); - if (getLog().isDebugEnabled()) { - runner.setProperty("sonar.verbose", "true"); + if (getLog().isDebugEnabled()) { + runner.setProperty("sonar.verbose", "true"); + } + runner.execute(); + } catch (Exception e) { + throw ExceptionHandling.handle(e, getLog()); } - runner.execute(); } private ArtifactVersion getMavenVersion() { diff --git a/sonar-maven-plugin/src/test/java/org/sonar/maven/ExceptionHandlingTest.java b/sonar-maven-plugin/src/test/java/org/sonar/maven/ExceptionHandlingTest.java new file mode 100644 index 00000000000..59d579f33d5 --- /dev/null +++ b/sonar-maven-plugin/src/test/java/org/sonar/maven/ExceptionHandlingTest.java @@ -0,0 +1,75 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.maven; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.junit.Test; +import org.sonar.runner.impl.RunnerException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ExceptionHandlingTest { + + private static final String MESSAGE = "the error message"; + + @Test + public void should_log_message_and_throw_exception() throws Exception { + Log log = mock(Log.class); + try { + ExceptionHandling.handle(MESSAGE, log); + fail(); + } catch (MojoExecutionException e) { + assertThat(e.getMessage()).isEqualTo(MESSAGE); + verify(log).error(MESSAGE); + } + } + + @Test + public void should_log_message_and_rethrow_exception() throws Exception { + Log log = mock(Log.class); + IllegalStateException cause = new IllegalStateException(MESSAGE); + try { + ExceptionHandling.handle(cause, log); + fail(); + } catch (MojoExecutionException e) { + assertThat(e.getMessage()).isEqualTo(MESSAGE); + assertThat(e.getCause()).isSameAs(cause); + verify(log).error(MESSAGE); + } + } + + @Test + public void should_hide_sonar_runner_stacktrace() throws Exception { + Log log = mock(Log.class); + IllegalStateException cause = new IllegalStateException(MESSAGE); + try { + ExceptionHandling.handle(new RunnerException(cause), log); + fail(); + } catch (MojoExecutionException e) { + assertThat(e.getMessage()).isEqualTo(MESSAGE); + assertThat(e.getCause()).isSameAs(cause); + verify(log).error(MESSAGE); + } + } +} diff --git a/sonar-maven-plugin/src/test/java/org/sonar/runner/impl/RunnerException.java b/sonar-maven-plugin/src/test/java/org/sonar/runner/impl/RunnerException.java new file mode 100644 index 00000000000..aff129c5355 --- /dev/null +++ b/sonar-maven-plugin/src/test/java/org/sonar/runner/impl/RunnerException.java @@ -0,0 +1,26 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.runner.impl; + +public class RunnerException extends RuntimeException { + public RunnerException(Throwable throwable) { + super(throwable); + } +} 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 25290dac9b2..fc41e59c26f 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 @@ -20,31 +20,21 @@ package org.sonar.api.utils; /** - * Runtime exception for "functional" errors. It aims to be displayed to end-users, without any technical information - * like stack traces. + * 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. + *

+ * Note that by design Maven still logs the stack trace when the option -e is set. * * @since 4.0 */ public class MessageException extends RuntimeException { - public MessageException(String s) { - super(s); + private MessageException(String message) { + super(message); } - /** - * Does not fill in the stack trace - * - * @see java.lang.Throwable#fillInStackTrace() - */ - @Override - public synchronized Throwable fillInStackTrace() { - return this; + public static MessageException of(String message) { + return new MessageException(message); } - @Override - public String toString() { - return getMessage(); - } - - } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/MessageExceptionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/MessageExceptionTest.java index 0b734416e94..77daa814d37 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/utils/MessageExceptionTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/MessageExceptionTest.java @@ -21,30 +21,15 @@ package org.sonar.api.utils; import org.junit.Test; -import java.io.PrintWriter; -import java.io.StringWriter; - import static org.fest.assertions.Assertions.assertThat; public class MessageExceptionTest { - /** - * The exception should log only the message, without the "org.sonar.api.utils.MessageException" prefix - * and stack traces - */ @Test - public void should_not_print_stacktrace() throws Exception { + public void should_create_exception() throws Exception { String message = "the message"; - try { - throw new MessageException(message); - - } catch (MessageException e) { - StringWriter writer = new StringWriter(); - e.printStackTrace(new PrintWriter(writer)); - - assertThat(e.getStackTrace()).isEmpty(); - assertThat(e.getMessage()).isEqualTo(message); - assertThat(writer.toString()).isEqualTo(message + System.getProperty("line.separator")); - } + MessageException exception = MessageException.of(message); + assertThat(exception.getMessage()).isEqualTo(message); + assertThat(exception).isInstanceOf(RuntimeException.class); } } diff --git a/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java b/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java index a8fb1a922a4..12043a4fdd9 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java @@ -35,7 +35,7 @@ public class DatabaseServerCompatibility implements ServerComponent { public void start() { DatabaseVersion.Status status = version.getStatus(); if (status== DatabaseVersion.Status.REQUIRES_DOWNGRADE) { - throw new MessageException("Database relates to a more recent version of sonar. Please check your settings."); + throw MessageException.of("Database relates to a more recent version of sonar. Please check your settings."); } if (status== DatabaseVersion.Status.REQUIRES_UPGRADE) { LoggerFactory.getLogger(DatabaseServerCompatibility.class).info("Database must be upgraded. Please browse /setup"); -- 2.39.5