package com.soundcloud.api; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpUriRequest; import java.io.IOException; import java.net.URI; /** * Interface with SoundCloud, using OAuth2. * * This is the interface, for the implementation see ApiWrapper. * @see ApiWrapper */ public interface CloudAPI { // OAuth2 parameters String GRANT_TYPE = "grant_type"; String CLIENT_ID = "client_id"; String CLIENT_SECRET = "client_secret"; String USERNAME = "username"; String REDIRECT_URI = "redirect_uri"; String CODE = "code"; String RESPONSE_TYPE = "response_type"; String SCOPE = "scope"; String DISPLAY = "display"; String STATE = "state"; // standard oauth2 grant types String PASSWORD = "password"; String AUTHORIZATION_CODE = "authorization_code"; String REFRESH_TOKEN = "refresh_token"; String CLIENT_CREDENTIALS = "client_credentials"; // custom String OAUTH1_TOKEN_GRANT_TYPE = "oauth1_token"; // soundcloud String FACEBOOK_GRANT_TYPE = "urn:soundcloud:oauth2:grant-type:facebook&access_token="; // oauth2 extension String GOOGLE_PLUS_GRANT_TYPE = "urn:soundcloud:oauth2:grant-type:google_plus&access_token="; // other constants String REALM = "SoundCloud"; String OAUTH_SCHEME = "oauth"; String VERSION = "1.3.1"; String USER_AGENT = "SoundCloud Java Wrapper ("+VERSION+")"; String POPUP = "popup"; /** * Request a token using <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.2"> * Resource Owner Password Credentials</a>. * * @param username SoundCloud username * @param password SoundCloud password * @param scopes the desired scope(s), or empty for default scope * @return a valid token * @throws com.soundcloud.api.CloudAPI.InvalidTokenException * invalid token * @throws IOException In case of network/server errors */ Token login(String username, String password, String... scopes) throws IOException; /** * Request a token using <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.1"> * Authorization Code</a>, requesting a default scope. * * @param code the authorization code * @param scopes the desired scope(s), or empty for default scope * @return a valid token * @throws com.soundcloud.api.CloudAPI.InvalidTokenException invalid token * @throws IOException In case of network/server errors */ Token authorizationCode(String code, String... scopes) throws IOException; /** * Request a "signup" token using <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4"> * Client Credentials</a>. * * Note that this token is <b>not</b> set as the current token in the wrapper - it should only be used * for one request (typically the signup / user creation request). * Also note that not all apps are allowed to request this token type (the wrapper throws * InvalidTokenException in this case). * * @param scopes the desired scope(s), or empty for default scope * @return a valid token * @throws IOException IO/Error * @throws com.soundcloud.api.CloudAPI.InvalidTokenException if requested scope is not available */ Token clientCredentials(String... scopes) throws IOException; /** * Request a token using an <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.5"> * extension grant type</a>. * @param grantType * @param scopes * @return * @throws IOException */ Token extensionGrantType(String grantType, String... scopes) throws IOException; /** * Tries to refresh the currently used access token with the refresh token. * If successful the API wrapper will have the new token set already. * @return a valid token * @throws IOException in case of network problems * @throws com.soundcloud.api.CloudAPI.InvalidTokenException invalid token * @throws IllegalStateException if no refresh token present */ Token refreshToken() throws IOException; /** * This method should be called when the token was found to be invalid. * Also replaces the current token, if there is one available. * @return an alternative token, or null if none available * (which indicates that a refresh could be tried) */ Token invalidateToken(); /** * @param request resource to HEAD * @return the HTTP response * @throws IOException IO/Error */ HttpResponse head(Request request) throws IOException; /** * @param request resource to GET * @return the HTTP response * @throws IOException IO/Error */ HttpResponse get(Request request) throws IOException; /** * @param request resource to POST * @return the HTTP response * @throws IOException IO/Error */ HttpResponse post(Request request) throws IOException; /** * @param request resource to PUT * @return the HTTP response * @throws IOException IO/Error */ HttpResponse put(Request request) throws IOException; /** * @param request resource to DELETE * @return the HTTP response * @throws IOException IO/Error */ HttpResponse delete(Request request) throws IOException; /** * @return the used httpclient */ HttpClient getHttpClient(); /** * Generic execute method, with added workarounds for various HTTPClient bugs. * * @param target the target host (can be null) * @param request the request * @return the HTTP response * @throws IOException network errors * @throws BrokenHttpClientException in case of HTTPClient framework bugs */ HttpResponse safeExecute(HttpHost target, HttpUriRequest request) throws IOException; /** * Resolve the given SoundCloud URI * * @param uri SoundCloud model URI, e.g. http://soundcloud.com/bob * @return the id * @throws IOException network errors * @throws ResolverException if object could not be resolved */ long resolve(String uri) throws IOException; /** * Resolve the given SoundCloud stream URI * * @param uri SoundCloud stream URI, e.g. https://api.soundcloud.com/tracks/25272620/stream * @param skipLogging skip logging the play of this track (client needs * {@link com.soundcloud.api.Token#SCOPE_PLAYCOUNT}) * @return the resolved stream * @throws IOException network errors * @throws com.soundcloud.api.CloudAPI.ResolverException resolver error (invalid status etc) */ Stream resolveStreamUrl(String uri, boolean skipLogging) throws IOException; /** @return the current token */ Token getToken(); /** @param token the token to be used */ void setToken(Token token); /** * Registers a listener. The listener will be informed when an access token was found * to be invalid, and when the token had to be refreshed. * @param listener token listener */ void setTokenListener(TokenListener listener); /** * Request login via authorization code * After login, control will go to the redirect URI (wrapper specific), with * one of the following query parameters appended: * <ul> * <li><code>code</code> in case of success, this will contain the code used for the * <code>authorizationCode</code> call to obtain the access token. * <li><code>error</code> in case of failure, this contains an error code (most likely * <code>access_denied</code>). * </ul> * @param options auth endpoint to use (leave out for default), requested scope (leave out for default), display ('popup' for mobile optimized screen) and state. * @return the URI to open in a browser/WebView etc. * @see CloudAPI#authorizationCode(String, String...) */ URI authorizationCodeUrl(String... options); /** * Changes the default content type sent in the "Accept" header. * If you don't set this it defaults to "application/json". * * @param contentType the request mime type. */ void setDefaultContentType(String contentType); void setDefaultAcceptEncoding(String encoding); /** * Interested in changes to the current token. */ interface TokenListener { /** * Called when token was found to be invalid * @param token the invalid token * @return a cached token if available, or null */ Token onTokenInvalid(Token token); /** * Called when the token got successfully refreshed * @param token the refreshed token */ void onTokenRefreshed(Token token); } /** * Thrown when token is not valid. */ class InvalidTokenException extends IOException { private static final long serialVersionUID = 1954919760451539868L; /** * @param code the HTTP error code * @param status the HTTP status, or other error message */ public InvalidTokenException(int code, String status) { super("HTTP error:" + code + " (" + status + ")"); } } /** * Thrown if resolving the audio stream of a SoundCloud sound fails. */ class ResolverException extends ApiResponseException { public ResolverException(String s, HttpResponse resp) { super(resp, s); } public ResolverException(Throwable throwable, HttpResponse response) { super(throwable, response); } } /** * Thrown if the service API responds in error. The HTTP status code can be obtained via {@link #getStatusCode()}. */ class ApiResponseException extends IOException { private static final long serialVersionUID = -2990651725862868387L; public final HttpResponse response; public ApiResponseException(HttpResponse resp, String error) { super(resp.getStatusLine().getStatusCode() + ": [" + resp.getStatusLine().getReasonPhrase() + "] " + (error != null ? error : "")); this.response = resp; } public ApiResponseException(Throwable throwable, HttpResponse response) { super(throwable == null ? null : throwable.toString()); initCause(throwable); this.response = response; } public int getStatusCode() { return response.getStatusLine().getStatusCode(); } @Override public String getMessage() { return super.getMessage()+" "+(response != null ? response.getStatusLine() : ""); } } class BrokenHttpClientException extends IOException { private static final long serialVersionUID = -4764332412926419313L; BrokenHttpClientException(Throwable throwable) { super(throwable == null ? null : throwable.toString()); initCause(throwable); } } }