./ct_report/coverage/mod_mam_meta.COVER.html

1 %%==============================================================================
2 %% Copyright 2016 Erlang Solutions Ltd.
3 %%
4 %% Licensed under the Apache License, Version 2.0 (the "License");
5 %% you may not use this file except in compliance with the License.
6 %% You may obtain a copy of the License at
7 %%
8 %% http://www.apache.org/licenses/LICENSE-2.0
9 %%
10 %% Unless required by applicable law or agreed to in writing, software
11 %% distributed under the License is distributed on an "AS IS" BASIS,
12 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 %% See the License for the specific language governing permissions and
14 %% limitations under the License.
15 %%==============================================================================
16
17 -module(mod_mam_meta).
18 -behaviour(gen_mod).
19 -behaviour(mongoose_module_metrics).
20
21 -type module_opts() :: gen_mod:module_opts().
22 -type module_map() :: gen_mod_deps:module_map().
23
24 -export([start/2, stop/1, config_spec/0, supported_features/0, deps/2, config_metrics/1]).
25
26 -export([remove_unused_backend_opts/1]).
27
28 -include("mongoose_config_spec.hrl").
29
30 %%--------------------------------------------------------------------
31 %% API
32 %%--------------------------------------------------------------------
33
34 -spec supported_features() -> [atom()].
35 supported_features() ->
36
:-(
[dynamic_domains].
37
38 -spec start(Host :: jid:server(), Opts :: list()) -> any().
39 start(_Host, _Opts) ->
40 36 ok.
41
42 -spec stop(Host :: jid:server()) -> any().
43 stop(_Host) ->
44 36 ok.
45
46 -spec config_spec() -> mongoose_config_spec:config_section().
47 config_spec() ->
48 164 Items = maps:merge(common_config_items(), root_config_items()),
49 164 #section{
50 items = Items#{<<"pm">> => pm_config_spec(),
51 <<"muc">> => muc_config_spec(),
52 <<"riak">> => riak_config_spec()},
53 defaults = #{<<"backend">> => rdbms,
54 <<"no_stanzaid_element">> => false,
55 <<"is_archivable_message">> => mod_mam_utils,
56 <<"send_message">> => mod_mam_utils,
57 <<"archive_chat_markers">> => false,
58 <<"message_retraction">> => true,
59 <<"full_text_search">> => true,
60 <<"cache_users">> => true,
61 <<"default_result_limit">> => 50,
62 <<"max_result_limit">> => 50},
63 format_items = map,
64 process = fun ?MODULE:remove_unused_backend_opts/1
65 }.
66
67
:-(
remove_unused_backend_opts(Opts = #{backend := riak}) -> Opts;
68
:-(
remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts).
69
70 pm_config_spec() ->
71 164 #section{items = maps:merge(common_config_items(), pm_config_items()),
72 format_items = map,
73 defaults = #{<<"archive_groupchats">> => false,
74 <<"same_mam_id_for_peers">> => false}}.
75
76 muc_config_spec() ->
77 164 #section{items = maps:merge(common_config_items(), muc_config_items()),
78 format_items = map,
79 defaults = #{<<"host">> => mod_muc:default_host()}}.
80
81 root_config_items() ->
82 164 Cache = mongoose_user_cache:config_spec(),
83 164 AsyncWriter = async_config_spec(),
84 164 #{<<"cache">> => Cache#section{include = always},
85 <<"async_writer">> => AsyncWriter#section{include = always}}.
86
87 common_config_items() ->
88 492 #{%% General options
89 <<"backend">> => #option{type = atom,
90 validate = {enum, [rdbms, riak, cassandra, elasticsearch]}},
91 <<"no_stanzaid_element">> => #option{type = boolean},
92 <<"is_archivable_message">> => #option{type = atom,
93 validate = module},
94 <<"send_message">> => #option{type = atom,
95 validate = module},
96 <<"archive_chat_markers">> => #option{type = boolean},
97 <<"message_retraction">> => #option{type = boolean},
98
99 %% Common backend options
100 <<"user_prefs_store">> => #option{type = atom,
101 validate = {enum, [rdbms, cassandra, mnesia]}},
102 <<"full_text_search">> => #option{type = boolean},
103
104 %% RDBMS-specific options
105 <<"cache_users">> => #option{type = boolean},
106
107 %% Low-level options
108 <<"default_result_limit">> => #option{type = integer,
109 validate = non_negative},
110 <<"max_result_limit">> => #option{type = integer,
111 validate = non_negative},
112 <<"db_jid_format">> => #option{type = atom,
113 validate = module},
114 <<"db_message_format">> => #option{type = atom,
115 validate = module},
116 <<"extra_fin_element">> => #option{type = atom,
117 validate = module},
118 <<"extra_lookup_params">> => #option{type = atom,
119 validate = module}
120 }.
121
122 pm_config_items() ->
123 164 #{<<"async_writer">> => async_config_spec(),
124 <<"archive_groupchats">> => #option{type = boolean},
125 <<"same_mam_id_for_peers">> => #option{type = boolean}}.
126
127 muc_config_items() ->
128 164 #{<<"async_writer">> => async_config_spec(),
129 <<"host">> => #option{type = string,
130 validate = subdomain_template,
131 process = fun mongoose_subdomain_utils:make_subdomain_pattern/1}}.
132
133 async_config_spec() ->
134 492 #section{
135 items = #{<<"enabled">> => #option{type = boolean},
136 <<"flush_interval">> => #option{type = integer, validate = non_negative},
137 <<"batch_size">> => #option{type = integer, validate = non_negative},
138 <<"pool_size">> => #option{type = integer, validate = non_negative}},
139 defaults = #{<<"enabled">> => true,
140 <<"flush_interval">> => 2000,
141 <<"batch_size">> => 30,
142 <<"pool_size">> => 4 * erlang:system_info(schedulers_online)},
143 format_items = map
144 }.
145
146 riak_config_spec() ->
147 164 #section{
148 items = #{<<"search_index">> => #option{type = binary,
149 validate = non_empty},
150 <<"bucket_type">> => #option{type = binary,
151 validate = non_empty}},
152 defaults = #{<<"search_index">> => <<"mam">>,
153 <<"bucket_type">> => <<"mam_yz">>},
154 format_items = map,
155 include = always
156 }.
157
158 -spec deps(mongooseim:host_type(), module_opts()) -> gen_mod_deps:deps().
159 deps(_HostType, Opts) ->
160 236 DepsWithPm = handle_nested_opts(pm, Opts, #{}),
161 236 DepsWithPmAndMuc = handle_nested_opts(muc, Opts, DepsWithPm),
162
163 236 [{DepMod, DepOpts, hard} || {DepMod, DepOpts} <- maps:to_list(DepsWithPmAndMuc)].
164
165 %%--------------------------------------------------------------------
166 %% Helpers
167 %%--------------------------------------------------------------------
168
169 -type mam_type() :: pm | muc.
170 -type mam_backend() :: rdbms | riak | cassandra | elasticsearch.
171
172 -spec handle_nested_opts(mam_type(), module_opts(), module_map()) -> module_map().
173 handle_nested_opts(Key, RootOpts, Deps) ->
174 472 case maps:find(Key, RootOpts) of
175 118 error -> Deps;
176 {ok, Opts} ->
177 354 FullOpts = maps:merge(maps:without([pm, muc], RootOpts), Opts),
178 354 parse_opts(Key, FullOpts, Deps)
179 end.
180
181 -spec parse_opts(mam_type(), module_opts(), module_map()) -> module_map().
182 parse_opts(Type, Opts, Deps) ->
183 %% Opts are merged root options with options inside pm or muc section
184 354 CoreMod = mam_type_to_core_mod(Type),
185 354 CoreModOpts = maps:with(valid_core_mod_opts(CoreMod), Opts),
186 354 WithCoreDeps = add_dep(CoreMod, CoreModOpts, Deps),
187 354 {Backend, BackendOpts} = maps:take(backend, Opts),
188 354 WithPrefs = add_prefs_store_module(Backend, Type, Opts, WithCoreDeps),
189 354 parse_backend_opts(Backend, Type, BackendOpts, WithPrefs).
190
191 -spec mam_type_to_core_mod(mam_type()) -> module().
192 187 mam_type_to_core_mod(pm) -> mod_mam;
193 167 mam_type_to_core_mod(muc) -> mod_mam_muc.
194
195 %% Get a list of options to pass into the two modules.
196 %% They don't have to be defined in pm or muc sections, the root section is enough.
197 -spec valid_core_mod_opts(module()) -> [atom()].
198 valid_core_mod_opts(mod_mam) ->
199 187 [archive_groupchats, same_mam_id_for_peers] ++ common_opts();
200 valid_core_mod_opts(mod_mam_muc) ->
201 167 [host] ++ common_opts().
202
203 common_opts() ->
204 354 [async_writer,
205 is_archivable_message,
206 send_message,
207 archive_chat_markers,
208 extra_fin_element,
209 extra_lookup_params,
210 full_text_search,
211 message_retraction,
212 default_result_limit,
213 max_result_limit,
214 no_stanzaid_element].
215
216 -spec add_prefs_store_module(mam_backend(), mam_type(), module_opts(), module_map()) -> module_map().
217 add_prefs_store_module(Backend, Type, #{user_prefs_store := Store}, Deps) ->
218 125 PrefsModule = prefs_module(Backend, Store),
219 125 add_dep(PrefsModule, #{Type => true}, Deps);
220 add_prefs_store_module(_Backend, _Type, _Opts, Deps) ->
221 229 Deps.
222
223 -spec parse_backend_opts(mam_backend(), mam_type(), module_opts(), module_map()) -> module_map().
224 parse_backend_opts(cassandra, Type, Opts, Deps) ->
225
:-(
Opts1 = maps:with([db_message_format], Opts),
226
:-(
add_dep(cassandra_arch_module(Type), maps:merge(arch_defaults(), Opts1), Deps);
227 parse_backend_opts(riak, Type, Opts, Deps) ->
228 354 Opts1 = maps:with([db_message_format, riak], Opts),
229 354 add_dep(mod_mam_riak_timed_arch_yz, maps:merge(arch_defaults(), Opts1#{Type => true}), Deps);
230 parse_backend_opts(rdbms, Type, Opts, Deps) ->
231
:-(
lists:foldl(fun(OptionGroup, DepsIn) -> add_rdbms_deps(OptionGroup, Type, Opts, DepsIn) end,
232 Deps, [basic, user_cache, async_writer]);
233 parse_backend_opts(elasticsearch, Type, _Opts, Deps0) ->
234
:-(
add_dep(elasticsearch_arch_module(Type), Deps0).
235
236 -spec add_rdbms_deps(basic | user_cache | async_writer,
237 mam_type(), module_opts(), module_map()) -> module_map().
238 add_rdbms_deps(basic, Type, Opts, Deps) ->
239
:-(
Opts1 = maps:with([db_message_format, db_jid_format], Opts),
240
:-(
Deps1 = add_dep(rdbms_arch_module(Type), maps:merge(rdbms_arch_defaults(Type), Opts1), Deps),
241
:-(
add_dep(mod_mam_rdbms_user, user_db_types(Type), Deps1);
242 add_rdbms_deps(user_cache, Type, #{cache_users := true, cache := CacheOpts}, Deps) ->
243
:-(
Deps1 = case gen_mod:get_opt(module, CacheOpts, internal) of
244
:-(
internal -> Deps;
245
:-(
mod_cache_users -> add_dep(mod_cache_users, Deps)
246 end,
247
:-(
add_dep(mod_mam_cache_user, CacheOpts#{Type => true}, Deps1);
248 add_rdbms_deps(async_writer, Type, #{async_writer := AsyncOpts = #{enabled := true}}, Deps) ->
249
:-(
Deps1 = add_dep(rdbms_arch_module(Type), #{no_writer => true}, Deps),
250
:-(
add_dep(rdbms_async_arch_module(Type), AsyncOpts, Deps1);
251 add_rdbms_deps(_, _Type, _Opts, Deps) ->
252
:-(
Deps.
253
254 % muc backend requires both pm and muc user DB to populate sender_id column
255 -spec user_db_types(mam_type()) -> module_opts().
256
:-(
user_db_types(pm) -> #{pm => true};
257
:-(
user_db_types(muc) -> #{pm => true, muc => true}.
258
259
:-(
cassandra_arch_module(pm) -> mod_mam_cassandra_arch;
260
:-(
cassandra_arch_module(muc) -> mod_mam_muc_cassandra_arch.
261
262 354 arch_defaults() -> #{db_message_format => mam_message_xml}.
263
264 rdbms_arch_defaults(pm) ->
265
:-(
maps:merge(rdbms_arch_defaults(), #{db_jid_format => mam_jid_mini});
266 rdbms_arch_defaults(muc) ->
267
:-(
maps:merge(rdbms_arch_defaults(), #{db_jid_format => mam_jid_rfc}).
268
269 rdbms_arch_defaults() ->
270
:-(
#{db_message_format => mam_message_compressed_eterm,
271 no_writer => false}.
272
273
:-(
rdbms_arch_module(pm) -> mod_mam_rdbms_arch;
274
:-(
rdbms_arch_module(muc) -> mod_mam_muc_rdbms_arch.
275
276
:-(
rdbms_async_arch_module(pm) -> mod_mam_rdbms_arch_async;
277
:-(
rdbms_async_arch_module(muc) -> mod_mam_muc_rdbms_arch_async.
278
279
:-(
elasticsearch_arch_module(pm) -> mod_mam_elasticsearch_arch;
280
:-(
elasticsearch_arch_module(muc) -> mod_mam_muc_elasticsearch_arch.
281
282
:-(
prefs_module(rdbms, rdbms) -> mod_mam_rdbms_prefs;
283
:-(
prefs_module(cassandra, cassandra) -> mod_mam_cassandra_prefs;
284 125 prefs_module(_, mnesia) -> mod_mam_mnesia_prefs;
285 prefs_module(Backend, PrefsStore) ->
286
:-(
error(#{what => invalid_mam_user_prefs_store,
287 backend => Backend,
288 user_prefs_store => PrefsStore}).
289
290 -spec add_dep(module(), module_map()) -> module_map().
291 add_dep(Dep, Deps) ->
292
:-(
add_dep(Dep, #{}, Deps).
293
294 -spec add_dep(module(), module_opts(), module_map()) -> module_map().
295 add_dep(Dep, Opts, Deps) ->
296 833 PrevOpts = maps:get(Dep, Deps, #{}),
297 833 NewOpts = maps:merge(PrevOpts, Opts),
298 833 maps:put(Dep, NewOpts, Deps).
299
300 config_metrics(Host) ->
301
:-(
mongoose_module_metrics:opts_for_module(Host, ?MODULE, [backend]).
Line Hits Source