blob: 4e5eb0c35c66a2da2a9ad4c23033361ba716f35c [file] [log] [blame]
%%------------------------------------------------------------------------------
%% Licensed to the Apache Software Foundation (ASF) under one or more
%% contributor license agreements. See the NOTICE file distributed with
%% this work for additional information regarding copyright ownership.
%% The ASF licenses this file to You under the Apache License, Version 2.0
%% (the "License"); you may not use this file except in compliance with
%% the License. You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%------------------------------------------------------------------------------
-module(dubbo_extension).
-behaviour(gen_server).
%% API
-export([run/3,run_fold/4,register/3,unregister/3]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(TAB, ?MODULE).
-record(state, {}).
-spec reg(HookName::hookname(), Module::atom(),Priority::integer()) -> ok | {error, term()}.
register(HookName, Module,Priority) ->
gen_server:call(?MODULE, {register, HookName, {Priority, {Module}}}).
-spec unregister(HookName::hookname(), Module::atom(),Priority::integer()) -> ok.
unregister(HookName, Module,Priority) ->
gen_server:call(?MODULE, {unregister, HookName, {Priority, Module}}).
%% @doc run all hooks registered for the HookName.
%% Execution can be interrupted if an hook return the atom `stop'.
-spec run(HookName::hookname(), Args::list()) -> ok.
run(HookName,Fun, Args) ->
case find_hooks(HookName) of
no_hook -> ok;
Hooks -> run1(Hooks, HookName,Fun, Args)
end.
run1([], _HookName,_Fun, _Args) ->
ok;
run1([M | Rest], HookName, Fun, Args) ->
Ret = (catch apply(M, Fun, Args)),
case Ret of
{'EXIT', Reason} ->
logger:error("~p~n error running extension: ~p~n", [HookName, Reason]),
run1(Rest, HookName,Fun, Args);
stop ->
ok;
_ ->
run1(Rest, HookName,Fun, Args)
end.
-spec run_fold(HookName::hookname(), Args::list(), Acc::any()) -> Acc2::any().
run_fold(HookName, Fun, Args, Acc) ->
case find_hooks(HookName) of
no_hook -> Acc;
Hooks -> run_fold1(Hooks,HookName, Fun, Args, Acc)
end.
run_fold1([], _HookName,_Fun, _Args, Acc) ->
Acc;
run_fold1([M | Rest], HookName,Fun, Args0, Acc) ->
Args = Args0 ++ [Acc],
Ret = (catch apply(M, Fun, Args)),
case Ret of
{'EXIT', Reason} ->
error_logger:error_msg("~p~n error running hook: ~p~n",
[HookName, Reason]),
run_fold1(Rest, HookName,Fun,Args0, Acc);
stop ->
Acc;
{stop, NewAcc} ->
NewAcc;
_ ->
run_fold1(Rest, HookName,Fun,Args0, Ret)
end.
%% @doc retrieve the lists of registered functions for an hook.
-spec find(HookName::hookname()) -> {ok, [{atom(), atom()}]} | error.
find(HookName) ->
case ?find_hook(HookName) of
no_hook -> error;
Hooks -> {ok, Hooks}
end.
%% @hidden
start_link() ->
_ = init_tabs(),
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init_tabs() ->
case ets:info(?TAB, name) of
undefined ->
ets:new(?TAB, [ordered_set, public, named_table,
{read_concurrency, true},
{write_concurrency, true}]);
_ ->
true
end.
%% @hidden
init([]) ->
{ok, #state{}}.
%% @hidden
handle_call({register, HookName, {Priority, Module}}, _From, State) ->
do_register(HookName, {Priority, Module}),
{reply, ok, State};
handle_call({unregister, HookName, {Priority, Module}}, _From, State) ->
do_unregister(HookName, {Priority, Module}),
{reply, ok, State};
handle_call(_Msg, _From, State) ->
{reply, badarg, State}.
%% @hidden
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
%% @hidden
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% @hidden
terminate(_Reason, _Srv) ->
ok.
do_register(HookName, {_Priority, ModuleName}=Hook) ->
check_module(ModuleName),
update_hooks(HookName, [Hook]).
do_unregister(HookName, Hook) ->
remove_hooks(HookName, [Hook]),
ok.
update_hooks(HookName, HookFuns) ->
case ets:lookup(?TAB, HookName) of
[] ->
true = ets:insert(?TAB, {HookName, HookFuns});
[{_, Funs}] ->
Funs2 = lists:keysort(1, Funs ++ HookFuns),
true = ets:insert(?TAB, {HookName, Funs2})
end.
remove_hooks(HookName, HookFuns) ->
case ets:lookup(?TAB, HookName) of
[] ->
ok;
[{_, Funs}] ->
Funs2 = Funs -- HookFuns,
case Funs2 of
[] ->
ets:delete(?TAB, HookName);
_ ->
ets:insert(?TAB, {HookName, Funs2})
end
end.
check_module(ModuleName) ->
_ = code:ensure_loaded(ModuleName),
ok.
find_hooks(HookName)->
case ets:lookup(?TAB,HookName) of
[]->
no_hook;
[{_, Modules}]->
Modules
end.