Very interesting comment and I would be the first to use a "channel"
metaphor for routing messages to specific widgets. As some of you know
we here at WU Vienna have worked some time ago on IWC for exactly this
kind of purpose - namely to notify all widgets in a certain user space
(we are using widgets in Elgg, therefore all widgets displayed in a
dashboard of one user).
As I gave the code a review just recently I did come up with a new
solution wiring together widget instances sharing the same sharedDataKey
and apiKey (excerpt of new code attached). We are using the code right
now because it fulfills exactly our purpose, but if someone comes up
with a generic solution I would be even happier.
It is by now not entirely clear where to best put the code to: Notifier,
WidgetAPIImpl or another class because all have advantages and
disadvantages. The Notifier class (as proposed by Scott Wilson) would be
a good fit, but code there should only notify widgets and not set data.
I not only want to notify other widgets of events happening in one user
space but also share the data with all of them, therefore I have to call
PropertiesController.updateSharedDataEntry() as well to set the data for
all instances.
Therefore, I adjusted WidgetAPIImpl.setSharedDataForKey() and
appendSharedDataForKey() accordingly to notify and set data for all
widgets having the same sharedDatakey and apiKey as the widget instance
data was initially set.
It is somehow a hack, because if you want to make it generic you should
give config variables for the sibling attributes, for example in the
local.widgetserver.properties. But anyway I liked the idea of
configuring message buses using these sibling attributes because
multiple combinations could be useful.
I just wanted to share my code with you guys and maybe hear some comments.
BTW: Quite hard to find out was how you actually one can delete shared
data. By setting a variable to null, but there is no actual method for
doing this (only a hack in PropertiesController.updateSharedDataEntry()
exists, which is a not very consistent logic). Maybe a method
deleteSharedDataEntry() would be useful?
@Scott: I completely re-designed the former approach of notifying
widgets, which now does not rely on attributes set in the ScriptSession
anymore. Therefore, your code comment at the end of Notifier.java is
somehow obsolete and if needed could be dramatically simplified (or
completely deleted). Moreover, I tried to change as less as possible at
the existing code of Wookie, therefore no changes in wookie-wrapper.js
or dwr.xml or a new DWRSessionRemote.js is needed for that (as was in
the former version).
Best,
Bernhard
On 07/10/10 11:44, Scott Wilson (JIRA) wrote:
[
https://issues.apache.org/jira/browse/WOOKIE-133?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12918864#action_12918864
]
Scott Wilson commented on WOOKIE-133:
-------------------------------------
Thanks, Ivan. Your table is my number one source of info on IWC!
Implement inter-widget messaging
--------------------------------
Key: WOOKIE-133
URL: https://issues.apache.org/jira/browse/WOOKIE-133
Project: Wookie
Issue Type: New Feature
Components: Server, Wookie REST API
Reporter: Scott Wilson
Priority: Minor
Original Estimate: 168h
Remaining Estimate: 168h
One of the more persistent new feature requests we've had for Wookie has been
to extend the mechanisms for inter-widget communication (IWC). As it currently
stands, Wookie supports two mechanisms for IWC:
1. Wave Shared States
Wookie enables "sibling" widgets to share their state. This is exposed to
widgets using the Google Wave Gadget API, which enables widgets to submit state update
deltas, and to register a callback to notify them when their state has been updated by
another widget instance. We define siblings using the algorithm in
org.apache.wookie.util.SiblingPageNormalizer; essentially this is that the widget
instance must be for the same widget, with the same shared data key, and the same API key.
2. HTML5 Drag and Drop
While not actually part of Wookie itself, Widgets can be developed using HTML5
drag and drop capability, enabling user-directed IWC.
Some use-cases for IWC have been collected on the Talk About Widgets mailing
list.
The most common use-cases for extending IWC proposed for Wookie I've seen involve "Dashboard"-style
messaging. In this model, widgets appearing in the same space for the user get to share events. For example, all the
widgets in a single user's dashboard can send data to each other. This might follow a single shared state model, but is
more likely to follow a "channel" metaphor, with named "channels" or "queues" between
widgets. There are several ways this could be implemented, for example:
A. User-specified channels
In this model, the user specifies exactly which messages are sent between widgets. For example, the EzWeb project defines a
"wiring" interface with "slots" and "events" connected with user-created "channels". For example,
the user create a channel from a widget with a "weather" event to a widget with a "weather" slot.
B. Widget-specified channels
In this model, widgets are automatically able to receive any events on any
channel that they choose to listen to. Users do not need to create channels to
enable IWC.
These two mechanisms are not necessarily exclusive, and could be supported
within the same Feature extension. For example, the JavaScript API may look
like this:
iwc.sendEvent(String event_name, Object event_value)
iwc.registerCallback(String slot_name, Function callback)
I would prefer having this IWC extension use its own functions in its own
object rather than overload the Wave and Widget objects, to avoid any possible
confusion.
In case "A", callbacks are only triggered where there are explicit channels linking the
sending event and the receiving slot; in case "B", events are propagated to all
registered callbacks that the event_name matches.
For example, in pseudocode, the implementation could look something like:
sendEvent(event_name, value, idkey){
instance = find widget instance (idkey)
if use_channels:
event = find event(instance.getWidget, event_name)
find channels (eventinstance = instance& event =
event_name)
for channel in channels:
slot = channel.slot
send notification to slot (target, slot, value)
Notifier.notifySingleInstance(target,"iwc.__callback({slot},{value})")
else:
Notifier.notifySiblingsByUser(instance,"iwc.__callback({event_name},{value})")
}
Note that in case "A", the widget author just registers callbacks for its slots without having to
be concerned with what the sending widget calls them, so if a user can wire a "weather" event to a
"temperature" slot, the sending widget calls:
sendEvent("weather","30C");
and the receiver can call:
registerCallback("temperature",my_function);
... and the channel wires things up despite the names not matching. In case B,
the receiving widget would have to know the name of the event, and register a
callback.
In case A, widgets need to declare their "events" and "slots" as extensions in
config.xml so they can be wired up in channels by a user, and there needs to be some sort of UI
where users get to do some wiring. Wookie could expose an API for creating/removing/editing
channels that could be implemented by containers rather than provide this UI itself.
(Interestingly, case A would in theory support situations where widgets send
notifications to widgets in different containers; though I think this would
only really work if we went did the OpenID implementation so could have some
assurance that the source and target widget instances belonged to the same
user).
[1] http://groups.google.com/group/talk-about-widgets/web/use-cases-for-iwc
[2] http://forge.morfeo-project.org/wiki/index.php/Gadget_development_guide
/*
* (non-Javadoc)
* @see org.apache.wookie.ajaxmodel.IWidgetAPI#setSharedDataForKey(java.lang.String, java.lang.String, java.lang.String)
*/
public String setSharedDataForKey(String id_key, String key, String value) {
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
Messages localizedMessages = LocaleHandler.localizeMessages(request);
IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
IWidgetInstance widgetInstance = persistenceManager.findWidgetInstanceByIdKey(id_key);
if(widgetInstance == null) return localizedMessages.getString("WidgetAPIImpl.0");
if(widgetInstance.isLocked()) return localizedMessages.getString("WidgetAPIImpl.2");
Map<String, Object> siblingWidgetAttributes = new HashMap<String, Object>();
siblingWidgetAttributes.put("sharedDataKey", widgetInstance.getSharedDataKey());
siblingWidgetAttributes.put("apiKey", widgetInstance.getApiKey());
IWidgetInstance[] allSiblingWidgetInstances = persistenceManager.findByValues(IWidgetInstance.class, siblingWidgetAttributes);
for (IWidgetInstance instance : allSiblingWidgetInstances) {
if(instance == null) return localizedMessages.getString("WidgetAPIImpl.0");
if(instance.isLocked()) return localizedMessages.getString("WidgetAPIImpl.2");
PropertiesController.updateSharedDataEntry(instance, key, value, false);
Notifier.notifySiblings(instance);
}
return "okay"; //$NON-NLS-1$
}