1 |
|
%% Generally, you should not call anything from this module directly. |
2 |
|
%% |
3 |
|
%% This module works together with mongoose_router_dynamic_domains. |
4 |
|
%% The lazy registration approach helps to speed up initialisation |
5 |
|
%% of the new node in a cluster with a large number of dynamically |
6 |
|
%% configured domains. |
7 |
|
%% |
8 |
|
%% In addition to dynamic domains/subdomains tracking, this module |
9 |
|
%% is also responsible for the lazy IQ handlers registration for the |
10 |
|
%% added domains/subdomains. |
11 |
|
%% |
12 |
|
%% while registration is done in a lazy manner, removal of domains, |
13 |
|
%% subdomain and corresponding IQ handlers is triggered immediately |
14 |
|
%% once domain or subdomain deleted from mongoose_subdomain_core or |
15 |
|
%% mongoose_domain_core ETS table. |
16 |
|
|
17 |
|
-module(mongoose_lazy_routing). |
18 |
|
-behaviour(gen_server). |
19 |
|
|
20 |
|
-include("mongoose_logger.hrl"). |
21 |
|
|
22 |
|
-export([start_link/0]). |
23 |
|
-export([sync/0]). |
24 |
|
|
25 |
|
%% domain/subdomain API |
26 |
|
-export([maybe_add_domain_or_subdomain/1, |
27 |
|
maybe_remove_domain/2, |
28 |
|
maybe_remove_subdomain/1]). |
29 |
|
|
30 |
|
%% IQ handling API |
31 |
|
-export([register_iq_handler_for_domain/4, |
32 |
|
register_iq_handler_for_subdomain/5, |
33 |
|
unregister_iq_handler_for_domain/3, |
34 |
|
unregister_iq_handler_for_subdomain/4]). |
35 |
|
|
36 |
|
%% gen_server callbacks |
37 |
|
-export([init/1, |
38 |
|
handle_call/3, |
39 |
|
handle_cast/2, |
40 |
|
handle_info/2, |
41 |
|
terminate/2, |
42 |
|
code_change/3]). |
43 |
|
|
44 |
|
-ignore_xref([start_link/0, sync/0]). |
45 |
|
|
46 |
|
-define(IQ_TABLE, mongoose_lazy_routing_iqs). |
47 |
|
-define(ROUTING_TABLE, mongoose_lazy_routing). |
48 |
|
|
49 |
|
-record(iq_table_key, {host_type :: host_type(), |
50 |
|
%% subdomain_pattern is 'undefined' for domains |
51 |
|
subdomain_pattern :: subdomain_pattern() | undefined, |
52 |
|
namespace = '_' :: binary() | '_', |
53 |
|
component = '_' :: module() | '_'}). |
54 |
|
|
55 |
|
-type subdomain_pattern() :: mongoose_subdomain_utils:subdomain_pattern(). |
56 |
|
-type subdomain_info() :: mongoose_subdomain_core:subdomain_info(). |
57 |
|
-type host_type() :: mongooseim:host_type(). |
58 |
|
-type domain() :: mongooseim:domain_name(). |
59 |
|
|
60 |
|
-type iq_entry() :: {#iq_table_key{}, mongoose_iq_handler:t()}. |
61 |
|
-type domain_entry() :: {domain(), host_type() | {host_type(), subdomain_pattern()}}. |
62 |
|
|
63 |
|
|
64 |
|
%%-------------------------------------------------------------------- |
65 |
|
%% API |
66 |
|
%%-------------------------------------------------------------------- |
67 |
|
|
68 |
|
start_link() -> |
69 |
33 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). |
70 |
|
|
71 |
|
-spec sync() -> ok. |
72 |
|
sync() -> |
73 |
:-( |
gen_server:call(?MODULE, sync). |
74 |
|
|
75 |
|
-spec maybe_add_domain_or_subdomain(mongooseim:domain_name()) -> boolean(). |
76 |
|
maybe_add_domain_or_subdomain(Domain) -> |
77 |
|
%% don't run gen_server:call/2 if this domain name is unknown. |
78 |
315 |
case mongoose_domain_api:get_host_type(Domain) of |
79 |
|
{ok, _} -> |
80 |
184 |
gen_server:call(?MODULE, {maybe_add_domain_or_subdomain, Domain}); |
81 |
|
{error, not_found} -> |
82 |
131 |
false |
83 |
|
end. |
84 |
|
|
85 |
|
-spec maybe_remove_domain(mongooseim:host_type(), mongooseim:domain_name()) -> ok. |
86 |
|
maybe_remove_domain(HostType, Domain) -> |
87 |
75 |
gen_server:cast(?MODULE, {maybe_remove_domain, HostType, Domain}). |
88 |
|
|
89 |
|
-spec maybe_remove_subdomain(subdomain_info()) -> ok. |
90 |
|
maybe_remove_subdomain(SubdomainInfo) -> |
91 |
294 |
gen_server:cast(?MODULE, {maybe_remove_subdomain, SubdomainInfo}). |
92 |
|
|
93 |
|
-spec register_iq_handler_for_domain(HostType :: mongooseim:host_type(), |
94 |
|
Namespace :: binary(), |
95 |
|
Component :: module(), |
96 |
|
IQHandler :: mongoose_iq_handler:t()) -> |
97 |
|
ok | {error, atom()}. |
98 |
|
register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) -> |
99 |
2407 |
gen_server:call(?MODULE, {register_iq_handler_for_domain, HostType, |
100 |
|
Namespace, Component, IQHandler}). |
101 |
|
|
102 |
|
-spec register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), |
103 |
|
SubdomainPattern :: subdomain_pattern(), |
104 |
|
Namespace :: binary(), |
105 |
|
Component :: module(), |
106 |
|
IQHandler :: mongoose_iq_handler:t()) -> |
107 |
|
ok | {error, atom()}. |
108 |
|
register_iq_handler_for_subdomain(HostType, SubdomainPattern, |
109 |
|
Namespace, Component, IQHandler) -> |
110 |
130 |
gen_server:call(?MODULE, {register_iq_handler_for_subdomain, HostType, |
111 |
|
SubdomainPattern, Namespace, Component, IQHandler}). |
112 |
|
|
113 |
|
-spec unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(), |
114 |
|
Namespace :: binary(), |
115 |
|
Component :: module()) -> |
116 |
|
{ok, mongoose_iq_handler:t()} | {error, not_found}. |
117 |
|
unregister_iq_handler_for_domain(HostType, Namespace, Component) -> |
118 |
2394 |
gen_server:call(?MODULE, {unregister_iq_handler_for_domain, |
119 |
|
HostType, Namespace, Component}). |
120 |
|
|
121 |
|
-spec unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), |
122 |
|
SubdomainPattern :: subdomain_pattern(), |
123 |
|
Namespace :: binary(), |
124 |
|
Component :: module()) -> |
125 |
|
{ok, mongoose_iq_handler:t()} | {error, not_found}. |
126 |
|
unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, |
127 |
|
Namespace, Component) -> |
128 |
130 |
gen_server:call(?MODULE, {unregister_iq_handler_for_subdomain, HostType, |
129 |
|
SubdomainPattern, Namespace, Component}). |
130 |
|
|
131 |
|
%%-------------------------------------------------------------------- |
132 |
|
%% gen_server callbacks |
133 |
|
%%-------------------------------------------------------------------- |
134 |
|
init(_) -> |
135 |
|
%% ?IQ_TABLE contains tuples of following format: |
136 |
|
%% * {#iq_table_key{}, IQHandler} |
137 |
33 |
ets:new(?IQ_TABLE, [set, named_table, protected]), |
138 |
|
%% ?ROUTING_TABLE contains tuples of one of the following formats: |
139 |
|
%% * {DomainName, HostType} |
140 |
|
%% * {SubdomainName, {HostType, SubdomainPattern}} |
141 |
33 |
ets:new(?ROUTING_TABLE, [set, named_table, protected]), |
142 |
|
%% no state, all required data is stored in ETS tables |
143 |
33 |
{ok, ok}. |
144 |
|
|
145 |
|
handle_call(sync, _From, State) -> |
146 |
:-( |
{reply, ok, State}; |
147 |
|
handle_call({maybe_add_domain_or_subdomain, Domain}, _From, State) -> |
148 |
184 |
RetValue = handle_maybe_add_domain_or_subdomain(Domain), |
149 |
184 |
{reply, RetValue, State}; |
150 |
|
handle_call({register_iq_handler_for_domain, |
151 |
|
HostType, Namespace, Component, IQHandler}, |
152 |
|
_From, State) -> |
153 |
2407 |
RetValue = handle_register_iq_handler_for_domain(HostType, Namespace, |
154 |
|
Component, IQHandler), |
155 |
2407 |
{reply, RetValue, State}; |
156 |
|
handle_call({register_iq_handler_for_subdomain, HostType, |
157 |
|
SubdomainPattern, Namespace, Component, IQHandler}, |
158 |
|
_From, State) -> |
159 |
130 |
RetValue = handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern, |
160 |
|
Namespace, Component, IQHandler), |
161 |
130 |
{reply, RetValue, State}; |
162 |
|
handle_call({unregister_iq_handler_for_domain, HostType, Namespace, Component}, |
163 |
|
_From, State) -> |
164 |
2394 |
RetValue = handle_unregister_iq_handler_for_domain(HostType, Namespace, Component), |
165 |
2394 |
{reply, RetValue, State}; |
166 |
|
handle_call({unregister_iq_handler_for_subdomain, HostType, SubdomainPattern, |
167 |
|
Namespace, Component}, |
168 |
|
_From, State) -> |
169 |
130 |
RetValue = handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, |
170 |
|
Namespace, Component), |
171 |
130 |
{reply, RetValue, State}; |
172 |
|
handle_call(Request, From, State) -> |
173 |
:-( |
?UNEXPECTED_CALL(Request, From), |
174 |
:-( |
{reply, ok, State}. |
175 |
|
|
176 |
|
handle_cast({maybe_remove_domain, HostType, Domain}, State) -> |
177 |
75 |
handle_maybe_remove_domain(HostType, Domain), |
178 |
75 |
{noreply, State}; |
179 |
|
handle_cast({maybe_remove_subdomain, SubdomainInfo}, State) -> |
180 |
294 |
handle_maybe_remove_subdomain(SubdomainInfo), |
181 |
294 |
{noreply, State}; |
182 |
|
handle_cast(Msg, State) -> |
183 |
:-( |
?UNEXPECTED_CAST(Msg), |
184 |
:-( |
{noreply, State}. |
185 |
|
|
186 |
|
handle_info(Info, State) -> |
187 |
:-( |
?UNEXPECTED_INFO(Info), |
188 |
:-( |
{noreply, State}. |
189 |
|
|
190 |
|
terminate(_Reason, _State) -> |
191 |
:-( |
ok. |
192 |
|
|
193 |
|
code_change(_OldVsn, State, _Extra) -> |
194 |
:-( |
{ok, State}. |
195 |
|
|
196 |
|
%%-------------------------------------------------------------------- |
197 |
|
%% local functions |
198 |
|
%%-------------------------------------------------------------------- |
199 |
|
|
200 |
|
-spec handle_register_iq_handler_for_domain(HostType :: mongooseim:host_type(), |
201 |
|
Namespace :: binary(), |
202 |
|
Component :: module(), |
203 |
|
IQHandler :: mongoose_iq_handler:t()) -> |
204 |
|
ok | {error, atom()}. |
205 |
|
handle_register_iq_handler_for_domain(HostType, Namespace, Component, IQHandler) -> |
206 |
2407 |
IQKey = #iq_table_key{host_type = HostType, namespace = Namespace, |
207 |
|
component = Component}, |
208 |
2407 |
NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}), |
209 |
2407 |
IQ = {IQKey, NewIQHandler}, |
210 |
2407 |
case ets:insert_new(?IQ_TABLE, IQ) of |
211 |
|
false -> |
212 |
:-( |
?LOG_WARNING(#{what => iq_already_registered, host_type => HostType, |
213 |
:-( |
namespace => Namespace, component => Component}), |
214 |
:-( |
{error, already_registered}; |
215 |
|
true -> |
216 |
2407 |
Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}), |
217 |
2407 |
register_iqs([IQ], Domains) |
218 |
|
end. |
219 |
|
|
220 |
|
-spec handle_register_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), |
221 |
|
SubdomainPattern :: subdomain_pattern(), |
222 |
|
Namespace :: binary(), |
223 |
|
Component :: module(), |
224 |
|
IQHandler :: mongoose_iq_handler:t()) -> |
225 |
|
ok | {error, atom()}. |
226 |
|
handle_register_iq_handler_for_subdomain(HostType, SubdomainPattern, Namespace, |
227 |
|
Component, IQHandler) -> |
228 |
130 |
IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern, |
229 |
|
namespace = Namespace, component = Component}, |
230 |
130 |
NewIQHandler = mongoose_iq_handler:add_extra(IQHandler, #{host_type => HostType}), |
231 |
130 |
IQ = {IQKey, NewIQHandler}, |
232 |
130 |
case ets:insert_new(?IQ_TABLE, IQ) of |
233 |
|
false -> |
234 |
:-( |
?LOG_WARNING(#{what => iq_already_registered, host_type => HostType, |
235 |
|
subdomain_pattern => SubdomainPattern, |
236 |
:-( |
namespace => Namespace, component => Component}), |
237 |
:-( |
{error, already_registered}; |
238 |
|
true -> |
239 |
130 |
Domains = ets:match_object(?ROUTING_TABLE, |
240 |
|
{'_', {HostType, SubdomainPattern}}), |
241 |
130 |
register_iqs([IQ], Domains) |
242 |
|
end. |
243 |
|
|
244 |
|
-spec handle_unregister_iq_handler_for_domain(HostType :: mongooseim:host_type(), |
245 |
|
Namespace :: binary(), |
246 |
|
Component :: module()) -> |
247 |
|
{ok, mongoose_iq_handler:t()} | {error, not_found}. |
248 |
|
handle_unregister_iq_handler_for_domain(HostType, Namespace, Component) -> |
249 |
2394 |
IQKey = #iq_table_key{host_type = HostType, namespace = Namespace, |
250 |
|
component = Component}, |
251 |
2394 |
case ets:lookup(?IQ_TABLE, IQKey) of |
252 |
|
[] -> |
253 |
252 |
?LOG_WARNING(#{what => iq_unregister_missing, host_type => HostType, |
254 |
:-( |
namespace => Namespace, component => Component}), |
255 |
252 |
{error, not_found}; |
256 |
|
[{_, IQHandler} = IQ] -> |
257 |
2142 |
Domains = ets:match_object(?ROUTING_TABLE, {'_', HostType}), |
258 |
2142 |
unregister_iqs([IQ], Domains), |
259 |
2142 |
ets:delete(?IQ_TABLE, IQKey), |
260 |
2142 |
{ok, IQHandler} |
261 |
|
end. |
262 |
|
|
263 |
|
-spec handle_unregister_iq_handler_for_subdomain(HostType :: mongooseim:host_type(), |
264 |
|
SubdomainPattern :: subdomain_pattern(), |
265 |
|
Namespace :: binary(), |
266 |
|
Component :: module()) -> |
267 |
|
{ok, mongoose_iq_handler:t()} | {error, not_found}. |
268 |
|
handle_unregister_iq_handler_for_subdomain(HostType, SubdomainPattern, |
269 |
|
Namespace, Component) -> |
270 |
130 |
IQKey = #iq_table_key{host_type = HostType, subdomain_pattern = SubdomainPattern, |
271 |
|
namespace = Namespace, component = Component}, |
272 |
130 |
case ets:lookup(?IQ_TABLE, IQKey) of |
273 |
|
[] -> |
274 |
:-( |
?LOG_WARNING(#{what => iq_unregister_missing, host_type => HostType, |
275 |
|
subdomain_pattern => SubdomainPattern, |
276 |
:-( |
namespace => Namespace, component => Component}), |
277 |
:-( |
{error, not_found}; |
278 |
|
[{_, IQHandler} = IQ] -> |
279 |
130 |
Domains = ets:match_object(?ROUTING_TABLE, |
280 |
|
{'_', {HostType, SubdomainPattern}}), |
281 |
130 |
unregister_iqs([IQ], Domains), |
282 |
130 |
ets:delete(?IQ_TABLE, IQKey), |
283 |
130 |
{ok, IQHandler} |
284 |
|
end. |
285 |
|
|
286 |
|
-spec handle_maybe_add_domain_or_subdomain(domain()) -> boolean(). |
287 |
|
handle_maybe_add_domain_or_subdomain(Domain) -> |
288 |
184 |
case ets:member(?ROUTING_TABLE, Domain) of |
289 |
|
true -> |
290 |
|
%% It's absolutely normal situation. We can receive |
291 |
|
%% a couple of maybe_add_domain_or_subdomain requests |
292 |
|
%% from different processes, so this domain can be |
293 |
|
%% already added. |
294 |
8 |
true; |
295 |
|
false -> |
296 |
176 |
try_to_add_domain_or_subdomain(Domain) |
297 |
|
end. |
298 |
|
|
299 |
|
-spec try_to_add_domain_or_subdomain(domain()) -> boolean(). |
300 |
|
try_to_add_domain_or_subdomain(Domain) -> |
301 |
176 |
case mongoose_domain_api:get_domain_host_type(Domain) of |
302 |
|
{ok, HostType} -> |
303 |
30 |
add_domain(HostType, Domain), |
304 |
30 |
true; |
305 |
|
{error, not_found} -> |
306 |
146 |
case mongoose_domain_api:get_subdomain_info(Domain) of |
307 |
|
{ok, Info} -> |
308 |
146 |
add_subdomain(Info), |
309 |
146 |
true; |
310 |
|
{error, not_found} -> |
311 |
:-( |
false |
312 |
|
end |
313 |
|
end. |
314 |
|
|
315 |
|
-spec add_domain(host_type(), domain()) -> ok. |
316 |
|
add_domain(HostType, Domain) -> |
317 |
30 |
DomainElement = {Domain, HostType}, |
318 |
30 |
case ets:insert_new(?ROUTING_TABLE, DomainElement) of |
319 |
|
true -> |
320 |
30 |
IQs = get_iqs(#iq_table_key{host_type = HostType}), |
321 |
30 |
register_iqs(IQs, [DomainElement]), |
322 |
30 |
ejabberd_local:register_host(Domain), |
323 |
30 |
ok; |
324 |
|
false -> |
325 |
|
%% we should never get here, but it's ok to just ignore this. |
326 |
:-( |
ok |
327 |
|
end. |
328 |
|
|
329 |
|
-spec add_subdomain(subdomain_info()) -> ok. |
330 |
|
add_subdomain(#{subdomain := Subdomain, host_type := HostType, |
331 |
|
subdomain_pattern := SubdomainPattern, |
332 |
|
packet_handler := PacketHandler}) -> |
333 |
146 |
SubdomainElement = {Subdomain, {HostType, SubdomainPattern}}, |
334 |
146 |
case ets:insert_new(?ROUTING_TABLE, SubdomainElement) of |
335 |
|
true -> |
336 |
146 |
IQs = get_iqs(#iq_table_key{host_type = HostType, |
337 |
|
subdomain_pattern = SubdomainPattern}), |
338 |
146 |
register_iqs(IQs, [SubdomainElement]), |
339 |
146 |
mongoose_router:register_route(Subdomain, PacketHandler); |
340 |
|
false -> |
341 |
|
%% we should never get here, but it's ok to just ignore this. |
342 |
:-( |
ok |
343 |
|
end. |
344 |
|
|
345 |
|
-spec handle_maybe_remove_domain(host_type(), domain()) -> ok. |
346 |
|
handle_maybe_remove_domain(HostType, Domain) -> |
347 |
75 |
case ets:lookup(?ROUTING_TABLE, Domain) of |
348 |
|
[{Domain, HostType} = DomainElement] -> |
349 |
13 |
ejabberd_local:unregister_host(Domain), |
350 |
13 |
IQs = get_iqs(#iq_table_key{host_type = HostType}), |
351 |
13 |
unregister_iqs(IQs, [DomainElement]), |
352 |
13 |
ets:delete(?ROUTING_TABLE, Domain); |
353 |
62 |
_ -> ok |
354 |
|
end. |
355 |
|
|
356 |
|
-spec handle_maybe_remove_subdomain(subdomain_info()) -> ok. |
357 |
|
handle_maybe_remove_subdomain(#{subdomain := Subdomain, host_type := HostType, |
358 |
|
subdomain_pattern := SubdomainPattern}) -> |
359 |
294 |
case ets:lookup(?ROUTING_TABLE, Subdomain) of |
360 |
|
[{Domain, {HostType, SubdomainPattern}} = SubdomainElement] -> |
361 |
145 |
mongoose_router:unregister_route(Domain), |
362 |
145 |
IQs = get_iqs(#iq_table_key{host_type = HostType, |
363 |
|
subdomain_pattern = SubdomainPattern}), |
364 |
145 |
unregister_iqs(IQs, [SubdomainElement]), |
365 |
145 |
ets:delete(?ROUTING_TABLE, Domain); |
366 |
149 |
_ -> ok |
367 |
|
end. |
368 |
|
|
369 |
|
-spec get_iqs(#iq_table_key{}) -> [iq_entry()]. |
370 |
|
get_iqs(KeyMatchPattern) -> |
371 |
334 |
ets:match_object(?IQ_TABLE, {KeyMatchPattern, '_'}). |
372 |
|
|
373 |
|
-spec register_iqs([iq_entry()], [domain_entry()]) -> ok. |
374 |
|
register_iqs(IQList, DomainList) -> |
375 |
2713 |
[register_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList, |
376 |
881 |
check_that_domain_and_iq_match(IQ, Domain)], |
377 |
2713 |
ok. |
378 |
|
|
379 |
|
-spec unregister_iqs([iq_entry()], [domain_entry()]) -> ok. |
380 |
|
unregister_iqs(IQList, DomainList) -> |
381 |
2430 |
[unregister_iq(IQ, Domain) || IQ <- IQList, Domain <- DomainList, |
382 |
783 |
check_that_domain_and_iq_match(IQ, Domain)], |
383 |
2430 |
ok. |
384 |
|
|
385 |
|
-spec check_that_domain_and_iq_match(iq_entry(), domain_entry()) -> boolean(). |
386 |
|
check_that_domain_and_iq_match({#iq_table_key{host_type = HostType, |
387 |
|
subdomain_pattern = undefined, |
388 |
|
namespace = Namespace, |
389 |
|
component = Component}, _}, |
390 |
|
{_, HostType}) when is_binary(Namespace), |
391 |
|
Component =/= '_' -> |
392 |
1436 |
true; |
393 |
|
check_that_domain_and_iq_match({#iq_table_key{host_type = HostType, |
394 |
|
subdomain_pattern = Pattern, |
395 |
|
namespace = Namespace, |
396 |
|
component = Component}, _}, |
397 |
|
{_, {HostType, Pattern}}) when is_binary(Namespace), |
398 |
|
Component =/= '_' -> |
399 |
228 |
true; |
400 |
|
check_that_domain_and_iq_match({Key = #iq_table_key{}, _} = IQEntry, Domain) -> |
401 |
|
%% we should not get here, log error |
402 |
:-( |
?LOG_ERROR(#{what => domain_and_iq_doesnt_match, domain => Domain, |
403 |
|
iq_entry => IQEntry, |
404 |
|
iq_key_record => mongoose_record_pp:format(Key, |
405 |
:-( |
iq_table_key, record_info(fields, iq_table_key))}), |
406 |
:-( |
false. |
407 |
|
|
408 |
|
-spec register_iq(iq_entry(), domain_entry()) -> ok. |
409 |
|
register_iq({#iq_table_key{namespace = Namespace, |
410 |
|
component = Component}, IQHandler}, |
411 |
|
{Domain, _})-> |
412 |
881 |
gen_iq_component:register_iq_handler(Component, Domain, Namespace, IQHandler), |
413 |
881 |
gen_iq_component:sync(Component). |
414 |
|
|
415 |
|
-spec unregister_iq(iq_entry(), domain_entry()) -> ok. |
416 |
|
unregister_iq({#iq_table_key{namespace = Namespace, |
417 |
|
component = Component}, _}, |
418 |
|
{Domain, _}) -> |
419 |
783 |
gen_iq_component:unregister_iq_handler(Component, Domain, Namespace). |