001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar 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 * Sonar 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
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.wsclient.connectors;
021
022import org.apache.commons.httpclient.*;
023import org.apache.commons.httpclient.auth.AuthScope;
024import org.apache.commons.httpclient.methods.*;
025import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
026import org.sonar.wsclient.Host;
027import org.sonar.wsclient.services.*;
028
029import java.io.*;
030
031/**
032 * @since 2.1
033 */
034public class HttpClient3Connector extends Connector {
035
036  private static final int MAX_TOTAL_CONNECTIONS = 40;
037  private static final int MAX_HOST_CONNECTIONS = 4;
038
039  private final Host server;
040  private HttpClient httpClient;
041
042  public HttpClient3Connector(final Host server) {
043    this.server = server;
044    createClient();
045  }
046
047  public HttpClient3Connector(Host server, HttpClient httpClient) {
048    this.httpClient = httpClient;
049    this.server = server;
050  }
051
052  private void createClient() {
053    final HttpConnectionManagerParams params = new HttpConnectionManagerParams();
054    params.setConnectionTimeout(AbstractQuery.DEFAULT_TIMEOUT_MILLISECONDS);
055    params.setSoTimeout(AbstractQuery.DEFAULT_TIMEOUT_MILLISECONDS);
056    params.setDefaultMaxConnectionsPerHost(MAX_HOST_CONNECTIONS);
057    params.setMaxTotalConnections(MAX_TOTAL_CONNECTIONS);
058    final MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
059    connectionManager.setParams(params);
060    this.httpClient = new HttpClient(connectionManager);
061    configureCredentials();
062  }
063
064  private void configureCredentials() {
065    if (server.getUsername() != null) {
066      httpClient.getParams().setAuthenticationPreemptive(true);
067      Credentials defaultcreds = new UsernamePasswordCredentials(server.getUsername(), server.getPassword());
068      httpClient.getState().setCredentials(AuthScope.ANY, defaultcreds);
069    }
070  }
071
072  /**
073   * @since 3.4
074   */
075  public HttpClient getHttpClient() {
076    return httpClient;
077  }
078
079  @Override
080  public String execute(Query<?> query) {
081    return executeRequest(newGetRequest(query));
082  }
083
084  @Override
085  public String execute(CreateQuery<?> query) {
086    return executeRequest(newPostRequest(query));
087  }
088
089  @Override
090  public String execute(UpdateQuery<?> query) {
091    return executeRequest(newPutRequest(query));
092  }
093
094  @Override
095  public String execute(DeleteQuery query) {
096    return executeRequest(newDeleteRequest(query));
097  }
098
099  private String executeRequest(HttpMethodBase method) {
100    String json = null;
101    try {
102      httpClient.executeMethod(method);
103
104      if (method.getStatusCode() == HttpStatus.SC_OK) {
105        json = getResponseBodyAsString(method);
106
107      } else if (method.getStatusCode() != HttpStatus.SC_NOT_FOUND) {
108        throw new ConnectionException("HTTP error: " + method.getStatusCode() + ", msg: " + method.getStatusText() + ", query: " + method);
109      }
110
111    } catch (HttpException e) {
112      throw new ConnectionException("Query: " + method, e);
113
114    } catch (IOException e) {
115      throw new ConnectionException("Query: " + method, e);
116
117    } finally {
118      if (method != null) {
119        method.releaseConnection();
120      }
121    }
122    return json;
123  }
124
125  private HttpMethodBase newGetRequest(Query<?> query) {
126    HttpMethodBase method = new GetMethod(server.getHost() + query.getUrl());
127    initRequest(method, query);
128    return method;
129  }
130
131  private HttpMethodBase newDeleteRequest(DeleteQuery query) {
132    HttpMethodBase method = new DeleteMethod(server.getHost() + query.getUrl());
133    initRequest(method, query);
134    return method;
135  }
136
137  private HttpMethodBase newPostRequest(CreateQuery<?> query) {
138    PostMethod method = new PostMethod(server.getHost() + query.getUrl());
139    initRequest(method, query);
140    setRequestEntity(method, query);
141    return method;
142  }
143
144  private HttpMethodBase newPutRequest(UpdateQuery<?> query) {
145    PutMethod method = new PutMethod(server.getHost() + query.getUrl());
146    initRequest(method, query);
147    setRequestEntity(method, query);
148    return method;
149  }
150
151  private void setRequestEntity(EntityEnclosingMethod request, AbstractQuery<?> query) {
152    if (query.getBody() != null) {
153      try {
154        request.setRequestEntity(new StringRequestEntity(query.getBody(), "text/plain; charset=UTF-8", "UTF-8"));
155      } catch (UnsupportedEncodingException e) {
156        throw new ConnectionException("Unsupported encoding", e);
157      }
158    }
159  }
160
161  private void initRequest(HttpMethodBase request, AbstractQuery query) {
162    request.setRequestHeader("Accept", "application/json");
163    if (query.getLocale() != null) {
164      request.setRequestHeader("Accept-Language", query.getLocale());
165    }
166    request.getParams().setSoTimeout(query.getTimeoutMilliseconds());
167  }
168
169  private String getResponseBodyAsString(HttpMethod method) {
170    BufferedReader reader = null;
171    try {
172      final InputStream inputStream = method.getResponseBodyAsStream();
173      reader = new BufferedReader(new InputStreamReader(inputStream));
174      final StringBuilder sb = new StringBuilder();
175      String line;
176
177      while ((line = reader.readLine()) != null) {
178        sb.append(line).append("\n");
179      }
180      return sb.toString();
181
182    } catch (IOException e) {
183      throw new ConnectionException("Can not read response", e);
184
185    } finally {
186      if (reader != null) {
187        try {
188          reader.close();
189        } catch (Exception e) {
190          // wsclient does not have logging ability -> silently ignore
191        }
192      }
193    }
194  }
195}