Java tutorial
/* * 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); } } }