SonarQualityGatesMojo.java

package com.hack23.maven.plugin;

import static com.mashape.unirest.http.Unirest.setHttpClient;
import static java.lang.String.format;
import static org.apache.http.impl.client.HttpClients.createDefault;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Properties;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hack23.maven.plugin.model.Conditions;
import com.hack23.maven.plugin.model.Measures;
import com.hack23.maven.plugin.model.MeasuresContainer;
import com.hack23.maven.plugin.model.QualityGateValue;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.mashape.unirest.request.GetRequest;
import com.mashape.unirest.request.HttpRequest;

/**
 * The Class SonarQualityGatesMojo.
 */
@Mojo(name = "inspect")
public class SonarQualityGatesMojo extends AbstractMojo {

	/** The Constant COLON. */
	private static final String COLON = ":";

	/** The Constant SONAR_API_URL. */
	private static final String SONAR_API_URL = "%s/api/measures/search?projectKeys=%s&metricKeys=alert_status,quality_gate_details";

	/** The Constant SONAR_DEFAULT_HOST_URL. */
	private static final String SONAR_DEFAULT_HOST_URL = "http://localhost:9000";

	/** The Constant SONAR_HOST_URL. */
	private static final String SONAR_HOST_URL = "sonar.host.url";

	/** The Constant SONAR_PROJECT_KEY. */
	private static final String SONAR_PROJECT_KEY = "sonar.projectKey";

	/** The Constant STATUS_CODE_OK. */
	private static final int STATUS_CODE_OK = 200;

	/** The session. */
	@Parameter(defaultValue = "${session}", readonly = true)
	private MavenSession session;

	/** The sonar host url. */
	@Parameter(property = SONAR_HOST_URL)
	private String sonarHostUrl;

	/**
	 * property key for sonar login (username ore token), see also
	 * <a href="https://docs.sonarqube.org/latest/extend/web-api/">SonarQube - Web API
	 * Authentication</a>
	 * <br/> aligned to sonar-maven-plugin analysis parameters, see also
	 * <a href="https://docs.sonarqube.org/latest/analysis/analysis-parameters/">SonarQube - Analysis
	 * Parameters</a>
	 */
	@Parameter(property = "sonar.login")
	private String sonarLogin;

	/**
	 * property key for sonar password, see also
	 * <a href="https://docs.sonarqube.org/latest/extend/web-api/">SonarQube - Web API
	 * Authentication</a>
	 * <br/> aligned to sonar-maven-plugin analysis parameters, see also
	 * <a href="https://docs.sonarqube.org/latest/analysis/analysis-parameters/">SonarQube - Analysis
	 * Parameters</a>
	 */
	@Parameter(property = "sonar.password")
	private String sonarPassword;

	/**
	 * Instantiates a new sonar quality gates mojo.
	 */
	@Inject
	public SonarQualityGatesMojo() {
		setHttpClient(createDefault());
	}

	/**
	 * Execute.
	 *
	 * @throws MojoExecutionException the mojo execution exception
	 * @throws MojoFailureException   the mojo failure exception
	 */
	public void execute() throws MojoExecutionException, MojoFailureException {
		final MavenProject topLevelProject = session.getTopLevelProject();
		final List<Measures> measures = retrieveSonarMeasures(
				format(SONAR_API_URL, getSonarHostUrl(topLevelProject.getProperties()), getSonarKey(topLevelProject)));

		if (measures.isEmpty()) {
			throw new MojoExecutionException(
					"\nno matching project in sonarqube for project key:" + getSonarKey(topLevelProject));
		}

		if (!"OK".equals(measures.get(0).getValue())) {

			try {
				final QualityGateValue qualityGateValue = new ObjectMapper().readValue(measures.get(1).getValue(),
						QualityGateValue.class);
				final StringBuilder builder = new StringBuilder();
				builder.append("\nFailed quality gate\n");
				final List<Conditions> conditions = qualityGateValue.getConditions();
				for (final Conditions condition : conditions) {
					if (!"OK".equals(condition.getLevel())) {
						builder.append(condition);
						builder.append('\n');
					}
				}

				throw new MojoExecutionException(builder.toString());

			} catch (final IOException e) {
				throw new MojoFailureException("Failed to execute mojo", e);
			}

		}
	}

	/**
	 * Retrieve sonar measures.
	 *
	 * @param url the url
	 * @return the list
	 * @throws MojoFailureException the mojo failure exception
	 */
	private List<Measures> retrieveSonarMeasures(final String url) throws MojoFailureException {
		try {
			final GetRequest request = Unirest.get(url);
			addCredentials(request);
			final HttpResponse<String> response = request.asString();
			final String body = response.getBody();

			if (response.getStatus() != STATUS_CODE_OK) {
				throw new MojoFailureException(MessageFormat.format(
						"Attempt to call Sonarqube responded with an error status :{0} : for url:{1} : response{2}: ",
						response.getStatus(), url, body));
			}

			return new ObjectMapper().readValue(body, MeasuresContainer.class).getMeasures();
		} catch (IOException | UnirestException e) {
			throw new MojoFailureException("Could not execute sonar-quality-gates-plugin", e);
		} finally {
			shutdown();
		}
	}

	/**
	 * Gets the sonar host url.
	 *
	 * @param properties the properties
	 * @return the sonar host url
	 */
	private String getSonarHostUrl(final Properties properties) {
		if (sonarHostUrl != null) {
			return sonarHostUrl;
		}

		return properties.containsKey(SONAR_HOST_URL) ? properties.getProperty(SONAR_HOST_URL) : SONAR_DEFAULT_HOST_URL;
	}

	/**
	 * Gets the sonar key.
	 *
	 * @param pom the pom
	 * @return the sonar key
	 */
	private static String getSonarKey(final MavenProject pom) {
		if (pom.getModel().getProperties().containsKey(SONAR_PROJECT_KEY)) {
			return pom.getModel().getProperties().getProperty(SONAR_PROJECT_KEY);
		}

		return pom.getGroupId() + COLON + pom.getArtifactId();
	}

	/**
	 * read auth configuration and update request, if valid values found
	 * @param request request to enhance with credentials
	 */
	protected void addCredentials(HttpRequest request) {
		if (StringUtils.isNotBlank(sonarLogin)) {
			if (StringUtils.isBlank(sonarPassword)) {
				request.basicAuth(sonarLogin, "");
			} else {
				request.basicAuth(sonarLogin, sonarPassword);
			}
		}
	}

	/**
	 * Shutdown.
	 *
	 * @throws MojoFailureException the mojo failure exception
	 */
	private static void shutdown() throws MojoFailureException {
		try {
			Unirest.shutdown();
		} catch (final IOException e) {
			throw new MojoFailureException("Could not properly shutdown", e);
		}
	}
}