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 */
020package org.sonar.wsclient;
021
022import org.sonar.wsclient.internal.HttpRequestFactory;
023import org.sonar.wsclient.issue.ActionPlanClient;
024import org.sonar.wsclient.issue.IssueClient;
025import org.sonar.wsclient.issue.internal.DefaultActionPlanClient;
026import org.sonar.wsclient.issue.internal.DefaultIssueClient;
027import org.sonar.wsclient.permissions.PermissionClient;
028import org.sonar.wsclient.permissions.internal.DefaultPermissionClient;
029import org.sonar.wsclient.project.ProjectClient;
030import org.sonar.wsclient.project.internal.DefaultProjectClient;
031import org.sonar.wsclient.qprofile.QProfileClient;
032import org.sonar.wsclient.qprofile.internal.DefaultQProfileClient;
033import org.sonar.wsclient.qualitygate.QualityGateClient;
034import org.sonar.wsclient.qualitygate.internal.DefaultQualityGateClient;
035import org.sonar.wsclient.system.SystemClient;
036import org.sonar.wsclient.system.internal.DefaultSystemClient;
037import org.sonar.wsclient.user.UserClient;
038import org.sonar.wsclient.user.internal.DefaultUserClient;
039
040import javax.annotation.Nullable;
041
042import java.util.Arrays;
043import java.util.HashMap;
044import java.util.Map;
045
046/**
047 * Entry point of the Java Client for Sonar Web Services. It does not support all web services yet.
048 * <p/>
049 * Example:
050 * <pre>
051 *   SonarClient client = SonarClient.create("http://localhost:9000");
052 *   IssueClient issueClient = client.issueClient();
053 * </pre>
054 *
055 * @since 3.6
056 */
057public class SonarClient {
058
059  public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30000;
060  public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60000;
061
062  /**
063   * Visibility relaxed for unit tests
064   */
065  final HttpRequestFactory requestFactory;
066
067  private SonarClient(Builder builder) {
068    this(new HttpRequestFactory(builder.url)
069      .setLogin(builder.login)
070      .setPassword(builder.password)
071      .setProxyHost(builder.proxyHost)
072      .setProxyPort(builder.proxyPort)
073      .setProxyLogin(builder.proxyLogin)
074      .setProxyPassword(builder.proxyPassword)
075      .setConnectTimeoutInMilliseconds(builder.connectTimeoutMs)
076      .setReadTimeoutInMilliseconds(builder.readTimeoutMs));
077  }
078
079  /**
080   * Visible for testing
081   */
082  SonarClient(HttpRequestFactory requestFactory) {
083    this.requestFactory = requestFactory;
084  }
085
086  /**
087   * New client to interact with web services related to issues
088   */
089  public IssueClient issueClient() {
090    return new DefaultIssueClient(requestFactory);
091  }
092
093  /**
094   * New client to interact with web services related to issue action plans
095   */
096  public ActionPlanClient actionPlanClient() {
097    return new DefaultActionPlanClient(requestFactory);
098  }
099
100  /**
101   * New client to interact with web services related to users
102   */
103  public UserClient userClient() {
104    return new DefaultUserClient(requestFactory);
105  }
106
107  /**
108   * New client to interact with web services related to users and groups permissions
109   */
110  public PermissionClient permissionClient() {
111    return new DefaultPermissionClient(requestFactory);
112  }
113
114  /**
115   * New client to interact with web services related to projects
116   */
117  public ProjectClient projectClient() {
118    return new DefaultProjectClient(requestFactory);
119  }
120
121  /**
122   * New client to interact with web services related to quality gates
123   */
124  public QualityGateClient qualityGateClient() {
125    return new DefaultQualityGateClient(requestFactory);
126  }
127
128  /**
129   * New client to interact with web services related to quality profiles
130   */
131  public QProfileClient qProfileClient() {
132    return new DefaultQProfileClient(requestFactory);
133  }
134
135  public SystemClient systemClient() {
136    return new DefaultSystemClient(requestFactory);
137  }
138
139  /**
140   * Create a builder of {@link SonarClient}s.
141   */
142  public static Builder builder() {
143    return new Builder();
144  }
145
146  /**
147   * Create a client with default configuration. Use {@link #builder()} to define
148   * a custom configuration (credentials, HTTP proxy, HTTP timeouts).
149   */
150  public static SonarClient create(String serverUrl) {
151    return builder().url(serverUrl).build();
152  }
153
154  /**
155   * Send a POST request on the given relativeUrl, with provided parameters (can be empty).
156   * The beginning slash (/) of relativeUrl is supported but not mandatory.
157   * <p/>
158   * Example:
159   * <pre>  {@code
160   *   Map<String,Object> params = new HashMap<>();
161   *   params.put("name", "My Quality Gate");
162   *   client.post("api/qualitygates/create", params);
163   * }</pre>
164   * @since 4.5
165   * @return the response body
166   */
167  public String post(String relativeUrl, Map<String, Object> params) {
168    return requestFactory.post(relativeUrl, params);
169  }
170
171  /**
172   * Same as {@link #post(String, java.util.Map)} but parameters are defined as an array
173   * of even number of elements (key1, value1, key, value2, ...). Keys must not be null.
174   */
175  public String post(String relativeUrl, Object... params) {
176    return post(relativeUrl, paramsAsMap(params));
177  }
178
179  /**
180   * Send a GET request on the given relativeUrl, with provided parameters (can be empty).
181   * The beginning slash (/) of relativeUrl is supported but not mandatory.
182   * @since 4.5
183   * @return the response body
184   */
185  public String get(String relativeUrl, Map<String, Object> params) {
186    return requestFactory.get(relativeUrl, params);
187  }
188
189  /**
190   * Same as {@link #get(String, java.util.Map)} but parameters are defined as an array
191   * of even number of elements (key1, value1, key, value2, ...). Keys must not be null.
192   */
193  public String get(String relativeUrl, Object... params) {
194    return get(relativeUrl, paramsAsMap(params));
195  }
196
197  private Map<String, Object> paramsAsMap(Object[] params) {
198    if (params.length % 2 == 1) {
199      throw new IllegalArgumentException(String.format(
200        "Expecting even number of elements. Got %s", Arrays.toString(params)));
201    }
202    Map<String, Object> map = new HashMap<String, Object>();
203    for (int index = 0; index < params.length; index++) {
204      if (params[index] == null) {
205        throw new IllegalArgumentException(String.format(
206          "Parameter key can't be null at index %d of %s", index, Arrays.toString(params)));
207      }
208      map.put(params[index].toString(), params[index + 1]);
209      index++;
210    }
211    return map;
212  }
213
214  public static class Builder {
215    private String login, password, url, proxyHost, proxyLogin, proxyPassword;
216    private int proxyPort = 0;
217    private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS, readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS;
218
219    private Builder() {
220    }
221
222    /**
223     * Mandatory HTTP server URL, eg "http://localhost:9000"
224     */
225    public Builder url(String url) {
226      this.url = url;
227      return this;
228    }
229
230    /**
231     * Optional login, for example "admin"
232     */
233    public Builder login(@Nullable String login) {
234      this.login = login;
235      return this;
236    }
237
238    /**
239     * Optional password related to {@link #login(String)}, for example "admin"
240     */
241    public Builder password(@Nullable String password) {
242      this.password = password;
243      return this;
244    }
245
246    /**
247     * Host and port of the optional HTTP proxy
248     */
249    public Builder proxy(@Nullable String proxyHost, int proxyPort) {
250      this.proxyHost = proxyHost;
251      this.proxyPort = proxyPort;
252      return this;
253    }
254
255    public Builder proxyLogin(@Nullable String proxyLogin) {
256      this.proxyLogin = proxyLogin;
257      return this;
258    }
259
260    public Builder proxyPassword(@Nullable String proxyPassword) {
261      this.proxyPassword = proxyPassword;
262      return this;
263    }
264
265    /**
266     * Sets a specified timeout value, in milliseconds, to be used when opening HTTP connection.
267     * A timeout of zero is interpreted as an infinite timeout. Default value is {@link SonarClient#DEFAULT_CONNECT_TIMEOUT_MILLISECONDS}
268     */
269    public Builder connectTimeoutMilliseconds(int i) {
270      this.connectTimeoutMs = i;
271      return this;
272    }
273
274    /**
275     * Sets the read timeout to a specified timeout, in milliseconds.
276     * A timeout of zero is interpreted as an infinite timeout. Default value is {@link SonarClient#DEFAULT_READ_TIMEOUT_MILLISECONDS}
277     */
278    public Builder readTimeoutMilliseconds(int i) {
279      this.readTimeoutMs = i;
280      return this;
281    }
282
283    /**
284     * Build a new client
285     */
286    public SonarClient build() {
287      if (url == null || "".equals(url)) {
288        throw new IllegalStateException("Server URL must be set");
289      }
290      return new SonarClient(this);
291    }
292  }
293}