emqttd_hook.erl 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2016 Feng Lee <feng@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_hook).
  17. -author("Feng Lee <feng@emqtt.io>").
  18. -behaviour(gen_server).
  19. %% Start
  20. -export([start_link/0]).
  21. %% Hooks API
  22. -export([add/3, add/4, delete/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. %%--------------------------------------------------------------------
  33. %% Start API
  34. %%--------------------------------------------------------------------
  35. start_link() ->
  36. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  37. %%--------------------------------------------------------------------
  38. %% Hooks API
  39. %%--------------------------------------------------------------------
  40. -spec(add(atom(), function(), list(any())) -> ok).
  41. add(HookPoint, Function, InitArgs) ->
  42. add(HookPoint, Function, InitArgs, 0).
  43. -spec(add(atom(), function(), list(any()), integer()) -> ok).
  44. add(HookPoint, Function, InitArgs, Priority) ->
  45. gen_server:call(?MODULE, {add, HookPoint, Function, InitArgs, Priority}).
  46. -spec(delete(atom(), function()) -> ok).
  47. delete(HookPoint, Function) ->
  48. gen_server:call(?MODULE, {delete, HookPoint, Function}).
  49. -spec(run(atom(), list(any()), any()) -> any()).
  50. run(HookPoint, Args, Acc) ->
  51. run_(lookup(HookPoint), Args, Acc).
  52. %% @private
  53. run_([#callback{function = Fun, init_args = InitArgs} | Callbacks], Args, Acc) ->
  54. case apply(Fun, lists:append([Args, [Acc], InitArgs])) of
  55. ok -> run_(Callbacks, Args, Acc);
  56. {ok, NewAcc} -> run_(Callbacks, Args, NewAcc);
  57. stop -> {stop, Acc};
  58. {stop, NewAcc} -> {stop, NewAcc}
  59. end;
  60. run_([], _Args, Acc) ->
  61. {ok, Acc}.
  62. -spec(lookup(atom()) -> [#callback{}]).
  63. lookup(HookPoint) ->
  64. case ets:lookup(?HOOK_TAB, HookPoint) of
  65. [] -> [];
  66. [#hook{callbacks = Callbacks}] -> Callbacks
  67. end.
  68. %%--------------------------------------------------------------------
  69. %% gen_server Callbacks
  70. %%--------------------------------------------------------------------
  71. init([]) ->
  72. ets:new(?HOOK_TAB, [set, protected, named_table, {keypos, #hook.name}]),
  73. {ok, #state{}}.
  74. handle_call({add, HookPoint, Function, InitArgs, Priority}, _From, State) ->
  75. Reply =
  76. case ets:lookup(?HOOK_TAB, HookPoint) of
  77. [#hook{callbacks = Callbacks}] ->
  78. case lists:keyfind(Function, #callback.function, Callbacks) of
  79. false ->
  80. Callback = #callback{function = Function,
  81. init_args = InitArgs,
  82. priority = Priority},
  83. insert_hook_(HookPoint, add_callback_(Callback, Callbacks));
  84. _Callback ->
  85. {error, already_hooked}
  86. end;
  87. [] ->
  88. Callback = #callback{function = Function,
  89. init_args = InitArgs,
  90. priority = Priority},
  91. insert_hook_(HookPoint, [Callback])
  92. end,
  93. {reply, Reply, State};
  94. handle_call({delete, HookPoint, Function}, _From, State) ->
  95. Reply =
  96. case ets:lookup(?HOOK_TAB, HookPoint) of
  97. [#hook{callbacks = Callbacks}] ->
  98. insert_hook_(HookPoint, del_callback_(Function, Callbacks));
  99. [] ->
  100. {error, not_found}
  101. end,
  102. {reply, Reply, State};
  103. handle_call(_Req, _From, State) ->
  104. {reply, ignore, State}.
  105. handle_cast(_Msg, State) ->
  106. {noreply, State}.
  107. handle_info(_Info, State) ->
  108. {noreply, State}.
  109. terminate(_Reason, _State) ->
  110. ok.
  111. code_change(_OldVsn, State, _Extra) ->
  112. {ok, State}.
  113. %%--------------------------------------------------------------------
  114. %% Internal functions
  115. %%--------------------------------------------------------------------
  116. insert_hook_(HookPoint, Callbacks) ->
  117. ets:insert(?HOOK_TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok.
  118. add_callback_(Callback, Callbacks) ->
  119. lists:keymerge(#callback.priority, Callbacks, [Callback]).
  120. del_callback_(Function, Callbacks) ->
  121. lists:keydelete(Function, #callback.function, Callbacks).