Wiki source code of ERIMAdaptor Framework

Last modified by Pascal Robert on 2012/07/19 21:12

Hide last authors
Pascal Robert 6.1 1 == Overview ==
Pascal Robert 2.1 2
Pascal Robert 8.1 3 ERIMAdaptor provides an Instant Messenger interface to your application using standard WOComponents. The architecture of this adaptor is somewhat different than the normal HTTP adaptor, because of inherent limitations in the IM interfaces like AOL IM:
Pascal Robert 2.1 4
Pascal Robert 8.1 5 * WOSessions are tracked by buddy name. When a buddy contacts the server's IM account, a Conversation is initiated. A Conversation associates a buddy name with the WOSession ID. Conversations have a configurable expiration, but the default is to expire after 5 minutes of inactivity. The WOSession will have the same expiration as they normally would, but the connection between the buddy name and that session will go away, effectively expiring the session's usefulness.
6 * Different IM networks may impose additional restrictions on your components. For instance, most networks limit the number of bytes that can be transferred in a message. Currently the framework doesn't auto-split, but that's an obvious enhancement that will probably be added in.
7 * Some features of WORequest and WOResponse may not behave exactly like the HTTP counterpart. For instance, there are no normal HTTP headers in the WORequest.
Pascal Robert 6.1 8 * The buddy name and message appear as form values and in the request userInfo dictionary - use whichever is more convenient.
Pascal Robert 2.1 9 * The Conversation object is accessible via the request userInfo dictionary if you need direct access (to force expiration, etc).
Pascal Robert 8.1 10 * The implementation is TEMPORARILY single threaded for all conversations. This will change soon, I just haven't had time to test it.
Pascal Robert 2.1 11
Pascal Robert 8.1 12 Currently there are implementions of the IInstantMessengerFactory for AIM using the jaimbot and daim libraries. Jaimbot is available from [[http:~~/~~/jaimbot.sourceforge.net>>url:http://jaimbot.sourceforge.net||shape="rect"]] and daim is available from [[http:~~/~~/daim.dev.java.net>>url:http://daim.dev.java.net||shape="rect"]] if you're interested in getting more information on either library.
Pascal Robert 2.1 13
Pascal Robert 6.1 14 == How to use it ==
Pascal Robert 2.1 15
16 ERIMAdaptor is a custom adaptor, so it can be added using the WOAdditionalAdaptors option on your WO:
17
Pascal Robert 6.1 18 {{code}}
Pascal Robert 2.1 19
Pascal Robert 6.1 20 -WOAdditionalAdaptors ({WOAdaptor="er.imadaptor.InstantMessengerAdaptor";})
Pascal Robert 2.1 21
Pascal Robert 6.1 22 {{/code}}
Pascal Robert 2.1 23
Pascal Robert 6.1 24 === Properties ===
Pascal Robert 2.1 25
26 There are several new settings that can (or must) appear in your Properties file as well:
27
Pascal Robert 8.1 28 * IMFactory (optional, default er.imadaptor.AimBotInstantMessenger$Factory) - the factory class for creating IM connections. If you want to provide a new type of IM network, you should implement the IInstantMessengerFactory interface for whatever class you provide here.
Pascal Robert 2.1 29 * IMScreenName (required) - the screen name of the IM account that the server should login with
30 * IMPassword (required) - the password of the IM account that the server should login with
31 * IMTimeout (optional, default 5 minutes) - the conversation timeout time in milliseconds
Pascal Robert 8.1 32 * IMAutoLogin (optional, default "true") - whether or not the adaptor should autologin to AIM. If false, you must call adaptor.connect()
33 * IMConversationActionName (optional, default "imConversation") - the name of the DirectAction to call when a Conversation is initiated, the default is that you must create a public WOActionResults imConversationAction() { .. } method in your DirectAction class
34 * IMWatcherEnabled (optional, default "false") - whether or not you want to have a second AIM account login and watchdog the first. Most of the AIM libraries can have issues with getting kicked off after a period of time. If you have two AIM accounts, they can keep eachother alive.
Pascal Robert 2.1 35 * IMWatcherFactory (optional, default same as IMFactory) - if IMWatcherEnabled, the factory class to use
36 * IMWatcherScreenName (optional, required if IMWatcherEnabled) - the screen name of the watcher IM account
37 * IMWatcherPassword (optional, required if IMWatcherEnabled) - the password of the watcher IM account
38
Pascal Robert 6.1 39 === Request ===
Pascal Robert 2.1 40
41 In your code, the following request headers are available:
42
43 * IsIM - Boolean.TRUE if the current request is an IM request (you can call InstantMessengerAdaptor.isIMRequest(WORequest) also)
44 * IMConversation - the Conversation associated with this request
45 * BuddyName - the name of the buddy that initiated the request
46 * Message - the message sent by the user
47
48 The following form values are available in the request:
49
50 * BuddyName - the name of the buddy that initiated the request
51 * Message - the message sent by the user
52
Pascal Robert 6.1 53 == DirectAction ==
Pascal Robert 2.1 54
Pascal Robert 8.1 55 To kick things off, you must have a direct action method that matches the value of IMConversationActionName (which defaults to "imConversation"). This method should return the the first "page" of the IM conversation.
Pascal Robert 2.1 56
57 For example:
58
Pascal Robert 6.1 59 {{code}}
Pascal Robert 2.1 60
Pascal Robert 6.1 61 public WOActionResults imConversationAction() {
62 return pageWithName(SayHelloPage.class.getName());
63 }
Pascal Robert 2.1 64
Pascal Robert 6.1 65 {{/code}}
Pascal Robert 2.1 66
Pascal Robert 6.1 67 == IMAction ==
Pascal Robert 2.1 68
Pascal Robert 8.1 69 For each request, the IM Adaptor needs to know what action to call when the subsequent request comes in. In a normal WOComponent, think of this as each page having a "Next" button on it that is clicked each time a request comes in. Instead of using a WOSubmitButton or a WOHyperlink, you instead use the IMAction element. This element has one attribute - "action" that points to the action method to call on your component. Because of inherent limitations in an instant messenging interface as an interaction method, only one IMAction can be triggered per-page (the last one that is evaluated on the page, specifically). This makes sense when you think of the user's response as the action being performed. There is no semantic equivalent of "multipleSubmit" on WOForm.
Pascal Robert 2.1 70
Pascal Robert 8.1 71 IMAction is the simplest action you can use, though there are several common response actions, and ERIMAdaptor provides default action implementations for them. Here is an example of a very simple conversation that uses IMAction (that will just continue to say the same thing over and over again):
Pascal Robert 2.1 72
73 Your Java component would look something like this:
74
Pascal Robert 6.1 75 {{code}}
76 public class IMComponent extends WOComponent {
77 public boolean userResponded;
Pascal Robert 2.1 78
Pascal Robert 6.1 79 ...
Pascal Robert 8.1 80 {{/code}}
Pascal Robert 2.1 81
Pascal Robert 6.1 82 {{code}}
Pascal Robert 2.1 83
Pascal Robert 6.1 84 public String buddyName() {
85 return InstantMessengerAdaptor.buddyName(context().request());
86 }
Pascal Robert 2.1 87
Pascal Robert 6.1 88 public WOActionResults processResponse() {
89 userResponded = true;
90 return null;
91 }
92 }
Pascal Robert 2.1 93
Pascal Robert 6.1 94 {{/code}}
95
Pascal Robert 2.1 96 The HTML would be:
97
Pascal Robert 6.1 98 {{code}}
Pascal Robert 2.1 99
Pascal Robert 6.1 100 <webobject name = "FirstContactConditional">
101 Hi <webobject name = "BuddyName"></webobject>!
102 </webobject>
103 <webobject name = "UserRespondedConditional">
104 Welcome back <webobject name = "BuddyName"></webobject>!
105 </webobject>
106 <webobject name = "IMAction"></webobject>
Pascal Robert 2.1 107
Pascal Robert 6.1 108 {{/code}}
Pascal Robert 2.1 109
110 And lastly the WOD file would contain:
111
Pascal Robert 6.1 112 {{code}}
Pascal Robert 2.1 113
Pascal Robert 6.1 114 FirstContactConditional : WOConditional {
115 condition = userResponded;
116 negate = true;
117 }
Pascal Robert 2.1 118
Pascal Robert 6.1 119 UserRespondedConditional : WOConditional {
120 condition = userResponded;
121 }
Pascal Robert 2.1 122
Pascal Robert 6.1 123 BuddyName : WOString {
124 value = buddyName;
125 }
126
127 IMAction : IMAction {
128 action = processResponse;
129 }
130
131 {{/code}}
132
Pascal Robert 2.1 133 Here's what an example conversation transcript might look like:
134
Pascal Robert 6.1 135 {{code}}
Pascal Robert 2.1 136
Pascal Robert 6.1 137 Mike> Hey server!
138 Server> Hi Mike!
139 Mike> How's it going?
140 Server> Welcome back Mike!
141 Mike> Uh. OK.
142 Server> Welcome back Mike!
143 ...
Pascal Robert 2.1 144
Pascal Robert 6.1 145 {{/code}}
Pascal Robert 2.1 146
Pascal Robert 6.1 147 === IMConfirmationAction ===
Pascal Robert 2.1 148
Pascal Robert 6.1 149 IMConfirmation has a single binding "confirmed". If the response from the IM buddy matches any of a set of common "yes", "no", etc words, confirmed is set to the appropriate value. If neither a yes nor a no word is found, confirmed is set to null. You should bind this to a Boolean rather than a boolean so that you can detect the third state properly and re-ask the question. Note that this is not internationalized, so it currently has "yes", "y", "yep", "true", "no", "n", "nope", "nah" as known values.
Pascal Robert 2.1 150
Pascal Robert 6.1 151 ==== Bindings ====
Pascal Robert 2.1 152
153 * confirmed = three-state Boolean, when Boolean.TRUE the response was affirmative, when Boolean.FALSE, the response was negative, and when null, the response did not match one of the known true/false values
154 * action = the action method to execute when the response is received
155
Pascal Robert 6.1 156 ==== Example ====
Pascal Robert 2.1 157
Pascal Robert 6.1 158 {{code}}
Pascal Robert 2.1 159
Pascal Robert 6.1 160 AreYouSureAction : IMConfirmationAction {
161 confirmed = yesOrNo;
162 action = nextStep;
163 }
Pascal Robert 2.1 164
Pascal Robert 6.1 165 {{/code}}
Pascal Robert 2.1 166
Pascal Robert 6.1 167 In the above example, the binding "yesOrNo" is a Boolean that will contain whether or not the user's response was a confirmation. After processing the response, the "nextStep" action will be called so that you can evaluate the "yesOrNo" value and respond accordingly.
Pascal Robert 2.1 168
Pascal Robert 6.1 169 === IMSearchMessageAction ===
Pascal Robert 2.1 170
Pascal Robert 6.1 171 IMSearchMessageAction allows you to map substrings that appear in AIM message responses to other objects. For instance, you can pass in an options dictionary that maps the word "hi" to the object Greeting, or the word "bug" to the object BugReport. If the word "hi" appears in the aim response, it will return the matching object as its value.
Pascal Robert 2.1 172
Pascal Robert 6.1 173 ==== Bindings: ====
Pascal Robert 2.1 174
175 * value = the binding to write the first matching value into
176 * values = (at least one of "value" or "values" must be specified, but you can use both as well) the binding to write the array of all matching values into
177 * optionsDictionary = a mapping of substrings to look for in the response to arbitrary objects those substrings are associated with
178 * optionsArray = a list of known substrings to look for
179 * optionKeyPath = (only applicable for optionsArray) instead of looking for the values in optionsArray, look for the value of this key path on each value in the optionsArray; for instance, optionsArray might contains Person objects, and optionKeyPath might be "firstName", so this would match the first names of the list of the people.
180 * quicksilver = match like Quicksilver matches ("NPE" matches "NullPointerException")
181 * action = the action method to execute when the response is received
182
Pascal Robert 6.1 183 ==== Example ====
Pascal Robert 2.1 184
Pascal Robert 6.1 185 {{code}}
Pascal Robert 2.1 186
Pascal Robert 6.1 187 PickRequestTypeAction : IMSearchMessageAction {
188 value = selectedOption;
189 optionsDictionary = options;
190 action = nextStep;
191 }
Pascal Robert 2.1 192
Pascal Robert 6.1 193 {{/code}}
Pascal Robert 2.1 194
Pascal Robert 6.1 195 In the above example, "optionsDictionary" contains a mapping where the keys are the substrings to look for in the response and the values are objects to associate them with. For instance, the dictionary from our bug tracking system looks like
Pascal Robert 8.1 196 { "bug"=>BugReportType object, "request"=>ServiceRequestType object }
197 . "action" is the method to fire when the user's response comes back, and "value" will contain the value that matched the user's response.\\
Pascal Robert 2.1 198
Pascal Robert 6.1 199 When the user responds, the response will be interpreted by the provided action, the "selectedOption" field will contain whatever object matched their response, or null if there was no match, and the "nextStep" action method will be called. "nextStep" is a normal action method like any other, except it has access to the additional AIM-only request parameters as described in the Request section.
Pascal Robert 2.1 200
Pascal Robert 6.1 201 === IMSearchOptionsAction ===
Pascal Robert 2.1 202
Pascal Robert 8.1 203 IMSearchOptionsAction, IMSearchMessageAction's evil twin, allows you to search your options for the AIM response that is received (as opposed to SearchMessage that searches the AIM response for your options ~-~- the other way around). For instance, you can pass in an options dictionary that maps the word "Company XYZ" to the object CompanyXYZ, or the word "Company ABC" to the object CompanyABC. If the word "XYZ" is aimed, it will return the matching CompanyXYZ object as its value.
Pascal Robert 2.1 204
Pascal Robert 6.1 205 ==== Bindings ====
Pascal Robert 2.1 206
207 * value = the binding to write the first matching value into
208 * values = (at least one of "value" or "values" must be specified, but you can use both as well) the binding to write the array of all matching values into
209 * optionsDictionary = a mapping of substrings to look for in the response to arbitrary objects those substrings are associated with
210 * optionsArray = a list of known substrings to look for
211 * optionKeyPath = (only applicable for optionsArray) instead of looking for the values in optionsArray, look for the value of this key path on each value in the optionsArray; for instance, optionsArray might contains Person objects, and optionKeyPath might be "firstName", so this would match the first names of the list of the people.
212 * quicksilver = match like Quicksilver matches ("NPE" matches "NullPointerException")
213 * action = the action method to execute when the response is received
214
Pascal Robert 6.1 215 ==== Example ====
Pascal Robert 2.1 216
Pascal Robert 6.1 217 {{code}}
Pascal Robert 2.1 218
Pascal Robert 6.1 219 PickCompanyAction : IMSearchOptionsAction {
220 optionsArray = companies;
221 optionKeyPath = "name";
222 value = company;
223 values = companies;
224 quicksilver = true;
225 action = nextStep;
226 }
Pascal Robert 2.1 227
Pascal Robert 6.1 228 {{/code}}
Pascal Robert 2.1 229
Pascal Robert 6.1 230 The above example from our bug reporting system allows a user to select a company to associate with a bug. "optionsArray" contains an array of Company objects. "optionKeyPath" specifies that the response should be compared against the value of the "name" keypath on the Companies. The resultant first Company object that matched will put stored in the "company" binding (as defined by "value"), and if there were multiple matches, they would all appear in the "companies" list. Quicksilver matching is enabled above, and after the response is processed, and the bindings set, the "nextStep" action will be called.
Pascal Robert 2.1 231
Pascal Robert 6.1 232 === IMPickListAction ===
Pascal Robert 2.1 233
Pascal Robert 6.1 234 A very common case is that you want to present the user with a list of options and have the user pick from that list. IMPickList provides a handy implementation of this behavior. Whereas the previous actions were "headless," IMPickList actuals renders the list on its own entirely. A typical use case is to continue rendering the IMPickList until the selection is whittled down to one by the user.
Pascal Robert 2.1 235
236 An example conversation might look like:
237
Pascal Robert 6.1 238 {{code}}
Pascal Robert 2.1 239
Pascal Robert 6.1 240 Server> Pick a company:
241 Server> 1) Company XYZ
242 Server> 2) Company VWX
243 Server> 3) Company ABC
244 User> X
245 // note this matches #1 and #2
246 Server> Pick a company:
247 Server> 1) Company XYZ
248 Server> 2) Company VWX
249 User> V
250 // Company VWX is now selected
251 Server> ...
Pascal Robert 2.1 252
Pascal Robert 6.1 253 {{/code}}
Pascal Robert 2.1 254
Pascal Robert 8.1 255 or
Pascal Robert 2.1 256
Pascal Robert 6.1 257 {{code}}
Pascal Robert 2.1 258
Pascal Robert 6.1 259 Server> Pick a company:
260 Server> 1) Company XYZ
261 Server> 2) Company VWX
262 Server> 3) Company ABC
263 User> 1
264 // picking by number uniquely identifies Company XYZ
Pascal Robert 2.1 265
Pascal Robert 6.1 266 {{/code}}
Pascal Robert 2.1 267
268 or
269
Pascal Robert 6.1 270 {{code}}
Pascal Robert 2.1 271
Pascal Robert 6.1 272 Server> Pick a company:
273 Server> 1) Company XYZ
274 Server> 2) Company VWX
275 Server> 3) Company ABC
276 User> XYZ
277 // XYZ uniquely matches Company XYZ
Pascal Robert 2.1 278
Pascal Robert 6.1 279 {{/code}}
Pascal Robert 2.1 280
Pascal Robert 6.1 281 ==== Bindings ====
Pascal Robert 2.1 282
Pascal Robert 6.1 283 * displayStringKeyPath = The key path of the string to use to display each object in the list. The value should be in "quotes".
Pascal Robert 2.1 284 * list = an NSArray of the list of options to pick from
285 * quicksilver = whether or not to use Quicksilver-style matching on the user's response ("NPE" matches "NullPointerException")
286 * selections = the list of selections that matched
287 * selection = the single matching selection (if there was only one)
288 * action = the action method to execute when the response is received
289
Pascal Robert 6.1 290 ==== Example ====
Pascal Robert 2.1 291
Pascal Robert 6.1 292 {{code}}
Pascal Robert 2.1 293
Pascal Robert 7.1 294 PickProductAction : IMPickListAction {
Pascal Robert 6.1 295 list = products.@sort.name;
296 selection = product;
297 selections = products;
298 displayStringKeyPath = "name";
299 quicksilver = true;
300 action = nextStep;
301 }
Pascal Robert 2.1 302
Pascal Robert 6.1 303 {{/code}}
Pascal Robert 2.1 304
305
Pascal Robert 8.1 306 The above example has the user selecting from a list of Products (from the "list" binding). The "displayStringKeyPath" binding specifies that the "name" keypath of the Product should be displayed in the list. When the user uniquely identifies a single Product, the Product object will be bound to the "product" variable. If there are multiple matches, the NSArray of the matches will be bound to the "products" variable. Quicksilver matching is enabled. After the response is processed, the "nextStep" method will be called. Notice in this example, the "products" array is both the "list" binding AND the "selections" binding. When the page is first loaded, the "products" NSArray is initialized with all the products. The nextStep method keeps returning the same page until there is only one product selected. Until that point, the list is reset with the products that matched the user's partial response (like in the example transcript of IMPickList above).
Pascal Robert 2.1 307
Pascal Robert 6.1 308 === IMTextAction ===
Pascal Robert 2.1 309
Pascal Robert 6.1 310 IMTextAction is one of the simplest IMAction implementations. It stores the user's response in a String binding and calls an action method.
Pascal Robert 2.1 311
Pascal Robert 6.1 312 ==== Bindings ====
313
Pascal Robert 2.1 314 * value = the variable to store the user's response into
315 * allowBlanks = if false, empty strings (response.trim.length == 0) are turned into nulls
316 * action = the action method to execute when the response is received
317
Pascal Robert 6.1 318 ==== Example ====
Pascal Robert 2.1 319
Pascal Robert 6.1 320 {{code}}
Pascal Robert 2.1 321
Pascal Robert 7.1 322 AskNameAction : IMPickListAction {
Pascal Robert 6.1 323 value = userName;
324 allowBlanks = false;
325 action = nextStep;
326 }
Pascal Robert 2.1 327
Pascal Robert 6.1 328 {{/code}}
Pascal Robert 2.1 329
Pascal Robert 8.1 330 The above example stores the user's response in the "userName" variable and does not allow blank values (i.e. they turn into nulls). The "nextStep" action method is called when the response is processed.