package qubexplorer.server;
import java.net.URI;
import java.net.URISyntaxException;
import qubexplorer.filter.SeverityFilter;
import qubexplorer.filter.IssueFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.netbeans.api.keyring.Keyring;
import org.openide.util.NetworkSettings;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.sonar.wsclient.Host;
import org.sonar.wsclient.Sonar;
import org.sonar.wsclient.SonarClient;
import org.sonar.wsclient.base.HttpException;
import org.sonar.wsclient.connectors.ConnectionException;
import org.sonar.wsclient.connectors.HttpClient4Connector;
import org.sonar.wsclient.issue.ActionPlan;
import org.sonar.wsclient.issue.Issue;
import org.sonar.wsclient.issue.IssueClient;
import org.sonar.wsclient.issue.IssueQuery;
import org.sonar.wsclient.issue.Issues;
import org.sonar.wsclient.services.Resource;
import org.sonar.wsclient.services.ResourceQuery;
import org.sonar.wsclient.services.Rule;
import org.sonar.wsclient.services.RuleQuery;
import org.sonar.wsclient.services.ServerQuery;
import qubexplorer.UserCredentials;
import qubexplorer.AuthorizationException;
import qubexplorer.IssuesContainer;
import qubexplorer.NoSuchProjectException;
import qubexplorer.PassEncoder;
import qubexplorer.RadarIssue;
import qubexplorer.ResourceKey;
import qubexplorer.Severity;
import qubexplorer.SonarQubeProjectConfiguration;
import qubexplorer.GenericSonarQubeProjectConfiguration;
import qubexplorer.Summary;
/**
*
* @author Victor
*/
public class SonarQube implements IssuesContainer {
private static final String VIOLATIONS_DENSITY_METRICS = "violations_density";
private static final int UNAUTHORIZED_RESPONSE_STATUS = 401;
private static final int PAGE_SIZE = 500;
private String serverUrl;
public SonarQube(String servelUrl) {
this.serverUrl = servelUrl;
/* remove ending '/' if needed because of a problem with the underlying http client. */
assert this.serverUrl.length() > 1;
if (this.serverUrl.endsWith("/")) {
this.serverUrl = this.serverUrl.substring(0, this.serverUrl.length() - 1);
}
}
public SonarQube() {
this("http://localhost:9000");
}
public String getServerUrl() {
return serverUrl;
}
public Version getVersion(UserCredentials userCredentials) {
Sonar sonar = createSonar(userCredentials);
ServerQuery serverQuery = new ServerQuery();
return new Version(sonar.find(serverQuery).getVersion());
}
public double getRulesCompliance(UserCredentials userCredentials, ResourceKey resourceKey) {
try {
if (!existsProject(userCredentials, resourceKey)) {
throw new NoSuchProjectException(resourceKey);
}
Sonar sonar = createSonar(userCredentials);
ResourceQuery query = new ResourceQuery(resourceKey.toString());
query.setMetrics(VIOLATIONS_DENSITY_METRICS);
Resource r = sonar.find(query);
return r.getMeasure(VIOLATIONS_DENSITY_METRICS).getValue();
} catch (ConnectionException ex) {
if (isError401(ex)) {
throw new AuthorizationException(ex);
} else {
throw ex;
}
}
}
@Override
public List<RadarIssue> getIssues(UserCredentials auth, ResourceKey projectKey, IssueFilter... filters) {
if (!existsProject(auth, projectKey)) {
throw new NoSuchProjectException(projectKey);
}
IssueQuery query = IssueQuery.create().componentRoots(projectKey.toString()).pageSize(PAGE_SIZE).statuses("OPEN");
for (IssueFilter filter : filters) {
filter.apply(query);
}
return getIssues(auth, query);
}
private List<RadarIssue> getIssues(UserCredentials userCredentials, IssueQuery query) {
try {
SonarClient sonarClient = createSonarClient(userCredentials);
IssueClient issueClient = sonarClient.issueClient();
List<RadarIssue> issues = new LinkedList<>();
Map<String, Rule> rulesCache = new HashMap<>();
Issues result;
int pageIndex = 1;
do {
query.pageIndex(pageIndex);
result = issueClient.find(query);
for (Issue issue : result.list()) {
Rule rule = searchInCacheOrLoadFromServer(rulesCache, issue.ruleKey(), userCredentials);
issues.add(new RadarIssue(issue, rule));
}
pageIndex++;
} while (result.paging().pages() != null && pageIndex <= result.paging().pages());
return issues;
} catch (HttpException ex) {
if (ex.status() == UNAUTHORIZED_RESPONSE_STATUS) {
throw new AuthorizationException(ex);
} else {
throw ex;
}
}
}
private Rule searchInCacheOrLoadFromServer(Map<String, Rule> rulesCache, String ruleKey, UserCredentials userCredentials) {
Rule rule = rulesCache.get(ruleKey);
if (rule == null) {
rule = getRule(userCredentials, ruleKey);
if (rule != null) {
rulesCache.put(ruleKey, rule);
}
}
if (rule == null) {
throw new IllegalStateException("No such rule in server: " + ruleKey);
}
return rule;
}
private Sonar createSonar(UserCredentials userCredentials) {
Host host = new Host(serverUrl);
if (userCredentials != null) {
host.setUsername(userCredentials.getUsername());
host.setPassword(PassEncoder.decodeAsString(userCredentials.getPassword()));
}
HttpClient4Connector connector = new HttpClient4Connector(host);
final ProxySettings proxySettings = getProxySettings();
if (proxySettings != null) {
DefaultHttpClient httpClient = connector.getHttpClient();
if (proxySettings.getUsername() != null) {
httpClient.getCredentialsProvider()
.setCredentials(new AuthScope(proxySettings.getHost(), proxySettings.getPort()), new UsernamePasswordCredentials(proxySettings.getUsername(), proxySettings.getPassword()));
}
HttpHost proxy = new HttpHost(proxySettings.getHost(), proxySettings.getPort());
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
httpClient.setRoutePlanner(routePlanner);
}
return new Sonar(connector);
}
private SonarClient createSonarClient(UserCredentials userCredentials) {
SonarClient.Builder builder = SonarClient.builder().url(serverUrl);
if (userCredentials != null) {
builder.login(userCredentials.getUsername()).password(PassEncoder.decodeAsString(userCredentials.getPassword()));
}
ProxySettings proxySettings = getProxySettings();
if (proxySettings != null) {
builder.proxy(proxySettings.getHost(), proxySettings.getPort());
if (proxySettings.getUsername() != null) {
builder.proxyLogin(proxySettings.getUsername()).proxyPassword(proxySettings.getPassword());
}
}
return builder.build();
}
private ProxySettings getProxySettings() {
try {
ProxySettings settings = null;
URI uri = new URI(serverUrl);
String proxyHost = NetworkSettings.getProxyHost(uri);
if (proxyHost != null) {
int proxyPort = 8080;
String stringProxyPort = NetworkSettings.getProxyPort(uri);
if (stringProxyPort != null) {
proxyPort = Integer.parseInt(stringProxyPort);
}
settings = new ProxySettings(proxyHost, proxyPort);
String authenticationUsername = NetworkSettings.getAuthenticationUsername(uri);
if (authenticationUsername != null) {
settings.setUsername(authenticationUsername);
settings.setKeyForPassword(NetworkSettings.getKeyForAuthenticationPassword(uri));
}
}
return settings;
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Wrong URI " + serverUrl, ex);
}
}
public List<ActionPlan> getActionPlans(UserCredentials userCredentials, ResourceKey resourceKey) {
try {
SonarClient client = createSonarClient(userCredentials);
return client.actionPlanClient().find(resourceKey.toString());
} catch (HttpException ex) {
if (ex.status() == UNAUTHORIZED_RESPONSE_STATUS) {
throw new AuthorizationException(ex);
} else {
throw ex;
}
}
}
public Rule getRule(UserCredentials userCredentials, String ruleKey) {
try {
//try first the newest Rule Search API
return new RuleSearchClient(serverUrl).getRule(userCredentials, ruleKey);
} catch (HttpException ex) {
if (ex.getMessage().contains("Error 404")) {
//fallback to old method
return getRuleWithQueryAPI(userCredentials, ruleKey);
} else if (ex.status() == UNAUTHORIZED_RESPONSE_STATUS) {
throw new AuthorizationException(ex);
}
throw ex;
}
}
private Rule getRuleWithQueryAPI(UserCredentials userCredentials, String ruleKey) {
RuleQuery ruleQuery = new RuleQuery("java");
String[] tokens = ruleKey.split(":");
ruleQuery.setSearchText(tokens.length == 2 ? tokens[1] : ruleKey);
Sonar sonar = createSonar(userCredentials);
List<Rule> rules = sonar.findAll(ruleQuery);
for (Rule rule : rules) {
if (ruleKey.equals(rule.getKey())) {
return rule;
}
}
return null;
}
public List<ResourceKey> getProjectsKeys(UserCredentials userCredentials) {
try {
Sonar sonar = createSonar(userCredentials);
List<Resource> resources = sonar.findAll(new ResourceQuery());
List<ResourceKey> keys = new ArrayList<>(resources.size());
for (Resource r : resources) {
keys.add(ResourceKey.valueOf(r.getKey()));
}
return keys;
} catch (ConnectionException ex) {
if (isError401(ex)) {
throw new AuthorizationException(ex);
} else {
throw ex;
}
}
}
private static boolean isError401(ConnectionException ex) {
return ex.getMessage().contains("HTTP error: 401");
}
public List<SonarQubeProjectConfiguration> getProjects(UserCredentials userCredentials) {
try {
Sonar sonar = createSonar(userCredentials);
List<Resource> resources = sonar.findAll(new ResourceQuery());
List<SonarQubeProjectConfiguration> projects = new ArrayList<>(resources.size());
for (Resource r : resources) {
projects.add(new GenericSonarQubeProjectConfiguration(r.getName(), ResourceKey.valueOf(r.getKey()), r.getVersion()));
}
return projects;
} catch (ConnectionException ex) {
if (isError401(ex)) {
throw new AuthorizationException(ex);
} else {
throw ex;
}
}
}
public boolean existsProject(UserCredentials auth, ResourceKey projectKey) {
for (ResourceKey tmp : getProjectsKeys(auth)) {
if (tmp.equals(projectKey)) {
return true;
}
}
return false;
}
@Override
public Summary getSummary(UserCredentials auth, ResourceKey resourceKey, IssueFilter[] filters) {
if (!existsProject(auth, resourceKey)) {
throw new NoSuchProjectException(resourceKey);
}
SimpleSummary simpleSummary = new SimpleSummary();
for (Severity severity : Severity.values()) {
IssueFilter[] tempFilters = new IssueFilter[filters.length + 1];
tempFilters[0] = new SeverityFilter(severity);
System.arraycopy(filters, 0, tempFilters, 1, filters.length);
List<RadarIssue> issues = getIssues(auth, resourceKey, tempFilters);
for (RadarIssue issue : issues) {
simpleSummary.increment(severity, issue.rule(), 1);
}
}
return simpleSummary;
}
private static class ProxySettings {
private final String host;
private final int port;
private String username;
private String keyForPassword;
public ProxySettings(String host, int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getUsername() {
return username;
}
public String getPassword() {
return new String(Keyring.read(keyForPassword));
}
public void setUsername(String username) {
this.username = username;
}
public void setKeyForPassword(String keyForPassword) {
this.keyForPassword = keyForPassword;
}
}
}