deb-murano/meta/io.murano.applications/Classes/events.yaml
Alexander Tivelkov e853cf861b Event Notification pattern implemented
This patch adds an 'Event' class which may be used to issue various
notifications to other MuranoPl classes in an Event-driven manner.

Any object which is going to emit the notifications should declare the
instances of this new class as its public Runtime properties. The
objects going to subscribe for the notifications should pass
themselves into the 'subscribe' method of the Event along with the
names of their methods which will be used to handle the notification.

The specified handler methods must be present in the subscriber class
(if the method name is missing it will be defaulted to the
'handle%Eventname%') and has at least one standard (i.e. non-vararg
or kwarg) argument.

The class going to emit the notification should call the 'notify'
method of the event and pass itself as the first argument. All the
optional parameters of the event may be passed as varargs/kwargs of
the 'notify' call and will be passed all the way to the handler
methods.

Since this approach relies on the reflection this patch also fixes a
bug #1596647 since its fix is required for the argument reflection to
work properly. It also documents new reflection capabilities which
were added as part of the bugfix.

Targets-blueprint: application-development-framework
Closes-Bug: #1596647
Change-Id: Ifa7053e4c7b8456030e8df743f57ed812104b064
2016-07-28 18:50:04 +03:00

105 lines
3.1 KiB
YAML

Namespaces:
=: io.murano.applications
std: io.murano
py: # empty, for python-originating exceptions
--- # ------------------------------------------------------------------ # ---
Name: Event
Properties:
name:
Contract: $.string().notNull()
Methods:
.init:
Body:
- $this._handlers: {}
subscribe:
Arguments:
- subscriber:
Contract: $.class(std:Object).notNull()
- methodName:
Contract: $.string()
Body:
- If: not $methodName
Then:
- $methodName: format('handle{0}', $this.name.substring(0,1).toUpper()+
$this.name.substring(1))
- Try:
- $method: typeinfo($subscriber).methods.where($.name = $methodName).single()
Catch:
With: py:StopIteration
Do:
- Throw: NoHandlerMethodException
Message: format('Unknown method {0} for
receiver {1} to handle event {2}',
$methodName, $subscriber, $this.name)
# This check ensures that the method passed as a handler has at least one
# standard (i.e. non vararg or kwarg) argument which is supposed to be
# "sender" object of the event.
# Although having the sender in the handler is not always nessesary it's
# still better to enforce its presence since it helps to prevent many
# hard-to-debug errors
- If: not $method.arguments.where($.usage=Standard).any()
Then:
- Throw: WrongHandlerMethodException
Message: format("Method {0} of handler {1} should accept at least
a 'sender' argument to handle event {2}",
$methodName, $subscriber, $this.name)
- $key: list($subscriber, $methodName)
- $this._handlers[$key]: $this._handlers.get($key, 0) + 1
unsubscribe:
Arguments:
- subscriber:
Contract: $.class(std:Object).notNull()
- methodName:
Contract: $.string()
Body:
- If: not $methodName
Then:
- $methodName: format('handle{0}', $this.name.substring(0,1).toUpper()+
$this.name.substring(1))
- $key: list($subscriber, $methodName)
- If: $key in $this._handlers.keys()
Then:
- $this._handlers[$key]: $this._handlers[$key] - 1
- If: $this._handlers[$key] = 0
Then:
- $this._handlers: $this._handlers.delete($key)
notify:
Arguments:
- sender:
Contract: $.notNull()
- args:
Contract: $
Usage: VarArgs
- kwargs:
Contract: $
Usage: KwArgs
Body:
- $combinedArgs: list($sender) + $args
- $this._handlers.keys().select(call($[1], $combinedArgs, $kwargs, $[0]))
notifyInParallel:
Arguments:
- sender:
Contract: $.notNull()
- args:
Contract: $
Usage: VarArgs
- kwargs:
Contract: $
Usage: KwArgs
Body:
- $combinedArgs: list($sender) + $args
- $this._handlers.keys().pselect(call($[1], $combinedArgs, $kwargs, $[0]))