aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Romain <christophe.romain@process-one.net>2009-06-15 13:45:40 +0000
committerChristophe Romain <christophe.romain@process-one.net>2009-06-15 13:45:40 +0000
commit50b73664e2d8751d5128dd2dea7210ed35b0cd74 (patch)
tree3f12a8a4de8deb07b2ead2a89a5e51d02eb7032d
parentPrevent process crash if the IP and port of a connection is unknown. (diff)
experimental patch including XEP-248 (thanks to Brian Cully)
SVN Revision: 2157
-rw-r--r--src/mod_pubsub/Makefile.win3210
-rw-r--r--src/mod_pubsub/gen_pubsub_nodetree.erl2
-rw-r--r--src/mod_pubsub/mod_pubsub.erl210
-rw-r--r--src/mod_pubsub/node.template17
-rw-r--r--src/mod_pubsub/node_buddy.erl3
-rw-r--r--src/mod_pubsub/node_club.erl3
-rw-r--r--src/mod_pubsub/node_dag.erl154
-rw-r--r--src/mod_pubsub/node_dispatch.erl5
-rw-r--r--src/mod_pubsub/node_flat.erl3
-rw-r--r--src/mod_pubsub/node_hometree.erl20
-rw-r--r--src/mod_pubsub/node_mb.erl3
-rw-r--r--src/mod_pubsub/node_pep.erl7
-rw-r--r--src/mod_pubsub/node_private.erl3
-rw-r--r--src/mod_pubsub/node_public.erl3
-rw-r--r--src/mod_pubsub/nodetree_dag.erl246
-rw-r--r--src/mod_pubsub/nodetree_tree.erl38
-rw-r--r--src/mod_pubsub/nodetree_virtual.erl18
-rw-r--r--src/mod_pubsub/pubsub.hrl2
18 files changed, 642 insertions, 105 deletions
diff --git a/src/mod_pubsub/Makefile.win32 b/src/mod_pubsub/Makefile.win32
index f981329c9..d3cdcc499 100644
--- a/src/mod_pubsub/Makefile.win32
+++ b/src/mod_pubsub/Makefile.win32
@@ -4,7 +4,7 @@ include ..\Makefile.inc
EFLAGS = -I .. -pz ..
OUTDIR = ..
-BEAMS = ..\gen_pubsub_node.beam ..\gen_pubsub_nodetree.beam ..\mod_pubsub.beam ..\nodetree_default.beam ..\nodetree_virtual.beam ..\node_buddy.beam ..\node_club.beam ..\node_default.beam ..\node_dispatch.beam ..\node_pep.beam ..\node_private.beam ..\node_public.beam
+BEAMS = ..\gen_pubsub_node.beam ..\gen_pubsub_nodetree.beam ..\mod_pubsub.beam ..\nodetree_tree.beam ..\nodetree_virtual.beam ..\node_buddy.beam ..\node_club.beam ..\node_hometree.beam ..\node_dispatch.beam ..\node_pep.beam ..\node_private.beam ..\node_public.beam
ALL : $(BEAMS)
@@ -20,8 +20,8 @@ $(OUTDIR)\gen_pubsub_nodetree.beam : gen_pubsub_nodetree.erl
$(OUTDIR)\mod_pubsub.beam : mod_pubsub.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_pubsub.erl
-$(OUTDIR)\nodetree_default.beam : nodetree_default.erl
- erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_default.erl
+$(OUTDIR)\nodetree_tree.beam : nodetree_tree.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_tree.erl
$(OUTDIR)\nodetree_virtual.beam : nodetree_virtual.erl
erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_virtual.erl
@@ -32,8 +32,8 @@ $(OUTDIR)\node_buddy.beam : node_buddy.erl
$(OUTDIR)\node_club.beam : node_club.erl
erlc -W $(EFLAGS) -o $(OUTDIR) node_club.erl
-$(OUTDIR)\node_default.beam : node_default.erl
- erlc -W $(EFLAGS) -o $(OUTDIR) node_default.erl
+$(OUTDIR)\node_hometree.beam : node_hometree.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) node_hometree.erl
$(OUTDIR)\node_dispatch.beam : node_dispatch.erl
erlc -W $(EFLAGS) -o $(OUTDIR) node_dispatch.erl
diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl
index f012b3d0b..f55694500 100644
--- a/src/mod_pubsub/gen_pubsub_nodetree.erl
+++ b/src/mod_pubsub/gen_pubsub_nodetree.erl
@@ -47,6 +47,8 @@ behaviour_info(callbacks) ->
{get_node, 1},
{get_nodes, 2},
{get_nodes, 1},
+ {get_parentnodes, 3},
+ {get_parentnodes_tree, 3},
{get_subnodes, 3},
{get_subnodes_tree, 3},
{create_node, 5},
diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl
index af3f76f62..b36d1a3ff 100644
--- a/src/mod_pubsub/mod_pubsub.erl
+++ b/src/mod_pubsub/mod_pubsub.erl
@@ -80,7 +80,7 @@
get_items/2,
get_item/3,
get_cached_item/2,
- broadcast_stanza/7,
+ broadcast_stanza/8,
get_configure/5,
set_configure/5,
tree_action/3,
@@ -304,7 +304,7 @@ update_node_database(Host, ServerHost) ->
mnesia:delete({pubsub_node, NodeId}),
{[#pubsub_node{nodeid = NodeId,
id = NodeIdx,
- parent = element(2, ParentId),
+ parents = [element(2, ParentId)],
owners = Owners,
options = Options} |
RecList], NodeIdx + 1}
@@ -334,12 +334,12 @@ update_node_database(Host, ServerHost) ->
#pubsub_node{
nodeid = NodeId,
id = 0,
- parent = Parent,
+ parents = [Parent],
type = Type,
owners = Owners,
options = Options}
end,
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parent, type, owners, options]),
+ mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
FNew = fun() ->
lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
@@ -371,6 +371,17 @@ update_node_database(Host, ServerHost) ->
?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]);
_ ->
ok
end.
@@ -1443,6 +1454,12 @@ replace_subscription_helper(_, OldSub, Acc) ->
-define(STRINGXFIELD(Label, Var, Val),
?XFIELD("text-single", Label, Var, Val)).
+-define(STRINGMXFIELD(Label, Var, Vals),
+ {xmlelement, "field", [{"type", "text-multi"},
+ {"label", translate:translate(Lang, Label)},
+ {"var", Var}],
+ [{xmlelement, "value", [], [{xmlcdata, V}]} || V <- Vals]}).
+
-define(XFIELDOPT(Type, Label, Var, Val, Opts),
{xmlelement, "field", [{"type", Type},
{"label", translate:translate(Lang, Label)},
@@ -1601,10 +1618,14 @@ delete_node(_Host, [], _Owner) ->
{error, ?ERR_NOT_ALLOWED};
delete_node(Host, Node, Owner) ->
Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ SubsByDepth = get_collection_subscriptions(Host, Node),
case node_call(Type, get_affiliation, [NodeId, Owner]) of
{result, owner} ->
Removed = tree_call(Host, delete_node, [Host, Node]),
- node_call(Type, delete_node, [Removed]);
+ case node_call(Type, delete_node, [Removed]) of
+ {result, Res} -> {result, {SubsByDepth, Res}};
+ Error -> Error
+ end;
_ ->
%% Entity is not an owner
{error, ?ERR_FORBIDDEN}
@@ -1612,27 +1633,26 @@ delete_node(Host, Node, Owner) ->
end,
Reply = [],
case transaction(Host, Node, Action, transaction) of
- {result, {_, {Result, broadcast, Removed}}} ->
- lists:foreach(fun({RNode, RSubscriptions}) ->
+ {result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} ->
+ lists:foreach(fun({RNode, _RSubscriptions}) ->
{RH, RN} = RNode#pubsub_node.nodeid,
NodeId = RNode#pubsub_node.id,
Type = RNode#pubsub_node.type,
Options = RNode#pubsub_node.options,
- broadcast_removed_node(RH, RN, NodeId, Type, Options, RSubscriptions),
- unset_cached_item(RH, NodeId)
+ broadcast_removed_node(RH, RN, NodeId, Type, Options, SubsByDepth)
end, Removed),
case Result of
default -> {result, Reply};
_ -> {result, Result}
end;
- {result, {_, {Result, _Removed}}} ->
+ {result, {_, {_, {Result, _Removed}}}} ->
case Result of
default -> {result, Reply};
_ -> {result, Result}
end;
- {result, {_, default}} ->
+ {result, {_, {_, default}}} ->
{result, Reply};
- {result, {_, Result}} ->
+ {result, {_, {_, Result}}} ->
{result, Result};
Error ->
Error
@@ -2605,19 +2625,28 @@ service_jid(Host) ->
%% @spec (LJID, PresenceDelivery) -> boolean()
%% LJID = jid()
+%% NotifyType = items | nodes
+%% Depth = integer()
%% NodeOptions = [{atom(), term()}]
%% SubOptions = [{atom(), term()}]
%% @doc <p>Check if a notification must be delivered or not based on
%% node and subscription options.</p>
-is_to_deliver(LJID, NodeOptions, SubOptions) ->
- sub_to_deliver(LJID, SubOptions) andalso node_to_deliver(LJID, NodeOptions).
-
-sub_to_deliver(_LJID, SubOptions) ->
- lists:all(fun sub_option_can_deliver/1, SubOptions).
-
-sub_option_can_deliver({deliver, false}) -> false;
-sub_option_can_deliver({expire, When}) -> now() < When;
-sub_option_can_deliver(_) -> true.
+is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) ->
+ sub_to_deliver(LJID, NotifyType, Depth, SubOptions)
+ andalso node_to_deliver(LJID, NodeOptions).
+
+sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) ->
+ lists:all(fun (Option) ->
+ sub_option_can_deliver(NotifyType, Depth, Option)
+ end, SubOptions).
+
+sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false;
+sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
+sub_option_can_deliver(_, _, {subscription_depth, all}) -> true;
+sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth < D;
+sub_option_can_deliver(_, _, {deliver, false}) -> false;
+sub_option_can_deliver(_, _, {expire, When}) -> now() < When;
+sub_option_can_deliver(_, _, _) -> true.
node_to_deliver(LJID, NodeOptions) ->
PresenceDelivery = get_option(NodeOptions, presence_based_delivery),
@@ -2652,11 +2681,8 @@ event_stanza(Els) ->
broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _From, Payload) ->
%broadcast(Host, Node, NodeId, NodeOptions, none, true, "items", ItemEls)
- case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
- {result, []} ->
- {result, false};
- {result, Subs} ->
- SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
+ case get_collection_subscriptions(Host, Node) of
+ SubsByDepth when is_list(SubsByDepth) ->
Content = case get_option(NodeOptions, deliver_payloads) of
true -> Payload;
false -> []
@@ -2665,7 +2691,7 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _
[{xmlelement, "items", nodeAttr(Node),
[{xmlelement, "item", itemAttr(ItemId), Content}]}]),
broadcast_stanza(Host, Node, NodeId, Type,
- NodeOptions, SubOptions, Stanza),
+ NodeOptions, SubsByDepth, items, Stanza),
case Removed of
[] ->
ok;
@@ -2676,8 +2702,8 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _
[{xmlelement, "items", nodeAttr(Node),
[{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]),
broadcast_stanza(Host, Node, NodeId, Type,
- NodeOptions, SubOptions,
- RetractStanza);
+ NodeOptions, SubsByDepth,
+ items, RetractStanza);
_ ->
ok
end
@@ -2695,16 +2721,13 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, ForceNotify, "retract", RetractEls)
case (get_option(NodeOptions, notify_retract) or ForceNotify) of
true ->
- case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
- {result, []} ->
- {result, false};
- {result, Subs} ->
- SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
+ case get_collection_subscriptions(Host, Node) of
+ SubsByDepth when is_list(SubsByDepth) ->
Stanza = event_stanza(
[{xmlelement, "items", nodeAttr(Node),
[{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]),
broadcast_stanza(Host, Node, NodeId, Type,
- NodeOptions, SubOptions, Stanza),
+ NodeOptions, SubsByDepth, items, Stanza),
{result, true};
_ ->
{result, false}
@@ -2717,16 +2740,13 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, false, "purge", [])
case get_option(NodeOptions, notify_retract) of
true ->
- case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
- {result, []} ->
- {result, false};
- {result, Subs} ->
- SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
+ case get_collection_subscriptions(Host, Node) of
+ SubsByDepth when is_list(SubsByDepth) ->
Stanza = event_stanza(
[{xmlelement, "purge", nodeAttr(Node),
[]}]),
broadcast_stanza(Host, Node, NodeId, Type,
- NodeOptions, SubOptions, Stanza),
+ NodeOptions, SubsByDepth, nodes, Stanza),
{result, true};
_ ->
{result, false}
@@ -2735,20 +2755,19 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
{result, false}
end.
-broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, Subs) ->
+broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
%broadcast(Host, Node, NodeId, NodeOptions, notify_delete, false, "delete", [])
case get_option(NodeOptions, notify_delete) of
true ->
- case Subs of
+ case SubsByDepth of
[] ->
{result, false};
_ ->
- SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
Stanza = event_stanza(
[{xmlelement, "delete", nodeAttr(Node),
[]}]),
broadcast_stanza(Host, Node, NodeId, Type,
- NodeOptions, SubOptions, Stanza),
+ NodeOptions, SubsByDepth, nodes, Stanza),
{result, true}
end;
_ ->
@@ -2759,11 +2778,8 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
%broadcast(Host, Node, NodeId, NodeOptions, notify_config, false, "items", ConfigEls)
case get_option(NodeOptions, notify_config) of
true ->
- case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
- {result, []} ->
- {result, false};
- {result, Subs} ->
- SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
+ case get_collection_subscriptions(Host, Node) of
+ SubsByDepth when is_list(SubsByDepth) ->
Content = case get_option(NodeOptions, deliver_payloads) of
true ->
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
@@ -2775,7 +2791,7 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
[{xmlelement, "items", nodeAttr(Node),
[{xmlelement, "item", itemAttr("configuration"), Content}]}]),
broadcast_stanza(Host, Node, NodeId, Type,
- NodeOptions, SubOptions, Stanza),
+ NodeOptions, SubsByDepth, nodes, Stanza),
{result, true};
_ ->
{result, false}
@@ -2784,6 +2800,28 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
{result, false}
end.
+get_collection_subscriptions(Host, Node) ->
+ case mnesia:transaction(fun tree_call/3,
+ [Host, get_parentnodes_tree,
+ [Host, Node, service_jid(Host)]]) of
+ {atomic, NodesByDepth} when is_list(NodesByDepth) ->
+ lists:map(fun ({Depth, Nodes}) ->
+ {Depth, [{N, get_node_subs(N)} || N <- Nodes]}
+ end, NodesByDepth);
+ Other ->
+ Other
+ end.
+
+get_node_subs(#pubsub_node{type = Type,
+ nodeid = {Host, Node},
+ id = NodeID}) ->
+ case node_action(Host, Type, get_node_subscriptions, [NodeID]) of
+ {result, Subs} ->
+ get_options_for_subs(Host, Node, NodeID, Subs);
+ Other ->
+ Other
+ end.
+
% TODO: merge broadcast code that way
%broadcast(Host, Node, NodeId, Type, NodeOptions, Feature, Force, ElName, SubEls) ->
% case (get_option(NodeOptions, Feature) or Force) of
@@ -2802,15 +2840,14 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
% {result, false}
% end
-broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, Stanza) ->
+broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, Stanza) ->
%AccessModel = get_option(NodeOptions, access_model),
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
From = service_jid(Host),
%% Handles explicit subscriptions
- DeliverSubs = lists:filter(fun({LJID, _Node, SubOptions}) ->
- is_to_deliver(LJID, NodeOptions, SubOptions)
- end, Subs),
- lists:foreach(fun({LJID, _Node, _SubOptions}) ->
+ FilteredSubsByDepth = depths_to_deliver(NotifyType, SubsByDepth),
+ NodesByJID = collate_subs_by_jid(FilteredSubsByDepth),
+ lists:foreach(fun ({LJID, Nodes}) ->
LJIDs = case BroadcastAll of
true ->
{U, S, _} = LJID,
@@ -2818,10 +2855,11 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, Stanza) ->
false ->
[LJID]
end,
+ SHIMStanza = add_headers(Stanza, collection_shim(Node, Nodes)),
lists:foreach(fun(To) ->
- ejabberd_router ! {route, From, jlib:make_jid(To), Stanza}
+ ejabberd_router ! {route, From, jlib:make_jid(To), SHIMStanza}
end, LJIDs)
- end, DeliverSubs),
+ end, NodesByJID),
%% Handles implicit presence subscriptions
case Host of
{LUser, LServer, LResource} ->
@@ -2869,6 +2907,37 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, Stanza) ->
ok
end.
+depths_to_deliver(NotifyType, SubsByDepth) ->
+ NodesToDeliver =
+ fun (Depth, Node, Subs, Acc) ->
+ lists:foldl(fun ({LJID, _Node, SubOptions} = S, Acc2) ->
+ case is_to_deliver(LJID, NotifyType, Depth,
+ Node#pubsub_node.options,
+ SubOptions) of
+ true -> [S | Acc2];
+ false -> Acc2
+ end
+ end, Acc, Subs)
+ end,
+
+ DepthsToDeliver =
+ fun ({Depth, SubsByNode}, Acc) ->
+ lists:foldl(fun ({Node, Subs}, Acc2) ->
+ NodesToDeliver(Depth, Node, Subs, Acc2)
+ end, Acc, SubsByNode)
+ end,
+
+ lists:foldl(DepthsToDeliver, [], SubsByDepth).
+
+collate_subs_by_jid(SubsByDepth) ->
+ lists:foldl(fun ({JID, Node, _Options}, Acc) ->
+ OldNodes = case lists:keysearch(JID, 1, Acc) of
+ {value, {JID, Nodes}} -> Nodes;
+ false -> []
+ end,
+ lists:keystore(JID, 1, Acc, {JID, [Node | OldNodes]})
+ end, [], SubsByDepth).
+
%% If we don't know the resource, just pick first if any
%% If no resource available, check if caps anyway (remote online)
user_resources(User, Server) ->
@@ -3000,6 +3069,10 @@ max_items(Options) ->
?LISTMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
get_option(Options, Var), Opts)).
+-define(NLIST_CONFIG_FIELD(Label, Var),
+ ?STRINGMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
+ [node_to_string(N) || N <- get_option(Options, Var)])).
+
get_configure_xfields(_Type, Options, Lang, Groups) ->
[?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
@@ -3020,7 +3093,8 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
?ALIST_CONFIG_FIELD("When to send the last published item", send_last_published_item,
[never, on_sub, on_sub_and_presence]),
- ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery)
+ ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
+ ?NLIST_CONFIG_FIELD("The collections with which a node is affiliated", collection)
].
%%<p>There are several reasons why the node configuration request might fail:</p>
@@ -3052,8 +3126,10 @@ set_configure(Host, Node, From, Els, Lang) ->
end,
case set_xoption(XData, OldOpts) of
NewOpts when is_list(NewOpts) ->
- tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]),
- {result, ok};
+ case tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]) of
+ ok -> {result, ok};
+ Err -> Err
+ end;
Err ->
Err
end
@@ -3166,6 +3242,9 @@ set_xoption([{"pubsub#type", Value} | Opts], NewOpts) ->
?SET_STRING_XOPT(type, Value);
set_xoption([{"pubsub#body_xslt", Value} | Opts], NewOpts) ->
?SET_STRING_XOPT(body_xslt, Value);
+set_xoption([{"pubsub#collection", Value} | Opts], NewOpts) ->
+ NewValue = [string_to_node(V) || V <- Value],
+ ?SET_LIST_XOPT(collection, NewValue);
set_xoption([_ | Opts], NewOpts) ->
% skip unknown field
set_xoption(Opts, NewOpts).
@@ -3257,7 +3336,7 @@ features() ->
"member-affiliation", % RECOMMENDED
%TODO "meta-data", % RECOMMENDED
% see plugin "modify-affiliations", % OPTIONAL
- %TODO "multi-collection", % OPTIONAL
+ % see plugin "multi-collection", % OPTIONAL
% see plugin "multi-subscribe", % OPTIONAL
% see plugin "outcast-affiliation", % RECOMMENDED
% see plugin "persistent-items", % RECOMMENDED
@@ -3395,3 +3474,10 @@ itemsEls(Items) ->
lists:map(fun(#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
{xmlelement, "item", itemAttr(ItemId), Payload}
end, Items).
+
+add_headers({xmlelement, Name, Attrs, Els}, Headers) ->
+ {xmlelement, Name, Attrs, Els ++ Headers}.
+
+collection_shim(Node, Nodes) ->
+ [{xmlelement, "header", [{"name", "Collection"}],
+ [{xmlcdata, node_to_string(N)}]} || N <- Nodes -- [Node]].
diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template
index 336c547a9..534c18226 100644
--- a/src/mod_pubsub/node.template
+++ b/src/mod_pubsub/node.template
@@ -65,7 +65,8 @@
get_items/2,
get_item/7,
get_item/2,
- set_item/1
+ set_item/1,
+ get_item_name/3
]).
@@ -76,8 +77,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, __TO_BE_DEFINED__},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@@ -118,8 +118,8 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
-subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
- node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
+subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
@@ -158,7 +158,7 @@ get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscriptions) ->
- node_hometree:set_subscriptions(NodeId, Owner, Subscription).
+ node_hometree:set_subscriptions(NodeId, Owner, Subscriptions).
get_states(NodeId) ->
node_hometree:get_states(NodeId).
@@ -172,7 +172,7 @@ set_state(State) ->
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
-get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId)
+get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
@@ -183,3 +183,6 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, Su
set_item(Item) ->
node_hometree:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_hometree:get_item_name(Host, Node, Id).
diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl
index 0094674e7..2e8dd0249 100644
--- a/src/mod_pubsub/node_buddy.erl
+++ b/src/mod_pubsub/node_buddy.erl
@@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, buddy},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl
index 6bbece021..91ff520c6 100644
--- a/src/mod_pubsub/node_club.erl
+++ b/src/mod_pubsub/node_club.erl
@@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, club},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
diff --git a/src/mod_pubsub/node_dag.erl b/src/mod_pubsub/node_dag.erl
new file mode 100644
index 000000000..954a26a95
--- /dev/null
+++ b/src/mod_pubsub/node_dag.erl
@@ -0,0 +1,154 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% @author Brian Cully <bjc@kublai.com>
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(node_dag).
+-author('bjc@kublai.com').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%% API definition
+-export([init/3, terminate/2,
+ options/0, features/0,
+ create_node_permission/6,
+ create_node/2,
+ delete_node/1,
+ purge_node/2,
+ subscribe_node/8,
+ unsubscribe_node/4,
+ publish_item/6,
+ delete_item/4,
+ remove_extra_items/3,
+ get_entity_affiliations/2,
+ get_node_affiliations/1,
+ get_affiliation/2,
+ set_affiliation/3,
+ get_entity_subscriptions/2,
+ get_node_subscriptions/1,
+ get_subscriptions/2,
+ set_subscriptions/3,
+ get_states/1,
+ get_state/2,
+ set_state/1,
+ get_items/6,
+ get_items/2,
+ get_item/7,
+ get_item/2,
+ set_item/1,
+ get_item_name/3]).
+
+
+init(Host, ServerHost, Opts) ->
+ node_hometree:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_hometree:terminate(Host, ServerHost).
+
+options() ->
+ [{node_type, leaf} | node_hometree:options()].
+
+features() ->
+ ["multi-collection" | node_hometree:features()].
+
+create_node_permission(_Host, _ServerHost, _Node, _ParentNode,
+ _Owner, _Access) ->
+ {result, true}.
+
+create_node(NodeID, Owner) ->
+ node_hometree:create_node(NodeID, Owner).
+
+delete_node(Removed) ->
+ node_hometree:delete_node(Removed).
+
+subscribe_node(NodeID, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_hometree:subscribe_node(NodeID, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup,
+ Options).
+
+unsubscribe_node(NodeID, Sender, Subscriber, SubID) ->
+ node_hometree:unsubscribe_node(NodeID, Sender, Subscriber, SubID).
+
+publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) ->
+ node_hometree:publish_item(NodeID, Publisher, Model, MaxItems,
+ ItemID, Payload).
+
+remove_extra_items(NodeID, MaxItems, ItemIDs) ->
+ node_hometree:remove_extra_items(NodeID, MaxItems, ItemIDs).
+
+delete_item(NodeID, Publisher, PublishModel, ItemID) ->
+ node_hometree:delete_item(NodeID, Publisher, PublishModel, ItemID).
+
+purge_node(NodeID, Owner) ->
+ node_hometree:purge_node(NodeID, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_hometree:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(NodeID) ->
+ node_hometree:get_node_affiliations(NodeID).
+
+get_affiliation(NodeID, Owner) ->
+ node_hometree:get_affiliation(NodeID, Owner).
+
+set_affiliation(NodeID, Owner, Affiliation) ->
+ node_hometree:set_affiliation(NodeID, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_hometree:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(NodeID) ->
+ node_hometree:get_node_subscriptions(NodeID).
+
+get_subscriptions(NodeID, Owner) ->
+ node_hometree:get_subscriptions(NodeID, Owner).
+
+set_subscriptions(NodeID, Owner, Subscriptions) ->
+ node_hometree:set_subscriptions(NodeID, Owner, Subscriptions).
+
+get_states(NodeID) ->
+ node_hometree:get_states(NodeID).
+
+get_state(NodeID, JID) ->
+ node_hometree:get_state(NodeID, JID).
+
+set_state(State) ->
+ node_hometree:set_state(State).
+
+get_items(NodeID, From) ->
+ node_hometree:get_items(NodeID, From).
+
+get_items(NodeID, JID, AccessModel, PresenceSubscription,
+ RosterGroup, SubID) ->
+ node_hometree:get_items(NodeID, JID, AccessModel, PresenceSubscription,
+ RosterGroup, SubID).
+
+get_item(NodeID, ItemID) ->
+ node_hometree:get_item(NodeID, ItemID).
+
+get_item(NodeID, ItemID, JID, AccessModel, PresenceSubscription,
+ RosterGroup, SubID) ->
+ node_hometree:get_item(NodeID, ItemID, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubID).
+
+set_item(Item) ->
+ node_hometree:set_item(Item).
+
+get_item_name(Host, Node, ID) ->
+ node_hometree:get_item_name(Host, Node, ID).
diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl
index 5661b39e9..f67ab81fa 100644
--- a/src/mod_pubsub/node_dispatch.erl
+++ b/src/mod_pubsub/node_dispatch.erl
@@ -76,8 +76,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, dispatch},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@@ -129,7 +128,7 @@ publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree:publish_item(
SubNode#pubsub_node.id, Publisher, Model,
MaxItems, ItemId, Payload)
- end, nodetree_default:get_subnodes(NodeId, Publisher)).
+ end, nodetree_tree:get_subnodes(NodeId, Publisher, Publisher)).
remove_extra_items(_NodeId, _MaxItems, ItemIds) ->
{result, {ItemIds, []}}.
diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl
index c530cf934..a4fbb3b67 100644
--- a/src/mod_pubsub/node_flat.erl
+++ b/src/mod_pubsub/node_flat.erl
@@ -69,8 +69,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, flat},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl
index 77acfdd94..45c695ae2 100644
--- a/src/mod_pubsub/node_hometree.erl
+++ b/src/mod_pubsub/node_hometree.erl
@@ -132,8 +132,7 @@ terminate(_Host, _ServerHost) ->
%% {send_last_published_item, never},
%% {presence_based_delivery, false}]'''
options() ->
- [{node_type, default},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@@ -597,7 +596,7 @@ get_entity_affiliations(Host, Owner) ->
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
[{nodetree, N}] -> N;
- _ -> nodetree_default
+ _ -> nodetree_tree
end,
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
case NodeTree:get_node(N) of
@@ -654,7 +653,7 @@ get_entity_subscriptions(Host, Owner) ->
end,
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
[{nodetree, N}] -> N;
- _ -> nodetree_default
+ _ -> nodetree_tree
end,
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
case NodeTree:get_node(N) of
@@ -774,9 +773,10 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenState = get_state(NodeId, GenKey),
+ SubState = get_state(NodeId, SubKey),
Affiliation = GenState#pubsub_state.affiliation,
- Subscription = GenState#pubsub_state.subscriptions,
- Whitelisted = can_fetch_item(Affiliation, Subscription),
+ Subscriptions = SubState#pubsub_state.subscriptions,
+ Whitelisted = can_fetch_item(Affiliation, Subscriptions),
if
%%SubID == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
@@ -796,7 +796,7 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
(AccessModel == whitelist) and (not Whitelisted) ->
%% Node has whitelist access model and entity lacks required affiliation
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
- (AccessModel == authorize) -> % TODO: to be done
+ (AccessModel == authorize) and (not Whitelisted) ->
%% Node has authorize access model
{error, ?ERR_FORBIDDEN};
%%MustPay ->
@@ -887,8 +887,10 @@ can_fetch_item(owner, _) -> true;
can_fetch_item(member, _) -> true;
can_fetch_item(publisher, _) -> true;
can_fetch_item(outcast, _) -> false;
-can_fetch_item(none, subscribed) -> true;
-can_fetch_item(none, none) -> false;
+can_fetch_item(none, Subscriptions) ->
+ lists:any(fun ({subscribed, _SubID}) -> true;
+ (_) -> false
+ end, Subscriptions);
can_fetch_item(_Affiliation, _Subscription) -> false.
%% Returns the first item where Pred() is true in List
diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl
index af8e9222b..70ceabbb1 100644
--- a/src/mod_pubsub/node_mb.erl
+++ b/src/mod_pubsub/node_mb.erl
@@ -81,8 +81,7 @@ terminate(Host, ServerHost) ->
ok.
options() ->
- [{node_type, pep},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, false},
diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl
index 5ef3bff70..8e2e7e0a3 100644
--- a/src/mod_pubsub/node_pep.erl
+++ b/src/mod_pubsub/node_pep.erl
@@ -76,8 +76,7 @@ terminate(Host, ServerHost) ->
ok.
options() ->
- [{node_type, pep},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, false},
@@ -174,7 +173,7 @@ get_entity_affiliations(_Host, Owner) ->
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = case ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
[{nodetree, N}] -> N;
- _ -> nodetree_default
+ _ -> nodetree_tree
end,
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
case NodeTree:get_node(N) of
@@ -206,7 +205,7 @@ get_entity_subscriptions(_Host, Owner) ->
end,
NodeTree = case ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
[{nodetree, N}] -> N;
- _ -> nodetree_default
+ _ -> nodetree_tree
end,
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
case NodeTree:get_node(N) of
diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl
index d62365ba6..e9c925403 100644
--- a/src/mod_pubsub/node_private.erl
+++ b/src/mod_pubsub/node_private.erl
@@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, private},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl
index cc753c847..ae7beff84 100644
--- a/src/mod_pubsub/node_public.erl
+++ b/src/mod_pubsub/node_public.erl
@@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
- [{node_type, public},
- {deliver_payloads, true},
+ [{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
diff --git a/src/mod_pubsub/nodetree_dag.erl b/src/mod_pubsub/nodetree_dag.erl
new file mode 100644
index 000000000..1a5921fb8
--- /dev/null
+++ b/src/mod_pubsub/nodetree_dag.erl
@@ -0,0 +1,246 @@
+%%% ====================================================================
+%%% ``The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% @author Brian Cully <bjc@kublai.com>
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(nodetree_dag).
+-author('bjc@kublai.com').
+
+%% API
+-export([init/3,
+ terminate/2,
+ options/0,
+ set_node/1,
+ get_node/3,
+ get_node/2,
+ get_node/1,
+ get_nodes/2,
+ get_nodes/1,
+ get_parentnodes/3,
+ get_parentnodes_tree/3,
+ get_subnodes/3,
+ get_subnodes_tree/3,
+ create_node/5,
+ delete_node/2]).
+
+-include_lib("stdlib/include/qlc.hrl").
+
+-include("ejabberd.hrl").
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_nodetree).
+
+-define(DEFAULT_NODETYPE, leaf).
+-define(DEFAULT_PARENTS, []).
+-define(DEFAULT_CHILDREN, []).
+
+-compile(export_all).
+
+%%====================================================================
+%% API
+%%====================================================================
+init(Host, ServerHost, Opts) ->
+ nodetree_tree:init(Host, ServerHost, Opts),
+ mnesia:transaction(fun create_node/5,
+ [Host, [], "default", service_jid(ServerHost), []]).
+
+terminate(Host, ServerHost) ->
+ nodetree_tree:terminate(Host, ServerHost).
+
+create_node(Key, NodeID, Type, Owner, Options) ->
+ OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ case find_node(Key, NodeID) of
+ false ->
+ ID = pubsub_index:new(node),
+ N = #pubsub_node{nodeid = oid(Key, NodeID),
+ id = ID,
+ type = Type,
+ owners = [OwnerJID],
+ options = Options},
+ case set_node(N) of
+ ok -> {ok, ID};
+ Other -> Other
+ end;
+ _ ->
+ {error, ?ERR_CONFLICT}
+ end.
+
+set_node(#pubsub_node{nodeid = {Key, _},
+ owners = Owners,
+ options = Options} = Node) ->
+ Parents = find_opt(collection, ?DEFAULT_PARENTS, Options),
+ case validate_parentage(Key, Owners, Parents) of
+ true ->
+ %% Update parents whenever the config changes.
+ mnesia:write(Node#pubsub_node{parents = Parents});
+ Other ->
+ Other
+ end.
+
+delete_node(Key, NodeID) ->
+ case find_node(Key, NodeID) of
+ false ->
+ {error, ?ERR_ITEM_NOT_FOUND};
+ Node ->
+ %% Find all of N's children, update their configs to
+ %% remove N from the collection setting.
+ lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
+ NewOpts = remove_config_parent(NodeID, Opts),
+ Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts),
+ ok = mnesia:write(pubsub_node,
+ Child#pubsub_node{
+ parents = Parents,
+ options = NewOpts},
+ write)
+ end, get_subnodes(Key, NodeID)),
+
+ %% Remove and return the requested node.
+ mnesia:delete_object(pubsub_node, Node, write),
+ [Node]
+ end.
+
+options() ->
+ nodetree_tree:options().
+
+get_node(Host, NodeID, _From) ->
+ get_node(Host, NodeID).
+
+get_node(Host, NodeID) ->
+ case find_node(Host, NodeID) of
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ Node -> Node
+ end.
+
+get_node(NodeId) ->
+ nodetree_tree:get_node(NodeId).
+
+get_nodes(Key, From) ->
+ nodetree_tree:get_nodes(Key, From).
+
+get_nodes(Key) ->
+ nodetree_tree:get_nodes(Key).
+
+get_parentnodes(Host, NodeID, _From) ->
+ case find_node(Host, NodeID) of
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ #pubsub_node{parents = Parents} ->
+ Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N <- mnesia:table(pubsub_node),
+ Parent <- Parents,
+ Host == NHost,
+ Parent == NNode]),
+ qlc:e(Q)
+ end.
+
+get_parentnodes_tree(Host, NodeID, _From) ->
+ Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) ->
+ NID == NNodeID
+ end,
+ Tr = fun (#pubsub_node{parents = Parents}) -> Parents end,
+ traversal_helper(Pred, Tr, Host, [NodeID]).
+
+get_subnodes(Host, NodeID, _From) ->
+ get_subnodes(Host, NodeID).
+
+get_subnodes(Host, NodeID) ->
+ case find_node(Host, NodeID) of
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ _ ->
+ Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
+ parents = Parents} = Node <- mnesia:table(pubsub_node),
+ Host == NHost,
+ lists:member(NodeID, Parents)]),
+ qlc:e(Q)
+ end.
+
+get_subnodes_tree(Host, NodeID, _From) ->
+ Pred = fun (NID, #pubsub_node{parents = Parents}) ->
+ lists:member(NID, Parents)
+ end,
+ Tr = fun (#pubsub_node{nodeid = {_, N}}) -> N end,
+ traversal_helper(Pred, Tr, Host, [NodeID]).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+oid(Key, Name) -> {Key, Name}.
+
+%% Key = jlib:jid() | host()
+%% NodeID = string()
+find_node(Key, NodeID) ->
+ case mnesia:read(pubsub_node, oid(Key, NodeID), read) of
+ [] -> false;
+ [Node] -> Node
+ end.
+
+%% Key = jlib:jid() | host()
+%% Default = term()
+%% Options = [{Key = atom(), Value = term()}]
+find_opt(Key, Default, Options) ->
+ case lists:keysearch(Key, 1, Options) of
+ {value, {Key, Val}} -> Val;
+ _ -> Default
+ end.
+
+traversal_helper(Pred, Tr, Host, NodeIDs) ->
+ traversal_helper(Pred, Tr, 0, Host, NodeIDs, []).
+
+traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) ->
+ Acc;
+traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) ->
+ Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}} = Node <- mnesia:table(pubsub_node),
+ NodeID <- NodeIDs,
+ Host == NHost,
+ Pred(NodeID, Node)]),
+ Nodes = qlc:e(Q),
+ IDs = lists:flatmap(Tr, Nodes),
+ traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]).
+
+remove_config_parent(NodeID, Options) ->
+ remove_config_parent(NodeID, Options, []).
+
+remove_config_parent(_NodeID, [], Acc) ->
+ lists:reverse(Acc);
+remove_config_parent(NodeID, [{collection, Parents} | T], Acc) ->
+ remove_config_parent(NodeID, T,
+ [{collection, lists:delete(NodeID, Parents)} | Acc]);
+remove_config_parent(NodeID, [H | T], Acc) ->
+ remove_config_parent(NodeID, T, [H | Acc]).
+
+validate_parentage(_Key, _Owners, []) ->
+ true;
+validate_parentage(Key, Owners, [[] | T]) ->
+ validate_parentage(Key, Owners, T);
+validate_parentage(Key, Owners, [ParentID | T]) ->
+ case find_node(Key, ParentID) of
+ false -> {error, ?ERR_ITEM_NOT_FOUND};
+ #pubsub_node{owners = POwners, options = POptions} ->
+ NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
+ MutualOwners = [O || O <- Owners, PO <- POwners,
+ O == PO],
+ case {MutualOwners, NodeType} of
+ {[], _} -> {error, ?ERR_NOT_ALLOWED};
+ {_, collection} -> validate_parentage(Key, Owners, T)
+ end
+ end.
+
+%% @spec (Host) -> jid()
+%% Host = host()
+%% @doc <p>Generate pubsub service JID.</p>
+service_jid(Host) ->
+ case Host of
+ {U,S,_} -> {jid, U, S, "", U, S, ""};
+ _ -> {jid, "", Host, "", "", Host, ""}
+ end.
diff --git a/src/mod_pubsub/nodetree_tree.erl b/src/mod_pubsub/nodetree_tree.erl
index 8f49a06b0..767b0e9c8 100644
--- a/src/mod_pubsub/nodetree_tree.erl
+++ b/src/mod_pubsub/nodetree_tree.erl
@@ -36,6 +36,8 @@
-module(nodetree_tree).
-author('christophe.romain@process-one.net').
+-include_lib("stdlib/include/qlc.hrl").
+
-include("pubsub.hrl").
-include("jlib.hrl").
@@ -50,6 +52,8 @@
get_node/1,
get_nodes/2,
get_nodes/1,
+ get_parentnodes/3,
+ get_parentnodes_tree/3,
get_subnodes/3,
get_subnodes_tree/3,
create_node/5,
@@ -124,6 +128,32 @@ get_nodes(Host, _From) ->
get_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
+%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
+%% Host = mod_pubsub:host() | mod_pubsub:jid()
+%% Node = mod_pubsub:pubsubNode()
+%% From = mod_pubsub:jid()
+%% Depth = integer()
+%% Record = pubsubNode()
+%% @doc <p>Default node tree does not handle parents, return empty list.</p>
+get_parentnodes(_Host, _Node, _From) ->
+ [].
+
+%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
+%% Host = mod_pubsub:host() | mod_pubsub:jid()
+%% Node = mod_pubsub:pubsubNode()
+%% From = mod_pubsub:jid()
+%% Depth = integer()
+%% Record = pubsubNode()
+%% @doc <p>Default node tree does not handle parents, return a list
+%% containing just this node.</p>
+get_parentnodes_tree(Host, Node, From) ->
+ case get_node(Host, Node, From) of
+ N when is_record(N, pubsub_node) ->
+ [{0, mnesia:read(pubsub_node, {Host, Node})}];
+ Error ->
+ Error
+ end.
+
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
@@ -131,7 +161,11 @@ get_nodes(Host) ->
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
- mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, parent = Node, _ = '_'}).
+ Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
+ parents = Parents} = N <- mnesia:table(pubsub_node),
+ Host == NHost,
+ lists:member(Node, Parents)]),
+ qlc:e(Q).
%% @spec (Host, Index) -> [pubsubNodeIdx()] | {error, Reason}
%% Host = mod_pubsub:host()
@@ -180,7 +214,7 @@ create_node(Host, Node, Type, Owner, Options) ->
NodeId = pubsub_index:new(node),
mnesia:write(#pubsub_node{nodeid = {Host, Node},
id = NodeId,
- parent = ParentNode,
+ parents = [ParentNode],
type = Type,
owners = [BJID],
options = Options}),
diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl
index 4b331aeb9..e003f9058 100644
--- a/src/mod_pubsub/nodetree_virtual.erl
+++ b/src/mod_pubsub/nodetree_virtual.erl
@@ -47,6 +47,8 @@
get_node/1,
get_nodes/2,
get_nodes/1,
+ get_parentnodes/3,
+ get_parentnodes_tree/3,
get_subnodes/3,
get_subnodes_tree/3,
create_node/5,
@@ -109,6 +111,22 @@ get_nodes(_Host) ->
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
+get_parentnodes(_Host, _Node, _From) ->
+ [].
+
+%% @spec (Host, Node, From) -> [pubsubNode()]
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% From = mod_pubsub:jid()
+%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
+get_parentnodes_tree(_Host, _Node, _From) ->
+ [].
+
+%% @spec (Host, Node, From) -> [pubsubNode()]
+%% Host = mod_pubsub:host()
+%% Node = mod_pubsub:pubsubNode()
+%% From = mod_pubsub:jid()
+%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(_Host, _Node) ->
diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl
index ee129aa9f..f406a05c3 100644
--- a/src/mod_pubsub/pubsub.hrl
+++ b/src/mod_pubsub/pubsub.hrl
@@ -91,7 +91,7 @@
%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
-record(pubsub_node, {nodeid,
id,
- parent,
+ parents = [],
type = "flat",
owners = [],
options = []