Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

proof of concept, basic code structure and layout and a simple test

  • Loading branch information...
commit fffb9920c0954e5544f792054d7695bfd29bb1b2 1 parent 792f0ba
@michaellavelle authored
Showing with 738 additions and 0 deletions.
  1. +70 −0 pom.xml
  2. +8 −0 src/main/java/org/springframework/social/soundcloud/api/SoundCloud.java
  3. +88 −0 src/main/java/org/springframework/social/soundcloud/api/SoundCloudProfile.java
  4. +7 −0 src/main/java/org/springframework/social/soundcloud/api/UserOperations.java
  5. +5 −0 src/main/java/org/springframework/social/soundcloud/api/impl/AbstractSoundCloudOperations.java
  6. +183 −0 src/main/java/org/springframework/social/soundcloud/api/impl/SoundCloudErrorHandler.java
  7. +92 −0 src/main/java/org/springframework/social/soundcloud/api/impl/SoundCloudTemplate.java
  8. +29 −0 src/main/java/org/springframework/social/soundcloud/api/impl/UserTemplate.java
  9. +25 −0 src/main/java/org/springframework/social/soundcloud/api/impl/json/SoundCloudModule.java
  10. +32 −0 src/main/java/org/springframework/social/soundcloud/api/impl/json/SoundCloudProfileMixin.java
  11. +50 −0 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudAdapter.java
  12. +12 −0 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudConnectionFactory.java
  13. +16 −0 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudOAuth2Template.java
  14. +20 −0 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudServiceProvider.java
  15. +101 −0 src/test/java/org/springframework/social/soundcloud/connect/SoundCloudAdapterTest.java
View
70 pom.xml
@@ -0,0 +1,70 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.springframework.social</groupId>
+ <artifactId>spring-social-soundcloud</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+ <name>Spring Social SoundCloud</name>
+ <repositories>
+ <repository>
+ <id>org.springframework.maven.milestone</id>
+ <name>Spring Maven Milestone Repository</name>
+ <url>http://maven.springframework.org/milestone</url>
+</repository>
+
+ </repositories>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.social</groupId>
+ <artifactId>spring-social-core</artifactId>
+ <version>1.0.0.RC3</version>
+</dependency>
+<dependency>
+ <groupId>org.springframework.social</groupId>
+ <artifactId>spring-social-test</artifactId>
+ <version>1.0.0.RC3</version>
+</dependency>
+<dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.8.5</version>
+ <scope>test</scope>
+</dependency>
+<dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ <version>1.8.5</version>
+</dependency>
+
+
+<dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.2</version>
+ <scope>test</scope>
+
+</dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <version>3.1.0.M2</version>
+ <scope>test</scope>
+ </dependency>
+
+
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
View
8 src/main/java/org/springframework/social/soundcloud/api/SoundCloud.java
@@ -0,0 +1,8 @@
+package org.springframework.social.soundcloud.api;
+
+
+public interface SoundCloud {
+
+ public UserOperations userOperations();
+
+}
View
88 src/main/java/org/springframework/social/soundcloud/api/SoundCloudProfile.java
@@ -0,0 +1,88 @@
+package org.springframework.social.soundcloud.api;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+* Model class containing a SoundCloud user's profile information.
+* @author Michael Lavelle
+*/
+public class SoundCloudProfile implements Serializable {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ private String id;
+ private String username;
+ private String avatarUrl;
+ private String permalinkUrl;
+ private String fullName;
+ private String uri;
+ private String city;
+
+
+ public SoundCloudProfile()
+ {
+
+ }
+
+ public SoundCloudProfile(String id,String username,String avatarUrl,String permalinkUrl,String fullName,String uri,String city)
+ {
+ this.id = id;
+ this.username = username;
+ this.avatarUrl = avatarUrl;
+ this.permalinkUrl = permalinkUrl;
+ this.fullName = fullName;
+ this.uri = uri;
+ this.city = city;
+
+ }
+
+ public String getAvatarUrl() {
+ return avatarUrl;
+ }
+ public void setAvatarUrl(String avatarUrl) {
+ this.avatarUrl = avatarUrl;
+ }
+ public String getPermalinkUrl() {
+ return permalinkUrl;
+ }
+ public void setPermalinkUrl(String permalinkUrl) {
+ this.permalinkUrl = permalinkUrl;
+ }
+ public String getFullName() {
+ return fullName;
+ }
+ public void setFullName(String fullName) {
+ this.fullName = fullName;
+ }
+ public String getUri() {
+ return uri;
+ }
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+ public String getCity() {
+ return city;
+ }
+ public void setCity(String city) {
+ this.city = city;
+ }
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+ public String getUsername() {
+ return username;
+ }
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+
+}
View
7 src/main/java/org/springframework/social/soundcloud/api/UserOperations.java
@@ -0,0 +1,7 @@
+package org.springframework.social.soundcloud.api;
+
+public interface UserOperations {
+
+ public SoundCloudProfile getUserProfile();
+
+}
View
5 src/main/java/org/springframework/social/soundcloud/api/impl/AbstractSoundCloudOperations.java
@@ -0,0 +1,5 @@
+package org.springframework.social.soundcloud.api.impl;
+
+public abstract class AbstractSoundCloudOperations {
+
+}
View
183 src/main/java/org/springframework/social/soundcloud/api/impl/SoundCloudErrorHandler.java
@@ -0,0 +1,183 @@
+package org.springframework.social.soundcloud.api.impl;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+
+import org.codehaus.jackson.JsonFactory;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.social.DuplicateStatusException;
+import org.springframework.social.ExpiredAuthorizationException;
+import org.springframework.social.InsufficientPermissionException;
+import org.springframework.social.InternalServerErrorException;
+import org.springframework.social.InvalidAuthorizationException;
+import org.springframework.social.MissingAuthorizationException;
+import org.springframework.social.NotAuthorizedException;
+import org.springframework.social.OperationNotPermittedException;
+import org.springframework.social.RateLimitExceededException;
+import org.springframework.social.ResourceNotFoundException;
+import org.springframework.social.RevokedAuthorizationException;
+import org.springframework.social.UncategorizedApiException;
+
+import org.springframework.web.client.DefaultResponseErrorHandler;
+
+/**
+ * Subclass of {@link DefaultResponseErrorHandler} that handles errors from SoundCloud's
+ * API, interpreting them into appropriate exceptions.
+ * @author Michael Lavelle
+ */
+class SoundCloudErrorHandler extends DefaultResponseErrorHandler {
+
+ @Override
+ public void handleError(ClientHttpResponse response) throws IOException {
+ Map<String, String> errorDetails = extractErrorDetailsFromResponse(response);
+ if (errorDetails == null) {
+ handleUncategorizedError(response, errorDetails);
+ }
+
+ handleSoundCloudError(response.getStatusCode(), errorDetails);
+
+ // if not otherwise handled, do default handling and wrap with UncategorizedApiException
+ handleUncategorizedError(response, errorDetails);
+ }
+
+ @Override
+ public boolean hasError(ClientHttpResponse response) throws IOException {
+ if(super.hasError(response)) {
+ return true;
+ }
+ // only bother checking the body for errors if we get past the default error check
+ String content = readFully(response.getBody());
+ return content.startsWith("{\"error\":") || content.equals("false");
+ }
+
+ /**
+ * Examines the error data returned from Facebook and throws the most applicable exception.
+ * @param errorDetails a Map containing a "type" and a "message" corresponding to the Graph API's error response structure.
+ */
+ void handleSoundCloudError(HttpStatus statusCode, Map<String, String> errorDetails) {
+ // Can't trust the type to be useful. It's often OAuthException, even for things not OAuth-related.
+ // Can rely only on the message (which itself isn't very consistent).
+ String message = errorDetails.get("message");
+
+ if (statusCode == HttpStatus.OK) {
+ if (message.contains("Some of the aliases you requested do not exist")) {
+ throw new ResourceNotFoundException(message);
+ }
+ } else if (statusCode == HttpStatus.BAD_REQUEST) {
+ if (message.contains("Unknown path components")) {
+ throw new ResourceNotFoundException(message);
+ } else if (message.equals("An access token is required to request this resource.")) {
+ throw new MissingAuthorizationException();
+ } else if (message.equals("An active access token must be used to query information about the current user.")) {
+ throw new MissingAuthorizationException();
+ } else if (message.startsWith("Error validating access token")) {
+ handleInvalidAccessToken(message);
+ } else if (message.equals("Error validating application.")) { // Access token with incorrect app ID
+ throw new InvalidAuthorizationException(message);
+ } else if (message.equals("Invalid access token signature.")) { // Access token that fails signature validation
+ throw new InvalidAuthorizationException(message);
+ } else if (message.contains("Application does not have the capability to make this API call.") || message.contains("App must be on whitelist")) {
+ throw new OperationNotPermittedException(message);
+ } else if (message.contains("Invalid fbid") || message.contains("The parameter url is required")) {
+ throw new OperationNotPermittedException("Invalid object for this operation");
+ } else if (message.contains("Duplicate status message") ) {
+ throw new DuplicateStatusException(message);
+ } else if (message.contains("Feed action request limit reached")) {
+ throw new RateLimitExceededException();
+ }
+ } else if (statusCode == HttpStatus.UNAUTHORIZED) {
+ if (message.startsWith("Error validating access token")) {
+ handleInvalidAccessToken(message);
+ }
+ throw new NotAuthorizedException(message);
+ } else if (statusCode == HttpStatus.FORBIDDEN) {
+ if (message.contains("Requires extended permission")) {
+ String requiredPermission = message.split(": ")[1];
+ throw new InsufficientPermissionException(requiredPermission);
+ } else if (message.contains("Permissions error")) {
+ throw new InsufficientPermissionException();
+ } else {
+ throw new OperationNotPermittedException(message);
+ }
+ } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) {
+ if (message.equals("User must be an owner of the friendlist")) { // watch for pattern in similar message in other resources
+ // TODO MLthrow new ResourceOwnershipException(message);
+ } else if (message.equals("The member must be a friend of the current user.")) {
+ // TODO ML throw new NotAFriendException(message);
+ } else {
+ throw new InternalServerErrorException(message);
+ }
+ }
+ }
+
+ private void handleInvalidAccessToken(String message) {
+ if (message.contains("Session has expired at unix time")) {
+ throw new ExpiredAuthorizationException();
+ } else if (message.contains("The session has been invalidated because the user has changed the password.")) {
+ throw new RevokedAuthorizationException();
+ } else if (message.contains("The session is invalid because the user logged out.")) {
+ throw new RevokedAuthorizationException();
+ } else if (message.contains("has not authorized application")) {
+ // Per https://developers.facebook.com/blog/post/500/, this could be in the message when the user removes the application.
+ // In reality, "The session has been invalidated because the user has changed the password." is what you get in that case.
+ // Leaving this check in place in case there FB does return this message (could be a bug in FB?)
+ throw new RevokedAuthorizationException();
+ } else {
+ throw new InvalidAuthorizationException(message);
+ }
+ }
+
+ private void handleUncategorizedError(ClientHttpResponse response, Map<String, String> errorDetails) {
+ try {
+ super.handleError(response);
+ } catch (Exception e) {
+ if (errorDetails != null) {
+ throw new UncategorizedApiException(errorDetails.get("message"), e);
+ } else {
+ throw new UncategorizedApiException("No error details from Facebook", e);
+ }
+ }
+ }
+
+ /*
+ * Attempts to extract Facebook error details from the response.
+ * Returns null if the response doesn't match the expected JSON error response.
+ */
+ @SuppressWarnings("unchecked")
+ private Map<String, String> extractErrorDetailsFromResponse(ClientHttpResponse response) throws IOException {
+ ObjectMapper mapper = new ObjectMapper(new JsonFactory());
+ String json = readFully(response.getBody());
+
+ if (json.equals("false")) {
+ // Sometimes FB returns "false" when requesting an object that the access token doesn't have permission for.
+ throw new InsufficientPermissionException();
+ }
+
+ try {
+ Map<String, Object> responseMap = mapper.<Map<String, Object>>readValue(json, new TypeReference<Map<String, Object>>() {});
+ if (responseMap.containsKey("error")) {
+ return (Map<String, String>) responseMap.get("error");
+ }
+ } catch (JsonParseException e) {
+ return null;
+ }
+ return null;
+ }
+
+ private String readFully(InputStream in) throws IOException {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ StringBuilder sb = new StringBuilder();
+ while (reader.ready()) {
+ sb.append(reader.readLine());
+ }
+ return sb.toString();
+ }
+}
View
92 src/main/java/org/springframework/social/soundcloud/api/impl/SoundCloudTemplate.java
@@ -0,0 +1,92 @@
+package org.springframework.social.soundcloud.api.impl;
+
+import java.util.List;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
+import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
+import org.springframework.social.soundcloud.api.SoundCloud;
+import org.springframework.social.soundcloud.api.UserOperations;
+import org.springframework.social.soundcloud.api.impl.json.SoundCloudModule;
+import org.springframework.social.support.ClientHttpRequestFactorySelector;
+import org.springframework.web.client.RestTemplate;
+
+public class SoundCloudTemplate extends AbstractOAuth2ApiBinding implements
+ SoundCloud {
+
+ private UserOperations userOperations;
+
+ private ObjectMapper objectMapper;
+
+ /**
+ * Create a new instance of SoundCloudTemplate. This constructor creates a
+ * new SoundCloudTemplate able to perform unauthenticated operations against
+ * SoundCloud's API. Some operations do not require OAuth authentication.
+ * For example, retrieving a specified user's profile or feed does not
+ * require authentication . A SoundCloudTemplate created with this
+ * constructor will support those operations. Those operations requiring
+ * authentication will throw {@link NotAuthorizedException}.
+ */
+ public SoundCloudTemplate() {
+ initialize(null);
+ }
+
+ /**
+ * Create a new instance of FacebookTemplate. This constructor creates the
+ * FacebookTemplate using a given access token.
+ *
+ * @param accessToken
+ * An access token given by Facebook after a successful OAuth 2
+ * authentication (or through Facebook's JS library).
+ */
+ public SoundCloudTemplate(String accessToken) {
+ super(accessToken);
+ System.out.println("MY ACCESS TOKEN IS:" + accessToken);
+ initialize(accessToken);
+ }
+
+
+ @Override
+ protected List<HttpMessageConverter<?>> getMessageConverters() {
+ List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
+ messageConverters.add(new ByteArrayHttpMessageConverter());
+ return messageConverters;
+ }
+
+ @Override
+ public UserOperations userOperations() {
+ return userOperations;
+ }
+
+ private void initSubApis(String accessToken) {
+ userOperations = new UserTemplate(getRestTemplate(),accessToken,isAuthorized());
+
+ }
+
+ // private helpers
+ private void initialize(String accessToken) {
+ registerSoundCloudJsonModule(getRestTemplate());
+ getRestTemplate().setErrorHandler(new SoundCloudErrorHandler());
+ // Wrap the request factory with a BufferingClientHttpRequestFactory so
+ // that the error handler can do repeat reads on the response.getBody()
+ super.setRequestFactory(ClientHttpRequestFactorySelector
+ .bufferRequests(getRestTemplate().getRequestFactory()));
+ initSubApis(accessToken);
+ }
+
+ private void registerSoundCloudJsonModule(RestTemplate restTemplate2) {
+ objectMapper = new ObjectMapper();
+ objectMapper.registerModule(new SoundCloudModule());
+ List<HttpMessageConverter<?>> converters = getRestTemplate()
+ .getMessageConverters();
+ for (HttpMessageConverter<?> converter : converters) {
+ if (converter instanceof MappingJacksonHttpMessageConverter) {
+ MappingJacksonHttpMessageConverter jsonConverter = (MappingJacksonHttpMessageConverter) converter;
+ jsonConverter.setObjectMapper(objectMapper);
+ }
+ }
+ }
+
+}
View
29 src/main/java/org/springframework/social/soundcloud/api/impl/UserTemplate.java
@@ -0,0 +1,29 @@
+package org.springframework.social.soundcloud.api.impl;
+
+import org.codehaus.jackson.JsonNode;
+import org.springframework.social.soundcloud.api.SoundCloudProfile;
+import org.springframework.social.soundcloud.api.UserOperations;
+import org.springframework.web.client.RestTemplate;
+
+public class UserTemplate implements UserOperations {
+
+ private final RestTemplate restTemplate;
+
+ private String accessToken;
+
+
+ @Override
+ public SoundCloudProfile getUserProfile() {
+ return restTemplate.getForObject("https://api.soundcloud.com/me?oauth_token=" + accessToken, SoundCloudProfile.class);
+ }
+
+ public UserTemplate(RestTemplate restTemplate, String accessToken, boolean isAuthorizedForUser) {
+ this.restTemplate = restTemplate;
+ this.accessToken = accessToken;
+ }
+
+
+
+
+
+}
View
25 src/main/java/org/springframework/social/soundcloud/api/impl/json/SoundCloudModule.java
@@ -0,0 +1,25 @@
+package org.springframework.social.soundcloud.api.impl.json;
+
+
+import org.codehaus.jackson.Version;
+import org.codehaus.jackson.map.module.SimpleModule;
+import org.springframework.social.soundcloud.api.SoundCloudProfile;
+
+
+/**
+ * Jackson module for setting up mixin annotations on SoundCloud model types. This enables the use of Jackson annotations without
+ * directly annotating the model classes themselves.
+ * @author Michael Lavelle
+ */
+public class SoundCloudModule extends SimpleModule {
+
+ public SoundCloudModule() {
+ super("SoundCloudModule", new Version(1, 0, 0, null));
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+ context.setMixInAnnotations(SoundCloudProfile.class, SoundCloudProfileMixin.class);
+
+ }
+}
View
32 src/main/java/org/springframework/social/soundcloud/api/impl/json/SoundCloudProfileMixin.java
@@ -0,0 +1,32 @@
+package org.springframework.social.soundcloud.api.impl.json;
+
+
+
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Annotated mixin to add Jackson annotations to SoundCloudProfile.
+ * @author Michael Lavelle
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+abstract class SoundCloudProfileMixin {
+
+
+ @JsonCreator
+ SoundCloudProfileMixin(
+ @JsonProperty("id") String id,
+ @JsonProperty("username") String username,
+ @JsonProperty("avatar_url") String avatarUrl,
+ @JsonProperty("permalink_url") String permalinkUrl,
+ @JsonProperty("full_name") String fullName,
+ @JsonProperty("uri") String uri,
+ @JsonProperty("city") String city) {}
+
+
+}
View
50 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudAdapter.java
@@ -0,0 +1,50 @@
+package org.springframework.social.soundcloud.connect;
+
+import org.springframework.social.ApiException;
+import org.springframework.social.connect.ApiAdapter;
+import org.springframework.social.connect.ConnectionValues;
+import org.springframework.social.connect.UserProfile;
+import org.springframework.social.connect.UserProfileBuilder;
+import org.springframework.social.soundcloud.api.SoundCloud;
+import org.springframework.social.soundcloud.api.SoundCloudProfile;
+
+/**
+* SoundCloud ApiAdapter implementation.
+* @author Michael Lavelle
+*/
+public class SoundCloudAdapter implements ApiAdapter<SoundCloud> {
+
+ @Override
+ public UserProfile fetchUserProfile(SoundCloud soundCloud) {
+ SoundCloudProfile profile = soundCloud.userOperations().getUserProfile();
+ return new UserProfileBuilder().setName(profile.getFullName()).setUsername(profile.getUsername()).build();
+
+ }
+
+ @Override
+ public void setConnectionValues(SoundCloud soundCloud, ConnectionValues values) {
+ SoundCloudProfile profile = soundCloud.userOperations().getUserProfile();
+ values.setProviderUserId(profile.getId());
+ values.setDisplayName(profile.getFullName());
+ values.setProfileUrl(profile.getPermalinkUrl());
+ values.setImageUrl(profile.getAvatarUrl());
+ }
+
+
+
+ @Override
+ public boolean test(SoundCloud soundCloud) {
+ try {
+ soundCloud.userOperations().getUserProfile();
+ return true;
+ } catch (ApiException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void updateStatus(SoundCloud soundCloud, String arg1) {
+
+ }
+
+}
View
12 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudConnectionFactory.java
@@ -0,0 +1,12 @@
+package org.springframework.social.soundcloud.connect;
+
+import org.springframework.social.connect.support.OAuth2ConnectionFactory;
+import org.springframework.social.soundcloud.api.SoundCloud;
+
+public class SoundCloudConnectionFactory extends OAuth2ConnectionFactory<SoundCloud> {
+
+ public SoundCloudConnectionFactory(String clientId, String clientSecret) {
+ super("soundcloud", new SoundCloudServiceProvider(clientId, clientSecret), new SoundCloudAdapter());
+ }
+
+}
View
16 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudOAuth2Template.java
@@ -0,0 +1,16 @@
+package org.springframework.social.soundcloud.connect;
+
+import org.springframework.social.oauth2.AccessGrant;
+import org.springframework.social.oauth2.OAuth2Template;
+import org.springframework.util.MultiValueMap;
+
+public class SoundCloudOAuth2Template extends OAuth2Template {
+
+ public SoundCloudOAuth2Template(String clientId, String clientSecret) {
+ super(clientId, clientSecret, "https://soundcloud.com/connect", "https://api.soundcloud.com/oauth2/token");
+ }
+
+
+
+
+}
View
20 src/main/java/org/springframework/social/soundcloud/connect/SoundCloudServiceProvider.java
@@ -0,0 +1,20 @@
+package org.springframework.social.soundcloud.connect;
+
+import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
+import org.springframework.social.soundcloud.api.SoundCloud;
+import org.springframework.social.soundcloud.api.impl.SoundCloudTemplate;
+
+public class SoundCloudServiceProvider extends AbstractOAuth2ServiceProvider<SoundCloud>{
+
+
+
+ public SoundCloudServiceProvider(String clientId, String clientSecret) {
+ super(new SoundCloudOAuth2Template(clientId, clientSecret));
+ }
+
+ @Override
+ public SoundCloud getApi(String accessToken) {
+ return new SoundCloudTemplate(accessToken);
+ }
+
+}
View
101 src/test/java/org/springframework/social/soundcloud/connect/SoundCloudAdapterTest.java
@@ -0,0 +1,101 @@
+package org.springframework.social.soundcloud.connect;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.social.connect.ConnectionValues;
+import org.springframework.social.connect.UserProfile;
+import org.springframework.social.soundcloud.api.SoundCloud;
+import org.springframework.social.soundcloud.api.SoundCloudProfile;
+import org.springframework.social.soundcloud.api.UserOperations;
+
+
+public class SoundCloudAdapterTest {
+
+ private SoundCloudAdapter apiAdapter = new SoundCloudAdapter();
+
+ private SoundCloud soundCloud = Mockito.mock(SoundCloud.class);
+
+ @Test
+ public void fetchProfileForFirstNameLastName() {
+ UserOperations userOperations = Mockito.mock(UserOperations.class);
+ Mockito.when(soundCloud.userOperations()).thenReturn(userOperations);
+ Mockito.when(userOperations.getUserProfile()).thenReturn(new SoundCloudProfile("12345678", "michaellavelle", "http://a1.sndcdn.com/images/default_avatar_large.png?8460df1","http://soundcloud.com/michaellavelle","Michael Lavelle","https://api.soundcloud.com/users/7031365","London"));
+ UserProfile profile = apiAdapter.fetchUserProfile(soundCloud);
+ assertEquals("Michael Lavelle", profile.getName());
+ assertEquals("Michael", profile.getFirstName());
+ assertEquals("Lavelle", profile.getLastName());
+ assertNull(profile.getEmail());
+ assertEquals("michaellavelle", profile.getUsername());
+ }
+
+ @Test
+ public void fetchProfileForOnlyFirstName() {
+ UserOperations userOperations = Mockito.mock(UserOperations.class);
+ Mockito.when(soundCloud.userOperations()).thenReturn(userOperations);
+ Mockito.when(userOperations.getUserProfile()).thenReturn(new SoundCloudProfile("01248", "mattslip", "http://a1.sndcdn.com/images/default_avatar_large.png?8460df1","http://soundcloud.com/mattslip","mattslip","https://api.soundcloud.com/users/3510549","London"));
+ UserProfile profile = apiAdapter.fetchUserProfile(soundCloud);
+ assertEquals("mattslip", profile.getName());
+ assertEquals("mattslip", profile.getFirstName());
+ assertNull(profile.getLastName());
+ assertNull(profile.getEmail());
+ assertEquals("mattslip", profile.getUsername());
+ }
+
+
+ @Test
+ public void setConnectionValues() {
+ UserOperations userOperations = Mockito.mock(UserOperations.class);
+ Mockito.when(soundCloud.userOperations()).thenReturn(userOperations);
+ Mockito.when(userOperations.getUserProfile()).thenReturn(new SoundCloudProfile("12345678", "michaellavelle", "http://a1.sndcdn.com/images/default_avatar_large.png?8460df1","http://soundcloud.com/michaellavelle","Michael Lavelle","https://api.soundcloud.com/users/7031365","London"));
+ TestConnectionValues connectionValues = new TestConnectionValues();
+ apiAdapter.setConnectionValues(soundCloud, connectionValues);
+ assertEquals("Michael Lavelle", connectionValues.getDisplayName());
+ assertEquals("http://a1.sndcdn.com/images/default_avatar_large.png?8460df1", connectionValues.getImageUrl());
+ assertEquals("http://soundcloud.com/michaellavelle", connectionValues.getProfileUrl());
+ assertEquals("12345678", connectionValues.getProviderUserId());
+ }
+
+ private static class TestConnectionValues implements ConnectionValues {
+
+ private String displayName;
+ private String imageUrl;
+ private String profileUrl;
+ private String providerUserId;
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
+ }
+
+ public String getProfileUrl() {
+ return profileUrl;
+ }
+
+ public void setProfileUrl(String profileUrl) {
+ this.profileUrl = profileUrl;
+ }
+
+ public String getProviderUserId() {
+ return providerUserId;
+ }
+
+ public void setProviderUserId(String providerUserId) {
+ this.providerUserId = providerUserId;
+ }
+
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.