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.wsclient;
021    
022    import org.sonar.wsclient.internal.HttpRequestFactory;
023    import org.sonar.wsclient.issue.ActionPlanClient;
024    import org.sonar.wsclient.issue.IssueClient;
025    import org.sonar.wsclient.issue.internal.DefaultActionPlanClient;
026    import org.sonar.wsclient.issue.internal.DefaultIssueClient;
027    import org.sonar.wsclient.permissions.PermissionClient;
028    import org.sonar.wsclient.permissions.internal.DefaultPermissionClient;
029    import org.sonar.wsclient.project.ProjectClient;
030    import org.sonar.wsclient.project.internal.DefaultProjectClient;
031    import org.sonar.wsclient.qprofile.QProfileClient;
032    import org.sonar.wsclient.qprofile.internal.DefaultQProfileClient;
033    import org.sonar.wsclient.qualitygate.QualityGateClient;
034    import org.sonar.wsclient.qualitygate.internal.DefaultQualityGateClient;
035    import org.sonar.wsclient.system.SystemClient;
036    import org.sonar.wsclient.system.internal.DefaultSystemClient;
037    import org.sonar.wsclient.user.UserClient;
038    import org.sonar.wsclient.user.internal.DefaultUserClient;
039    
040    import javax.annotation.Nullable;
041    
042    import java.util.Arrays;
043    import java.util.HashMap;
044    import 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     */
057    public 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    }