/*
* Copyright 2014 Sonoport (Asia) Pte Ltd
*
* 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.sonoport.freesound;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map.Entry;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.mashape.unirest.request.GetRequest;
import com.mashape.unirest.request.HttpRequest;
import com.mashape.unirest.request.HttpRequestWithBody;
import com.sonoport.freesound.query.BinaryResponseQuery;
import com.sonoport.freesound.query.JSONResponseQuery;
import com.sonoport.freesound.query.OAuthQuery;
import com.sonoport.freesound.query.PagingQuery;
import com.sonoport.freesound.query.Query;
import com.sonoport.freesound.query.oauth2.AccessTokenQuery;
import com.sonoport.freesound.query.oauth2.OAuth2AccessTokenRequest;
import com.sonoport.freesound.query.oauth2.RefreshOAuth2AccessTokenRequest;
import com.sonoport.freesound.response.AccessTokenDetails;
import com.sonoport.freesound.response.PagingResponse;
import com.sonoport.freesound.response.Response;
/**
* Client used to make calls to the freesound.org API (v2).
*
* Users of this library must first register their application with Freesound (http://www.freesound.org/apiv2/apply).
* The credentials generated (Client ID & Client Secret/API Key) are then used to construct an instance of this class.
*/
public class FreesoundClient {
/** Base address for all calls to the freesound.org APIv2. */
protected static final String API_ENDPOINT = "https://www.freesound.org/apiv2";
/** Name of the HTTP Header specifying the user agent string. */
protected static final String HTTP_USER_AGENT_HEADER = "User-Agent";
/** The default User-Agent string to pass with requests, if user does not specify their own. */
protected static final String DEFAULT_USER_AGENT_STRING =
"Sonoport-freesound-java/0.5.0 (https://github.com/Sonoport/freesound-java)";
/** Name of the HTTP Header specifying the content types to be accepted. */
protected static final String HTTP_ACCEPT_HEADER = "Accept";
/** The content types the library will accept. */
protected static final String CONTENT_TYPES_TO_ACCEPT = "application/json, application/octet-stream";
/** The Client ID created by freesound.org for the application. */
private final String clientId;
/** The Client Secret/API Key generated by freesound.org for the application. */
private final String clientSecret;
/**
* @param clientId Client ID for application
* @param clientSecret Client Secret (API Key) for application
*/
public FreesoundClient(final String clientId, final String clientSecret) {
this(clientId, clientSecret, null);
}
/**
* @param clientId Client ID for application
* @param clientSecret Client Secret (API Key) for application
* @param userAgentString The User-Agent string to send with all requests
*/
public FreesoundClient(final String clientId, final String clientSecret, final String userAgentString) {
this.clientId = clientId;
this.clientSecret = clientSecret;
Unirest.setDefaultHeader(HTTP_ACCEPT_HEADER, CONTENT_TYPES_TO_ACCEPT);
if (userAgentString != null) {
Unirest.setDefaultHeader(HTTP_USER_AGENT_HEADER, userAgentString);
} else {
Unirest.setDefaultHeader(HTTP_USER_AGENT_HEADER, DEFAULT_USER_AGENT_STRING);
}
}
/**
* Execute a given query (synchronously) against the freesound API.
*
* @param <S> The expected response type from the query
* @param <R> The response type to return
*
* @param query The query to execute
* @return The result of the query
* @throws FreesoundClientException Any errors encountered when performing API call
*/
@SuppressWarnings("unchecked")
public <S extends Object, R extends Object> Response<R> executeQuery(final Query<S, R> query)
throws FreesoundClientException {
final HttpRequest request = buildHTTPRequest(query);
final String credential = buildAuthorisationCredential(query);
if (credential != null) {
request.header("Authorization", credential);
}
try {
if (query instanceof JSONResponseQuery) {
final HttpResponse<JsonNode> httpResponse = request.asJson();
final S responseBody = (S) httpResponse.getBody().getObject();
return query.processResponse(httpResponse.getStatus(), httpResponse.getStatusText(), responseBody);
} else if (query instanceof BinaryResponseQuery) {
final HttpResponse<InputStream> httpResponse = request.asBinary();
final S responseBody = (S) httpResponse.getBody();
return query.processResponse(httpResponse.getStatus(), httpResponse.getStatusText(), responseBody);
} else {
throw new FreesoundClientException(String.format("Unknown request type: %s", query.getClass()));
}
} catch (final ClassCastException | UnirestException e) {
throw new FreesoundClientException("Error when attempting to make API call", e);
}
}
/**
* Build the Unirest {@link HttpRequest} that will be used to make the call to the API.
*
* @param query The query to be made
* @return Properly configured {@link HttpRequest} representing query
*/
private HttpRequest buildHTTPRequest(final Query<?, ?> query) {
final String url = API_ENDPOINT + query.getPath();
HttpRequest request;
switch (query.getHttpRequestMethod()) {
case GET:
request = Unirest.get(url);
if ((query.getQueryParameters() != null) && !query.getQueryParameters().isEmpty()) {
((GetRequest) request).queryString(query.getQueryParameters());
}
break;
case POST:
request = Unirest.post(url);
if ((query.getQueryParameters() != null) && !query.getQueryParameters().isEmpty()) {
((HttpRequestWithBody) request).fields(query.getQueryParameters());
}
break;
default:
request = Unirest.get(url);
}
/*
* Add any named route parameters to the request (i.e. elements used to build the URI, such as
* '/sound/{sound_id}' would have a parameter named 'sound_id').
*/
if ((query.getRouteParameters() != null) && !query.getRouteParameters().isEmpty()) {
for (final Entry<String, String> routeParameter : query.getRouteParameters().entrySet()) {
request.routeParam(routeParameter.getKey(), routeParameter.getValue());
}
}
return request;
}
/**
* Build the credential that will be passed in the 'Authorization' HTTP header as part of the API call. The nature
* of the credential will depend on the query being made.
*
* @param query The query being made
* @return The string to pass in the Authorization header (or null if none)
*/
private String buildAuthorisationCredential(final Query<?, ?> query) {
String credential = null;
if (query instanceof OAuthQuery) {
final String oauthToken = ((OAuthQuery) query).getOauthToken();
credential = String.format("Bearer %s", oauthToken);
} else if (query instanceof AccessTokenQuery) {
// Don't set the Authorization header
} else {
credential = String.format("Token %s", clientSecret);
}
return credential;
}
/**
* Retrieve the next page of results for a {@link PagingQuery}.
*
* @param <I> The data type of items returned by the query
*
* @param query The {@link PagingQuery} being run
* @return The results of the query
*
* @throws FreesoundClientException If it is not possible to retrieve the next page
*/
public <I extends Object> PagingResponse<I> nextPage(final PagingQuery<?, I> query)
throws FreesoundClientException {
final int currentPage = query.getPage();
query.setPage(currentPage + 1);
return (PagingResponse<I>) executeQuery(query);
}
/**
* Retrieve the previous page of results for a {@link PagingQuery}.
*
* @param <I> The data type of items returned by the query
*
* @param query The {@link PagingQuery} being run
* @return The results of the query
*
* @throws FreesoundClientException If it is not possible to retrieve the previous page
*/
public <I extends Object> PagingResponse<I> previousPage(final PagingQuery<?, I> query)
throws FreesoundClientException {
final int currentPage = query.getPage();
query.setPage(currentPage - 1);
return (PagingResponse<I>) executeQuery(query);
}
/**
* Redeem an authorisation code received from freesound.org for an access token that can be used to make calls to
* OAuth2 protected resources.
*
* @param authorisationCode The authorisation code received
* @return Details of the access token returned
*
* @throws FreesoundClientException Any exception thrown during call
*/
public Response<AccessTokenDetails> redeemAuthorisationCodeForAccessToken(final String authorisationCode)
throws FreesoundClientException {
final OAuth2AccessTokenRequest tokenRequest =
new OAuth2AccessTokenRequest(clientId, clientSecret, authorisationCode);
return executeQuery(tokenRequest);
}
/**
* Retrieve a new OAuth2 access token using a refresh token.
*
* @param refreshToken The refresh token to present
* @return Details of the access token returned
* @throws FreesoundClientException Any exception thrown during call
*/
public Response<AccessTokenDetails> refreshAccessToken(final String refreshToken) throws FreesoundClientException {
final RefreshOAuth2AccessTokenRequest tokenRequest =
new RefreshOAuth2AccessTokenRequest(clientId, clientSecret, refreshToken);
return executeQuery(tokenRequest);
}
/**
* Shutdown the client. Ensures that all background processes are properly terminated so that application can be
* shutdown cleanly.
*
* @throws FreesoundClientException Any errors encountered performing shutdown
*/
public void shutdown() throws FreesoundClientException {
try {
Unirest.shutdown();
} catch (final IOException e) {
throw new FreesoundClientException("Error shutting down background Unirest service", e);
}
}
}