/**
 * Copyright (C) 2015 Zalando SE (http://tech.zalando.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zalando.stups.tokens;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.fasterxml.jackson.databind.ObjectMapper;

public class CloseableHttpProvider extends AbstractHttpProvider {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final CloseableHttpClient client;
    private final HttpClientContext localContext;
    private UserCredentials userCredentials;
    private URI accessTokenUri;
    private final HttpHost host;
    private final RequestConfig requestConfig;

    public CloseableHttpProvider(ClientCredentials clientCredentials, UserCredentials userCredentials,
            URI accessTokenUri, HttpConfig httpConfig) {
        this.userCredentials = userCredentials;
        this.accessTokenUri = accessTokenUri;
        requestConfig = RequestConfig.custom().setSocketTimeout(httpConfig.getSocketTimeout())
                .setConnectTimeout(httpConfig.getConnectTimeout())
                .setConnectionRequestTimeout(httpConfig.getConnectionRequestTimeout())
                .setStaleConnectionCheckEnabled(httpConfig.isStaleConnectionCheckEnabled()).build();

        // prepare basic auth credentials
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(new AuthScope(accessTokenUri.getHost(), accessTokenUri.getPort()),
                new UsernamePasswordCredentials(clientCredentials.getId(), clientCredentials.getSecret()));

        client = HttpClients.custom().setUserAgent(USER_AGENT.get()).useSystemProperties()
                .setDefaultCredentialsProvider(credentialsProvider).build();

        host = new HttpHost(accessTokenUri.getHost(), accessTokenUri.getPort(), accessTokenUri.getScheme());

        // enable basic auth for the request
        final AuthCache authCache = new BasicAuthCache();
        final BasicScheme basicAuth = new BasicScheme();
        authCache.put(host, basicAuth);

        localContext = HttpClientContext.create();
        localContext.setAuthCache(authCache);
    }

    @Override
    public AccessToken createToken(final AccessTokenConfiguration tokenConfig) throws UnsupportedEncodingException {
        final List<NameValuePair> values = buildParameterList(tokenConfig);

        final HttpPost request = new HttpPost(accessTokenUri);
        request.setEntity(new UrlEncodedFormEntity(values));
        request.setConfig(requestConfig);

        try (final CloseableHttpResponse response = client.execute(host, request, localContext)) {

            // success status code?
            final int status = response.getStatusLine().getStatusCode();
            if (status < 200 || status >= 300) {
                throw AccessTokenEndpointException.from(response);
            }

            // get json response
            final HttpEntity entity = response.getEntity();
            final AccessTokenResponse accessTokenResponse = OBJECT_MAPPER.readValue(EntityUtils.toByteArray(entity),
                    AccessTokenResponse.class);

            // create new access token object
            final Date validUntil = calculateValidUntil(accessTokenResponse);

            return new AccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getTokenType(),
                    accessTokenResponse.getExpiresInSeconds(), validUntil);
        } catch (Throwable t) {
            throw new AccessTokenEndpointException(t.getMessage(), t);
        }
    }

    private List<NameValuePair> buildParameterList(final AccessTokenConfiguration tokenConfig) {
        Map<String, String> parameterList = buildParameterMap(tokenConfig, userCredentials);
        List<NameValuePair> nameValuePairs = new ArrayList<>();
        for (Map.Entry<String, String> entry : parameterList.entrySet()) {
            nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        return nameValuePairs;
    }

    @Override
    public void close() throws IOException {
        client.close();
    }
}

@@ -2224,16 +2224,37 @@
 onfig;%0A%0A
+    //@formatter:off%0A
     publ
@@ -2348,16 +2348,16 @@
 ntials,%0A
-
         
@@ -2397,32 +2397,33 @@
 g httpConfig) %7B%0A
+%0A
         this.use
@@ -2496,24 +2496,25 @@
 ssTokenUri;%0A
+%0A
         requ
@@ -2543,24 +2543,62 @@
 fig.custom()
+%0A                                     
 .setSocketTi
@@ -2642,32 +2642,53 @@
                 
+              
+       
 .setConnectTimeo
@@ -2730,32 +2730,53 @@
                 
+          
+           
 .setConnectionRe
@@ -2842,24 +2842,45 @@
             
+       
+              
 .setStaleCon
@@ -2942,16 +2942,54 @@
 abled())
+%0A                                     
 .build()
@@ -3377,16 +3377,45 @@
 custom()
+%0A                            
 .setUser
@@ -3437,16 +3437,45 @@
 T.get())
+%0A                            
 .useSyst
@@ -3489,16 +3489,28 @@
 rties()%0A
+            
         
@@ -3568,16 +3568,45 @@
 rovider)
+%0A                            
 .build()
@@ -4016,21 +4016,41 @@
 Cache);%0A
-
     %7D
+%0A    //@formatter:on
 %0A%0A    @O

format code