STREAMS-122 | Updated the InstagramActivityUtil class to fully map Instagram MediaFeedData objects to Activities. Updated tests so that this deserialization and mapping can be tested
Project: http://git-wip-us.apache.org/repos/asf/incubator-streams/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-streams/commit/11636535 Tree: http://git-wip-us.apache.org/repos/asf/incubator-streams/tree/11636535 Diff: http://git-wip-us.apache.org/repos/asf/incubator-streams/diff/11636535 Branch: refs/heads/STREAMS-46 Commit: 116365355dad985da52092c85378b5bd7497e907 Parents: 7b301ce Author: Robert Douglas <[email protected]> Authored: Tue Jul 1 17:12:57 2014 -0500 Committer: Robert Douglas <[email protected]> Committed: Tue Jul 1 17:12:57 2014 -0500 ---------------------------------------------------------------------- streams-contrib/pom.xml | 1 + .../InstagramJsonActivitySerializer.java | 22 ++- .../serializer/util/InstagramActivityUtil.java | 170 ++++++++++++++++--- .../test/InstagramActivitySerDeTest.java | 24 ++- .../src/test/resources/testMediaFeedObjects.txt | 2 + 5 files changed, 181 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-streams/blob/11636535/streams-contrib/pom.xml ---------------------------------------------------------------------- diff --git a/streams-contrib/pom.xml b/streams-contrib/pom.xml index 620f68e..699274e 100644 --- a/streams-contrib/pom.xml +++ b/streams-contrib/pom.xml @@ -47,6 +47,7 @@ <module>streams-amazon-aws</module> <!--<module>streams-processor-lucene</module>--> <!--<module>streams-processor-tika</module>--> + <module>streams-provider-instagram</module> <module>streams-processor-json</module> <module>streams-processor-urls</module> <module>streams-provider-datasift</module> http://git-wip-us.apache.org/repos/asf/incubator-streams/blob/11636535/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/InstagramJsonActivitySerializer.java ---------------------------------------------------------------------- diff --git a/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/InstagramJsonActivitySerializer.java b/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/InstagramJsonActivitySerializer.java index 8d92641..c5bbdf1 100644 --- a/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/InstagramJsonActivitySerializer.java +++ b/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/InstagramJsonActivitySerializer.java @@ -18,14 +18,21 @@ package org.apache.streams.instagram.serializer; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.NotImplementedException; import org.apache.streams.data.ActivitySerializer; import org.apache.streams.exceptions.ActivitySerializerException; +import org.apache.streams.jackson.StreamsJacksonMapper; import org.apache.streams.pojo.json.Activity; +import org.jinstagram.entity.users.feed.MediaFeedData; +import java.io.IOException; import java.io.Serializable; import java.util.List; +import static org.apache.streams.instagram.serializer.util.InstagramActivityUtil.updateActivity; + public class InstagramJsonActivitySerializer implements ActivitySerializer<String>, Serializable { @@ -46,9 +53,20 @@ public class InstagramJsonActivitySerializer implements ActivitySerializer<Strin @Override public Activity deserialize(String serialized) throws ActivitySerializerException { - Activity activity = null; + ObjectMapper mapper = StreamsJacksonMapper.getInstance(); + MediaFeedData mediaFeedData = null; + + try { + mediaFeedData = mapper.readValue(serialized, MediaFeedData.class); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + Activity activity = new Activity(); - // implement + updateActivity(mediaFeedData, activity); return activity; } http://git-wip-us.apache.org/repos/asf/incubator-streams/blob/11636535/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/util/InstagramActivityUtil.java ---------------------------------------------------------------------- diff --git a/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/util/InstagramActivityUtil.java b/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/util/InstagramActivityUtil.java index e71c43e..0561ba7 100644 --- a/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/util/InstagramActivityUtil.java +++ b/streams-contrib/streams-provider-instagram/src/main/java/org/apache/streams/instagram/serializer/util/InstagramActivityUtil.java @@ -19,16 +19,21 @@ package org.apache.streams.instagram.serializer.util; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Joiner; +import com.google.common.base.Optional; import com.google.common.collect.Lists; import org.apache.streams.exceptions.ActivitySerializerException; -import org.apache.streams.pojo.json.Activity; -import org.apache.streams.pojo.json.ActivityObject; -import org.apache.streams.pojo.json.Actor; -import org.apache.streams.pojo.json.Provider; +import org.apache.streams.pojo.json.*; +import org.jinstagram.entity.common.ImageData; +import org.jinstagram.entity.common.Images; +import org.jinstagram.entity.common.VideoData; +import org.jinstagram.entity.common.Videos; import org.jinstagram.entity.users.feed.MediaFeedData; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,7 +44,7 @@ import static org.apache.streams.data.util.ActivityUtil.ensureExtensions; * Provides utilities for working with Activity objects within the context of Instagram */ public class InstagramActivityUtil { - + private static final Logger LOGGER = LoggerFactory.getLogger(InstagramActivityUtil.class); /** * Updates the given Activity object with the values from the item * @param item the object to use as the source @@ -47,7 +52,22 @@ public class InstagramActivityUtil { * @throws ActivitySerializerException */ public static void updateActivity(MediaFeedData item, Activity activity) throws ActivitySerializerException { + activity.setActor(buildActor(item)); + activity.setPublished(new DateTime(Long.parseLong(item.getCreatedTime()) * 1000)); + + activity.setId(formatId(activity.getVerb(), + Optional.fromNullable( + item.getId()) + .orNull())); + + activity.setProvider(getProvider()); + activity.setUrl(item.getLink()); + activity.setObject(buildActivityObject(item)); + if(item.getCaption() != null) + activity.setContent(item.getCaption().getText()); + + addInstagramExtensions(activity, item); } /** @@ -57,6 +77,18 @@ public class InstagramActivityUtil { */ public static Actor buildActor(MediaFeedData item) { Actor actor = new Actor(); + + Image image = new Image(); + image.setUrl(item.getUser().getProfilePictureUrl()); + + Map<String, Object> extensions = new HashMap<String, Object>(); + extensions.put("screenName", item.getUser().getUserName()); + + actor.setId(formatId(String.valueOf(item.getUser().getId()))); + actor.setImage(image); + actor.setAdditionalProperty("extensions", extensions); + actor.setAdditionalProperty("handle", item.getUser().getUserName()); + return actor; } @@ -67,18 +99,90 @@ public class InstagramActivityUtil { */ public static ActivityObject buildActivityObject(MediaFeedData item) { ActivityObject actObj = new ActivityObject(); + + actObj.setObjectType(item.getType()); + actObj.setAttachments(buildActivityObjectAttachments(item)); + return actObj; } + /** + * Builds all of the attachments associated with a MediaFeedData object + * + * @param item + * @return + */ + public static List<ActivityObject> buildActivityObjectAttachments(MediaFeedData item) { + List<ActivityObject> attachments = new ArrayList<ActivityObject>(); + + addImageObjects(attachments, item); + addVideoObjects(attachments, item); + + return attachments; + } /** - * Updates the content, and associated fields, with those from the given tweet - * @param activity the target of the updates. Will receive all values from the tweet. - * @param item the object to use as the source - * @param verb the verb for the given activity's type + * Adds any image objects to the attachment field + * @param attachments + * @param item + */ + public static void addImageObjects(List<ActivityObject> attachments, MediaFeedData item) { + Images images = item.getImages(); + + if(images != null) { + try { + ImageData thumbnail = images.getThumbnail(); + ImageData lowResolution = images.getLowResolution(); + + ActivityObject thumbnailObject = new ActivityObject(); + Image thumbnailImage = new Image(); + thumbnailImage.setUrl(thumbnail.getImageUrl()); + thumbnailImage.setHeight((double) thumbnail.getImageHeight()); + thumbnailImage.setWidth((double) thumbnail.getImageWidth()); + thumbnailObject.setImage(thumbnailImage); + thumbnailObject.setObjectType("image"); + + ActivityObject lowResolutionObject = new ActivityObject(); + Image lowResolutionImage = new Image(); + lowResolutionImage.setUrl(lowResolution.getImageUrl()); + lowResolutionImage.setHeight((double) lowResolution.getImageHeight()); + lowResolutionImage.setWidth((double) lowResolution.getImageWidth()); + lowResolutionObject.setImage(lowResolutionImage); + lowResolutionObject.setObjectType("image"); + + attachments.add(thumbnailObject); + attachments.add(lowResolutionObject); + } catch (Exception e) { + LOGGER.error("Failed to add image objects: {}", e.getMessage()); + } + } + } + + /** + * Adds any video objects to the attachment field + * @param attachments + * @param item */ - public static void updateActivityContent(Activity activity, MediaFeedData item, String verb) { + public static void addVideoObjects(List<ActivityObject> attachments, MediaFeedData item) { + Videos videos = item.getVideos(); + + if(videos != null) { + try { + VideoData lowResolutionVideo = videos.getLowResolution(); + + ActivityObject lowResolutionVideoObject = new ActivityObject(); + Image lowResolutionVideoImage = new Image(); + lowResolutionVideoImage.setUrl(lowResolutionVideo.getUrl()); + lowResolutionVideoImage.setHeight((double) lowResolutionVideo.getHeight()); + lowResolutionVideoImage.setWidth((double) lowResolutionVideo.getWidth()); + lowResolutionVideoObject.setImage(lowResolutionVideoImage); + lowResolutionVideoObject.setObjectType("video"); + attachments.add(lowResolutionVideoObject); + } catch (Exception e) { + LOGGER.error("Failed to add video objects: {}", e.getMessage()); + } + } } /** @@ -98,7 +202,14 @@ public class InstagramActivityUtil { */ public static void addLocationExtension(Activity activity, MediaFeedData item) { Map<String, Object> extensions = ensureExtensions(activity); - Map<String, Object> location = new HashMap<String, Object>(); + + if(item.getLocation() != null) { + Map<String, Object> coordinates = new HashMap<String, Object>(); + coordinates.put("type", "Point"); + coordinates.put("coordinates", "[" + item.getLocation().getLatitude() + "," + item.getLocation().getLongitude() + "]"); + + extensions.put("coordinates", coordinates); + } } @@ -112,15 +223,7 @@ public class InstagramActivityUtil { provider.setDisplayName("Instagram"); return provider; } - /** - * Adds the given Instagram event to the activity as an extension - * @param activity the Activity object to update - * @param event the Instagram event to add as the extension - */ - public static void addInstagramExtension(Activity activity, ObjectNode event) { - Map<String, Object> extensions = org.apache.streams.data.util.ActivityUtil.ensureExtensions(activity); - extensions.put("instagram", event); - } + /** * Formats the ID to conform with the Apache Streams activity ID convention * @param idparts the parts of the ID to join @@ -138,5 +241,28 @@ public class InstagramActivityUtil { */ public static void addInstagramExtensions(Activity activity, MediaFeedData item) { Map<String, Object> extensions = ensureExtensions(activity); + + addLocationExtension(activity, item); + + Map<String, Object> likes = new HashMap<String, Object>(); + likes.put("count", item.getLikes().getCount()); + extensions.put("likes", likes); + + extensions.put("hashtags", item.getTags()); + + Image standardResolution = new Image(); + if(item.getType() == "image" && item.getImages() != null) { + ImageData standardResolutionData = item.getImages().getStandardResolution(); + standardResolution.setHeight((double)standardResolutionData.getImageHeight()); + standardResolution.setWidth((double)standardResolutionData.getImageWidth()); + standardResolution.setUrl(standardResolutionData.getImageUrl()); + } else if(item.getType() == "video" && item.getVideos() != null) { + VideoData standardResolutionData = item.getVideos().getStandardResolution(); + standardResolution.setHeight((double)standardResolutionData.getHeight()); + standardResolution.setWidth((double)standardResolutionData.getWidth()); + standardResolution.setUrl(standardResolutionData.getUrl()); + } + + extensions.put("image", standardResolution); } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-streams/blob/11636535/streams-contrib/streams-provider-instagram/src/test/java/org/apache/streams/twitter/test/InstagramActivitySerDeTest.java ---------------------------------------------------------------------- diff --git a/streams-contrib/streams-provider-instagram/src/test/java/org/apache/streams/twitter/test/InstagramActivitySerDeTest.java b/streams-contrib/streams-provider-instagram/src/test/java/org/apache/streams/twitter/test/InstagramActivitySerDeTest.java index fcf5e81..075da80 100644 --- a/streams-contrib/streams-provider-instagram/src/test/java/org/apache/streams/twitter/test/InstagramActivitySerDeTest.java +++ b/streams-contrib/streams-provider-instagram/src/test/java/org/apache/streams/twitter/test/InstagramActivitySerDeTest.java @@ -20,11 +20,12 @@ package org.apache.streams.twitter.test; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.StringUtils; +import org.apache.streams.instagram.serializer.util.InstagramDeserializer; import org.apache.streams.instagram.serializer.InstagramJsonActivitySerializer; import org.apache.streams.jackson.StreamsJacksonMapper; import org.apache.streams.pojo.json.Activity; +import org.jinstagram.entity.users.feed.MediaFeedData; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +34,7 @@ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; +import static org.apache.streams.instagram.serializer.util.InstagramActivityUtil.updateActivity; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; @@ -46,16 +48,11 @@ import static org.junit.Assert.assertThat; public class InstagramActivitySerDeTest { private final static Logger LOGGER = LoggerFactory.getLogger(InstagramActivitySerDeTest.class); - private ObjectMapper mapper = StreamsJacksonMapper.getInstance(); - private InstagramJsonActivitySerializer instagramJsonActivitySerializer = new InstagramJsonActivitySerializer(); - - // remove @Ignore after implementation - @Ignore @Test - public void Tests() - { - InputStream is = InstagramActivitySerDeTest.class.getResourceAsStream("/test.txt"); + public void Tests() { + InstagramDeserializer instagramDeserializer = new InstagramDeserializer(""); + InputStream is = InstagramActivitySerDeTest.class.getResourceAsStream("/testMediaFeedObjects.txt"); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); @@ -66,13 +63,13 @@ public class InstagramActivitySerDeTest { { LOGGER.info("raw: {}", line); - // convert to MediaFeedData? - Activity activity = instagramJsonActivitySerializer.deserialize(line); + MediaFeedData mediaFeedData = instagramDeserializer.createObjectFromResponse(MediaFeedData.class, line); - String activitystring = mapper.writeValueAsString(activity); + Activity activity = new Activity(); - LOGGER.info("activity: {}", activitystring); + LOGGER.info("activity: {}", activity.toString()); + updateActivity(mediaFeedData, activity); assertThat(activity, is(not(nullValue()))); assertThat(activity.getId(), is(not(nullValue()))); @@ -80,7 +77,6 @@ public class InstagramActivitySerDeTest { assertThat(activity.getActor().getId(), is(not(nullValue()))); assertThat(activity.getVerb(), is(not(nullValue()))); assertThat(activity.getProvider(), is(not(nullValue()))); - } } } catch( Exception e ) { http://git-wip-us.apache.org/repos/asf/incubator-streams/blob/11636535/streams-contrib/streams-provider-instagram/src/test/resources/testMediaFeedObjects.txt ---------------------------------------------------------------------- diff --git a/streams-contrib/streams-provider-instagram/src/test/resources/testMediaFeedObjects.txt b/streams-contrib/streams-provider-instagram/src/test/resources/testMediaFeedObjects.txt index e69de29..b62e599 100644 --- a/streams-contrib/streams-provider-instagram/src/test/resources/testMediaFeedObjects.txt +++ b/streams-contrib/streams-provider-instagram/src/test/resources/testMediaFeedObjects.txt @@ -0,0 +1,2 @@ +{ "attribution":null, "tags":[ ], "type":"image", "location":null, "comments":{ "count":0, "data":[ ] }, "filter":"X-Pro II", "created_time":"1404162054", "link":"http://instagram.com/p/p4ez__syJi/", "likes":{ "count":0, "data":[ ] }, "images":{ "low_resolution":{ "url":"http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10518155_283164271863608_1534480525_a.jpg", "width":306, "height":306 }, "thumbnail":{ "url":"http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10518155_283164271863608_1534480525_s.jpg", "width":150, "height":150 }, "standard_resolution":{ "url":"http://scontent-a.cdninstagram.com/hphotos-xpa1/t51.2885-15/10518155_283164271863608_1534480525_n.jpg", "width":640, "height":640 } }, "users_in_photo":[ ], "caption":{ "created_time":"1404162054", "text":"Testing streams", "from":{ "username":"ktsafford", "profile_picture":"http://images.ak.instagram.com/profiles/anonymousUser.jpg", "id":"1412068271", "full_name":"ktsafford" }, "id":"754488452958068751" }, "user_has_liked":false, "id":"754488452387644002_1412068271", "user":{ "username":"ktsafford", "website":"", "profile_picture":"http://images.ak.instagram.com/profiles/anonymousUser.jpg", "full_name":"ktsafford", "bio":"", "id":"1412068271" } } +{ "type":"image", "users_in_photo":[ { "user":{ "username":"kevin", "full_name":"Kevin S", "id":"3", "profile_picture":"..." }, "position":{ "x":0.315, "y":0.9111 } } ], "filter":"Walden", "tags":[ ], "comments":{ "data":[ { "created_time":"1279332030", "text":"Love the sign here", "from":{ "username":"mikeyk", "full_name":"Mikey Krieger", "id":"4", "profile_picture":"http://distillery.s3.amazonaws.com/profiles/profile_1242695_75sq_1293915800.jpg" }, "id":"8" }, { "created_time":"1279341004", "text":"Chilako taco", "from":{ "username":"kevin", "full_name":"Kevin S", "id":"3", "profile_picture":"..." }, "id":"3" } ], "count":2 }, "caption":null, "likes":{ "count":1, "data":[ { "username":"mikeyk", "full_name":"Mikeyk", "id":"4", "profile_picture":"..." } ] }, "link":"http://instagr.am/p/D/", "user":{ "username":"kevin", "full_name":"Kevin S", "profile_picture":"...", "bio":"...", "website":"...", "id":"3" }, "created_time":"1279340983", "images":{ "low_resolution":{ "url":"http://dis tillery.s3.amazonaws.com/media/2010/07/16/4de37e03aa4b4372843a7eb33fa41cad_6.jpg", "width":306, "height":306 }, "thumbnail":{ "url":"http://distillery.s3.amazonaws.com/media/2010/07/16/4de37e03aa4b4372843a7eb33fa41cad_5.jpg", "width":150, "height":150 }, "standard_resolution":{ "url":"http://distillery.s3.amazonaws.com/media/2010/07/16/4de37e03aa4b4372843a7eb33fa41cad_7.jpg", "width":612, "height":612 } }, "id":"3", "location":null }
