The idea with callbacks is to provide an alternative to service contracts that doesn't involve the database. Some of the background is in this thread and this older thread. The latter includes some code.
For reasons we'll explain, callbacks will be called through
ad_proc
s marked with a new -callback_handler
flag. As an example invocation, in the core code for
member-state-change.tcl
where .LRN installations might
call extra .LRN specific code such as
we'll now call something that looks more like thisnotification::request::delete_all_for_user -user_id $user_id dotlrn_community::remove_user_from_all -user_id $user_id
It's::acs_user::changing_state $user_id $old_state $new_state
::acs_user::changing_state
's responsibility to
identify and call implementing procs. Typically, or maybe always,
this will be handled using a new callback
utility.
dotlrn
package to take an action
when the user's state is changing. To hook in, I'll create a
namespace and proc that match the callback handler.
Thead_proc -private dotlrn::callback::acs_user::changing_state { user_id old_state new_state } { docstring } { if (blah blah) { notification::request::delete_all_for_user -user_id $user_id dotlrn_community::remove_user_from_all -user_id $user_id } }
dotlrn
prefix keeps our code separate from other
implementing packages, and from having a situation where multiple
packages are creating code in the same namespace (an accident waiting
to happen). The callback
qualifier helps our
introspection code find callbacks. The remaining path is an exact
match to the callback we're implementing.
-callback_handler
?
We want to document the callback entry points. We have a standard way
to document our code in OpenACS -- ad_proc
! The proposal
here is to use new flag to mark the callback handler procs, so that we
can create a page of all callbacks.
On to the callback handler proc. Basically these will all call the same utility function.
The utility proc is calledad_proc -public -callback_handler ::acs_user::changing_state { user_id old_state new_state } { Documentation about this callback. } { callback [info level 0] }
callback
, and it will actually
do the lookups and evaling. A preliminary version of this utility is
listed below.
As part of implementing callback
we'll have a
procedure that lists all matching procs. We can use this on a special
api-doc
page to list out all implementors for a given
callback.
I have some preliminary tcl introspection code pasted in below. We still have to
-callback_handler
flag and api-doc
pages.proc get_callback_namespaces ns { set results [list] set children [namespace children $ns] foreach child $children { if { [string equal [namespace tail $child] callback] } { set results [concat $results [namespace children $child]] } else { set results [concat $results [get_callback_namespaces $child]] } } return $results } proc get_callback_functions name { set callbacks [list] set func [namespace tail $name] set qual [namespace qualifiers $name] foreach ns [get_callback_namespaces ::] { if { ![string match "*::callback::$qual" $ns] } { continue } set procs [namespace eval $ns {info procs}] if { [lsearch $procs $func] >= 0 } { lappend callbacks "${ns}::$func" } } return $callbacks } proc callback args { set callback_name [lindex $args 0] set extra_args [lrange $args 1 end] foreach func [get_callback_functions $callback_name] { #eval with args ns_log Notice "ag: calling callback $func with args $extra_args" } } #test code namespace eval ::impl1::callback::acs_user { proc change_state {old_state new_state} { } } namespace eval ::impl2::callback::acs_user { proc other_proc args { } } namespace eval ::impl3 { proc change_state {old_state new_state} { } } namespace eval ::impl4::callback::acs_user { proc change_state {old_state new_state} { } } # impl1 and impl4 but *not* impl3 because impl3 doesn't have "callback" # in the namespace path callback acs_user::change_state # impl2 callback acs_user::other_proc # none callback foobar::change_state
I wonder if we shouldn't use: ::callback:::: ::callback:: :: ::impl:: eg ::callback::acs_user::changing_state ::callback::acs_user::changing_state::impl:: so that for a given callback you would already know the namespace to inspect. we could then make ad_proc -callback acs_user::change_state { ... } and ad_proc -callback acs_user::change_state -implementation dotlrn { ... } Just declare things in the namespaces we want. Then the introspection just becomes [info procs ::callback::acs_user::changing_state::impl::*] rather than walking the namespaces.