1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2016 Erlang Solutions Ltd. |
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 |
|
|
17 |
|
-module(mod_event_pusher). |
18 |
|
-author('konrad.zemek@erlang-solutions.com'). |
19 |
|
|
20 |
|
-behaviour(gen_mod). |
21 |
|
-behaviour(mongoose_module_metrics). |
22 |
|
|
23 |
|
-include("jlib.hrl"). |
24 |
|
-include("mod_event_pusher_events.hrl"). |
25 |
|
-include("mongoose_config_spec.hrl"). |
26 |
|
|
27 |
|
-type event() :: #user_status_event{} | #chat_event{} | #unack_msg_event{}. |
28 |
|
-export_type([event/0]). |
29 |
|
|
30 |
|
-export([deps/2, start/2, stop/1, config_spec/0, push_event/3]). |
31 |
|
|
32 |
|
-export([config_metrics/1]). |
33 |
|
|
34 |
|
-ignore_xref([behaviour_info/1]). |
35 |
|
|
36 |
|
%%-------------------------------------------------------------------- |
37 |
|
%% Callbacks |
38 |
|
%%-------------------------------------------------------------------- |
39 |
|
|
40 |
|
-callback push_event(Acc :: mongoose_acc:t(), Host :: jid:lserver(), Event :: event()) -> mongoose_acc:t(). |
41 |
|
|
42 |
|
%%-------------------------------------------------------------------- |
43 |
|
%% API |
44 |
|
%%-------------------------------------------------------------------- |
45 |
|
|
46 |
|
%% @doc Pushes the event to each backend registered with the event_pusher. |
47 |
|
-spec push_event(mongoose_acc:t(), Host :: jid:server(), Event :: event()) -> mongoose_acc:t(). |
48 |
|
push_event(Acc, Host, Event) -> |
49 |
:-( |
lists:foldl(fun(B, Acc0) -> |
50 |
:-( |
B:push_event(Acc0, Host, Event) end, |
51 |
|
Acc, |
52 |
|
ets:lookup_element(ets_name(Host), backends, 2)). |
53 |
|
|
54 |
|
%%-------------------------------------------------------------------- |
55 |
|
%% gen_mod API |
56 |
|
%%-------------------------------------------------------------------- |
57 |
|
|
58 |
|
-spec deps(Host :: jid:server(), Opts :: proplists:proplist()) -> gen_mod:deps_list(). |
59 |
|
deps(_Host, Opts) -> |
60 |
:-( |
Backends = get_backends(Opts), |
61 |
:-( |
BackendDeps = [{B, DepOpts, hard} || {B, DepOpts} <- Backends], |
62 |
:-( |
[{mod_event_pusher_hook_translator, hard} | BackendDeps]. |
63 |
|
|
64 |
|
-spec start(Host :: jid:server(), Opts :: proplists:proplist()) -> any(). |
65 |
|
start(Host, Opts) -> |
66 |
:-( |
create_ets(Host), |
67 |
:-( |
Backends = get_backends(Opts), |
68 |
:-( |
ets:insert(ets_name(Host), {backends, [B || {B, _} <- Backends]}). |
69 |
|
|
70 |
|
-spec stop(Host :: jid:server()) -> any(). |
71 |
|
stop(Host) -> |
72 |
:-( |
ets:delete(ets_name(Host)). |
73 |
|
|
74 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
75 |
|
config_spec() -> |
76 |
146 |
BackendItems = [{atom_to_binary(B, utf8), |
77 |
146 |
(translate_backend(B)):config_spec()} || B <- all_backends()], |
78 |
146 |
#section{ |
79 |
|
items = #{<<"backend">> => #section{items = maps:from_list(BackendItems), |
80 |
|
wrap = {kv, backends}} |
81 |
|
} |
82 |
|
}. |
83 |
|
|
84 |
|
%%-------------------------------------------------------------------- |
85 |
|
%% Helpers |
86 |
|
%%-------------------------------------------------------------------- |
87 |
|
|
88 |
|
-spec get_backends(Opts :: proplists:proplist()) -> [{module(), proplists:proplist()}]. |
89 |
|
get_backends(Opts) -> |
90 |
:-( |
{backends, Backends0} = lists:keyfind(backends, 1, Opts), |
91 |
:-( |
lists:foldr(fun add_backend/2, [], Backends0). |
92 |
|
|
93 |
|
add_backend({http, Opts}, BackendList) -> |
94 |
|
% http backend is treated somewhat differently - we allow configuration settings as if there |
95 |
|
% were many modules, while here we put together a single list of settings for the http event |
96 |
|
% pusher module. Thus, you can configure event pusher like: |
97 |
|
%{mod_event_pusher, |
98 |
|
% [{backends, |
99 |
|
% [{http, |
100 |
|
% [{path, "/push_here"}, |
101 |
|
% {callback_module, mod_event_pusher_http_one}, |
102 |
|
% {pool_name, http_pool}] |
103 |
|
% }, |
104 |
|
% {http, |
105 |
|
% [{path, "/push_there"}, |
106 |
|
% {callback_module, mod_event_pusher_http_two}, |
107 |
|
% {pool_name, http_pool}] |
108 |
|
% } |
109 |
|
% ] |
110 |
|
% }] |
111 |
:-( |
HttpModName = translate_backend(http), |
112 |
:-( |
case lists:keyfind(HttpModName, 1, BackendList) of |
113 |
|
false -> |
114 |
:-( |
[{HttpModName, [{configs, [Opts]}]} | BackendList]; |
115 |
|
{HttpModName, [{configs, O}]} -> |
116 |
:-( |
lists:keyreplace(HttpModName, 1, BackendList, |
117 |
|
{HttpModName, [{configs, [Opts | O]}]}) |
118 |
|
end; |
119 |
|
add_backend({Mod, Opts}, BackendList) -> |
120 |
:-( |
[{translate_backend(Mod), Opts} | BackendList]. |
121 |
|
|
122 |
|
-spec translate_backend(Backend :: atom()) -> module(). |
123 |
|
translate_backend(Backend) -> |
124 |
584 |
list_to_atom(?MODULE_STRING ++ "_" ++ atom_to_list(Backend)). |
125 |
|
|
126 |
|
-spec ets_name(Host :: jid:server()) -> atom(). |
127 |
|
ets_name(Host) -> |
128 |
:-( |
gen_mod:get_module_proc(Host, ?MODULE). |
129 |
|
|
130 |
|
-spec create_ets(Host :: jid:server()) -> any(). |
131 |
|
create_ets(Host) -> |
132 |
:-( |
Self = self(), |
133 |
:-( |
Heir = case whereis(ejabberd_sup) of |
134 |
:-( |
undefined -> none; |
135 |
:-( |
Self -> none; |
136 |
:-( |
Pid -> Pid |
137 |
|
end, |
138 |
:-( |
ets:new(ets_name(Host), [public, named_table, {read_concurrency, true}, {heir, Heir, testing}]). |
139 |
|
|
140 |
|
config_metrics(Host) -> |
141 |
:-( |
try |
142 |
:-( |
Opts = gen_mod:get_module_opts(Host, ?MODULE), |
143 |
:-( |
BackendsWithOpts = proplists:get_value(backends, Opts, none), |
144 |
:-( |
Backends = proplists:get_keys(BackendsWithOpts), |
145 |
:-( |
ReturnList = lists:map(pa:bind(fun get_backend/2, BackendsWithOpts), Backends), |
146 |
:-( |
lists:flatten(ReturnList) |
147 |
|
catch |
148 |
:-( |
_:_ -> [{none, none}] |
149 |
|
end. |
150 |
|
|
151 |
|
get_backend(BackendsWithOpts, Backend) -> |
152 |
:-( |
case Backend of |
153 |
|
push -> |
154 |
:-( |
PushOptions = proplists:get_value(push, BackendsWithOpts), |
155 |
:-( |
PushBackend = atom_to_list(proplists:get_value(backend, PushOptions, mnesia)), |
156 |
:-( |
[{backend, push}, {backend, list_to_atom("push_" ++ PushBackend)}]; |
157 |
|
Backend -> |
158 |
:-( |
{backend, Backend} |
159 |
|
end. |
160 |
|
|
161 |
|
all_backends() -> |
162 |
146 |
[sns, push, http, rabbit]. |