/src/mod_roster_riak.erl

https://github.com/esl/MongooseIM · Erlang · 204 lines · 161 code · 26 blank · 17 comment · 0 complexity · f9cf20270c547e81676d232517e0f286 MD5 · raw file

  1. %%%----------------------------------------------------------------------
  2. %%% File : mod_roster_riak.erl
  3. %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
  4. %%% Purpose : mod_roster Riak backend with quasi-transaction support
  5. %%% (see comment before transaction/1)
  6. %%%
  7. %%% MongooseIM, Copyright (C) 2015 Erlang Solutions Ltd.
  8. %%%
  9. %%%----------------------------------------------------------------------
  10. -module(mod_roster_riak).
  11. -include("mod_roster.hrl").
  12. -include("jlib.hrl").
  13. -include("mongoose.hrl").
  14. -behaviour(mod_roster_backend).
  15. %% API
  16. -export([init/2,
  17. transaction/2,
  18. read_roster_version/3,
  19. write_roster_version/5,
  20. get_roster/3,
  21. get_roster_entry/6,
  22. get_subscription_lists/3,
  23. roster_subscribe_t/2,
  24. update_roster_t/2,
  25. del_roster_t/4,
  26. remove_user_t/3]).
  27. -define(ROSTER_BUCKET(HostType, LServer),
  28. {get_opt(HostType, [riak, bucket_type]), LServer}).
  29. -define(VER_BUCKET(HostType, LServer),
  30. {get_opt(HostType, [riak, version_bucket_type]), LServer}).
  31. get_opt(HostType, Opt) ->
  32. gen_mod:get_module_opt(HostType, mod_roster, Opt).
  33. %% --------------------- mod_roster backend API -------------------------------
  34. -spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
  35. init(_HostType, _Opts) ->
  36. ok. % Common Riak pool is used
  37. %% WARNING: Riak does not support *real* transactions, so we are in fact applying
  38. %% all accumulated changes with no rollback support so it is possible to end up with
  39. %% inconsistent state e.g. if Riak connectivity goes down in the middle of application.
  40. -spec transaction(mongooseim:host_type(), fun(() -> any())) ->
  41. {aborted, any()} | {atomic, any()}.
  42. transaction(HostType, F) ->
  43. put(riak_roster_t, []),
  44. put(riak_version_t, []),
  45. try F() of
  46. Result ->
  47. %% Applying
  48. apply_t_roster(HostType),
  49. apply_t_version(HostType),
  50. put(riak_roster_t, []),
  51. {atomic, Result}
  52. catch
  53. _:Reason -> {aborted, Reason}
  54. after
  55. lists:foreach(fun(Key) -> erase({riak_roster_t, Key}) end, erase(riak_roster_t)),
  56. erase(riak_version_t)
  57. end.
  58. -spec read_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
  59. binary() | error.
  60. read_roster_version(HostType, LUser, LServer) ->
  61. case mongoose_riak:get(?VER_BUCKET(HostType, LServer), LUser) of
  62. {ok, VerObj} -> riakc_obj:get_value(VerObj);
  63. _ -> error
  64. end.
  65. -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver(),
  66. mod_roster:transaction_state(), mod_roster:version()) -> ok.
  67. write_roster_version(HostType, LUser, LServer, no_transaction, Ver) ->
  68. VerObj = case mongoose_riak:get(?VER_BUCKET(HostType, LServer), LUser) of
  69. {ok, VerObj1} -> riakc_obj:update_value(VerObj1, Ver);
  70. _ -> riakc_obj:new(?VER_BUCKET(HostType, LServer), LUser, Ver)
  71. end,
  72. mongoose_riak:put(VerObj);
  73. write_roster_version(_HostType, LUser, LServer, in_transaction, Ver) ->
  74. Versions1 = get(riak_version_t),
  75. put(riak_version_t, lists:keystore({LUser, LServer}, 1, Versions1, {{LUser, LServer}, Ver})).
  76. -spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [mod_roster:roster()].
  77. get_roster(HostType, LUser, LServer) ->
  78. RosterMap = get_rostermap(HostType, LUser, LServer),
  79. riakc_map:fold(fun({_, register}, ItemReg, Acc) ->
  80. [unpack_item(ItemReg) | Acc]
  81. end,
  82. [], RosterMap).
  83. -spec get_roster_entry(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:contact(),
  84. mod_roster:transaction_state(), mod_roster:entry_format()) ->
  85. mod_roster:roster() | does_not_exist.
  86. get_roster_entry(HostType, LUser, LServer, LJID, TransactionState, _Format) ->
  87. RosterMap = case TransactionState of
  88. in_transaction ->
  89. get_t_roster(HostType, LUser, LServer);
  90. no_transaction ->
  91. get_rostermap(HostType, LUser, LServer)
  92. end,
  93. find_in_rostermap(LJID, RosterMap).
  94. -spec get_subscription_lists(mongoose_acc:t(), jid:luser(), jid:lserver()) -> [mod_roster:roster()].
  95. get_subscription_lists(Acc, LUser, LServer) ->
  96. HostType = mongoose_acc:host_type(Acc),
  97. get_roster(HostType, LUser, LServer).
  98. roster_subscribe_t(HostType, Item = #roster{us = {LUser, LServer}, jid = LJID}) ->
  99. set_t_roster(HostType, LUser, LServer, LJID, Item).
  100. -spec update_roster_t(mongooseim:host_type(), mod_roster:roster()) -> ok.
  101. update_roster_t(HostType, Item = #roster{us = {LUser, LServer}, jid = LJID}) ->
  102. set_t_roster(HostType, LUser, LServer, LJID, Item).
  103. -spec del_roster_t(mongooseim:host_type(), jid:luser(), jid:lserver(), mod_roster:contact()) -> ok.
  104. del_roster_t(HostType, LUser, LServer, LJID) ->
  105. del_t_roster(HostType, LUser, LServer, LJID).
  106. remove_user_t(HostType, LUser, LServer) ->
  107. mongoose_riak:delete(?VER_BUCKET(HostType, LServer), LUser),
  108. mongoose_riak:delete(?ROSTER_BUCKET(HostType, LServer), LUser),
  109. ok.
  110. %% --------------------- Helpers --------------------------------
  111. find_in_rostermap(LJID, RosterMap) ->
  112. case riakc_map:find({jid:to_binary(LJID), register}, RosterMap) of
  113. {ok, ItemReg} -> unpack_item(ItemReg);
  114. error -> does_not_exist
  115. end.
  116. %% this is a transaction-less equivalent of get_t_roster
  117. get_rostermap(HostType, LUser, LServer) ->
  118. case mongoose_riak:fetch_type(?ROSTER_BUCKET(HostType, LServer), LUser) of
  119. {ok, RMap} ->
  120. RMap;
  121. _ ->
  122. riakc_map:new()
  123. end.
  124. -spec unpack_item(ItemReg :: binary()) -> mod_roster:roster().
  125. unpack_item(ItemReg) ->
  126. binary_to_term(ItemReg).
  127. -spec get_t_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
  128. riakc_map:crdt_map().
  129. get_t_roster(HostType, LUser, LServer) ->
  130. case get({riak_roster_t, {LUser, LServer}}) of
  131. undefined ->
  132. case mongoose_riak:fetch_type(?ROSTER_BUCKET(HostType, LServer), LUser) of
  133. {ok, RosterMap} -> put({riak_roster_t, {LUser, LServer}}, RosterMap);
  134. _ -> put({riak_roster_t, {LUser, LServer}}, riakc_map:new())
  135. end,
  136. put(riak_roster_t, [{LUser, LServer} | get(riak_roster_t)]),
  137. get({riak_roster_t, {LUser, LServer}});
  138. RosterMap ->
  139. RosterMap
  140. end.
  141. -spec set_t_roster(HostType :: mongooseim:host_type(),
  142. LUser :: jid:luser(),
  143. LServer :: jid:lserver(),
  144. LJID :: jid:simple_jid(),
  145. Item :: mod_roster:roster()) -> any().
  146. set_t_roster(HostType, LUser, LServer, LJID, Item) ->
  147. RosterMap1 = get_t_roster(HostType, LUser, LServer),
  148. put({riak_roster_t, {LUser, LServer}},
  149. riakc_map:update({jid:to_binary(LJID), register},
  150. fun(R) -> riakc_register:set(term_to_binary(Item), R) end, RosterMap1)).
  151. -spec del_t_roster(HostType :: mongooseim:host_type(),
  152. LUser :: jid:luser(),
  153. LServer :: jid:lserver(),
  154. LJID :: jid:simple_jid()) -> any().
  155. del_t_roster(HostType, LUser, LServer, LJID) ->
  156. RosterMap1 = get_t_roster(HostType, LUser, LServer),
  157. RosterMap = case catch riakc_map:erase({jid:to_binary(LJID), register}, RosterMap1) of
  158. context_required -> RosterMap1;
  159. RosterMap2 -> RosterMap2
  160. end,
  161. put({riak_roster_t, {LUser, LServer}}, RosterMap).
  162. -spec apply_t_roster(mongooseim:host_type()) -> ok.
  163. apply_t_roster(HostType) ->
  164. lists:foreach(
  165. fun({LUser, LServer} = LUS) ->
  166. RosterMap = erase({riak_roster_t, LUS}),
  167. case riakc_map:to_op(RosterMap) of
  168. undefined -> ok;
  169. ToOp -> catch mongoose_riak:update_type(
  170. ?ROSTER_BUCKET(HostType, LServer), LUser, ToOp)
  171. end
  172. end, get(riak_roster_t)).
  173. -spec apply_t_version(mongooseim:host_type()) -> ok.
  174. apply_t_version(HostType) ->
  175. lists:foreach(
  176. fun({{LUser, LServer}, NewVer}) ->
  177. catch write_roster_version(HostType, LUser, LServer, no_transaction, NewVer)
  178. end, get(riak_version_t)).