./ct_report/coverage/mod_mam_cassandra_prefs.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Uvarov Michael <arcusfelis@gmail.com>
3 %%% @copyright (C) 2013, Uvarov Michael
4 %%% @doc A backend for storing MAM preferencies using Cassandra.
5 %%% @end
6 %%%-------------------------------------------------------------------
7 -module(mod_mam_cassandra_prefs).
8 -behaviour(mongoose_cassandra).
9 -behaviour(gen_mod).
10
11 %% ----------------------------------------------------------------------
12 %% Exports
13
14 %% gen_mod handlers
15 -export([start/2, stop/1]).
16
17 %% MAM hook handlers
18 -behaviour(ejabberd_gen_mam_prefs).
19 -export([get_behaviour/3,
20 get_prefs/3,
21 set_prefs/3,
22 remove_archive/3]).
23
24 -export([prepared_queries/0]).
25
26 -include("mongoose.hrl").
27 -include("jlib.hrl").
28 -include_lib("exml/include/exml.hrl").
29
30 -type host_type() :: mongooseim:host_type().
31
32 %% ----------------------------------------------------------------------
33 %% gen_mod callbacks
34 %% Starting and stopping functions for users' archives
35
36 -spec start(host_type(), gen_mod:module_opts()) -> ok.
37 start(HostType, Opts) ->
38 17 gen_hook:add_handlers(hooks(HostType, Opts)).
39
40 -spec stop(host_type()) -> ok.
41 stop(HostType) ->
42 17 Opts = gen_mod:get_loaded_module_opts(HostType, ?MODULE),
43 17 gen_hook:delete_handlers(hooks(HostType, Opts)).
44
45 %% ----------------------------------------------------------------------
46 %% Hooks
47
48 hooks(HostType, Opts) ->
49 34 lists:flatmap(fun(Type) -> hooks(HostType, Type, Opts) end, [pm, muc]).
50
51 hooks(HostType, pm, #{pm := true}) ->
52 24 [{mam_get_behaviour, HostType, fun ?MODULE:get_behaviour/3, #{}, 50},
53 {mam_get_prefs, HostType, fun ?MODULE:get_prefs/3, #{}, 50},
54 {mam_set_prefs, HostType, fun ?MODULE:set_prefs/3, #{}, 50},
55 {mam_remove_archive, HostType, fun ?MODULE:remove_archive/3, #{}, 50}];
56 hooks(HostType, muc, #{muc := true}) ->
57 20 [{mam_muc_get_behaviour, HostType, fun ?MODULE:get_behaviour/3, #{}, 50},
58 {mam_muc_get_prefs, HostType, fun ?MODULE:get_prefs/3, #{}, 50},
59 {mam_muc_set_prefs, HostType, fun ?MODULE:set_prefs/3, #{}, 50},
60 {mam_muc_remove_archive, HostType, fun ?MODULE:remove_archive/3, #{}, 50}];
61 hooks(_HostType, _Opt, _Opts) ->
62 24 [].
63
64 %% ----------------------------------------------------------------------
65
66 prepared_queries() ->
67 692 [
68 {set_prefs_ts_query,
69 "INSERT INTO mam_config(user_jid, remote_jid, behaviour) VALUES (?, ?, ?) USING TIMESTAMP ?"},
70 {get_prefs_query,
71 "SELECT remote_jid, behaviour FROM mam_config WHERE user_jid = ?"},
72 {get_behaviour_bare_query,
73 "SELECT remote_jid, behaviour FROM mam_config WHERE user_jid = ? AND remote_jid IN ('', ?)"},
74 {get_behaviour_full_query,
75 "SELECT remote_jid, behaviour FROM mam_config WHERE user_jid = ? AND remote_jid "
76 "IN ('', :start_remote_jid, :end_remote_jid)"},
77 {del_prefs_ts_query,
78 "DELETE FROM mam_config USING TIMESTAMP ? WHERE user_jid = ?"}
79 ].
80
81 %% ----------------------------------------------------------------------
82 %% Internal functions and callbacks
83
84 -spec get_behaviour(Acc, Params, Extra) -> {ok, Acc} when
85 Acc :: mod_mam:archive_behaviour(),
86 Params :: ejabberd_gen_mam_prefs:get_behaviour_params(),
87 Extra :: gen_hook:extra().
88 get_behaviour(DefaultBehaviour,
89 #{owner := LocJID, remote := RemJID},
90 #{host_type := HostType}) ->
91 282 get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType);
92 get_behaviour(DefaultBehaviour,
93 #{room := LocJID, remote := RemJID},
94 #{host_type := HostType}) ->
95 132 get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType).
96
97 get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType) ->
98 414 BUserJID = mod_mam_utils:bare_jid(LocJID),
99 414 BRemBareJID = mod_mam_utils:bare_jid(RemJID),
100 414 BRemJID = mod_mam_utils:full_jid(RemJID),
101 414 case query_behaviour(HostType, LocJID, BUserJID, BRemJID, BRemBareJID) of
102 {ok, []} ->
103 246 {ok, DefaultBehaviour};
104 {ok, [_ | _] = Rows} ->
105 %% After sort <<>>, <<"a">>, <<"a/b">>
106 168 SortedRows = lists:sort(
107 fun(#{remote_jid := JID1, behaviour := B1},
108 #{remote_jid := JID2, behaviour := B2}) ->
109 72 {JID1, B1} < {JID2, B2}
110 end, Rows),
111 168 #{behaviour := Behaviour} = lists:last(SortedRows),
112 168 {ok, decode_behaviour(Behaviour)}
113 end.
114
115
116 -spec set_prefs(Acc, Params, Extra) -> {ok, Acc} when
117 Acc :: term(),
118 Params :: ejabberd_gen_mam_prefs:set_prefs_params(),
119 Extra :: gen_hook:extra().
120 set_prefs(_Result,
121 #{owner := UserJID, default_mode := DefaultMode, always_jids := AlwaysJIDs,
122 never_jids := NeverJIDs},
123 #{host_type := HostType}) ->
124 44 set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs);
125 set_prefs(_Result,
126 #{room := UserJID, default_mode := DefaultMode, always_jids := AlwaysJIDs,
127 never_jids := NeverJIDs},
128 #{host_type := HostType}) ->
129 23 set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs).
130
131 set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
132 67 try
133 67 {ok, set_prefs2(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs)}
134 catch Type:Error:StackTrace ->
135
:-(
?LOG_ERROR(#{what => mam_set_prefs_failed,
136 user_jid => UserJID, default_mode => DefaultMode,
137 always_jids => AlwaysJIDs, never_jids => NeverJIDs,
138
:-(
class => Type, reason => Error, stacktrace => StackTrace}),
139
:-(
{ok, {error, Error}}
140 end.
141
142 set_prefs2(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
143 67 BUserJID = mod_mam_utils:bare_jid(UserJID),
144 %% Force order of operations using timestamps
145 %% http://stackoverflow.com/questions/30317877/cassandra-batch-statement-execution-order
146 67 Now = mongoose_cassandra:now_timestamp(),
147 67 Next = Now + 1,
148 67 DelParams = #{'[timestamp]' => Now, user_jid => BUserJID},
149 67 MultiParams = [encode_row(BUserJID, <<>>, encode_behaviour(DefaultMode), Next)]
150 42 ++ [encode_row(BUserJID, BinJID, <<"A">>, Next) || BinJID <- AlwaysJIDs]
151 38 ++ [encode_row(BUserJID, BinJID, <<"N">>, Next) || BinJID <- NeverJIDs],
152 67 DelQuery = {del_prefs_ts_query, [DelParams]},
153 67 SetQuery = {set_prefs_ts_query, MultiParams},
154 67 Queries = [DelQuery, SetQuery],
155 67 Res = [mongoose_cassandra:cql_write(pool_name(HostType), UserJID, ?MODULE, Query, Params)
156 67 || {Query, Params} <- Queries],
157 67 ?LOG_DEBUG(#{what => mam_set_prefs, user_jid => UserJID, default_mode => DefaultMode,
158 67 always_jids => AlwaysJIDs, never_jids => NeverJIDs, result => Res}),
159 67 ok.
160
161 encode_row(BUserJID, BRemoteJID, Behaviour, Timestamp) ->
162 147 #{user_jid => BUserJID, remote_jid => BRemoteJID,
163 behaviour => Behaviour, '[timestamp]' => Timestamp}.
164
165
166 -spec get_prefs(Acc, Params, Extra) -> {ok, Acc} when
167 Acc :: mod_mam:preference(),
168 Params :: ejabberd_gen_mam_prefs:get_prefs_params(),
169 Extra :: gen_hook:extra().
170 get_prefs({GlobalDefaultMode, _, _}, #{owner := UserJID}, #{host_type := HostType}) ->
171 23 get_prefs2(GlobalDefaultMode, UserJID, HostType);
172 get_prefs({GlobalDefaultMode, _, _}, #{room := UserJID}, #{host_type := HostType}) ->
173 2 get_prefs2(GlobalDefaultMode, UserJID, HostType).
174
175 get_prefs2(GlobalDefaultMode, UserJID, HostType) ->
176 25 BUserJID = mod_mam_utils:bare_jid(UserJID),
177 25 Params = #{user_jid => BUserJID},
178 25 {ok, Rows} = mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE,
179 get_prefs_query, Params),
180 25 {ok, decode_prefs_rows(Rows, GlobalDefaultMode, [], [])}.
181
182 -spec remove_archive(Acc, Params, Extra) -> {ok, Acc} when
183 Acc :: term(),
184 Params :: #{archive_id := mod_mam:archive_id() | undefined, owner => jid:jid(), room => jid:jid()},
185 Extra :: gen_hook:extra().
186 remove_archive(Acc, #{owner := UserJID}, #{host_type := HostType}) ->
187 35 remove_archive(HostType, UserJID),
188 35 {ok, Acc};
189 remove_archive(Acc, #{room := UserJID}, #{host_type := HostType}) ->
190 84 remove_archive(HostType, UserJID),
191 84 {ok, Acc}.
192
193 remove_archive(HostType, UserJID) ->
194 119 BUserJID = mod_mam_utils:bare_jid(UserJID),
195 119 Now = mongoose_cassandra:now_timestamp(),
196 119 Params = #{'[timestamp]' => Now, user_jid => BUserJID},
197 119 mongoose_cassandra:cql_write(pool_name(HostType), UserJID,
198 ?MODULE, del_prefs_ts_query, [Params]).
199
200 -spec query_behaviour(host_type(), UserJID :: jid:jid(), BUserJID :: binary() | string(),
201 BRemJID :: binary() | string(), BRemBareJID :: binary() | string()) -> any().
202 query_behaviour(HostType, UserJID, BUserJID, BRemJID, BRemBareJID)
203 when BRemJID == BRemBareJID ->
204 10 Params = #{user_jid => BUserJID, remote_jid => BRemBareJID},
205 10 mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE,
206 get_behaviour_bare_query, Params);
207 query_behaviour(HostType, UserJID, BUserJID, BRemJID, BRemBareJID) ->
208 404 Params = #{user_jid => BUserJID, start_remote_jid => BRemJID,
209 end_remote_jid => BRemBareJID},
210 404 mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE,
211 get_behaviour_full_query, Params).
212
213 %% ----------------------------------------------------------------------
214 %% Helpers
215
216 -spec encode_behaviour('always' | 'never' | 'roster') -> binary().
217 25 encode_behaviour(roster) -> <<"R">>;
218 21 encode_behaviour(always) -> <<"A">>;
219 21 encode_behaviour(never) -> <<"N">>.
220
221
222 -spec decode_behaviour(<<_:8>>) -> 'always' | 'never' | 'roster'.
223 43 decode_behaviour(<<"R">>) -> roster;
224 75 decode_behaviour(<<"A">>) -> always;
225 75 decode_behaviour(<<"N">>) -> never.
226
227 -spec decode_prefs_rows([[term()]], DefaultMode, AlwaysJIDs, NeverJIDs) ->
228 {DefaultMode, AlwaysJIDs, NeverJIDs} when
229 DefaultMode :: mod_mam:archive_behaviour(),
230 AlwaysJIDs :: [jid:literal_jid()],
231 NeverJIDs :: [jid:literal_jid()].
232 decode_prefs_rows([], DefaultMode, AlwaysJIDs, NeverJIDs) ->
233 25 {DefaultMode, AlwaysJIDs, NeverJIDs};
234
235 decode_prefs_rows([#{remote_jid := <<>>, behaviour := Behaviour} | Rows],
236 _DefaultMode, AlwaysJIDs, NeverJIDs) ->
237 25 decode_prefs_rows(Rows, decode_behaviour(Behaviour), AlwaysJIDs, NeverJIDs);
238 decode_prefs_rows([#{remote_jid := JID, behaviour := <<"A">>} | Rows],
239 DefaultMode, AlwaysJIDs, NeverJIDs) ->
240 18 decode_prefs_rows(Rows, DefaultMode, [JID | AlwaysJIDs], NeverJIDs);
241 decode_prefs_rows([#{remote_jid := JID, behaviour := <<"N">>} | Rows],
242 DefaultMode, AlwaysJIDs, NeverJIDs) ->
243 14 decode_prefs_rows(Rows, DefaultMode, AlwaysJIDs, [JID | NeverJIDs]).
244
245 %% ----------------------------------------------------------------------
246 %% Params getters
247
248 -spec pool_name(HostType :: host_type()) -> term().
249 pool_name(_HostType) ->
250 692 default.
Line Hits Source