Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

adds first cut of support for activitystrea.ms protocol and associate…

…d REST endpoint
  • Loading branch information...
commit 7e14c53e391d85f21e28ccc684b5ecd3d6bae542 1 parent ac70c24
Phillip Rhodes mindcrime authored

Showing 26 changed files with 615 additions and 75 deletions. Show diff stats Hide diff stats

  1. +79 1 .classpath
  2. +1 1  .project
  3. +5 0 .settings/org.eclipse.wst.common.component
  4. +2 2 grails-app/conf/Config.groovy
  5. +3 1 grails-app/conf/DataSource.groovy
  6. +63 8 grails-app/controllers/org/fogbeam/quoddy/ActivityStreamController.groovy
  7. +9 0 grails-app/controllers/org/fogbeam/quoddy/SpecialController.groovy
  8. +6 6 grails-app/controllers/org/fogbeam/quoddy/StatusController.groovy
  9. +234 4 grails-app/domain/org/fogbeam/quoddy/Activity.groovy
  10. +22 9 grails-app/services/org/fogbeam/quoddy/ActivityStreamService.groovy
  11. +36 39 grails-app/services/org/fogbeam/quoddy/EventQueueService.groovy
  12. +56 0 grails-app/services/org/fogbeam/quoddy/transformer/ActivityStreamTransformerService.groovy
  13. +4 4 grails-app/views/_activityStream.gsp
  14. +18 0 grails-app/views/special/post_as.gsp
  15. BIN  lib/arq-2.8.7.jar
  16. BIN  lib/iri-0.8.jar
  17. BIN  lib/jackson-all-1.9.3.jar
  18. BIN  lib/jena-2.6.4.jar
  19. BIN  lib/stax-api-1.0.1.jar
  20. BIN  lib/wstx-asl-3.2.9.jar
  21. 0  src/groovy/groovy.txt
  22. +20 0 src/groovy/org/fogbeam/quoddy/integration/activitystream/ActivityStreamEntry.groovy
  23. +18 0 src/groovy/org/fogbeam/quoddy/integration/activitystream/Actor.groovy
  24. +13 0 src/groovy/org/fogbeam/quoddy/integration/activitystream/Image.groovy
  25. +12 0 src/groovy/org/fogbeam/quoddy/integration/activitystream/Object.groovy
  26. +14 0 src/groovy/org/fogbeam/quoddy/integration/activitystream/Target.groovy
80 .classpath
@@ -8,6 +8,7 @@
8 8 <classpathentry kind="src" path="grails-app/ldap"/>
9 9 <classpathentry kind="src" path="test/unit"/>
10 10 <classpathentry kind="src" path="test/integration"/>
  11 + <classpathentry kind="src" path="src/groovy"/>
11 12 <classpathentry kind="lib" path="lib/activation.jar"/>
12 13 <classpathentry kind="lib" path="lib/commons-math-2.1.jar"/>
13 14 <classpathentry kind="lib" path="lib/lucene-core-3.0.1.jar"/>
@@ -17,7 +18,6 @@
17 18 <classpathentry kind="lib" path="lib/commons-lang-2.5.jar"/>
18 19 <classpathentry kind="lib" path="lib/commons-pool-1.5.4.jar"/>
19 20 <classpathentry kind="lib" path="lib/ldapbp-1.0.jar"/>
20   - <classpathentry kind="lib" path="lib/log4j-1.2.9.jar"/>
21 21 <classpathentry kind="lib" path="lib/org.springframework.asm-3.0.5.RELEASE.jar"/>
22 22 <classpathentry kind="lib" path="lib/org.springframework.expression-3.0.5.RELEASE.jar"/>
23 23 <classpathentry kind="lib" path="lib/spring-beans-3.0.5.RELEASE.jar"/>
@@ -85,6 +85,26 @@
85 85 <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
86 86 </attributes>
87 87 </classpathentry>
  88 + <classpathentry kind="src" path=".link_to_grails_plugins/jms-1.2/grails-app/utils">
  89 + <attributes>
  90 + <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
  91 + </attributes>
  92 + </classpathentry>
  93 + <classpathentry kind="src" path=".link_to_grails_plugins/jms-1.2/grails-app/services">
  94 + <attributes>
  95 + <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
  96 + </attributes>
  97 + </classpathentry>
  98 + <classpathentry kind="src" path=".link_to_grails_plugins/jms-1.2/src/java">
  99 + <attributes>
  100 + <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
  101 + </attributes>
  102 + </classpathentry>
  103 + <classpathentry kind="src" path=".link_to_grails_plugins/jms-1.2/src/groovy">
  104 + <attributes>
  105 + <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
  106 + </attributes>
  107 + </classpathentry>
88 108 <classpathentry kind="src" path=".link_to_grails_plugins/code-coverage-1.2/grails-app/i18n">
89 109 <attributes>
90 110 <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
@@ -120,5 +140,63 @@
120 140 <attribute name="com.springsource.sts.grails.core.SOURCE_FOLDER" value="true"/>
121 141 </attributes>
122 142 </classpathentry>
  143 + <classpathentry kind="lib" path="/usr/java/junit3.8.1/junit.jar"/>
  144 + <classpathentry kind="lib" path="lib/arq-2.8.7.jar"/>
  145 + <classpathentry kind="lib" path="lib/concurrent.jar"/>
  146 + <classpathentry kind="lib" path="lib/iri-0.8.jar"/>
  147 + <classpathentry kind="lib" path="lib/jboss-aop-client.jar"/>
  148 + <classpathentry kind="lib" path="lib/jboss-appclient.jar"/>
  149 + <classpathentry kind="lib" path="lib/jboss-aspect-jdk50-client.jar"/>
  150 + <classpathentry kind="lib" path="lib/jboss-client.jar"/>
  151 + <classpathentry kind="lib" path="lib/jboss-common-core.jar"/>
  152 + <classpathentry kind="lib" path="lib/jboss-deployers-client-spi.jar"/>
  153 + <classpathentry kind="lib" path="lib/jboss-deployers-client.jar"/>
  154 + <classpathentry kind="lib" path="lib/jboss-deployers-core-spi.jar"/>
  155 + <classpathentry kind="lib" path="lib/jboss-deployers-core.jar"/>
  156 + <classpathentry kind="lib" path="lib/jboss-deployment.jar"/>
  157 + <classpathentry kind="lib" path="lib/jboss-ejb3-common-client.jar"/>
  158 + <classpathentry kind="lib" path="lib/jboss-ejb3-core-client.jar"/>
  159 + <classpathentry kind="lib" path="lib/jboss-ejb3-ext-api.jar"/>
  160 + <classpathentry kind="lib" path="lib/jboss-ejb3-proxy-clustered-client.jar"/>
  161 + <classpathentry kind="lib" path="lib/jboss-ejb3-proxy-impl-client.jar"/>
  162 + <classpathentry kind="lib" path="lib/jboss-ejb3-proxy-spi-client.jar"/>
  163 + <classpathentry kind="lib" path="lib/jboss-ejb3-security-client.jar"/>
  164 + <classpathentry kind="lib" path="lib/jboss-ha-client.jar"/>
  165 + <classpathentry kind="lib" path="lib/jboss-ha-legacy-client.jar"/>
  166 + <classpathentry kind="lib" path="lib/jboss-iiop-client.jar"/>
  167 + <classpathentry kind="lib" path="lib/jboss-integration.jar"/>
  168 + <classpathentry kind="lib" path="lib/jboss-j2se.jar"/>
  169 + <classpathentry kind="lib" path="lib/jboss-javaee.jar"/>
  170 + <classpathentry kind="lib" path="lib/jboss-jsr77-client.jar"/>
  171 + <classpathentry kind="lib" path="lib/jboss-logging-jdk.jar"/>
  172 + <classpathentry kind="lib" path="lib/jboss-logging-log4j.jar"/>
  173 + <classpathentry kind="lib" path="lib/jboss-logging-spi.jar"/>
  174 + <classpathentry kind="lib" path="lib/jboss-main-client.jar"/>
  175 + <classpathentry kind="lib" path="lib/jboss-mdr.jar"/>
  176 + <classpathentry kind="lib" path="lib/jboss-messaging-client.jar"/>
  177 + <classpathentry kind="lib" path="lib/jboss-remoting.jar"/>
  178 + <classpathentry kind="lib" path="lib/jboss-security-spi.jar"/>
  179 + <classpathentry kind="lib" path="lib/jboss-serialization.jar"/>
  180 + <classpathentry kind="lib" path="lib/jboss-srp-client.jar"/>
  181 + <classpathentry kind="lib" path="lib/jboss-system-client.jar"/>
  182 + <classpathentry kind="lib" path="lib/jboss-system-jmx-client.jar"/>
  183 + <classpathentry kind="lib" path="lib/jbosscx-client.jar"/>
  184 + <classpathentry kind="lib" path="lib/jbossjts-integration.jar"/>
  185 + <classpathentry kind="lib" path="lib/jbossjts.jar"/>
  186 + <classpathentry kind="lib" path="lib/jbosssx-as-client.jar"/>
  187 + <classpathentry kind="lib" path="lib/jbosssx-client.jar"/>
  188 + <classpathentry kind="lib" path="lib/jena-2.6.4.jar"/>
  189 + <classpathentry kind="lib" path="lib/jmx-client.jar"/>
  190 + <classpathentry kind="lib" path="lib/jmx-invoker-adaptor-client.jar"/>
  191 + <classpathentry kind="lib" path="lib/jnp-client.jar"/>
  192 + <classpathentry kind="lib" path="lib/log4j-1.2.16.jar"/>
  193 + <classpathentry kind="lib" path="lib/sample.jar"/>
  194 + <classpathentry kind="lib" path="lib/shiro-core-1.1.0.jar"/>
  195 + <classpathentry kind="lib" path="lib/shiro-web-1.1.0.jar"/>
  196 + <classpathentry kind="lib" path="lib/stax-api-1.0.1.jar"/>
  197 + <classpathentry kind="lib" path="lib/trove.jar"/>
  198 + <classpathentry kind="lib" path="lib/wstx-asl-3.2.9.jar"/>
  199 + <classpathentry kind="lib" path="lib/xercesImpl-2.9.1.jar"/>
  200 + <classpathentry kind="lib" path="lib/jackson-all-1.9.3.jar"/>
123 201 <classpathentry kind="output" path="bin"/>
124 202 </classpath>
2  .project
@@ -32,7 +32,7 @@
32 32 <link>
33 33 <name>.link_to_grails_plugins</name>
34 34 <type>2</type>
35   - <locationURI>GRAILS_ROOT/1.3.6/projects/quoddy2/plugins</locationURI>
  35 + <locationURI>GRAILS_ROOT/1.3.7.BUILD-SNAPSHOT/projects/quoddy2/plugins</locationURI>
36 36 </link>
37 37 </linkedResources>
38 38 </projectDescription>
5 .settings/org.eclipse.wst.common.component
@@ -5,5 +5,10 @@
5 5 <wb-resource deploy-path="/" source-path="/grails-app/listeners"/>
6 6 <wb-resource deploy-path="/" source-path="/test/unit"/>
7 7 <wb-resource deploy-path="/" source-path="/test/integration"/>
  8 + <wb-resource deploy-path="/" source-path="/.link_to_grails_plugins/jms-1.2/grails-app/utils"/>
  9 + <wb-resource deploy-path="/" source-path="/.link_to_grails_plugins/jms-1.2/grails-app/services"/>
  10 + <wb-resource deploy-path="/" source-path="/.link_to_grails_plugins/jms-1.2/src/java"/>
  11 + <wb-resource deploy-path="/" source-path="/.link_to_grails_plugins/jms-1.2/src/groovy"/>
  12 + <wb-resource deploy-path="/" source-path="/src/groovy"/>
8 13 </wb-module>
9 14 </project-modules>
4 grails-app/conf/Config.groovy
@@ -50,7 +50,7 @@ grails.project.groupId = appName // change this to alter the default package nam
50 50 grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
51 51 grails.mime.use.accept.header = false
52 52 grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
53   - xml: ['text/xml', 'application/xml'],
  53 + // xml: ['text/xml', 'application/xml'],
54 54 text: 'text/plain',
55 55 js: 'text/javascript',
56 56 rss: 'application/rss+xml',
@@ -58,7 +58,7 @@ grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
58 58 css: 'text/css',
59 59 csv: 'text/csv',
60 60 all: '*/*',
61   - json: ['application/json','text/json'],
  61 + // json: ['application/json','text/json'],
62 62 form: 'application/x-www-form-urlencoded',
63 63 multipartForm: 'multipart/form-data'
64 64 ]
4 grails-app/conf/DataSource.groovy
@@ -5,8 +5,8 @@ dataSource {
5 5 driverClassName = "org.postgresql.Driver"
6 6 // username = "sa"
7 7 username = "postgres"
8   - // password = ""
9 8 password = ""
  9 + // password = ""
10 10 logSql=false
11 11 }
12 12 hibernate {
@@ -20,6 +20,8 @@ environments {
20 20 dataSource {
21 21 dbCreate = "update" // one of 'create', 'create-drop','update'
22 22 url = "jdbc:postgresql:quoddy2";
  23 + // dbCreate = "create-drop"
  24 + // url = "jdbc:hsqldb:mem:devDb
23 25 }
24 26 }
25 27 test {
71 grails-app/controllers/org/fogbeam/quoddy/ActivityStreamController.groovy
... ... @@ -1,9 +1,13 @@
1 1 package org.fogbeam.quoddy
2 2
  3 +import org.codehaus.jackson.map.ObjectMapper
  4 +import org.fogbeam.quoddy.integration.activitystream.ActivityStreamEntry
  5 +
3 6
4 7 class ActivityStreamController
5 8 {
6 9 def activityStreamService;
  10 + def activityStreamTransformerService;
7 11 def userService;
8 12 def jmsService;
9 13 def eventQueueService;
@@ -72,6 +76,7 @@ class ActivityStreamController
72 76 else
73 77 {
74 78 println "logged in; so proceeding...";
  79 + // def originTime = new Date().getTime();
75 80
76 81 // get our user
77 82 user = userService.findUserByUserId( session.user.userId );
@@ -105,16 +110,20 @@ class ActivityStreamController
105 110
106 111 session.user = user;
107 112
108   - def originTime = new Date().getTime();
109   - Activity activity = new Activity(text:newStatus.text);
110   - activity.creator = user;
111   - activity.originTime = originTime;
  113 +
  114 + Activity activity = new Activity(content:newStatus.text);
  115 + activity.title = "Internal Activity";
  116 + activity.url = new URL( "http://www.example.com" );
  117 + activity.verb = "status_update";
  118 + activity.userActor = user;
  119 + activity.published = new Date(); // set published to "now"
112 120 activityStreamService.saveActivity( activity );
113 121
114 122 Map msg = new HashMap();
115   - msg.creator = activity.creator.userId;
  123 + msg.creator = activity.userActor.userId;
116 124 msg.text = newStatus.text;
117   - msg.originTime = originTime;
  125 + // msg.published = activity.published;
  126 + msg.originTime = activity.dateCreated.time;
118 127
119 128 println "sending message to JMS";
120 129 jmsService.send( queue: 'uitestActivityQueue', msg, 'standard', null );
@@ -124,5 +133,51 @@ class ActivityStreamController
124 133 println( "returning status 200" );
125 134 render( "OK");
126 135
127   - }
128   -}
  136 + }
  137 +
  138 + def index = {
  139 +
  140 + switch(request.method){
  141 + case "POST":
  142 + // def originTime = new Date().getTime();
  143 + println "Create\n"
  144 + // String json = request.reader.text;
  145 + String json = params.activityJson;
  146 + println("Got json:\n " + json );
  147 +
  148 + ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
  149 +
  150 + // convert from JSON to Groovy classes
  151 + ActivityStreamEntry streamEntry = mapper.readValue(json, ActivityStreamEntry.class);
  152 +
  153 + // map to our internal representation and save / msg
  154 + Activity activity = activityStreamTransformerService.getActivity( streamEntry );
  155 + activityStreamService.saveActivity( activity );
  156 +
  157 + // send notification message
  158 + Map msg = new HashMap();
  159 + msg.creator = activity.userActor.userId;
  160 + msg.text = activity.content;
  161 + // msg.published = activity.published;
  162 + msg.originTime = activity.dateCreated.time;
  163 +
  164 + println "sending message to JMS";
  165 + jmsService.send( queue: 'uitestActivityQueue', msg, 'standard', null );
  166 +
  167 + // println streamEntry.toString();
  168 +
  169 + break
  170 + case "GET":
  171 + println "Retrieve\n"
  172 + break
  173 + case "PUT":
  174 + println "Update\n"
  175 + break
  176 + case "DELETE":
  177 + println "Delete\n"
  178 + break
  179 + }
  180 +
  181 + render "OK";
  182 + }
  183 +}
9 grails-app/controllers/org/fogbeam/quoddy/SpecialController.groovy
... ... @@ -0,0 +1,9 @@
  1 +package org.fogbeam.quoddy
  2 +
  3 +class SpecialController
  4 +{
  5 + def post_as = {
  6 +
  7 + [];
  8 + }
  9 +}
12 grails-app/controllers/org/fogbeam/quoddy/StatusController.groovy
... ... @@ -1,4 +1,4 @@
1   -package org.fogbeam.quoddy
  1 +package org.fogbeam.quoddy;
2 2
3 3 import org.fogbeam.quoddy.Activity;
4 4 import org.fogbeam.quoddy.StatusUpdate;
@@ -14,11 +14,11 @@ class StatusController {
14 14
15 15 User user = null;
16 16
17   - if( !session.user )
  17 + if( !session.user )
18 18 {
19 19 flash.message = "Must be logged in before updating status";
20 20 }
21   - else
  21 + else
22 22 {
23 23 println "logged in; so proceeding...";
24 24
@@ -49,7 +49,7 @@ class StatusController {
49 49 }
50 50 else
51 51 {
52   - // handle failure to update User
  52 + // handle failure to update User
53 53 }
54 54
55 55 session.user = user;
@@ -76,7 +76,7 @@ class StatusController {
76 76 redirect( controller:"home", action:"index", params:[userId:user.userId]);
77 77 }
78 78
79   - def listUpdates =
  79 + def listUpdates =
80 80 {
81 81 User user = null;
82 82 List<StatusUpdate> updates = new ArrayList<StatusUpdate>();
@@ -96,6 +96,6 @@ class StatusController {
96 96 }
97 97
98 98 [updates:updates]
99   - }
  99 + }
100 100
101 101 }
238 grails-app/domain/org/fogbeam/quoddy/Activity.groovy
... ... @@ -1,9 +1,239 @@
1 1 package org.fogbeam.quoddy
2 2
  3 +/* TODO: map the fields of this class to the activitystrea.ms protocol */
  4 +/* SPEC: http://activitystrea.ms/specs/json/1.0/ */
  5 +/*
  6 + In its simplest form, an activity consists of an actor, a verb, an an object,
  7 + and a target. It tells the story of a person performing an action on or with
  8 + an object -- "Geraldine posted a photo to her album" or "John shared a video".
  9 + In most cases these components will be explicit, but they may also be implied.
  10 +
  11 + It is a goal of this specification to provide sufficient metadata about an activity
  12 + such that a consumer of the data can present it to a user in a rich human-friendly
  13 + format. This may include constructing readable sentences about the activity that
  14 + occurred, visual representations of the activity, or combining similar activities
  15 + for display.
  16 +*/
  17 +
  18 +
  19 +/*
  20 +
  21 +
  22 +Following is a simple, minimal example of a JSON serialized activity:
  23 +
  24 + {
  25 + "published": "2011-02-10T15:04:55Z",
  26 + "actor": {
  27 + "url": "http://example.org/martin",
  28 + "objectType" : "person",
  29 + "id": "tag:example.org,2011:martin",
  30 + "image": {
  31 + "url": "http://example.org/martin/image",
  32 + "width": 250,
  33 + "height": 250
  34 + },
  35 + "displayName": "Martin Smith"
  36 + },
  37 + "verb": "post",
  38 + "object" : {
  39 + "url": "http://example.org/blog/2011/02/entry",
  40 + "id": "tag:example.org,2011:abc123/xyz"
  41 + },
  42 + "target" : {
  43 + "url": "http://example.org/blog/",
  44 + "objectType": "blog",
  45 + "id": "tag:example.org,2011:abc123",
  46 + "displayName": "Martin's Blog"
  47 + }
  48 + }
  49 +
  50 +*/
  51 +
  52 +
  53 +/*
  54 +
  55 + {
  56 + "items" : [
  57 + {
  58 + "published": "2011-02-10T15:04:55Z",
  59 + "foo": "some extension property",
  60 + "generator": {
  61 + "url": "http://example.org/activities-app"
  62 + },
  63 + "provider": {
  64 + "url": "http://example.org/activity-stream"
  65 + },
  66 + "title": "Martin posted a new video to his album.",
  67 + "actor": {
  68 + "url": "http://example.org/martin",
  69 + "objectType": "person",
  70 + "id": "tag:example.org,2011:martin",
  71 + "foo2": "some other extension property",
  72 + "image": {
  73 + "url": "http://example.org/martin/image",
  74 + "width": 250,
  75 + "height": 250
  76 + },
  77 + "displayName": "Martin Smith"
  78 + },
  79 + "verb": "post",
  80 + "object" : {
  81 + "url": "http://example.org/album/my_fluffy_cat.jpg",
  82 + "objectType": "photo",
  83 + "id": "tag:example.org,2011:my_fluffy_cat",
  84 + "image": {
  85 + "url": "http://example.org/album/my_fluffy_cat_thumb.jpg",
  86 + "width": 250,
  87 + "height": 250
  88 + }
  89 + },
  90 + "target": {
  91 + "url": "http://example.org/album/",
  92 + "objectType": "photo-album",
  93 + "id": "tag:example.org,2011:abc123",
  94 + "displayName": "Martin's Photo Album",
  95 + "image": {
  96 + "url": "http://example.org/album/thumbnail.jpg",
  97 + "width": 250,
  98 + "height": 250
  99 + }
  100 + }
  101 + }
  102 + ]
  103 + }
  104 +*/
  105 +
  106 +
  107 +/*
  108 +
  109 + 3.2. Activity Serialization
  110 +
  111 + Property Value Description
  112 +
  113 + actor Object Describes the entity that performed the activity. An activity MUST contain one actor property whose value is a single Object.
  114 + content JSON [RFC4627] String Natural-language description of the activity encoded as a single JSON String containing HTML markup. Visual elements such as thumbnail images MAY be included. An activity MAY contain a content property.
  115 + generator Object Describes the application that generated the activity. An activity MAY contain a generator property whose value is a single Object.
  116 + icon Media Link Description of a resource providing a visual representation of the object, intended for human consumption. The image SHOULD have an aspect ratio of one (horizontal) to one (vertical) and SHOULD be suitable for presentation at a small size. An activity MAY have an icon property.
  117 + id JSON [RFC4627] String Provides a permanent, universally unique identifier for the activity in the form of an absolute IRI [RFC3987]. An activity SHOULD contain a single id property. If an activity does not contain an id property, consumers MAY use the value of the url property as a less-reliable, non-unique identifier.
  118 + object Object Describes the primary object of the activity. For instance, in the activity, "John saved a movie to his wishlist", the object of the activity is "movie". An activity SHOULD contain an object property whose value is a single Object. If the object property is not contained, the primary object of the activity MAY be implied by context.
  119 + published [RFC3339] date-time The date and time at which the activity was published. An activity MUST contain a published property.
  120 + provider Object Describes the application that published the activity. Note that this is not necessarily the same entity that generated the activity. An activity MAY contain a provider property whose value is a single Object.
  121 + target Object Describes the target of the activity. The precise meaning of the activity's target is dependent on the activities verb, but will often be the object the English preposition "to". For instance, in the activity, "John saved a movie to his wishlist", the target of the activity is "wishlist". The activity target MUST NOT be used to identity an indirect object that is not a target of the activity. An activity MAY contain a target property whose value is a single Object.
  122 + title JSON [RFC4627] String Natural-language title or headline for the activity encoded as a single JSON String containing HTML markup. An activity MAY contain a title property.
  123 + updated [RFC3339] date-time The date and time at which a previously published activity has been modified. An Activity MAY contain an updated property.
  124 + url JSON [RFC4627] String An IRI [RFC3987] identifying a resource providing an HTML representation of the activity. An activity MAY contain a url property.
  125 + verb JSON [RFC4627] String Identifies the action that the activity describes. An activity SHOULD contain a verb property whose value is a JSON String that is non-empty and matches either the "isegment-nz-nc" or the "IRI" production in [RFC3339]. Note that the use of a relative reference other than a simple name is not allowed. If the verb is not specified, or if the value is null, the verb is assumed to be "post".
  126 +
  127 +*/
  128 +
  129 +/*
  130 +
  131 + 3.4. Object Serialization
  132 +
  133 + Property Value Description
  134 +
  135 + attachments JSON [RFC4627] Array of Objects A collection of one or more additional, associated objects, similar to the concept of attached files in an email message. An object MAY have an attachments property whose value is a JSON Array of Objects.
  136 + author Object Describes the entity that created or authored the object. An object MAY contain a single author property whose value is an Object of any type. Note that the author field identifies the entity that created the object and does not necessarily identify the entity that published the object. For instance, it may be the case that an object created by one person is posted and published to a system by an entirely different entity.
  137 + content JSON [RFC4627] String Natural-language description of the object encoded as a single JSON String containing HTML markup. Visual elements such as thumbnail images MAY be included. An object MAY contain a content property.
  138 + displayName JSON [RFC4627] String A natural-language, human-readable and plain-text name for the object. HTML markup MUST NOT be included. An object MAY contain a displayName property. If the object does not specify an objectType property, the object SHOULD specify a displayName.
  139 + downstreamDuplicates JSON [RFC4627] Array of Strings A JSON Array of one or more absolute IRI's [RFC3987] identifying objects that duplicate this object's content. An object SHOULD contain a downstreamDuplicates property when there are known objects, possibly in a different system, that duplicate the content in this object. This MAY be used as a hint for consumers to use when resolving duplicates between objects received from different sources.
  140 + id JSON [RFC4627] String Provides a permanent, universally unique identifier for the object in the form of an absolute IRI [RFC3987]. An object SHOULD contain a single id property. If an object does not contain an id property, consumers MAY use the value of the url property as a less-reliable, non-unique identifier.
  141 + image Media Link Description of a resource providing a visual representation of the object, intended for human consumption. An object MAY contain an image property whose value is a Media Link.
  142 + objectType JSON [RFC4627] String Identifies the type of object. An object MAY contain an objectType property whose value is a JSON String that is non-empty and matches either the "isegment-nz-nc" or the "IRI" production in [RFC3987]. Note that the use of a relative reference other than a simple name is not allowed. If no objectType property is contained, the object has no specific type.
  143 + published [RFC3339] date-time The date and time at which the object was published. An object MAY contain a published property.
  144 + summary JSON [RFC4627] String Natural-language summarization of the object encoded as a single JSON String containing HTML markup. Visual elements such as thumbnail images MAY be included. An activity MAY contain a summary property.
  145 + updated [RFC3339] date-time The date and time at which a previously published object has been modified. An Object MAY contain an updated property.
  146 + upstreamDuplicates JSON [RFC4627] Array of Strings A JSON Array of one or more absolute IRI's [RFC3987] identifying objects that duplicate this object's content. An object SHOULD contain an upstreamDuplicates property when a publisher is knowingly duplicating with a new ID the content from another object. This MAY be used as a hint for consumers to use when resolving duplicates between objects received from different sources.
  147 + url JSON [RFC4627] String An IRI [RFC3987] identifying a resource providing an HTML representation of the object. An object MAY contain a url property
  148 +
  149 +*/
3 150 class Activity {
  151 +
  152 + public Activity()
  153 + {
  154 + this.uuid = java.util.UUID.randomUUID().toString();
  155 + }
4 156
5   - String text;
6   - User creator;
7   - Long originTime;
8   - Date dateCreated;
  157 + static constraints = {
  158 +
  159 + updated(nullable:true);
  160 + icon(nullable:true);
  161 +
  162 + userActor(nullable:true);
  163 +
  164 + actorUuid(nullable:true);
  165 + actorUrl(nullable:true);
  166 + actorContent(nullable:true);
  167 + actorDisplayName(nullable:true);
  168 + actorObjectType(nullable:true);
  169 + actorImageUrl(nullable:true);
  170 + actorImageHeight(nullable:true);
  171 + actorImageWidth(nullable:true);
  172 +
  173 + objectUuid(nullable:true);
  174 + objectUrl(nullable:true);
  175 + objectContent(nullable:true);
  176 + objectDisplayName(nullable:true);
  177 + objectObjectType(nullable:true);
  178 + objectImageUrl(nullable:true);
  179 + objectImageHeight(nullable:true);
  180 + objectImageWidth(nullable:true);
  181 +
  182 + targetUuid(nullable:true);
  183 + targetUrl(nullable:true);
  184 + targetContent(nullable:true);
  185 + targetDisplayName(nullable:true);
  186 + targetObjectType(nullable:true);
  187 + targetImageUrl(nullable:true);
  188 + targetImageHeight(nullable:true);
  189 + targetImageWidth(nullable:true);
  190 +
  191 + generatorUrl(nullable:true);
  192 + providerUrl(nullable:true);
  193 +
  194 + dateCreated()
  195 + }
  196 +
  197 + String content;
  198 + Date published;
  199 + String title;
  200 + Date updated;
  201 + URL url;
  202 + String verb;
  203 + URL icon;
  204 + String uuid;
  205 +
  206 + User userActor;
  207 +
  208 + String actorUuid;
  209 + String actorUrl;
  210 + String actorContent;
  211 + String actorDisplayName;
  212 + String actorObjectType;
  213 + String actorImageUrl;
  214 + String actorImageHeight;
  215 + String actorImageWidth;
  216 +
  217 + String objectUuid;
  218 + String objectUrl;
  219 + String objectContent;
  220 + String objectDisplayName;
  221 + String objectObjectType;
  222 + String objectImageUrl;
  223 + String objectImageHeight;
  224 + String objectImageWidth;
  225 +
  226 + String targetUuid;
  227 + String targetUrl;
  228 + String targetContent;
  229 + String targetDisplayName;
  230 + String targetObjectType;
  231 + String targetImageUrl;
  232 + String targetImageHeight;
  233 + String targetImageWidth;
  234 +
  235 + String generatorUrl
  236 + String providerUrl;
  237 +
  238 + Date dateCreated;
9 239 }
31 grails-app/services/org/fogbeam/quoddy/ActivityStreamService.groovy
@@ -39,11 +39,16 @@ class ActivityStreamService {
39 39 public void saveActivity( Activity activity )
40 40 {
41 41 println "about to save activity...";
42   - if( !activity.save() )
  42 + if( !activity.save(flush:true) )
43 43 {
44 44 println( "Saving activity FAILED");
45 45 activity.errors.allErrors.each { println it };
46 46 }
  47 + else
  48 + {
  49 + println "Successfully saved Activity: ${activity.id}";
  50 + }
  51 +
47 52 }
48 53
49 54
@@ -107,7 +112,8 @@ class ActivityStreamService {
107 112
108 113 println "Messages to read from queue: ${msgsToRead}";
109 114
110   - long oldestOriginTime = Long.MAX_VALUE;
  115 + // long oldestOriginTime = Long.MAX_VALUE;
  116 + long oldestOriginTime = new Date().getTime();
111 117
112 118 // NOTE: we could avoid iterating over this list again by returning the "oldest message time"
113 119 // as part of this call. But it'll mean wrapping this stuff up into an object of some
@@ -123,6 +129,7 @@ class ActivityStreamService {
123 129 }
124 130
125 131 println "oldestOriginTime: ${oldestOriginTime}";
  132 + println "as date: " + new Date( oldestOriginTime);
126 133
127 134 // convert our messages to Activity instances and
128 135 // put them in this list...
@@ -139,9 +146,9 @@ class ActivityStreamService {
139 146 Activity activity = new Activity();
140 147
141 148 // println "msg class: " + msg?.getClass().getName();
142   - activity.creator = userService.findUserByUserId( msg.creator );
143   - activity.text = msg.text;
144   - activity.originTime = msg.originTime;
  149 + activity.userActor = userService.findUserByUserId( msg.creator );
  150 + activity.content = msg.text;
  151 + activity.dateCreated = new Date( msg.originTime );
145 152 recentActivities.add( activity );
146 153 }
147 154
@@ -170,9 +177,10 @@ class ActivityStreamService {
170 177 Date cutoffDate = cal.getTime();
171 178
172 179 println "Using ${cutoffDate} as cutoffDate";
  180 + println "Using ${new Date(oldestOriginTime)} as oldestOriginTime";
173 181
174 182 List<User> friends = userService.listFriends( user );
175   - if( friends != null && friends.size() > 0 )
  183 + if( friends != null && friends.size() >= 0 )
176 184 {
177 185 println "Found ${friends.size()} friends";
178 186 List<Integer> friendIds = new ArrayList<Integer>();
@@ -183,10 +191,15 @@ class ActivityStreamService {
183 191 friendIds.add( id );
184 192 }
185 193
186   -
  194 +
  195 + // for the purpose of this query, treat a user as their own friend... that is, we
  196 + // will want to read Activities created by this user (we see out own updates in our
  197 + // own feed)
  198 + friendIds.add( user.id );
  199 +
187 200 List<Activity> queryResults =
188   - Activity.executeQuery( "select activity from Activity as activity where activity.dateCreated >= :cutoffDate and activity.creator.id in (:friendIds) and activity.originTime < :oldestOriginTime order by activity.dateCreated desc",
189   - ['cutoffDate':cutoffDate, 'oldestOriginTime':oldestOriginTime, 'friendIds':friendIds], ['max': recordsToRetrieve ]);
  201 + Activity.executeQuery( "select activity from Activity as activity where activity.dateCreated >= :cutoffDate and activity.userActor.id in (:friendIds) and activity.dateCreated < :oldestOriginTime order by activity.dateCreated desc",
  202 + ['cutoffDate':cutoffDate, 'oldestOriginTime':new Date(oldestOriginTime), 'friendIds':friendIds], ['max': recordsToRetrieve ]);
190 203
191 204 println "adding ${queryResults.size()} activities read from DB";
192 205 recentActivities.addAll( queryResults );
75 grails-app/services/org/fogbeam/quoddy/EventQueueService.groovy
@@ -17,10 +17,6 @@ class EventQueueService
17 17
18 18 // now, figure out which user(s) are interested in this message, and put it on
19 19 // all the appropriate queues
20   -
21   -
22   - // NOTE: in this prototype version, we'll assume anybody who is logged in
23   - // is interested in messages from every other user (note: except themselves)
24 20 Set<Map.Entry<String, Deque<Map>>> entries = eventQueues.entrySet();
25 21 println "got entrySet from eventQueues object: ${entries}";
26 22 for( Map.Entry<String, Deque<Map>> entry : entries )
@@ -30,50 +26,51 @@ class EventQueueService
30 26
31 27 String key = entry.getKey();
32 28
33   - // don't put the message on the queue for the user who sent it
34   - if( !key.equalsIgnoreCase( msg.creator ))
  29 +
  30 + // TODO: don't offer message unless the owner of this queue
  31 + // and the event creator, are friends (or the owner *is* the creator)
  32 + println "msg creator: ${msg.creator}";
  33 + User msgCreator = userService.findUserByUserId( msg.creator );
  34 + if( msgCreator )
  35 + {
  36 + println "found User object for ${msgCreator.userId}";
  37 + }
  38 +
  39 + FriendCollection friendCollection = FriendCollection.findByOwnerUuid( msgCreator.uuid );
  40 + if( friendCollection )
35 41 {
  42 + println "got a valid friends collection for ${msgCreator.userId}";
  43 + }
36 44
37   - // TODO: don't offer message unless the owner of this queue
38   - // and the event creator, are friends.
39   - println "msg creator: ${msg.creator}";
40   - User msgCreator = userService.findUserByUserId( msg.creator );
41   - if( msgCreator )
42   - {
43   - println "found User object for ${msgCreator.userId}";
44   - }
45   -
46   - FriendCollection friendCollection = FriendCollection.findByOwnerUuid( msgCreator.uuid );
47   - if( friendCollection )
  45 + Set<String> friends = friendCollection.friends;
  46 + if( friends )
  47 + {
  48 + println "got valid friends set: ${friends}";
  49 + for( String friend : friends )
48 50 {
49   - println "got a valid friends collection for ${msgCreator.userId}";
  51 + println "friend: ${friend}";
50 52 }
51   -
52   - Set<String> friends = friendCollection.friends;
53   - if( friends )
  53 + }
  54 + User targetUser = userService.findUserByUserId( key );
  55 + if( friends.contains( targetUser.uuid ) || msgCreator.uuid.equals( targetUser.uuid ) )
  56 + {
  57 + println "match found, offering message";
  58 + Deque<Map> userQueue = entry.getValue();
  59 + if( msg instanceof Map )
54 60 {
55   - println "got valid friends set: ${friends}";
56   - for( String friend : friends )
57   - {
58   - println "friend: ${friend}";
59   - }
  61 + println "MapMessage being offered";
  62 + userQueue.offerFirst( msg );
60 63 }
61   - User targetUser = userService.findUserByUserId( key );
62   - if( friends.contains( targetUser.uuid ))
  64 + else
63 65 {
64   - println "match found, offering message";
65   - Deque<Map> userQueue = entry.getValue();
66   - if( msg instanceof Map )
67   - {
68   - println "MapMessage being offered";
69   - userQueue.offerFirst( msg );
70   - }
71   - else
72   - {
73   - println "WTF is this? ${msg}";
74   - }
  66 + println "WTF is this? ${msg}";
75 67 }
76 68 }
  69 +
  70 +
  71 +
  72 +
  73 +
77 74 }
78 75 println "done processing eventQueue instances";
79 76 }
56 grails-app/services/org/fogbeam/quoddy/transformer/ActivityStreamTransformerService.groovy
... ... @@ -0,0 +1,56 @@
  1 +package org.fogbeam.quoddy.transformer
  2 +
  3 +import java.net.URL
  4 +import java.text.SimpleDateFormat
  5 +
  6 +import org.fogbeam.quoddy.Activity
  7 +import org.fogbeam.quoddy.User
  8 +import org.fogbeam.quoddy.integration.activitystream.ActivityStreamEntry
  9 +
  10 +class ActivityStreamTransformerService
  11 +{
  12 + // see note here: http://stackoverflow.com/questions/2580925/simpledateformat-parsing-date-with-z-literal
  13 + // about parsing the RFC3339 format with the 'Z' literal
  14 + // "yyyy-MM-dd'T'HH:mm:ss.SSSZ" -- with fractional seconds part (do we need this?)
  15 + private static final String RFC3339_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
  16 + def userService;
  17 +
  18 + public Activity getActivity( ActivityStreamEntry entry )
  19 + {
  20 + Activity activity = new Activity();
  21 +
  22 + activity.content = entry.content;
  23 + activity.title = entry.title;
  24 + if( entry.url )
  25 + {
  26 + activity.url = new URL(entry.url);
  27 + }
  28 +
  29 + // do date translation
  30 + SimpleDateFormat sdf = new SimpleDateFormat( RFC3339_DATE_FORMAT );
  31 + String eventPublishedDate = entry.published;
  32 + if( eventPublishedDate.endsWith( "Z" ))
  33 + {
  34 + eventPublishedDate = eventPublishedDate.replaceAll( "Z\$", "GMT+00:00" );
  35 + }
  36 +
  37 + activity.published = sdf.parse( eventPublishedDate );
  38 +
  39 + activity.verb = entry.verb;
  40 +
  41 + // do user translation
  42 + println "Looking for user ${entry.actor.id}";
  43 + User userActor = userService.findUserByUserId(entry.actor.id);
  44 + if( userActor )
  45 + {
  46 + println "Found user: ${userActor}";
  47 + activity.userActor = userActor;
  48 + }
  49 + else
  50 + {
  51 + println "failed to lookup Actor as a User in our system.";
  52 + }
  53 +
  54 + return activity;
  55 + }
  56 +}
8 grails-app/views/_activityStream.gsp
@@ -3,18 +3,18 @@
3 3 <div class="aseWrapper">
4 4
5 5 <div class="aseAvatarBlock">
6   - <img src="${createLink(controller:'profilePic',action:'thumbnail',id:activity.creator.userId)}" />
  6 + <img src="${createLink(controller:'profilePic',action:'thumbnail',id:activity.userActor.userId)}" />
7 7 </div>
8 8 <div class="aseTitleBar"> <!-- http://localhost:8080/quoddy/user/viewUser?userId=testuser2 -->
9   - <a href="${createLink(controller:'user', action:'viewUser', params:[userId:activity.creator.userId])}">${activity.creator.fullName}</a>
  9 + <a href="${createLink(controller:'user', action:'viewUser', params:[userId:activity.userActor.userId])}">${activity.userActor.fullName}</a>
10 10 </div>
11 11 <div class="activityStreamEntry">
12   - ${activity.text}
  12 + ${activity.content}
13 13 </div>
14 14 <div class="aseClear" >
15 15 </div>
16 16 <div class="aseFooter" >
17   - ${activity.dateCreated}
  17 + <g:formatDate date="${activity.dateCreated}" type="datetime" style="LONG" timeStyle="SHORT"/>
18 18 </div>
19 19 </div>
20 20
18 grails-app/views/special/post_as.gsp
... ... @@ -0,0 +1,18 @@
  1 +<html>
  2 +
  3 + <head>
  4 + <title>Quoddy: Post ActivityStream as JSON</title>
  5 + <meta name="layout" content="main" />
  6 + <nav:resources />
  7 + </head>
  8 +
  9 + <body>
  10 + <p />
  11 + <g:form controller="activityStream" action="index" method="POST">
  12 + <g:textArea name="activityJson" style="height:400px;width:600px;" />
  13 + <br />
  14 + <input type="submit" name="submitJson" value="SubmitJson" />
  15 + </g:form>
  16 + </body>
  17 +
  18 +</html>
BIN  lib/arq-2.8.7.jar
Binary file not shown
BIN  lib/iri-0.8.jar
Binary file not shown
BIN  lib/jackson-all-1.9.3.jar
Binary file not shown
BIN  lib/jena-2.6.4.jar
Binary file not shown
BIN  lib/stax-api-1.0.1.jar
Binary file not shown
BIN  lib/wstx-asl-3.2.9.jar
Binary file not shown
0  src/groovy/groovy.txt
No changes.
20 src/groovy/org/fogbeam/quoddy/integration/activitystream/ActivityStreamEntry.groovy
... ... @@ -0,0 +1,20 @@
  1 +package org.fogbeam.quoddy.integration.activitystream
  2 +
  3 +class ActivityStreamEntry
  4 +{
  5 + String content;
  6 + String published;
  7 + String title;
  8 + String url;
  9 + String verb;
  10 +
  11 + Actor actor;
  12 + org.fogbeam.quoddy.integration.activitystream.Object object;
  13 + Target target;
  14 +
  15 +
  16 + public String toString()
  17 + {
  18 + return "ActivityStreamEntry: published: ${published}, verb: ${verb}, actor: ${actor}, object: ${object}, target: ${target}";
  19 + }
  20 +}
18 src/groovy/org/fogbeam/quoddy/integration/activitystream/Actor.groovy
... ... @@ -0,0 +1,18 @@
  1 +package org.fogbeam.quoddy.integration.activitystream
  2 +
  3 +class Actor {
  4 +
  5 + static constraints = {
  6 + }
  7 +
  8 + String displayName;
  9 + String objectType;
  10 + String id;
  11 + String url;
  12 + Image image;
  13 +
  14 + public String toString()
  15 + {
  16 + return "Actor: displayName: ${displayName}, objectType: ${objectType}, id: ${id}, url: ${url}, image: ${image}";
  17 + }
  18 +}
13 src/groovy/org/fogbeam/quoddy/integration/activitystream/Image.groovy
... ... @@ -0,0 +1,13 @@
  1 +package org.fogbeam.quoddy.integration.activitystream
  2 +
  3 +class Image
  4 +{
  5 + String url;
  6 + String width;
  7 + String height;
  8 +
  9 + public String toString()
  10 + {
  11 + return "Image: url: ${url}, width:${width}, height: ${height}";
  12 + }
  13 +}
12 src/groovy/org/fogbeam/quoddy/integration/activitystream/Object.groovy
... ... @@ -0,0 +1,12 @@
  1 +package org.fogbeam.quoddy.integration.activitystream
  2 +
  3 +class Object
  4 +{
  5 + String url;
  6 + String id;
  7 +
  8 + public String toString()
  9 + {
  10 + return "Object: url: ${url}, id: ${id}";
  11 + }
  12 +}
14 src/groovy/org/fogbeam/quoddy/integration/activitystream/Target.groovy
... ... @@ -0,0 +1,14 @@
  1 +package org.fogbeam.quoddy.integration.activitystream
  2 +
  3 +class Target
  4 +{
  5 + String url;
  6 + String objectType;
  7 + String id;
  8 + String displayName;
  9 +
  10 + public String toString()
  11 + {
  12 + return "Target: url: ${url}, objectType: ${objectType}, id: ${id}, displayName: ${displayName}";
  13 + }
  14 +}

0 comments on commit 7e14c53

Please sign in to comment.
Something went wrong with that request. Please try again.