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 |
|
% C2S accumulator |
38 |
|
-export([get_statem_acc/1, set_statem_acc/2]). |
39 |
|
% Access to namespaced fields |
40 |
|
-export([ |
41 |
|
set/3, |
42 |
|
set/4, |
43 |
|
set_permanent/2, |
44 |
|
set_permanent/3, |
45 |
|
set_permanent/4, |
46 |
|
append/4, |
47 |
|
get_permanent_keys/1, |
48 |
|
get_permanent_fields/1, |
49 |
|
get/2, |
50 |
|
get/3, |
51 |
|
get/4, |
52 |
|
delete/3, |
53 |
|
delete/2, |
54 |
|
delete_many/3 |
55 |
|
]). |
56 |
|
% Strip with or without stanza replacement |
57 |
|
-export([strip/1, strip/2]). |
58 |
|
|
59 |
|
-ignore_xref([delete/2, ref/1]). |
60 |
|
|
61 |
|
%% Note about 'undefined' to_jid and from_jid: these are the special cases when JID may be |
62 |
|
%% truly unknown: before a client is authorized. |
63 |
|
|
64 |
|
-type line_number() :: non_neg_integer(). |
65 |
|
-type location() :: #{mfa := mfa(), |
66 |
|
line := line_number(), |
67 |
|
file := string()}. |
68 |
|
|
69 |
|
-type stanza_metadata() :: #{ |
70 |
|
element := exml:element(), |
71 |
|
from_jid := jid:jid() | undefined, |
72 |
|
to_jid := jid:jid() | undefined, |
73 |
|
name := binary(), |
74 |
|
type := binary(), |
75 |
|
ref := reference() |
76 |
|
}. |
77 |
|
|
78 |
|
%% If it is defined as -opaque then dialyzer fails |
79 |
|
%% It's still valid in acc 2.0 and gain is probably not worth the effort |
80 |
|
-type t() :: #{ |
81 |
|
mongoose_acc := true, |
82 |
|
ref := reference(), |
83 |
|
timestamp := integer(), %microsecond |
84 |
|
origin_pid := pid(), |
85 |
|
origin_location := location(), |
86 |
|
stanza := stanza_metadata() | undefined, |
87 |
|
lserver := jid:lserver(), |
88 |
|
host_type := binary() | undefined, |
89 |
|
non_strippable := [ns_key()], |
90 |
|
statem_acc := mongoose_c2s_acc:t(), |
91 |
|
ns_key() => Value :: any() |
92 |
|
}. |
93 |
|
|
94 |
|
-export_type([t/0, |
95 |
|
new_acc_params/0]). |
96 |
|
|
97 |
|
-type new_acc_params() :: #{ |
98 |
|
location := location(), |
99 |
|
lserver => jid:lserver(), |
100 |
|
element => exml:element() | undefined, |
101 |
|
host_type => binary() | undefined, % optional |
102 |
|
from_jid => jid:jid() | undefined, % optional |
103 |
|
to_jid => jid:jid() | undefined, % optional |
104 |
|
statem_acc => mongoose_c2s_acc:t() % optional |
105 |
|
}. |
106 |
|
|
107 |
|
-type strip_params() :: #{ |
108 |
|
lserver := jid:lserver(), |
109 |
|
element := exml:element(), |
110 |
|
host_type => binary() | undefined, % optional |
111 |
|
from_jid => jid:jid() | undefined, % optional |
112 |
|
to_jid => jid:jid() | undefined % optional |
113 |
|
}. |
114 |
|
|
115 |
|
-type stanza_params() :: #{ |
116 |
|
element := exml:element(), |
117 |
|
from_jid => jid:jid() | undefined, % optional |
118 |
|
to_jid => jid:jid() | undefined, % optional |
119 |
|
_ => _ |
120 |
|
}. |
121 |
|
|
122 |
|
-type ns_key() :: {NS :: any(), Key :: any()}. |
123 |
|
-type ns_key_value() :: {Namespace :: any(), K :: any(), V :: any()}. |
124 |
|
|
125 |
|
%% -------------------------------------------------------- |
126 |
|
%% API |
127 |
|
%% -------------------------------------------------------- |
128 |
|
|
129 |
|
-spec new(Params :: new_acc_params()) -> t(). |
130 |
|
new(#{ location := Location, lserver := LServer } = Params) -> |
131 |
1003 |
Stanza = case maps:get(element, Params, undefined) of |
132 |
430 |
undefined -> undefined; |
133 |
573 |
_Element -> stanza_from_params(Params) |
134 |
|
end, |
135 |
1003 |
HostType = get_host_type(Params), |
136 |
1003 |
#{ |
137 |
|
mongoose_acc => true, |
138 |
|
ref => make_ref(), |
139 |
|
timestamp => erlang:system_time(microsecond), |
140 |
|
origin_pid => self(), |
141 |
|
origin_location => Location, |
142 |
|
stanza => Stanza, |
143 |
|
lserver => LServer, |
144 |
|
host_type => HostType, |
145 |
|
statem_acc => get_mongoose_c2s_acc(Params), |
146 |
|
%% The non_strippable elements must be unique. |
147 |
|
%% This used to be represented with the sets module, but as the number of elements inserted |
148 |
|
%% was too small, sets were themselves an overhead, and also annoying when printing |
149 |
|
non_strippable => [] |
150 |
|
}. |
151 |
|
|
152 |
|
-spec ref(Acc :: t()) -> reference(). |
153 |
|
ref(#{ mongoose_acc := true, ref := Ref }) -> |
154 |
:-( |
Ref. |
155 |
|
|
156 |
|
-spec timestamp(Acc :: t()) -> integer(). |
157 |
|
timestamp(#{ mongoose_acc := true, timestamp := TS }) -> |
158 |
15 |
TS. |
159 |
|
|
160 |
|
-spec lserver(Acc :: t()) -> jid:lserver(). |
161 |
|
lserver(#{ mongoose_acc := true, lserver := LServer }) -> |
162 |
653 |
LServer. |
163 |
|
|
164 |
|
-spec host_type(Acc :: t()) -> binary() | undefined. |
165 |
|
host_type(#{ mongoose_acc := true, host_type := HostType }) -> |
166 |
1739 |
HostType. |
167 |
|
|
168 |
|
-spec element(Acc :: t()) -> exml:element() | undefined. |
169 |
|
element(#{ mongoose_acc := true, stanza := #{ element := El } }) -> |
170 |
2719 |
El; |
171 |
|
element(#{ mongoose_acc := true }) -> |
172 |
:-( |
undefined. |
173 |
|
|
174 |
|
-spec from_jid(Acc :: t()) -> jid:jid() | undefined. |
175 |
|
from_jid(#{ mongoose_acc := true, stanza := #{ from_jid := FromJID } }) -> |
176 |
86 |
FromJID; |
177 |
|
from_jid(#{ mongoose_acc := true }) -> |
178 |
:-( |
undefined. |
179 |
|
|
180 |
|
-spec to_jid(Acc :: t()) -> jid:jid() | undefined. |
181 |
|
to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) -> |
182 |
32 |
ToJID; |
183 |
|
to_jid(#{ mongoose_acc := true }) -> |
184 |
:-( |
undefined. |
185 |
|
|
186 |
|
-spec packet(Acc :: t()) -> mongoose_c2s:packet() | undefined. |
187 |
|
packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID, |
188 |
|
from_jid := FromJID, |
189 |
|
element := El } }) -> |
190 |
1290 |
{FromJID, ToJID, El}; |
191 |
|
packet(#{ mongoose_acc := true }) -> |
192 |
:-( |
undefined. |
193 |
|
|
194 |
|
-spec stanza_name(Acc :: t()) -> binary() | undefined. |
195 |
|
stanza_name(#{ mongoose_acc := true, stanza := #{ name := Name } }) -> |
196 |
931 |
Name; |
197 |
|
stanza_name(#{ mongoose_acc := true }) -> |
198 |
:-( |
undefined. |
199 |
|
|
200 |
|
-spec stanza_type(Acc :: t()) -> binary() | undefined. |
201 |
|
stanza_type(#{ mongoose_acc := true, stanza := #{ type := Type } }) -> |
202 |
961 |
Type; |
203 |
|
stanza_type(#{ mongoose_acc := true }) -> |
204 |
:-( |
undefined. |
205 |
|
|
206 |
|
-spec stanza_ref(Acc :: t()) -> reference() | undefined. |
207 |
|
stanza_ref(#{ mongoose_acc := true, stanza := #{ ref := StanzaRef } }) -> |
208 |
501 |
StanzaRef; |
209 |
|
stanza_ref(#{ mongoose_acc := true }) -> |
210 |
:-( |
undefined. |
211 |
|
|
212 |
|
-spec update_stanza(NewStanzaParams :: stanza_params(), Acc :: t()) -> t(). |
213 |
|
update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) -> |
214 |
2440 |
Acc#{ stanza := stanza_from_params(NewStanzaParams) }. |
215 |
|
|
216 |
|
-spec get_statem_acc(Acc :: t()) -> mongoose_c2s_acc:t(). |
217 |
|
get_statem_acc(#{ mongoose_acc := true, statem_acc := StatemAcc }) -> |
218 |
1321 |
StatemAcc. |
219 |
|
|
220 |
|
-spec set_statem_acc(NewStatemAcc :: mongoose_c2s_acc:t(), Acc :: t()) -> t(). |
221 |
|
set_statem_acc(NewStatemAcc, Acc = #{ mongoose_acc := true }) -> |
222 |
447 |
Acc#{statem_acc := NewStatemAcc}. |
223 |
|
|
224 |
|
%% Values set with this function are discarded during 'strip' operation... |
225 |
|
-spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t(). |
226 |
|
set(NS, K, V, #{ mongoose_acc := true } = Acc) -> |
227 |
1767 |
Acc#{ {NS, K} => V }. |
228 |
|
|
229 |
|
-spec set(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t(). |
230 |
|
set(NS, KVs, #{ mongoose_acc := true } = Acc) -> |
231 |
1324 |
NSKVs = [ {{NS, K}, V} || {K, V} <- KVs ], |
232 |
1324 |
Input = maps:from_list(NSKVs), |
233 |
1324 |
maps:merge(Acc, Input). |
234 |
|
|
235 |
|
-spec set([ns_key_value()], Acc :: t()) -> t(). |
236 |
|
set(NSKVs, #{ mongoose_acc := true } = Acc) -> |
237 |
:-( |
PropList = [ {{NS, K}, V} || {NS, K, V} <- NSKVs ], |
238 |
:-( |
Input = maps:from_list(PropList), |
239 |
:-( |
maps:merge(Acc, Input). |
240 |
|
|
241 |
|
%% .. while these are not. |
242 |
|
-spec set_permanent(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t(). |
243 |
|
set_permanent(NS, K, V, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) -> |
244 |
177 |
Key = {NS, K}, |
245 |
177 |
NewNonStrippable = [Key | lists:delete(Key, NonStrippable)], |
246 |
177 |
Acc#{ Key => V, non_strippable := NewNonStrippable }. |
247 |
|
|
248 |
|
-spec set_permanent(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t(). |
249 |
|
set_permanent(NS, KVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) -> |
250 |
705 |
NewKeys = [{NS, K} || {K, _V} <- KVs, not lists:member({NS, K}, NonStrippable)], |
251 |
705 |
set(NS, KVs, Acc#{non_strippable := NewKeys ++ NonStrippable }). |
252 |
|
|
253 |
|
-spec set_permanent([ns_key_value()], Acc :: t()) -> t(). |
254 |
|
set_permanent(NSKVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) -> |
255 |
:-( |
NewKeys = [{NS, K} || {NS, K, _V} <- NSKVs, not lists:member({NS, K}, NonStrippable)], |
256 |
:-( |
set(NSKVs, Acc#{non_strippable := NewKeys ++ NonStrippable }). |
257 |
|
|
258 |
|
-spec append(NS :: any(), Key :: any(), Val :: any() | [any()], Acc :: t()) -> t(). |
259 |
|
append(NS, Key, Val, Acc) -> |
260 |
721 |
OldVal = get(NS, Key, [], Acc), |
261 |
721 |
set(NS, Key, append(OldVal, Val), Acc). |
262 |
|
|
263 |
|
-spec get_permanent_keys(Acc :: t()) -> [ns_key()]. |
264 |
|
get_permanent_keys(#{mongoose_acc := true, non_strippable := NonStrippable}) -> |
265 |
:-( |
NonStrippable. |
266 |
|
|
267 |
|
-spec get_permanent_fields(Acc :: t()) -> [ns_key_value()]. |
268 |
|
get_permanent_fields(Acc) -> |
269 |
:-( |
[{NS, Key, mongoose_acc:get(NS, Key, Acc)} || |
270 |
:-( |
{NS, Key} <- mongoose_acc:get_permanent_keys(Acc)]. |
271 |
|
|
272 |
|
-spec get(Namespace :: any(), Acc :: t()) -> [{K :: any(), V :: any()}]. |
273 |
|
get(NS, #{mongoose_acc := true} = Acc) -> |
274 |
:-( |
Fn = fun({Namespace, K}, V, List) when Namespace =:= NS -> |
275 |
:-( |
[{K, V} | List]; |
276 |
|
(_, _, List) -> |
277 |
:-( |
List |
278 |
|
end, |
279 |
:-( |
maps:fold(Fn, [], Acc). |
280 |
|
|
281 |
|
-spec get(Namespace :: any(), K :: any(), Acc :: t()) -> V :: any(). |
282 |
|
get(NS, K, #{ mongoose_acc := true } = Acc) -> |
283 |
1420 |
maps:get({NS, K}, Acc). |
284 |
|
|
285 |
|
-spec get(Namespace :: any(), K :: any(), Default :: any(), Acc :: t()) -> V :: any(). |
286 |
|
get(NS, K, Default, #{ mongoose_acc := true } = Acc) -> |
287 |
3759 |
maps:get({NS, K}, Acc, Default). |
288 |
|
|
289 |
|
-spec delete(Namespace :: any(), K :: any(), Acc :: t()) -> t(). |
290 |
|
delete(NS, K, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) -> |
291 |
63 |
Key = {NS, K}, |
292 |
63 |
Acc1 = maps:remove(Key, Acc0), |
293 |
63 |
Acc1#{ non_strippable := lists:delete(Key, NonStrippable) }. |
294 |
|
|
295 |
|
-spec delete_many(Namespace :: any(), [K :: any()], Acc :: t()) -> t(). |
296 |
|
delete_many(NS, Keys, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) -> |
297 |
:-( |
KVs = [{NS, K} || K <- Keys], |
298 |
:-( |
Acc1 = maps:without(KVs, Acc0), |
299 |
:-( |
Acc1#{ non_strippable := lists:subtract(NonStrippable, KVs) }. |
300 |
|
|
301 |
|
-spec delete(Namespace :: any(), Acc :: t()) -> t(). |
302 |
|
delete(NS, Acc) -> |
303 |
:-( |
KeyList = [K || {K, _} <- get(NS, Acc)], |
304 |
:-( |
delete_many(NS, KeyList, Acc). |
305 |
|
|
306 |
|
-spec strip(Acc :: t()) -> t(). |
307 |
|
strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) -> |
308 |
654 |
Stripped = maps:with(NonStrippable ++ default_non_strippable(), Acc), |
309 |
654 |
Stripped#{statem_acc := mongoose_c2s_acc:new()}. |
310 |
|
|
311 |
|
-spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t(). |
312 |
|
strip(#{ lserver := NewLServer } = Params, Acc) -> |
313 |
651 |
StrippedAcc = strip(Acc), |
314 |
651 |
StrippedAcc#{ lserver := NewLServer, host_type := get_host_type(Params), |
315 |
|
stanza := stanza_from_params(Params) }. |
316 |
|
|
317 |
|
%% -------------------------------------------------------- |
318 |
|
%% Internal functions |
319 |
|
%% -------------------------------------------------------- |
320 |
|
-spec get_host_type(new_acc_params() | strip_params()) -> binary() | undefined. |
321 |
|
get_host_type(#{host_type := HostType}) -> |
322 |
1575 |
HostType; |
323 |
|
get_host_type(_) -> |
324 |
79 |
undefined. |
325 |
|
|
326 |
|
-spec get_mongoose_c2s_acc(new_acc_params() | strip_params()) -> mongoose_c2s_acc:t() | undefined. |
327 |
|
get_mongoose_c2s_acc(#{statem_acc := C2SAcc}) -> |
328 |
:-( |
C2SAcc; |
329 |
|
get_mongoose_c2s_acc(_) -> |
330 |
1003 |
mongoose_c2s_acc:new(). |
331 |
|
|
332 |
|
-spec stanza_from_params(Params :: stanza_params() | strip_params()) -> |
333 |
|
stanza_metadata(). |
334 |
|
stanza_from_params(#{ element := El } = Params) -> |
335 |
3664 |
#{ |
336 |
|
element => El, |
337 |
|
from_jid => jid_from_params(from_jid, <<"from">>, Params), |
338 |
|
to_jid => jid_from_params(to_jid, <<"to">>, Params), |
339 |
|
name => El#xmlel.name, |
340 |
|
type => exml_query:attr(El, <<"type">>), |
341 |
|
ref => make_ref() |
342 |
|
}. |
343 |
|
|
344 |
|
-spec jid_from_params(MapKey :: to_jid | from_jid, |
345 |
|
StanzaAttrName :: binary(), |
346 |
|
Params :: stanza_params()) -> jid:jid(). |
347 |
|
jid_from_params(to_jid, _, #{to_jid := Jid}) -> |
348 |
3605 |
Jid; |
349 |
|
jid_from_params(from_jid, _, #{from_jid := Jid}) -> |
350 |
3664 |
Jid; |
351 |
|
jid_from_params(_, StanzaAttrName, #{element := El}) -> |
352 |
59 |
#jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName)). |
353 |
|
|
354 |
|
-spec default_non_strippable() -> [atom()]. |
355 |
|
default_non_strippable() -> |
356 |
654 |
[ |
357 |
|
mongoose_acc, |
358 |
|
ref, |
359 |
|
timestamp, |
360 |
|
origin_pid, |
361 |
|
origin_location, |
362 |
|
stanza, |
363 |
|
lserver, |
364 |
|
host_type, |
365 |
|
statem_acc, |
366 |
|
non_strippable |
367 |
|
]. |
368 |
|
|
369 |
|
-spec append(OldVal :: list(), Val :: list() | any()) -> list(). |
370 |
:-( |
append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val; |
371 |
721 |
append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal]. |