1 |
|
%%%------------------------------------------------------------------- |
2 |
|
%%% File : mongoose_acc.erl |
3 |
|
%%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com> |
4 |
|
%%% Author : Bartlomiej Gorny <bartlomiej.gorny@erlang-solutions.com> |
5 |
|
%%% Purpose : Mongoose accumulator implementation |
6 |
|
%%% Created : 11 Sep 2018 by Piotr Nosek <piotr.nosek@erlang-solutions.com> |
7 |
|
%%% |
8 |
|
%%% NS:Key conventions: |
9 |
|
%%% * hook:result should be used to return hook processing result |
10 |
|
%%% * iq:* contains useful IQ metadata but must be provided by mongoose_iq.erl |
11 |
|
%%%------------------------------------------------------------------- |
12 |
|
-module(mongoose_acc). |
13 |
|
-author("bartlomiej.gorny@erlang-solutions.com"). |
14 |
|
-author("piotr.nosek@erlang-solutions.com"). |
15 |
|
|
16 |
|
-include("jlib.hrl"). |
17 |
|
|
18 |
|
%% API |
19 |
|
% Constructor |
20 |
|
-export([new/1]). |
21 |
|
% Access to built-in fields |
22 |
|
-export([ |
23 |
|
ref/1, |
24 |
|
timestamp/1, |
25 |
|
lserver/1, |
26 |
|
host_type/1, |
27 |
|
element/1, |
28 |
|
to_jid/1, |
29 |
|
from_jid/1, |
30 |
|
packet/1, |
31 |
|
stanza_name/1, |
32 |
|
stanza_type/1, |
33 |
|
stanza_ref/1 |
34 |
|
]). |
35 |
|
% Stanza update |
36 |
|
-export([update_stanza/2]). |
37 |
|
% Access to namespaced fields |
38 |
|
-export([ |
39 |
|
set/3, |
40 |
|
set/4, |
41 |
|
set_permanent/2, |
42 |
|
set_permanent/3, |
43 |
|
set_permanent/4, |
44 |
|
append/4, |
45 |
|
get_permanent_keys/1, |
46 |
|
get_permanent_fields/1, |
47 |
|
get/2, |
48 |
|
get/3, |
49 |
|
get/4, |
50 |
|
delete/3, |
51 |
|
delete/2, |
52 |
|
delete_many/3 |
53 |
|
]). |
54 |
|
% Strip with or without stanza replacement |
55 |
|
-export([strip/1, strip/2]). |
56 |
|
|
57 |
|
-ignore_xref([delete/2, ref/1, set/3]). |
58 |
|
|
59 |
|
%% Note about 'undefined' to_jid and from_jid: these are the special cases when JID may be |
60 |
|
%% truly unknown: before a client is authorized. |
61 |
|
|
62 |
|
-type line_number() :: non_neg_integer(). |
63 |
|
-type location() :: #{mfa := mfa(), |
64 |
|
line := line_number(), |
65 |
|
file := string()}. |
66 |
|
|
67 |
|
-type stanza_metadata() :: #{ |
68 |
|
element := exml:element(), |
69 |
|
from_jid := jid:jid() | undefined, |
70 |
|
to_jid := jid:jid() | undefined, |
71 |
|
name := binary(), |
72 |
|
type := binary(), |
73 |
|
ref := reference() |
74 |
|
}. |
75 |
|
|
76 |
|
%% If it is defined as -opaque then dialyzer fails |
77 |
|
%% It's still valid in acc 2.0 and gain is probably not worth the effort |
78 |
|
-type t() :: #{ |
79 |
|
mongoose_acc := true, |
80 |
|
ref := reference(), |
81 |
|
timestamp := integer(), %microsecond |
82 |
|
origin_pid := pid(), |
83 |
|
origin_location := location(), |
84 |
|
origin_stanza := binary() | undefined, |
85 |
|
stanza := stanza_metadata() | undefined, |
86 |
|
lserver := jid:lserver(), |
87 |
|
host_type := binary() | undefined, |
88 |
|
non_strippable := [ns_key()], |
89 |
|
ns_key() => Value :: any() |
90 |
|
}. |
91 |
|
|
92 |
|
-export_type([t/0]). |
93 |
|
|
94 |
|
-type new_acc_params() :: #{ |
95 |
|
location := location(), |
96 |
|
lserver := jid:lserver(), |
97 |
|
element => exml:element() | undefined, |
98 |
|
host_type => binary() | undefined, % optional |
99 |
|
from_jid => jid:jid() | undefined, % optional |
100 |
|
to_jid => jid:jid() | undefined % optional |
101 |
|
}. |
102 |
|
|
103 |
|
-type strip_params() :: #{ |
104 |
|
lserver := jid:lserver(), |
105 |
|
element := exml:element(), |
106 |
|
host_type => binary() | undefined, % optional |
107 |
|
from_jid => jid:jid() | undefined, % optional |
108 |
|
to_jid => jid:jid() | undefined % optional |
109 |
|
}. |
110 |
|
|
111 |
|
-type stanza_params() :: #{ |
112 |
|
element := exml:element(), |
113 |
|
from_jid => jid:jid() | undefined, % optional |
114 |
|
to_jid => jid:jid() | undefined, % optional |
115 |
|
_ => _ |
116 |
|
}. |
117 |
|
|
118 |
|
-type ns_key() :: {NS :: any(), Key :: any()}. |
119 |
|
-type ns_key_value() :: {Namespace :: any(), K :: any(), V :: any()}. |
120 |
|
|
121 |
|
%% -------------------------------------------------------- |
122 |
|
%% API |
123 |
|
%% -------------------------------------------------------- |
124 |
|
|
125 |
|
-spec new(Params :: new_acc_params()) -> t(). |
126 |
|
new(#{ location := Location, lserver := LServer } = Params) -> |
127 |
32115 |
{ElementBin, Stanza} = |
128 |
|
case maps:get(element, Params, undefined) of |
129 |
6631 |
undefined -> {undefined, undefined}; |
130 |
25484 |
Element -> {exml:to_binary(Element), stanza_from_params(Params)} |
131 |
|
end, |
132 |
32115 |
HostType = get_host_type(Params), |
133 |
32115 |
#{ |
134 |
|
mongoose_acc => true, |
135 |
|
ref => make_ref(), |
136 |
|
timestamp => os:system_time(microsecond), |
137 |
|
origin_pid => self(), |
138 |
|
origin_location => Location, |
139 |
|
origin_stanza => ElementBin, |
140 |
|
stanza => Stanza, |
141 |
|
lserver => LServer, |
142 |
|
host_type => HostType, |
143 |
|
%% The non_strippable elements must be unique. |
144 |
|
%% This used to be represented with the sets module, but as the number of elements inserted |
145 |
|
%% was too small, sets were themselves an overhead, and also annoying when printing |
146 |
|
non_strippable => [] |
147 |
|
}. |
148 |
|
|
149 |
|
-spec ref(Acc :: t()) -> reference(). |
150 |
|
ref(#{ mongoose_acc := true, ref := Ref }) -> |
151 |
6 |
Ref. |
152 |
|
|
153 |
|
-spec timestamp(Acc :: t()) -> integer(). |
154 |
|
timestamp(#{ mongoose_acc := true, timestamp := TS }) -> |
155 |
171 |
TS. |
156 |
|
|
157 |
|
-spec lserver(Acc :: t()) -> jid:lserver(). |
158 |
|
lserver(#{ mongoose_acc := true, lserver := LServer }) -> |
159 |
16618 |
LServer. |
160 |
|
|
161 |
|
-spec host_type(Acc :: t()) -> binary() | undefined. |
162 |
|
host_type(#{ mongoose_acc := true, host_type := HostType }) -> |
163 |
160881 |
HostType. |
164 |
|
|
165 |
|
-spec element(Acc :: t()) -> exml:element() | undefined. |
166 |
|
element(#{ mongoose_acc := true, stanza := #{ element := El } }) -> |
167 |
51230 |
El; |
168 |
|
element(#{ mongoose_acc := true }) -> |
169 |
:-( |
undefined. |
170 |
|
|
171 |
|
-spec from_jid(Acc :: t()) -> jid:jid() | undefined. |
172 |
|
from_jid(#{ mongoose_acc := true, stanza := #{ from_jid := FromJID } }) -> |
173 |
36184 |
FromJID; |
174 |
|
from_jid(#{ mongoose_acc := true }) -> |
175 |
:-( |
undefined. |
176 |
|
|
177 |
|
-spec to_jid(Acc :: t()) -> jid:jid() | undefined. |
178 |
|
to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) -> |
179 |
12888 |
ToJID; |
180 |
|
to_jid(#{ mongoose_acc := true }) -> |
181 |
:-( |
undefined. |
182 |
|
|
183 |
|
-spec packet(Acc :: t()) -> ejabberd_c2s:packet() | undefined. |
184 |
|
packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID, |
185 |
|
from_jid := FromJID, |
186 |
|
element := El } }) -> |
187 |
351 |
{FromJID, ToJID, El}; |
188 |
|
packet(#{ mongoose_acc := true }) -> |
189 |
:-( |
undefined. |
190 |
|
|
191 |
|
-spec stanza_name(Acc :: t()) -> binary() | undefined. |
192 |
|
stanza_name(#{ mongoose_acc := true, stanza := #{ name := Name } }) -> |
193 |
22233 |
Name; |
194 |
|
stanza_name(#{ mongoose_acc := true }) -> |
195 |
:-( |
undefined. |
196 |
|
|
197 |
|
-spec stanza_type(Acc :: t()) -> binary() | undefined. |
198 |
|
stanza_type(#{ mongoose_acc := true, stanza := #{ type := Type } }) -> |
199 |
57630 |
Type; |
200 |
|
stanza_type(#{ mongoose_acc := true }) -> |
201 |
77 |
undefined. |
202 |
|
|
203 |
|
-spec stanza_ref(Acc :: t()) -> reference() | undefined. |
204 |
|
stanza_ref(#{ mongoose_acc := true, stanza := #{ ref := StanzaRef } }) -> |
205 |
5581 |
StanzaRef; |
206 |
|
stanza_ref(#{ mongoose_acc := true }) -> |
207 |
:-( |
undefined. |
208 |
|
|
209 |
|
-spec update_stanza(NewStanzaParams :: stanza_params(), Acc :: t()) -> t(). |
210 |
|
update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) -> |
211 |
28353 |
Acc#{ stanza := stanza_from_params(NewStanzaParams) }. |
212 |
|
|
213 |
|
%% Values set with this function are discarded during 'strip' operation... |
214 |
|
-spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t(). |
215 |
|
set(NS, K, V, #{ mongoose_acc := true } = Acc) -> |
216 |
114646 |
Acc#{ {NS, K} => V }. |
217 |
|
|
218 |
|
-spec set(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t(). |
219 |
|
set(_, [], Acc) -> |
220 |
:-( |
Acc; |
221 |
|
set(NS, [{K, V} | T], Acc) -> |
222 |
:-( |
NewAcc = set(NS, K, V, Acc), |
223 |
:-( |
set(NS, T, NewAcc). |
224 |
|
|
225 |
|
%% .. while these are not. |
226 |
|
-spec set_permanent(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t(). |
227 |
|
set_permanent(NS, K, V, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) -> |
228 |
25881 |
Key = {NS, K}, |
229 |
25881 |
NewNonStrippable = [Key | lists:delete(Key, NonStrippable)], |
230 |
25881 |
Acc#{ Key => V, non_strippable => NewNonStrippable }. |
231 |
|
|
232 |
|
-spec set_permanent(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t(). |
233 |
|
set_permanent(_, [], #{mongoose_acc := true} = Acc) -> |
234 |
1083 |
Acc; |
235 |
|
set_permanent(NS, [{K, V} | T], Acc) -> |
236 |
80 |
NewAcc = set_permanent(NS, K, V, Acc), |
237 |
80 |
set_permanent(NS, T, NewAcc). |
238 |
|
|
239 |
|
-spec set_permanent([ns_key_value()], Acc :: t()) -> t(). |
240 |
|
set_permanent([], #{mongoose_acc := true} = Acc) -> |
241 |
499 |
Acc; |
242 |
|
set_permanent([{NS, K, V} | T], Acc) -> |
243 |
878 |
NewAcc = set_permanent(NS, K, V, Acc), |
244 |
878 |
set_permanent(T, NewAcc). |
245 |
|
|
246 |
|
-spec append(NS :: any(), Key :: any(), Val :: any() | [any()], Acc :: t()) -> t(). |
247 |
|
append(NS, Key, Val, Acc) -> |
248 |
19473 |
OldVal = get(NS, Key, [], Acc), |
249 |
19473 |
set(NS, Key, append(OldVal, Val), Acc). |
250 |
|
|
251 |
|
-spec get_permanent_keys(Acc :: t()) -> [ns_key()]. |
252 |
|
get_permanent_keys(#{mongoose_acc := true, non_strippable := NonStrippable}) -> |
253 |
549 |
NonStrippable. |
254 |
|
|
255 |
|
-spec get_permanent_fields(Acc :: t()) -> [ns_key_value()]. |
256 |
|
get_permanent_fields(Acc) -> |
257 |
549 |
[{NS, Key, mongoose_acc:get(NS, Key, Acc)} || |
258 |
549 |
{NS, Key} <- mongoose_acc:get_permanent_keys(Acc)]. |
259 |
|
|
260 |
|
-spec get(Namespace :: any(), Acc :: t()) -> [{K :: any(), V :: any()}]. |
261 |
|
get(NS, #{mongoose_acc := true} = Acc) -> |
262 |
1083 |
Fn = fun({Namespace, K}, V, List) when Namespace =:= NS -> |
263 |
80 |
[{K, V} | List]; |
264 |
|
(_, _, List) -> |
265 |
12706 |
List |
266 |
|
end, |
267 |
1083 |
maps:fold(Fn, [], Acc). |
268 |
|
|
269 |
|
-spec get(Namespace :: any(), K :: any(), Acc :: t()) -> V :: any(). |
270 |
|
get(NS, K, #{ mongoose_acc := true } = Acc) -> |
271 |
51658 |
maps:get({NS, K}, Acc). |
272 |
|
|
273 |
|
-spec get(Namespace :: any(), K :: any(), Default :: any(), Acc :: t()) -> V :: any(). |
274 |
|
get(NS, K, Default, #{ mongoose_acc := true } = Acc) -> |
275 |
133290 |
maps:get({NS, K}, Acc, Default). |
276 |
|
|
277 |
|
-spec delete(Namespace :: any(), K :: any(), Acc :: t()) -> t(). |
278 |
|
delete(NS, K, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) -> |
279 |
3488 |
Key = {NS, K}, |
280 |
3488 |
Acc1 = maps:remove(Key, Acc0), |
281 |
3488 |
Acc1#{ non_strippable => lists:delete(Key, NonStrippable) }. |
282 |
|
|
283 |
|
-spec delete_many(Namespace :: any(), [K :: any()], Acc :: t()) -> t(). |
284 |
|
delete_many(_, [], Acc) -> |
285 |
361 |
Acc; |
286 |
|
delete_many(NS, [K | T], Acc) -> |
287 |
722 |
NewAcc = delete(NS, K, Acc), |
288 |
722 |
delete_many(NS, T, NewAcc). |
289 |
|
|
290 |
|
-spec delete(Namespace :: any(), Acc :: t()) -> t(). |
291 |
|
delete(NS, Acc) -> |
292 |
:-( |
KeyList = [K || {K, _} <- get(NS, Acc)], |
293 |
:-( |
delete_many(NS, KeyList, Acc). |
294 |
|
|
295 |
|
-spec strip(Acc :: t()) -> t(). |
296 |
|
strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) -> |
297 |
16526 |
maps:with(NonStrippable ++ default_non_strippable(), Acc). |
298 |
|
|
299 |
|
-spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t(). |
300 |
|
strip(#{ lserver := NewLServer } = Params, Acc) -> |
301 |
16465 |
StrippedAcc = strip(Acc), |
302 |
16465 |
StrippedAcc#{ lserver := NewLServer, host_type := get_host_type(Params), |
303 |
|
stanza := stanza_from_params(Params) }. |
304 |
|
|
305 |
|
%% -------------------------------------------------------- |
306 |
|
%% Internal functions |
307 |
|
%% -------------------------------------------------------- |
308 |
|
-spec get_host_type(new_acc_params() | strip_params()) -> binary() | undefined. |
309 |
|
get_host_type(#{host_type := HostType}) -> |
310 |
46119 |
HostType; |
311 |
|
get_host_type(_) -> |
312 |
2461 |
undefined. |
313 |
|
|
314 |
|
-spec stanza_from_params(Params :: stanza_params() | strip_params()) -> |
315 |
|
stanza_metadata(). |
316 |
|
stanza_from_params(#{ element := El } = Params) -> |
317 |
70302 |
#{ |
318 |
|
element => El, |
319 |
|
from_jid => jid_from_params(from_jid, <<"from">>, Params), |
320 |
|
to_jid => jid_from_params(to_jid, <<"to">>, Params), |
321 |
|
name => El#xmlel.name, |
322 |
|
type => exml_query:attr(El, <<"type">>), |
323 |
|
ref => make_ref() |
324 |
|
}. |
325 |
|
|
326 |
|
-spec jid_from_params(MapKey :: to_jid | from_jid, |
327 |
|
StanzaAttrName :: binary(), |
328 |
|
Params :: stanza_params()) -> jid:jid(). |
329 |
|
jid_from_params(MapKey, StanzaAttrName, #{ element := El } = Params) -> |
330 |
140604 |
case maps:find(MapKey, Params) of |
331 |
137230 |
{ok, JID0} -> JID0; |
332 |
3374 |
error -> #jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName)) |
333 |
|
end. |
334 |
|
|
335 |
|
-spec default_non_strippable() -> [atom()]. |
336 |
|
default_non_strippable() -> |
337 |
16526 |
[ |
338 |
|
mongoose_acc, |
339 |
|
ref, |
340 |
|
timestamp, |
341 |
|
origin_pid, |
342 |
|
origin_location, |
343 |
|
origin_stanza, |
344 |
|
stanza, |
345 |
|
lserver, |
346 |
|
host_type, |
347 |
|
non_strippable |
348 |
|
]. |
349 |
|
|
350 |
|
-spec append(OldVal :: list(), Val :: list() | any()) -> list(). |
351 |
2769 |
append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val; |
352 |
16704 |
append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal]. |