All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.runner.impl.ServerConnection Maven / Gradle / Ivy

/*
 * SonarQube Runner - API
 * Copyright (C) 2011 SonarSource
 * [email protected]
 *
 * This program 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.
 *
 * This program 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  02
 */
package org.sonar.runner.impl;

import com.github.kevinsawicki.http.HttpRequest;
import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.sonar.runner.cache.Logger;
import org.sonar.runner.cache.PersistentCache;

class ServerConnection {

  private static final String SONAR_SERVER_CAN_NOT_BE_REACHED = "SonarQube 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 = 5000;
  static final int READ_TIMEOUT_MILLISECONDS = 60000;
  private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");

  private final String serverUrl;
  private final String userAgent;

  private final PersistentCache wsCache;
  private final boolean preferCache;
  private final Logger logger;
  private final boolean isCacheEnabled;

  private ServerConnection(String serverUrl, String app, String appVersion, boolean preferCache, boolean cacheEnabled, PersistentCache cache, Logger logger) {
    this.isCacheEnabled = cacheEnabled;
    this.logger = logger;
    this.serverUrl = removeEndSlash(serverUrl);
    this.userAgent = app + "/" + appVersion;
    this.wsCache = cache;
    this.preferCache = preferCache;
  }

  private static String removeEndSlash(String url) {
    return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
  }

  static ServerConnection create(Properties properties, PersistentCache cache, Logger logger, boolean preferCache) {
    String serverUrl = properties.getProperty("sonar.host.url");
    String app = properties.getProperty(InternalProperties.RUNNER_APP);
    String appVersion = properties.getProperty(InternalProperties.RUNNER_APP_VERSION);
    boolean enableCache = isCacheEnabled(properties);

    return new ServerConnection(serverUrl, app, appVersion, preferCache, enableCache, cache, logger);
  }

  private static boolean isCacheEnabled(Properties properties) {
    String analysisMode = properties.getProperty("sonar.analysis.mode");
    return "issues".equalsIgnoreCase(analysisMode);
  }

  /**
   * 
   * @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 {
    logger.debug("Download: " + url);
    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()));
      }

      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();
      }
    }
  }

  void download(String path, File toFile) {
    String fullUrl = serverUrl + path;
    try {
      logger.debug("Download " + fullUrl + " to " + toFile.getAbsolutePath());
      HttpRequest httpRequest = newHttpRequest(new URL(fullUrl));
      if (!httpRequest.ok()) {
        throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, fullUrl, httpRequest.code()));
      }
      httpRequest.receive(toFile);

    } catch (Exception e) {
      if (isCausedByConnection(e)) {
        logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
      }
      FileUtils.deleteQuietly(toFile);
      throw new IllegalStateException("Fail to download: " + fullUrl, e);
    }
  }

  /**
   * Tries to fetch from cache and server. If both attempts fail, it throws the exception linked to the server connection failure.
   */
  String loadString(String path) throws IOException {
    String fullUrl = serverUrl + path;

    if (isCacheEnabled && preferCache) {
      return tryCacheFirst(fullUrl);
    } else {
      return tryServerFirst(fullUrl, isCacheEnabled);
    }

  }

  private String tryCacheFirst(String fullUrl) throws IOException {
    String cached = getFromCache(fullUrl);
    if (cached != null) {
      return cached;
    }

    try {
      return downloadString(fullUrl, preferCache);
    } catch (Exception e) {
      logger.error(MessageFormat.format("Data is not cached and " + SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
      throw e;
    }
  }

  private String tryServerFirst(String fullUrl, boolean cacheEnabled) throws IOException {
    try {
      return downloadString(fullUrl, cacheEnabled);
    } catch (HttpRequest.HttpRequestException e) {
      if (cacheEnabled && isCausedByConnection(e)) {
        logger.info(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED + ", trying cache", serverUrl));
        String cached = getFromCache(fullUrl);
        if (cached != null) {
          return cached;
        }
        logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED + " and data is not cached", serverUrl));
        throw e;
      }

      logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
      throw e;
    }
  }

  private static boolean isCausedByConnection(Exception e) {
    return e.getCause() instanceof ConnectException || e.getCause() instanceof UnknownHostException ||
      e.getCause() instanceof java.net.SocketTimeoutException;
  }

  private String getFromCache(String fullUrl) {
    try {
      return wsCache.getString(fullUrl);
    } catch (IOException e) {
      throw new IllegalStateException("Failed to access cache", e);
    }
  }

  private HttpRequest newHttpRequest(URL url) {
    HttpRequest request = HttpRequest.get(url);
    request.trustAllCerts().trustAllHosts();
    request.acceptGzipEncoding().uncompress(true);
    request.connectTimeout(CONNECT_TIMEOUT_MILLISECONDS).readTimeout(READ_TIMEOUT_MILLISECONDS);
    request.userAgent(userAgent);
    return request;
  }

  /**
   * Parse out a charset from a content type header.
   *
   * @param contentType e.g. "text/html; charset=EUC-JP"
   * @return "EUC-JP", or null if not found. Charset is trimmed and upper-cased.
   */
  static String getCharsetFromContentType(String contentType) {
    if (contentType == null) {
      return null;
    }
    Matcher m = CHARSET_PATTERN.matcher(contentType);
    if (m.find()) {
      return m.group(1).trim().toUpperCase();
    }
    return null;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy