fm.audiobox.core.AudioBoxClient.java Source code

Java tutorial

Introduction

Here is the source code for fm.audiobox.core.AudioBoxClient.java

Source

/*
 * Copyright 2009-2014 iCoreTech, Inc.
 *
 * 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 fm.audiobox.core;

import com.google.api.client.auth.oauth2.*;
import com.google.api.client.http.*;
import com.google.api.client.json.JsonObjectParser;
import fm.audiobox.core.config.Configuration;
import fm.audiobox.core.config.ConfigurationException;
import fm.audiobox.core.exceptions.*;
import fm.audiobox.core.models.*;
import fm.audiobox.core.net.NetworkProgressListener;
import fm.audiobox.core.net.Upload;
import fm.audiobox.core.store.CredentialDataStore;
import fm.audiobox.core.utils.HttpStatus;
import fm.audiobox.core.utils.Io;
import fm.audiobox.core.utils.ModelUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * {@link AudioBoxClient} is the main object of this library and allows you to perform requests and
 * operations on AudioBox.
 * <p/>
 * AudioBox let developers access to API in order to build any kind of modern applications,
 * libraries and integrations on top of the platform.
 * <br/>
 * AudioBox exposes basic and special API endpoints aimed at fast iteration of code and easiness.
 * <p/>
 * All the HTTP/HTTPS are performed using the JSON data format and content type.
 * <p/>
 * This library supports the OAuth2 Resource Owner Password Credentials Grant Type, thus, in order to
 * work with it, you first need to register your application here (you need a valid AudioBox account):
 * <p/>
 * <a href="https://audiobox.fm/oauth2/applications">https://audiobox.fm/oauth2/applications</a>
 * <p/>
 * Once you registered your application you have to properly configure the client as follows:
 * <code><pre>
 * Configuration config = new Configuration()
 *   .setApiKey( "[Your Consumer Key]" )
 *   .setApiSecret( "[Your Consumer Secret]" );
 * </pre></code>
 * Through the {@link fm.audiobox.core.config.Configuration} object you can configure many aspects
 * of the library behaviors; some are trivial such as application name, version, etc. and
 * other are more complex such as HttpTransport or JSON parser.
 * <p/>
 * This library does not offer a data store for credentials storage out of the box. You should provide
 * an implementation of the {@link fm.audiobox.core.store.CredentialDataStore}.
 * <br/>
 * This data store should be used to store credentials so you should be really carefully with it.
 * <p/>
 * To set it use the configuration:
 * <code><pre>
 *  config.setCredentialDataStore( new MyCredentialDataStore() );
 * </pre></code>
 * To comply with OAuth standard you also have to provide a {@link com.google.api.client.auth.oauth2.CredentialRefreshListener}
 * in order to keep tokens up to date.
 * <code><pre>
 *   config.setCredentialRefreshListener( new MyCredentialRefreshListener() );
 * </pre></code>
 * Since this library wants to be as much agnostic as possible regarding the HTTP client and
 * the JSON parser libraries you should set them at this moment by choosing amongst:
 * <p/>
 * <ul>
 * <li><strong>NetHttpTransport:</strong> based on HttpURLConnection that is found in all Java SDKs, and thus usually the simplest choice.</li>
 * <li><strong>ApacheHttpTransport:</strong> based on the popular Apache HttpClient that allows for more customization.</li>
 * <li><strong>UrlFetchTransport:</strong> based on URL Fetch Java API in the Google App Engine SDK</li>
 * </ul>
 * <p/>
 * as HTTP transport, and:
 * <p/>
 * <ul>
 * <li><strong>JacksonFactory:</strong> based on the popular Jackson library which is considered the fastest in terms of parsing/serialization speed</li>
 * <li><strong>GsonFactory:</strong> based on the Google GSON library which is a lighter-weight option (small size) that is pretty fast also (though not quite as fast as Jackson)</li>
 * <li><strong>AndroidJsonFactory:</strong> based on the JSON library built-in to Android Honeycomb (SDK 3.0) or higher that is identical to the Google GSON library</li>
 * </ul>
 * <p/>
 * as JSON parser library.
 * <p/>
 * There are no defaults that's why you must provide them through the configuration:
 * <code><pre>
 *  config
 *    .setHttpTransport( new NetHttpTransport() )
 *    .setJsonFactory( new JacksonFactory() );
 * </pre></code>
 * <p/>
 * This is the basic configuration and once the setup is completed you can create your
 * Client, authorize the application and start performing any kind of operation supported
 * by AudioBox API through it:
 * <code><pre>
 *   Client client = new Client( config );
 *   client.authorize( "username", "password" );
 *   List&lt;Playlist&gt; playlists = client.getPlaylists();
 *   ...
 * </pre></code>
 * <p/>
 * <strong>NOTE:</strong> {@link AudioBoxClient#authorize(String, String)} is only needed once
 * to get and store the OAuth2 grant token; password is never (and it never should be) stored.
 * <p/>
 * <strong>NOTE:</strong> grant tokens may expires at any time. A request against AudioBox with
 * an expired token will result in an
 * {@link fm.audiobox.core.exceptions.AuthorizationException AuthorizationException}.
 * Your application should be ready to trap it in order to present a new login form.
 * <p/>
 * <strong>NOTE:</strong> most of the methods of this library performs requests against
 * AudioBox services. In order to avoid too many requests is highly recommended to implement
 * some sort of caching system (memory or persisted).
 * <br/>
 * <br/>
 * For a complete list of API endpoints you can consult the AudioBox API handbook at this address:
 * <p/>
 * <a href="http://audiobox.fm/apidocs">http://audiobox.fm/apidocs</a>
 * <p/>
 */
public class AudioBoxClient {

    private Configuration conf;

    private CredentialDataStore userDb;

    /**
     * The key under which tokens are stored in the DataStore
     */
    public static final String ACCOUNT_TOKENS = "_audiobox_account_tokens";

    private CredentialRefreshListener refreshListener;

    private JsonObjectParser jsonObjectParser;

    private HttpHeaders defaultHeaders;

    private static Logger logger = LoggerFactory.getLogger("[ " + AudioBoxClient.class.getSimpleName() + " ]");

    /**
     * Lock on access to the store.
     */
    private final Lock lock = new ReentrantLock();

    /**
     * Instantiates a new Client.
     *
     * @param conf the conf
     *
     * @throws ConfigurationException the configuration exception
     * @throws java.io.IOException    if any problem occurs with the configured data store factory
     */
    public AudioBoxClient(Configuration conf) throws ConfigurationException, IOException {
        conf.checkConfiguration();
        this.conf = conf;
        this.userDb = conf.getCredentialDataStore();
        this.refreshListener = conf.getRefreshListener();
        this.jsonObjectParser = new JsonObjectParser(getConf().getJsonFactory());
        this.defaultHeaders = new HttpHeaders();
        this.defaultHeaders.setUserAgent(getConf().getUserAgent());
    }

    /**
     * Gets the global client configuration.
     *
     * @return the configuration
     */
    public Configuration getConf() {
        return conf;
    }

    /**
     * Gets global request headers
     *
     * @return the default headers
     */
    public HttpHeaders getDefaultHeaders() {
        return defaultHeaders;
    }

    /**
     * Starts the authorization flow.
     * <p/>
     * Given a username and a password if the request succeed this method will store the
     * grant token for future requests and return the response.
     *
     * @param username the username
     * @param password the password
     *
     * @return the token response, may be null
     *
     * @throws AuthorizationException in case the authorization fails.
     * @throws java.io.IOException    if any connection or configured data store problems occurs.
     */
    public TokenResponse authorize(String username, String password) throws IOException {
        return authorize(username, password, false);
    }

    /**
     * Starts the authorization flow.
     * <p/>
     * Given a username and a password if the request succeed this method will store the
     * grant token for future requests and return the response.
     *
     * @param username           the username
     * @param password           the password
     * @param relaunchExceptions whether or not relaunch possible exceptions.
     *
     * @return the token response, may be null
     *
     * @throws AuthorizationException in case the authorization fails.
     * @throws java.io.IOException    if any connection or configured data store problems occurs.
     */
    public synchronized TokenResponse authorize(String username, String password, boolean relaunchExceptions)
            throws IOException {
        lock.lock();
        try {

            PasswordTokenRequest ptr = new PasswordTokenRequest(getConf().getHttpTransport(),
                    getConf().getJsonFactory(), getConf().getEnvTokenUrl(), username, password);

            ptr.setClientAuthentication(new BasicAuthentication(getConf().getApiKey(), getConf().getApiSecret()));
            ptr.setRequestInitializer(new HttpRequestInitializer() {

                @Override
                public void initialize(HttpRequest request) throws IOException {
                    request.setSuppressUserAgentSuffix(true);
                    request.setHeaders(defaultHeaders);
                }
            });

            logger.info("Authorizing: " + username);
            TokenResponse response = ptr.execute();

            logger.info(username + " authorized, save credentials.");
            userDb.saveCredentials(ACCOUNT_TOKENS, (Credential) createCredentialWithRefreshToken(response));

            return response;

        } catch (TokenResponseException e) {

            AudioBoxException up = new AuthorizationException(e);
            if (!isExceptionHandled(up) && relaunchExceptions) {
                throw up; // Deh eh eh... :-P
            }

            return null;

        } finally {
            lock.unlock();
        }
    }

    /**
     * This method returns {@code true} if AudioBox Desktop application is active on any computer.
     *
     * @return {@code true} if AudioBox Desktop application is active on any computer. {@code false} if not
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public boolean isDaemonRunning() throws IOException {
        return StringUtils.isNotBlank(remoteDaemonIp());
    }

    /**
     * This methods returns the {@code remote ip address} of AudioBox Desktop application
     *
     * @return the {@code remote ip address}
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public String remoteDaemonIp() throws IOException {
        HttpResponse rsp = doRequestToChannel(HttpMethods.GET, "/daemon/keepalive", null, null,
                Configuration.Channels.daemon, null);
        return rsp != null && rsp.isSuccessStatusCode() ? Io.contentToString(rsp.getContent()) : StringUtils.EMPTY;
    }

    /**
     * Returns information about the authorized user.
     * <br/>
     * <strong>NOTE:</strong> this method always performs a request, use it wisely
     *
     * @return the {@link fm.audiobox.core.models.User user}
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public User getUser() throws IOException {
        HttpResponse rsp = doGET(UserWrapper.getPath());
        return rsp != null && rsp.isSuccessStatusCode() ? rsp.parseAs(UserWrapper.class).getUser() : null;
    }

    /**
     * Gets user's playlists.
     *
     * @return a {@link java.util.List} of {@link fm.audiobox.core.models.Playlist playlists}
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public List<Playlist> getPlaylists() throws IOException {
        HttpResponse rsp = doGET(Playlists.getPath());
        return rsp != null && rsp.isSuccessStatusCode() ? rsp.parseAs(Playlists.class).getPlaylists() : null;
    }

    /**
     * Gets the token-specified playlist.
     * Triggers Smart Playlist compilation if the requested playlist is a SmartPlaylist.
     * <br/>
     * <strong>NOTE:</strong> this method will always perform a request against AudioBox servers, for this reason be
     * smart and try to apply some sort of cache strategy.
     *
     * @param token the token of the playlist to get.
     *
     * @return the specified {@link fm.audiobox.core.models.Playlist playlist}
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public Playlist getPlaylist(String token) throws IOException {
        HttpResponse rsp = doGET(ModelUtil.interpolate(Playlist.getPath(), token));
        return rsp != null && rsp.isSuccessStatusCode() ? rsp.parseAs(PlaylistWrapper.class).getPlaylist() : null;
    }

    /**
     * Gets user's notifications.
     *
     * @return the {@link fm.audiobox.core.models.Notifications notifications}
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public Notifications getNotifications() throws IOException {
        HttpResponse rsp = doGET(Notifications.getPath());
        return rsp != null && rsp.isSuccessStatusCode() ? rsp.parseAs(Notifications.class) : null;
    }

    /**
     * Builds a new {@link fm.audiobox.core.net.Upload} ready to start.
     * <p>
     * You can still set a listener with {@link fm.audiobox.core.net.Upload#setListener(fm.audiobox.core.net.NetworkProgressListener)}
     * </p>
     *
     * @param file the file to upload on AudioBox
     *
     * @return a {@link fm.audiobox.core.net.Upload} ready to {@link fm.audiobox.core.net.Upload#start()}
     */
    public Upload newUpload(final File file) {
        return newUpload(file, null);
    }

    /**
     * Builds a new {@link fm.audiobox.core.net.Upload} ready to start.
     *
     * @param file     the file to upload on AudioBox
     * @param listener the {@link fm.audiobox.core.net.NetworkProgressListener} for progress monitoring
     *
     * @return a {@link fm.audiobox.core.net.Upload} ready to {@link fm.audiobox.core.net.Upload#start()}
     */
    public Upload newUpload(final File file, NetworkProgressListener listener) {
        return new Upload(this, file, listener);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} GET requests and returns the response.
     *
     * @param path the AudioBox API path where to make the request to.
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doGET(String path) throws IOException {
        return doGET(path, null, null);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} GET requests and returns the response.
     *
     * @param path    the AudioBox API path where to make the request to.
     * @param parser  the {@link com.google.api.client.json.JsonObjectParser} to use to parse the response.
     * @param headers additional request headers
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doGET(String path, JsonObjectParser parser, HttpHeaders headers) throws IOException {
        return doRequest(HttpMethods.GET, path, null, parser, headers);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} PUT requests and returns the response.
     *
     * @param path the AudioBox API path where to make the request to.
     * @param data the data to send with the request
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doPUT(String path, HttpContent data) throws IOException {
        return doPUT(path, data, null, null);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} PUT requests and returns the response.
     *
     * @param path    the AudioBox API path where to make the request to.
     * @param data    the data to send with the request
     * @param parser  the {@link com.google.api.client.json.JsonObjectParser} to use to parse the response.
     * @param headers additional request headers
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doPUT(String path, HttpContent data, JsonObjectParser parser, HttpHeaders headers)
            throws IOException {
        return doRequest(HttpMethods.PUT, path, data, parser, headers);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} DELETE requests to the given path.
     *
     * @param path the AudioBox API path where to make the request to.
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doDELETE(String path) throws IOException {
        return doDELETE(path, null, null);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} DELETE requests to the given path.
     *
     * @param path    the AudioBox API path where to make the request to.
     * @param parser  the {@link com.google.api.client.json.JsonObjectParser} to use to parse the response.
     * @param headers additional request headers
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doDELETE(String path, JsonObjectParser parser, HttpHeaders headers) throws IOException {
        return doRequest(HttpMethods.DELETE, path, null, parser, headers);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} POST requests and returns the response.
     *
     * @param path the AudioBox API path where to make the request to.
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doPOST(String path) throws IOException {
        return doPOST(path, null, null, null);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} POST requests and returns the response.
     *
     * @param path the AudioBox API path where to make the request to.
     * @param data the data to send with the request
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doPOST(String path, HttpContent data) throws IOException {
        return doPOST(path, data, null, null);
    }

    /**
     * Performs {@link AudioBoxClient#authorize(String, String) signed} POST requests and returns the response.
     *
     * @param path    the AudioBox API path where to make the request to.
     * @param data    the data to send with the request
     * @param parser  the {@link com.google.api.client.json.JsonObjectParser} to use to parse the response.
     * @param headers additional request headers
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doPOST(String path, HttpContent data, JsonObjectParser parser, HttpHeaders headers)
            throws IOException {
        return doRequest(HttpMethods.POST, path, data, parser, headers);
    }

    /**
     * Executes the configured request by calling AudioBox API services.
     *
     * @param method  the method to use
     * @param path    the AudioBox API path where to make the request to.
     * @param data    the data to send with the request
     * @param parser  the parser to use for the resulting object
     * @param headers additional request headers
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public HttpResponse doRequest(String method, String path, HttpContent data, JsonObjectParser parser,
            HttpHeaders headers) throws IOException {
        return doRequestToChannel(method, path, data, parser, null, headers);
    }

    /**
     * Executes the configured request by calling AudioBox API services.
     *
     * @param method  the method to use
     * @param path    the AudioBox API path where to make the request to.
     * @param data    the data to send with the request
     * @param parser  the parser to use for the resulting object
     * @param channel the {@link fm.audiobox.core.config.Configuration.Channels Channel} to query
     * @param headers additional request headers
     *
     * @return the http response, may be null if any error occurs during the request.
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if any of the remote error exception is detected.
     * @throws java.io.IOException                           if any connection problem occurs.
     * @see fm.audiobox.core.exceptions.AudioBoxException
     */
    public synchronized HttpResponse doRequestToChannel(String method, String path, HttpContent data,
            JsonObjectParser parser, Configuration.Channels channel, HttpHeaders headers) throws IOException {
        lock.lock();
        try {

            if (channel == null) {
                channel = Configuration.Channels.api;
            }

            GenericUrl url = new GenericUrl(getConf().getBaseUrl(channel) + path);
            logger.info(method + " " + url);

            HttpResponse response = getRequestFactory(parser, headers).buildRequest(method, url, data).execute();
            validateResponse(response);
            return response;

        } catch (TokenResponseException e) {
            logger.warn("Token error occurred: " + e.getDetails().getError());

            AudioBoxException ex = new AuthorizationException(e);
            if (!isExceptionHandled(ex)) {
                throw ex;
            }

        } finally {
            lock.unlock();
        }

        return null;
    }

    /* ================ */
    /*  Private methods */
    /* ================ */

    /**
     * Gets request factory.
     *
     * @param parser  the parser to use for the resulting object
     * @param headers additional request headers
     *
     * @return the request factory
     */
    private HttpRequestFactory getRequestFactory(final JsonObjectParser parser, final HttpHeaders headers) {

        return getConf().getHttpTransport().createRequestFactory(new HttpRequestInitializer() {

            @Override
            public void initialize(HttpRequest request) throws IOException {
                HttpRequestInitializer c = createCredentialWithRefreshToken();
                c.initialize(request);
                request.setParser(parser == null ? jsonObjectParser : parser);
                request.setThrowExceptionOnExecuteError(false);
                request.setSuppressUserAgentSuffix(true);

                HttpHeaders head = getDefaultHeaders();
                if (headers != null) {
                    if (head != null) {
                        headers.fromHttpHeaders(head);
                    }
                    head = headers;
                }

                request.setHeaders(head);
            }
        });
    }

    /**
     * Build not signed credential.
     *
     * @return the credential
     */
    private Credential buildNotSignedCredential() {
        return new Credential.Builder(BearerToken.authorizationHeaderAccessMethod())
                .setTransport(getConf().getHttpTransport()).setJsonFactory(getConf().getJsonFactory())
                .setTokenServerUrl(getConf().getEnvTokenUrl())
                .setClientAuthentication(new BasicAuthentication(getConf().getApiKey(), getConf().getApiSecret()))
                .addRefreshListener(this.refreshListener).build();
    }

    /**
     * Create credential with refresh token.
     *
     * @return the credential
     *
     * @throws java.io.IOException if any problem occurs with the configured data store.
     */
    private HttpRequestInitializer createCredentialWithRefreshToken() throws IOException {
        StoredCredential c = getStoredCredential();
        logger.trace("Signing request method with access_token: " + StringUtils.abbreviate(c.getAccessToken(), 8));

        return createCredentialWithRefreshToken(c);
    }

    /**
     * Create credential starting from the token response with refresh token.
     *
     * @param tokenResponse the token response to use for Credential building
     *
     * @return the credential
     */
    private HttpRequestInitializer createCredentialWithRefreshToken(TokenResponse tokenResponse) {
        return buildNotSignedCredential().setFromTokenResponse(tokenResponse);
    }

    /**
     * Create credential starting from the stored credential with refresh token.
     *
     * @param storedCredential the stored credential to use for Credential building
     *
     * @return the credential
     */
    private HttpRequestInitializer createCredentialWithRefreshToken(StoredCredential storedCredential) {
        return buildNotSignedCredential().setAccessToken(storedCredential.getAccessToken())
                .setExpiresInSeconds(storedCredential.getExpirationTimeMilliseconds())
                .setRefreshToken(storedCredential.getRefreshToken());
    }

    /**
     * Gets stored credential.
     *
     * @return the stored credential
     *
     * @throws java.io.IOException if any problem occurs with the configured data store.
     */
    private StoredCredential getStoredCredential() throws IOException {
        return userDb.getCredentials(ACCOUNT_TOKENS);
    }

    /**
     * Validates the given response and eventually throws the corresponding exception.
     *
     * @param response the response to validate
     *
     * @throws AudioBoxException in case of 400, 401, 402, 403, 404, 409, 422, 500 or 503 response codes.
     */
    private void validateResponse(HttpResponse response) throws IOException {
        try {

            switch (response.getStatusCode()) {
            case HttpStatus.SC_BAD_REQUEST: // 400
            case HttpStatus.SC_UNAUTHORIZED: // 401
                throw new AuthorizationException(response);

            case HttpStatus.SC_PAYMENT_REQUIRED: // 402
            case HttpStatus.SC_FORBIDDEN: // 403
                throw new ForbiddenException(response);

            case HttpStatus.SC_NOT_FOUND: // 404
                // Due to technical issues 404 is also given for not editable resources.
                throw new ResourceNotFoundException(response);

            case HttpStatus.SC_CONFLICT: // 409 (uploads)
                throw new FileAlreadyUploaded(response);

            case HttpStatus.SC_UNPROCESSABLE_ENTITY: // 422
                throw new ValidationException(response);

            case HttpStatus.SC_INTERNAL_SERVER_ERROR: // 500
                throw new RemoteMessageException(response);

            case HttpStatus.SC_SERVICE_UNAVAILABLE: // 503
                throw new SystemOverloadedException(response);

            }

        } catch (AudioBoxException e) {
            if (!isExceptionHandled(e)) {
                throw e;
            }
        }
    }

    /**
     * Handles with {@link fm.audiobox.core.exceptions.ExceptionHandler} any given
     * {@link fm.audiobox.core.exceptions.AudioBoxException}
     *
     * @param e the exception to handle
     *
     * @throws fm.audiobox.core.exceptions.AudioBoxException if exception is not handled by an ExceptionHandler
     */
    private boolean isExceptionHandled(AudioBoxException e) throws AudioBoxException {
        ExceptionHandler eh = this.getConf().getExceptionHandler();
        return eh != null && eh.onException(e);
    }

}