emqttd_hooks.erl 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2013-2017 EMQ Enterprise, Inc. (http://emqtt.io)
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqttd_hooks).
  17. -behaviour(gen_server).
  18. -author("Feng Lee <feng@emqtt.io>").
  19. %% Start
  20. -export([start_link/0]).
  21. %% Hooks API
  22. -export([add/3, add/4, delete/2, run/2, run/3, lookup/1]).
  23. %% gen_server Function Exports
  24. -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
  25. terminate/2, code_change/3]).
  26. -record(state, {}).
  27. -record(callback, {function :: function(),
  28. init_args = [] :: list(any()),
  29. priority = 0 :: integer()}).
  30. -record(hook, {name :: atom(), callbacks = [] :: list(#callback{})}).
  31. -define(HOOK_TAB, mqtt_hook).
  32. start_link() ->
  33. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  34. %%--------------------------------------------------------------------
  35. %% Hooks API
  36. %%--------------------------------------------------------------------
  37. -spec(add(atom(), function(), list(any())) -> ok).
  38. add(HookPoint, Function, InitArgs) ->
  39. add(HookPoint, Function, InitArgs, 0).
  40. -spec(add(atom(), function(), list(any()), integer()) -> ok).
  41. add(HookPoint, Function, InitArgs, Priority) ->
  42. gen_server:call(?MODULE, {add, HookPoint, Function, InitArgs, Priority}).
  43. -spec(delete(atom(), function()) -> ok).
  44. delete(HookPoint, Function) ->
  45. gen_server:call(?MODULE, {delete, HookPoint, Function}).
  46. %% @doc Run hooks without Acc.
  47. -spec(run(atom(), list(Arg :: any())) -> ok | stop).
  48. run(HookPoint, Args) ->
  49. run_(lookup(HookPoint), Args).
  50. -spec(run(atom(), list(Arg :: any()), any()) -> any()).
  51. run(HookPoint, Args, Acc) ->
  52. run_(lookup(HookPoint), Args, Acc).
  53. %% @private
  54. run_([#callback{function = Fun, init_args = InitArgs} | Callbacks], Args) ->
  55. case apply(Fun, lists:append([Args, InitArgs])) of
  56. ok -> run_(Callbacks, Args);
  57. stop -> stop;
  58. _Any -> run_(Callbacks, Args)
  59. end;
  60. run_([], _Args) ->
  61. ok.
  62. %% @private
  63. run_([#callback{function = Fun, init_args = InitArgs} | Callbacks], Args, Acc) ->
  64. case apply(Fun, lists:append([Args, [Acc], InitArgs])) of
  65. ok -> run_(Callbacks, Args, Acc);
  66. {ok, NewAcc} -> run_(Callbacks, Args, NewAcc);
  67. stop -> {stop, Acc};
  68. {stop, NewAcc} -> {stop, NewAcc}
  69. end;
  70. run_([], _Args, Acc) ->
  71. {ok, Acc}.
  72. -spec(lookup(atom()) -> [#callback{}]).
  73. lookup(HookPoint) ->
  74. case ets:lookup(?HOOK_TAB, HookPoint) of
  75. [] -> [];
  76. [#hook{callbacks = Callbacks}] -> Callbacks
  77. end.
  78. %%--------------------------------------------------------------------
  79. %% gen_server Callbacks
  80. %%--------------------------------------------------------------------
  81. init([]) ->
  82. ets:new(?HOOK_TAB, [set, protected, named_table, {keypos, #hook.name}]),
  83. {ok, #state{}}.
  84. handle_call({add, HookPoint, Function, InitArgs, Priority}, _From, State) ->
  85. Reply =
  86. case ets:lookup(?HOOK_TAB, HookPoint) of
  87. [#hook{callbacks = Callbacks}] ->
  88. case lists:keyfind(Function, #callback.function, Callbacks) of
  89. false ->
  90. Callback = #callback{function = Function,
  91. init_args = InitArgs,
  92. priority = Priority},
  93. insert_hook_(HookPoint, add_callback_(Callback, Callbacks));
  94. _Callback ->
  95. {error, already_hooked}
  96. end;
  97. [] ->
  98. Callback = #callback{function = Function,
  99. init_args = InitArgs,
  100. priority = Priority},
  101. insert_hook_(HookPoint, [Callback])
  102. end,
  103. {reply, Reply, State};
  104. handle_call({delete, HookPoint, Function}, _From, State) ->
  105. Reply =
  106. case ets:lookup(?HOOK_TAB, HookPoint) of
  107. [#hook{callbacks = Callbacks}] ->
  108. insert_hook_(HookPoint, del_callback_(Function, Callbacks));
  109. [] ->
  110. {error, not_found}
  111. end,
  112. {reply, Reply, State};
  113. handle_call(_Req, _From, State) ->
  114. {reply, ignore, State}.
  115. handle_cast(_Msg, State) ->
  116. {noreply, State}.
  117. handle_info(_Info, State) ->
  118. {noreply, State}.
  119. terminate(_Reason, _State) ->
  120. ok.
  121. code_change(_OldVsn, State, _Extra) ->
  122. {ok, State}.
  123. %%--------------------------------------------------------------------
  124. %% Internal functions
  125. %%--------------------------------------------------------------------
  126. insert_hook_(HookPoint, Callbacks) ->
  127. ets:insert(?HOOK_TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok.
  128. add_callback_(Callback, Callbacks) ->
  129. lists:keymerge(#callback.priority, Callbacks, [Callback]).
  130. del_callback_(Function, Callbacks) ->
  131. lists:keydelete(Function, #callback.function, Callbacks).