001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.batch.bootstrap;
021
022 import com.google.common.base.Preconditions;
023 import com.google.common.base.Strings;
024 import com.google.common.io.Files;
025 import com.google.common.io.InputSupplier;
026 import org.apache.commons.io.IOUtils;
027 import org.apache.commons.lang.StringEscapeUtils;
028 import org.apache.commons.lang.StringUtils;
029 import org.sonar.api.BatchComponent;
030 import org.sonar.api.CoreProperties;
031 import org.sonar.api.utils.HttpDownloader;
032 import org.sonar.batch.bootstrapper.EnvironmentInformation;
033
034 import javax.annotation.Nullable;
035
036 import java.io.File;
037 import java.io.IOException;
038 import java.io.InputStream;
039 import java.io.UnsupportedEncodingException;
040 import java.net.URI;
041 import java.net.URLEncoder;
042
043 /**
044 * Replace the deprecated org.sonar.batch.ServerMetadata
045 * TODO extends Server when removing the deprecated org.sonar.batch.ServerMetadata
046 *
047 * @since 3.4
048 */
049 public class ServerClient implements BatchComponent {
050
051 private BootstrapProperties props;
052 private HttpDownloader.BaseHttpDownloader downloader;
053
054 public ServerClient(BootstrapProperties settings, EnvironmentInformation env) {
055 this.props = settings;
056 this.downloader = new HttpDownloader.BaseHttpDownloader(settings.properties(), env.toString());
057 }
058
059 public String getURL() {
060 return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/");
061 }
062
063 public void download(String pathStartingWithSlash, File toFile) {
064 download(pathStartingWithSlash, toFile, null);
065 }
066
067 public void download(String pathStartingWithSlash, File toFile, @Nullable Integer readTimeoutMillis) {
068 try {
069 InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash, readTimeoutMillis);
070 Files.copy(inputSupplier, toFile);
071 } catch (HttpDownloader.HttpException he) {
072 throw handleHttpException(he);
073 } catch (IOException e) {
074 throw new IllegalStateException(String.format("Unable to download '%s' to: %s", pathStartingWithSlash, toFile), e);
075 }
076 }
077
078 public String request(String pathStartingWithSlash) {
079 return request(pathStartingWithSlash, true);
080 }
081
082 public String request(String pathStartingWithSlash, boolean wrapHttpException) {
083 return request(pathStartingWithSlash, wrapHttpException, null);
084 }
085
086 public String request(String pathStartingWithSlash, boolean wrapHttpException, @Nullable Integer timeoutMillis) {
087 InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash, timeoutMillis);
088 try {
089 return IOUtils.toString(inputSupplier.getInput(), "UTF-8");
090 } catch (HttpDownloader.HttpException e) {
091 throw wrapHttpException ? handleHttpException(e) : e;
092 } catch (IOException e) {
093 throw new IllegalStateException(String.format("Unable to request: %s", pathStartingWithSlash), e);
094 }
095 }
096
097 private InputSupplier<InputStream> doRequest(String pathStartingWithSlash, @Nullable Integer timeoutMillis) {
098 Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /");
099 String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash);
100
101 URI uri = URI.create(getURL() + path);
102 try {
103 InputSupplier<InputStream> inputSupplier;
104 if (Strings.isNullOrEmpty(getLogin())) {
105 inputSupplier = downloader.newInputSupplier(uri, timeoutMillis);
106 } else {
107 inputSupplier = downloader.newInputSupplier(uri, getLogin(), getPassword(), timeoutMillis);
108 }
109 return inputSupplier;
110 } catch (Exception e) {
111 throw new IllegalStateException(String.format("Unable to request: %s", uri), e);
112 }
113 }
114
115 private RuntimeException handleHttpException(HttpDownloader.HttpException he) {
116 if (he.getResponseCode() == 401) {
117 return new IllegalStateException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD));
118 }
119 if (he.getResponseCode() == 403) {
120 // SONAR-4397 Details are in response content
121 return new IllegalStateException(he.getResponseContent());
122 }
123 return new IllegalStateException(String.format("Fail to execute request [code=%s, url=%s]", he.getResponseCode(), he.getUri()), he);
124 }
125
126 private String getMessageWhenNotAuthorized() {
127 if (Strings.isNullOrEmpty(getLogin()) && Strings.isNullOrEmpty(getPassword())) {
128 return "Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties %s and %s.";
129 }
130 return "Not authorized. Please check the properties %s and %s.";
131 }
132
133 private String getLogin() {
134 return props.property(CoreProperties.LOGIN);
135 }
136
137 private String getPassword() {
138 return props.property(CoreProperties.PASSWORD);
139 }
140
141 public static String encodeForUrl(String url) {
142 try {
143 return URLEncoder.encode(url, "UTF-8");
144
145 } catch (UnsupportedEncodingException e) {
146 throw new IllegalStateException("Encoding not supported", e);
147 }
148 }
149 }