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 java.io.IOException;
023import java.io.UnsupportedEncodingException;
024
025import org.apache.http.HttpEntity;
026import org.apache.http.HttpException;
027import org.apache.http.HttpHost;
028import org.apache.http.HttpRequest;
029import org.apache.http.HttpRequestInterceptor;
030import org.apache.http.HttpResponse;
031import org.apache.http.HttpStatus;
032import org.apache.http.auth.AuthScheme;
033import org.apache.http.auth.AuthScope;
034import org.apache.http.auth.AuthState;
035import org.apache.http.auth.Credentials;
036import org.apache.http.auth.UsernamePasswordCredentials;
037import org.apache.http.client.CredentialsProvider;
038import org.apache.http.client.methods.HttpDelete;
039import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
040import org.apache.http.client.methods.HttpGet;
041import org.apache.http.client.methods.HttpPost;
042import org.apache.http.client.methods.HttpPut;
043import org.apache.http.client.methods.HttpRequestBase;
044import org.apache.http.client.protocol.ClientContext;
045import org.apache.http.entity.StringEntity;
046import org.apache.http.impl.auth.BasicScheme;
047import org.apache.http.impl.client.DefaultHttpClient;
048import org.apache.http.params.CoreConnectionPNames;
049import org.apache.http.params.HttpConnectionParams;
050import org.apache.http.params.HttpParams;
051import org.apache.http.protocol.BasicHttpContext;
052import org.apache.http.protocol.ExecutionContext;
053import org.apache.http.protocol.HttpContext;
054import org.apache.http.util.EntityUtils;
055import org.sonar.wsclient.Host;
056import org.sonar.wsclient.services.AbstractQuery;
057import org.sonar.wsclient.services.CreateQuery;
058import org.sonar.wsclient.services.DeleteQuery;
059import org.sonar.wsclient.services.Query;
060import org.sonar.wsclient.services.UpdateQuery;
061
062/**
063 * @since 2.1
064 */
065public class HttpClient4Connector extends Connector {
066
067  private Host server;
068
069  public HttpClient4Connector(Host server) {
070    this.server = server;
071  }
072
073  @Override
074  public String execute(Query<?> query) {
075    return executeRequest(newGetMethod(query));
076  }
077
078  @Override
079  public String execute(CreateQuery<?> query) {
080    return executeRequest(newPostMethod(query));
081  }
082
083  @Override
084  public String execute(UpdateQuery<?> query) {
085    return executeRequest(newPutMethod(query));
086  }
087
088  @Override
089  public String execute(DeleteQuery query) {
090    return executeRequest(newDeleteMethod(query));
091  }
092
093  private String executeRequest(HttpRequestBase request) {
094    String json = null;
095    DefaultHttpClient client = createClient();
096    try {
097      BasicHttpContext context = createLocalContext(client);
098      HttpResponse response = client.execute(request, context);
099      HttpEntity entity = response.getEntity();
100      if (entity != null) {
101        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
102          json = EntityUtils.toString(entity);
103
104        } else if (response.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_FOUND) {
105          throw new ConnectionException("HTTP error: " + response.getStatusLine().getStatusCode()
106              + ", msg: " + response.getStatusLine().getReasonPhrase()
107              + ", query: " + request.toString());
108        }
109      }
110
111    } catch (IOException e) {
112      throw new ConnectionException("Query: " + request.getURI(), e);
113
114    } finally {
115      client.getConnectionManager().shutdown();
116    }
117    return json;
118  }
119
120  private DefaultHttpClient createClient() {
121    DefaultHttpClient client = new DefaultHttpClient();
122    HttpParams params = client.getParams();
123    HttpConnectionParams.setConnectionTimeout(params, AbstractQuery.DEFAULT_TIMEOUT_MILLISECONDS);
124    HttpConnectionParams.setSoTimeout(params, AbstractQuery.DEFAULT_TIMEOUT_MILLISECONDS);
125    if (server.getUsername() != null) {
126      client.getCredentialsProvider()
127          .setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(server.getUsername(), server.getPassword()));
128    }
129    return client;
130  }
131
132  private BasicHttpContext createLocalContext(DefaultHttpClient client) {
133    BasicHttpContext localcontext = new BasicHttpContext();
134
135    if (server.getUsername() != null) {
136      // Generate BASIC scheme object and stick it to the local
137      // execution context
138      BasicScheme basicAuth = new BasicScheme();
139      localcontext.setAttribute("preemptive-auth", basicAuth);
140
141      // Add as the first request interceptor
142      client.addRequestInterceptor(new PreemptiveAuth(), 0);
143    }
144    return localcontext;
145  }
146
147  private HttpGet newGetMethod(Query<?> query) {
148    HttpGet get = new HttpGet(server.getHost() + query.getUrl());
149    initRequest(get, query);
150    return get;
151  }
152
153  private HttpDelete newDeleteMethod(DeleteQuery query) {
154    HttpDelete delete = new HttpDelete(server.getHost() + query.getUrl());
155    initRequest(delete, query);
156    return delete;
157  }
158
159  private HttpPost newPostMethod(CreateQuery<?> query) {
160    HttpPost post = new HttpPost(server.getHost() + query.getUrl());
161    initRequest(post, query);
162    setRequestEntity(post, query);
163    return post;
164  }
165
166  private HttpPut newPutMethod(UpdateQuery<?> query) {
167    HttpPut put = new HttpPut(server.getHost() + query.getUrl());
168    initRequest(put, query);
169    setRequestEntity(put, query);
170    return put;
171  }
172
173  private void setRequestEntity(HttpEntityEnclosingRequestBase request, AbstractQuery<?> query) {
174    if (query.getBody() != null) {
175      try {
176        request.setEntity(new StringEntity(query.getBody(), "UTF-8"));
177      } catch (UnsupportedEncodingException e) {
178        throw new ConnectionException("Encoding is not supported", e);
179      }
180    }
181  }
182
183  private void initRequest(HttpRequestBase request, AbstractQuery query) {
184    request.setHeader("Accept", "application/json");
185    if (query.getLocale() != null) {
186      request.setHeader("Accept-Language", query.getLocale());
187    }
188    request.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, query.getTimeoutMilliseconds());
189    request.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, query.getTimeoutMilliseconds());
190  }
191
192  static final class PreemptiveAuth implements HttpRequestInterceptor {
193    public void process(
194        final HttpRequest request,
195        final HttpContext context) throws HttpException {
196
197      AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
198
199      // If no auth scheme available yet, try to initialize it preemptively
200      if (authState.getAuthScheme() == null) {
201        AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
202        CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
203        HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
204        if (authScheme != null) {
205          Credentials creds = credsProvider.getCredentials(
206              new AuthScope(
207                  targetHost.getHostName(),
208                  targetHost.getPort()));
209          if (creds == null) {
210            throw new HttpException("No credentials for preemptive authentication");
211          }
212          authState.setAuthScheme(authScheme);
213          authState.setCredentials(creds);
214        }
215      }
216    }
217  }
218}