blob: c2586c291e2923cfd7fbd259bbe168cfd305e458 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015-2016 Ericsson
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Francois Chouinard - Initial API and implementation
*******************************************************************************/
package org.eclipse.egerrit.internal.core;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.SSLHandshakeException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.util.EntityUtils;
import org.eclipse.egerrit.internal.core.exception.EGerritException;
import org.osgi.framework.Version;
import org.slf4j.LoggerFactory;
/**
* A GerritRepository represents the interface to a gerrit repository.
* <p>
* In this implementation, the gerrit repository is accessible through HTTP(S) for the REST operations.
*
* @since 1.0
*/
public class GerritRepository {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(GerritRepository.class);
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
public final static Version NO_VERSION = new Version(0, 0, 0);
/** The gerrit scheme */
private final String fScheme;
/** The gerrit host name */
private final String fHostname;
/** The gerrit port */
private final int fPort;
/** The host URL */
private final String fPath;
/** The gerrit host */
private final HttpHost fHost;
/** The user credentials for the repository */
private GerritCredentials fCredentials = null;
/** The proxy host (if applicable) */
private boolean fAcceptSelfSignedCerts;
/** The repository version */
private Version fVersion = null;
/** The HTTP client */
private GerritHttpClient fHttpClient = null;
/** The gerrit server information */
private GerritServerInformation serverInfo = null;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
private int fStatus;
/**
* The constructor
*
* @param protocol
* The transport protocol (http, https, ...)
* @param host
* The host URL
* @param port
* The gerrit port
* @param path
* The gerrit path
*/
public GerritRepository(String scheme, String host, int port, String path) {
fScheme = scheme;
fHostname = host;
fPort = port;
if (fHostname != null && !fHostname.isEmpty()) {
//Host name may not be blank
fHost = new HttpHost(fHostname, fPort, fScheme);
} else {
fHost = null;
}
fPath = path;
}
/**
* The constructor
*
* @param urlString
* The repository URL
*/
public GerritRepository(String urlString) throws EGerritException {
try {
URL url = new URL(urlString);
fScheme = url.getProtocol();
fHostname = url.getHost();
fPort = url.getPort();
fHost = new HttpHost(fHostname, fPort, fScheme);
fPath = url.getPath();
} catch (MalformedURLException e) {
throw new EGerritException(e.getLocalizedMessage());
}
}
// ------------------------------------------------------------------------
// Setters
// ------------------------------------------------------------------------
public GerritRepository acceptSelfSignedCerts(boolean accept) {
fAcceptSelfSignedCerts = accept;
return this;
}
public GerritRepository setCredentials(GerritCredentials credentials) {
fCredentials = credentials;
fHttpClient = null;//need to reset the HttpClient when resetting the Credentials
return this;
}
// ------------------------------------------------------------------------
// Getters
// ------------------------------------------------------------------------
/**
* @return the repository version
*/
public Version getVersion() {
if (fVersion == null) {
fVersion = queryVersion();
}
return fVersion;
}
/**
* @return the transport protocol
*/
public String getScheme() {
return fScheme;
}
/**
* @return the host URL
*/
public HttpHost getHost() {
return fHost;
}
/**
* @return
*/
public String getPath() {
return fPath;
}
/**
* @return the host name
*/
public String getHostname() {
return fHost != null ? fHost.getHostName() : ""; //$NON-NLS-1$
}
/**
* @return the gerrit port
*/
public int getPort() {
return fHost != null ? fHost.getPort() : -1;
}
/**
* @return the user ID
*/
public GerritCredentials getCredentials() {
return fCredentials;
}
// ------------------------------------------------------------------------
// Operations
// ------------------------------------------------------------------------
/**
* Validate the gerrit connection and retrieves the server version.
*
* @throws EGerritException
*/
public boolean connect() {
fVersion = null;
fHttpClient = getHttpClient();
if (fHttpClient != null) {
fVersion = queryVersion();
}
return fVersion != null;
}
/**
* @return the HTTP client for this repository
*/
public GerritHttpClient getHttpClient() {
if (fHttpClient == null) {
GerritHttpClient client = new GerritHttpClient(this, fCredentials);
if (client.authenticate()) {
fHttpClient = client;
}
fStatus = client.getStatus();
}
return fHttpClient;
}
/**
* Construct a base URI of the form "<prot>://<host>:<port>/<path>" which can then be augmented by the interested
* parties.
*
* @return the generic URI to access the repository for REST operations
*/
public URIBuilder getURIBuilder(boolean requiresAuthentication) {
// Build the path
StringBuilder sb = new StringBuilder(fPath);
if (requiresAuthentication) {
sb.append("/a"); //$NON-NLS-1$
}
String path = sb.toString();
URIBuilder builder = new URIBuilder().setScheme(fScheme).setHost(fHostname).setPath(path);
if (fPort > 0) {
builder.setPort(fPort);
}
return builder;
}
/**
* Returns the server information
*
* @return the {@link GerritServerInformation} associated with this repository
*/
public GerritServerInformation getServerInfo() {
return serverInfo;
}
/**
* Sets the server information
*/
public void setServerInfo(GerritServerInformation serverInfo) {
this.serverInfo = serverInfo;
}
// ------------------------------------------------------------------------
// Version discovery
// ------------------------------------------------------------------------
private static final String VERSION_REQUEST = "/config/server/version"; //$NON-NLS-1$
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; //$NON-NLS-1$
public static final int SSL_PROBLEM = -1;
public static final int SSL_INVALID_ROOT_CERTIFICATE = -2;
private Version queryVersion() {
Version version = null;
try {
URIBuilder builder = getURIBuilder(false);
String path = new StringBuilder(builder.getPath()).append(VERSION_REQUEST).toString();
URI uri = builder.setPath(path).build();
HttpUriRequest request = new HttpGet(uri);
logger.debug("Request: " + uri.toString()); //$NON-NLS-1$
ResponseHandler<String> rh = new ResponseHandler<String>() {
@Override
public String handleResponse(final HttpResponse response) throws IOException {
StatusLine statusLine = response.getStatusLine();
logger.debug("Result : " + statusLine.toString()); //$NON-NLS-1$
int status = statusLine.getStatusCode();
if (status < 200 || status >= 300) {
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new ClientProtocolException("Response has no content"); //$NON-NLS-1$
}
// The response has the general form:
// ")]}'\n\"<version>\"\n"
// To extract raw server version, we have to abstract the
// JSON
// prefix, the version enclosing quotes and the final line
// feed.
int prefixLength = JSON_NON_EXECUTABLE_PREFIX.length();
String rawResult = EntityUtils.toString(entity);
logger.debug("Server version information is: '" + rawResult + "'"); //$NON-NLS-1$ //$NON-NLS-2$
return rawResult.substring(prefixLength + 1, rawResult.length() - 2);
}
};
GerritHttpClient gerritClient = getHttpClient();
if (gerritClient != null) {
String result = getHttpClient().execute(request, rh);
version = parseVersion(result);
}
} catch (HttpResponseException httpException) {
if (httpException.getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
version = NO_VERSION;
} else {
EGerritCorePlugin.logError(httpException.getLocalizedMessage(), httpException);
}
} catch (SSLHandshakeException e) {
fStatus = SSL_PROBLEM;
if (e.getCause().getClass().getName().equals("sun.security.validator.ValidatorException")) { //$NON-NLS-1$
fStatus = SSL_INVALID_ROOT_CERTIFICATE;
}
EGerritCorePlugin.logError(e.getLocalizedMessage(), e);
} catch (IOException | URISyntaxException e) {
EGerritCorePlugin.logError(e.getLocalizedMessage(), e);
}
return version;
}
private static final Pattern MAJOR_MINOR_MICRO_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)?(.*)"); //$NON-NLS-1$
private static final Pattern MAJOR_MINOR_QUALIFIER_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)-(\\w+).*"); //$NON-NLS-1$
private static final Pattern MAJOR_MINOR_MICRO_QUALIFIER_VERSION_PATTERN = Pattern
.compile("(\\d+)\\.(\\d+).(\\d+)-(\\w+).*"); //$NON-NLS-1$
private static final Pattern MAJOR_MINOR_RC_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)(\\d+)?(.*)"); //$NON-NLS-1$
private Version parseVersion(String rawVersion) {
// The raw version string has the general form:
// "<major>.<minor>[[.<micro>]-<qualifier>]"
Matcher matcher = MAJOR_MINOR_MICRO_VERSION_PATTERN.matcher(rawVersion);
if (matcher.matches()) {
// Try to math a major-minor-micro with any other string attach (2.14.6-54-ge539946) as qualifier
return new Version(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)),
Integer.parseInt(matcher.group(3)), matcher.group(4));
}
matcher = MAJOR_MINOR_QUALIFIER_VERSION_PATTERN.matcher(rawVersion);
if (matcher.matches()) {
return new Version(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), 0,
matcher.group(3));
}
matcher = MAJOR_MINOR_MICRO_QUALIFIER_VERSION_PATTERN.matcher(rawVersion);
if (matcher.matches()) {
return new Version(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)),
Integer.parseInt(matcher.group(3)), matcher.group(4));
}
//Try to math a major-minor with any other string attach (2.15-rc1-577-g6b1fed0abe)
// "<major>.<minor>-<qualifier>"
matcher = MAJOR_MINOR_RC_VERSION_PATTERN.matcher(rawVersion);
if (matcher.matches()) {
return new Version(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), 0,
matcher.group(4));
}
return Version.emptyVersion;
}
public GerritClient instantiateGerrit() {
GerritClient gerrit = null;
try {
gerrit = GerritFactory.create(this);
} catch (EGerritException e) {
EGerritCorePlugin.logError(formatGerritVersion() + e.getLocalizedMessage(), e);
}
return gerrit;
}
/**
* Format a string with the Gerrit version if available
*
* @return String
*/
public String formatGerritVersion() {
String gerritVersion;
StringBuilder sb = new StringBuilder();
sb.append("Gerrit server: " + (serverInfo != null ? serverInfo.getServerURI() : "Unknown")); //$NON-NLS-1$ //$NON-NLS-2$
sb.append("\nVersion: "); //$NON-NLS-1$
gerritVersion = fVersion != null ? fVersion.toString() : "Unknown"; //$NON-NLS-1$
sb.append(gerritVersion);
sb.append("\n"); //$NON-NLS-1$
return sb.toString();
}
/*
* return the error code of the http connection
* @return
*/
public int getStatus() {
return fStatus;
}
}