All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ning.billing.client.KillBillHttpClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010-2013 Ning, Inc.
 *
 * Ning licenses this file to you 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 com.ning.billing.client;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ning.billing.client.model.BillingException;
import com.ning.billing.client.model.KillBillObjects;
import com.ning.billing.jaxrs.resources.JaxrsResource;
import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.ListenableFuture;
import com.ning.http.client.Realm;
import com.ning.http.client.Realm.RealmBuilder;
import com.ning.http.client.Response;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.google.common.collect.ImmutableMap;

public class KillBillHttpClient {

    public static final int DEFAULT_HTTP_TIMEOUT_SEC = 10;

    public static final Map DEFAULT_EMPTY_QUERY = ImmutableMap.of();
    public static final String AUDIT_OPTION_CREATED_BY = "__AUDIT_OPTION_CREATED_BY";
    public static final String AUDIT_OPTION_REASON = "__AUDIT_OPTION_REASON";
    public static final String AUDIT_OPTION_COMMENT = "__AUDIT_OPTION_COMMENT";
    public static final String TENANT_OPTION_API_KEY = "__TENANT_OPTION_API_KEY";
    public static final String TENANT_OPTION_API_SECRET = "__TENANT_OPTION_API_SECRET";
    public static final String RBAC_OPTION_USERNAME = "__RBAC_OPTION_USERNAME";
    public static final String RBAC_OPTION_PASSWORD = "__RBAC_OPTION_PASSWORD";

    private static final Logger log = LoggerFactory.getLogger(KillBillHttpClient.class);
    private static final String USER_AGENT = "KillBill-JavaClient/1.0";

    private final boolean DEBUG = Boolean.parseBoolean(System.getProperty("killbill.debug", "false"));

    private final String kbServerUrl;
    private final String username;
    private final String password;
    private final String apiKey;
    private final String apiSecret;
    private final AsyncHttpClient httpClient;
    private final ObjectMapper mapper;

    public KillBillHttpClient(final String kbServerUrl, final String username, final String password, final String apiKey, final String apiSecret) {
        this.kbServerUrl = kbServerUrl;
        this.username = username;
        this.password = password;
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;

        final AsyncHttpClientConfig.Builder cfg = new AsyncHttpClientConfig.Builder();
        cfg.setUserAgent(USER_AGENT);
        this.httpClient = new AsyncHttpClient(cfg.build());

        mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());
    }

    public KillBillHttpClient() {
        this(System.getProperty("killbill.url", "http://127.0.0.1:8080/"),
             System.getProperty("killbill.username", "admin"),
             System.getProperty("killbill.password", "password"),
             System.getProperty("killbill.apiKey", "bob"),
             System.getProperty("killbill.apiSecret", "lazar"));
    }

    public void close() {
        httpClient.close();
    }

    // POST

    public Response doPost(final String uri, final Object body, final Map options) throws KillBillClientException {
        return doPost(uri, body, options, Response.class);
    }

    public  T doPost(final String uri, final Object body, final Map options, final Class clazz) throws KillBillClientException {
        return doPost(uri, body, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doPost(final String uri, final Object body, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doPostAndMaybeFollowLocation(uri, body, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz, false);
    }

    public  T doPostAndFollowLocation(final String uri, final Object body, final Map options, final Class clazz) throws KillBillClientException {
        return doPostAndFollowLocation(uri, body, options, DEFAULT_EMPTY_QUERY, clazz);
    }

    public  T doPostAndFollowLocation(final String uri, final Object body, final Map options, final Map optionsForFollow, final Class clazz) throws KillBillClientException {
        return doPostAndFollowLocation(uri, body, options, optionsForFollow, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doPostAndFollowLocation(final String uri, final Object body, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doPostAndFollowLocation(uri, body, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz);
    }

    public  T doPostAndFollowLocation(final String uri, final Object body, final Map options, final Map optionsForFollow, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doPostAndMaybeFollowLocation(uri, body, options, optionsForFollow, timeoutSec, clazz, true);
    }

    public  T doPostAndMaybeFollowLocation(final String uri, final Object body, final Map options, final Map optionsForFollow, final int timeoutSec, final Class clazz, final boolean followLocation) throws KillBillClientException {
        final String verb = "POST";
        return doPrepareRequestAndMaybeFollowLocation(verb, uri, body, options, optionsForFollow, timeoutSec, clazz, followLocation);
    }

    // PUT

    public Response doPut(final String uri, final Object body, final Map options) throws KillBillClientException {
        return doPut(uri, body, options, Response.class);
    }

    public  T doPut(final String uri, final Object body, final Map options, final Class clazz) throws KillBillClientException {
        return doPut(uri, body, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doPut(final String uri, final Object body, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doPutAndMaybeFollowLocation(uri, body, options, timeoutSec, clazz, false);
    }

    public  T doPutAndFollowLocation(final String uri, final Object body, final Map options, final Class clazz) throws KillBillClientException {
        return doPutAndFollowLocation(uri, body, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doPutAndFollowLocation(final String uri, final Object body, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doPutAndMaybeFollowLocation(uri, body, options, timeoutSec, clazz, true);
    }

    public  T doPutAndMaybeFollowLocation(final String uri, final Object body, final Map options, final int timeoutSec, final Class clazz, final boolean followLocation) throws KillBillClientException {
        final String verb = "PUT";
        return doPrepareRequestAndMaybeFollowLocation(verb, uri, body, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz, followLocation);
    }

    // DELETE

    public Response doDelete(final String uri, final Map options) throws KillBillClientException {
        return doDelete(uri, options, Response.class);
    }

    public  T doDelete(final String uri, final Map options, final Class clazz) throws KillBillClientException {
        return doDeleteAndMaybeFollowLocation(uri, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz, false);
    }

    public  T doDeleteAndFollowLocation(final String uri, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doDeleteAndMaybeFollowLocation(uri, options, timeoutSec, clazz, true);
    }

    public  T doDeleteAndMaybeFollowLocation(final String uri, final Map options, final int timeoutSec, final Class clazz, final boolean followLocation) throws KillBillClientException {
        final String verb = "DELETE";
        return doPrepareRequestAndMaybeFollowLocation(verb, uri, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz, followLocation);
    }

    // GET

    public Response doGet(final String uri, final Map options) throws KillBillClientException {
        return doGet(uri, options, Response.class);
    }

    public  T doGet(final String uri, final Map options, final Class clazz) throws KillBillClientException {
        return doGetWithUrl(uri, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doGet(final String uri, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doGetWithUrl(uri, options, timeoutSec, clazz);
    }

    public  T doGetWithUrl(final String url, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        final String verb = "GET";
        return doPrepareRequestAndMaybeFollowLocation(verb, url, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz);
    }

    // HEAD

    public Response doHead(final String uri, final Map options) throws KillBillClientException {
        return doHead(uri, options, Response.class);
    }

    public  T doHead(final String uri, final Map options, final Class clazz) throws KillBillClientException {
        return doHeadWithUrl(uri, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doHead(final String uri, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doHeadWithUrl(uri, options, timeoutSec, clazz);
    }

    public  T doHeadWithUrl(final String url, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        final String verb = "HEAD";
        return doPrepareRequestAndMaybeFollowLocation(verb, url, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz);
    }

    // OPTIONS

    public Response doOptions(final String uri, final Map options) throws KillBillClientException {
        return doOptions(uri, options, Response.class);
    }

    public  T doOptions(final String uri, final Map options, final Class clazz) throws KillBillClientException {
        return doOptionsWithUrl(uri, options, DEFAULT_HTTP_TIMEOUT_SEC, clazz);
    }

    public  T doOptions(final String uri, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doOptionsWithUrl(uri, options, timeoutSec, clazz);
    }

    public  T doOptionsWithUrl(final String url, final Map options, final int timeoutSec, final Class clazz) throws KillBillClientException {
        final String verb = "OPTIONS";
        return doPrepareRequestAndMaybeFollowLocation(verb, url, options, DEFAULT_EMPTY_QUERY, timeoutSec, clazz);
    }

    // COMMON

    private  T doPrepareRequestAndMaybeFollowLocation(final String verb, final String uri, final Map options, final Map optionsForFollow, final int timeoutSec, final Class clazz) throws KillBillClientException {
        return doPrepareRequestAndMaybeFollowLocation(verb, uri, null, options, optionsForFollow, timeoutSec, clazz, false);
    }

    private  T doPrepareRequestAndMaybeFollowLocation(final String verb, final String uri, final Map options, final Map optionsForFollow, final int timeoutSec, final Class clazz, final boolean followLocation) throws KillBillClientException {
        return doPrepareRequestAndMaybeFollowLocation(verb, uri, null, options, optionsForFollow, timeoutSec, clazz, followLocation);
    }

    private  T doPrepareRequestAndMaybeFollowLocation(final String verb, final String uri, final Object body, final Map optionsRo, final Map optionsForFollow, final int timeoutSec, final Class clazz, final boolean followLocation) throws KillBillClientException {
        final Map options = new HashMap(optionsRo);

        final String createdBy = options.get(AUDIT_OPTION_CREATED_BY);
        final String reason = options.get(AUDIT_OPTION_REASON);
        final String comment = options.get(AUDIT_OPTION_COMMENT);
        String apiKey = options.get(TENANT_OPTION_API_KEY);
        if (apiKey == null) {
            apiKey = this.apiKey;
        }
        String apiSecret = options.get(TENANT_OPTION_API_SECRET);
        if (apiSecret == null) {
            apiSecret = this.apiSecret;
        }
        String username = options.get(RBAC_OPTION_USERNAME);
        if (username == null) {
            username = this.username;
        }
        String password = options.get(RBAC_OPTION_PASSWORD);
        if (password == null) {
            password = this.password;
        }

        options.remove(AUDIT_OPTION_CREATED_BY);
        options.remove(AUDIT_OPTION_REASON);
        options.remove(AUDIT_OPTION_COMMENT);
        options.remove(TENANT_OPTION_API_KEY);
        options.remove(TENANT_OPTION_API_SECRET);
        options.remove(RBAC_OPTION_USERNAME);
        options.remove(RBAC_OPTION_PASSWORD);

        final BoundRequestBuilder builder = getBuilderWithHeaderAndQuery(verb, getKBServerUrl(uri), username, password, options);

        // Multi-Tenancy headers
        if (apiKey != null) {
            builder.addHeader(JaxrsResource.HDR_API_KEY, apiKey);
        }
        if (apiSecret != null) {
            builder.addHeader(JaxrsResource.HDR_API_SECRET, apiSecret);
        }
        // Metadata Additional headers
        if (createdBy != null) {
            builder.addHeader(JaxrsResource.HDR_CREATED_BY, createdBy);
        }
        if (reason != null) {
            builder.addHeader(JaxrsResource.HDR_REASON, reason);
        }
        if (comment != null) {
            builder.addHeader(JaxrsResource.HDR_COMMENT, comment);
        }

        if (!"GET".equals(verb) && !"HEAD".equals(verb)) {
            if (body != null) {
                try {
                    builder.setBody(mapper.writeValueAsString(body));
                } catch (JsonProcessingException e) {
                    throw new KillBillClientException(e);
                }
            } else {
                builder.setBody("{}");
            }
        }

        if (followLocation) {
            final Response response = executeAndWait(builder, timeoutSec, Response.class);
            if (response == null || response.getHeader("Location") == null) {
                // 404, bad request, ...
                return Response.class.isAssignableFrom(clazz) ? (T) response : null;
            } else {
                final String location = response.getHeader("Location");
                return doGetWithUrl(location, optionsForFollow, timeoutSec, clazz);
            }
        } else {
            return executeAndWait(builder, timeoutSec, clazz);
        }
    }

    private  T executeAndWait(final BoundRequestBuilder builder, final int timeoutSec, final Class clazz) throws KillBillClientException {
        Response response;
        final ListenableFuture futureStatus;
        try {
            futureStatus = builder.execute(new AsyncCompletionHandler() {
                @Override
                public Response onCompleted(final Response response) throws Exception {
                    return response;
                }
            });
            response = futureStatus.get(timeoutSec, TimeUnit.SECONDS);
        } catch (final IOException e) {
            throw new KillBillClientException(e);
        } catch (final InterruptedException e) {
            throw new KillBillClientException(e);
        } catch (final ExecutionException e) {
            throw new KillBillClientException(e);
        } catch (final TimeoutException e) {
            throw new KillBillClientException(e);
        }

        if (response != null && response.getStatusCode() == 401) {
            throw new KillBillClientException(new IllegalArgumentException("Unauthorized - did you configure your RBAC and/or tenant credentials?"),
                                              response);
        } else if (response != null && (response.getStatusCode() == 404 || response.getStatusCode() == 204)) {
            // Return empty list for KillBillObjects instead of null for convenience
            if (Iterable.class.isAssignableFrom(clazz)) {
                for (final Constructor constructor : clazz.getConstructors()) {
                    if (constructor.getParameterTypes().length == 0) {
                        try {
                            return (T) constructor.newInstance();
                        } catch (InstantiationException e) {
                            return null;
                        } catch (IllegalAccessException e) {
                            return null;
                        } catch (InvocationTargetException e) {
                            return null;
                        }
                    }
                }
                return null;
            } else {
                return null;
            }
        } else if (response != null && response.getStatusCode() >= 400) {
            final BillingException exception = deserializeResponse(response, BillingException.class);
            log.warn("Error " + response.getStatusCode() + " from Kill Bill: " + exception.getMessage());
            throw new KillBillClientException(exception, response);
        }

        // No deserialization required?
        if (Response.class.isAssignableFrom(clazz)) {
            return (T) response;
        }

        return deserializeResponse(response, clazz);
    }

    private  T deserializeResponse(final Response response, final Class clazz) throws KillBillClientException {
        final T result;
        try {
            if (DEBUG) {
                final String content = response.getResponseBody();
                log.debug("Received: " + content);
                result = mapper.readValue(content, clazz);
            } else {
                InputStream in = null;
                try {
                    in = response.getResponseBodyAsStream();
                    result = mapper.readValue(in, clazz);
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            log.warn("Failed to close http-client - provided InputStream: {}", e.getLocalizedMessage());
                        }
                    }
                }
            }
        } catch (final IOException e) {
            throw new KillBillClientException(e, response);
        }

        if (KillBillObjects.class.isAssignableFrom(clazz)) {
            final KillBillObjects objects = ((KillBillObjects) result);
            final String paginationCurrentOffset = response.getHeader(JaxrsResource.HDR_PAGINATION_CURRENT_OFFSET);
            if (paginationCurrentOffset != null) {
                objects.setPaginationCurrentOffset(Integer.parseInt(paginationCurrentOffset));
            }
            final String paginationNextOffset = response.getHeader(JaxrsResource.HDR_PAGINATION_NEXT_OFFSET);
            if (paginationNextOffset != null) {
                objects.setPaginationNextOffset(Integer.parseInt(paginationNextOffset));
            }
            final String paginationTotalNbRecords = response.getHeader(JaxrsResource.HDR_PAGINATION_TOTAL_NB_RECORDS);
            if (paginationTotalNbRecords != null) {
                objects.setPaginationTotalNbRecords(Integer.parseInt(paginationTotalNbRecords));
            }
            final String paginationMaxNbRecords = response.getHeader(JaxrsResource.HDR_PAGINATION_MAX_NB_RECORDS);
            if (paginationMaxNbRecords != null) {
                objects.setPaginationMaxNbRecords(Integer.parseInt(paginationMaxNbRecords));
            }
            objects.setPaginationNextPageUri(response.getHeader(JaxrsResource.HDR_PAGINATION_NEXT_PAGE_URI));
            objects.setKillBillHttpClient(this);
        }

        return result;
    }

    private BoundRequestBuilder getBuilderWithHeaderAndQuery(final String verb, final String url, @Nullable final String username, @Nullable final String password, final Map options) {
        BoundRequestBuilder builder;

        if (verb.equals("GET")) {
            builder = httpClient.prepareGet(url);
        } else if (verb.equals("POST")) {
            builder = httpClient.preparePost(url);
        } else if (verb.equals("PUT")) {
            builder = httpClient.preparePut(url);
        } else if (verb.equals("DELETE")) {
            builder = httpClient.prepareDelete(url);
        } else if (verb.equals("HEAD")) {
            builder = httpClient.prepareHead(url);
        } else if (verb.equals("OPTIONS")) {
            builder = httpClient.prepareOptions(url);
        } else {
            throw new IllegalArgumentException("Unrecognized verb: " + verb);
        }

        if (username != null && password != null) {
            final Realm realm = new RealmBuilder().setPrincipal(username).setPassword(password).build();
            builder.setRealm(realm);
        }

        builder.addHeader("Content-Type", "application/json; charset=utf-8");

        for (final String key : options.keySet()) {
            if (options.get(key) != null) {
                builder.addQueryParameter(key, options.get(key));
            }
        }

        return builder;
    }

    private String getKBServerUrl(final String uri) throws KillBillClientException {
        try {
            final URI u = new URI(uri);
            if (u.isAbsolute()) {
                return uri;
            } else {
                return String.format("%s%s", kbServerUrl, uri);
            }
        } catch (URISyntaxException e) {
            throw new KillBillClientException(e);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy