Robot development for Google Wave has sparked my imagination lately. You can* write Robots in Java and Python and run them on Google App Engine, but what about ColdFusion ? Well since Open Blue Dragon (OpenBD) can now run on GAE, I thought it would be fun to have a go at a Wave Robot written in ColdFusion.
|
Update: 1 Jun 2010 - Its working now (Yea !). I have working Operations being sent back to the server causing changes in the Wave. The profile is also working. The problem was :
Try it yourself. Invite cfwavebot@appspot.com to a wave.
|
After reading API docs for the Google Wave, the Robot Wire Protocol is what I decided to implement (partially – as a proof or concept). It is a light-weight message and event protocol that uses JSON to exchange data.
*Update : At I/O 2010 Google announced that Robots can run on servers other that Google App Engine. I haven't tried it myself yet.
This blog post is about my efforts so far, and explains how to get setup the way I did. CFML source code is included
In this article :
- What is / isnt working ?
- Problems Overcome
- What I have / what remains to be implemented
- How To Build Your Own ColdFusion Wave Robots
- Essential Reading
- Where to get help
What is / isnt working ?
- jsonrpc message bundles are being received by the robot OK
- The robot is replying with operations json :
- wavelet.appendBlip
- Fixed ! The Wave Server wont:
- process the operations json the robot sends back
- Display any of the profile details (avatar, profile web link)
You can see how it is currently running from this screencast (doesn’t have sound).
http://www.youtube.com/watch?v=q3h4oU7hGKI
Notice from the video that :
- I start a new wave with the robot but nothing from it appears in the wave
- From the GAE logs, you can see the robot receives jsonrpc messages and responds with operations like "wavelet_appendBlip"
- I conclude (with a guess) that the UPPERCASE field names that ColdFusion creates when using its serializejson() function cant be understood by the Google Wave Server
There are more detailed screenshots of the issues so far below.
Problems Overcome
- Logging to the console is the only way to debug. Forget about <cfdumps> to the page, because you are always returning json strings, not HTML. Fortunately good-ole <cflog > works both locally and on Google App Engine.
- Debugging with a locally running version of your robot is much quicker that deploying to GAE. You need to fake jsonrpc calls from the Wave Server which you can do with a simple web page that you can cut and paste json messages and then fire them at your robot.
- Different JSON http content encoding (binary and text)
- What isn't documented
- OpenBD can't process requests like /robots/jsonrpc without a URL re-writer
- You need to use the tuckey url rewriter
How the ColdFusion version works
I started with a single cfml template for each of the 3 URLs that the Robot must answer on. ColdFusion has built-in support for JSON serialization (although its insistance on CAPITALIZING field names has be stuck at the moment).
I made use of the URL rewriting technique for OpenBD described on the OpenBD wiki to handle all the required Robot URLS :
- URL re-writing maps the required calls as follows :
| Robot URL | Mapped to CFML Script |
| _wave/capabilities.xml | _wave/robot/capabilies.cfm |
| _wave/robot/profile | _wave/robot/profile.cfm |
| _wave/robot/jsonrpc | _wave/robot/jsonrpc.cfm |
- Capabilites Hash - The Robot needs to provide a hash of its current capabilites each time it replies to the Wave Server. I think the idea here is that this should be changed when you decide to add or remove one of the events in capabilities.xml. It is 16 hex characters long so I used a built-in hash generator and trimmed it.
<cfset HashInput = "capabilitiesRevision000"><!--- Change this when you change your capabilities.xml --->
<cfset VersionHash = Right(Hash(string=HashInput, algorithm="CFMX_COMPAT"),16)> <!--- 16 char hash --->
- Event Handling - there is a <cfswitch> statement that sends events to individual event handler functions. Simple for now.
- Operations Queue - A simple queue (an Array) of Operation objects is built for the event handlers to generate operations. These are bundled up and serialized as JSON and sent back to the wave server.
What remains to Implement
- The ColdFusion code I have so far is really just proof-of-concept. Once I have a working robot I plan to do the following :
- Implement classes (using CFCs) to mimick the Wave Robot Java Client Library
Essential Reading
- Official Wave API docs from Google
- Google Wave API OverView Has a description of Wave Entities (and a diagram to help explain them). You need to know and undestand these key concepts :
- Wave
- Wavelet
- Blips and Data Documents
- Google Wave API OverView Has a description of Wave Entities (and a diagram to help explain them). You need to know and undestand these key concepts :
- The Events and Operations detail in the API explain the interactions between Robots and the Wave Server
- The Wave Robot Wire Protocol is what I set out to implement. It explains the JSON-RPC message passing.
- Google App Engine on the OpenBD Wiki has some important information about what to expect when running ColdFusion on GAE. The essential things are :
- No <CFFile> access or writing to the file system
- Debugging Wave Robots : This essential article explains the technique of debugging your robot on a local development server and "faking" Wave JSON message bundles from your browser. It also explains the logs viewer on Google App Engine.
How To Build Your Own ColdFusion Wave Robots
- Setup Eclipse with the Google plug-in for Eclipse
- There are good instructions for doing this :
- Setting Up App Engine, Google Wave Robots API: Java Tutorial
- There are good instructions for doing this :
- Download OpenBD for Google App Engine (make sure you get the Google Ap Engine bundle)
TIP: The configuration instructions are in the zip file that you dowload ( NOT in the Wiki)
- Create a GAE Project and add the JARs and configure OpenBD using the instruction above.
- Configure the project for a ColdFusion Wave Robot
- Copy the CFML source files to /war so that you have :
/war/_wave/robot/
- capabilities.cfm
- jsonrpc.cfm
- profile.cfm
/war/_wave
- index.cfm
/war/images
- avatar.png
/war/static
- profile.html
- json2.js
- Edit /war/WEB-INF/web.xml and add the following :
<!-- Enable the URLRewriter. The Rules are configured seperately in urlrewrite.xml -->
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- set the amount of seconds the conf file will be checked for reload
can be a valid integer (0 denotes check every time,
empty/not set denotes no reload check) -->
<init-param>
<param-name>confReloadCheckInterval</param-name>
<param-value>0</param-value>
</init-param>
And this is worth doing to :
<!-- Comment out the welcome file list. Not essential, but less confusing with 404 errors
<welcome-file-list>
<welcome-file>index.cfm</welcome-file>
</welcome-file-list>
-->
- Turn verbose logging on (.level = ALL) in /war/WEB-INF/logging.properties
HINT : If you don’t do this then you wont see any <cflog > output in your Google App Engine logs
# Set the default logging level for all loggers to WARNING
.level = ALL
Here is what your project layout should look like in Eclipse
- Run and Debug your Robot Locally
I have a slightly modified version of the browser-based JSON message POST tool in this article by Pamela Fox. The key to making this work for you is getting your hands on some real JSON message bundles from a wave session.
I created a second Robot sample in Java and implemented just the methods I wanted to get working in my ColdFusion version. I was able to copy the JSON message bundles from the Google App Engine Logs, and then use them locally.
<cflog text="#message#"> is your friend here. Log output displays in real-time locally in the Eclipse console, and quite quickly after a page refresh in the App Engine Logs (but make sure you have enabled verbose logging in the WEB-INF/logging.properties file)
Its ironic that JSON serialization turns out to be a great way to dump messages sent and received to the console. You can dump the contents of a struct (say called properties) like this :
<!--- Dump the struct to the console --->
<cflog text="HandleEvent: properties=#serializeJSON(properties)#">
- Deploy to Google App Engine and Debug
Once you have a local version running you will want to deploy to App Engine. This can take anywhere from 15 secs to 2 minutes so I don’t do it very often.
One difference between the App Engine and local environments is that the Wave Server encodes a binary version of the HTTP POST JSON content which I had to deal with like this :
<!--- Page Globals --->
<cfset HttpRequestData = GetHttpRequestData()>
<cffunction name="UnpackJSONRequest" output="No" returntype="Struct">
<!--- Get and deserialize the JSON Request --->
<!--- The JSON request is packed differenty depending how its called :
* Google Wave Servers send the request as binary
* Browser testing with XMLHTTPRequest sends request as text
--->
<cfset pageContentStr = "">
<cfif IsBinary(HttpRequestData.CONTENT)>
<cfset pageContentStr = ToString(HttpRequestData.CONTENT)>
<cfelse>
<cfset pageContentStr = HttpRequestData.CONTENT>
</cfif>
<!--- LOG ---><cflog text="UnpackJSONRequest: #pageContentStr#">
<cfset formStruct = deserializeJSON(pageContentStr)>
<cfreturn formStruct>
</cffunction>
TIP : Click the radio button Show: All requests to refresh the logs view.
- Add your Robot to a New Wave and watch the Google App Engine logs
Update 1 June : My robot is working and does the following.
- Responds to a SELF_ADDED message with an introduction (which is a wavelet.appendBlip operation)
- Responds to a BLIP_SUBMITTED event (that when you click Done or type Shift-Enter when typing ) event with a greeting (another wavelet.appendBlip operation)
CFML Source Code
Here is the ColdFusion source for the main files :
/war/_wave/robot/
- capabilities.cfm
- jsonrpc.cfm
- profile.cfm
Capabilities.cfm
<cfsilent><cfset logpage = "_wave/robot/capabilities.cfm: ">
<cfcontent type="text/xml">
<cfset hashInput = Now()>
<cfset versionHash = Right(Hash(string=hashInput, algorithm="CFMX_COMPAT"),16)>
<cflog text="#logpage# cgi.script_name= #cgi.script_name#">
</cfsilent><cfoutput>
<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">
<w:version>#versionHash#</w:version>
<w:protocolversion>0.21</w:protocolversion>
<w:capabilities>
<!--- <w:capability name="DOCUMENT_CHANGED"/> --->
<w:capability name="WAVELET_SELF_ADDED"/>
<w:capability name="BLIP_SUBMITTED"/>
<w:capability name="WAVELET_PARTICIPANTS_CHANGED"/>
</w:capabilities>
</w:robot>
</cfoutput>
Profile.cfm - updated 1 June
<!--- Profile.cfm
Requires :
cfjson CFC
<cfsilent><cfcontent type="application/json">
<cfset operationsObj = StructNew()>
<cfset operationsObj['name'] = "cfwaverobot">
<cfset operationsObj['imageUrl'] = "http://cfwavebot.appspot.com/images/avatar.png">
<cfset operationsObj['profileUrl'] = "http://cfwavebot.appspot.com/static/profile.html">
<cfinvoke component="json"
method="encode"
data= "#operationsObj#"
returnvariable="returnJSON" />
</cfsilent><cfoutput>#returnJSON#</cfoutput>
Jsonrpc.cfm - updated 1 June
<cftry><cfsilent>
<cfcontent type="application/json">
<cfset logpage = "_wave/robot/jsonrpc.cfm: ">
<!--- JSONRPC.cfm
A Google Wave Robot written in ColdFusion
INPUT :
Message Bundle JSON (Expected as POST)
OUTPUT :
Operations JSON (MIME TYPE: application/json)
Requires :
cfjson CFC
http://www.epiphantastic.com/cfjson/
Find Out More :
http://blog.johnoscott.com/2010/05/building-wave-robots-in-coldfusion-with.html
--->
<!--- Page Globals --->
<cfset HttpRequestData = GetHttpRequestData()>
<!--- <cfset ResponseOperationsJSON = ""> ---> <!--- the JSON string we will be returning --->
<cfset OperationsArray = ArrayNew(1)>
<cfset OperationIntanceCount = 0><!--- Used to generate the id for each operation --->
<cfset RequestMessageBundle = null >
<cfset HashInput = "capabilitiesRevision000"><!--- Change this when you change your capabilities.xml --->
<cfset VersionHash = Right(Hash(string=HashInput, algorithm="CFMX_COMPAT"),16)> <!--- 16 char hash --->
<cfset RobotProtocolVersion = "0.21"> <!--- Not sure what this is but its used with each Operation object --->
<!--- LOGGING --->
<!--- <cflog text="#logpage# cgi.script_name= #cgi.script_name#">
<cflog text="#logpage# cgi.REQUEST_METHOD= #cgi.REQUEST_METHOD#">
<cflog text="#logpage# cgi.CONTENT_TYPE= #cgi.CONTENT_TYPE#"> --->
<!--- LOG: FORM --->
<!--- <cflog text="StructCount(Form)=#StructCount(Form)#">
<cfset fieldNum = 1>
<cfloop collection="#Form#" ITEM="VarName">
<cflog text="#logpage# Form Field #fieldNum# Name=#VarName#; Value=#Form[VarName]#">
<cfset fieldNum = fieldNum + 1>
</cfloop> --->
<!--- LOG: HTTPREQUESTDATA --->
<!--- <cflog text="StructCount(HttpRequestData)=#StructCount(HttpRequestData)#">
<cflog text="All HttpRequestData=#CustomSerializeJSON(HttpRequestData)#">
<cflog text="StructCount(HttpRequestData.HEADERS)=#StructCount(HttpRequestData.HEADERS)#">
<cflog text="HttpRequestData.HEADERS=#CustomSerializeJSON(HttpRequestData.HEADERS)#">
<cfset fieldNum = 1>
<cfloop collection="#HttpRequestData.HEADERS#" ITEM="key">
<cflog text="#logpage# Header:#fieldNum# #key#:#HttpRequestData.HEADERS[key]#">
<cfset fieldNum = fieldNum + 1>
</cfloop> --->
<!--- CONTENT --->
<!--- <cfif IsBinary(HttpRequestData.CONTENT)>
<cflog text="Type=BINARY for HttpRequestData.CONTENT of Length #Len(HttpRequestData.CONTENT)#">
<cflog text="Attempting to Unpack BINARY CONTENT">
<cfset content = ToString(#HttpRequestData.CONTENT#)>
<cflog text="Unpacked BINARY HttpRequestData.CONTENT= (#content#)">
<cfelse>
<cflog text="Type=String for HttpRequestData.CONTENT=#HttpRequestData.CONTENT#">
</cfif>
<cflog text="HttpRequestData.METHOD=#HttpRequestData.METHOD#">
<cflog text="HttpRequestData.PROTOCOL=#HttpRequestData.PROTOCOL#">
<cflog text="CGI=#CustomSerializeJSON(CGI)#">
<cflog text="URL=#CustomSerializeJSON(URL)#"> --->
<!--- FUNCTIONS --->
<cffunction name="GetStructDump" output="No" returntype="String">
<cfargument name="theStruct" type="Struct" required="Yes">
<cfargument name="theDepth" type="Numeric" required="No">
<!---
<cfset i = 1>
<cfset dumpStr = "">
<cfloop collection="#Form#" ITEM="VarName">
<cflog text="#logpage# Form Field #fieldNum# Name=#VarName#; Value=#Form[VarName]#">
<cfset fieldNum = fieldNum + 1>
</cfloop>
--->
<cfreturn CustomSerializeJSON(theStruct)>
</cffunction>
<cffunction name="UnpackJSONRequest" output="No" returntype="Struct">
<!--- Get and deserialize the JSON Request --->
<!--- The JSON request is packed differenty depending how its called :
* Google Wave Servers send the request as binary
* Browser testing with XMLHTTPRequest sends request as text
--->
<cfset pageContentStr = "">
<cfif IsBinary(HttpRequestData.CONTENT)>
<cfset pageContentStr = ToString(HttpRequestData.CONTENT)>
<cfelse>
<cfset pageContentStr = HttpRequestData.CONTENT>
</cfif>
<!--- LOG ---><cflog text="UnpackJSONRequest: #pageContentStr#">
<cfset formStruct = deserializeJSON(pageContentStr)>
<cfreturn formStruct>
</cffunction>
<!--- EVENT HANDLERS --->
<cffunction name="ProcessEvents" output="No" >
<!--- HANDLE THE EVENTS a--->
<cfset events = RequestMessageBundle["EVENTS"]>
<cfif IsArray(events)>
<!--- <cflog text="#logpage# WE GOT EVENTS:" > --->
<cfloop Index="i" From="1" To="#ArrayLen(events)#">
<!--- LOG ---><cflog text="Event[#i#] = #CustomSerializeJSON(events[i])#">
<cftry>
<!--- HANDLE THE EVENT AND GET A RESPONSE OPERATION --->
<!--- The event will queue an operation if it wants to which will be returned with the page response --->
<cfset dontCare = HandleEvent(events[i])>
<!--- <cflog text="Event[#i#] Result RequestMessageBundle = #resultOperationsJSON#"> --->
<cfcatch type="any">
<!--- LOG ---><cflog text="** ProcessEvents: Event:#i# cfcatch caught error: #cfcatch.Message# *********">
</cfcatch>
</cftry>
</cfloop>
<cfelse>
<!--- LOG ---><cflog text="#logpage# NO EVENTS" >
</cfif>
<cfreturn>
</cffunction>
<cffunction name="HandleEvent" output="No">
<cfargument name="event" type="Struct" required="Yes">
<!--- Unpack the event and dispatch to its event handler --->
<!--- An Event looks something like this :
{ "MODIFIEDBY":"johnoscott@googlewave.com",
"PROPERTIES": {"BLIPID":"b+N-PjdA1UO","PARTICIPANTSREMOVED":[],"PARTICIPANTSADDED":["cfwavebot@appspot.com"]},
"TIMESTAMP":1.27519999508E+12,
"TYPE":"WAVELET_PARTICIPANTS_CHANGED"
}
--->
<cfset properties = event["PROPERTIES"]>
<!--- <cflog text="HandleEvent: properties = #CustomSerializeJSON(properties)#"> --->
<cfset eventType = event["TYPE"]>
<!--- <cflog text="HandleEvent:eventType = #eventType#"> --->
<cfset result="">
<cfswitch expression = "#eventtype#">
<cfcase value = "BLIP_SUBMITTED">
<cfset result = BlipSubmittedEvent(event)>
</cfcase>
<cfcase value = "WAVELET_SELF_ADDED">
<cfset result = WaveletSelfAddedEvent(event)>
</cfcase>
<cfcase value = "WAVELET_PARTICIPANTS_CHANGED">
<cfset result = WaveletParticipantsChangedEvent(event)>
</cfcase>
</cfswitch>
<cfreturn>
</cffunction>
<cffunction name="BlipSubmittedEvent" output="No" >
<cfargument name="event" type="Struct" required="Yes">
<!--- Sample Event
{
"type": "BLIP_SUBMITTED",
"modifiedBy": "user@example.com",
"timestamp": 1255935016481,
"properties": {
"blipId": "b+ja8F_Hw4J"
}
}
--->
<!--- LOG ---><cflog text="BlipSubmittedEvent:" >
<cfset properties = event["properties"]>
<cfset blipID = properties["blipId"]>
<!--- LOG ---><cflog text="BlipSubmittedEvent: blipID=#blipID#" >
<!--- <cflog text="HandleEvent: properties = #CustomSerializeJSON(properties)#"> --->
<cfset content = getBlipContent(blipID)>
<!--- LOG ---> <cflog text="BlipSubmittedEvent: content = #content#">
<!--- Look for signs of CF-speaking bretheran :) --->
<cfif content EQ "">
<cfreturn>
<cfelseif findNoCase('hello',content)>
<cfset replyStr = "And a <cfgood_day_to_you_too> !">
</cfif>
<cfset dontCare = WaveletReply(replyStr, event)>
<cfreturn>
</cffunction>
<cffunction name="WaveletSelfAddedEvent" output="No" >
<cfargument name="event" type="Struct" required="Yes">
<!--- Event looks something like this :
{ "MODIFIEDBY":"johnoscott@googlewave.com",
"PROPERTIES": {"BLIPID":"b+N-PjdA1UO","PARTICIPANTSREMOVED":[],"PARTICIPANTSADDED":["cfwavebot@appspot.com"]},
"TIMESTAMP":1.27519999508E+12,
"TYPE":"WAVELET_PARTICIPANTS_CHANGED"
}
--->
<!--- LOG ---><cflog text="WaveletSelfAddedEvent:" >
<!--- <cfset properties = event["PROPERTIES"]> --->
<!--- <cflog text="HandleEvent: properties = #CustomSerializeJSON(properties)#"> --->
<cfset replyStr = "Howdy, I'm a ColdFusion robot.\n" &
"I only do one thing right now, and that is to reply to " &
"a BLIP_SUBMITTED event if it contains a greeting (hint : starts with hel..). " &
"Find out how i came into being here : http://bit.ly/dhQRQn"
>
<cfset dontCare = WaveletReply(replyStr, event)>
<cfreturn>
</cffunction>
<cffunction name="WaveletParticipantsChangedEvent" output="No">
<cfargument name="event" type="Struct" required="Yes">
<!--- Event looks something like this :
{ "MODIFIEDBY":"johnoscott@googlewave.com",
"PROPERTIES": {"BLIPID":"b+N-PjdA1UO","PARTICIPANTSREMOVED":[],"PARTICIPANTSADDED":["cfwavebot@appspot.com"]},
"TIMESTAMP":1.27519999508E+12,
"TYPE":"WAVELET_PARTICIPANTS_CHANGED"
}
--->
<!--- LOG ---><cflog text="WaveletParticipantsChangedEvent:" >
<cfset properties = event["PROPERTIES"]>
<!--- <cflog text="HandleEvent: properties = #CustomSerializeJSON(properties)#"> --->
<cfreturn>
</cffunction>
<!--- OPERATIONS --->
<!--- Return a new Operation structure --->
<!--- RequestMessageBundle to return look like this, an array of operations
[ { "method":"robot.notifyCapabilitiesHash",
"id":"op1",
"params":{"protocolVersion":"0.21","capabilitiesHash":"ffffff79d8b7888c"}
},
{ "method":"wavelet.appendBlip",
"id":"op2",
"params":{ "waveId":"googlewave.com!w+jH65e-xWW",
"waveletId":"googlewave.com!conv+root",
"blipData":{"annotations":[],
"elements":{},
"blipId":"TBD_googlewave.com!conv+root_1771192937",
"childBlipIds":[],
"contributors":[],
"creator":null,
"content":"\nHi everybody! MY JOB IS TO STOP EVERYONE FROM SHOUTING",
"lastModifiedTime":-1,
"parentBlipId":null,
"version":-1,
"waveId":"googlewave.com!w+jH65e-xWW",
"waveletId":"googlewave.com!conv+root"}
}
}
]
--->
<cffunction name="InitOperationsQueue" output="No">
<!--- Initialize the Queue and add queue the first operation, the capabilities operation --->
<!--- OperationsArray is initialized at the top of the page --->
<!--- <cfset OperationsArray = ArrayNew(1)> --->
<!--- Get the Capabilites Operation and queue it --->
<cfset capabilitiesOperation = GetCapabilitiesOperation()>
<cfset dontCare = QueueOperation(capabilitiesOperation) >
<cfreturn>
</cffunction>
<cffunction name="QueueOperation" output="No">
<cfargument name="operation" type="Struct" required="Yes">
<!--- Queue the operation to be returned when the page returns --->
<!--- LOG ---><cflog text="QueueOperation: id=#operation.ID# json=#CustomSerializeJSON(operation)#">
<cfset ArrayAppend(OperationsArray,operation) >
<cfreturn>
</cffunction>
<cffunction name="NewOperation" output="No" returntype="Struct">
<!--- RETURNS a new operations struct --->
<!--- Example :
{
"method":"undefined",
"id":"op1",
"params":{}
}
--->
<cfset OperationIntanceCount = OperationIntanceCount + 1 >
<cfset o = StructNew()>
<cfset o['method'] = "undefined">
<cfset o['id'] = "op#OperationIntanceCount#">
<cfset o['params'] = StructNew()>
<cfreturn o>
</cffunction>
<cffunction name="GetCapabilitiesOperation" output="No">
<!--- RETURNS a Capabilities Operation --->
<!--- There is always one of these send back with our operations response --->
<cfset o = NewOperation()>
<!--- Example :
"method":"undefined",
"id":"op1",
"params":{"protocolVersion":"0.21","capabilitiesHash":"ffffff79d8b7888c"}
},
--->
<cfset o['method'] = "robot.notifyCapabilitiesHash">
<cfset o['params']['protocolVersion'] = "#RobotProtocolVersion#">
<cfset o['params']['capabilitiesHash'] = "#VersionHash#">
<cfreturn o>
</cffunction>
<cffunction name="GetQueuedOperationsJSON" output="No" returntype="String">
<!--- Queue the operation to be returned when the page returns --->
<!--- GENERATE THE RESPONSE --->
<!--- LOG ---><cflog text="GetQueuedOperationsJSON: #ArrayLen(OperationsArray)# Operation(s) to return :">
<cfloop Index="i" From="1" To="#ArrayLen(OperationsArray)#">
<cfset o = OperationsArray[i]>
<!--- LOG ---><cflog text="-- Operation: #i# #o.Method# as json:#CustomSerializeJSON(o)#">
</cfloop>
<cfset operationsArrayJSON = CustomSerializeJSON(OperationsArray)>
<cfreturn operationsArrayJSON>
</cffunction>
<cffunction name="GetBlipContent" output="No" returntype="String">
<cfargument name="blipID" type="String" required="Yes">
<!--- Return Blip.Content for the first blip --->
<cfscript>
try {
return RequestMessageBundle.blips[blipId].content;
} catch (Any e) {
return "";
}
</cfscript>
</cffunction>
<cffunction name="GetWavelet" output="No" returntype="Struct">
<!--- RETURNS a new wavelet struct --->
<!--- Example :
{
"waveId":"googlewave.com!w+jH65e-xWW",
"waveletId":"googlewave.com!conv+root",
}
--->
<!---
Get the Wavelet object from the Message Bundle
It looks something like this
"wavelet": {
"creationTime": 1255934856713,
"creator": "user@example.com",
"lastModifiedTime": 1255935016481,
"participants": [ "user@example.com","user2@example.com" ],
"rootBlipId": "b+ja8F_Hw4J",
"title": "",
"version": 11,
"waveId": "example.com!w+ja8F_Hw4I",
"waveletId": "example.com!conv+root",
"dataDocuments": { }
}
--->
<cfset w = StructNew()>
<cfset messageWavelet = RequestMessageBundle['wavelet']>
<cfif IsStruct(messageWavelet) >
// Create the return structure
<cfset w['waveId'] = messageWavelet.waveId>
<cfset w['waveletId'] = messageWavelet.waveletId>
<cfelse>
<!--- LOG ---><cflog text="GetWavelet: *** ERROR *** Cant get Wavelet">
</cfif>
<cfreturn w>
</cffunction>
<cffunction name="NewBlip" output="No" returntype="Struct">
<!--- RETURNS a new Blip struct --->
<cfargument name="blipID" type="String" required="Yes">
<cfargument name="wavelet" type="Struct" required="Yes">
<cfargument name="reply" type="String" required="Yes">
<!--- Example :
{
"blipId":"TBD_googlewave.com!conv+root_-1113654217",
"annotations":[],
"elements":{},
"childBlipIds":[],
"contributors":[],
"content":"\nHi everybody, I just got added to the wave.",
"lastModifiedTime":-1,
"version":-1,
"waveId":"googlewave.com!w+N-PjdA1UX",
"waveletId":"googlewave.com!conv+root"
}
--->
<cfscript>
blip = StructNew();
blip['blipId'] = "#blipID#"; // Not sure if this should be the same
blip['annotations'] = ArrayNew(1);
blip['elements'] = StructNew();
blip['childBlipIds']= ArrayNew(1);
blip['contributors']= ArrayNew(1);
blip['content'] = Chr(10) & "#reply#"; // Must start with a newline
blip['lastModifiedTime'] = -1;
blip['version'] = -1;
blip['waveId'] = wavelet.waveId;
blip['waveletId']= wavelet.waveletId;
return blip;
</cfscript>
</cffunction>
<cffunction name="WaveletReply" output="No">
<cfargument name="reply" type="String" required="Yes">
<cfargument name="event" type="Struct" required="Yes">
<!--- Append a reply to the current wavelet --->
<!--- Example Operation looks like this
{ "method":"wavelet.appendBlip",
"id":"op2",
"params":{ "waveId":"googlewave.com!w+jH65e-xWW",
"waveletId":"googlewave.com!conv+root",
"blipData":{"annotations":[],
"elements":{},
"blipId":"TBD_googlewave.com!conv+root_1771192937",
"childBlipIds":[],
"contributors":[],
"creator":null,
"content":"\nHi everybody! MY JOB IS TO STOP EVERYONE FROM SHOUTING",
"lastModifiedTime":-1,
"parentBlipId":null,
"version":-1,
"waveId":"googlewave.com!w+jH65e-xWW",
"waveletId":"googlewave.com!conv+root"}
}
} --->
<cfscript>
// Start with an empty operation
o = NewOperation();
// Set the method
o['method'] = "wavelet.appendBlip";
// Set the Wave and Wavelet
w = GetWavelet();
o['params']['waveId'] = w.waveID;
o['params']['waveletId'] = w.waveletId;
// Add the Blip with our reply in it
// blipID = event.properties.blipID; // get the blipID from the event
blipID = "TBD_#w.waveletId#_-239145996"; // TBD_googlewave.com!conv+root_-239145996
o['params']['blipData'] = NewBlip(blipID, w, reply);
// Add it to the queue
QueueOperation(o);
</cfscript>
<cfreturn >
</cffunction>
<!--- Utilities --->
<cffunction name="CustomSerializeJSON" output="No" returntype="String">
<cfargument name="o" required="Yes">
<!--- OpenBD can lowercase JSON serializations http://wiki.openbluedragon.org/wiki/index.php/SerializeJSON() --->
<!--- <cfreturn serializeJSON(o, false, true)> --->
<!--- I ended up using this JSON serializer that honours the case of the keys (e.g. waveletId) --->
<!--- http://www.epiphantastic.com/cfjson/ --->
<cfinvoke component="json"
method="encode"
data= "#o#"
returnvariable="returnJSON" />
<cfreturn returnJSON>
</cffunction>
<!--- --------------------------------------------------------------------- --->
<cfscript>
/*
* MAIN PAGE EXECUTION
*/
// --- UNPACK THE MESSAGE BUNDLE ---
RequestMessageBundle = UnpackJSONRequest(); // sets the global RequestMessageBundle
// --- Initialise the Operations Queue ---
InitOperationsQueue();
// --- Handle the Events ---
ProcessEvents();
</cfscript>
<!--- Get the return Operations --->
<!--- LOG ---><cflog text="GetQueuedOperationsJSON: ">
<cfset ResponseOperationsJSON = GetQueuedOperationsJSON() >
<!--- Return Page Output --->
<!--- LOG ---><cflog text=">>>> We are done ! Returning Operations as JSON: #ResponseOperationsJSON# ">
</cfsilent><cfoutput>#ResponseOperationsJSON#</cfoutput>
<cfcatch type="any"><cfoutput>[{"error":"Please POST me a JSON Message Bundle"}]</cfoutput>
<!--- LOG ---><cflog text="******************** cfcatch caught error: #cfcatch.Message# ********************************">
</cfcatch>
</cftry>

2 comments:
Interesting stuff. Thanks for posting this, Johno.
hey its reall such a nice
Post a Comment