com.rossjourdain.util.xero.XeroClient.java Source code

Java tutorial

Introduction

Here is the source code for com.rossjourdain.util.xero.XeroClient.java

Source

/*
 *  Copyright 2011 Ross Jourdain
 *
 *  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 com.rossjourdain.util.xero;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthException;
import net.oauth.OAuthMessage;
import net.oauth.OAuthProblemException;
import net.oauth.OAuthServiceProvider;
import net.oauth.ParameterStyle;
import net.oauth.client.OAuthClient;
import net.oauth.client.OAuthResponseMessage;
import net.oauth.client.httpclient4.HttpClient4;
import net.oauth.client.httpclient4.HttpClientPool;
import net.oauth.http.HttpResponseMessage;
import net.oauth.signature.RSA_SHA1;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

/**
 * Construct this class for the private API.
 *
 * For Public and Partner API use {@link PublicXeroClient}
 * @author ross
 */
public class XeroClient {

    private String consumerKey;
    private String consumerSecret;
    private String privateKey; // NOTE This is only for private apps

    private static final HttpClientPool SHARED_CLIENT = new SingleClient();

    /**
     * For use by subclasses.  i.e. Public and Partner apps
     */
    protected XeroClient(String consumerKey, String consumerSecret) {
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
    }

    public XeroClient(String consumerKey, String consumerSecret, String privateKey) {
        this(consumerKey, consumerSecret);
        this.privateKey = privateKey;
    }

    public XeroClient(XeroClientProperties clientProperties) {
        this.consumerKey = clientProperties.getConsumerKey();
        this.consumerSecret = clientProperties.getConsumerSecret();
        this.privateKey = clientProperties.getPrivateKey();
    }

    public String getEndpointUrl() {
        return "https://api.xero.com/api.xro/2.0/";
    }

    public String getRequestTokenUrl() {
        return "https://api.xero.com/oauth/RequestToken";
    }

    public String getAuthoriseUrl() {
        return "https://api.xero.com/oauth/Authorize";
    }

    public String getAccessTokenUrl() {
        return "https://api.xero.com/oauth/AccessToken";
    }

    protected OAuthAccessor buildAccessor() {
        return buildAccessor(null);
    }

    protected OAuthAccessor buildAccessor(String callbackUrl) {
        OAuthConsumer consumer = new OAuthConsumer(callbackUrl, consumerKey, null, getServiceProvider());
        consumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey);
        consumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1);

        OAuthAccessor accessor = new OAuthAccessor(consumer);
        accessor.accessToken = consumerKey;
        accessor.tokenSecret = consumerSecret;
        return accessor;
    }

    protected OAuthServiceProvider getServiceProvider() {
        return new OAuthServiceProvider(getRequestTokenUrl(), getAuthoriseUrl(), getAccessTokenUrl());
    }

    public RequestToken getRequestToken(String callbackUrl) throws OAuthException, IOException, URISyntaxException {
        throw new UnsupportedOperationException("This only makes sense for public or partner apps.");
    }

    public AccessToken getAccessToken(RequestToken requestToken)
            throws OAuthException, IOException, URISyntaxException {
        throw new UnsupportedOperationException("This only makes sense for public or partner apps.");
    }

    public AccessToken refreshAccessToken(AccessToken currentAccessToken)
            throws OAuthException, IOException, URISyntaxException {
        throw new UnsupportedOperationException("This only makes sense for partner apps.");
    }

    public ArrayOfInvoice getInvoices() throws XeroClientUnexpectedException, OAuthProblemException {
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET, getEndpointUrl() + "Invoices", null);
            ArrayOfInvoice invoices = XeroXmlManager.fromXml(response.getBodyAsStream()).getInvoices();
            if (invoices == null)
                return new ArrayOfInvoice();
            return invoices;
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    public Invoice getInvoice(String invoiceId) throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfInvoice arrayOfInvoice = getInvoiceWithExtraInfo("/" + invoiceId);
        if (arrayOfInvoice.getInvoice().size() != 1) {
            throw new XeroClientUnexpectedException("Should have had 1 invoice for id[" + invoiceId
                    + "] - Instead have[" + arrayOfInvoice.getInvoice().size() + "]", new IllegalStateException());
        }
        return arrayOfInvoice.getInvoice().get(0);
    }

    /**
     * This should be unique per invoice (excluding ones you might VOID).  Useful as a unique key for recovery after POSTING an invoice but not getting a reply.
     *
     * @see Invoice#setUrl(String)
     *
     * @return A
     */
    public ArrayOfInvoice getInvoiceByUrl(String url) throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfInvoice arrayOfInvoice = getInvoiceWithExtraInfo("?where=Url==\"" + url + "\"");
        if (arrayOfInvoice == null)
            return new ArrayOfInvoice();
        return arrayOfInvoice;
    }

    /**
     * @param extraInfo Extra information added to the url.  Do not url encode this, it's done internally.
     */
    private ArrayOfInvoice getInvoiceWithExtraInfo(String extraInfo)
            throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfInvoice arrayOfInvoice = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();

            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET,
                    getEndpointUrl() + "Invoices" + URIUtil.encodeQuery(extraInfo), null);
            arrayOfInvoice = XeroXmlManager.fromXml(response.getBodyAsStream()).getInvoices();
            return arrayOfInvoice;
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    public Organisation getOrganisation() throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfOrganisation arrayOfContact = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET, getEndpointUrl() + "Organisation",
                    null);
            arrayOfContact = XeroXmlManager.fromXml(response.getBodyAsStream()).getOrganisations();
            if (arrayOfContact.getOrganisation().size() != 1) {
                throw new XeroClientUnexpectedException("Should have had 1 orgainisation Instead have["
                        + arrayOfContact.getOrganisation().size() + "]", new IllegalStateException());
            }
            return arrayOfContact.getOrganisation().get(0);
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    /**
     * Currently returns ALL transactions for the specified bank code, in date order (may require multiple internal API hits, but gets put together for return)
     */
    public ArrayOfBankTransaction getBankTransactions(String bankCode)
            throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfBankTransaction arrayOfBankTransaction = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            Collection<OAuth.Parameter> parameters = new ArrayList<>();
            parameters.add(new OAuth.Parameter("where", "BankAccount.Code==\"" + bankCode + "\""));
            OAuthMessage response;
            ArrayOfBankTransaction bankTransactions;
            int pageNo = 0;
            do {
                pageNo++;
                response = client.invoke(accessor, OAuthMessage.GET,
                        getEndpointUrl() + "BankTransactions?order=Date&page=" + pageNo, parameters);
                bankTransactions = XeroXmlManager.fromXml(response.getBodyAsStream()).getBankTransactions();
                if (bankTransactions == null || bankTransactions.getBankTransaction() == null
                        || bankTransactions.getBankTransaction().isEmpty())
                    break;
                if (arrayOfBankTransaction == null) {
                    arrayOfBankTransaction = bankTransactions;
                } else {
                    arrayOfBankTransaction.getBankTransaction().addAll(bankTransactions.getBankTransaction());
                }
            } while (true);
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
        return arrayOfBankTransaction;
    }

    public ArrayOfItem getItems() throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfItem arrayOfItem = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET, getEndpointUrl() + "Items", null);
            arrayOfItem = XeroXmlManager.fromXml(response.getBodyAsStream()).getItems();
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
        return arrayOfItem;
    }

    public Contact getContact(String contactId) throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfContact arrayOfContact = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET,
                    getEndpointUrl() + "Contacts" + "/" + contactId, null);
            arrayOfContact = XeroXmlManager.fromXml(response.getBodyAsStream()).getContacts();
            if (arrayOfContact.getContact().size() != 1) {
                throw new XeroClientUnexpectedException("Should have had 1 client for id[" + contactId
                        + "] - Instead have[" + arrayOfContact.getContact().size() + "]",
                        new IllegalStateException());
            }
            return arrayOfContact.getContact().get(0);
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    public ArrayOfContact getContacts() throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfContact arrayOfContact = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET, getEndpointUrl() + "Contacts", null);
            arrayOfContact = XeroXmlManager.fromXml(response.getBodyAsStream()).getContacts();
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
        if (arrayOfContact == null)
            return new ArrayOfContact();
        return arrayOfContact;
    }

    public ArrayOfAccount getAccounts() throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfAccount arrayOfAccount = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET, getEndpointUrl() + "Accounts", null);
            arrayOfAccount = XeroXmlManager.fromXml(response.getBodyAsStream()).getAccounts();
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
        if (arrayOfAccount == null)
            return new ArrayOfAccount();
        return arrayOfAccount;
    }

    public ArrayOfTrackingCategory getTrackingCategories()
            throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfTrackingCategory arrayOfTrackingCategory = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET,
                    getEndpointUrl() + "TrackingCategories", null);
            arrayOfTrackingCategory = XeroXmlManager.fromXml(response.getBodyAsStream()).getTrackingCategories();
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
        if (arrayOfTrackingCategory == null)
            return new ArrayOfTrackingCategory();
        return arrayOfTrackingCategory;
    }

    public Report getReport(String reportUrl) throws XeroClientUnexpectedException, OAuthProblemException {
        Report report = null;
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.GET,
                    getEndpointUrl() + "Reports" + reportUrl, null);
            ResponseType responseType = XeroXmlManager.xmlToResponse(response.getBodyAsStream());
            if (responseType != null && responseType.getReports() != null
                    && responseType.getReports().getReport() != null
                    && responseType.getReports().getReport().size() > 0) {
                report = responseType.getReports().getReport().get(0);
            }
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
        return report;
    }

    public void postContacts(ArrayOfContact arrayOfContact)
            throws XeroClientUnexpectedException, OAuthProblemException {
        try {
            String contactsString = XeroXmlManager.contactsToXml(arrayOfContact);
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            OAuthMessage response = client.invoke(accessor, OAuthMessage.POST, getEndpointUrl() + "Contacts",
                    OAuth.newList("xml", contactsString));
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    public Invoice postInvoice(Invoice invoice) throws XeroClientUnexpectedException, OAuthProblemException {
        ArrayOfInvoice arrayOfInvoice = new ArrayOfInvoice();
        arrayOfInvoice.getInvoice().add(invoice);
        ArrayOfInvoice returnedInvoices = postInvoices(arrayOfInvoice);
        return returnedInvoices.getInvoice().get(0);
    }

    public ArrayOfInvoice postInvoices(ArrayOfInvoice arrayOfInvoices)
            throws XeroClientUnexpectedException, OAuthProblemException {
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            String invoicesXml = XeroXmlManager.invoicesToXml(arrayOfInvoices);
            OAuthMessage response = client.invoke(accessor, OAuthMessage.POST, getEndpointUrl() + "Invoices",
                    OAuth.newList("xml", invoicesXml));
            return XeroXmlManager.fromXml(response.getBodyAsStream()).getInvoices();
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    public void postPayments(ArrayOfPayment arrayOfPayment)
            throws XeroClientUnexpectedException, OAuthProblemException {
        try {
            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();
            String paymentsString = XeroXmlManager.paymentsToXml(arrayOfPayment);
            OAuthMessage response = client.invoke(accessor, OAuthMessage.POST, getEndpointUrl() + "Payments",
                    OAuth.newList("xml", paymentsString));
        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        }
    }

    public File getInvoiceAsPdfFile(String invoiceId) throws XeroClientUnexpectedException, OAuthProblemException {
        File file = null;
        FileOutputStream out = null;
        try {
            file = new File("Invoice-" + invoiceId + ".pdf");
            out = new FileOutputStream(file);
            out.write(getInvoiceAsPdfByteArray(invoiceId));
        } catch (IOException e) {
            throw new XeroClientUnexpectedException("", e);
        } finally {
            try {
                if (out != null) {
                    out.flush();
                    out.close();
                }
            } catch (IOException ex) {
            }
        }
        return file;
    }

    public byte[] getInvoiceAsPdfByteArray(String invoiceId)
            throws XeroClientUnexpectedException, OAuthProblemException {

        File file = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;

        try {

            OAuthClient client = getOAuthClient();
            OAuthAccessor accessor = buildAccessor();

            OAuthMessage request = accessor.newRequestMessage(OAuthMessage.GET,
                    getEndpointUrl() + "Invoices" + "/" + invoiceId, null);
            request.getHeaders().add(new OAuth.Parameter("Accept", "application/pdf"));
            OAuthResponseMessage response = client.access(request, ParameterStyle.BODY);

            if (response != null && response.getHttpResponse() != null
                    && (response.getHttpResponse().getStatusCode() == HttpResponseMessage.STATUS_OK)) {
                in = response.getBodyAsStream();
                out = new ByteArrayOutputStream();

                byte[] buffer = new byte[1024];
                int bytesRead = 0;
                while ((bytesRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
            } else {
                throw response.toOAuthProblemException();
            }

        } catch (OAuthProblemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new XeroClientUnexpectedException("", ex);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
            }
            try {
                if (out != null) {
                    out.flush();
                    out.close();
                }
            } catch (IOException ex) {
            }
        }
        return out.toByteArray();
    }

    public void setConsumerKey(String consumerKey) {
        this.consumerKey = consumerKey;
    }

    public void setConsumerSecret(String consumerSecret) {
        this.consumerSecret = consumerSecret;
    }

    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    protected OAuthClient getOAuthClient() {
        HttpClient4 httpClient4 = new HttpClient4(getClientPool());
        OAuthClient oAuthClient = new OAuthClient(httpClient4);
        oAuthClient.getHttpParameters().put(net.oauth.http.HttpClient.READ_TIMEOUT, new Integer(5000));
        oAuthClient.getHttpParameters().put(net.oauth.http.HttpClient.CONNECT_TIMEOUT, new Integer(5000));
        return oAuthClient;
    }

    protected HttpClientPool getClientPool() {
        return SHARED_CLIENT;
    }

    // This is based on the HttpClient4 one used by the default constructor, but allowing overridding the con per route.
    // It's also been updated to use
    protected static class SingleClient implements HttpClientPool {

        private final CloseableHttpClient client;

        private final PoolingHttpClientConnectionManager clientConnectionManager;

        // Bit lost with all of this, but this is the only way I've found to override the ConnPerRoute problem I was having.
        private SingleClient() {
            clientConnectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
            clientConnectionManager.setDefaultMaxPerRoute(30);
            client = buildClient();
            //            client = new DefaultHttpClient(clientConnectionManager);

            //                if (!(clientConnectionManager instanceof ThreadSafeClientConnManager)) {
            //                    HttpParams params = client.getParams();
            //                    client = new DefaultHttpClient(
            //                            new ThreadSafeClientConnManager(clientConnectionManager.getSchemeRegistry(),
            //                                    5000,TimeUnit.MILLISECONDS,
            //                                    new ConnPerRouteBean(20)
            //                            ), params);
            //                }
        }

        public PoolingHttpClientConnectionManager getClientConnectionManager() {
            return clientConnectionManager;
        }

        protected CloseableHttpClient buildClient() {
            return HttpClientBuilder.create().setConnectionManager(getClientConnectionManager()).build();
        }

        public void closeExpiredConnections() {
            clientConnectionManager.closeExpiredConnections();
        }

        @Override
        public HttpClient getHttpClient(URL server) {
            // May as well clean up expired connections.  Make sure to be logging org.apache.http on debug to see them getting cleaned up
            // Turned this off, looks like they are meant to stay persistent for performance reasons.
            //                clientConnectionManager.closeExpiredConnections();
            return client;
        }
    }
}