%%%----------------------------------------------------------------------------- %%% @copyright (C) 2010-2019, 2600Hz %%% @doc Store routing keys/pid bindings. When a binding is fired, %%% pass the payload to the pid for evaluation, accumulating %%% the results for the response to the running process. %%% %%% ``` %%% foo.erl -> bind("module.init"). %%% *** Later *** %%% module.erl %%% init() -> run("module.init", {some, "payload", 4, <<"You">>}). %%% foo ! Payload, %%% receive -> Resp %%% init() <- [Resp] %%% init() -> Decides what to do with responses %%% ''' %%% %%% @author James Aimonetti %%% @author Karl Anderson %%% @author Ben Wann %%% @end %%%----------------------------------------------------------------------------- -module(blackhole_bindings). %% API -export([bind/3,bind/4 ,unbind/3,unbind/4 ,map/2, map/3 ,pmap/2, pmap/3 ,fold/2 ,flush/0, flush/1, flush_mod/1 ,filter/1 ,modules_loaded/0 ,init/0, init_mod/1 ,bindings/0, bindings/1 ]). -export([start_link/0]). %% Helper Functions for Results of a map/2 -export([any/1 ,all/1 ,succeeded/1 ,failed/1 ]). -include("blackhole.hrl"). -type payload_el() :: bh_context:context() | kz_term:ne_binary() | map() | kz_json:object(). -type payload() :: [payload_el()] | payload_el(). %%%============================================================================= %%% API %%%============================================================================= -type map_results() :: list(). %% return `[{Result, Payload1}]', a list of tuples, the first element %% of which is the result of the bound handler, and the second element %% is the payload, possibly modified -type kz_bindings() :: list(). %% return `[{Result, Payload1}]', a list of tuples, the first element %% of which is the result of the bound handler, and the second element %% is the payload, possibly modified %%------------------------------------------------------------------------------ %% @doc return the modified Payload after it has been threaded through %% all matching bindings %% @end %%------------------------------------------------------------------------------ -type fold_results() :: payload(). -spec fold(kz_term:ne_binary(), payload()) -> fold_results(). fold(Routing, Payload) -> kazoo_bindings:fold(Routing, Payload). %%------------------------------------------------------------------------------ %% @doc Helper functions for working on a result set of bindings %% @end %%------------------------------------------------------------------------------ -spec any(kz_term:proplist()) -> boolean(). any(Res) when is_list(Res) -> kazoo_bindings:any(Res, fun check_bool/1). -spec all(kz_term:proplist()) -> boolean(). all(Res) when is_list(Res) -> kazoo_bindings:all(Res, fun check_bool/1). -spec failed(map_results()) -> map_results(). failed(Res) when is_list(Res) -> kazoo_bindings:failed(Res, fun filter_out_succeeded/1); failed(Res)-> failed([Res]). -spec succeeded(map_results()) -> map_results(). succeeded(Res) when is_list(Res) -> kazoo_bindings:succeeded(Res, fun filter_out_failed/1); succeeded(Res)-> succeeded([Res]). %%------------------------------------------------------------------------------ %% @doc Helpers for the result set helpers %% @end %%------------------------------------------------------------------------------ -spec check_bool({boolean(), any()} | boolean()) -> boolean(). check_bool({'true', _}) -> 'true'; check_bool('true') -> 'true'; check_bool(_) -> 'false'. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec filter_out_failed({boolean() | 'halt', any()} | boolean() | any()) -> boolean(). filter_out_failed({'true', _}) -> 'true'; filter_out_failed('true') -> 'true'; filter_out_failed({'halt', _}) -> 'true'; filter_out_failed({'false', _}) -> 'false'; filter_out_failed('false') -> 'false'; filter_out_failed({'EXIT', _}) -> 'false'; filter_out_failed(#bh_context{}=Ctx) -> not bh_context:success(Ctx); filter_out_failed([#bh_context{}=Ctx]) -> not bh_context:success(Ctx); filter_out_failed(Term) -> not kz_term:is_empty(Term). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec filter_out_succeeded({boolean() | 'halt', any()} | boolean() | any()) -> boolean(). filter_out_succeeded({'true', _}) -> 'false'; filter_out_succeeded('true') -> 'false'; filter_out_succeeded({'halt', _}) -> 'true'; filter_out_succeeded({'false', _}) -> 'true'; filter_out_succeeded('false') -> 'true'; filter_out_succeeded({'EXIT', _}) -> 'true'; filter_out_succeeded(#bh_context{}=Ctx) -> bh_context:success(Ctx); filter_out_succeeded([#bh_context{}=Ctx]) -> bh_context:success(Ctx); filter_out_succeeded(Term) -> kz_term:is_empty(Term). -type bind_result() :: 'ok' | {'error', 'exists'}. -type bind_results() :: [bind_result()]. -spec bind(kz_term:ne_binary() | kz_term:ne_binaries(), atom(), atom()) -> bind_result() | bind_results(). bind(Bindings, Module, Fun) -> bind(Bindings, Module, Fun, 'undefined'). -spec bind(kz_term:ne_binary() | kz_term:ne_binaries(), atom(), atom(), any()) -> bind_result() | bind_results(). bind([_|_]=Bindings, Module, Fun, Payload) -> [bind(Binding, Module, Fun, Payload) || Binding <- Bindings]; bind(Binding, Module, Fun, Payload) when is_binary(Binding) -> kazoo_bindings:bind(Binding, Module, Fun, Payload). -spec unbind(kz_term:ne_binary() | kz_term:ne_binaries(), atom(), atom()) -> 'ok'. unbind(Bindings, Module, Fun) -> unbind(Bindings, Module, Fun, 'undefined'). -spec unbind(kz_term:ne_binary() | kz_term:ne_binaries(), atom(), atom(), any()) -> 'ok'. unbind([_|_]=Bindings, Module, Fun, Payload) -> _ = [unbind(Binding, Module, Fun, Payload) || Binding <- Bindings], 'ok'; unbind(Binding, Module, Fun, Payload) when is_binary(Binding) -> _ = kazoo_bindings:unbind(Binding, Module, Fun, Payload), 'ok'. -spec filter(kazoo_bindings:filter_fun()) -> 'ok'. filter(Predicate) -> kazoo_bindings:filter(Predicate). -spec flush() -> 'ok'. flush() -> lists:foreach(fun kazoo_bindings:flush_mod/1, modules_loaded()). -spec flush(kz_term:ne_binary()) -> 'ok'. flush(Binding) -> kazoo_bindings:flush(Binding). -spec flush_mod(kz_term:ne_binary() | atom()) -> 'ok'. flush_mod(BHMod) when is_binary(BHMod) -> flush_mod(kz_term:to_atom(BHMod, 'true')); flush_mod(BHMod) -> kazoo_bindings:flush_mod(BHMod). -spec modules_loaded() -> kz_term:atoms(). modules_loaded() -> lists:usort( [Mod || Mod <- kazoo_bindings:modules_loaded(), is_bh_module(Mod) ]). -spec is_bh_module(kz_term:ne_binary() | atom()) -> boolean(). is_bh_module(<<"bh_", _/binary>>) -> 'true'; is_bh_module(<<"blackhole_", _/binary>>) -> 'true'; is_bh_module(<<_/binary>>) -> 'false'; is_bh_module(Mod) -> is_bh_module(kz_term:to_binary(Mod)). -spec init() -> 'ok'. init() -> lager:debug("initializing blackhole bindings"), kz_util:put_callid(?DEFAULT_LOG_SYSTEM_ID), Mods = lists:usort(blackhole_config:autoload_modules() ++ ?COMMAND_MODULES), lists:foreach(fun init_mod/1, Mods). -spec init_mod(kz_term:ne_binary() | atom()) -> 'ok' | {'error', 'undefined' | 'unknown'}. init_mod(ModuleName) -> lager:debug("initializing module: ~p", [ModuleName]), maybe_init_mod(ModuleName). -spec maybe_init_mod(kz_term:ne_binary() | atom()) -> 'ok' | {'error', 'undefined' | 'unknown'}. maybe_init_mod(ModuleName) -> lager:debug("trying to init module: ~p", [ModuleName]), try (kz_term:to_atom(ModuleName, 'true')):init() of _ -> 'ok' catch 'error':'undef' -> lager:warning("failed to find module ~s", [ModuleName]), {'error', 'undefined'}; _E:_R -> lager:warning("failed to initialize ~s: ~p, ~p.", [ModuleName, _E, _R]), {'error', 'unknown'} end. -spec bindings() -> kazoo_bindings:kz_bindings(). bindings() -> bindings(<<"blackhole.#">>). -spec bindings(kz_term:ne_binary()) -> kazoo_bindings:kz_bindings(). bindings(Routing) -> RTOptions = [{'matches', fun bh_match/2}], kazoo_bindings:bindings(Routing, RTOptions). %%------------------------------------------------------------------------------ %% @doc Match routing patterns. `*' matches one slot, `#' matches zero or more. %% For example pattern `<<"#.6.*.1.4.*">>' can match `<<"6.a.a.6.a.1.4.a">>'. %% %%