1 |
|
-module(mongoose_component). |
2 |
|
%% API |
3 |
|
-export([has_component/1, |
4 |
|
dirty_get_all_components/1, |
5 |
|
register_components/4, |
6 |
|
unregister_components/1, |
7 |
|
lookup_component/1, |
8 |
|
lookup_component/2]). |
9 |
|
|
10 |
|
-export([start/0, stop/0]). |
11 |
|
-export([node_cleanup/3]). |
12 |
|
|
13 |
|
-include("mongoose.hrl"). |
14 |
|
-include("jlib.hrl"). |
15 |
|
-include("external_component.hrl"). |
16 |
|
|
17 |
|
-type domain() :: jid:server(). |
18 |
|
|
19 |
|
-type external_component() :: #external_component{domain :: domain(), |
20 |
|
handler :: mongoose_packet_handler:t(), |
21 |
|
node :: node(), |
22 |
|
is_hidden :: boolean()}. |
23 |
|
|
24 |
|
-export_type([external_component/0]). |
25 |
|
|
26 |
|
% Not simple boolean() because is probably going to support third value in the future: only_hidden. |
27 |
|
% Besides, it increases readability. |
28 |
|
-type return_hidden() :: only_public | all. |
29 |
|
|
30 |
|
-export_type([return_hidden/0]). |
31 |
|
|
32 |
|
%%==================================================================== |
33 |
|
%% API |
34 |
|
%%==================================================================== |
35 |
|
|
36 |
|
start() -> |
37 |
100 |
Backend = mongoose_config:get_opt(component_backend), |
38 |
100 |
mongoose_component_backend:init(#{backend => Backend}), |
39 |
100 |
gen_hook:add_handlers(hooks()). |
40 |
|
|
41 |
|
stop() -> |
42 |
:-( |
gen_hook:delete_handlers(hooks()). |
43 |
|
|
44 |
|
-spec hooks() -> [gen_hook:hook_tuple()]. |
45 |
|
hooks() -> |
46 |
100 |
[{node_cleanup, global, fun ?MODULE:node_cleanup/3, #{}, 90}]. |
47 |
|
|
48 |
|
-spec register_components(Domain :: [domain()], |
49 |
|
Node :: node(), |
50 |
|
Handler :: mongoose_packet_handler:t(), |
51 |
|
AreHidden :: boolean()) -> {ok, [external_component()]} | {error, any()}. |
52 |
|
register_components(Domains, Node, Handler, AreHidden) -> |
53 |
31 |
try |
54 |
31 |
register_components_unsafe(Domains, Node, Handler, AreHidden) |
55 |
|
catch Class:Reason:Stacktrace -> |
56 |
3 |
?LOG_ERROR(#{what => component_register_failed, |
57 |
:-( |
class => Class, reason => Reason, stacktrace => Stacktrace}), |
58 |
3 |
{error, Reason} |
59 |
|
end. |
60 |
|
|
61 |
|
register_components_unsafe(Domains, Node, Handler, AreHidden) -> |
62 |
31 |
LDomains = prepare_ldomains(Domains), |
63 |
31 |
Components = make_components(LDomains, Node, Handler, AreHidden), |
64 |
31 |
assert_can_register_components(Components), |
65 |
28 |
register_components(Components), |
66 |
|
%% We do it outside of Mnesia transaction |
67 |
28 |
lists:foreach(fun run_register_hook/1, Components), |
68 |
28 |
{ok, Components}. |
69 |
|
|
70 |
|
register_components(Components) -> |
71 |
28 |
mongoose_component_backend:register_components(Components). |
72 |
|
|
73 |
|
make_components(LDomains, Node, Handler, AreHidden) -> |
74 |
31 |
[make_record_component(LDomain, Handler, Node, AreHidden) || LDomain <- LDomains]. |
75 |
|
|
76 |
|
make_record_component(LDomain, Handler, Node, IsHidden) -> |
77 |
32 |
#external_component{domain = LDomain, handler = Handler, |
78 |
|
node = Node, is_hidden = IsHidden}. |
79 |
|
|
80 |
|
run_register_hook(#external_component{domain = LDomain, is_hidden = IsHidden}) -> |
81 |
29 |
mongoose_hooks:register_subhost(LDomain, IsHidden), |
82 |
29 |
ok. |
83 |
|
|
84 |
|
run_unregister_hook(#external_component{domain = LDomain}) -> |
85 |
29 |
mongoose_hooks:unregister_subhost(LDomain), |
86 |
29 |
ok. |
87 |
|
|
88 |
|
-spec unregister_components(Components :: [external_component()]) -> ok. |
89 |
|
unregister_components(Components) -> |
90 |
28 |
lists:foreach(fun run_unregister_hook/1, Components), |
91 |
28 |
mongoose_component_backend:unregister_components(Components). |
92 |
|
|
93 |
|
assert_can_register_components(Components) -> |
94 |
31 |
ConflictComponents = lists:filter(fun is_already_registered/1, Components), |
95 |
31 |
ConflictDomains = records_to_domains(ConflictComponents), |
96 |
31 |
case ConflictDomains of |
97 |
|
[] -> |
98 |
28 |
ok; |
99 |
|
_ -> |
100 |
3 |
error({routes_already_exist, ConflictDomains}) |
101 |
|
end. |
102 |
|
|
103 |
|
records_to_domains(Components) -> |
104 |
31 |
[LDomain || #external_component{domain = LDomain} <- Components]. |
105 |
|
|
106 |
|
%% Returns true if any component route is registered for the domain. |
107 |
|
-spec has_component(jid:lserver()) -> boolean(). |
108 |
|
has_component(Domain) -> |
109 |
:-( |
[] =/= lookup_component(Domain). |
110 |
|
|
111 |
|
%% @doc Check if the component/route is already registered somewhere. |
112 |
|
-spec is_already_registered(external_component()) -> boolean(). |
113 |
|
is_already_registered(#external_component{domain = LDomain, node = Node}) -> |
114 |
32 |
has_dynamic_domains(LDomain) |
115 |
31 |
orelse has_domain_route(LDomain) |
116 |
31 |
orelse has_component_registered(LDomain, Node). |
117 |
|
|
118 |
|
has_dynamic_domains(LDomain) -> |
119 |
32 |
{error, not_found} =/= mongoose_domain_api:get_host_type(LDomain). |
120 |
|
|
121 |
|
%% check that route for this domain is not already registered |
122 |
|
has_domain_route(LDomain) -> |
123 |
31 |
no_route =/= mongoose_router:lookup_route(LDomain). |
124 |
|
|
125 |
|
%% check that there is no component registered globally for this node |
126 |
|
has_component_registered(LDomain, Node) -> |
127 |
31 |
no_route =/= get_component(LDomain, Node). |
128 |
|
|
129 |
|
%% Find a component registered globally for this node (internal use) |
130 |
|
get_component(LDomain, Node) -> |
131 |
31 |
filter_component(lookup_component(LDomain), Node). |
132 |
|
|
133 |
|
filter_component([], _) -> |
134 |
29 |
no_route; |
135 |
|
filter_component([Comp|Tail], Node) -> |
136 |
3 |
case Comp of |
137 |
|
#external_component{node = Node} -> |
138 |
2 |
Comp; |
139 |
|
_ -> |
140 |
1 |
filter_component(Tail, Node) |
141 |
|
end. |
142 |
|
|
143 |
|
%% @doc Returns a list of components registered for this domain by any node, |
144 |
|
%% the choice is yours. |
145 |
|
-spec lookup_component(Domain :: jid:lserver()) -> [external_component()]. |
146 |
|
lookup_component(Domain) -> |
147 |
315 |
mongoose_component_backend:lookup_component(Domain). |
148 |
|
|
149 |
|
%% @doc Returns a list of components registered for this domain at the given node. |
150 |
|
%% (must be only one, or nothing) |
151 |
|
-spec lookup_component(Domain :: jid:lserver(), Node :: node()) -> [external_component()]. |
152 |
|
lookup_component(Domain, Node) -> |
153 |
284 |
mongoose_component_backend:lookup_component(Domain, Node). |
154 |
|
|
155 |
|
-spec dirty_get_all_components(return_hidden()) -> [jid:lserver()]. |
156 |
|
dirty_get_all_components(ReturnHidden) -> |
157 |
73 |
mongoose_component_backend:get_all_components(ReturnHidden). |
158 |
|
|
159 |
|
-spec node_cleanup(map(), map(), map()) -> {ok, map()}. |
160 |
|
node_cleanup(Acc, #{node := Node}, _) -> |
161 |
8 |
mongoose_component_backend:node_cleanup(Node), |
162 |
8 |
{ok, maps:put(?MODULE, ok, Acc)}. |
163 |
|
|
164 |
|
prepare_ldomains(Domains) -> |
165 |
31 |
LDomains = [jid:nameprep(Domain) || Domain <- Domains], |
166 |
31 |
Zip = lists:zip(Domains, LDomains), |
167 |
31 |
InvalidDomains = [Domain || {Domain, error} <- Zip], |
168 |
31 |
case InvalidDomains of |
169 |
|
[] -> |
170 |
31 |
LDomains; |
171 |
|
_ -> |
172 |
:-( |
error({invalid_domains, InvalidDomains}) |
173 |
|
end. |