aboutsummaryrefslogtreecommitdiff
path: root/src/mod_pubsub/pubsub_odbc.patch
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_pubsub/pubsub_odbc.patch')
-rw-r--r--src/mod_pubsub/pubsub_odbc.patch761
1 files changed, 761 insertions, 0 deletions
diff --git a/src/mod_pubsub/pubsub_odbc.patch b/src/mod_pubsub/pubsub_odbc.patch
new file mode 100644
index 000000000..a23147bc5
--- /dev/null
+++ b/src/mod_pubsub/pubsub_odbc.patch
@@ -0,0 +1,761 @@
+--- mod_pubsub.erl 2009-10-20 16:33:47.000000000 +0200
++++ mod_pubsub_odbc.erl 2009-10-20 16:33:26.000000000 +0200
+@@ -42,7 +42,7 @@
+ %%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
+ %%% XEP-0060 section 12.18.
+
+--module(mod_pubsub).
++-module(mod_pubsub_odbc).
+ -author('christophe.romain@process-one.net').
+ -version('1.13-0').
+
+@@ -54,9 +54,9 @@
+ -include("jlib.hrl").
+ -include("pubsub.hrl").
+
+--define(STDTREE, "tree").
+--define(STDNODE, "flat").
+--define(PEPNODE, "pep").
++-define(STDTREE, "tree_odbc").
++-define(STDNODE, "flat_odbc").
++-define(PEPNODE, "pep_odbc").
+
+ %% exports for hooks
+ -export([presence_probe/3,
+@@ -102,7 +102,7 @@
+ string_to_affiliation/1,
+ extended_error/2,
+ extended_error/3,
+- rename_default_nodeplugin/0
++ escape/1
+ ]).
+
+ %% API and gen_server callbacks
+@@ -121,7 +121,7 @@
+ -export([send_loop/1
+ ]).
+
+--define(PROCNAME, ejabberd_mod_pubsub).
++-define(PROCNAME, ejabberd_mod_pubsub_odbc).
+ -define(PLUGIN_PREFIX, "node_").
+ -define(TREE_PREFIX, "nodetree_").
+
+@@ -215,8 +215,6 @@
+ ok
+ end,
+ ejabberd_router:register_route(Host),
+- update_node_database(Host, ServerHost),
+- update_state_database(Host, ServerHost),
+ init_nodes(Host, ServerHost, NodeTree, Plugins),
+ State = #state{host = Host,
+ server_host = ServerHost,
+@@ -269,207 +267,14 @@
+
+ init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
+ %% TODO, this call should be done plugin side
+- case lists:member("hometree", Plugins) of
++ case lists:member("hometree_odbc", Plugins) of
+ true ->
+- create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree"),
+- create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree");
++ create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree_odbc"),
++ create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree_odbc");
+ false ->
+ ok
+ end.
+
+-update_node_database(Host, ServerHost) ->
+- mnesia:del_table_index(pubsub_node, type),
+- mnesia:del_table_index(pubsub_node, parentid),
+- case catch mnesia:table_info(pubsub_node, attributes) of
+- [host_node, host_parent, info] ->
+- ?INFO_MSG("upgrade node pubsub tables",[]),
+- F = fun() ->
+- {Result, LastIdx} = lists:foldl(
+- fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) ->
+- ItemsList =
+- lists:foldl(
+- fun({item, IID, Publisher, Payload}, Acc) ->
+- C = {unknown, Publisher},
+- M = {now(), Publisher},
+- mnesia:write(
+- #pubsub_item{itemid = {IID, NodeIdx},
+- creation = C,
+- modification = M,
+- payload = Payload}),
+- [{Publisher, IID} | Acc]
+- end, [], Items),
+- Owners =
+- dict:fold(
+- fun(JID, {entity, Aff, Sub}, Acc) ->
+- UsrItems =
+- lists:foldl(
+- fun({P, I}, IAcc) ->
+- case P of
+- JID -> [I | IAcc];
+- _ -> IAcc
+- end
+- end, [], ItemsList),
+- mnesia:write({pubsub_state,
+- {JID, NodeIdx},
+- UsrItems,
+- Aff,
+- Sub}),
+- case Aff of
+- owner -> [JID | Acc];
+- _ -> Acc
+- end
+- end, [], Entities),
+- mnesia:delete({pubsub_node, NodeId}),
+- {[#pubsub_node{nodeid = NodeId,
+- id = NodeIdx,
+- parents = [element(2, ParentId)],
+- owners = Owners,
+- options = Options} |
+- RecList], NodeIdx + 1}
+- end, {[], 1},
+- mnesia:match_object(
+- {pubsub_node, {Host, '_'}, '_', '_'})),
+- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []}),
+- Result
+- end,
+- {atomic, NewRecords} = mnesia:transaction(F),
+- {atomic, ok} = mnesia:delete_table(pubsub_node),
+- {atomic, ok} = mnesia:create_table(pubsub_node,
+- [{disc_copies, [node()]},
+- {attributes, record_info(fields, pubsub_node)}]),
+- FNew = fun() -> lists:foreach(fun(Record) ->
+- mnesia:write(Record)
+- end, NewRecords)
+- end,
+- case mnesia:transaction(FNew) of
+- {atomic, Result} ->
+- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
+- {aborted, Reason} ->
+- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
+- end;
+- [nodeid, parentid, type, owners, options] ->
+- F = fun({pubsub_node, NodeId, {_, Parent}, Type, Owners, Options}) ->
+- #pubsub_node{
+- nodeid = NodeId,
+- id = 0,
+- parents = [Parent],
+- type = Type,
+- owners = Owners,
+- options = Options}
+- end,
+- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
+- FNew = fun() ->
+- LastIdx = lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
+- mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
+- lists:foreach(fun(#pubsub_state{stateid = StateId} = State) ->
+- {JID, _} = StateId,
+- mnesia:delete({pubsub_state, StateId}),
+- mnesia:write(State#pubsub_state{stateid = {JID, NodeIdx}})
+- end, mnesia:match_object(#pubsub_state{stateid = {'_', NodeId}, _ = '_'})),
+- lists:foreach(fun(#pubsub_item{itemid = ItemId} = Item) ->
+- {IID, _} = ItemId,
+- {M1, M2} = Item#pubsub_item.modification,
+- {C1, C2} = Item#pubsub_item.creation,
+- mnesia:delete({pubsub_item, ItemId}),
+- mnesia:write(Item#pubsub_item{itemid = {IID, NodeIdx},
+- modification = {M2, M1},
+- creation = {C2, C1}})
+- end, mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'})),
+- NodeIdx + 1
+- end, 1, mnesia:match_object(
+- {pubsub_node, {Host, '_'}, '_', '_', '_', '_', '_'})
+- ++ mnesia:match_object(
+- {pubsub_node, {{'_', ServerHost, '_'}, '_'}, '_', '_', '_', '_', '_'})),
+- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []})
+- end,
+- case mnesia:transaction(FNew) of
+- {atomic, Result} ->
+- rename_default_nodeplugin(),
+- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
+- {aborted, Reason} ->
+- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
+- end;
+- [nodeid, id, parent, type, owners, options] ->
+- F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) ->
+- #pubsub_node{
+- nodeid = NodeId,
+- id = Id,
+- parents = [Parent],
+- type = Type,
+- owners = Owners,
+- options = Options}
+- end,
+- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
+- rename_default_nodeplugin();
+- _ ->
+- ok
+- end,
+- mnesia:transaction(fun() ->
+- case catch mnesia:first(pubsub_node) of
+- {_, L} when is_list(L) ->
+- lists:foreach(
+- fun({H, N}) when is_list(N) ->
+- [Node] = mnesia:read({pubsub_node, {H, N}}),
+- Type = Node#pubsub_node.type,
+- BN = element(2, node_call(Type, path_to_node, [N])),
+- BP = case [element(2, node_call(Type, path_to_node, [P])) || P <- Node#pubsub_node.parents] of
+- [<<>>] -> [];
+- Parents -> Parents
+- end,
+- mnesia:write(Node#pubsub_node{nodeid={H, BN}, parents=BP}),
+- mnesia:delete({pubsub_node, {H, N}});
+- (_) ->
+- ok
+- end, mnesia:all_keys(pubsub_node));
+- _ ->
+- ok
+- end
+- end).
+-
+-rename_default_nodeplugin() ->
+- lists:foreach(fun(Node) ->
+- mnesia:dirty_write(Node#pubsub_node{type = "hometree"})
+- end, mnesia:dirty_match_object(#pubsub_node{type = "default", _ = '_'})).
+-
+-update_state_database(_Host, _ServerHost) ->
+- case catch mnesia:table_info(pubsub_state, attributes) of
+- [stateid, items, affiliation, subscription] ->
+- ?INFO_MSG("upgrade state pubsub tables", []),
+- F = fun ({pubsub_state, {JID, NodeID}, Items, Aff, Sub}, Acc) ->
+- Subs = case Sub of
+- none ->
+- [];
+- _ ->
+- {result, SubID} = pubsub_subscription:subscribe_node(JID, NodeID, []),
+- [{Sub, SubID}]
+- end,
+- NewState = #pubsub_state{stateid = {JID, NodeID},
+- items = Items,
+- affiliation = Aff,
+- subscriptions = Subs},
+- [NewState | Acc]
+- end,
+- {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3,
+- [F, [], pubsub_state]),
+- {atomic, ok} = mnesia:delete_table(pubsub_state),
+- {atomic, ok} = mnesia:create_table(pubsub_state,
+- [{disc_copies, [node()]},
+- {attributes, record_info(fields, pubsub_state)}]),
+- FNew = fun () ->
+- lists:foreach(fun mnesia:write/1, NewRecs)
+- end,
+- case mnesia:transaction(FNew) of
+- {atomic, Result} ->
+- ?INFO_MSG("Pubsub state tables updated correctly: ~p",
+- [Result]);
+- {aborted, Reason} ->
+- ?ERROR_MSG("Problem updating Pubsub state tables:~n~p",
+- [Reason])
+- end;
+- _ ->
+- ok
+- end.
+-
+ send_queue(State, Msg) ->
+ Pid = State#state.send_loop,
+ case is_process_alive(Pid) of
+@@ -492,17 +297,15 @@
+ %% for each node From is subscribed to
+ %% and if the node is so configured, send the last published item to From
+ lists:foreach(fun(PType) ->
+- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]),
++ Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of
++ {result, S} -> S;
++ _ -> []
++ end,
+ lists:foreach(
+ fun({Node, subscribed, _, SubJID}) ->
+ if (SubJID == LJID) or (SubJID == BJID) ->
+- #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId, options = Options} = Node,
+- case get_option(Options, send_last_published_item) of
+- on_sub_and_presence ->
+- send_items(H, N, NodeId, Type, LJID, last);
+- _ ->
+- ok
+- end;
++ #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node,
++ send_items(H, N, NodeId, Type, LJID, last);
+ true ->
+ % resource not concerned about that subscription
+ ok
+@@ -825,10 +628,10 @@
+ {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Subscriber]),
+ lists:foreach(fun
+ ({Node, subscribed, _, JID}) ->
+- #pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId} = Node,
++ #pubsub_node{options = Options, type = Type, id = NodeId} = Node,
+ case get_option(Options, access_model) of
+ presence ->
+- case lists:member(BJID, Owners) of
++ case lists:member(BJID, node_owners(Host, Type, NodeId)) of
+ true ->
+ node_action(Host, Type, unsubscribe_node, [NodeId, Subscriber, JID, all]);
+ false ->
+@@ -943,7 +746,8 @@
+ sub_el = SubEl} = IQ ->
+ {xmlelement, _, QAttrs, _} = SubEl,
+ Node = xml:get_attr_s("node", QAttrs),
+- Res = case iq_disco_items(Host, Node, From) of
++ Rsm = jlib:rsm_decode(IQ),
++ Res = case iq_disco_items(Host, Node, From, Rsm) of
+ {result, IQRes} ->
+ jlib:iq_to_xml(
+ IQ#iq{type = result,
+@@ -1048,7 +852,7 @@
+ [] ->
+ ["leaf"]; %% No sub-nodes: it's a leaf node
+ _ ->
+- case node_call(Type, get_items, [NodeId, From]) of
++ case node_call(Type, get_items, [NodeId, From, none]) of
+ {result, []} -> ["collection"];
+ {result, _} -> ["leaf", "collection"];
+ _ -> []
+@@ -1064,8 +868,9 @@
+ [];
+ true ->
+ [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
+- lists:map(fun(T) ->
+- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
++ lists:map(fun
++ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
++ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
+ end, features(Type))]
+ end,
+ %% TODO: add meta-data info (spec section 5.4)
+@@ -1093,21 +898,22 @@
+ {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
+ {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
+ {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
+- lists:map(fun(Feature) ->
+- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
++ lists:map(fun
++ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
++ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
+ end, features(Host, Node))};
+ _ ->
+ node_disco_info(Host, Node, From)
+ end.
+
+-iq_disco_items(Host, [], From) ->
++iq_disco_items(Host, [], From, _RSM) ->
+ {result, lists:map(
+ fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
+ {result, Path} = node_call(Type, node_to_path, [SubNode]),
+ [Name|_] = lists:reverse(Path),
+ {xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
+ end, tree_action(Host, get_subnodes, [Host, <<>>, From]))};
+-iq_disco_items(Host, Item, From) ->
++iq_disco_items(Host, Item, From, RSM) ->
+ case string:tokens(Item, "!") of
+ [_SNode, _ItemID] ->
+ {result, []};
+@@ -1115,10 +921,10 @@
+ Node = string_to_node(SNode),
+ Action =
+ fun(#pubsub_node{type = Type, id = NodeId}) ->
+- % TODO call get_items/6 instead for access control (EJAB-1033)
+- NodeItems = case node_call(Type, get_items, [NodeId, From]) of
++ %% TODO call get_items/6 instead for access control (EJAB-1033)
++ {NodeItems, RsmOut} = case node_call(Type, get_items, [NodeId, From, RSM]) of
+ {result, I} -> I;
+- _ -> []
++ _ -> {[], none}
+ end,
+ Nodes = lists:map(
+ fun(#pubsub_node{nodeid = {_, SubNode}}) ->
+@@ -1129,7 +935,7 @@
+ {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
+ {xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
+ end, NodeItems),
+- {result, Nodes ++ Items}
++ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+ {result, {_, Result}} -> {result, Result};
+@@ -1258,7 +1064,8 @@
+ (_, Acc) ->
+ Acc
+ end, [], xml:remove_cdata(Els)),
+- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
++ RSM = jlib:rsm_decode(SubEl),
++ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
+ {get, "subscriptions"} ->
+ get_subscriptions(Host, Node, From, Plugins);
+ {get, "affiliations"} ->
+@@ -1281,7 +1088,9 @@
+
+ iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
+ {xmlelement, _, _, SubEls} = SubEl,
+- Action = xml:remove_cdata(SubEls),
++ Action = lists:filter(fun({xmlelement, "set", _, _}) -> false;
++ (_) -> true
++ end, xml:remove_cdata(SubEls)),
+ case Action of
+ [{xmlelement, Name, Attrs, Els}] ->
+ Node = string_to_node(xml:get_attr_s("node", Attrs)),
+@@ -1404,7 +1213,8 @@
+ _ -> []
+ end
+ end,
+- case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end,
++ case transaction(Host,
++ fun () -> {result, lists:flatmap(Tr, Plugins)} end,
+ sync_dirty) of
+ {result, Res} -> Res;
+ Err -> Err
+@@ -1444,7 +1254,7 @@
+
+ %%% authorization handling
+
+-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) ->
++send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) ->
+ Lang = "en", %% TODO fix
+ Stanza = {xmlelement, "message",
+ [],
+@@ -1473,7 +1283,7 @@
+ [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
+ lists:foreach(fun(Owner) ->
+ ejabberd_router ! {route, service_jid(Host), jlib:make_jid(Owner), Stanza}
+- end, Owners).
++ end, node_owners(Host, Type, NodeId)).
+
+ find_authorization_response(Packet) ->
+ {xmlelement, _Name, _Attrs, Els} = Packet,
+@@ -1537,8 +1347,8 @@
+ "true" -> true;
+ _ -> false
+ end,
+- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
+- IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners),
++ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
++ IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)),
+ {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
+ if
+ not IsApprover ->
+@@ -1729,7 +1539,7 @@
+ Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
+ [{xmlelement, "create", nodeAttr(Node),
+ []}]}],
+- case transaction(CreateNode, transaction) of
++ case transaction(Host, CreateNode, transaction) of
+ {result, {Result, broadcast}} ->
+ %%Lang = "en", %% TODO: fix
+ %%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+@@ -1837,7 +1647,7 @@
+ %%<li>The node does not exist.</li>
+ %%</ul>
+ subscribe_node(Host, Node, From, JID, Configuration) ->
+- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
++ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
+@@ -1845,7 +1655,7 @@
+ error -> {"", "", ""};
+ J -> jlib:jid_tolower(J)
+ end,
+- Action = fun(#pubsub_node{options = Options, owners = [Owner|_], type = Type, id = NodeId}) ->
++ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
+ Features = features(Type),
+ SubscribeFeature = lists:member("subscribe", Features),
+ OptionsFeature = lists:member("subscription-options", Features),
+@@ -1864,9 +1674,13 @@
+ {"", "", ""} ->
+ {false, false};
+ _ ->
+- {OU, OS, _} = Owner,
+- get_roster_info(OU, OS,
+- Subscriber, AllowedGroups)
++ case node_owners_call(Type, NodeId) of
++ [{OU, OS, _}|_] ->
++ get_roster_info(OU, OS,
++ Subscriber, AllowedGroups);
++ _ ->
++ {false, false}
++ end
+ end
+ end,
+ if
+@@ -2197,7 +2011,7 @@
+ %% <p>The permission are not checked in this function.</p>
+ %% @todo We probably need to check that the user doing the query has the right
+ %% to read the items.
+-get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) ->
++get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) ->
+ MaxItems =
+ if
+ SMaxItems == "" -> get_max_items_node(Host);
+@@ -2236,11 +2050,11 @@
+ node_call(Type, get_items,
+ [NodeId, From,
+ AccessModel, PresenceSubscription, RosterGroup,
+- SubId])
++ SubId, RSM])
+ end
+ end,
+ case transaction(Host, Node, Action, sync_dirty) of
+- {result, {_, Items}} ->
++ {result, {_, {Items, RSMOut}}} ->
+ SendItems = case ItemIDs of
+ [] ->
+ Items;
+@@ -2253,7 +2067,8 @@
+ %% number of items sent to MaxItems:
+ {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
+ [{xmlelement, "items", nodeAttr(Node),
+- itemsEls(lists:sublist(SendItems, MaxItems))}]}]};
++ itemsEls(lists:sublist(SendItems, MaxItems))}
++ | jlib:rsm_encode(RSMOut)]}]};
+ Error ->
+ Error
+ end
+@@ -2285,16 +2100,27 @@
+ %% @doc <p>Resend the items of a node to the user.</p>
+ %% @todo use cache-last-item feature
+ send_items(Host, Node, NodeId, Type, LJID, last) ->
+- case get_cached_item(Host, NodeId) of
++ Stanza = case get_cached_item(Host, NodeId) of
+ undefined ->
+- send_items(Host, Node, NodeId, Type, LJID, 1);
++ % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc
++ case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of
++ {result, [LastItem]} ->
++ {ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
++ event_stanza_with_delay(
++ [{xmlelement, "items", nodeAttr(Node),
++ itemsEls([LastItem])}], ModifNow, ModifLjid);
++ _ ->
++ event_stanza(
++ [{xmlelement, "items", nodeAttr(Node),
++ itemsEls([])}])
++ end;
+ LastItem ->
+ {ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
+- Stanza = event_stanza_with_delay(
++ event_stanza_with_delay(
+ [{xmlelement, "items", nodeAttr(Node),
+- itemsEls([LastItem])}], ModifNow, ModifLjid),
+- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}
+- end;
++ itemsEls([LastItem])}], ModifNow, ModifLjid)
++ end,
++ ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza};
+ send_items(Host, Node, NodeId, Type, LJID, Number) ->
+ ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
+ {result, []} ->
+@@ -2420,29 +2246,12 @@
+ error ->
+ {error, ?ERR_BAD_REQUEST};
+ _ ->
+- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
+- case lists:member(Owner, Owners) of
++ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
++ case lists:member(Owner, node_owners_call(Type, NodeId)) of
+ true ->
+ lists:foreach(
+ fun({JID, Affiliation}) ->
+- node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
+- case Affiliation of
+- owner ->
+- NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+- NewOwners = [NewOwner|Owners],
+- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
+- none ->
+- OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+- case lists:member(OldOwner, Owners) of
+- true ->
+- NewOwners = Owners--[OldOwner],
+- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
+- _ ->
+- ok
+- end;
+- _ ->
+- ok
+- end
++ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
+ end, Entities),
+ {result, []};
+ _ ->
+@@ -2495,11 +2304,11 @@
+ end.
+
+ read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
+- case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of
++ case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of
+ {error, notfound} ->
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {result, #pubsub_subscription{options = Options}} ->
+- {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options),
++ {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options),
+ OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
+ {"subid", SubID}|nodeAttr(Node)],
+ [XdataEl]},
+@@ -2525,7 +2334,7 @@
+ end.
+
+ set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
+- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
++ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
+ {result, GoodSubOpts} -> GoodSubOpts;
+ _ -> invalid
+ end,
+@@ -2554,7 +2363,7 @@
+ write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
+ {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
+ write_sub(Subscriber, NodeID, SubID, Options) ->
+- case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of
++ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of
+ {error, notfound} ->
+ {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+ {result, _} ->
+@@ -2722,8 +2531,8 @@
+ {"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
+ ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza}
+ end,
+- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
+- case lists:member(Owner, Owners) of
++ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
++ case lists:member(Owner, node_owners_call(Type, NodeId)) of
+ true ->
+ Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
+
+@@ -3007,7 +2816,7 @@
+ {Depth, [{N, get_node_subs(N)} || N <- Nodes]}
+ end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))}
+ end,
+- case transaction(Action, sync_dirty) of
++ case transaction(Host, Action, sync_dirty) of
+ {result, CollSubs} -> CollSubs;
+ _ -> []
+ end.
+@@ -3021,9 +2830,9 @@
+
+ get_options_for_subs(NodeID, Subs) ->
+ lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
+- case pubsub_subscription:read_subscription(JID, NodeID, SubID) of
++ case pubsub_subscription_odbc:get_subscription(JID, NodeID, SubID) of
+ {error, notfound} -> [{JID, SubID, []} | Acc];
+- #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc];
++ {result, #pubsub_subscription{options = Options}} -> [{JID, SubID, Options} | Acc];
+ _ -> Acc
+ end;
+ (_, Acc) ->
+@@ -3221,6 +3030,30 @@
+ Result
+ end.
+
++%% @spec (Host, Type, NodeId) -> [ljid()]
++%% NodeId = pubsubNodeId()
++%% @doc <p>Return list of node owners.</p>
++node_owners(Host, Type, NodeId) ->
++ case node_action(Host, Type, get_node_affiliations, [NodeId]) of
++ {result, Affiliations} ->
++ lists:foldl(
++ fun({LJID, owner}, Acc) -> [LJID|Acc];
++ (_, Acc) -> Acc
++ end, [], Affiliations);
++ _ ->
++ []
++ end.
++node_owners_call(Type, NodeId) ->
++ case node_call(Type, get_node_affiliations, [NodeId]) of
++ {result, Affiliations} ->
++ lists:foldl(
++ fun({LJID, owner}, Acc) -> [LJID|Acc];
++ (_, Acc) -> Acc
++ end, [], Affiliations);
++ _ ->
++ []
++ end.
++
+ %% @spec (Host, Options) -> MaxItems
+ %% Host = host()
+ %% Options = [Option]
+@@ -3607,7 +3440,13 @@
+ tree_action(Host, Function, Args) ->
+ ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
+ Fun = fun() -> tree_call(Host, Function, Args) end,
+- catch mnesia:sync_dirty(Fun).
++ case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of
++ {atomic, Result} ->
++ Result;
++ {aborted, Reason} ->
++ ?ERROR_MSG("transaction return internal error: ~p~n",[{aborted, Reason}]),
++ {error, ?ERR_INTERNAL_SERVER_ERROR}
++ end.
+
+ %% @doc <p>node plugin call.</p>
+ node_call(Type, Function, Args) ->
+@@ -3627,13 +3466,13 @@
+
+ node_action(Host, Type, Function, Args) ->
+ ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
+- transaction(fun() ->
++ transaction(Host, fun() ->
+ node_call(Type, Function, Args)
+ end, sync_dirty).
+
+ %% @doc <p>plugin transaction handling.</p>
+ transaction(Host, Node, Action, Trans) ->
+- transaction(fun() ->
++ transaction(Host, fun() ->
+ case tree_call(Host, get_node, [Host, Node]) of
+ N when is_record(N, pubsub_node) ->
+ case Action(N) of
+@@ -3646,8 +3485,14 @@
+ end
+ end, Trans).
+
+-transaction(Fun, Trans) ->
+- case catch mnesia:Trans(Fun) of
++transaction(Host, Fun, Trans) ->
++ transaction_retry(Host, Fun, Trans, 2).
++transaction_retry(Host, Fun, Trans, Count) ->
++ SqlFun = case Trans of
++ transaction -> sql_transaction;
++ _ -> sql_bloc
++ end,
++ case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of
+ {result, Result} -> {result, Result};
+ {error, Error} -> {error, Error};
+ {atomic, {result, Result}} -> {result, Result};
+@@ -3655,6 +3500,15 @@
+ {aborted, Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
++ {'EXIT', {timeout, _} = Reason} ->
++ case Count of
++ 0 ->
++ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
++ {error, ?ERR_INTERNAL_SERVER_ERROR};
++ N ->
++ erlang:yield(),
++ transaction_retry(Host, Fun, Trans, N-1)
++ end;
+ {'EXIT', Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
+ {error, ?ERR_INTERNAL_SERVER_ERROR};
+@@ -3663,6 +3517,17 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
+
++odbc_conn({_U, Host, _R})->
++ Host;
++odbc_conn(Host) ->
++ Host--"pubsub.". %% TODO, improve that for custom host
++
++%% escape value for database storage
++escape({_U, _H, _R}=JID)->
++ ejabberd_odbc:escape(jlib:jid_to_string(JID));
++escape(Value)->
++ ejabberd_odbc:escape(Value).
++
+ %%%% helpers
+
+ %% Add pubsub-specific error element