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