*/
package org.sonar.runner.impl;
+import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
+
import com.github.kevinsawicki.http.HttpRequest;
+
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.Properties;
-import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import org.apache.commons.io.FileUtils;
import org.sonar.home.cache.Logger;
import org.sonar.home.cache.PersistentCache;
private static final String SONAR_SERVER_CAN_NOT_BE_REACHED = "Sonar server ''{0}'' can not be reached";
private static final String STATUS_RETURNED_BY_URL_IS_INVALID = "Status returned by url : ''{0}'' is invalid : {1}";
- static final int CONNECT_TIMEOUT_MILLISECONDS = 30000;
+ static final int CONNECT_TIMEOUT_MILLISECONDS = 5000;
static final int READ_TIMEOUT_MILLISECONDS = 60000;
private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");
private final String userAgent;
private final PersistentCache wsCache;
- private final boolean isModePreview;
+ private final boolean isCacheEnable;
private final Logger logger;
- private ServerConnection(String serverUrl, String app, String appVersion, boolean preview, PersistentCache cache, Logger logger) {
+ private ServerConnection(String serverUrl, String app, String appVersion, boolean isCacheEnable, PersistentCache cache, Logger logger) {
this.logger = logger;
this.serverUrl = removeEndSlash(serverUrl);
this.userAgent = app + "/" + appVersion;
this.wsCache = cache;
- this.isModePreview = preview;
+ this.isCacheEnable = isCacheEnable;
}
private static String removeEndSlash(String url) {
String app = properties.getProperty(InternalProperties.RUNNER_APP);
String appVersion = properties.getProperty(InternalProperties.RUNNER_APP_VERSION);
String analysisMode = properties.getProperty("sonar.analysis.mode");
- boolean preview = "preview".equalsIgnoreCase(analysisMode);
+ String enableOffline = properties.getProperty("sonar.enableOffline");
+ boolean enableCache = "preview".equalsIgnoreCase(analysisMode) && "true".equals(enableOffline);
- return new ServerConnection(serverUrl, app, appVersion, preview, cache, logger);
+ return new ServerConnection(serverUrl, app, appVersion, enableCache, cache, logger);
}
- private class StringDownloader implements Callable<String> {
- private String url;
-
- StringDownloader(String url) {
- this.url = url;
- }
+ /**
+ *
+ * @throws HttpRequestException If there is an underlying IOException related to the connection
+ * @throws IOException If the HTTP response code is != 200
+ */
+ private String downloadString(String url, boolean saveCache) throws HttpRequestException, IOException {
+ HttpRequest httpRequest = null;
+ try {
+ httpRequest = newHttpRequest(new URL(url));
+ String charset = getCharsetFromContentType(httpRequest.contentType());
+ if (charset == null || "".equals(charset)) {
+ charset = "UTF-8";
+ }
+ if (!httpRequest.ok()) {
+ throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, url, httpRequest.code()));
+ }
- @Override
- public String call() throws Exception {
- HttpRequest httpRequest = null;
- try {
- httpRequest = newHttpRequest(new URL(url));
- String charset = getCharsetFromContentType(httpRequest.contentType());
- if (charset == null || "".equals(charset)) {
- charset = "UTF-8";
- }
- if (!httpRequest.ok()) {
- throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, url, httpRequest.code()));
- }
- return httpRequest.body(charset);
- } finally {
- if (httpRequest != null) {
- httpRequest.disconnect();
+ byte[] body = httpRequest.bytes();
+ if (saveCache) {
+ try {
+ wsCache.put(url, body);
+ } catch (IOException e) {
+ logger.warn("Failed to cache WS call: " + e.getMessage());
}
}
+ return new String(body, charset);
+ } finally {
+ if (httpRequest != null) {
+ httpRequest.disconnect();
+ }
}
}
}
}
- String downloadStringCache(String path) throws Exception {
+ /**
+ * Tries to fetch from server and falls back to cache. If both attempts fail, it throws the exception
+ * linked to the server connection failure.
+ */
+ String downloadStringCache(String path) throws IOException {
String fullUrl = serverUrl + path;
try {
- if (isModePreview) {
- return wsCache.getString(serverUrl, new StringDownloader(fullUrl));
- } else {
- return new StringDownloader(fullUrl).call();
- }
+ return downloadString(fullUrl, isCacheEnable);
} catch (HttpRequest.HttpRequestException e) {
if (e.getCause() instanceof ConnectException || e.getCause() instanceof UnknownHostException) {
- logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
+ if (isCacheEnable) {
+ return fallbackToCache(fullUrl, e);
+ }
}
+
+ logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
throw e;
}
}
+ private String fallbackToCache(String fullUrl, HttpRequest.HttpRequestException originalException) {
+ logger.info(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED + ", trying cache", serverUrl));
+
+ try {
+ String cached = wsCache.getString(fullUrl, null);
+ if (cached != null) {
+ return cached;
+ }
+ logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED + " and had a cache miss", serverUrl));
+ throw originalException;
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to access cache", e);
+ }
+
+ }
+
private HttpRequest newHttpRequest(URL url) {
HttpRequest request = HttpRequest.get(url);
request.trustAllCerts().trustAllHosts();
*/
package org.sonar.runner.impl;
+import com.github.kevinsawicki.http.HttpRequest;
+
import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
import java.util.Properties;
+
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.sonar.home.cache.Logger;
import org.sonar.home.cache.PersistentCache;
import org.sonar.home.cache.PersistentCacheBuilder;
-
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Mockito.mock;
Properties props = new Properties();
props.setProperty("sonar.host.url", httpServer.url() + "/");
props.setProperty("sonar.analysis.mode", "preview");
+ props.setProperty("sonar.enableOffline", "true");
assertThat(cacheDir.list().length).isEqualTo(0);
ServerConnection connection = ServerConnection.create(props, cache, mock(Logger.class));
assertThat(str).isEqualTo("abcde");
assertThat(cacheDir.list().length).isEqualTo(2);
- httpServer.setMockResponseData("never requested");
+ httpServer.after();
str = connection.downloadStringCache("/batch/index.txt");
assertThat(str).isEqualTo("abcde");
}
+
+ @Test
+ public void should_throw_connection_exception_() throws IOException {
+ File cacheDir = new File(temp.getRoot(), "ws_cache");
+ httpServer.setMockResponseData("abcde");
+ Properties props = new Properties();
+ props.setProperty("sonar.host.url", httpServer.url() + "/");
+
+ assertThat(cacheDir.list().length).isEqualTo(0);
+ ServerConnection connection = ServerConnection.create(props, cache, mock(Logger.class));
+ String str = connection.downloadStringCache("/batch/index.txt");
+ assertThat(str).isEqualTo("abcde");
+
+ httpServer.after();
+
+ try {
+ connection.downloadStringCache("/batch/index.txt");
+ fail("exception expected");
+ } catch(HttpRequest.HttpRequestException e) {
+ //expected
+ assertThat(e.getCause()).isInstanceOf(ConnectException.class);
+
+ //cache never used
+ assertThat(cacheDir.list().length).isEqualTo(0);
+ }
+
+ }
@Test
public void should_not_cache_not_preview() throws Exception {