./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 9 gen_hook:add_handlers(hooks(HostType, Opts)).
39
40 -spec stop(host_type()) -> ok.
41 stop(HostType) ->
42 9 Opts = gen_mod:get_loaded_module_opts(HostType, ?MODULE),
43 9 gen_hook:delete_handlers(hooks(HostType, Opts)).
44
45 %% ----------------------------------------------------------------------
46 %% Hooks
47
48 hooks(HostType, Opts) ->
49 18 lists:flatmap(fun(Type) -> hooks(HostType, Type, Opts) end, [pm, muc]).
50
51 hooks(HostType, pm, #{pm := true}) ->
52 16 [{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 4 [{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 16 [].
63
64 %% ----------------------------------------------------------------------
65
66 prepared_queries() ->
67 536 [
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 278 get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType);
92 get_behaviour(DefaultBehaviour,
93 #{room := LocJID, remote := RemJID},
94 #{host_type := HostType}) ->
95 46 get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType).
96
97 get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType) ->
98 324 BUserJID = mod_mam_utils:bare_jid(LocJID),
99 324 BRemBareJID = mod_mam_utils:bare_jid(RemJID),
100 324 BRemJID = mod_mam_utils:full_jid(RemJID),
101 324 case query_behaviour(HostType, LocJID, BUserJID, BRemJID, BRemBareJID) of
102 {ok, []} ->
103 240 {ok, DefaultBehaviour};
104 {ok, [_ | _] = Rows} ->
105 %% After sort <<>>, <<"a">>, <<"a/b">>
106 84 SortedRows = lists:sort(
107 fun(#{remote_jid := JID1, behaviour := B1},
108 #{remote_jid := JID2, behaviour := B2}) ->
109 48 {JID1, B1} < {JID2, B2}
110 end, Rows),
111 84 #{behaviour := Behaviour} = lists:last(SortedRows),
112 84 {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 try
125 44 {ok, set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs)}
126 catch Type:Error:StackTrace ->
127
:-(
?LOG_ERROR(#{what => mam_set_prefs_failed,
128 user_jid => UserJID, default_mode => DefaultMode,
129 always_jids => AlwaysJIDs, never_jids => NeverJIDs,
130
:-(
class => Type, reason => Error, stacktrace => StackTrace}),
131
:-(
{ok, {error, Error}}
132 end.
133
134 set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
135 44 BUserJID = mod_mam_utils:bare_jid(UserJID),
136 %% Force order of operations using timestamps
137 %% http://stackoverflow.com/questions/30317877/cassandra-batch-statement-execution-order
138 44 Now = mongoose_cassandra:now_timestamp(),
139 44 Next = Now + 1,
140 44 DelParams = #{'[timestamp]' => Now, user_jid => BUserJID},
141 44 MultiParams = [encode_row(BUserJID, <<>>, encode_behaviour(DefaultMode), Next)]
142 27 ++ [encode_row(BUserJID, BinJID, <<"A">>, Next) || BinJID <- AlwaysJIDs]
143 25 ++ [encode_row(BUserJID, BinJID, <<"N">>, Next) || BinJID <- NeverJIDs],
144 44 DelQuery = {del_prefs_ts_query, [DelParams]},
145 44 SetQuery = {set_prefs_ts_query, MultiParams},
146 44 Queries = [DelQuery, SetQuery],
147 44 Res = [mongoose_cassandra:cql_write(pool_name(HostType), UserJID, ?MODULE, Query, Params)
148 44 || {Query, Params} <- Queries],
149 44 ?LOG_DEBUG(#{what => mam_set_prefs, user_jid => UserJID, default_mode => DefaultMode,
150 44 always_jids => AlwaysJIDs, never_jids => NeverJIDs, result => Res}),
151 44 ok.
152
153 encode_row(BUserJID, BRemoteJID, Behaviour, Timestamp) ->
154 96 #{user_jid => BUserJID, remote_jid => BRemoteJID,
155 behaviour => Behaviour, '[timestamp]' => Timestamp}.
156
157
158 -spec get_prefs(Acc, Params, Extra) -> {ok, Acc} when
159 Acc :: mod_mam:preference(),
160 Params :: ejabberd_gen_mam_prefs:get_prefs_params(),
161 Extra :: gen_hook:extra().
162 get_prefs({GlobalDefaultMode, _, _}, #{owner := UserJID}, #{host_type := HostType}) ->
163 23 BUserJID = mod_mam_utils:bare_jid(UserJID),
164 23 Params = #{user_jid => BUserJID},
165 23 {ok, Rows} = mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE,
166 get_prefs_query, Params),
167 23 {ok, decode_prefs_rows(Rows, GlobalDefaultMode, [], [])}.
168
169
170 -spec remove_archive(Acc, Params, Extra) -> {ok, Acc} when
171 Acc :: term(),
172 Params :: #{archive_id := mod_mam:archive_id() | undefined, owner => jid:jid(), room => jid:jid()},
173 Extra :: gen_hook:extra().
174 remove_archive(Acc, #{owner := UserJID}, #{host_type := HostType}) ->
175 37 remove_archive(HostType, UserJID),
176 37 {ok, Acc};
177 remove_archive(Acc, #{room := UserJID}, #{host_type := HostType}) ->
178 64 remove_archive(HostType, UserJID),
179 64 {ok, Acc}.
180
181 remove_archive(HostType, UserJID) ->
182 101 BUserJID = mod_mam_utils:bare_jid(UserJID),
183 101 Now = mongoose_cassandra:now_timestamp(),
184 101 Params = #{'[timestamp]' => Now, user_jid => BUserJID},
185 101 mongoose_cassandra:cql_write(pool_name(HostType), UserJID,
186 ?MODULE, del_prefs_ts_query, [Params]).
187
188 -spec query_behaviour(host_type(), UserJID :: jid:jid(), BUserJID :: binary() | string(),
189 BRemJID :: binary() | string(), BRemBareJID :: binary() | string()) -> any().
190 query_behaviour(HostType, UserJID, BUserJID, BRemJID, BRemBareJID)
191 when BRemJID == BRemBareJID ->
192 10 Params = #{user_jid => BUserJID, remote_jid => BRemBareJID},
193 10 mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE,
194 get_behaviour_bare_query, Params);
195 query_behaviour(HostType, UserJID, BUserJID, BRemJID, BRemBareJID) ->
196 314 Params = #{user_jid => BUserJID, start_remote_jid => BRemJID,
197 end_remote_jid => BRemBareJID},
198 314 mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE,
199 get_behaviour_full_query, Params).
200
201 %% ----------------------------------------------------------------------
202 %% Helpers
203
204 -spec encode_behaviour('always' | 'never' | 'roster') -> binary().
205 16 encode_behaviour(roster) -> <<"R">>;
206 14 encode_behaviour(always) -> <<"A">>;
207 14 encode_behaviour(never) -> <<"N">>.
208
209
210 -spec decode_behaviour(<<_:8>>) -> 'always' | 'never' | 'roster'.
211 21 decode_behaviour(<<"R">>) -> roster;
212 43 decode_behaviour(<<"A">>) -> always;
213 43 decode_behaviour(<<"N">>) -> never.
214
215 -spec decode_prefs_rows([[term()]], DefaultMode, AlwaysJIDs, NeverJIDs) ->
216 {DefaultMode, AlwaysJIDs, NeverJIDs} when
217 DefaultMode :: mod_mam:archive_behaviour(),
218 AlwaysJIDs :: [jid:literal_jid()],
219 NeverJIDs :: [jid:literal_jid()].
220 decode_prefs_rows([], DefaultMode, AlwaysJIDs, NeverJIDs) ->
221 23 {DefaultMode, AlwaysJIDs, NeverJIDs};
222
223 decode_prefs_rows([#{remote_jid := <<>>, behaviour := Behaviour} | Rows],
224 _DefaultMode, AlwaysJIDs, NeverJIDs) ->
225 23 decode_prefs_rows(Rows, decode_behaviour(Behaviour), AlwaysJIDs, NeverJIDs);
226 decode_prefs_rows([#{remote_jid := JID, behaviour := <<"A">>} | Rows],
227 DefaultMode, AlwaysJIDs, NeverJIDs) ->
228 15 decode_prefs_rows(Rows, DefaultMode, [JID | AlwaysJIDs], NeverJIDs);
229 decode_prefs_rows([#{remote_jid := JID, behaviour := <<"N">>} | Rows],
230 DefaultMode, AlwaysJIDs, NeverJIDs) ->
231 13 decode_prefs_rows(Rows, DefaultMode, AlwaysJIDs, [JID | NeverJIDs]).
232
233 %% ----------------------------------------------------------------------
234 %% Params getters
235
236 -spec pool_name(HostType :: host_type()) -> term().
237 pool_name(_HostType) ->
238 536 default.
Line Hits Source