com.mondora.chargify.controller.HttpsXmlChargify.java Source code

Java tutorial

Introduction

Here is the source code for com.mondora.chargify.controller.HttpsXmlChargify.java

Source

/*
 * Copyright (c) 2012. Jonathan S. Fisher
 * and individual contributors as indicated by the @authors tag.
 * See the copyright.txt in the distribution for a full listing of individual contributors.
 * 
 * This is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this
 * software; if not see the FSF site: http://www.fsf.org.
 */
package com.mondora.chargify.controller;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mondora.chargify.Chargify;
import com.mondora.chargify.domain.Components;
import com.mondora.chargify.domain.CreateSubscription;
import com.mondora.chargify.domain.Customer;
import com.mondora.chargify.domain.Customers;
import com.mondora.chargify.domain.Errors;
import com.mondora.chargify.domain.Product;
import com.mondora.chargify.domain.Products;
import com.mondora.chargify.domain.Subscription;
import com.mondora.chargify.domain.Subscriptions;
import com.mondora.chargify.exception.AuthenticationFailedException;
import com.mondora.chargify.exception.ChargifyException;
import com.mondora.chargify.exception.DisabledEndpointException;
import com.mondora.chargify.exception.DuplicateEntityException;
import com.mondora.chargify.exception.InternalServerException;
import com.mondora.chargify.exception.InvalidRequestException;
import com.mondora.chargify.exception.NotFoundException;

/**
 * Https + xml implementation. Liberal use of Apache commons involved.<br />
 * <p>
 * If you're having runtime issues, there is a lot of debug statements in the code. You can enable them by providing
 * SLF4J an implementation at runtime. I recommend Logback, but if your app already has a implementation (like log4j or
 * commons-logging), then simply include the correct SLF4J->[Your logging framework] bridge and this class will
 * magically log statements through your favorite logging framework. Find out the details on this at www.slf4j.org
 * </p>
 * <ul>
 * <li>Debug: prints invocations and object info</li>
 * <li>Trace: prints raw XML requests back and forth between Chargify and you</li>
 * </ul>
 * Chargify is a registered trademark. Don't infringe on their stuff.<br/>
 * <p>
 * By using anything produced from this project, you agree not to litigate against anyone and everyone, for anything and
 * everything, related in any way to using such artifacts. In plain simple English: In additional to the normal LGPL
 * license, you must play nice, you are forbidden to be a troll.
 * </p>
 * 
 * @author jfisher
 */
public class HttpsXmlChargify implements Chargify {

    private static final long serialVersionUID = 1L;
    private static final String UNKOWN_ERROR_CHARGIFY = "Unkown Error; Chargify didn't give any details... sorry!";
    private static final String HTTPS = "https://";
    private static final String APP_XML = "application/xml";
    private static final Logger log = LoggerFactory.getLogger(HttpsXmlChargify.class);

    private final DefaultHttpClient httpClient;
    private final JAXBContext context;
    private final Unmarshaller unmarshaller;
    private final Marshaller marshaller;
    private final URLCodec urlCodec = new URLCodec();
    private final String base;

    private final HttpGet productsHttpGet;
    private final HttpGet subscriptionsHttpGet;
    private final HttpGet customersHttpGet;

    /**
     * Creates a new HttpsXmlChargify instance.
     * 
     * @param apiKey
     *            Your API key from Chargify.
     * @param fqChargifySubdomain
     *            Your fully qualified Chargify subdomain. Example: bubbles.chargify.com
     */
    public HttpsXmlChargify(String apiKey, String fqChargifySubdomain) {
        httpClient = new DefaultHttpClient();
        httpClient.getCredentialsProvider().setCredentials(new AuthScope(fqChargifySubdomain, 443),
                new UsernamePasswordCredentials(apiKey, "x"));

        base = HTTPS + fqChargifySubdomain;
        productsHttpGet = new HttpGet(base + "/products.xml");
        subscriptionsHttpGet = new HttpGet(base + "/subscriptions.xml");
        customersHttpGet = new HttpGet(base + "/customers.xml");

        try {
            context = JAXBContext.newInstance(Components.class, Products.class, Subscriptions.class, Errors.class,
                    CreateSubscription.class, Customers.class);
            unmarshaller = context.createUnmarshaller();
            marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Product> getProducts() {
        log.debug("getProducts()");
        try {
            HttpResponse response = httpClient.execute(productsHttpGet);
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
            Products products = unmarshal(Products.class, streamSource);
            log.debug("getProducts() returning:{}", products);
            return products.getProducts();
        } catch (IOException e) {
            log.error("getProducts() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("getProducts() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Subscription> getSubscriptions() {
        log.debug("getSubscriptions()");
        try {
            HttpResponse response = httpClient.execute(subscriptionsHttpGet);
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
            Subscriptions subscriptions = unmarshal(Subscriptions.class, streamSource);
            log.debug("getSubscriptions() returning:{}", subscriptions);
            return subscriptions.getSubscriptions();
        } catch (IOException e) {
            log.error("getSubscriptions() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("getSubscriptions() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Subscription getSubscription(int id) {
        log.debug("getSubscription() id:{}", id);
        try {
            HttpResponse response = httpClient.execute(new HttpGet(base + "/subscriptions/" + id + ".xml"));
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
            Subscription subscription = unmarshal(Subscription.class, streamSource);
            log.debug("getSubscription() returning:{}", subscription);
            return subscription;
        } catch (IOException e) {
            log.error("getSubscription() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("getSubscription() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Subscription createSubscription(CreateSubscription createSubscription) {
        log.debug("createSubscription() subscription:{}", createSubscription);
        try {
            HttpPost subscriptionsHttpPost = new HttpPost(base + "/subscriptions.xml");
            subscriptionsHttpPost.setEntity(marshal(createSubscription));
            HttpResponse response = httpClient.execute(subscriptionsHttpPost);
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
            Subscription subscription = unmarshal(Subscription.class, streamSource);
            log.debug("createSubscription() returning:{}", subscription);
            return subscription;
        } catch (IOException e) {
            log.error("createSubscription() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("createSubscription() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cancelSubscription(int id) {
        log.debug("cancelSubscription() id:{}", id);
        try {
            HttpResponse response = httpClient.execute(new HttpDelete(base + "/subscriptions/" + id + ".xml"));
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
        } catch (IOException e) {
            log.error("cancelSubscription() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("cancelSubscription() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createCustomer(Customer customer) {
        log.debug("createCustomer() customer:{}", customer);
        try {
            HttpPost customersHttpPost = new HttpPost(base + "/customers.xml");
            customersHttpPost.setEntity(marshal(customer));
            HttpResponse response = httpClient.execute(customersHttpPost);
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
        } catch (IOException e) {
            log.error("createCustomer() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("createCustomer() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Customer> getCustomers() {
        log.debug("getCustomers()");
        try {
            HttpResponse response = httpClient.execute(customersHttpGet);
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
            Customers customers = unmarshal(Customers.class, streamSource);
            log.debug("getCustomers() returning:{}", customers);
            return customers.getCustomers();
        } catch (IOException e) {
            log.error("getCustomers() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("getCustomers() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Customer getCustomer(String reference) {
        log.debug("getCustomer() reference:{}", reference);
        try {
            HttpResponse response = httpClient
                    .execute(new HttpGet(base + "/customers/lookup.xml?reference=" + encode(reference)));
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
            Customer customer = unmarshal(Customer.class, streamSource);
            log.debug("getCustomer() returning:{}", customer);
            return customer;
        } catch (IOException e) {
            log.error("getCustomer() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("getCustomer() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updateCustomer(Customer customer) {
        log.debug("updateCustomer() customer:{}", customer);
        try {
            HttpPut customersHttpPost = new HttpPut(base + "/customers/" + customer.getId() + ".xml");
            customersHttpPost.setEntity(marshal(customer));
            HttpResponse response = httpClient.execute(customersHttpPost);
            StreamSource streamSource = createStreamSource(response);
            throwExceptionIfNeeded(response.getStatusLine(), streamSource);
        } catch (IOException e) {
            log.error("updateCustomer() caught exception", e);
            throw new RuntimeException(e);
        } catch (IllegalStateException e) {
            log.error("updateCustomer() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    protected String encode(String reference) {
        synchronized (urlCodec) {
            try {
                return urlCodec.encode(reference);
            } catch (EncoderException e) {
                log.error("encode() caught exception", e);
                throw new RuntimeException(e);
            }
        }
    }

    protected StreamSource createStreamSource(HttpResponse response) throws IOException {
        InputStream inputStream = new BufferedInputStream(response.getEntity().getContent());
        byte[] byteArray = IOUtils.toByteArray(inputStream);
        if (log.isTraceEnabled()) {
            log.trace("createStreamSource() raw HttpsXmlChargify response:{}", new String(byteArray));
        }
        return new StreamSource(new ByteArrayInputStream(byteArray));
    }

    protected void throwExceptionIfNeeded(StatusLine statusLine, StreamSource streamSource) {
        log.debug("throwExceptionIfNeeded() statusLine:{}", statusLine);
        if (statusLine.getStatusCode() >= 200 && statusLine.getStatusCode() < 300) {
            log.debug("throwExceptionIfNeeded() returning without throwing exception");
            return;
        } else {
            String errorMsg;
            try {
                Errors errors = unmarshal(Errors.class, streamSource);
                log.debug("throwExceptionIfNeeded() parsed XML from error response:{}", errors);
                if (errors.getError() != null && !errors.getError().isEmpty()) {
                    errorMsg = errors.getError().get(0);
                } else {
                    errorMsg = UNKOWN_ERROR_CHARGIFY;
                }
            } catch (Exception e) {
                log.error("throwExceptionIfNeeded() couldn't parse XML out of error response (this is normal)", e);
                errorMsg = statusLine.toString();
            }

            RuntimeException e;
            switch (statusLine.getStatusCode()) {
            case 401:
                e = new AuthenticationFailedException(errorMsg);
                break;
            case 403:
                e = new DisabledEndpointException(errorMsg);
                break;
            case 404:
                e = new NotFoundException(errorMsg);
                break;
            case 500:
                e = new InternalServerException(errorMsg);
                break;
            case 422: {
                if (errorMsg.contains("must be unique - that value has been taken")) {
                    e = new DuplicateEntityException(errorMsg);
                } else {
                    e = new InvalidRequestException(errorMsg);
                }
                break;
            }
            default:
                e = new ChargifyException(errorMsg);
                break;
            }
            log.debug("throwExceptionIfNeeded() throwing exception", e);
            throw e;
        }
    }

    protected <T> T unmarshal(Class<T> clazz, StreamSource streamSource) {
        try {
            synchronized (unmarshaller) {
                T obj = unmarshaller.unmarshal(streamSource, clazz).getValue();
                log.debug("unmarshal() returning obj:{}", obj);
                return obj;
            }
        } catch (JAXBException e) {
            log.error("unmarshal() caught exception", e);
            throw new RuntimeException(e);
        }
    }

    protected HttpEntity marshal(Object object) {
        log.debug("marshal() object:{}", object);
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            synchronized (marshaller) {
                marshaller.marshal(object, baos);
            }
            baos.flush();
            ByteArrayEntity entity = new ByteArrayEntity(baos.toByteArray());
            entity.setContentType(APP_XML);
            if (log.isTraceEnabled()) {
                log.trace("marshal() raw HttpsXmlChargify request:{}", new String(baos.toByteArray()));
            }
            log.debug("marshal() returning entity:{}", entity);
            return entity;
        } catch (JAXBException e) {
            log.error("marshal() caught exception", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("marshal() caught exception", e);
            throw new RuntimeException(e);
        }
    }
}