]> granicus.if.org Git - ejabberd/commitdiff
* src/mod_caps.erl: CAPS support (thanks to Magnus Henoch)
authorAlexey Shchepin <alexey@process-one.net>
Sat, 1 Dec 2007 05:16:30 +0000 (05:16 +0000)
committerAlexey Shchepin <alexey@process-one.net>
Sat, 1 Dec 2007 05:16:30 +0000 (05:16 +0000)
* src/ejabberd_local.erl: Support for IQ responses
* src/jlib.erl: Added iq_query_or_response_info/1 function
* src/jlib.hrl: Added NS_PUBSUB_ERRORS and NS_CAPS

* src/mod_pubsub/Makefile.in: New pubsub+pep implementation
(thanks to Christophe Romain and Magnus Henoch)
* src/ejabberd_sm.erl: Added get_session_pid/3 function
* src/ejabberd_c2s.erl: Added get_subscribed_and_online/1 function

SVN Revision: 1004

23 files changed:
ChangeLog
src/ejabberd_c2s.erl
src/ejabberd_local.erl
src/ejabberd_sm.erl
src/jlib.erl
src/jlib.hrl
src/mod_caps.erl [new file with mode: 0644]
src/mod_pubsub/Makefile.in
src/mod_pubsub/Makefile.win32
src/mod_pubsub/gen_pubsub_node.erl [new file with mode: 0644]
src/mod_pubsub/gen_pubsub_nodetree.erl [new file with mode: 0644]
src/mod_pubsub/mod_pubsub.erl
src/mod_pubsub/node.template [new file with mode: 0644]
src/mod_pubsub/node_buddy.erl [new file with mode: 0644]
src/mod_pubsub/node_club.erl [new file with mode: 0644]
src/mod_pubsub/node_default.erl [new file with mode: 0644]
src/mod_pubsub/node_dispatch.erl [new file with mode: 0644]
src/mod_pubsub/node_pep.erl [new file with mode: 0644]
src/mod_pubsub/node_private.erl [new file with mode: 0644]
src/mod_pubsub/node_public.erl [new file with mode: 0644]
src/mod_pubsub/nodetree_default.erl [new file with mode: 0644]
src/mod_pubsub/nodetree_virtual.erl [new file with mode: 0644]
src/mod_pubsub/pubsub.hrl [new file with mode: 0644]

index fb216e24cb45bc8a35b2da353aa0682fee2b719a..ec0a32436b373cdc5017d21afbe9cfc938eced57 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,32 +1,44 @@
+2007-12-01  Alexey Shchepin  <alexey@process-one.net>
+
+       * src/mod_caps.erl: CAPS support (thanks to Magnus Henoch)
+       * src/ejabberd_local.erl: Support for IQ responses
+       * src/jlib.erl: Added iq_query_or_response_info/1 function
+       * src/jlib.hrl: Added NS_PUBSUB_ERRORS and NS_CAPS
+
+       * src/mod_pubsub/Makefile.in: New pubsub+pep implementation
+       (thanks to Christophe Romain and Magnus Henoch)
+       * src/ejabberd_sm.erl: Added get_session_pid/3 function
+       * src/ejabberd_c2s.erl: Added get_subscribed_and_online/1 function
+
 2007-11-30  Mickael Remond  <mremond@process-one.net>
 
-       * src/odbc_queries.erl: Added a default define value so that we can
-       recompile the file manually with a simple erlc command.
+       * src/odbc_queries.erl: Added a default define value so that we
+       can recompile the file manually with a simple erlc command.
 
 2007-11-29  Badlop  <badlop@process-one.net>
 
-       * src/mod_vcard.erl: Add type of x:data field to search
-       results (thanks to Robin Redeker) (EJAB-327)
-       * src/mod_vcard_ldap.erl: 
-       * src/mod_vcard_odbc.erl: 
+       * src/mod_vcard.erl: Add type of x:data field to search results
+       (thanks to Robin Redeker) (EJAB-327)
+       * src/mod_vcard_ldap.erl: Likewise
+       * src/mod_vcard_odbc.erl: Likewise
 
        * src/aclocal.m4: Fix autoconf caching for SSL libraries (thanks
        to Michael Shields) (EJAB-439)
 
        * src/configure.ac: Don't hardcode gcc and gcc options in
        Makefiles (thanks to Etan Reisner) (EJAB-436)
-       * src/Makefile.in: 
-       * src/ejabberd_zlib/Makefile.in: 
-       * src/eldap/Makefile.in: 
-       * src/mod_irc/Makefile.in: 
-       * src/mod_muc/Makefile.in: 
-       * src/mod_proxy65/Makefile.in: 
-       * src/mod_pubsub/Makefile.in: 
-       * src/odbc/Makefile.in: 
-       * src/pam/Makefile.in: 
-       * src/stringprep/Makefile.in: 
-       * src/tls/Makefile.in: 
-       * src/web/Makefile.in: 
+       * src/Makefile.in: Likewise
+       * src/ejabberd_zlib/Makefile.in: Likewise
+       * src/eldap/Makefile.in: Likewise
+       * src/mod_irc/Makefile.in: Likewise
+       * src/mod_muc/Makefile.in: Likewise
+       * src/mod_proxy65/Makefile.in: Likewise
+       * src/mod_pubsub/Makefile.in: Likewise
+       * src/odbc/Makefile.in: Likewise
+       * src/pam/Makefile.in: Likewise
+       * src/stringprep/Makefile.in: Likewise
+       * src/tls/Makefile.in: Likewise
+       * src/web/Makefile.in: Likewise
 
        * src/mod_muc/mod_muc_room.erl: Hide the option 'Make room
        moderated' because it isn't implemented, and set the default value
index f606997274633f5bf07f36c791adbfc7bee8dc09..7253519504997598501f31f947f855e17645d188 100644 (file)
@@ -18,7 +18,8 @@
         send_text/2,
         send_element/2,
         socket_type/0,
-        get_presence/1]).
+        get_presence/1,
+        get_subscribed_and_online/1]).
 
 %% gen_fsm callbacks
 -export([init/1,
@@ -39,6 +40,7 @@
 -include("jlib.hrl").
 
 -define(SETS, gb_sets).
+-define(DICT, dict).
 
 -record(state, {socket,
                sockmod,
@@ -60,6 +62,7 @@
                pres_f = ?SETS:new(),
                pres_a = ?SETS:new(),
                pres_i = ?SETS:new(),
+               pres_available = ?DICT:new(),
                pres_last, pres_pri,
                pres_timestamp,
                pres_invis = false,
@@ -173,6 +176,12 @@ init([{SockMod, Socket}, Opts]) ->
                                 shaper         = Shaper,
                                 ip             = IP}, ?C2S_OPEN_TIMEOUT}.
 
+%% Return list of all available resources of contacts,
+%% in form [{JID, Caps}].
+get_subscribed_and_online(FsmRef) ->
+    gen_fsm:sync_send_all_state_event(
+      FsmRef, get_subscribed_and_online, 1000).
+
 
 %%----------------------------------------------------------------------
 %% Func: StateName/2
@@ -572,7 +581,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
            case xml:get_subtag(El, "method") of
                false ->
                    send_element(StateData,
-                                {xmlelement, "failure",
+                                {xmlelement, "failure",
                                  [{"xmlns", ?NS_COMPRESS}],
                                  [{xmlelement, "setup-failed", [], []}]}),
                    fsm_next_state(wait_for_feature_request, StateData);
@@ -964,6 +973,17 @@ handle_sync_event({get_presence}, _From, StateName, StateData) ->
     Reply = {User, Resource, Show, Status},
     fsm_reply(Reply, StateName, StateData);
 
+handle_sync_event(get_subscribed_and_online, _From, StateName, StateData) ->
+    Subscribed = StateData#state.pres_f,
+    Online = StateData#state.pres_available,
+    Pred = fun(User, _Caps) ->
+                  ?SETS:is_element(jlib:jid_remove_resource(User),
+                                   Subscribed) orelse
+                      ?SETS:is_element(User, Subscribed)
+          end,
+    SubscribedAndOnline = ?DICT:filter(Pred, Online),
+    {reply, ?DICT:to_list(SubscribedAndOnline), StateName, StateData};
+
 handle_sync_event(_Event, _From, StateName, StateData) ->
     Reply = ok,
     fsm_reply(Reply, StateName, StateData).
@@ -1054,32 +1074,42 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
                            allow ->
                                LFrom = jlib:jid_tolower(From),
                                LBFrom = jlib:jid_remove_resource(LFrom),
+                               %% Note contact availability
+                               Caps = mod_caps:read_caps(Els),
+                               mod_caps:note_caps(StateData#state.server, From, Caps),
+                               NewAvailable = case xml:get_attr_s("type", Attrs) of
+                                                  "unavailable" ->
+                                                      ?DICT:erase(LFrom, StateData#state.pres_available);
+                                                  _ ->
+                                                      ?DICT:store(LFrom, Caps, StateData#state.pres_available)
+                                              end,
+                               NewStateData = StateData#state{pres_available = NewAvailable},
                                case ?SETS:is_element(
-                                       LFrom, StateData#state.pres_a) orelse
+                                       LFrom, NewStateData#state.pres_a) orelse
                                    ?SETS:is_element(
-                                      LBFrom, StateData#state.pres_a) of
+                                      LBFrom, NewStateData#state.pres_a) of
                                    true ->
-                                       {true, Attrs, StateData};
+                                       {true, Attrs, NewStateData};
                                    false ->
                                        case ?SETS:is_element(
-                                               LFrom, StateData#state.pres_f) of
+                                               LFrom, NewStateData#state.pres_f) of
                                            true ->
                                                A = ?SETS:add_element(
                                                       LFrom,
-                                                      StateData#state.pres_a),
+                                                      NewStateData#state.pres_a),
                                                {true, Attrs,
-                                                StateData#state{pres_a = A}};
+                                                NewStateData#state{pres_a = A}};
                                            false ->
                                                case ?SETS:is_element(
-                                                       LBFrom, StateData#state.pres_f) of
+                                                       LBFrom, NewStateData#state.pres_f) of
                                                    true ->
                                                        A = ?SETS:add_element(
                                                               LBFrom,
-                                                              StateData#state.pres_a),
+                                                              NewStateData#state.pres_a),
                                                        {true, Attrs,
-                                                        StateData#state{pres_a = A}};
+                                                        NewStateData#state{pres_a = A}};
                                                    false ->
-                                                       {true, Attrs, StateData}
+                                                       {true, Attrs, NewStateData}
                                                end
                                        end
                                end;
index afb5332a7d94f0c2b12aab891754c4654e5e5f7c..37655f39b63d3d291b308fcdb3cc4d5887374411 100644 (file)
@@ -18,6 +18,7 @@
 -export([route/3,
         register_iq_handler/4,
         register_iq_handler/5,
+        register_iq_response_handler/4,
         unregister_iq_handler/2,
         refresh_iq_handlers/0,
         bounce_resource_packet/3
@@ -32,6 +33,8 @@
 
 -record(state, {}).
 
+-record(iq_response, {id, module, function}).
+
 -define(IQTABLE, local_iqtable).
 
 %%====================================================================
@@ -68,13 +71,38 @@ process_iq(From, To, Packet) ->
                    ejabberd_router:route(To, From, Err)
            end;
        reply ->
-           ok;
+           process_iq_reply(From, To, Packet);
        _ ->
            Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
            ejabberd_router:route(To, From, Err),
            ok
     end.
 
+process_iq_reply(From, To, Packet) ->
+    IQ = jlib:iq_query_or_response_info(Packet),
+    #iq{id = ID} = IQ,
+    case catch mnesia:dirty_read(iq_response, ID) of
+       [] ->
+           ok;
+       _ ->
+           F = fun() ->
+                       case mnesia:read({iq_response, ID}) of
+                           [] ->
+                               nothing;
+                           [#iq_response{module = Module,
+                                         function = Function}] ->
+                               mnesia:delete({iq_response, ID}),
+                               {Module, Function}
+                       end
+               end,
+           case mnesia:transaction(F) of
+               {atomic, {Module, Function}} ->
+                   Module:Function(From, To, IQ);
+               _ ->
+                   ok
+           end
+    end.
+
 route(From, To, Packet) ->
     case catch do_route(From, To, Packet) of
        {'EXIT', Reason} ->
@@ -84,6 +112,9 @@ route(From, To, Packet) ->
            ok
     end.
 
+register_iq_response_handler(Host, ID, Module, Fun) ->
+    ejabberd_local ! {register_iq_response_handler, Host, ID, Module, Fun}.
+
 register_iq_handler(Host, XMLNS, Module, Fun) ->
     ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
 
@@ -120,6 +151,9 @@ init([]) ->
                                 ?MODULE, bounce_resource_packet, 100)
       end, ?MYHOSTS),
     catch ets:new(?IQTABLE, [named_table, public]),
+    mnesia:create_table(iq_response,
+                       [{ram_copies, [node()]},
+                        {attributes, record_info(fields, iq_response)}]),
     {ok, #state{}}.
 
 %%--------------------------------------------------------------------
@@ -159,6 +193,9 @@ handle_info({route, From, To, Packet}, State) ->
            ok
     end,
     {noreply, State};
+handle_info({register_iq_response_handler, _Host, ID, Module, Function}, State) ->
+    mnesia:dirty_write(#iq_response{id = ID, module = Module, function = Function}),
+    {noreply, State};
 handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
     ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
     catch mod_disco:register_feature(Host, XMLNS),
index 051d18bdfd5a1c66838df4cb3a89b3d6e6fad03e..0904b7198b0baafb9f440e27fdd9b9ee471ef892 100644 (file)
@@ -28,6 +28,7 @@
         register_iq_handler/5,
         unregister_iq_handler/2,
         ctl_process/2,
+        get_session_pid/3,
         get_user_ip/3
        ]).
 
@@ -74,7 +75,7 @@ open_session(SID, User, Server, Resource, IP) ->
 close_session(SID, User, Server, Resource) ->
     F = fun() ->
                mnesia:delete({session, SID})
-        end,
+       end,
     mnesia:sync_dirty(F),
     JID = jlib:make_jid(User, Server, Resource),
     ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
@@ -139,6 +140,15 @@ close_session_unset_presence(SID, User, Server, Resource, Status) ->
     ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
                       [User, Server, Resource, Status]).
 
+get_session_pid(User, Server, Resource) ->
+    LUser = jlib:nodeprep(User),
+    LServer = jlib:nameprep(Server),
+    LResource = jlib:resourceprep(Resource),
+    USR = {LUser, LServer, LResource},
+    case catch mnesia:dirty_index_read(session, USR, #session.usr) of
+       [#session{sid = {_, Pid}}] -> Pid;
+       _ -> none
+    end.
 
 dirty_get_sessions_list() ->
     mnesia:dirty_select(
@@ -315,7 +325,7 @@ clean_table_from_bad_node(Node) ->
                lists:foreach(fun(E) ->
                                      mnesia:delete({session, E#session.sid})
                              end, Es)
-        end,
+       end,
     mnesia:sync_dirty(F).
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -368,7 +378,6 @@ do_route(From, To, Packet) ->
                                {true, false}
                        end,
                    if Pass ->
-                           LFrom = jlib:jid_tolower(From),
                            PResources = get_user_present_resources(
                                           LUser, LServer),
                            lists:foreach(
@@ -377,7 +386,9 @@ do_route(From, To, Packet) ->
                                        From,
                                        jlib:jid_replace_resource(To, R),
                                        Packet)
-                             end, PResources);
+                             end, PResources),
+                           ejabberd_hooks:run(incoming_presence_hook, LServer,
+                                              [From, To, Packet]);
                       true ->
                            ok
                    end;
@@ -649,4 +660,3 @@ update_tables() ->
        false ->
            ok
     end.
-
index 74369cf22a91f938a8580c3ef3f98fbb6ef38b48..ac54c51a86ddffa5828bd6ccd769b741438d3be6 100644 (file)
@@ -32,6 +32,7 @@
         jid_replace_resource/2,
         get_iq_namespace/1,
         iq_query_info/1,
+        iq_query_or_response_info/1,
         is_iq_request_type/1,
         iq_to_xml/1,
         parse_xdata_submit/1,
@@ -331,39 +332,66 @@ get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
 get_iq_namespace(_) ->
     "".
 
-iq_query_info({xmlelement, Name, Attrs, Els}) when Name == "iq" ->
+iq_query_info(El) ->
+    iq_info_internal(El, request).
+
+iq_query_or_response_info(El) ->
+    iq_info_internal(El, any).
+
+iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
+    %% Filter is either request or any.  If it is request, any replies
+    %% are converted to the atom reply.
     ID = xml:get_attr_s("id", Attrs),
     Type = xml:get_attr_s("type", Attrs),
     Lang = xml:get_attr_s("xml:lang", Attrs),
-    Type1 = case Type of
-               "set" -> set;
-               "get" -> get;
-               "result" -> reply;
-               "error" -> reply;
-               _ -> invalid
+    {Type1, Class} = case Type of
+                        "set" -> {set, request};
+                        "get" -> {get, request};
+                        "result" -> {result, reply};
+                        "error" -> {error, reply};
+                        _ -> {invalid, invalid}
            end,
     if
-       (Type1 /= invalid) and (Type1 /= reply) ->
-           case xml:remove_cdata(Els) of
-               [{xmlelement, Name2, Attrs2, Els2}] ->
-                   XMLNS = xml:get_attr_s("xmlns", Attrs2),
-                   if
-                       XMLNS /= "" ->
+       Type1 == invalid ->
+           invalid;
+       Class == request; Filter == any ->
+           %% The iq record is a bit strange.  The sub_el field is an
+           %% XML tuple for requests, but a list of XML tuples for
+           %% responses.
+           FilteredEls = xml:remove_cdata(Els),
+           {XMLNS, SubEl} =
+               case {Class, FilteredEls} of
+                   {request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
+                       {xml:get_attr_s("xmlns", Attrs2),
+                        hd(FilteredEls)};
+                   {reply, _} ->
+                       %% Find the namespace of the first non-error
+                       %% element, if there is one.
+                       NonErrorEls = [El || 
+                                         {xmlelement, SubName, _, _} = El
+                                             <- FilteredEls, 
+                                         SubName /= "error"],
+                       {case NonErrorEls of
+                            [NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
+                            _ -> invalid
+                        end,
+                        FilteredEls};
+                   _ ->
+                       {invalid, invalid}
+               end,
+           if XMLNS == "", Class == request ->
+                   invalid;
+              true ->
                            #iq{id = ID,
                                type = Type1,
                                xmlns = XMLNS,
                                lang = Lang,
-                               sub_el = {xmlelement, Name2, Attrs2, Els2}};
-                       true ->
-                           invalid
+                       sub_el = SubEl}
                    end;
-               _ ->
-                   invalid
-           end;
-       true ->
-           Type1
+       Class == reply, Filter /= any ->
+           reply
     end;
-iq_query_info(_) ->
+iq_info_internal(_, _) ->
     not_iq.
 
 is_iq_request_type(set) -> true;
index 8034698eb36f44e83261e2ca13e435b8f5b19f6d..b83625ee04373aae76aa2c16dbb32b061269cdef 100644 (file)
@@ -33,6 +33,7 @@
 -define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
 -define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
 -define(NS_PUBSUB_NMI,   "http://jabber.org/protocol/pubsub#node-meta-info").
+-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
 -define(NS_COMMANDS,     "http://jabber.org/protocol/commands").
 -define(NS_BYTESTREAMS,  "http://jabber.org/protocol/bytestreams").
 -define(NS_ADMIN,        "http://jabber.org/protocol/admin").
@@ -55,6 +56,8 @@
 
 -define(NS_COMPRESS,     "http://jabber.org/protocol/compress").
 
+-define(NS_CAPS,          "http://jabber.org/protocol/caps").
+
 % TODO: remove "code" attribute (currently it used for backward-compatibility)
 -define(STANZA_ERROR(Code, Type, Condition),
        {xmlelement, "error",
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
new file mode 100644 (file)
index 0000000..66afd10
--- /dev/null
@@ -0,0 +1,259 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_caps.erl
+%%% Author  : Magnus Henoch <henoch@dtek.chalmers.se>
+%%% Purpose : Request and cache Entity Capabilities (XEP-0115)
+%%% Created : 7 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
+%%%----------------------------------------------------------------------
+
+-module(mod_caps).
+-author('henoch@dtek.chalmers.se').
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+-export([read_caps/1,
+        note_caps/3,
+        get_features/2,
+        handle_disco_response/3]).
+
+%% gen_mod callbacks
+-export([start/2, start_link/2,
+        stop/1]).
+
+%% gen_server callbacks
+-export([init/1,
+        handle_info/2,
+        handle_call/3,
+        handle_cast/2,
+        terminate/2,
+        code_change/3
+       ]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+-define(PROCNAME, ejabberd_mod_caps).
+-define(DICT, dict).
+
+-record(caps, {node, version, exts}).
+-record(caps_features, {node_pair, features}).
+-record(state, {host,
+               disco_requests = ?DICT:new(),
+               feature_queries = []}).
+
+%% read_caps takes a list of XML elements (the child elements of a
+%% <presence/> stanza) and returns an opaque value representing the
+%% Entity Capabilities contained therein, or the atom nothing if no
+%% capabilities are advertised.
+read_caps([{xmlelement, "c", Attrs, _Els} | Tail]) ->
+    case xml:get_attr_s("xmlns", Attrs) of
+       ?NS_CAPS ->
+           Node = xml:get_attr_s("node", Attrs),
+           Version = xml:get_attr_s("ver", Attrs),
+           Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
+           #caps{node = Node, version = Version, exts = Exts};
+       _ ->
+           read_caps(Tail)
+    end;
+read_caps([_ | Tail]) ->
+    read_caps(Tail);
+read_caps([]) ->
+    nothing.
+
+%% note_caps should be called to make the module request disco
+%% information.  Host is the host that asks, From is the full JID that
+%% sent the caps packet, and Caps is what read_caps returned.
+note_caps(Host, From, Caps) ->
+    case Caps of
+       nothing -> ok;
+       _ ->
+           Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+           gen_server:cast(Proc, {note_caps, From, Caps})
+    end.
+
+%% get_features returns a list of features implied by the given caps
+%% record (as extracted by read_caps).  It may block, and may signal a
+%% timeout error.
+get_features(Host, Caps) ->
+    case Caps of
+       nothing -> [];
+       #caps{} ->
+           Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+           gen_server:call(Proc, {get_features, Caps})
+    end.
+
+start_link(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    ChildSpec =
+       {Proc,
+        {?MODULE, start_link, [Host, Opts]},
+        transient,
+        1000,
+        worker,
+        [?MODULE]},
+    supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:call(Proc, stop),
+    supervisor:stop_child(ejabberd_sup, Proc).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+init([Host, _Opts]) ->
+    mnesia:create_table(caps_features,
+                       [{ram_copies, [node()]},
+                        {attributes, record_info(fields, caps_features)}]),
+    mnesia:add_table_copy(caps_features, node(), ram_copies),
+    {ok, #state{host = Host}}.
+
+maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
+    SubNodes = [Version | Exts],
+    F = fun() ->
+               %% Make sure that we have all nodes we need to know.
+               %% If a single one is missing, we wait for more disco
+               %% responses.
+               lists:foldl(fun(SubNode, Acc) ->
+                                   case Acc of
+                                       fail -> fail;
+                                       _ ->
+                                           case mnesia:read({caps_features, {Node, SubNode}}) of
+                                               [] -> fail;
+                                               [#caps_features{features = Features}] -> Features ++ Acc
+                                           end
+                                   end
+                           end, [], SubNodes)
+       end,
+    case mnesia:transaction(F) of
+       {atomic, fail} ->
+           wait;
+       {atomic, Features} ->
+           {ok, Features}
+    end.
+
+timestamp() ->
+    {MegaSecs, Secs, _MicroSecs} = now(),
+    MegaSecs * 1000000 + Secs.
+    
+handle_call({get_features, Caps}, From, State) ->
+    case maybe_get_features(Caps) of
+       {ok, Features} -> 
+           {reply, Features, State};
+       wait ->
+           Timeout = timestamp() + 10,
+           FeatureQueries = State#state.feature_queries,
+           NewFeatureQueries = [{From, Caps, Timeout} | FeatureQueries],
+           NewState = State#state{feature_queries = NewFeatureQueries},
+           {noreply, NewState}
+    end;
+
+handle_call(stop, _From, State) ->
+    {stop, normal, ok, State}.
+
+handle_cast({note_caps, From, 
+            #caps{node = Node, version = Version, exts = Exts}}, 
+           #state{host = Host, disco_requests = Requests} = State) ->
+    %% XXX: this leads to race conditions where ejabberd will send
+    %% lots of caps disco requests.
+    SubNodes = [Version | Exts],
+    %% Now, find which of these are not already in the database.
+    Fun = fun() ->
+                 lists:foldl(fun(SubNode, Acc) ->
+                                     case mnesia:read({caps_features, {Node, SubNode}}) of
+                                         [] ->
+                                             [SubNode | Acc];
+                                         _ ->
+                                             Acc
+                                     end
+                             end, [], SubNodes)
+         end,
+    case mnesia:transaction(Fun) of
+       {atomic, Missing} ->
+           %% For each unknown caps "subnode", we send a disco
+           %% request.
+           NewRequests =
+               lists:foldl(
+                 fun(SubNode, Dict) ->
+                         ID = randoms:get_string(),
+                         Stanza =
+                             {xmlelement, "iq",
+                              [{"type", "get"},
+                               {"id", ID}],
+                              [{xmlelement, "query",
+                                [{"xmlns", ?NS_DISCO_INFO},
+                                 {"node", Node ++ "#" ++ SubNode}],
+                                []}]},
+                         ejabberd_local:register_iq_response_handler
+                           (Host, ID, ?MODULE, handle_disco_response),
+                         ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
+                         ?DICT:store(ID, {Node, SubNode}, Dict)
+                 end, Requests, Missing),
+           {noreply, State#state{disco_requests = NewRequests}};
+       Error ->
+           ?ERROR_MSG("Transaction failed: ~p", [Error]),
+           {noreply, State}
+    end;
+handle_cast({disco_response, From, _To, 
+            #iq{type = Type, id = ID,
+                sub_el = SubEls}},
+           #state{disco_requests = Requests} = State) ->
+    case {Type, SubEls} of
+       {result, [{xmlelement, "query", Attrs, Els}]} ->
+           case ?DICT:find(ID, Requests) of
+               {ok, {Node, SubNode}} ->
+                   Features =
+                       lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
+                                             [xml:get_attr_s("var", FAttrs)];
+                                        (_) ->
+                                             []
+                                     end, Els),
+                   mnesia:transaction(
+                     fun() ->
+                             mnesia:write(#caps_features{node_pair = {Node, SubNode},
+                                                         features = Features})
+                     end),
+                   gen_server:cast(self(), visit_feature_queries);
+               error ->
+                   ?ERROR_MSG("ID '~s' matches no query", [ID])
+           end;
+       {result, _} ->
+           ?ERROR_MSG("Invalid IQ contents from ~s: ~p", [jlib:jid_to_string(From), SubEls]);
+       _ ->
+           %% Can't do anything about errors
+           ok
+    end,
+    NewRequests = ?DICT:erase(ID, Requests),
+    {noreply, State#state{disco_requests = NewRequests}};
+handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) ->
+    Timestamp = timestamp(),
+    NewFeatureQueries =
+       lists:foldl(fun({From, Caps, Timeout}, Acc) ->
+                           case maybe_get_features(Caps) of
+                               wait when Timeout < Timestamp -> [{From, Caps, Timeout} | Acc];
+                               wait -> Acc;
+                               {ok, Features} ->
+                                   gen_server:reply(From, Features),
+                                   Acc
+                           end
+                   end, [], FeatureQueries),
+    {noreply, State#state{feature_queries = NewFeatureQueries}}.
+
+handle_disco_response(From, To, IQ) ->
+    #jid{lserver = Host} = To,
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:cast(Proc, {disco_response, From, To, IQ}).
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
index 95d0a131c11d0e2194ddfe0f72cbe2e89b2ab4b7..116ddbfbc1d89339b8eda19e30ff041d70b91aa8 100644 (file)
@@ -15,11 +15,18 @@ OUTDIR = ..
 EFLAGS = -I .. -pz ..
 # make debug=true to compile Erlang module with debug informations.
 ifdef debug
-  EFLAGS+=+debug_info
+EFLAGS+=+debug_info
 endif
 
 OBJS   = \
-       $(OUTDIR)/mod_pubsub.beam
+       $(OUTDIR)/gen_pubsub_node.beam \
+       $(OUTDIR)/gen_pubsub_nodetree.beam \
+       $(OUTDIR)/nodetree_default.beam \
+       $(OUTDIR)/nodetree_virtual.beam \
+       $(OUTDIR)/mod_pubsub.beam \
+       $(OUTDIR)/mod_pubsub_old.beam \
+       $(OUTDIR)/node_default.beam \
+       $(OUTDIR)/node_pep.beam 
 
 all:    $(OBJS)
 
index bb44dd77865750c7009689921ddf95b581448cd8..077b028996132fc1642f4f44623e95eb41fb0a33 100644 (file)
@@ -5,12 +5,38 @@ OUTDIR = ..
 EFLAGS = -I .. -pz ..
 
 OBJS = \
-       $(OUTDIR)\mod_pubsub.beam
+       $(OUTDIR)/gen_pubsub_node.beam \
+       $(OUTDIR)/gen_pubsub_nodetree.beam \
+       $(OUTDIR)/nodetree_default.beam \
+       $(OUTDIR)/nodetree_virtual.beam \
+       $(OUTDIR)/mod_pubsub.beam \
+       $(OUTDIR)/mod_pubsub_old.beam \
+       $(OUTDIR)/node_default.beam \
+       $(OUTDIR)/node_pep.beam 
 
 ALL : $(OBJS)
 
 CLEAN :
        -@erase $(OBJS)
 
+$(OUTDIR)\gen_pubsub_node.beam : gen_pubsub_node.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) gen_pubsub_node.erl
+
+$(OUTDIR)\gen_pubsub_nodetree.beam : gen_pubsub_nodetree.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) 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_virtual.beam : nodetree_virtual.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_virtual.erl
+
+$(OUTDIR)\node_default.beam : node_default.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) node_default.erl
+
+$(OUTDIR)\node_pep.beam : node_pep.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) node_pep.erl
+
diff --git a/src/mod_pubsub/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl
new file mode 100644 (file)
index 0000000..33c2704
--- /dev/null
@@ -0,0 +1,55 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2006 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @private
+%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
+%%% plugin behaviour. This behaviour is used to check that a PubSub plugin
+%%% respects the current ejabberd PubSub plugin API.</p>
+
+-module(gen_pubsub_node).
+
+-export([behaviour_info/1]).
+
+%% @spec (Query::atom()) -> Callbacks | atom()
+%%     Callbacks = [{Function,Arity}]
+%%     Function = atom()
+%%     Arity = integer()
+%% @doc Behaviour definition
+behaviour_info(callbacks) ->
+    [{init, 3},
+     {terminate, 2},
+     {options, 0},
+     {features, 0},
+     {create_node_permission, 6},
+     {create_node, 3},
+     {delete_node, 2},
+     {purge_node, 3},
+     {subscribe_node, 8},
+     {unsubscribe_node, 5},
+     {publish_item, 7},
+     {delete_item, 4},
+     {remove_extra_items, 4},
+     {get_node_affiliations, 2},
+     {get_entity_affiliations, 2},
+     {get_affiliation, 3},
+     {set_affiliation, 4},
+     {get_node_subscriptions, 2},
+     {get_entity_subscriptions, 2},
+     {get_subscription, 3},
+     {set_subscription, 4},
+     {get_states, 2},
+     {get_state, 3},
+     {set_state, 1},
+     {get_items, 2},
+     {get_item, 3},
+     {set_item, 1}
+    ];
+behaviour_info(_Other) ->
+    undefined.
diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl
new file mode 100644 (file)
index 0000000..7529850
--- /dev/null
@@ -0,0 +1,40 @@
+%%% ====================================================================
+%%% This software is copyright 2006, Process-one.
+%%%
+%%% $Id: gen_pubsub_nodetree.erl 100 2007-11-15 13:04:44Z mremond $
+%%%
+%%% @copyright 2006 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @private
+%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
+%%% tree plugin behaviour. This behaviour is used to check that a PubSub 
+%%% node tree plugin respects the current ejabberd PubSub plugin API.</p>
+
+-module(gen_pubsub_nodetree).
+
+-export([behaviour_info/1]).
+
+%% @spec (Query::atom()) -> Callbacks | atom()
+%%     Callbacks = [{Function,Arity}]
+%%     Function = atom()
+%%     Arity = integer()
+%% @doc Behaviour definition
+behaviour_info(callbacks) ->
+       [{init, 3},
+        {terminate, 2},
+        {options, 0},
+        {set_node, 1},
+        {get_node, 2},
+        {get_nodes, 1},
+        {get_subnodes, 2},
+        {get_subnodes_tree, 2},
+        {create_node, 5},
+        {delete_node, 2}
+       ];
+behaviour_info(_Other) ->
+       undefined.
index 82a0bace77d110e44952d461a879b52eb7efce2b..c218e171151a720f1edf7557312a9d40d6f301ee 100644 (file)
-%%%----------------------------------------------------------------------
-%%% File    : mod_pubsub.erl
-%%% Author  : Alexey Shchepin <alexey@sevcom.net>
-%%% Purpose : Pub/sub support (JEP-0060)
-%%% Created :  4 Jul 2003 by Alexey Shchepin <alexey@sevcom.net>
-%%%----------------------------------------------------------------------
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+
+%%% @doc The module <strong>{@module}</strong> is the core of the PubSub
+%%% extension. It relies on PubSub plugins for a large part of its functions.
+%%%
+%%% @headerfile "pubsub.hrl"
+%%%
+%%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for
+%%% the latest version of the PubSub specification.
+%%% This module uses version 1.10 of the specification as a base.
+%%% Most of the specification is implemented.
+%%% Code is derivated from the original pubsub v1.7, functions concerning config may be rewritten.
+%%% Code also inspired from the original PEP patch by Magnus Henoch <mange@freemail.hu>
+
+%%% TODO
+%%% plugin: generate Reply (do not use broadcast atom anymore)
+%%% to be implemented: deliver_notifications
 
 -module(mod_pubsub).
--author('alexey@sevcom.net').
+-version('1.10-01').
 
 -behaviour(gen_server).
 -behaviour(gen_mod).
 
-%% API
--export([start_link/2,
-        start/2,
-        stop/1]).
-
--export([delete_item/3,
-        set_entities/4,
-        delete_node/2,
-        create_new_node/2,
-        subscribe_node/3,
-        get_node_config/4,
-        set_node_config/4]).
-
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
-        terminate/2, code_change/3]).
-
 -include("ejabberd.hrl").
 -include("jlib.hrl").
-
--record(state, {host, server_host, access}).
-
--define(DICT, dict).
--define(MAXITEMS, 20).
--define(MAX_PAYLOAD_SIZE, 100000).
-
--record(pubsub_node, {host_node, host_parent, info}).
--record(pubsub_presence, {key, presence}).
--record(nodeinfo, {items = [],
-                  options = [],
-                  entities = ?DICT:new()
-                 }).
--record(entity, {affiliation = none,
-                subscription = none}).
--record(item, {id, publisher, payload}).
+-include("pubsub.hrl").
+
+-define(STDTREE, "default").
+-define(STDNODE, "default").
+
+%% exports for hooks
+-export([set_presence/4,
+        unset_presence/4,
+        incoming_presence/3,
+        disco_local_identity/5,
+        disco_sm_identity/5,
+        disco_sm_features/5,
+        disco_sm_items/5
+       ]).
+%% exported iq handlers
+-export([iq_local/3,
+        iq_sm/3
+       ]).
+
+%% exports for console debug manual use
+-export([create_node/5,
+        delete_node/3,
+        subscribe_node/4,
+        unsubscribe_node/5,
+        publish_item/6,
+        delete_item/4,
+        get_configure/4,
+        set_configure/5,
+        get_items/2,
+        node_action/3,
+        node_action/4
+       ]).
+
+%% general helpers for plugins
+-export([node_to_string/1,
+        string_to_node/1,
+        subscription_to_string/1,
+        affiliation_to_string/1,
+        string_to_subscription/1,
+        string_to_affiliation/1,
+        extended_error/2,
+        extended_error/3
+       ]).
+
+%% API and gen_server callbacks
+-export([start_link/2,
+        start/2,
+        stop/1,
+        init/1,
+        handle_call/3,
+        handle_cast/2,
+        handle_info/2,
+        terminate/2,
+        code_change/3
+       ]).
 
 -define(PROCNAME, ejabberd_mod_pubsub).
--define(MYJID, #jid{user = "", server = Host, resource = "",
-                   luser = "", lserver = Host, lresource = ""}).
+-define(PLUGIN_PREFIX, "node_").
+-define(TREE_PREFIX, "nodetree_").
+
+-record(state, {server_host,
+               host,
+               access,
+               nodetree = ?STDTREE,
+               plugins = [?STDNODE]}).
 
 %%====================================================================
 %% API
@@ -64,13 +112,9 @@ start_link(Host, Opts) ->
 
 start(Host, Opts) ->
     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
-    ChildSpec =
-       {Proc,
-        {?MODULE, start_link, [Host, Opts]},
-        temporary,
-        1000,
-        worker,
-        [?MODULE]},
+    ChildSpec = {Proc,
+                {?MODULE, start_link, [Host, Opts]},
+                transient, 1000, worker, [?MODULE]},
     supervisor:start_child(ejabberd_sup, ChildSpec).
 
 stop(Host) ->
@@ -78,30 +122,6 @@ stop(Host) ->
     gen_server:call(Proc, stop),
     supervisor:delete_child(ejabberd_sup, Proc).
 
-delete_item(From, Node, ItemID) ->
-    delete_item(get_host(), From, Node, ItemID).
-
-delete_node(From, Node) ->
-    delete_node(get_host(), From, Node).
-
-create_new_node(Node, From) ->
-    create_new_node(get_host(), Node, From).
-
-subscribe_node(From, JID, Node) ->
-    subscribe_node(get_host(), From, JID, Node).
-
-set_node_config(From, Node, Els, Lang) ->
-    set_node_config(get_host(), From, Node, Els, Lang).
-
-get_host() ->
-    ejabberd_mod_pubsub ! {get_host, self()},
-    receive
-       {pubsub_host, Host} ->
-           Host
-    after 5000 ->
-           timeout
-    end.
-
 %%====================================================================
 %% gen_server callbacks
 %%====================================================================
@@ -114,30 +134,256 @@ get_host() ->
 %% Description: Initiates the server
 %%--------------------------------------------------------------------
 init([ServerHost, Opts]) ->
-    mnesia:create_table(pubsub_node,
-                       [{disc_only_copies, [node()]},
-                        {attributes, record_info(fields, pubsub_node)}]),
+    ?INFO_MSG("pubsub init ~p ~p",[ServerHost,Opts]),
     mnesia:create_table(pubsub_presence,
-                       [{ram_copies, [node()]},
+                       [{disc_copies, [node()]},
                         {attributes, record_info(fields, pubsub_presence)}]),
     Host = gen_mod:get_opt_host(ServerHost, Opts, "pubsub.@HOST@"),
-    update_table(Host),
-    mnesia:add_table_index(pubsub_node, host_parent),
     ServedHosts = gen_mod:get_opt(served_hosts, Opts, []),
     Access = gen_mod:get_opt(access_createnode, Opts, all),
-
+    mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
+    ejabberd_hooks:add(disco_local_identity, ServerHost,
+                      ?MODULE, disco_local_identity, 75),
+    ejabberd_hooks:add(disco_sm_identity, ServerHost,
+                      ?MODULE, disco_sm_identity, 75),
+    ejabberd_hooks:add(disco_sm_features, ServerHost,
+                      ?MODULE, disco_sm_features, 75),
+    ejabberd_hooks:add(disco_sm_items, ServerHost,
+                      ?MODULE, disco_sm_items, 75),
+    ejabberd_hooks:add(incoming_presence_hook, ServerHost,
+                      ?MODULE, incoming_presence, 50),
+    %%ejabberd_hooks:add(set_presence_hook, ServerHost, ?MODULE, set_presence, 50),
+    %%ejabberd_hooks:add(unset_presence_hook, ServerHost, ?MODULE, unset_presence, 50),
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+    lists:foreach(
+      fun({NS,Mod,Fun}) ->
+             gen_iq_handler:add_iq_handler(
+               Mod, ServerHost, NS, ?MODULE, Fun, IQDisc)
+      end,
+      [{?NS_PUBSUB, ejabberd_local, iq_local},
+       {?NS_PUBSUB_OWNER, ejabberd_local, iq_local},
+       {?NS_PUBSUB, ejabberd_sm, iq_sm},
+       {?NS_PUBSUB_OWNER, ejabberd_sm, iq_sm}]),
     ejabberd_router:register_route(Host),
-    create_new_node(Host, ["pubsub"], ?MYJID),
-    create_new_node(Host, ["pubsub", "nodes"], ?MYJID),
-    create_new_node(Host, ["home"], ?MYJID),
-    create_new_node(Host, ["home", ServerHost], ?MYJID),
-    lists:foreach(fun(H) ->
-                         create_new_node(Host, ["home", H], ?MYJID)
-                 end, ServedHosts),
-    {ok, #state{host = Host, server_host = ServerHost, access = Access}}.
+    {Plugins, NodeTree} = init_plugins(Host, ServerHost, Opts),
+    update_database(Host),
+    ets:new(gen_mod:get_module_proc(Host, pubsub_state), [set, named_table]),
+    ets:insert(gen_mod:get_module_proc(Host, pubsub_state), {nodetree, NodeTree}),
+    ets:insert(gen_mod:get_module_proc(Host, pubsub_state), {plugins, Plugins}),
+    ets:new(gen_mod:get_module_proc(ServerHost, pubsub_state), [set, named_table]),
+    ets:insert(gen_mod:get_module_proc(ServerHost, pubsub_state), {nodetree, NodeTree}),
+    ets:insert(gen_mod:get_module_proc(ServerHost, pubsub_state), {plugins, Plugins}),
+    init_nodes(Host, ServerHost, ServedHosts),
+    {ok, #state{host = Host,
+               server_host = ServerHost,
+               access = Access,
+               nodetree = NodeTree,
+               plugins = Plugins}}.
+
+%% @spec (Host, Opts) -> Plugins
+%%      Host = mod_pubsub:host()   Opts = [{Key,Value}]
+%%      ServerHost = host()
+%%      Key = atom()
+%%      Value = term()
+%%      Plugins = [Plugin::string()]
+%% @doc Call the init/1 function for each plugin declared in the config file.
+%% The default plugin module is implicit.
+%% <p>The Erlang code for the plugin is located in a module called
+%% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p>
+%% <p>The modules are initialized in alphetical order and the list is checked
+%% and sorted to ensure that each module is initialized only once.</p>
+%% <p>See {@link node_default:init/1} for an example implementation.</p>
+init_plugins(Host, ServerHost, Opts) ->
+    TreePlugin = list_to_atom(?TREE_PREFIX ++
+                             gen_mod:get_opt(nodetree, Opts, ?STDTREE)),
+    ?INFO_MSG("** tree plugin is ~p",[TreePlugin]),
+    TreePlugin:init(Host, ServerHost, Opts),
+    Plugins = lists:usort(gen_mod:get_opt(plugins, Opts, []) ++ [?STDNODE]),
+    lists:foreach(fun(Name) ->
+                         ?INFO_MSG("** init ~s plugin~n",[Name]),
+                         Plugin = list_to_atom(?PLUGIN_PREFIX ++ Name),
+                         Plugin:init(Host, ServerHost, Opts)
+                 end, Plugins),
+    {Plugins, TreePlugin}.
+
+terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
+    lists:foreach(fun(Name) ->
+                         ?INFO_MSG("** terminate ~s plugin~n",[Name]),
+                         Plugin = list_to_atom(?PLUGIN_PREFIX++Name),
+                         Plugin:terminate(Host, ServerHost)
+                 end, Plugins),
+    TreePlugin:terminate(Host, ServerHost),
+    ok.
+
+init_nodes(Host, ServerHost, ServedHosts) ->
+    create_node(Host, ServerHost, ["pubsub"], ?PUBSUB_JID, ?STDNODE),
+    create_node(Host, ServerHost, ["pubsub", "nodes"], ?PUBSUB_JID, ?STDNODE),
+    create_node(Host, ServerHost, ["home"], ?PUBSUB_JID, ?STDNODE),
+    lists:foreach(
+      fun(H) ->
+             create_node(Host, ServerHost, ["home", H], ?PUBSUB_JID, ?STDNODE)
+      end, [ServerHost | ServedHosts]),
+    ok.
+
+update_database(Host) ->
+    case catch mnesia:table_info(pubsub_node, attributes) of
+       [host_node, host_parent, info] ->
+           ?INFO_MSG("upgrade pubsub tables",[]),
+           F = fun() ->
+                       NewRecords =
+                           lists:foldl(
+                             fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, RecList) ->
+                                     ItemsList =
+                                         lists:foldl(
+                                           fun({item, IID, Publisher, Payload}, Acc) ->
+                                                   C = {Publisher, unknown},
+                                                   M = {Publisher, now()},
+                                                   mnesia:write(
+                                                     #pubsub_item{itemid = {IID, NodeId},
+                                                                  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{stateid = {JID, NodeId},
+                                                                   items = UsrItems,
+                                                                   affiliation = Aff,
+                                                                   subscription = Sub}),
+                                                   case Aff of
+                                                       owner -> [JID | Acc];
+                                                       _ -> Acc
+                                                   end
+                                           end, [], Entities),
+                                     mnesia:delete({pubsub_node, NodeId}),
+                                     [#pubsub_node{nodeid = NodeId,
+                                                   parentid = ParentId,
+                                                   owners = Owners,
+                                                   options = Options} |
+                                      RecList]
+                             end, [],
+                             mnesia:match_object(
+                               {pubsub_node, {Host, '_'}, '_', '_'})),
+                       mnesia:delete_table(pubsub_node),
+                       mnesia:create_table(pubsub_node,
+                                           [{disc_copies, [node()]},
+                                            {attributes, record_info(fields, pubsub_node)},
+                                            {index, [type, parentid]}]),
+                       lists:foreach(fun(Record) ->
+                                             mnesia:write(Record)
+                                     end, NewRecords)
+               end,
+           mnesia:transaction(F);
+       _ ->
+           ok
+    end.
+
+%% -------
+%% hooks handling functions
+%%
+
+disco_local_identity(Acc, _From, _To, [], _Lang) ->
+    Acc ++ [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "service"}], []},
+           {xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []} ];
+disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
+    Acc.
+
+disco_sm_identity(Acc, _From, _To, [], _Lang) ->
+    Acc ++ [
+           {xmlelement, "identity", [{"category", "pubsub"}, {"type", "service"}], []},
+           {xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []} ];
+disco_sm_identity(Acc, From, To, Node, _Lang) ->
+    LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
+    Acc ++ case node_disco_identity(LOwner, From, Node) of
+              {result, I} -> I;
+              _ -> []
+          end.
+
+disco_sm_features(Acc, _From, _To, [], _Lang) ->
+    Acc;
+disco_sm_features(Acc, From, To, Node, _Lang) ->
+    LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
+    Features = node_disco_features(LOwner, From, Node),
+    case {Acc, Features} of
+       {{result, AccFeatures}, {result, AddFeatures}} ->
+           {result, AccFeatures++AddFeatures};
+       {_, {result, AddFeatures}} ->
+           {result, AddFeatures};
+       {_, _} ->
+           Acc
+    end.
+
+disco_sm_items(Acc, _From, To, [], _Lang) ->
+    %% TODO, use iq_disco_items(Host, [], From)
+    Host = To#jid.lserver,
+    LJID = jlib:jid_tolower(jlib:jid_remove_resource(To)),
+    case tree_action(Host, get_nodes, [Host]) of
+       [] ->
+           Acc;
+       Nodes ->
+           Items = case Acc of
+                       {result, I} -> I;
+                       _ -> []
+                   end,
+           NodeItems = lists:map(
+                         fun(Node) ->
+                                 {xmlelement, "item",
+                                  [{"jid", jlib:jid_to_string(LJID)},
+                                   {"node", node_to_string(Node)}],
+                                  []}
+                         end, Nodes),
+           {result, NodeItems ++ Items}
+    end;
+
+disco_sm_items(Acc, _From, To, Node, _Lang) ->
+    %% TODO, use iq_disco_items(Host, Node, From)
+    Host = To#jid.lserver,
+    LJID = jlib:jid_tolower(jlib:jid_remove_resource(To)),
+    case tree_action(Host, get_items, [Host, Node]) of
+       [] ->
+           Acc;
+       AllItems ->
+           Items = case Acc of
+                       {result, I} -> I;
+                       _ -> []
+                   end,
+           NodeItems = lists:map(
+                         fun(#pubsub_item{itemid = Id}) ->
+                                 %% "jid" is required by XEP-0030, and
+                                 %% "node" is forbidden by XEP-0060.
+                                 {xmlelement, "item",
+                                  [{"jid", jlib:jid_to_string(LJID)},
+                                   {"name", Id}],
+                                  []}
+                         end, AllItems),
+           {result, NodeItems ++ Items}
+    end.
+
+
+set_presence(User, Server, Resource, Presence) ->
+    Proc = gen_mod:get_module_proc(Server, ?PROCNAME),
+    gen_server:cast(Proc, {set_presence, User, Server, Resource, Presence}).
+unset_presence(User, Server, Resource, Status) ->
+    Proc = gen_mod:get_module_proc(Server, ?PROCNAME),
+    gen_server:cast(Proc, {unset_presence, User, Server, Resource, Status}).
+incoming_presence(From, #jid{lserver = Host} = To, Packet) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:cast(Proc, {presence, From, To, Packet}).
 
 %%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% Function:
+%% handle_call(Request, From, State) -> {reply, Reply, State} |
 %%                                      {reply, Reply, State, Timeout} |
 %%                                      {noreply, State} |
 %%                                      {noreply, State, Timeout} |
@@ -145,6 +391,13 @@ init([ServerHost, Opts]) ->
 %%                                      {stop, Reason, State}
 %% Description: Handling call messages
 %%--------------------------------------------------------------------
+%% @private
+handle_call(server_host, _From, State) ->
+    {reply, State#state.server_host, State};
+handle_call(plugins, _From, State) ->
+    {reply, State#state.plugins, State};
+handle_call(nodetree, _From, State) ->
+    {reply, State#state.nodetree, State};
 handle_call(stop, _From, State) ->
     {stop, normal, ok, State}.
 
@@ -154,6 +407,142 @@ handle_call(stop, _From, State) ->
 %%                                      {stop, Reason, State}
 %% Description: Handling cast messages
 %%--------------------------------------------------------------------
+%% @private
+handle_cast({presence, From, To, Packet}, State) ->
+    Plugins = State#state.plugins,
+    Key = {To#jid.lserver, From#jid.luser, From#jid.lserver},
+    Record = #pubsub_presence{key = Key, resource = From#jid.lresource},
+    Priority = case xml:get_subtag(Packet, "priority") of
+                  false -> 0;
+                  SubEl ->
+                      case catch list_to_integer(xml:get_tag_cdata(SubEl)) of
+                          P when is_integer(P) -> P;
+                          _ -> 0
+                      end
+              end,
+    PresenceType = case xml:get_tag_attr_s("type", Packet) of
+                      "" ->
+                          if Priority < 0 -> unavailable;
+                             true -> available
+                          end;
+                      "unavailable" -> unavailable;
+                      "error" -> unavailable;
+                      _ -> none
+                  end,
+    PreviouslyAvailable =
+       lists:member(From#jid.lresource,
+                    lists:map(fun(#pubsub_presence{resource = R}) -> R end,
+                              case catch mnesia:dirty_read(pubsub_presence, Key) of
+                                  Result when is_list(Result) -> Result;
+                                  _ -> []
+                              end)),
+    case PresenceType of
+       available -> mnesia:dirty_write(Record);
+       unavailable -> mnesia:dirty_delete_object(Record);
+       _ -> ok
+    end,
+    if PreviouslyAvailable == false,
+       PresenceType == available ->
+           %% A new resource is available. Loop through all nodes
+           %% and see if the contact is subscribed, and if so, and if
+           %% the node is so configured, send the last item.
+           Host = From#jid.lserver,
+           JID = jlib:jid_tolower(From),
+           lists:foreach(
+             fun(Type) ->
+                     {result, Subscriptions} = node_action(Type, get_entity_subscriptions, [Host, From]),
+                     lists:foreach(
+                       fun({Node, Subscription}) ->
+                               Options = node_options(Type),
+                               SendLast = get_option(Options, send_last_published_item),
+                               if
+                                   Subscription /= none, Subscription /= pending, SendLast == on_sub_and_presence ->
+                                       send_last_item(Host, Node, JID);
+                                   SendLast == on_sub_and_presence ->
+                                       AccessModel = get_option(Options, access_model),
+                                       AllowedGroups = get_option(Options, roster_groups_allowed),
+                                       {PresenceSubscription, RosterGroup} = get_roster_info(
+                                                                               To#jid.luser, To#jid.lserver, JID, AllowedGroups),
+                                       Features = case catch mod_caps:get_features(Host, mod_caps:read_caps(element(4, Packet))) of
+                                                      F when is_list(F) -> F;
+                                                      _ -> []
+                                                  end,
+                                       case lists:member(Node ++ "+notify", Features) of
+                                           true ->
+                                               MaySubscribe =
+                                                   case AccessModel of
+                                                       open -> true;
+                                                       presence -> PresenceSubscription;
+                                                       whitelist -> false; % subscribers are added manually
+                                                       authorize -> false; % likewise
+                                                       roster -> RosterGroup
+                                                   end,
+                                               if MaySubscribe ->
+                                                       send_last_item(
+                                                         Host, Node, JID);
+                                                  true ->
+                                                       ok
+                                               end;
+                                           false ->
+                                               ok
+                                       end;
+                                   true ->
+                                       ok
+                               end;
+                          (_) ->
+                               ok
+                       end, Subscriptions)
+             end, Plugins),
+           {noreply, State};
+       true ->
+           {noreply, State}
+    end;
+
+handle_cast({set_presence, _User, _Server, _Resource, _Presence}, State) ->
+    {noreply, State};
+handle_cast({unset_presence, _User, _Server, _Resource, _Status}, State) ->
+    {noreply, State};
+%      Owner = jlib:jid_tolower(jlib:jid_remove_resource(#jid{luser = User, lserver = Server, lresource = Resource})),
+%      JID = User ++ "@" ++ Server ++ "/" ++ Resource,
+%      F = fun() ->
+%              case mnesia:dirty_match_object(#pubsub_state{stateid = {Owner, '_'}, _ = '_'}) of
+%              [] ->
+%                      {error, ?ERR_ITEM_NOT_FOUND};
+%              States ->
+%                      {result, lists:foldl(
+%                              fun(#pubsub_state{stateid = {_, {Host, Node}}, items = Items} = S, ItemsList) ->
+%                                      %% To make this code work for nodetree_default system, we must be sure node type is correct
+%                                      %% this can be done asking a match {Host, Node} when Type=this_module_type
+%                                      %% case mnesia:dirty_match_object(#pubsub_node{nodeid={Host, Node}, type=virtual, _ = '_'})
+%                                      %% this call should be handled by the nodetree plugin
+%                                      %% but at this stage we don't know what is the actual nodetree plugin
+%                                      %% this must be implemented into the API, mod_pubsub telling us what is the nodetree plugin
+%                                      NewItems = lists:filter(fun(ItemID) -> ItemID /= JID end, Items),
+%                                      if NewItems /= Items ->
+%                                              mnesia:dirty_write(S#pubsub_state{items = NewItems}),
+%                                              Key = {JID, {Host, Node}},
+%                                              mnesia:dirty_delete({pubsub_item, Key}),
+%                                              ItemsList ++ [Key];
+%                                      true ->
+%                                              ItemsList
+%                                      end
+%                              end, [], States)}
+%              end
+%      end,
+%      case catch mnesia:sync_dirty(F) of
+%      {'EXIT', Reason} ->
+%              {error, Reason};
+%      {error, Reason} ->
+%              {error, Reason};
+%      {result, Items} ->
+%              lists:foreach(fun({ItemID, {Host, Node}}) ->
+%                              mod_pubsub:broadcast_retract_item(Host, Node, ItemID)
+%                      end, Items),
+%              {result, []};
+%      _ ->
+%              {error, ?ERR_INTERNAL_SERVER_ERROR}
+%      end.
+
 handle_cast(_Msg, State) ->
     {noreply, State}.
 
@@ -163,13 +552,14 @@ handle_cast(_Msg, State) ->
 %%                                       {stop, Reason, State}
 %% Description: Handling all non call/cast messages
 %%--------------------------------------------------------------------
-handle_info({route, From, To, Packet}, 
-#state{server_host = ServerHost, access = Access} = State) ->
-    case catch do_route(To#jid.lserver, ServerHost, Access, From, To, Packet) of
-       {'EXIT', Reason} ->
-           ?ERROR_MSG("~p", [Reason]);
-       _ ->
-           ok
+%% @private
+handle_info({route, From, To, Packet},
+           #state{server_host = ServerHost,
+                  access = Access,
+                  plugins = Plugins} = State) ->
+    case catch do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, Packet) of
+       {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+       _ -> ok
     end,
     {noreply, State};
 handle_info(_Info, State) ->
@@ -182,87 +572,108 @@ handle_info(_Info, State) ->
 %% cleaning up. When it returns, the gen_server terminates with Reason.
 %% The return value is ignored.
 %%--------------------------------------------------------------------
-terminate(_Reason, State) ->
-    ejabberd_router:unregister_route(State#state.host),
+%% @private
+terminate(_Reason, #state{host = Host,
+                         server_host = ServerHost,
+                         nodetree = TreePlugin,
+                         plugins = Plugins}) ->
+    terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
+    ejabberd_router:unregister_route(Host),
+    ejabberd_hooks:delete(disco_local_identity, ServerHost,
+                         ?MODULE, disco_local_identity, 75),
+    ejabberd_hooks:delete(disco_sm_identity, ServerHost,
+                         ?MODULE, disco_sm_identity, 75),
+    ejabberd_hooks:delete(disco_sm_features, ServerHost,
+                         ?MODULE, disco_sm_features, 75),
+    ejabberd_hooks:delete(disco_sm_items, ServerHost,
+                         ?MODULE, disco_sm_items, 75),
+    ejabberd_hooks:delete(incoming_presence_hook, ServerHost,
+                         ?MODULE, incoming_presence, 50),
+    %%ejabberd_hooks:delete(set_presence_hook, ServerHost, ?MODULE, set_presence, 50),
+    %%ejabberd_hooks:delete(unset_presence_hook, ServerHost, ?MODULE, unset_presence, 50),
+    lists:foreach(fun({NS,Mod}) ->
+                         gen_iq_handler:remove_iq_handler(Mod, ServerHost, NS)
+                 end, [{?NS_PUBSUB, ejabberd_local},
+                       {?NS_PUBSUB_OWNER, ejabberd_local},
+                       {?NS_PUBSUB, ejabberd_sm},
+                       {?NS_PUBSUB_OWNER, ejabberd_sm}]),
+    mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB),
     ok.
 
 %%--------------------------------------------------------------------
 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
 %% Description: Convert process state when code is changed
 %%--------------------------------------------------------------------
+%% @private
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
 %%--------------------------------------------------------------------
 %%% Internal functions
 %%--------------------------------------------------------------------
-do_route(Host, ServerHost, Access, From, To, Packet) ->
-    {xmlelement, Name, Attrs, Els} = Packet,
+do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
+    {xmlelement, Name, Attrs, _Els} = Packet,
     case To of
        #jid{luser = "", lresource = ""} ->
            case Name of
                "iq" ->
                    case jlib:iq_query_info(Packet) of
-                       #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
+                       #iq{type = get, xmlns = ?NS_DISCO_INFO,
                            sub_el = SubEl, lang = Lang} = IQ ->
                            {xmlelement, _, QAttrs, _} = SubEl,
                            Node = xml:get_attr_s("node", QAttrs),
-                           Res = IQ#iq{type = result,
-                                       sub_el = [{xmlelement, "query",
-                                                  QAttrs,
-                                                  iq_disco_info(Node, Lang)}]},
-                           ejabberd_router:route(To,
-                                                 From,
-                                                 jlib:iq_to_xml(Res));
-                       #iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS,
+                           Res = case iq_disco_info(Host, Node, From, Lang) of
+                                     {result, IQRes} ->
+                                         jlib:iq_to_xml(
+                                           IQ#iq{type = result,
+                                                 sub_el = [{xmlelement, "query",
+                                                            QAttrs, IQRes}]});
+                                     {error, Error} ->
+                                         jlib:make_error_reply(Packet, Error)
+                                 end,
+                           ejabberd_router:route(To, From, Res);
+                       #iq{type = get, xmlns = ?NS_DISCO_ITEMS,
                            sub_el = SubEl} = IQ ->
                            {xmlelement, _, QAttrs, _} = SubEl,
                            Node = xml:get_attr_s("node", QAttrs),
-                           Res =
-                               case iq_disco_items(Host, From, Node) of
-                                   {result, IQRes} ->
-                                       jlib:iq_to_xml(
-                                         IQ#iq{type = result,
-                                               sub_el = [{xmlelement, "query",
-                                                          QAttrs,
-                                                          IQRes}]});
-                                   {error, Error} ->
-                                       jlib:make_error_reply(
-                                         Packet, Error)
-                               end,
+                           Res = case iq_disco_items(Host, Node, From) of
+                                     {result, IQRes} ->
+                                         jlib:iq_to_xml(
+                                           IQ#iq{type = result,
+                                                 sub_el = [{xmlelement, "query",
+                                                            QAttrs, IQRes}]});
+                                     {error, Error} ->
+                                         jlib:make_error_reply(Packet, Error)
+                                 end,
                            ejabberd_router:route(To, From, Res);
-                       #iq{type = Type, xmlns = ?NS_PUBSUB = XMLNS,
-                           sub_el = SubEl} = IQ ->
+                       #iq{type = IQType, xmlns = ?NS_PUBSUB,
+                           lang = Lang, sub_el = SubEl} = IQ ->
                            Res =
-                               case iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) of
+                               case iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) of
                                    {result, IQRes} ->
                                        jlib:iq_to_xml(
                                          IQ#iq{type = result,
                                                sub_el = IQRes});
                                    {error, Error} ->
-                                       jlib:make_error_reply(
-                                         Packet, Error)
+                                       jlib:make_error_reply(Packet, Error)
                                end,
                            ejabberd_router:route(To, From, Res);
-                       #iq{type = Type, xmlns = ?NS_PUBSUB_OWNER = XMLNS,
+                       #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER,
                            lang = Lang, sub_el = SubEl} = IQ ->
                            Res =
-                               case iq_pubsub_owner(
-                                      Host, From, Type, Lang, SubEl) of
+                               case iq_pubsub_owner(Host, From, IQType, SubEl, Lang) of
                                    {result, IQRes} ->
                                        jlib:iq_to_xml(
                                          IQ#iq{type = result,
                                                sub_el = IQRes});
                                    {error, Error} ->
-                                       jlib:make_error_reply(
-                                         Packet, Error)
+                                       jlib:make_error_reply(Packet, Error)
                                end,
                            ejabberd_router:route(To, From, Res);
                        #iq{type = get, xmlns = ?NS_VCARD = XMLNS,
-                           lang = Lang, sub_el = SubEl} = IQ ->
+                           lang = Lang, sub_el = _SubEl} = IQ ->
                            Res = IQ#iq{type = result,
-                                       sub_el = [{xmlelement, "vCard",
-                                                  [{"xmlns", XMLNS}],
+                                       sub_el = [{xmlelement, "vCard", [{"xmlns", XMLNS}],
                                                   iq_get_vcard(Lang)}]},
                            ejabberd_router:route(To,
                                                  From,
@@ -276,14 +687,23 @@ do_route(Host, ServerHost, Access, From, To, Packet) ->
                            ok
                    end;
                "presence" ->
-                   Type = xml:get_attr_s("type", Attrs),
-                   if
-                       (Type == "unavailable") or (Type == "error") ->
-                mnesia:dirty_delete(pubsub_presence, {Host, From#jid.luser, From#jid.lserver});
-                       true ->
-                mnesia:dirty_write(#pubsub_presence{key={Host, From#jid.luser, From#jid.lserver}, presence=[]})
-                   end,
+                   incoming_presence(From, To, Packet),
                    ok;
+               "message" ->
+                   case xml:get_attr_s("type", Attrs) of
+                       "error" ->
+                           ok;
+                       _ ->
+                           case find_authorization_response(Packet) of
+                               none ->
+                                   ok;
+                               invalid ->
+                                   ejabberd_router:route(To, From,
+                                                         jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST));
+                               XFields ->
+                                   handle_authorization_response(Host, From, To, Packet, XFields)
+                           end
+                   end;
                _ ->
                    ok
            end;
@@ -294,173 +714,412 @@ do_route(Host, ServerHost, Access, From, To, Packet) ->
                "result" ->
                    ok;
                _ ->
-                   Err = jlib:make_error_reply(
-                           Packet, ?ERR_ITEM_NOT_FOUND),
+                   Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
                    ejabberd_router:route(To, From, Err)
            end
     end.
 
+node_disco_info(Host, Node, From) ->
+    node_disco_info(Host, Node, From, true, true).
+node_disco_identity(Host, Node, From) ->
+    node_disco_info(Host, Node, From, true, false).
+node_disco_features(Host, Node, From) ->
+    node_disco_info(Host, Node, From, false, true).
+node_disco_info(Host, Node, From, Identity, Features) ->
+    Action =
+       fun(#pubsub_node{type = Type}) ->
+               I = case Identity of
+                       false ->
+                           [];
+                       true ->
+                           Types =
+                               case tree_call(Host, get_subnodes, [Host, Node]) of
+                                   [] ->
+                                       ["leaf"]; %% No sub-nodes: it's a leaf node
+                                   _ ->
+                                       case node_call(Type, get_items, [Host, Node]) of
+                                           {result, []} -> ["collection"];
+                                           {result, _} -> ["leaf", "collection"];
+                                           _ -> []
+                                       end
+                               end,
+                           lists:map(fun(T) ->
+                                             {xmlelement, "identity", [{"category", "pubsub"},
+                                                                       {"type", T}], []}
+                                     end, Types)
+                   end,
+               F = case Features of
+                       false ->
+                           [];
+                       true ->
+                           [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
+                            lists:map(fun(T) ->
+                                              {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
+                                      end, features(Type))]
+                   end,
+               %% TODO: add meta-data info (spec section 5.4)
+               {result, I ++ F}
+       end,
+    transaction(Host, Node, Action, sync_dirty).
 
-
-node_to_string(Node) ->
-    string:strip(lists:flatten(lists:map(fun(S) -> [S, "/"] end, Node)),
-                right, $/).
-
-
-iq_disco_info(SNode, Lang) ->
-    Node = string:tokens(SNode, "/"),
+iq_disco_info(Host, SNode, From, Lang) ->
+    Node = string_to_node(SNode),
     case Node of
        [] ->
-           [{xmlelement, "identity",
-             [{"category", "pubsub"},
-              {"type", "generic"},
-              {"name", translate:translate(Lang, "Publish-Subscribe")}], []},
-            {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
-            {xmlelement, "feature", [{"var", ?NS_PUBSUB_EVENT}], []},
-            {xmlelement, "feature", [{"var", ?NS_PUBSUB_OWNER}], []},
-            {xmlelement, "feature", [{"var", ?NS_VCARD}], []}];
+           {result,
+            [{xmlelement, "identity",
+              [{"category", "pubsub"},
+               {"type", "service"},
+               {"name", translate:translate(Lang, "Publish-Subscribe")}], []},
+             {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
+             {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
+            lists:map(fun(Feature) ->
+                              {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
+                      end, features(Host, Node))};
        _ ->
-           % TODO
-           []
+           node_disco_info(Host, Node, From)
     end.
 
-iq_disco_items(Host, From, SNode) ->
-       {Node,ItemID} = case SNode of
-       [] ->
-               {[],none};
-       _ ->
-               Tokens = string:tokens(SNode, "!"),
-               NodeList = string:tokens(lists:nth(1, Tokens), "/"),
-               ItemName = case length(Tokens) of
-               2 -> lists:nth(2, Tokens);
-               _ -> none
+iq_disco_items(Host, [], _From) ->
+    {result, lists:map(
+              fun(#pubsub_node{nodeid = {_, SubNode}}) ->
+                      SN = node_to_string(SubNode),
+                      RN = lists:last(SubNode),
+                      %% remove name attribute
+                      {xmlelement, "item", [{"jid", Host},
+                                            {"node", SN},
+                                            {"name", RN}], []}
+              end, tree_action(Host, get_subnodes, [Host, []]))};
+iq_disco_items(Host, Item, _From) ->
+    case string:tokens(Item, "!") of
+       [_SNode, _ItemID] ->
+           {result, []};
+       [SNode] ->
+           Node = string_to_node(SNode),
+           %% Note: Multiple Node Discovery not supported (mask on pubsub#type)
+           %% TODO this code is also back-compatible with pubsub v1.8 (for client issue)
+           %% TODO make it pubsub v1.10 compliant (this breaks client compatibility)
+           %% TODO That is, remove name attribute
+           Action =
+               fun(#pubsub_node{type = Type}) ->
+                       NodeItems = case node_call(Type, get_items, [Host, Node]) of
+                                       {result, I} -> I;
+                                       _ -> []
+                                   end,
+                       Nodes = lists:map(
+                                 fun(#pubsub_node{nodeid = {_, SubNode}}) ->
+                                         SN = node_to_string(SubNode),
+                                         RN = lists:last(SubNode),
+                                         {xmlelement, "item", [{"jid", Host}, {"node", SN}, {"name", RN}], []}
+                                 end, tree_call(Host, get_subnodes, [Host, Node])),
+                       Items = lists:map(
+                                 fun(#pubsub_item{itemid = {RN, _}}) ->
+                                         SN = node_to_string(Node) ++ "!" ++ RN,
+                                         {xmlelement, "item", [{"jid", Host}, {"node", SN}, {"name", RN}], []}
+                                 end, NodeItems),
+                       {result, Nodes ++ Items}
                end,
-               {NodeList, ItemName}
-       end,
-       NodeFull = string:tokens(SNode,"/"),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info}] ->
-                       case ItemID of
-                       none ->
-                               SubNodes = mnesia:index_read(pubsub_node,
-                                                    {Host, Node},
-                                                    #pubsub_node.host_parent),
-                               SubItems = lists:map(fun(#pubsub_node{host_node = {_, N}}) ->
-                                             SN = node_to_string(N),
-                                             {xmlelement, "item",
-                                              [{"jid", Host},
-                                               {"node", SN},
-                                               {"name", lists:last(N)}], []}
-                                     end, SubNodes),
-                               SN = node_to_string(Node),
-                               Items = lists:map(fun(#item{id = Name}) ->
-                                               RealName = case Name of
-                                               [] -> "item";
-                                               _ -> Name
-                                               end,
-                                             {xmlelement, "item",
-                                              [{"jid", Host},
-                                               {"node", SN ++ "!" ++ Name},
-                                               {"name", RealName}], []}
-                                     end, Info#nodeinfo.items),
-                               SubItems ++ Items;
-                       _ ->
-                               []
-                       end;
-                   [] ->
-                       case Node of
-                           [] ->
-                               SubNodes = mnesia:index_read(
-                                            pubsub_node,
-                                            {Host, Node},
-                                            #pubsub_node.host_parent),
-                               lists:map(
-                                 fun(#pubsub_node{host_node = {_, N}}) ->
-                                         SN = node_to_string(N),
-                                         {xmlelement, "item",
-                                          [{"jid", Host},
-                                           {"node", SN},
-                                           {"name", lists:last(N)}],
-                                          []}
-                                 end, SubNodes) ;
-                           _ ->
-                               {error, ?ERR_ITEM_NOT_FOUND}
-                       end
-               end
-       end,
-    case mnesia:transaction(F) of
-       {atomic, {error, _} = Error} ->
-           Error;
-       {atomic, Res} ->
-           {result, Res};
-       _ ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR}
+           transaction(Host, Node, Action, sync_dirty)
     end.
 
-iq_get_vcard(Lang) ->
-    [{xmlelement, "FN", [],
-      [{xmlcdata, "ejabberd/mod_pubsub"}]},
-     {xmlelement, "URL", [],
-      [{xmlcdata, ?EJABBERD_URI}]},
-     {xmlelement, "DESC", [],
-      [{xmlcdata, translate:translate(
-                   Lang,
-                   "ejabberd pub/sub module\n"
-                   "Copyright (c) 2003-2007 Alexey Shchepin")}]}].
+iq_local(From, To, #iq{type = Type,
+                      sub_el = SubEl,
+                      xmlns = XMLNS,
+                      lang = Lang} = IQ) ->
+    ServerHost = To#jid.lserver,
+    %% Accept IQs to server only from our own users.
+    if
+       From#jid.lserver /= ServerHost ->
+           IQ#iq{type = error, sub_el = [?ERR_FORBIDDEN, SubEl]};
+       true ->
+           LOwner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
+           Res = case XMLNS of
+                     ?NS_PUBSUB -> iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
+                     ?NS_PUBSUB_OWNER -> iq_pubsub_owner(LOwner, From, Type, SubEl, Lang)
+                 end,
+           case Res of
+               {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
+               {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]}
+           end
+    end.
 
+iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
+    ServerHost = To#jid.lserver,
+    LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
+    Res = case XMLNS of
+             ?NS_PUBSUB -> iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
+             ?NS_PUBSUB_OWNER -> iq_pubsub_owner(LOwner, From, Type, SubEl, Lang)
+         end,
+    case Res of
+       {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes};
+       {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]}
+    end.
 
-iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) ->
+iq_get_vcard(Lang) ->
+    [{xmlelement, "FN", [], [{xmlcdata, "ejabberd/mod_pubsub"}]},
+     {xmlelement, "URL", [], [{xmlcdata, "http://www.process-one.net/en/projects/ejabberd/"}]},
+     {xmlelement, "DESC", [],
+      [{xmlcdata,
+       translate:translate(Lang,
+                           "ejabberd Publish-Subscribe module\n"
+                           "Copyright (c) 2004-2007 Process-One")}]}].
+
+iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
+    Plugins = case ets:lookup(gen_mod:get_module_proc(ServerHost, pubsub_state), plugins) of
+                 [{plugins, PL}] -> PL;
+                 _ -> [?STDNODE]
+             end,
+    iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, Plugins).
+
+iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) ->
     {xmlelement, _, _, SubEls} = SubEl,
-    case xml:remove_cdata(SubEls) of
+    WithoutCdata = xml:remove_cdata(SubEls),
+    Configuration = lists:filter(fun({xmlelement, Name, _, _}) ->
+                                        Name == "configure"
+                                end, WithoutCdata),
+    Action = WithoutCdata -- Configuration,
+    case Action of
        [{xmlelement, Name, Attrs, Els}] ->
-           SNode = xml:get_attr_s("node", Attrs),
-           Node = string:tokens(SNode, "/"),
-           case {Type, Name} of
+           Node = case Host of
+                      {_, _, _} -> xml:get_attr_s("node", Attrs);
+                      _ -> string_to_node(xml:get_attr_s("node", Attrs))
+                  end,
+           case {IQType, Name} of
                {set, "create"} ->
-                   create_new_node(Host, Node, From, ServerHost, Access);
+                   case Configuration of
+                       [{xmlelement, "configure", _, Config}] ->
+                           %% Get the type of the node
+                           Type = case xml:get_attr_s("type", Attrs) of
+                                      [] -> ?STDNODE;
+                                      T -> T
+                                  end,
+                           %% we use Plugins list matching because we do not want to allocate
+                           %% atoms for non existing type, this prevent atom allocation overflow
+                           case lists:member(Type, Plugins) of
+                               false ->
+                                   {error, extended_error(
+                                             ?ERR_FEATURE_NOT_IMPLEMENTED,
+                                             unsupported, "create-nodes")};
+                               true ->
+                                   create_node(Host, ServerHost, Node, From,
+                                               Type, Access, Config)
+                           end;
+                       _ ->
+                           %% this breaks backward compatibility!
+                           %% can not create node without <configure/>
+                           %% but this is the new spec anyway
+                           ?INFO_MSG("Node ~p ; invalid configuration: ~p", [Node, Configuration]),
+                           {error, ?ERR_BAD_REQUEST}
+                   end;
                {set, "publish"} ->
                    case xml:remove_cdata(Els) of
                        [{xmlelement, "item", ItemAttrs, Payload}] ->
-                           ItemID = xml:get_attr_s("id", ItemAttrs),
-                           publish_item(Host, From, Node, ItemID, Payload);
+                           ItemId = xml:get_attr_s("id", ItemAttrs),
+                           publish_item(Host, ServerHost, Node, From, ItemId, Payload);
+                       [] ->
+                           %% Publisher attempts to publish to persistent node with no item
+                           {error, extended_error(?ERR_BAD_REQUEST,
+                                                  "item-required")};
                        _ ->
-                           {error, ?ERR_BAD_REQUEST}
+                           %% Entity attempts to publish item with multiple payload elements or namespace does not match
+                           {error, extended_error(?ERR_BAD_REQUEST,
+                                                  "invalid-payload")}
                    end;
                {set, "retract"} ->
+                   ForceNotify = case xml:get_attr_s("notify", Attrs) of
+                                     "1" -> true;
+                                     "true" -> true;
+                                     _ -> false
+                                 end,
                    case xml:remove_cdata(Els) of
                        [{xmlelement, "item", ItemAttrs, _}] ->
-                           ItemID = xml:get_attr_s("id", ItemAttrs),
-                           delete_item(Host, From, Node, ItemID);
+                           ItemId = xml:get_attr_s("id", ItemAttrs),
+                           delete_item(Host, Node, From, ItemId, ForceNotify);
                        _ ->
-                           {error, ?ERR_BAD_REQUEST}
+                           %% Request does not specify an item
+                           {error, extended_error(?ERR_BAD_REQUEST,
+                                                  "item-required")}
                    end;
                {set, "subscribe"} ->
                    JID = xml:get_attr_s("jid", Attrs),
-                   subscribe_node(Host, From, JID, Node);
+                   subscribe_node(Host, Node, From, JID);
                {set, "unsubscribe"} ->
                    JID = xml:get_attr_s("jid", Attrs),
-                   unsubscribe_node(Host, From, JID, Node);
+                   SubId = xml:get_attr_s("subid", Attrs),
+                   unsubscribe_node(Host, Node, From, JID, SubId);
                {get, "items"} ->
                    MaxItems = xml:get_attr_s("max_items", Attrs),
-                   get_items(Host, From, Node, MaxItems);
+                   get_items(Host, Node, From, MaxItems);
+               {get, "subscriptions"} ->
+                   get_subscriptions(Host, From, Plugins);
+               {get, "affiliations"} ->
+                   get_affiliations(Host, From, Plugins);
+               _ ->
+                   {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+           end;
+       _ ->
+           ?INFO_MSG("Too many actions: ~p", [Action]),
+           {error, ?ERR_BAD_REQUEST}
+    end.
+
+iq_pubsub_owner(Host, From, IQType, SubEl, Lang) ->
+    {xmlelement, _, _, SubEls} = SubEl,
+    Action = xml:remove_cdata(SubEls),
+    case Action of
+       [{xmlelement, Name, Attrs, Els}] ->
+           Node = case Host of
+                      {_, _, _} -> xml:get_attr_s("node", Attrs);
+                      _ -> string_to_node(xml:get_attr_s("node", Attrs))
+                  end,
+           case {IQType, Name} of
+               {get, "configure"} ->
+                   get_configure(Host, Node, From, Lang);
+               {set, "configure"} ->
+                   set_configure(Host, Node, From, Els, Lang);
+               {get, "default"} ->
+                   get_default(Host, Node, From, Lang);
                {set, "delete"} ->
-                   delete_node(Host, From, Node);
+                   delete_node(Host, Node, From);
                {set, "purge"} ->
-                   purge_node(Host, From, Node);
-               {get, "entities"} ->
-                   get_entities(Host, From, Node);
-               {set, "entities"} ->
-                   set_entities(Host, From, Node, xml:remove_cdata(Els));
+                   purge_node(Host, Node, From);
+               {get, "subscriptions"} ->
+                   get_subscriptions(Host, Node, From);
+               {set, "subscriptions"} ->
+                   set_subscriptions(Host, Node, From, xml:remove_cdata(Els));
                {get, "affiliations"} ->
-                   get_affiliations(Host, From);
+                   get_affiliations(Host, Node, From);
+               {set, "affiliations"} ->
+                   set_affiliations(Host, Node, From, xml:remove_cdata(Els));
                _ ->
                    {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
            end;
        _ ->
+           ?INFO_MSG("Too many actions: ~p", [Action]),
            {error, ?ERR_BAD_REQUEST}
     end.
 
+%%% authorization handling
+
+send_authorization_request(Host, Node, Subscriber) ->
+    Lang = "en", %% TODO fix
+    Stanza = {xmlelement, "message",
+             [],
+             [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
+               [{xmlelement, "title", [],
+                 [{xmlcdata, translate:translate(Lang, "PubSub subscriber request")}]},
+                {xmlelement, "instructions", [],
+                 [{xmlcdata, translate:translate(Lang, "Choose whether to approve this entity's subscription.")}]},
+                {xmlelement, "field",
+                 [{"var", "FORM_TYPE"}, {"type", "hidden"}],
+                 [{xmlelement, "value", [], [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]},
+                {xmlelement, "field",
+                 [{"var", "pubsub#node"}, {"type", "text-single"},
+                  {"label", translate:translate(Lang, "Node ID")}],
+                 [{xmlelement, "value", [],
+                   [{xmlcdata, node_to_string(Node)}]}]},
+                {xmlelement, "field", [{"var", "pubsub#subscriber_jid"},
+                                       {"type", "jid-single"},
+                                       {"label", translate:translate(Lang, "Subscriber Address")}],
+                 [{xmlelement, "value", [],
+                   [{xmlcdata, jlib:jid_to_string(Subscriber)}]}]},
+                {xmlelement, "field",
+                 [{"var", "pubsub#allow"},
+                  {"type", "boolean"},
+                  {"label", translate:translate(Lang, "Allow this JID to subscribe to this pubsub node?")}],
+                 [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
+    case tree_action(Host, get_node, [Host, Node]) of
+       #pubsub_node{owners = Owners} ->
+           lists:foreach(
+             fun(Owner) ->
+                     ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(Owner),
+                                           Stanza)
+             end, Owners),
+           ok;
+       _ ->
+           ok
+    end.
+
+find_authorization_response(Packet) ->
+    {xmlelement, _Name, _Attrs, Els} = Packet,
+    XData1 = lists:map(fun({xmlelement, "x", XAttrs, _} = XEl) ->
+                              case xml:get_attr_s("xmlns", XAttrs) of
+                                  ?NS_XDATA ->
+                                      case xml:get_attr_s("type", XAttrs) of
+                                          "cancel" ->
+                                              none;
+                                          _ ->
+                                              jlib:parse_xdata_submit(XEl)
+                                      end;
+                                  _ ->
+                                      none
+                              end;
+                         (_) ->
+                              none
+                      end, xml:remove_cdata(Els)),
+    XData = lists:filter(fun(E) -> E /= none end, XData1),
+    case XData of
+       [invalid] -> invalid;
+       [] -> none;
+       [XFields] when is_list(XFields) ->
+           case lists:keysearch("FORM_TYPE", 1, XFields) of
+               {value, {_, ?NS_PUBSUB_SUB_AUTH}} ->
+                   XFields;
+               _ ->
+                   invalid
+           end
+    end.
+
+handle_authorization_response(Host, From, To, Packet, XFields) ->
+    case {lists:keysearch("pubsub#node", 1, XFields),
+         lists:keysearch("pubsub#subscriber_jid", 1, XFields),
+         lists:keysearch("pubsub#allow", 1, XFields)} of
+       {{value, {_, SNode}}, {value, {_, SSubscriber}},
+        {value, {_, SAllow}}} ->
+           Node = case Host of
+                      {_, _, _} -> [SNode];
+                      _ -> string:tokens(SNode, "/")
+                  end,
+           Subscriber = jlib:string_to_jid(SSubscriber),
+           Allow = case SAllow of
+                       "1" -> true;
+                       "true" -> true;
+                       _ -> false
+                   end,
+           Action = fun(#pubsub_node{type = Type,
+                                     options = Options,
+                                     owners = Owners}) ->
+                            IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners),
+                            Subscription = node_call(Type, get_subscription, [Host, Node, Subscriber]),
+                            if
+                                not IsApprover ->
+                                    {error, ?ERR_FORBIDDEN};
+                                Subscription /= pending ->
+                                    {error, ?ERR_UNEXPECTED_REQUEST};
+                                true ->
+                                    NewSubscription = case Allow of
+                                                          true -> subscribed;
+                                                          false -> none
+                                                      end,
+                                    node_call(Type, set_subscription, [Host, Node, Subscriber, NewSubscription])
+                            end
+                    end,
+           case transaction(Host, Node, Action, sync_dirty) of
+               {error, Error} ->
+                   ejabberd_router:route(To, From,
+                                         jlib:make_error_reply(Packet, Error));
+               {result, _NewSubscription} ->
+                   %% XXX: notify about subscription state change, section 12.11
+                   ok;
+               _ ->
+                   ejabberd_router:route(
+                     To, From,
+                     jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR))
+           end;
+       _ ->
+           ejabberd_router:route(
+             To, From,
+             jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE))
+    end.
 
 -define(XFIELD(Type, Label, Var, Val),
        {xmlelement, "field", [{"type", Type},
@@ -492,430 +1151,669 @@ iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) ->
 -define(LISTXFIELD(Label, Var, Val, Opts),
        ?XFIELDOPT("list-single", Label, Var, Val, Opts)).
 
-
-
-%% Create new pubsub nodes
-%% This function is used during init to create the first bootstrap nodes
-create_new_node(Host, Node, Owner) ->
-    %% This is the case use during "bootstrapping to create the initial
-    %% hierarchy. Should always be ... undefined,all
-    create_new_node(Host, Node, Owner, undefined, all).
-create_new_node(Host, Node, Owner, ServerHost, Access) ->
-    case Node of
-       [] ->
+%% @spec (Host::host(), ServerHost::host(), Node::pubsubNode(), Owner::jid(), NodeType::nodeType()) ->
+%%               {error, Reason::stanzaError()} |
+%%               {result, []}
+%% @doc <p>Create new pubsub nodes</p>
+%%<p>In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:</p>
+%%<ul>
+%%<li>The service does not support node creation.
+%%<li>Only entities that are registered with the service are allowed to create nodes but the requesting entity is not registered.
+%%<li>The requesting entity does not have sufficient privileges to create nodes.
+%%<li>The requested NodeID already exists.
+%%<li>The request did not include a NodeID and "instant nodes" are not supported.
+%%</ul>
+%%<p>ote: node creation is a particular case, error return code is evaluated at many places:</p>
+%%<ul>
+%%<li>iq_pubsub checks if service supports node creation (type exists)
+%%<li>create_node checks if instant nodes are supported
+%%<li>create_node asks node plugin if entity have sufficient privilege
+%%<li>nodetree create_node checks if nodeid already exists
+%%<li>node plugin create_node just sets default affiliation/subscription
+%%</ul>
+create_node(Host, ServerHost, Node, Owner, Type) ->
+    create_node(Host, ServerHost, Node, Owner, Type, all, []).
+
+create_node(Host, ServerHost, [], Owner, Type, Access, Configuration) ->
+    case lists:member("instant-nodes", features(Type)) of
+       true ->
            {LOU, LOS, _} = jlib:jid_tolower(Owner),
            HomeNode = ["home", LOS, LOU],
-           create_new_node(Host, HomeNode, Owner, ServerHost, Access),
-           NewNode = ["home", LOS, LOU, randoms:get_string()],
-           create_new_node(Host, NewNode, Owner, ServerHost, Access);
-       _ ->
-           LOwner = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
-           Parent = lists:sublist(Node, length(Node) - 1),
-           F = fun() ->
-                       ParentExists = (Parent == []) orelse
-                           case mnesia:read({pubsub_node, {Host, Parent}}) of
-                               [_] ->
-                                   true;
-                               [] ->
-                                   false
-                           end,
-                       case ParentExists of
-                           false ->
-                               {error, ?ERR_CONFLICT};
+           create_node(Host, ServerHost,
+                       HomeNode, Owner, Type, Access, Configuration),
+           NewNode = HomeNode ++ [randoms:get_string()],
+           case create_node(Host, ServerHost,
+                            NewNode, Owner, Type, Access, Configuration) of
+               {result, []} ->
+                   {result,
+                    [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
+                      [{xmlelement, "create", [{"node", node_to_string(NewNode)}], []}]}]};
+               Error -> Error
+           end;
+       false ->
+           %% Service does not support instant nodes
+           {error, extended_error(?ERR_NOT_ACCEPTABLE, "nodeid-required")}
+    end;
+create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
+    Type = case Host of
+              {_User, _Server, _Resource} -> "pep";
+              _ -> GivenType
+          end,
+    Parent = lists:sublist(Node, length(Node) - 1),
+    %% TODO, check/set node_type = Type
+    ParseOptions = case xml:remove_cdata(Configuration) of
+                      [] ->
+                          {result, node_options(Type)};
+                      [{xmlelement, "x", _Attrs, _SubEls} = XEl] ->
+                          case jlib:parse_xdata_submit(XEl) of
+                              invalid ->
+                                  {error, ?ERR_BAD_REQUEST};
+                              XData ->
+                                  case set_xoption(XData, node_options(Type)) of
+                                      NewOpts when is_list(NewOpts) ->
+                                          {result, NewOpts};
+                                      Err ->
+                                          Err
+                                  end
+                          end;
+                      _ ->
+                          ?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]),
+                          {error, ?ERR_BAD_REQUEST}
+                  end,
+    case ParseOptions of
+       {result, NodeOptions} ->
+           CreateNode =
+               fun() ->
+                       case node_call(Type, create_node_permission, [Host, ServerHost, Node, Parent, Owner, Access]) of
+                           {result, true} ->
+                               case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions]) of
+                                   ok ->
+                                       node_call(Type, create_node, [Host, Node, Owner]);
+                                   {error, ?ERR_CONFLICT} ->
+                                       case ets:lookup(gen_mod:get_module_proc(ServerHost, pubsub_state), nodetree) of
+                                           [{nodetree, nodetree_virtual}] -> node_call(Type, create_node, [Host, Node, Owner]);
+                                           _ -> {error, ?ERR_CONFLICT}
+                                       end;
+                                   Error ->
+                                       Error
+                               end;
                            _ ->
-                               case mnesia:read({pubsub_node, {Host, Node}}) of
-                                   [_] ->
-                                       {error, ?ERR_CONFLICT};
-                                   [] ->
-                                       Entities =
-                                           ?DICT:store(
-                                              LOwner,
-                                              #entity{affiliation = owner,
-                                                      subscription = none},
-                                              ?DICT:new()),
-                                       mnesia:write(
-                                         #pubsub_node{host_node = {Host, Node},
-                                                      host_parent = {Host, Parent},
-                                                      info = #nodeinfo{
-                                                        entities = Entities}}),
-                                       ok
-                               end
+                               {error, ?ERR_FORBIDDEN}
                        end
                end,
-           case check_create_permission(Host, Node, Owner, ServerHost, Access) of
-               true ->
-                   case mnesia:transaction(F) of
-                       {atomic, ok} ->
-                           Lang = "",
-                           broadcast_publish_item(
-                             Host, ["pubsub", "nodes"], node_to_string(Node),
-                             [{xmlelement, "x",
-                               [{"xmlns", ?NS_XDATA},
-                                {"type", "result"}],
-                               [?XFIELD("hidden", "", "FORM_TYPE",
-                                        ?NS_PUBSUB_NMI),
-                                ?XFIELD("jid-single", "Node Creator",
-                                        "creator",
-                                        jlib:jid_to_string(LOwner))]}]),
-                           {result,
-                            [{xmlelement, "pubsub",
-                               [{"xmlns", ?NS_PUBSUB}],
-                               [{xmlelement, "create",
-                                 [{"node", node_to_string(Node)}], []}]}]};
-                       {atomic, {error, _} = Error} ->
-                           Error;
-                       _ ->
-                           {error, ?ERR_INTERNAL_SERVER_ERROR}
+           Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
+                     [{xmlelement, "create", [{"node", node_to_string(Node)}],
+                       []}]}],
+           case transaction(CreateNode, transaction) of
+               {error, Error} ->
+                   %% in case we change transaction to sync_dirty...
+                   %%node_action:
+                   %%  node_call(Type, delete_node, [Host, Node]),
+                   %%  tree_call(Host, delete_node, [Host, Node]),
+                   {error, Error};
+               {result, {Result, broadcast}} ->
+                   %%Lang = "en", %% TODO: fix
+                   %%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+                   %%broadcast_publish_item(Host, Node, uniqid(), Owner,
+                   %%  [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
+                   %%          [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NMI),
+                   %%          ?XFIELD("jid-single", "Node Creator", "creator", jlib:jid_to_string(OwnerKey))]}]),
+                   %% todo publish_item(Host, ServerHost, ["pubsub", "nodes"], node_to_string(Node)),
+                   case Result of
+                       default -> {result, Reply};
+                       _ -> {result, Result}
                    end;
-               _ ->
-                   {error, ?ERR_NOT_ALLOWED}
-           end
+               {result, default} ->
+                   {result, Reply};
+               {result, Result} ->
+                   {result, Result}
+           end;
+       Error ->
+           Error
     end.
 
-
-publish_item(Host, JID, Node, ItemID, Payload) ->
-    ejabberd_hooks:run(pubsub_publish_item, Host,
-                      [JID, ?MYJID, Node, ItemID, Payload]),
-    Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       Affiliation = get_affiliation(Info, Publisher),
-                       Subscription = get_subscription(Info, Publisher),
-                       MaxSize = get_node_option(Info, max_payload_size),
-                       Model = get_node_option(Info, publish_model),
-                       Size = size(term_to_binary(Payload)),
-                       if
-                           ((Model == open) or
-                           ((Model == publishers) and
-                            ((Affiliation == owner) or
-                             (Affiliation == publisher))) or
-                           ((Model == subscribers) and
-                            (Subscription == subscribed))) and
-                           (Size =< MaxSize) ->
-                               NewInfo =
-                                   insert_item(Info, ItemID,
-                                               Publisher, Payload),
-                               mnesia:write(
-                                 N#pubsub_node{info = NewInfo}),
-                               {result, []};
-                           true ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    case mnesia:transaction(F) of
-       {atomic, {error, _} = Error} ->
-           Error;
-       {atomic, {result, Res}} ->
-           broadcast_publish_item(Host, Node, ItemID, Payload),
-           {result, Res};
-       _ ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR}
+%% @spec (Host, Node, Owner) ->
+%%                     {error, Reason} | {result, []}
+%%      Host = host()
+%%      Node = pubsubNode()
+%%      Owner = jid()
+%%      Reason = stanzaError()
+%% @doc <p>Delete specified node and all childs.</p>
+%%<p>There are several reasons why the node deletion request might fail:</p>
+%%<ul>
+%%<li>The requesting entity does not have sufficient privileges to delete the node.
+%%<li>The node is the root collection node, which cannot be deleted.
+%%<li>The specified node does not exist.
+%%</ul>
+delete_node(_Host, [], _Owner) ->
+    %% Node is the root
+    {error, ?ERR_NOT_ALLOWED};
+delete_node(Host, Node, Owner) ->
+    Action = fun(#pubsub_node{type = Type}) ->
+                    case node_call(Type, get_affiliation, [Host, Node, Owner]) of
+                        {result, owner} ->
+                            Removed = tree_call(Host, delete_node, [Host, Node]),
+                            node_call(Type, delete_node, [Host, Removed]);
+                        _ ->
+                            %% Entity is not an owner
+                            {error, ?ERR_FORBIDDEN}
+                    end
+            end,
+    Reply = [],
+    case transaction(Host, Node, Action, transaction) of
+       {error, Error} ->
+           {error, Error};
+       {result, {Result, broadcast, Removed}} ->
+           broadcast_removed_node(Host, Removed),
+           %%broadcast_retract_item(Host, ["pubsub", "nodes"], node_to_string(Node)),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
+           end;
+       {result, {Result, Removed}} ->
+           broadcast_removed_node(Host, Removed),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
+           end;
+       {result, default} ->
+           {result, Reply};
+       {result, Result} ->
+           {result, Result}
     end.
 
-
-delete_item(Host, JID, Node, ItemID) ->
-    Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       case check_item_publisher(Info, ItemID, Publisher)
-                           orelse
-                           (get_affiliation(Info, Publisher) == owner) of
-                           true ->
-                               NewInfo =
-                                   remove_item(Info, ItemID),
-                               mnesia:write(
-                                 N#pubsub_node{info = NewInfo}),
-                               {result, []};
-                           _ ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    case mnesia:transaction(F) of
-       {atomic, {error, _} = Error} ->
-           Error;
-       {atomic, {result, Res}} ->
-           broadcast_retract_item(Host, Node, ItemID),
-           {result, Res};
-       _ ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR}
+%% @spec (Host, Node, From, JID) ->
+%%               {error, Reason::stanzaError()} |
+%%               {result, []}
+%%      Host = host()
+%%      Node = pubsubNode()
+%%      From = jid()
+%%      JID = jid()
+%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
+%% @see node_default:subscribe_node/5
+%%<p>There are several reasons why the subscription request might fail:</p>
+%%<ul>
+%%<li>The bare JID portions of the JIDs do not match.
+%%<li>The node has an access model of "presence" and the requesting entity is not subscribed to the owner's presence.
+%%<li>The node has an access model of "roster" and the requesting entity is not in one of the authorized roster groups.
+%%<li>The node has an access model of "whitelist" and the requesting entity is not on the whitelist.
+%%<li>The service requires payment for subscriptions to the node.
+%%<li>The requesting entity is anonymous and the service does not allow anonymous entities to subscribe.
+%%<li>The requesting entity has a pending subscription.
+%%<li>The requesting entity is blocked from subscribing (e.g., because having an affiliation of outcast).
+%%<li>The node does not support subscriptions.
+%%<li>The node does not exist.
+%%</ul>
+subscribe_node(Host, Node, From, JID) ->
+    Subscriber = case jlib:string_to_jid(JID) of
+                    error -> {"", "", ""};
+                    J -> jlib:jid_tolower(J)
+                end,
+    SubId = uniqid(),
+    Action = fun(#pubsub_node{options = Options, type = Type}) ->
+                    Features = features(Type),
+                    SubscribeFeature = lists:member("subscribe", Features),
+                    SubscribeConfig = get_option(Options, subscribe),
+                    AccessModel = get_option(Options, access_model),
+                    SendLast = get_option(Options, send_last_published_item),
+                    AllowedGroups = get_option(Options, roster_groups_allowed),
+                    {PresenceSubscription, RosterGroup} =
+                        case Host of
+                            {OUser, OServer, _} ->
+                                get_roster_info(OUser, OServer,
+                                                Subscriber, AllowedGroups);
+                            _ ->
+                                {true, true}
+                        end,
+                    if
+                        not SubscribeFeature ->
+                            %% Node does not support subscriptions
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
+                        not SubscribeConfig ->
+                            %% Node does not support subscriptions
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
+                        true ->
+                            node_call(Type, subscribe_node,
+                                      [Host, Node, From, Subscriber,
+                                       AccessModel, SendLast,
+                                       PresenceSubscription, RosterGroup])
+                    end
+            end,
+    Reply = fun(Subscription) ->
+                   %% TODO, this is subscription-notification, should depends on node features
+                   Fields =
+                       [{"node", node_to_string(Node)},
+                        {"jid", jlib:jid_to_string(Subscriber)},
+                        {"subscription", subscription_to_string(Subscription)}],
+                   case Subscription of
+                       subscribed ->
+                           [{xmlelement, "subscription",
+                             Fields ++ [{"subid", SubId}], []}];
+                       _ ->
+                           [{xmlelement, "subscription", Fields, []}]
+                   end
+           end,
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, Error} ->
+           {error, Error};
+       {result, {Result, subscribed, send_last}} ->
+           %% TODO was send_last_published_item
+           send_all_items(Host, Node, Subscriber),
+           case Result of
+               default -> {result, Reply(subscribed)};
+               _ -> {result, Result}
+           end;
+       {result, {Result, Subscription}} ->
+           case Subscription of
+               pending -> send_authorization_request(Host, Node, Subscriber);
+               _ -> ok
+           end,
+           case Result of
+               default -> {result, Reply(Subscription)};
+               _ -> {result, Result}
+           end;
+       {result, Result} ->
+           %% this case should never occure anyway
+           {result, Result}
     end.
 
+%% @spec (Host, Noce, From, JID) -> {error, Reason} | {result, []}
+%%      Host = host()
+%%      Node = pubsubNode()
+%%      From = jid()
+%%      JID = string()
+%%      SubId = string()
+%%      Reason = stanzaError()
+%% @doc <p>Unsubscribe <tt>JID</tt> from the <tt>Node</tt>.</p>
+%%<p>There are several reasons why the unsubscribe request might fail:</p>
+%%<ul>
+%%<li>The requesting entity has multiple subscriptions to the node but does not specify a subscription ID.
+%%<li>The request does not specify an existing subscriber.
+%%<li>The requesting entity does not have sufficient privileges to unsubscribe the specified JID.
+%%<li>The node does not exist.
+%%<li>The request specifies a subscription ID that is not valid or current.
+%%</il>
+unsubscribe_node(Host, Node, From, JID, SubId) ->
+    Subscriber = case jlib:string_to_jid(JID) of
+                    error -> {"", "", ""};
+                    J -> jlib:jid_tolower(J)
+                end,
+    case node_action(Host, Node, unsubscribe_node,
+                    [Host, Node, From, Subscriber, SubId]) of
+       {error, Error} ->
+           {error, Error};
+       {result, default} ->
+           {result, []};
+       {result, Result} ->
+           {result, Result}
+    end.
 
-subscribe_node(Host, From, JID, Node) ->
-    Sender = jlib:jid_tolower(jlib:jid_remove_resource(From)),
-    SubscriberJID =
-       case jlib:string_to_jid(JID) of
-           error ->
-               {"", "", ""};
-           J ->
-               J
-       end,
-    Subscriber = jlib:jid_tolower(SubscriberJID),
-    SubscriberWithoutResource = jlib:jid_remove_resource(Subscriber),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       Affiliation = get_affiliation(Info, Subscriber),
-                       AllowSubscriptions = get_node_option(Info, subscribe),
-                       if
-                           AllowSubscriptions and
-                           (Affiliation /= outcast) ->
-                               NewInfo = add_subscriber(Info, Subscriber),
-                               mnesia:write(N#pubsub_node{info = NewInfo}),
-                               {result, [], Info};
-                           true ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    if
-       Sender == SubscriberWithoutResource ->
-           case mnesia:transaction(F) of
-               {atomic, {error, _} = Error} ->
-                   Error;
-               {atomic, {result, Res, Info}} ->
-                   case get_node_option(Info, send_item_subscribe) of
-                       true ->
-                           ItemsEls =
-                               lists:map(
-                                 fun(#item{id = ItemID,
-                                           payload = Payload}) ->
-                                         ItemAttrs = case ItemID of
-                                                         "" -> [];
-                                                         _ -> [{"id", ItemID}]
-                                                     end,
-                                         {xmlelement, "item",
-                                          ItemAttrs, Payload}
-                                 end, Info#nodeinfo.items),
-                           Stanza =
-                               {xmlelement, "message",
-                                [],
-                                [{xmlelement, "x",
-                                  [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                  [{xmlelement, "items",
-                                    [{"node", node_to_string(Node)}],
-                                    ItemsEls}]}]},
-                           ejabberd_router:route(
-                             ?MYJID, jlib:make_jid(Subscriber), Stanza);
-                       false ->
-                           ok
-                   end,
-                   {result, Res};
-               _ ->
-                   {error, ?ERR_INTERNAL_SERVER_ERROR}
+%% @spec (Host::host(), ServerHost::host(), JID::jid(), Node::pubsubNode(), ItemId::string(), Payload::term())  ->
+%%               {error, Reason::stanzaError()} |
+%%               {result, []}
+%% @doc <p>Publish item to a PubSub node.</p>
+%% <p>The permission to publish an item must be verified by the plugin implementation.</p>
+%%<p>There are several reasons why the publish request might fail:</p>
+%%<ul>
+%%<li>The requesting entity does not have sufficient privileges to publish.
+%%<li>The node does not support item publication.
+%%<li>The node does not exist.
+%%<li>The payload size exceeds a service-defined limit.
+%%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.
+%%<li>The request does not match the node configuration.
+%%</ul>
+publish_item(Host, ServerHost, Node, Publisher, "", Payload) ->
+    %% if publisher does not specify an ItemId, the service MUST generate the ItemId
+    publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload);
+publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
+    Action = fun(#pubsub_node{options = Options, type = Type}) ->
+                    Features = features(Type),
+                    PublishFeature = lists:member("publish", Features),
+                    Model = get_option(Options, publish_model),
+                    MaxItems = max_items(Options),
+                    PayloadSize = size(term_to_binary(Payload)),
+                    PayloadMaxSize = get_option(Options, max_payload_size),
+                    if
+                        not PublishFeature ->
+                            %% Node does not support item publication
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "publish")};
+                        PayloadSize > PayloadMaxSize ->
+                            %% Entity attempts to publish very large payload
+                            {error, extended_error(?ERR_NOT_ACCEPTABLE, "payload-too-big")};
+                        %%?? ->   iq_pubsub just does that matchs
+                        %%     % Entity attempts to publish item with multiple payload elements or namespace does not match
+                        %%     {error, extended_error(?ERR_BAD_REQUEST, "invalid-payload")};
+                        %%     % Publisher attempts to publish to persistent node with no item
+                        %%     {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
+                        Payload == "" ->
+                            %% Publisher attempts to publish to payload node with no payload
+                            {error, extended_error(?ERR_BAD_REQUEST, "payload-required")};
+                        %%?? ->
+                        %%     % Publisher attempts to publish to transient notification node with item
+                        %%     {error, extended_error(?ERR_BAD_REQUEST, "item-forbidden")};
+                        true ->
+                            node_call(Type, publish_item, [Host, Node, Publisher, Model, MaxItems, ItemId, Payload])
+                    end
+            end,
+    %%ejabberd_hooks:run(pubsub_publish_item, Host, [Host, Node, JID, ?PUBSUB_JID, ItemId, Payload]),
+    Reply = [],
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, ?ERR_ITEM_NOT_FOUND} ->
+           %% handles auto-create feature
+           %% for automatic node creation. we'll take the default node type:
+           %% first listed into the plugins configuration option
+           Type = case ets:lookup(gen_mod:get_module_proc(ServerHost, pubsub_state), plugins) of
+                      [{plugins, PL}] -> hd(PL);
+                      _ -> ?STDNODE
+                  end,
+           case lists:member("auto-create", features(Type)) of
+               true ->
+                   case create_node(Host, ServerHost, Node, Publisher, Type) of
+                       {result, [CreateRes]} ->
+                           %% Ugly hack
+                           SNewNode = xml:get_path_s(
+                                        CreateRes,
+                                        [{elem, "create"},
+                                         {attr, "node"}]),
+                           NewNode = string_to_node(SNewNode),
+                           if
+                               NewNode /= Node ->
+                                   publish_item(Host, ServerHost, NewNode,
+                                                Publisher, ItemId, Payload);
+                               true ->
+                                   {error, ?ERR_ITEM_NOT_FOUND}
+                           end;
+                       _ ->
+                           {error, ?ERR_ITEM_NOT_FOUND}
+                   end;
+               false ->
+                   {error, ?ERR_ITEM_NOT_FOUND}
            end;
-       true ->
-           {error, ?ERR_NOT_ALLOWED}
+       {error, Reason} ->
+           {error, Reason};
+       {result, {Result, broadcast, Removed}} ->
+           lists:foreach(fun(OldItem) ->
+                                 broadcast_retract_item(Host, Node, OldItem)
+                         end, Removed),
+           broadcast_publish_item(Host, Node, ItemId, jlib:jid_tolower(Publisher), Payload),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
+           end;
+       {result, default, Removed} ->
+           lists:foreach(fun(OldItem) ->
+                                 broadcast_retract_item(Host, Node, OldItem)
+                         end, Removed),
+           {result, Reply};
+       {result, Result, Removed} ->
+           lists:foreach(fun(OldItem) ->
+                                 broadcast_retract_item(Host, Node, OldItem)
+                         end, Removed),
+           {result, Result};
+       {result, default} ->
+           {result, Reply};
+       {result, Result} ->
+           {result, Result}
     end.
 
-
-unsubscribe_node(Host, From, JID, Node) ->
-    Sender = jlib:jid_tolower(jlib:jid_remove_resource(From)),
-    SubscriberJID =
-       case jlib:string_to_jid(JID) of
-           error ->
-               {"", "", ""};
-           J ->
-               J
-       end,
-    Subscriber = jlib:jid_tolower(SubscriberJID),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       Subscription = get_subscription(Info, Subscriber),
-                       if
-                           Subscription /= none ->
-                               NewInfo =
-                                   remove_subscriber(Info, Subscriber),
-                               mnesia:write(
-                                 N#pubsub_node{info = NewInfo}),
-                               {result, []};
-                           true ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    if
-       Sender == Subscriber ->
-           case mnesia:transaction(F) of
-               {atomic, {error, _} = Error} ->
-                   Error;
-               {atomic, {result, Res}} ->
-                   {result, Res};
-               _ ->
-                   {error, ?ERR_INTERNAL_SERVER_ERROR}
+%% @spec (Host::host(), JID::jid(), Node::pubsubNode(), ItemId::string()) ->
+%%               {error, Reason::stanzaError()} |
+%%               {result, []}
+%% @doc <p>Delete item from a PubSub node.</p>
+%% <p>The permission to delete an item must be verified by the plugin implementation.</p>
+%%<p>There are several reasons why the item retraction request might fail:</p>
+%%<ul>
+%%<li>The publisher does not have sufficient privileges to delete the requested item.
+%%<li>The node or item does not exist.
+%%<li>The request does not specify a node.
+%%<li>The request does not include an <item/> element or the <item/> element does not specify an ItemId.
+%%<li>The node does not support persistent items.
+%%<li>The service does not support the deletion of items.
+%%</ul>
+delete_item(Host, Node, Publisher, ItemId) ->
+    delete_item(Host, Node, Publisher, ItemId, false).
+delete_item(_, "", _, _, _) ->
+    %% Request does not specify a node
+    {error, extended_error(?ERR_BAD_REQUEST, "node-required")};
+delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
+    Action = fun(#pubsub_node{type = Type}) ->
+                    Features = features(Type),
+                    PersistentFeature = lists:member("persistent-items", Features),
+                    DeleteFeature = lists:member("delete-nodes", Features),
+                    if
+                        %%?? ->   iq_pubsub just does that matchs
+                        %%     %% Request does not specify an item
+                        %%     {error, extended_error(?ERR_BAD_REQUEST, "item-required")};
+                        not PersistentFeature ->
+                            %% Node does not support persistent items
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
+                        not DeleteFeature ->
+                            %% Service does not support item deletion
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "delete-nodes")};
+                        true ->
+                            node_call(Type, delete_item, [Host, Node, Publisher, ItemId])
+                    end
+            end,
+    Reply = [],
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, Reason} ->
+           {error, Reason};
+       {result, {Result, broadcast}} ->
+           broadcast_retract_item(Host, Node, ItemId, ForceNotify),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
            end;
-       true ->
-           {error, ?ERR_NOT_ALLOWED}
+       {result, default} ->
+           {result, Reply};
+       {result, Result} ->
+           {result, Result}
     end.
 
+%% @spec (Host, JID, Node) ->
+%%                     {error, Reason} | {result, []}
+%%      Host = host()
+%%      Node = pubsubNode()
+%%      JID = jid()
+%%      Reason = stanzaError()
+%% @doc <p>Delete all items of specified node owned by JID.</p>
+%%<p>There are several reasons why the node purge request might fail:</p>
+%%<ul>
+%%<li>The node or service does not support node purging.
+%%<li>The requesting entity does not have sufficient privileges to purge the node.
+%%<li>The node is not configured to persist items.
+%%<li>The specified node does not exist.
+%%</ul>
+purge_node(Host, Node, Owner) ->
+    Action = fun(#pubsub_node{type = Type, options = Options}) ->
+                    Features = features(Type),
+                    PurgeFeature = lists:member("purge-nodes", Features),
+                    PersistentFeature = lists:member("persistent-items", Features),
+                    PersistentConfig = get_option(Options, persist_items),
+                    if
+                        not PurgeFeature ->
+                            %% Service does not support node purging
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "purge-nodes")};
+                        not PersistentFeature ->
+                            %% Node does not support persistent items
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
+                        not PersistentConfig ->
+                            %% Node is not configured for persistent items
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")};
+                        true ->
+                            node_call(Type, purge_node, [Host, Node, Owner])
+                    end
+            end,
+    Reply = [],
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, Reason} ->
+           {error, Reason};
+       {result, {Result, broadcast}} ->
+           broadcast_purge_node(Host, Node),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
+           end;
+       {result, default} ->
+           {result, Reply};
+       {result, Result} ->
+           {result, Result}
+    end.
 
-get_items(Host, JID, Node, SMaxItems) ->
+%% @doc <p>Return the items of a given node.</p>
+%% <p>The number of items to return is limited by MaxItems.</p>
+%% <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, _JID, SMaxItems) ->
     MaxItems =
        if
-           SMaxItems == "" ->
-               ?MAXITEMS;
+           SMaxItems == "" -> ?MAXITEMS;
            true ->
                case catch list_to_integer(SMaxItems) of
-                   {'EXIT', _} ->
-                       {error, ?ERR_BAD_REQUEST};
-                   Val ->
-                       Val
+                   {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+                   Val -> Val
                end
        end,
     case MaxItems of
-       {error, _} = Error ->
-           Error;
+       {error, Error} ->
+           {error, Error};
        _ ->
-           case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-               [#pubsub_node{info = Info}] ->
-                   Items = lists:sublist(Info#nodeinfo.items, MaxItems),
-                   ItemsEls =
-                       lists:map(
-                         fun(#item{id = ItemID,
-                                   payload = Payload}) ->
-                                 ItemAttrs = case ItemID of
-                                                 "" -> [];
-                                                 _ -> [{"id", ItemID}]
-                                             end,
-                                 {xmlelement, "item", ItemAttrs, Payload}
-                         end, Items),
-                   {result, [{xmlelement, "pubsub",
-                              [{"xmlns", ?NS_PUBSUB_EVENT}],
-                              [{xmlelement, "items",
-                                [{"node", node_to_string(Node)}],
-                                ItemsEls}]}]};
-               _ ->
-                   {error, ?ERR_ITEM_NOT_FOUND}
+           case get_items(Host, Node) of
+               [] ->
+                   {error, ?ERR_ITEM_NOT_FOUND};
+               Items ->
+                   %% Generate the XML response (Item list), limiting the
+                   %% number of items sent to MaxItems:
+                   ItemsEls = lists:map(
+                                fun(#pubsub_item{itemid = {ItemId, _},
+                                                 payload = Payload}) ->
+                                        ItemAttrs = case ItemId of
+                                                        "" -> [];
+                                                        _ -> [{"id", ItemId}]
+                                                    end,
+                                        {xmlelement, "item", ItemAttrs, Payload}
+                                end, lists:sublist(Items, MaxItems)),
+                   {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_EVENT}],
+                              [{xmlelement, "items", [{"node", node_to_string(Node)}],
+                                ItemsEls}]}]}
            end
     end.
 
-
-delete_node(Host, JID, Node) ->
-    Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info}] ->
-                       case get_affiliation(Info, Owner) of
-                           owner ->
-                               % TODO: don't iterate over entire table
-                               Removed =
-                                   mnesia:foldl(
-                                     fun(#pubsub_node{host_node = {_, N},
-                                                      info = NInfo}, Acc) ->
-                                             case lists:prefix(Node, N) of
-                                                 true ->
-                                                     [{N, NInfo} | Acc];
-                                                 _ ->
-                                                     Acc
-                                             end
-                                     end, [], pubsub_node),
-                               lists:foreach(
-                                 fun({N, _}) ->
-                                         mnesia:delete({pubsub_node, {Host, N}})
-                                 end, Removed),
-                               {removed, Removed};
-                           _ ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    case mnesia:transaction(F) of
-       {atomic, {error, _} = Error} ->
-           Error;
-       {atomic, {removed, Removed}} ->
-           broadcast_removed_node(Host, Removed),
-           Lang = "",
-           broadcast_retract_item(
-             Host, ["pubsub", "nodes"], node_to_string(Node)),
-           {result, []};
-       _ ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR}
+get_items(Host, Node) ->
+    case node_action(Host, Node, get_items, [Host, Node]) of
+       {result, Items} -> Items;
+       _ -> []
     end.
 
-
-purge_node(Host, JID, Node) ->
-    Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       case get_affiliation(Info, Owner) of
-                           owner ->
-                               NewInfo = Info#nodeinfo{items = []},
-                               mnesia:write(
-                                 N#pubsub_node{info = NewInfo}),
-                               {result, Info#nodeinfo.items, []};
-                           _ ->
-                               {error, ?ERR_NOT_ALLOWED}
-                       end;
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    case mnesia:transaction(F) of
-       {atomic, {error, _} = Error} ->
-           Error;
-       {atomic, {result, Items, Res}} ->
-           lists:foreach(
-             fun(#item{id = ItemID}) ->
-                     broadcast_retract_item(Host, Node, ItemID)
-             end, Items),
-           {result, Res};
-       _ ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR}
-    end.
-
-
-get_entities(Host, OJID, Node) ->
-    Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
-    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-       [#pubsub_node{info = Info}] ->
-           case get_affiliation(Info, Owner) of
-               owner ->
-                   Entities = Info#nodeinfo.entities,
-                   EntitiesEls =
-                       ?DICT:fold(
-                         fun(JID,
-                             #entity{affiliation = Affiliation,
-                                     subscription = Subscription},
-                             Acc) ->
-                                 [{xmlelement, "entity",
-                                   [{"jid", jlib:jid_to_string(JID)},
-                                    {"affiliation",
-                                     affiliation_to_string(Affiliation)},
-                                    {"subscription",
-                                     subscription_to_string(Subscription)}],
-                                   []} | Acc]
-                         end, [], Entities),
-                   {result, [{xmlelement, "pubsub",
-                              [{"xmlns", ?NS_PUBSUB_EVENT}],
-                              [{xmlelement, "entities",
-                                [{"node", node_to_string(Node)}],
-                                EntitiesEls}]}]};
-               _ ->
-                   {error, ?ERR_NOT_ALLOWED}
-           end;
-       _ ->
-           {error, ?ERR_ITEM_NOT_FOUND}
+%% @spec (Host, Node, LJID) -> any()
+%%      Host = host()
+%%      Node = pubsubNode()
+%%      LJID = {U,S,""}
+%% @doc <p>Resend the items of a node to the user.</p>
+send_all_items(Host, Node, LJID) ->
+    send_items(Host, Node, LJID, all).
+
+send_last_item(Host, Node, LJID) ->
+    send_items(Host, Node, LJID, last).
+
+%% TODO use cache-last-item feature
+send_items(Host, Node, LJID, Number) ->
+    Items = get_items(Host, Node),
+    ToSend = case Number of
+                last -> lists:sublist(Items, 1);
+                all -> Items;
+                N when N > 0 -> lists:sublist(Items, N);
+                _ -> Items
+            end,
+    ItemsEls = lists:map(
+                fun(#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
+                        ItemAttrs = case ItemId of
+                                        "" -> [];
+                                        _ -> [{"id", ItemId}]
+                                    end,
+                        {xmlelement, "item", ItemAttrs, Payload}
+                end, ToSend),
+    Stanza = {xmlelement, "message", [],
+             [{xmlelement, "x", [{"xmlns", ?NS_PUBSUB_EVENT}],
+               [{xmlelement, "items", [{"node", node_to_string(Node)}],
+                 ItemsEls}]}]},
+    ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(LJID), Stanza).
+
+%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
+%%      Host = host()
+%%      JID = jid()
+%%      Plugins = [Plugin::string()]
+%%      Reason = stanzaError()
+%%      Response = [pubsubIQResponse()]
+%% @doc <p>Return the list of affiliations as an XMPP response.</p>
+get_affiliations(Host, JID, Plugins) when is_list(Plugins) ->
+    Result = lists:foldl(
+              fun(Type, {Status, Acc}) ->
+                      Features = features(Type),
+                      RetrieveFeature = lists:member("retrieve-affiliations", Features),
+                      if
+                          not RetrieveFeature ->
+                              %% Service does not support retreive affiliatons
+                              {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc};
+                          true ->
+                              {result, Affiliations} = node_action(Type, get_entity_affiliations, [Host, JID]),
+                              {Status, [Affiliations|Acc]}
+                      end
+              end, {ok, []}, Plugins),
+    case Result of
+       {ok, Affiliations} ->
+           Entities = lists:flatmap(
+                        fun({_, none}) -> [];
+                           ({Node, Affiliation}) ->
+                                [{xmlelement, "affiliation",
+                                  [{"node", node_to_string(Node)},
+                                   {"affiliation", affiliation_to_string(Affiliation)}],
+                                  []}]
+                        end, lists:flatten(Affiliations)),
+           {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
+                      [{xmlelement, "affiliations", [],
+                        Entities}]}]};
+       {Error, _} ->
+           Error
+    end;
+get_affiliations(Host, Node, JID) ->
+    Action = fun(#pubsub_node{type = Type}) ->
+                    Features = features(Type),
+                    RetrieveFeature = lists:member("modify-affiliations", Features),
+                    Affiliation = node_call(Type, get_affiliation, [Host, Node, JID]),
+                    if
+                        not RetrieveFeature ->
+                            %% Service does not support modify affiliations
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "modify-affiliations")};
+                        Affiliation /= {result, owner} ->
+                            %% Entity is not an owner
+                            {error, ?ERR_FORBIDDEN};
+                        true ->
+                            node_call(Type, get_node_affiliations, [Host, Node])
+                    end
+            end,
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, Reason} ->
+           {error, Reason};
+       {result, []} ->
+           {error, ?ERR_ITEM_NOT_FOUND};
+       {result, Affiliations} ->
+           Entities = lists:flatmap(
+                        fun({_, none}) -> [];
+                           ({AJID, Affiliation}) ->
+                                [{xmlelement, "affiliation",
+                                  [{"jid", jlib:jid_to_string(AJID)},
+                                   {"affiliation", affiliation_to_string(Affiliation)}],
+                                  []}]
+                        end, Affiliations),
+           {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
+                      [{xmlelement, "affiliations", [{"node", node_to_string(Node)}],
+                        Entities}]}]}
     end.
 
-
-set_entities(Host, OJID, Node, EntitiesEls) ->
-    Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
+set_affiliations(Host, Node, From, EntitiesEls) ->
+    Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
     Entities =
        lists:foldl(
          fun(El, Acc) ->
@@ -924,38 +1822,17 @@ set_entities(Host, OJID, Node, EntitiesEls) ->
                          error;
                      _ ->
                          case El of
-                             {xmlelement, "entity", Attrs, _} ->
+                             {xmlelement, "affiliation", Attrs, _} ->
                                  JID = jlib:string_to_jid(
                                          xml:get_attr_s("jid", Attrs)),
-                                 Affiliation =
-                                     case xml:get_attr_s("affiliation",
-                                                         Attrs) of
-                                         "owner" -> owner;
-                                         "publisher" -> publisher;
-                                         "outcast" -> outcast;
-                                         "none" -> none;
-                                         _ -> false
-                                     end,
-                                 Subscription =
-                                     case xml:get_attr_s("subscription",
-                                                         Attrs) of
-                                         "subscribed" -> subscribed;
-                                         "pending" -> pending;
-                                         "unconfigured" -> unconfigured;
-                                         "none" -> none;
-                                         _ -> false
-                                     end,
+                                 Affiliation = string_to_affiliation(
+                                                 xml:get_attr_s("affiliation", Attrs)),
                                  if
                                      (JID == error) or
-                                     (Affiliation == false) or
-                                     (Subscription == false) ->
+                                     (Affiliation == false) ->
                                          error;
                                      true ->
-                                         [{jlib:jid_tolower(JID),
-                                           #entity{
-                                             affiliation = Affiliation,
-                                             subscription = Subscription}} |
-                                          Acc]
+                                         [{jlib:jid_tolower(JID), Affiliation} | Acc]
                                  end
                          end
                  end
@@ -964,568 +1841,743 @@ set_entities(Host, OJID, Node, EntitiesEls) ->
        error ->
            {error, ?ERR_BAD_REQUEST};
        _ ->
-           F = fun() ->
-                       case mnesia:read({pubsub_node, {Host, Node}}) of
-                           [#pubsub_node{info = Info} = N] ->
-                               case get_affiliation(Info, Owner) of
-                                   owner ->
-                                       NewInfo =
-                                           set_info_entities(Info, Entities),
-                                       mnesia:write(
-                                         N#pubsub_node{info = NewInfo}),
-                                       {result, []};
-                                   _ ->
-                                       {error, ?ERR_NOT_ALLOWED}
-                               end;
-                           [] ->
-                               {error, ?ERR_ITEM_NOT_FOUND}
-                       end
-               end,
-           case mnesia:transaction(F) of
-               {atomic, {error, _} = Error} ->
-                   Error;
-               {atomic, {result, _}} ->
-                   {result, []};
-               _ ->
-                   {error, ?ERR_INTERNAL_SERVER_ERROR}
-           end
+           Action = fun(#pubsub_node{type = Type, owners = Owners}) ->
+                            case lists:member(Owner, Owners) of
+                                true ->
+                                    lists:foreach(
+                                      fun({JID, Affiliation}) ->
+                                              node_call(
+                                                Type, set_affiliation,
+                                                [Host, Node, JID, Affiliation])
+                                      end, Entities),
+                                    {result, []};
+                                _ ->
+                                    {error, ?ERR_NOT_ALLOWED}
+                            end
+                    end,
+           transaction(Host, Node, Action, sync_dirty)
     end.
 
 
-get_affiliations(Host, JID) ->
-    LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    case catch mnesia:dirty_select(
-                pubsub_node,
-                [{#pubsub_node{_ = '_'},
-                  [],
-                  ['$_']}]) of
-       {'EXIT', _} ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR};
-       Nodes ->
-           Entities =
-               lists:flatmap(
-                 fun(#pubsub_node{host_node = {H, Node}, info = Info})
-                    when H == Host ->
-                         Affiliation = get_affiliation(Info, LJID),
-                         Subscription = get_subscription(Info, LJID),
-                         if
-                             (Affiliation /= none) or
-                             (Subscription /= none) ->
-                                 [{xmlelement, "entity",
-                                   [{"node", node_to_string(Node)},
-                                    {"jid", jlib:jid_to_string(JID)},
-                                    {"affiliation",
-                                     affiliation_to_string(Affiliation)},
-                                    {"subscription",
-                                     subscription_to_string(Subscription)}],
-                                   []}];
-                             true ->
-                                 []
-                         end;
-                    (_) ->
-                         []
-                 end, Nodes),
-           {result, [{xmlelement, "pubsub",
-                      [{"xmlns", ?NS_PUBSUB_EVENT}],
-                      [{xmlelement, "affiliations", [],
+%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
+%%      Host = host()
+%%      JID = jid()
+%%      Plugins = [Plugin::string()]
+%%      Reason = stanzaError()
+%%      Response = [pubsubIQResponse()]
+%% @doc <p>Return the list of subscriptions as an XMPP response.</p>
+get_subscriptions(Host, JID, Plugins) when is_list(Plugins) ->
+    Result = lists:foldl(
+              fun(Type, {Status, Acc}) ->
+                      Features = features(Type),
+                      RetrieveFeature = lists:member("retrieve-subscriptions", Features),
+                      if
+                          not RetrieveFeature ->
+                              %% Service does not support retreive subscriptions
+                              {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-subscriptions")}, Acc};
+                          true ->
+                              {result, Subscriptions} = node_action(Type, get_entity_subscriptions, [Host, JID]),
+                              {Status, [Subscriptions|Acc]}
+                      end
+              end, {ok, []}, Plugins),
+    case Result of
+       {ok, Subscriptions} ->
+           Entities = lists:flatmap(
+                        fun({_, none}) -> [];
+                           ({Node, Subscription}) ->
+                                [{xmlelement, "subscription",
+                                  [{"node", node_to_string(Node)},
+                                   {"subscription", subscription_to_string(Subscription)}],
+                                  []}];
+                           ({_, none, _}) -> [];
+                           ({Node, Subscription, SubJID}) ->
+                                [{xmlelement, "subscription",
+                                  [{"node", node_to_string(Node)},
+                                   {"jid", jlib:jid_to_string(SubJID)},
+                                   {"subscription", subscription_to_string(Subscription)}],
+                                  []}]
+                        end, lists:flatten(Subscriptions)),
+           {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
+                      [{xmlelement, "subscriptions", [],
+                        Entities}]}]};
+       {Error, _} ->
+           Error
+    end;
+get_subscriptions(Host, Node, JID) ->
+    Action = fun(#pubsub_node{type = Type}) ->
+                    Features = features(Type),
+                    RetrieveFeature = lists:member("manage-subscriptions", Features),
+                    Affiliation = node_call(Type, get_affiliation, [Host, Node, JID]),
+                    if
+                        not RetrieveFeature ->
+                            %% Service does not support manage subscriptions
+                            {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "manage-affiliations")};
+                        Affiliation /= {result, owner} ->
+                                               % Entity is not an owner
+                            {error, ?ERR_FORBIDDEN};
+                        true ->
+                            node_call(Type, get_node_subscriptions, [Host, Node])
+                    end
+            end,
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, Reason} ->
+           {error, Reason};
+       {result, []} ->
+           {error, ?ERR_ITEM_NOT_FOUND};
+       {result, Subscriptions} ->
+           Entities = lists:flatmap(
+                        fun({_, none}) -> [];
+                           ({AJID, Subscription}) ->
+                                [{xmlelement, "subscription",
+                                  [{"jid", jlib:jid_to_string(AJID)},
+                                   {"subscription", subscription_to_string(Subscription)}],
+                                  []}];
+                           ({AJID, Subscription, SubId}) ->
+                                [{xmlelement, "subscription",
+                                  [{"jid", jlib:jid_to_string(AJID)},
+                                   {"subscription", subscription_to_string(Subscription)},
+                                   {"subid", SubId}],
+                                  []}]
+                        end, Subscriptions),
+           {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
+                      [{xmlelement, "subscriptions", [{"node", node_to_string(Node)}],
                         Entities}]}]}
     end.
 
-
-
-
-get_affiliation(#nodeinfo{entities = Entities}, JID) ->
-    LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    case ?DICT:find(LJID, Entities) of
-       {ok, #entity{affiliation = Affiliation}} ->
-           Affiliation;
-       _ ->
-           none
-    end.
-
-get_subscription(#nodeinfo{entities = Entities}, JID) ->
-    LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
-    case ?DICT:find(LJID, Entities) of
-       {ok, #entity{subscription = Subscription}} ->
-           Subscription;
-       _ ->
-           none
-    end.
-
-affiliation_to_string(Affiliation) ->
-    case Affiliation of
-       owner -> "owner";
-       publisher -> "publisher";
-       outcast -> "outcast";
-       _ -> "none"
-    end.
-
-subscription_to_string(Subscription) ->
-    case Subscription of
-       subscribed -> "subscribed";
-       pending -> "pending";
-       unconfigured -> "unconfigured";
-       _ -> "none"
-    end.
-
-
-check_create_permission(Host, Node, Owner, ServerHost, Access) ->
-       #jid{luser = User, lserver = Server, lresource = Resource} = Owner,
-    case acl:match_rule(ServerHost, Access, {User, Server, Resource}) of
-    allow ->
-       if Server == Host ->
-               true;
-               true ->
-               case Node of
-                       ["home", Server, User | _] ->
-                       true;
-                       _ ->
-                           false
-                   end
-           end;
-       _ ->
-           case Owner of
-               ?MYJID ->
-                   true;
-               _ ->
-                   false
-           end
-    end.
-
-insert_item(Info, ItemID, Publisher, Payload) ->
-    Items = Info#nodeinfo.items,
-    Items1 = lists:filter(fun(I) ->
-                                 I#item.id /= ItemID
-                         end, Items),
-    Items2 = [#item{id = ItemID, publisher = Publisher, payload = Payload} |
-             Items1],
-    Items3 = lists:sublist(Items2, get_max_items(Info)),
-    Info#nodeinfo{items = Items3}.
-
-remove_item(Info, ItemID) ->
-    Items = Info#nodeinfo.items,
-    Items1 = lists:filter(fun(I) ->
-                                 I#item.id /= ItemID
-                         end, Items),
-    Info#nodeinfo{items = Items1}.
-
-check_item_publisher(Info, ItemID, Publisher) ->
-    Items = Info#nodeinfo.items,
-    case lists:keysearch(ItemID, #item.id, Items) of
-       {value, #item{publisher = Publisher}} ->
-           true;
-       _ ->
-           false
-    end.
-
-add_subscriber(Info, Subscriber) ->
-    Entities = Info#nodeinfo.entities,
-    case ?DICT:find(Subscriber, Entities) of
-       {ok, Entity} ->
-           Info#nodeinfo{
-             entities = ?DICT:store(Subscriber,
-                                    Entity#entity{subscription = subscribed},
-                                    Entities)};
-       _ ->
-           Info#nodeinfo{
-             entities = ?DICT:store(Subscriber,
-                                    #entity{subscription = subscribed},
-                                    Entities)}
-    end.
-
-remove_subscriber(Info, Subscriber) ->
-    Entities = Info#nodeinfo.entities,
-    case ?DICT:find(Subscriber, Entities) of
-       {ok, #entity{affiliation = none}} ->
-           Info#nodeinfo{
-             entities = ?DICT:erase(Subscriber, Entities)};
-       {ok, Entity} ->
-           Info#nodeinfo{
-             entities = ?DICT:store(Subscriber,
-                                    Entity#entity{subscription = none},
-                                    Entities)};
-       _ ->
-           Info
-    end.
-
-
-set_info_entities(Info, Entities) ->
-    NewEntities =
+set_subscriptions(Host, Node, From, EntitiesEls) ->
+    Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
+    Entities =
        lists:foldl(
-         fun({JID, Ent}, Es) ->
-                 case Ent of
-                     #entity{affiliation = none, subscription = none} ->
-                         ?DICT:erase(JID, Es);
+         fun(El, Acc) ->
+                 case Acc of
+                     error ->
+                         error;
                      _ ->
-                         ?DICT:store(JID, Ent, Es)
+                         case El of
+                             {xmlelement, "subscriptions", Attrs, _} ->
+                                 JID = jlib:string_to_jid(
+                                         xml:get_attr_s("jid", Attrs)),
+                                 Subscription = string_to_subscription(
+                                                  xml:get_attr_s("subscription", Attrs)),
+                                 if
+                                     (JID == error) or
+                                     (Subscription == false) ->
+                                         error;
+                                     true ->
+                                         [{jlib:jid_tolower(JID), Subscription} | Acc]
+                                 end
+                         end
                  end
-         end, Info#nodeinfo.entities, Entities),
-    Info#nodeinfo{entities = NewEntities}.
-
-
-
-broadcast_publish_item(Host, Node, ItemID, Payload) ->
-    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-       [#pubsub_node{info = Info}] ->
-           ?DICT:fold(
-              fun(JID, #entity{subscription = Subscription}, _) ->
-                      Present = case get_node_option(
-                                       Info, presence_based_delivery) of
-                                    true ->
-                         case mnesia:dirty_read(pubsub_presence, {Host, element(1, JID), element(2, JID)}) of
-                         [_] -> true;
-                         [] -> false
-                         end;
-                                    false ->
-                         true
-                                end,
-                      if
-                          (Subscription /= none) and
-                          (Subscription /= pending) and
-                          Present ->
-                              ItemAttrs = case ItemID of
-                                              "" -> [];
-                                              _ -> [{"id", ItemID}]
-                                          end,
-                              Content = case get_node_option(
-                                               Info, deliver_payloads) of
-                                            true ->
-                                                Payload;
-                                            false ->
-                                                []
-                                        end,
-                              Stanza =
-                                  {xmlelement, "message", [],
-                                   [{xmlelement, "event",
-                                     [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                     [{xmlelement, "items",
-                                       [{"node", node_to_string(Node)}],
-                                       [{xmlelement, "item",
-                                         ItemAttrs,
-                                         Content}]}]}]},
-                              ejabberd_router:route(
-                                ?MYJID, jlib:make_jid(JID), Stanza);
-                          true ->
-                              ok
-                      end
-              end, ok, Info#nodeinfo.entities);
+         end, [], EntitiesEls),
+    case Entities of
+       error ->
+           {error, ?ERR_BAD_REQUEST};
        _ ->
-           false
+           Action = fun(#pubsub_node{type = Type, owners = Owners}) ->
+                            case lists:member(Owner, Owners) of
+                                true ->
+                                    lists:foreach(fun({JID, Subscription}) ->
+                                                          node_call(Type, set_subscription, [Host, Node, JID, Subscription])
+                                                  end, Entities),
+                                    {result, []};
+                                _ ->
+                                    {error, ?ERR_NOT_ALLOWED}
+                            end
+                    end,
+           transaction(Host, Node, Action, sync_dirty)
     end.
 
 
-broadcast_retract_item(Host, Node, ItemID) ->
-    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-       [#pubsub_node{info = Info}] ->
-           case get_node_option(Info, notify_retract) of
-               true ->
-                   ?DICT:fold(
-                      fun(JID, #entity{subscription = Subscription}, _) ->
-                              if 
-                                  (Subscription /= none) and
-                                  (Subscription /= pending) ->
-                                      ItemAttrs = case ItemID of
-                                                      "" -> [];
-                                                      _ -> [{"id", ItemID}]
-                                                  end,
-                                      Stanza =
-                                          {xmlelement, "message", [],
-                                           [{xmlelement, "x",
-                                             [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                             [{xmlelement, "items",
-                                               [{"node", node_to_string(Node)}],
-                                               [{xmlelement, "retract",
-                                                 ItemAttrs, []}]}]}]},
-                                      ejabberd_router:route(
-                                        ?MYJID, jlib:make_jid(JID), Stanza);
-                                  true ->
-                                      ok
-                              end
-                      end, ok, Info#nodeinfo.entities);
-               false ->
-                   ok
-           end;
-       _ ->
-           false
+% @spec (OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups)
+get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
+    {Subscription, Groups} =
+       ejabberd_hooks:run_fold(
+         roster_get_jid_info, OwnerServer,
+         {none, []},
+         [OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, ""}]),
+    PresenceSubscription = (Subscription == both) orelse (Subscription == from),
+    RosterGroup = lists:any(fun(Group) ->
+                                   lists:member(Group, AllowedGroups)
+                           end, Groups),
+    {PresenceSubscription, RosterGroup}.
+
+%% @spec (AffiliationStr) -> Affiliation
+%%      AffiliationStr = string()
+%%      Affiliation = atom()
+%% @doc <p>Convert an affiliation type from string to atom.</p>
+string_to_affiliation("owner") -> owner;
+string_to_affiliation("publisher") -> publisher;
+string_to_affiliation("outcast") -> outcast;
+string_to_affiliation("none") -> none;
+string_to_affiliation(_) -> false.
+
+%% @spec (SubscriptionStr) -> Subscription
+%%      SubscriptionStr = string()
+%%      Subscription = atom()
+%% @doc <p>Convert a subscription type from string to atom.</p>
+string_to_subscription("subscribed") -> subscribed;
+string_to_subscription("pending") -> pending;
+string_to_subscription("unconfigured") -> unconfigured;
+string_to_subscription("none") -> none;
+string_to_subscription(_) -> false.
+
+%% @spec (Affiliation) -> AffiliationStr
+%%      Affiliation = atom()
+%%      AffiliationStr = string()
+%% @doc <p>Convert an affiliation type from atom to string.</p>
+affiliation_to_string(owner) -> "owner";
+affiliation_to_string(publisher) -> "publisher";
+affiliation_to_string(outcast) -> "outcast";
+affiliation_to_string(_) -> "none".
+
+%% @spec (Subscription) -> SubscriptionStr
+%%      Subscription = atom()
+%%      SubscriptionStr = string()
+%% @doc <p>Convert a subscription type from atom to string.</p>
+subscription_to_string(subscribed) -> "subscribed";
+subscription_to_string(pending) -> "pending";
+subscription_to_string(unconfigured) -> "unconfigured";
+subscription_to_string(_) -> "none".
+
+%% @spec (Node) -> NodeStr
+%%      Node = pubsubNode()
+%%      NodeStr = string()
+%% @doc <p>Convert a node type from pubsubNode to string.</p>
+node_to_string([]) -> "/";
+node_to_string(Node) ->
+    case Node of
+       [[_ | _] | _] -> string:strip(lists:flatten(["/", lists:map(fun(S) -> [S, "/"] end, Node)]), right, $/);
+       [Head | _] when is_integer(Head) -> Node
     end.
-
+string_to_node(SNode) ->
+    string:tokens(SNode, "/").
+
+
+%%%%%% broadcast functions
+
+broadcast_publish_item(Host, Node, ItemId, From, Payload) ->
+    Action =
+       fun(#pubsub_node{options = Options, type = Type}) ->
+               case node_call(Type, get_states, [Host, Node]) of
+                   {error, _} -> {result, false};
+                   {result, []} -> {result, false};
+                   {result, States} ->
+                       PresenceDelivery = get_option(Options, presence_based_delivery),
+                       DeliverPayloads = get_option(Options, deliver_payloads),
+                       BroadcastAll = get_option(Options, broadcast_all_resources),
+                       lists:foreach(
+                         fun(#pubsub_state{stateid = {JID, _},
+                                           subscription = Subscription}) ->
+                                 ToBeSent =
+                                     case PresenceDelivery of
+                                         true ->
+                                             case mnesia:dirty_read(
+                                                    pubsub_presence,
+                                                    {Host,
+                                                     element(1, JID),
+                                                     element(2, JID)}) of
+                                                 [_] -> true;
+                                                 [] -> false
+                                             end;
+                                         false ->
+                                             true
+                                     end,
+                                 if
+                                     (Subscription /= none) and
+                                     (Subscription /= pending) and
+                                     ToBeSent ->
+                                         ItemAttrs = case ItemId of
+                                                         "" -> [];
+                                                         _ -> [{"id", ItemId}]
+                                                     end,
+                                         Content = case DeliverPayloads of
+                                                       true -> Payload;
+                                                       false -> []
+                                                   end,
+                                         DestJIDs = case BroadcastAll of
+                                                        true -> ejabberd_sm:get_user_resources(element(1, JID), element(2, JID));
+                                                        false -> [JID]
+                                                    end,
+                                         Stanza =
+                                             {xmlelement, "message", [],
+                                              [{xmlelement, "event",
+                                                [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                                [{xmlelement, "items", [{"node", node_to_string(Node)}],
+                                                  [{xmlelement, "item", ItemAttrs, Content}]}]}]},
+                                         lists:foreach(
+                                           fun(DestJID) ->
+                                                   ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(DestJID), Stanza)
+                                           end, DestJIDs),
+                                         broadcast_by_caps(Host, Node, Type, Stanza),
+                                         true;
+                                     true ->
+                                         false
+                                 end
+                         end, States),
+                       {result, true}
+               end
+       end,
+    transaction(Host, Node, Action, sync_dirty).
+
+broadcast_retract_item(Host, Node, ItemId) ->
+    broadcast_retract_item(Host, Node, ItemId, false).
+broadcast_retract_item(Host, Node, ItemId, ForceNotify) ->
+    Action =
+       fun(#pubsub_node{options = Options, type = Type}) ->
+               case node_call(Type, get_states, [Host, Node]) of
+                   {error, _} -> {result, false};
+                   {result, []} -> {result, false};
+                   {result, States} ->
+                       Notify = case ForceNotify of
+                                    true -> true;
+                                    _ -> get_option(Options, notify_retract)
+                                end,
+                       case Notify of
+                           true ->
+                               lists:foreach(
+                                 fun(#pubsub_state{stateid = {JID, _},
+                                                   subscription = Subscription}) ->
+                                         if (Subscription /= none) and
+                                            (Subscription /= pending) ->
+                                                 ItemAttrs =
+                                                     case ItemId of
+                                                         "" -> [];
+                                                         _ -> [{"id", ItemId}]
+                                                     end,
+                                                 Stanza =
+                                                     {xmlelement, "message", [],
+                                                      [{xmlelement, "x",
+                                                        [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                                        [{xmlelement, "items", [{"node", node_to_string(Node)}],
+                                                          [{xmlelement, "retract", ItemAttrs, []}]}]}]},
+                                                 ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(JID), Stanza),
+                                                 broadcast_by_caps(Host, Node, Type, Stanza),
+                                                 true;
+                                            true ->
+                                                 false
+                                         end
+                                 end, States),
+                               {result, true};
+                           false ->
+                               {result, false}
+                       end
+               end
+       end,
+    transaction(Host, Node, Action, sync_dirty).
+
+broadcast_purge_node(Host, Node) ->
+    Action =
+       fun(#pubsub_node{options = Options, type = Type}) ->
+               case node_call(Type, get_states, [Host, Node]) of
+                   {error, _} -> {result, false};
+                   {result, []} -> {result, false};
+                   {result, States} ->
+                       case get_option(Options, notify_retract) of
+                           true ->
+                               lists:foreach(
+                                 fun(#pubsub_state{stateid = {JID,_},
+                                                   subscription = Subscription}) ->
+                                         if (Subscription /= none) and
+                                            (Subscription /= pending) ->
+                                                 Stanza = {xmlelement, "message", [],
+                                                           [{xmlelement, "event",
+                                                             [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                                             [{xmlelement, "purge", [{"node", node_to_string(Node)}],
+                                                               []}]}]},
+                                                 ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(JID), Stanza),
+                                                 broadcast_by_caps(Host, Node, Type, Stanza),
+                                                 true;
+                                            true ->
+                                                 false
+                                         end
+                                 end, States),
+                               {result, true};
+                           false ->
+                               {result, false}
+                       end
+               end
+       end,
+    transaction(Host, Node, Action, sync_dirty).
 
 broadcast_removed_node(Host, Removed) ->
     lists:foreach(
-      fun({Node, Info}) ->
-             case get_node_option(Info, notify_delete) of
-                 true ->
-                     Entities = Info#nodeinfo.entities,
-                     ?DICT:fold(
-                        fun(JID, #entity{subscription = Subscription}, _) ->
-                                if 
-                                    (Subscription /= none) and
-                                    (Subscription /= pending) ->
-                                        Stanza =
-                                            {xmlelement, "message", [],
-                                             [{xmlelement, "x",
-                                               [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                               [{xmlelement, "delete",
-                                                 [{"node", node_to_string(Node)}],
-                                                 []}]}]},
-                                        ejabberd_router:route(
-                                          ?MYJID, jlib:make_jid(JID), Stanza);
-                                    true ->
-                                        ok
-                                end
-                        end, ok, Entities);
-                 false ->
-                     ok
-             end
+      fun(Node) ->
+             Action =
+                 fun(#pubsub_node{options = Options, type = Type}) ->
+                         case get_option(Options, notify_delete) of
+                             true ->
+                                 case node_call(Type, get_states, [Host, Node]) of
+                                     {result, States} ->
+                                         lists:foreach(
+                                           fun(#pubsub_state{stateid = {JID, _},
+                                                             subscription = Subscription}) ->
+                                                   if (Subscription /= none) and
+                                                      (Subscription /= pending) ->
+                                                           Stanza = {xmlelement, "message", [],
+                                                                     [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                                                       [{xmlelement, "delete", [{"node", node_to_string(Node)}],
+                                                                         []}]}]},
+                                                           ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(JID), Stanza),
+                                                           broadcast_by_caps(Host, Node, Type, Stanza),
+                                                           true;
+                                                      true ->
+                                                           false
+                                                   end
+                                           end, States),
+                                         {result, true};
+                                     _ ->
+                                         {result, false}
+                                 end;
+                             _ ->
+                                 {result, false}
+                         end
+                 end,
+             transaction(Host, Node, Action, sync_dirty)
       end, Removed).
 
-
 broadcast_config_notification(Host, Node, Lang) ->
-    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-       [#pubsub_node{info = Info}] ->
-           case get_node_option(Info, notify_config) of
-               true ->
-                   ?DICT:fold(
-                      fun(JID, #entity{subscription = Subscription}, _) ->
-                              Present = case get_node_option(
-                                               Info, presence_based_delivery) of
+    Action =
+       fun(#pubsub_node{options = Options, owners = Owners, type = Type}) ->
+               case node_call(Type, get_states, [Host, Node]) of
+                   {error, _} -> {result, false};
+                   {result, []} -> {result, false};
+                   {result, States} ->
+                       case get_option(Options, notify_config) of
+                           true ->
+                               lists:foreach(
+                                 fun(#pubsub_state{stateid = {JID, _},
+                                                   subscription = Subscription}) ->
+                                         ToBeSent = case get_option(Options, presence_based_delivery) of
+                                                        true ->
+                                                            case mnesia:dirty_read(pubsub_presence,
+                                                                                   {Host, element(1, JID), element(2, JID)}) of
+                                                                [_] -> true;
+                                                                [] -> false
+                                                            end;
+                                                        false ->
+                                                            true
+                                                    end,
+                                         if (Subscription /= none) and
+                                            (Subscription /= pending) and
+                                            ToBeSent ->
+                                                 Content = case get_option(Options, deliver_payloads) of
+                                                               true ->
+                                                                   [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
+                                                                     get_configure_xfields(Type, Options, Lang, Owners)}];
+                                                               false ->
+                                                                   []
+                                                           end,
+                                                 Stanza = {xmlelement, "message", [],
+                                                           [{xmlelement, "x", [{"xmlns", ?NS_PUBSUB_EVENT}],
+                                                             [{xmlelement, "items", [{"node", node_to_string(Node)}],
+                                                               [{xmlelement, "item", [{"id", "configuration"}],
+                                                                 Content}]}]}]},
+                                                 ejabberd_router:route(?PUBSUB_JID, jlib:make_jid(JID), Stanza),
+                                                 broadcast_by_caps(Host, Node, Type, Stanza),
+                                                 true;
                                             true ->
-                             case mnesia:dirty_read(pubsub_presence, {Host, element(1, JID), element(2, JID)}) of
-                             [_] -> true;
-                             [] -> false
-                             end;
-                         false ->
-                             true
-                                        end,
-                              if
-                                  (Subscription /= none) and
-                                  (Subscription /= pending) and
-                                  Present ->
-                                      Fields = get_node_config_xfields(
-                                                 Node, Info, Lang),
-                                      Content = case get_node_option(
-                                                       Info, deliver_payloads) of
-                                                    true ->
-                                                        [{xmlelement, "x",
-                                                          [{"xmlns", ?NS_XDATA},
-                                                           {"type", "form"}],
-                                                          Fields}];
-                                                    false ->
-                                                        []
-                                                end,
-                                      Stanza =
-                                          {xmlelement, "message", [],
-                                           [{xmlelement, "x",
-                                             [{"xmlns", ?NS_PUBSUB_EVENT}],
-                                             [{xmlelement, "items",
-                                               [{"node", node_to_string(Node)}],
-                                               [{xmlelement, "item",
-                                                 [{"id", "configuration"}],
-                                                 Content}]}]}]},
-                                      ejabberd_router:route(
-                                        ?MYJID, jlib:make_jid(JID), Stanza);
-                                  true ->
-                                      ok
-                              end
-                      end, ok, Info#nodeinfo.entities);
-               false ->
+                                                 false
+                                         end
+                                 end, States),
+                               {result, true};
+                           _ ->
+                               {result, false}
+                       end
+               end
+       end,
+    transaction(Host, Node, Action, dirty_sync).
+
+%TODO: simplify broadcast_* using a generic function like that:
+%broadcast(Host, Node, Fun) ->
+%      transaction(fun() ->
+%              case tree_call(Host, get_node, [Host, Node]) of
+%              #pubsub_node{options = Options, owners = Owners, type = Type} ->
+%                      case node_call(Type, get_states, [Host, Node]) of
+%                      {error, _} -> {result, false};
+%                      {result, []} -> {result, false};
+%                      {result, States} ->
+%                              lists:foreach(fun(#pubsub_state{stateid = {JID,_}, subscription = Subscription}) ->
+%                                      Fun(Host, Node, Options, Owners, JID, Subscription)
+%                              end, States),
+%                              {result, true}
+%                      end;
+%              Other ->
+%                      Other
+%              end
+%      end, sync_dirty).
+
+
+%% broadcast Stanza to all contacts of the user that are advertising
+%% interest in this kind of Node.
+broadcast_by_caps({LUser, LServer, LResource}, Node, Type, Stanza) ->
+    ?DEBUG("looking for pid of ~p@~p/~p", [LUser, LServer, LResource]),
+    %% We need to know the resource, so we can ask for presence data.
+    SenderResources = ejabberd_sm:get_user_resources(LUser, LServer),
+    SenderResource = case LResource of
+                        "" ->
+                            %% If we don't know the resource, just pick one.
+                            case SenderResources of
+                                [R|_] ->
+                                    R;
+                                [] ->
+                                    ?ERROR_MSG("~p@~p is offline; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
+                                    ""
+                            end;
+                        _ ->
+                            LResource
+                    end,
+    case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
+       C2SPid when is_pid(C2SPid) ->
+           %% set the from address on the notification to the bare JID of the account owner
+           %% Also, add "replyto" if entity has presence subscription to the account owner
+           %% See XEP-0163 1.1 section 4.3.1
+           Sender = jlib:make_jid(LUser, LServer, ""),
+           ReplyTo = jlib:make_jid(LUser, LServer, SenderResource),  % This has to be used
+           case catch ejabberd_c2s:get_subscribed_and_online(C2SPid) of
+               ContactsWithCaps when is_list(ContactsWithCaps) ->
+                   ?DEBUG("found contacts with caps: ~p", [ContactsWithCaps]),
+                   LookingFor = Node ++ "+notify",
+                   lists:foreach(
+                     fun({JID, Caps}) ->
+                             case catch mod_caps:get_features(?MYNAME, Caps) of
+                                 Features when is_list(Features) ->
+                                     case lists:member(LookingFor, Features) of
+                                         true ->
+                                             To = jlib:make_jid(JID),
+                                             ejabberd_router:route(Sender, To, Stanza);
+                                         _ ->
+                                             ok
+                                     end;
+                                 _ ->
+                                     %% couldn't get entity capabilities.
+                                     %% nothing to do about that...
+                                     ok
+                             end
+                     end, ContactsWithCaps);
+               _ ->
                    ok
-           end;
+           end,
+           %% also send a notification to any
+           %% of the account owner's available resources.
+           %% See: XEP-0163 1.1 section 3
+           lists:foreach(fun(Resource) ->
+                                 To = jlib:make_jid(LUser, LServer, Resource),
+                                 ejabberd_router:route(Sender, To, Stanza)
+                         end, SenderResources),
+           ok;
        _ ->
-           false
-    end.
-
+           ok
+    end;
+broadcast_by_caps(_, _, _, _) ->
+    ok.
 
+%%%%%%% Configuration handling
+
+%%<p>There are several reasons why the default node configuration options request might fail:</p>
+%%<ul>
+%%<li>The service does not support node configuration.
+%%<li>The service does not support retrieval of default node configuration.
+%%</ul>
+get_configure(Host, Node, From, Lang) ->
+    Action =
+       fun(#pubsub_node{options = Options, owners = Owners, type = Type}) ->
+               case node_call(Type, get_affiliation, [Host, Node, From]) of
+                   {result, owner} ->
+                       {result,
+                        [{xmlelement, "pubsub",
+                          [{"xmlns", ?NS_PUBSUB_OWNER}],
+                          [{xmlelement, "configure",
+                            [{"node", node_to_string(Node)}],
+                            [{xmlelement, "x",
+                              [{"xmlns", ?NS_XDATA}, {"type", "form"}],
+                              get_configure_xfields(Type, Options, Lang, Owners)
+                             }]}]}]};
+                   _ ->
+                       {error, ?ERR_FORBIDDEN}
+               end
+       end,
+    transaction(Host, Node, Action, sync_dirty).
+
+get_default(Host, Node, From, Lang) ->
+    Action =
+       fun(#pubsub_node{owners = Owners, type = Type}) ->
+               case node_call(Type, get_affiliation, [Host, Node, From]) of
+                   {result, owner} ->
+                       Options = node_options(Type),
+                       {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}],
+                                  [{xmlelement, "default", [],
+                                    [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
+                                      get_configure_xfields(Type, Options, Lang, Owners)
+                                     }]}]}]};
+                   _ ->
+                       {error, ?ERR_FORBIDDEN}
+               end
+       end,
+    transaction(Host, Node, Action, sync_dirty).
+
+%% Get node option
+%% The result depend of the node type plugin system.
+get_option([], _) -> false;
+get_option(Options, Var) ->
+    case lists:keysearch(Var, 1, Options) of
+       {value, {_Val, Ret}} -> Ret;
+       _ -> false
+    end.
 
-iq_pubsub_owner(Host, From, Type, Lang, SubEl) ->
-    {xmlelement, _, _, SubEls} = SubEl,
-    case xml:remove_cdata(SubEls) of
-       [{xmlelement, Name, Attrs, Els}] ->
-           SNode = xml:get_attr_s("node", Attrs),
-           Node = string:tokens(SNode, "/"),
-           case {Type, Name} of
-               {get, "configure"} ->
-                   get_node_config(Host, From, Node, Lang);
-               {set, "configure"} ->
-                   set_node_config(Host, From, Node, Els, Lang);
-               _ ->
-                   {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
-           end;
-       _ ->
-           {error, ?ERR_BAD_REQUEST}
+%% Get default options from the module plugin.
+node_options(Type) ->
+    Module = list_to_atom(?PLUGIN_PREFIX ++ Type),
+    case catch Module:options() of
+       {'EXIT',{undef,_}} ->
+           DefaultModule = list_to_atom(?PLUGIN_PREFIX++?STDNODE),
+           DefaultModule:options();
+       Result ->
+           Result
     end.
 
-get_node_config(Host, From, Node, Lang) ->
-    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-       [#pubsub_node{info = Info}] ->
-           case get_affiliation(Info, From) of
-               owner ->
-                   Fields = get_node_config_xfields(Node, Info, Lang),
-                   {result, [{xmlelement, "pubsub",
-                              [{"xmlns", ?NS_PUBSUB_OWNER}],
-                              [{xmlelement, "configure",
-                                [{"node", node_to_string(Node)}],
-                                [{xmlelement, "x", [{"xmlns", ?NS_XDATA},
-                                                    {"type", "form"}],
-                                  Fields}]}]}]};
-               _ ->
-                   {error, ?ERR_NOT_AUTHORIZED}
+%% @spec (Options) -> MaxItems
+%%      Options = [Option]
+%%      Option = {Key::atom(), Value::term()}
+%%      MaxItems = integer() | unlimited
+%% @doc <p>Return the maximum number of items for a given node.</p>
+%% <p>Unlimited means that there is no limit in the number of items that can
+%% be stored.</p>
+%% @todo In practice, the current data structure means that we cannot manage
+%% millions of items on a given node. This should be addressed in a new
+%% version.
+max_items(Options) ->
+    case get_option(Options, persist_items) of
+       true ->
+           case get_option(Options, max_items) of
+               false -> unlimited;
+               Result when (Result < 0) -> 0;
+               Result -> Result
            end;
-       _ ->
-           {error, ?ERR_ITEM_NOT_FOUND}
+       false ->
+           case get_option(Options, send_last_published_item) of
+               never -> 0;
+               _ -> 1
+           end
     end.
 
-% TODO: move to jlib.hrl
--define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
-
 -define(BOOL_CONFIG_FIELD(Label, Var),
        ?BOOLXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
-                   get_node_option(Info, Var))).
+                   get_option(Options, Var))).
 
 -define(STRING_CONFIG_FIELD(Label, Var),
        ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
-                     get_node_option(Info, Var))).
+                     get_option(Options, Var))).
 
 -define(INTEGER_CONFIG_FIELD(Label, Var),
        ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
-                     integer_to_list(get_node_option(Info, Var)))).
+                     integer_to_list(get_option(Options, Var)))).
 
 -define(JLIST_CONFIG_FIELD(Label, Var, Opts),
        ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
-                   jlib:jid_to_string(get_node_option(Info, Var)),
+                   jlib:jid_to_string(get_option(Options, Var)),
                    [jlib:jid_to_string(O) || O <- Opts])).
 
 -define(ALIST_CONFIG_FIELD(Label, Var, Opts),
        ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
-                   atom_to_list(get_node_option(Info, Var)),
+                   atom_to_list(get_option(Options, Var)),
                    [atom_to_list(O) || O <- Opts])).
 
-
--define(DEFAULT_OPTIONS,
-       [{deliver_payloads, true},
-        {notify_config, false},
-        {notify_delete, false},
-        {notify_retract, true},
-        {persist_items, true},
-        {max_items, ?MAXITEMS div 2},
-        {subscribe, true},
-        {subscription_model, open},
-        {publish_model, publishers},
-        {max_payload_size, ?MAX_PAYLOAD_SIZE},
-        {send_item_subscribe, false},
-        {presence_based_delivery, false}]).
-
-get_node_option(Info, current_approver) ->
-    Default = hd(get_owners_jids(Info)),
-    Options = Info#nodeinfo.options,
-    element(
-      2, element(2, lists:keysearch(
-                     current_approver, 1,
-                     Options ++ [{current_approver, Default}])));
-get_node_option(#nodeinfo{options = Options}, Var) ->
-    element(
-      2, element(2, lists:keysearch(Var, 1, Options ++ ?DEFAULT_OPTIONS))).
-
-get_max_items(Info) ->
-    case get_node_option(Info, persist_items) of
-       true ->
-           get_node_option(Info, max_items);
-       false ->
-           0
-    end.
-
-get_owners_jids(Info) ->
-    Entities = Info#nodeinfo.entities,
-    Owners =
-       ?DICT:fold(
-          fun(JID,
-              #entity{affiliation = Affiliation,
-                      subscription = Subscription},
-              Acc) ->
-                  case Affiliation of
-                      owner ->
-                          [JID | Acc];
-                      _ ->
-                          Acc
-                  end
-          end, [], Entities),
-    lists:sort(Owners).
-
-
-get_node_config_xfields(Node, Info, Lang) ->
+get_configure_xfields(Type, Options, Owners, Lang) ->
     [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
      ?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
+     ?BOOL_CONFIG_FIELD("Deliver event notifications", deliver_notifications),
      ?BOOL_CONFIG_FIELD("Notify subscribers when the node configuration changes", notify_config),
      ?BOOL_CONFIG_FIELD("Notify subscribers when the node is deleted", notify_delete),
      ?BOOL_CONFIG_FIELD("Notify subscribers when items are removed from the node", notify_retract),
      ?BOOL_CONFIG_FIELD("Persist items to storage", persist_items),
      ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items),
      ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe),
-     ?ALIST_CONFIG_FIELD("Specify the subscriber model", subscription_model,
-                        [open]),
+     ?ALIST_CONFIG_FIELD("Specify the access model", access_model,
+                        [open, authorize, presence, roster, whitelist]),
+     %% XXX: change to list-multi, include current roster groups as options
+     {xmlelement, "field", [{"type", "text-multi"},
+                           {"label", translate:translate(Lang, "Roster groups that may subscribe (if access model is roster)")},
+                           {"var", "pubsub#roster_groups_allowed"}],
+      [{xmlelement, "value", [], [{xmlcdata, Value}]} ||
+         Value <- get_option(Options, roster_groups_allowed)]},
      ?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
                         [publishers, subscribers, open]),
      ?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
-     ?BOOL_CONFIG_FIELD("Send items to new subscribers", send_item_subscribe),
-     ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
-     ?JLIST_CONFIG_FIELD("Specify the current subscription approver", current_approver,
-                        get_owners_jids(Info))
+     ?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)
     ].
 
-
-set_node_config(Host, From, Node, Els, Lang) ->
-    case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
-       [#pubsub_node{info = Info} = N] ->
-           case get_affiliation(Info, From) of
-               owner ->
-                   case xml:remove_cdata(Els) of
-                       [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
-                           case {xml:get_tag_attr_s("xmlns", XEl),
-                                 xml:get_tag_attr_s("type", XEl)} of
-                               {?NS_XDATA, "cancel"} ->
-                                   {result, []};
-                               {?NS_XDATA, "submit"} ->
-                                   CurOpts = Info#nodeinfo.options,
-                                   set_node_config1(
-                                     Host, From, Node, XEl, CurOpts, Lang);
-                               _ ->
-                                   {error, ?ERR_BAD_REQUEST}
-                           end;
-                       _ ->
-                           {error, ?ERR_BAD_REQUEST}
+%%<p>There are several reasons why the node configuration request might fail:</p>
+%%<ul>
+%%<li>The service does not support node configuration.
+%%<li>The requesting entity does not have sufficient privileges to configure the node.
+%%<li>The request did not specify a node.
+%%<li>The node has no configuration options.
+%%<li>The specified node does not exist.
+%%</ul>
+set_configure(Host, Node, From, Els, Lang) ->
+    case xml:remove_cdata(Els) of
+       [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
+           case {xml:get_tag_attr_s("xmlns", XEl), xml:get_tag_attr_s("type", XEl)} of
+               {?NS_XDATA, "cancel"} ->
+                   {result, []};
+               {?NS_XDATA, "submit"} ->
+                   Action =
+                       fun(#pubsub_node{options = Options, type = Type}=N) ->
+                               case node_call(Type, get_affiliation,
+                                              [Host, Node, From]) of
+                                   {result, owner} ->
+                                       case jlib:parse_xdata_submit(XEl) of
+                                           invalid ->
+                                               {error, ?ERR_BAD_REQUEST};
+                                           XData ->
+                                               OldOpts = case Options of
+                                                             [] -> node_options(Type);
+                                                             _ -> Options
+                                                         end,
+                                               case set_xoption(XData, OldOpts) of
+                                                   NewOpts when is_list(NewOpts) ->
+                                                       tree_call(Host, set_node,
+                                                                 [N#pubsub_node{options = NewOpts}]),
+                                                       {result, ok};
+                                                   Err ->
+                                                       Err
+                                               end
+                                       end;
+                                   _ ->
+                                       {error, ?ERR_FORBIDDEN}
+                               end
+                       end,
+                   case transaction(Host, Node, Action, transaction) of
+                       {result, ok} ->
+                           broadcast_config_notification(Host, Node, Lang),
+                           {result, []};
+                       Other ->
+                           Other
                    end;
                _ ->
-                   {error, ?ERR_NOT_AUTHORIZED}
+                   {error, ?ERR_BAD_REQUEST}
            end;
        _ ->
-           {error, ?ERR_ITEM_NOT_FOUND}
-    end.
-
-
-set_node_config1(Host, From, Node, XEl, CurOpts, Lang) ->
-    XData = jlib:parse_xdata_submit(XEl),
-    case XData of
-       invalid ->
-           {error, ?ERR_BAD_REQUEST};
-       _ ->
-           case set_xoption(XData, CurOpts) of
-               NewOpts when is_list(NewOpts) ->
-                   change_node_opts(Host, NewOpts, Node, Lang);
-               Err ->
-                   Err
-           end
+           {error, ?ERR_BAD_REQUEST}
     end.
 
 add_opt(Key, Value, Opts) ->
     Opts1 = lists:keydelete(Key, 1, Opts),
     [{Key, Value} | Opts1].
 
-
 -define(SET_BOOL_XOPT(Opt, Val),
-       case Val of
-           "0" -> set_xoption(Opts, add_opt(Opt, false, NewOpts));
-           "1" -> set_xoption(Opts, add_opt(Opt, true, NewOpts));
-           _ -> {error, ?ERR_BAD_REQUEST}
+       BoolVal = case Val of
+                     "0" -> false;
+                     "1" -> true;
+                     "false" -> false;
+                     "true" -> true;
+                     _ -> error
+                 end,
+       case BoolVal of
+           error -> {error, ?ERR_NOT_ACCEPTABLE};
+           _ -> set_xoption(Opts, add_opt(Opt, false, NewOpts))
        end).
 
 -define(SET_STRING_XOPT(Opt, Val),
@@ -1534,28 +2586,32 @@ add_opt(Key, Value, Opts) ->
 -define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
        case catch list_to_integer(Val) of
            IVal when is_integer(IVal),
-                     IVal >= Min,
-                     IVal =< Max ->
+           IVal >= Min,
+           IVal =< Max ->
                set_xoption(Opts, add_opt(Opt, IVal, NewOpts));
            _ ->
-               {error, ?ERR_BAD_REQUEST}
+               {error, ?ERR_NOT_ACCEPTABLE}
        end).
 
 -define(SET_ALIST_XOPT(Opt, Val, Vals),
        case lists:member(Val, [atom_to_list(V) || V <- Vals]) of
-           true ->
-               set_xoption(Opts, add_opt(Opt, list_to_atom(Val), NewOpts));
-           false ->
-               {error, ?ERR_BAD_REQUEST}
+           true -> set_xoption(Opts, add_opt(Opt, list_to_atom(Val), NewOpts));
+           false -> {error, ?ERR_NOT_ACCEPTABLE}
        end).
 
+-define(SET_LIST_XOPT(Opt, Val),
+       set_xoption(Opts, add_opt(Opt, list_to_atom(Val), NewOpts))).
 
 set_xoption([], NewOpts) ->
     NewOpts;
 set_xoption([{"FORM_TYPE", _} | Opts], NewOpts) ->
     set_xoption(Opts, NewOpts);
+set_xoption([{"pubsub#roster_groups_allowed", Value} | Opts], NewOpts) ->
+    ?SET_LIST_XOPT(roster_groups_allowed, Value);
 set_xoption([{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts) ->
     ?SET_BOOL_XOPT(deliver_payloads, Val);
+set_xoption([{"pubsub#deliver_notifications", [Val]} | Opts], NewOpts) ->
+    ?SET_BOOL_XOPT(deliver_notifications, Val);
 set_xoption([{"pubsub#notify_config", [Val]} | Opts], NewOpts) ->
     ?SET_BOOL_XOPT(notify_config, Val);
 set_xoption([{"pubsub#notify_delete", [Val]} | Opts], NewOpts) ->
@@ -1568,116 +2624,167 @@ set_xoption([{"pubsub#max_items", [Val]} | Opts], NewOpts) ->
     ?SET_INTEGER_XOPT(max_items, Val, 0, ?MAXITEMS);
 set_xoption([{"pubsub#subscribe", [Val]} | Opts], NewOpts) ->
     ?SET_BOOL_XOPT(subscribe, Val);
-set_xoption([{"pubsub#subscription_model", [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(subscription_model, Val, [open]);
+set_xoption([{"pubsub#access_model", [Val]} | Opts], NewOpts) ->
+    ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]);
 set_xoption([{"pubsub#publish_model", [Val]} | Opts], NewOpts) ->
     ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
+set_xoption([{"pubsub#node_type", [Val]} | Opts], NewOpts) ->
+    ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]);
 set_xoption([{"pubsub#max_payload_size", [Val]} | Opts], NewOpts) ->
     ?SET_INTEGER_XOPT(max_payload_size, Val, 0, ?MAX_PAYLOAD_SIZE);
-set_xoption([{"pubsub#send_item_subscribe", [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(send_item_subscribe, Val);
+set_xoption([{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts) ->
+    ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
 set_xoption([{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
     ?SET_BOOL_XOPT(presence_based_delivery, Val);
-set_xoption([{"pubsub#current_approver", _} | Opts], NewOpts) ->
-    % TODO
-    set_xoption(Opts, NewOpts);
-%set_xoption([{"title", [Val]} | Opts], NewOpts) ->
-%    ?SET_STRING_XOPT(title, Val);
+set_xoption([{"pubsub#title", Value} | Opts], NewOpts) ->
+    ?SET_STRING_XOPT(title, Value);
+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([_ | _Opts], _NewOpts) ->
-    {error, ?ERR_BAD_REQUEST}.
-
-
-change_node_opts(Host, NewOpts, Node, Lang) ->
-    F = fun() ->
-               case mnesia:read({pubsub_node, {Host, Node}}) of
-                   [#pubsub_node{info = Info} = N] ->
-                       NewInfo = Info#nodeinfo{options = NewOpts},
-                       mnesia:write(
-                         N#pubsub_node{info = NewInfo}),
-                       {result, []};
-                   [] ->
-                       {error, ?ERR_ITEM_NOT_FOUND}
-               end
-       end,
-    case mnesia:transaction(F) of
-       {atomic, {error, _} = Error} ->
-           Error;
-       {atomic, {result, Res}} ->
-           broadcast_config_notification(Host, Node, Lang),
-           {result, Res};
-       _ ->
-           {error, ?ERR_INTERNAL_SERVER_ERROR}
-    end.
-
-
-
-
-
-find_my_host(LServer) ->
-    Parts = string:tokens(LServer, "."),
-    find_my_host(Parts, ?MYHOSTS).
-
-find_my_host([], _Hosts) ->
-    ?MYNAME;
-find_my_host([_ | Tail] = Parts, Hosts) ->
-    Domain = parts_to_string(Parts),
-    case lists:member(Domain, Hosts) of
-       true ->
-           Domain;
-       false ->
-           find_my_host(Tail, Hosts)
+    {error, ?ERR_NOT_ACCEPTABLE}.
+
+%%%% plugin handling
+
+features() ->
+       [
+        %"access-authorize",   % OPTIONAL
+        "access-open",   % OPTIONAL this relates to access_model option in node_default
+        %"access-presence",   % OPTIONAL
+        %"access-roster",   % OPTIONAL
+        %"access-whitelist",   % OPTIONAL
+        % see plugin "auto-create",   % OPTIONAL
+        % see plugin "auto-subscribe",   % RECOMMENDED
+        "collections",   % RECOMMENDED
+        "config-node",   % RECOMMENDED
+        "create-and-configure",   % RECOMMENDED
+        % see plugin "create-nodes",   % RECOMMENDED
+        %TODO "delete-any",   % OPTIONAL
+        % see plugin "delete-nodes",   % RECOMMENDED
+        % see plugin "filtered-notifications",   % RECOMMENDED
+        %TODO "get-pending",   % OPTIONAL
+        % see plugin "instant-nodes",   % RECOMMENDED
+        %TODO "item-ids",   % RECOMMENDED
+        "last-published",   % RECOMMENDED
+        %TODO "cache-last-item",
+        %TODO "leased-subscription",   % OPTIONAL
+        % see plugin "manage-subscriptions",   % OPTIONAL
+        %TODO "member-affiliation",   % RECOMMENDED
+        %TODO "meta-data",   % RECOMMENDED
+        % see plugin "modify-affiliations",   % OPTIONAL
+        %TODO "multi-collection",   % OPTIONAL
+        %TODO "multi-subscribe",   % OPTIONAL
+        % see plugin "outcast-affiliation",   % RECOMMENDED
+        % see plugin "persistent-items",   % RECOMMENDED
+        "presence-notifications",   % OPTIONAL
+        "presence-subscribe",   % RECOMMENDED
+        % see plugin "publish",   % REQUIRED
+        %TODO "publish-options",   % OPTIONAL
+        "publisher-affiliation",   % RECOMMENDED
+        % see plugin "purge-nodes",   % OPTIONAL
+        % see plugin "retract-items",   % OPTIONAL
+        % see plugin "retrieve-affiliations",   % RECOMMENDED
+        "retrieve-default"   % RECOMMENDED
+        % see plugin "retrieve-items",   % RECOMMENDED
+        % see plugin "retrieve-subscriptions",   % RECOMMENDED
+        % see plugin "subscribe",   % REQUIRED
+        %TODO "subscription-options",   % OPTIONAL
+        % see plugin "subscription-notifications"   % OPTIONAL
+       ].
+features(Type) ->
+    Module = list_to_atom(?PLUGIN_PREFIX++Type),
+    features() ++ case catch Module:features() of
+                     {'EXIT', {undef, _}} -> [];
+                     Result -> Result
+                 end.
+features(Host, []) ->
+    features(?STDNODE);
+features(Host, Node) ->
+    {result, Features} = node_action(Host, Node, features, []),
+    features() ++ Features.
+
+%% @doc <p>node tree plugin call.</p>
+tree_call({_User, Server, _Resource}, Function, Args) ->
+    tree_call(Server, Function, Args);
+tree_call(Host, Function, Args) ->
+    Module = case ets:lookup(gen_mod:get_module_proc(Host, pubsub_state),
+                            nodetree) of
+                [{nodetree, N}] -> N;
+                _ -> list_to_atom(?TREE_PREFIX ++ ?STDNODE)
+            end,
+    catch apply(Module, Function, Args).
+tree_action(Host, Function, Args) ->
+    Fun = fun() -> tree_call(Host, Function, Args) end,
+    catch mnesia:sync_dirty(Fun).
+
+%% @doc <p>node plugin call.</p>
+node_call(Type, Function, Args) ->
+    Module = list_to_atom(?PLUGIN_PREFIX++Type),
+    case catch apply(Module, Function, Args) of
+       {result, Result} -> {result, Result};
+       {error, Error} -> {error, Error};
+       {'EXIT', {undef, Undefined}} ->
+           case Type of
+               ?STDNODE -> {error, {undef, Undefined}};
+               _ -> node_call(?STDNODE, Function, Args)
+           end;
+       {'EXIT', Reason} -> {error, Reason};
+       Result -> {result, Result} %% any other return value is forced as result
     end.
 
-parts_to_string(Parts) ->
-    string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
-                right, $.).
-
-
-
-update_table(Host) ->
-    Fields = record_info(fields, pubsub_node),
-    case mnesia:table_info(pubsub_node, attributes) of
-       Fields ->
-           ok;
-       [node, parent, info] ->
-           ?INFO_MSG("Converting pubsub_node table from "
-                     "{node, parent, info} format", []),
-           {atomic, ok} = mnesia:create_table(
-                            mod_pubsub_tmp_table,
-                            [{disc_only_copies, [node()]},
-                             {type, bag},
-                             {local_content, true},
-                             {record_name, pubsub_node},
-                             {attributes, record_info(fields, pubsub_node)}]),
-           mnesia:del_table_index(pubsub_node, parent),
-           mnesia:transform_table(pubsub_node, ignore, Fields),
-           F1 = fun() ->
-                        mnesia:write_lock_table(mod_pubsub_tmp_table),
-                        mnesia:foldl(
-                          fun(#pubsub_node{host_node = N,
-                                           host_parent = P} = R, _) ->
-                                  mnesia:dirty_write(
-                                    mod_pubsub_tmp_table,
-                                    R#pubsub_node{host_node = {Host, N},
-                                                  host_parent = {Host, P}})
-                          end, ok, pubsub_node)
-                end,
-           mnesia:transaction(F1),
-           mnesia:clear_table(pubsub_node),
-           F2 = fun() ->
-                        mnesia:write_lock_table(pubsub_node),
-                        mnesia:foldl(
-                          fun(R, _) ->
-                                  mnesia:dirty_write(R)
-                          end, ok, mod_pubsub_tmp_table)
-                end,
-           mnesia:transaction(F2),
-           mnesia:delete_table(mod_pubsub_tmp_table);
-       _ ->
-           ?INFO_MSG("Recreating pubsub_node table", []),
-           mnesia:transform_table(pubsub_node, ignore, Fields)
+node_action(Type, Function, Args) ->
+    transaction(fun() ->
+                       node_call(Type, Function, Args)
+               end, sync_dirty).
+node_action(Host, Node, Function, Args) ->
+    transaction(fun() ->
+                       case tree_call(Host, get_node, [Host, Node]) of
+                           #pubsub_node{type=Type} -> node_call(Type, Function, Args);
+                           Other -> Other
+                       end
+               end, sync_dirty).
+
+%% @doc <p>plugin transaction handling.</p>
+transaction(Host, Node, Action, Trans) ->
+    transaction(fun() ->
+                       case tree_call(Host, get_node, [Host, Node]) of
+                           Record when is_record(Record, pubsub_node) -> Action(Record);
+                           Other -> Other
+                       end
+               end, Trans).
+
+transaction(Fun, Trans) ->
+    case catch mnesia:Trans(Fun) of
+       {result, Result} -> {result, Result};
+       {error, Error} -> {error, Error};
+       {atomic, {result, Result}} -> {result, Result};
+       {atomic, {error, Error}} -> {error, Error};
+       {aborted, Reason} ->
+           ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
+           {error, ?ERR_INTERNAL_SERVER_ERROR};
+       {'EXIT', Reason} ->
+           ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
+           {error, ?ERR_INTERNAL_SERVER_ERROR};
+       Other ->
+           ?ERROR_MSG("transaction return internal error: ~p~n", [Other]),
+           {error, ?ERR_INTERNAL_SERVER_ERROR}
     end.
 
-
-
-
+%%%% helpers
+
+%% Add pubsub-specific error element
+extended_error(Error, Ext) ->
+    extended_error(Error, Ext, [{"xmlns", ?NS_PUBSUB_ERRORS}]).
+extended_error(Error, unsupported, Feature) ->
+    extended_error(Error, "unsupported",
+                  [{"xmlns", ?NS_PUBSUB_ERRORS},
+                   {"feature", Feature}]);
+extended_error({xmlelement, Error, Attrs, SubEls}, Ext, ExtAttrs) ->
+    {xmlelement, Error, Attrs,
+     lists:reverse([{xmlelement, Ext, ExtAttrs, []} | SubEls])}.
+
+%% Give a uniq identifier
+uniqid() ->
+    {T1, T2, T3} = now(),
+    lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template
new file mode 100644 (file)
index 0000000..8e514a8
--- /dev/null
@@ -0,0 +1,163 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(__TO_BE_DEFINED__).
+-author(__TO_BE_DEFINED__).
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%% Note on function definition
+%%   included is all defined plugin function
+%%   it's possible not to define some function at all
+%%   in that case, warning will be generated at compilation
+%%   and function call will fail,
+%%   then mod_pubsub will call function from node_default
+%%   (this makes code cleaner, but execution a little bit longer)
+
+%% API definition
+-export([init/3, terminate/2,
+        options/0, features/0,
+        create_node_permission/6,
+        create_node/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost).
+
+options() ->
+    [{node_type, __TO_BE_DEFINED__},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, open},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, true},
+     {presence_based_delivery, false}].
+
+features() ->
+    ["create-nodes",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items",
+     "retrieve-subscriptions",
+     "subscribe",
+     "subscription-notifications"
+    ].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+    node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Host, Node, Owner) ->
+    node_default:create_node(Host, Node, Owner).
+
+delete_node(Host, Removed) ->
+    node_default:delete_node(Host, Removed).
+
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
+    node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
+
+unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
+    node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
+
+delete_item(Host, Node, JID, ItemId) ->
+    node_default:delete_item(Host, Node, JID, ItemId).
+
+purge_node(Host, Node, Owner) ->
+    node_default:purge_node(Host, Node, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+    node_default:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Host, Node) ->
+    node_default:get_node_affiliations(Host, Node).
+
+get_affiliation(Host, Node, Owner) ->
+    node_default:get_affiliation(Host, Node, Owner).
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+    node_default:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Host, Node) ->
+    node_default:get_node_subscriptions(Host, Node).
+
+get_subscription(Host, Node, Owner) ->
+    node_default:get_subscription(Host, Node, Owner).
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl
new file mode 100644 (file)
index 0000000..2d240e1
--- /dev/null
@@ -0,0 +1,163 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(node_buddy).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%% Note on function definition
+%%   included is all defined plugin function
+%%   it's possible not to define some function at all
+%%   in that case, warning will be generated at compilation
+%%   and function call will fail,
+%%   then mod_pubsub will call function from node_default
+%%   (this makes code cleaner, but execution a little bit longer)
+
+%% API definition
+-export([init/3, terminate/2,
+        options/0, features/0,
+        create_node_permission/6,
+        create_node/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost).
+
+options() ->
+    [{node_type, buddy},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, presence},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, true},
+     {presence_based_delivery, false}].
+
+features() ->
+    ["create-nodes",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items",
+     "retrieve-subscriptions",
+     "subscribe",
+     "subscription-notifications"
+    ].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+    node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Host, Node, Owner) ->
+    node_default:create_node(Host, Node, Owner).
+
+delete_node(Host, Removed) ->
+    node_default:delete_node(Host, Removed).
+
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
+    node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
+
+unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
+    node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
+
+delete_item(Host, Node, JID, ItemId) ->
+    node_default:delete_item(Host, Node, JID, ItemId).
+
+purge_node(Host, Node, Owner) ->
+    node_default:purge_node(Host, Node, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+    node_default:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Host, Node) ->
+    node_default:get_node_affiliations(Host, Node).
+
+get_affiliation(Host, Node, Owner) ->
+    node_default:get_affiliation(Host, Node, Owner).
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+    node_default:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Host, Node) ->
+    node_default:get_node_subscriptions(Host, Node).
+
+get_subscription(Host, Node, Owner) ->
+    node_default:get_subscription(Host, Node, Owner).
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl
new file mode 100644 (file)
index 0000000..a0ea2b0
--- /dev/null
@@ -0,0 +1,163 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(node_club).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%% Note on function definition
+%%   included is all defined plugin function
+%%   it's possible not to define some function at all
+%%   in that case, warning will be generated at compilation
+%%   and function call will fail,
+%%   then mod_pubsub will call function from node_default
+%%   (this makes code cleaner, but execution a little bit longer)
+
+%% API definition
+-export([init/3, terminate/2,
+        options/0, features/0,
+        create_node_permission/6,
+        create_node/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost).
+
+options() ->
+    [{node_type, club},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, authorize},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, true},
+     {presence_based_delivery, false}].
+
+features() ->
+    ["create-nodes",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items",
+     "retrieve-subscriptions",
+     "subscribe",
+     "subscription-notifications"
+    ].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+    node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Host, Node, Owner) ->
+    node_default:create_node(Host, Node, Owner).
+
+delete_node(Host, Removed) ->
+    node_default:delete_node(Host, Removed).
+
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
+    node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
+
+unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
+    node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
+
+delete_item(Host, Node, JID, ItemId) ->
+    node_default:delete_item(Host, Node, JID, ItemId).
+
+purge_node(Host, Node, Owner) ->
+    node_default:purge_node(Host, Node, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+    node_default:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Host, Node) ->
+    node_default:get_node_affiliations(Host, Node).
+
+get_affiliation(Host, Node, Owner) ->
+    node_default:get_affiliation(Host, Node, Owner).
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+    node_default:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Host, Node) ->
+    node_default:get_node_subscriptions(Host, Node).
+
+get_subscription(Host, Node, Owner) ->
+    node_default:get_subscription(Host, Node, Owner).
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/node_default.erl b/src/mod_pubsub/node_default.erl
new file mode 100644 (file)
index 0000000..02a6983
--- /dev/null
@@ -0,0 +1,712 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @todo The item table should be handled by the plugin, but plugin that do
+%%% not want to manage it should be able to use the default behaviour.
+%%% @todo Plugin modules should be able to register to receive presence update
+%%% send to pubsub.
+
+%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
+%%% <p>It is used as a default for all unknown PubSub node type.  It can serve
+%%% as a developer basis and reference to build its own custom pubsub node
+%%% types.</p>
+%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
+%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
+%%% development is still a work in progress. However, the system is already
+%%% useable and useful as is. Please, send us comments, feedback and
+%%% improvements.</p>
+
+-module(node_default).
+-author('christophe.romain@process-one.net').
+
+-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/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+%% ================
+%% API definition
+%% ================
+
+%% @spec (Host) -> any()
+%%      Host = mod_pubsub:host()
+%%      ServerHost = host()
+%%      Opts = list()
+%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
+%% implement this function. It can return anything.</p>
+%% <p>This function is mainly used to trigger the setup task necessary for the
+%% plugin. It can be used for example by the developer to create the specific
+%% module database schema if it does not exists yet.</p>
+init(_Host, _ServerHost, _Opts) ->
+    mnesia:create_table(pubsub_state,
+                       [{disc_copies, [node()]},
+                        {attributes, record_info(fields, pubsub_state)}]),
+    StatesFields = record_info(fields, pubsub_state),
+    case mnesia:table_info(pubsub_state, attributes) of
+       StatesFields -> ok;
+       _ ->
+           mnesia:transform_table(pubsub_state, ignore, StatesFields)
+    end,
+    mnesia:create_table(pubsub_item,
+                       [{disc_only_copies, [node()]},
+                        {attributes, record_info(fields, pubsub_item)}]),
+    ItemsFields = record_info(fields, pubsub_item),
+    case mnesia:table_info(pubsub_item, attributes) of
+       ItemsFields -> ok;
+       _ ->
+           mnesia:transform_table(pubsub_item, ignore, ItemsFields)
+    end,
+    ok.
+
+%% @spec (Host) -> any()
+%%      Host = mod_pubsub:host()
+%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
+%% implement this function. It can return anything.</p>
+terminate(_Host, _ServerHost) ->
+    ok.
+
+%% @spec () -> [Option]
+%%      Option = mod_pubsub:nodeOption()
+%% @doc Returns the default pubsub node options.
+%% <p>Example of function return value:</p>
+%%     ```
+%%      [{deliver_payloads, true},
+%%       {notify_config, false},
+%%       {notify_delete, false},
+%%       {notify_retract, true},
+%%       {persist_items, true},
+%%       {max_items, 10},
+%%       {subscribe, true},
+%%       {access_model, open},
+%%       {publish_model, publishers},
+%%       {max_payload_size, 100000},
+%%       {send_last_published_item, never},
+%%       {presence_based_delivery, false}]'''
+options() ->
+    [{node_type, default},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, open},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, true},
+     {presence_based_delivery, false}].
+
+%% @spec () -> []
+%% @doc Returns the node features
+features() ->
+    ["create-nodes",
+     "auto-create",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "manage-subscriptions",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items",
+     "retrieve-subscriptions",
+     "subscribe",
+     "subscription-notifications"
+    ].
+
+%% @spec (Host, Node, Owner, Access) -> bool()
+%%      Host = mod_pubsub:host()
+%%      ServerHost = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      Owner = mod_pubsub:jid()
+%%      Access = all | atom()
+%% @doc Checks if the current user has the permission to create the requested node
+%% <p>In {@link node_default}, the permission is decided by the place in the
+%% hierarchy where the user is creating the node. The access parameter is also
+%% checked in the default module. This parameter depends on the value of the
+%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
+%% <p>This function also check that node can be created a a children of its
+%% parent node</p>
+%% <p>PubSub plugins can redefine the PubSub node creation rights as they
+%% which. They can simply delegate this check to the {@link node_default}
+%% module by implementing this function like this:
+%% ```check_create_user_permission(Host, Node, Owner, Access) ->
+%%        node_default:check_create_user_permission(Host, Node, Owner, Access).'''</p>
+create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
+    LOwner = jlib:jid_tolower(Owner),
+    {User, Server, _Resource} = LOwner,
+    Allowed = case acl:match_rule(ServerHost, Access, LOwner) of
+                 allow ->
+                     if Server == Host ->  %% Server == ServerHost ??
+                             true;
+                        true ->
+                             case Node of
+                                 ["home", Server, User | _] -> true;
+                                 _ -> false
+                             end
+                     end;
+                 _ ->
+                     case Owner of
+                         ?PUBSUB_JID -> true;
+                         _ -> false
+                     end
+             end,
+    ChildOK = true, %% TODO test with ParentNode
+    {result, Allowed and ChildOK}.
+
+%% @spec (Host, Node, Owner) ->
+%%               {result, Result} | exit
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      Owner = mod_pubsub:jid()
+%% @doc <p></p>
+create_node(Host, Node, Owner) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    mnesia:write(#pubsub_state{stateid = {OwnerKey, {Host, Node}},
+                              affiliation = owner, subscription = none}),
+    {result, {default, broadcast}}.
+
+
+%% @spec (Host, Removed) -> ok
+%%      Host = mod_pubsub:host()
+%%      Removed = [mod_pubsub:pubsubNode()]
+%% @doc <p>purge items of deleted nodes after effective deletion.</p>
+delete_node(Host, Removed) ->
+    lists:foreach(
+      fun(Node) ->
+             lists:foreach(
+               fun(#pubsub_state{stateid = StateId, items = Items}) ->
+                       lists:foreach(
+                         fun(ItemId) ->
+                                 mnesia:delete(
+                                   {pubsub_item, {ItemId, {Host, Node}}})
+                         end, Items),
+                       mnesia:delete({pubsub_state, StateId})
+               end,
+               mnesia:match_object(
+                 #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}))
+      end, Removed),
+    {result, {default, broadcast, Removed}}.
+
+%% @spec (Host, Node, Sender, Subscriber, AccessModel, SendLast) ->
+%%              {error, Reason} | {result, Result}
+%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the subscription and passes the
+%% result of the preparation as a record.</li>
+%% <li>This function gets the prepared record and several other parameters and
+%% can decide to:<ul>
+%%  <li>reject the subscription;</li>
+%%  <li>allow it as is, letting the main module perform the database
+%%  persistance;</li>
+%%  <li>allow it, modifying the record. The main module will store the
+%%  modified record;</li>
+%%  <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%%  <ul>
+%%   <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
+%%   subscription will actually be performed.</li>
+%%   <li><tt>true</tt>: Subscribe operation is allowed, based on the
+%%   unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
+%%   parameter contains an error, no subscription will be performed.</li>
+%%   <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
+%%   the {@link mod_pubsub:pubsubState()} record returned replaces the value
+%%   passed in parameter <tt>SubscribeResult</tt>.</li>
+%%   <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
+%%   {@link mod_pubsub:pubsubState()} will be considered as already stored and
+%%   no further persistance operation will be performed. This case is used,
+%%   when the plugin module is doing the persistance by itself or when it want
+%%   to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
+              SendLast, PresenceSubscription, RosterGroup) ->
+    SenderKey = jlib:jid_tolower(Sender),
+    Authorized = (jlib:jid_remove_resource(SenderKey) == jlib:jid_remove_resource(Subscriber)),
+                                               % TODO add some acl check for Authorized ?
+    State = case get_state(Host, Node, Subscriber) of
+               {error, ?ERR_ITEM_NOT_FOUND} ->
+                   #pubsub_state{stateid = {Subscriber, {Host, Node}}}; % TODO: bug on Key ?
+               {result, S} -> S
+           end,
+    #pubsub_state{affiliation = Affiliation,
+                 subscription = Subscription} = State,
+    if
+       not Authorized ->
+           %% JIDs do not match
+           {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
+       Subscription == pending ->
+           %% Requesting entity has pending subscription
+           {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
+       Affiliation == outcast ->
+           %% Requesting entity is blocked
+           {error, ?ERR_FORBIDDEN};
+       (AccessModel == presence) and (not PresenceSubscription) ->
+           %% Entity is not authorized to create a subscription (presence subscription required)
+           {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
+       (AccessModel == roster) and (not RosterGroup) ->
+           %% Entity is not authorized to create a subscription (not in roster group)
+           {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
+       (AccessModel == whitelist) ->  % TODO: to be done
+           %% Node has whitelist access model
+           {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
+       (AccessModel == authorize) -> % TODO: to be done
+           %% Node has authorize access model
+           {error, ?ERR_FORBIDDEN};
+       %%MustPay ->
+       %%      % Payment is required for a subscription
+       %%      {error, ?ERR_PAYMENT_REQUIRED};
+       %%ForbiddenAnonymous ->
+       %%      % Requesting entity is anonymous
+       %%      {error, ?ERR_FORBIDDEN};
+       true ->
+           NewSubscription =
+               if
+                   AccessModel == authorize ->
+                       pending;
+                   %%TODO Affiliation == none -> ?
+                   %%NeedConfiguration ->
+                   %%  unconfigured
+                   true ->
+                       subscribed
+               end,
+           set_state(State#pubsub_state{subscription = NewSubscription}),
+           case NewSubscription of
+               subscribed ->
+                   case SendLast of
+                       never -> {result, {default, NewSubscription}};
+                       _ -> {result, {default, NewSubscription, send_last}}
+                   end;
+               _ ->
+                   {result, {default, NewSubscription}}
+           end
+    end.
+
+%% @spec (Host, Node, Sender, Subscriber, SubID) ->
+%%                     {error, Reason} | {result, []}
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      Sender = mod_pubsub:jid()
+%%      Subscriber = mod_pubsub:jid()
+%%      SubID = string()
+%%      Reason = mod_pubsub:stanzaError()
+%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
+unsubscribe_node(Host, Node, Sender, Subscriber, _SubId) ->
+    SenderKey = jlib:jid_tolower(Sender),
+    Match = jlib:jid_remove_resource(SenderKey) == jlib:jid_remove_resource(Subscriber),
+    Authorized = case Match of
+                    true ->
+                        true;
+                    false ->
+                        case get_state(Host, Node, SenderKey) of % TODO: bug on Key ?
+                            {result, #pubsub_state{affiliation=owner}} -> true;
+                            _ -> false
+                        end
+                end,
+    case get_state(Host, Node, Subscriber) of
+       {error, ?ERR_ITEM_NOT_FOUND} ->
+           %% Requesting entity is not a subscriber
+           {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
+       {result, State} ->
+           if
+               %% Entity did not specify SubID
+               %%SubID == "", ?? ->
+               %%      {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
+               %% Invalid subscription identifier
+               %%InvalidSubID ->
+               %%      {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
+               %% Requesting entity is not a subscriber
+               State#pubsub_state.subscription == none ->
+                   {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
+               %% Requesting entity is prohibited from unsubscribing entity
+               not Authorized ->
+                   {error, ?ERR_FORBIDDEN};
+               true ->
+                   set_state(State#pubsub_state{subscription = none}),
+                   {result, default}
+           end
+    end.
+
+%% @spec (Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+%%              {true, PubsubItem} | {result, Reply}
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      Publisher = mod_pubsub:jid()
+%%      PublishModel = atom()
+%%      MaxItems = integer()
+%%      ItemId = string()
+%%      Payload = term()
+%% @doc <p>Publishes the item passed as parameter.</p>
+%% <p>The mechanism works as follow:
+%% <ul>
+%% <li>The main PubSub module prepares the item to publish and passes the
+%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
+%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
+%%  <li>reject the publication;</li>
+%%  <li>allow the publication as is, letting the main module perform the database persistance;</li>
+%%  <li>allow the publication, modifying the record. The main module will store the modified record;</li>
+%%  <li>allow it, but perform the needed persistance operations.</li></ul>
+%% </li></ul></p>
+%% <p>The selected behaviour depends on the return parameter:
+%%  <ul>
+%%   <li><tt>{error, Reason}</tt>: an iq error result will be return. No
+%%   publication is actually performed.</li>
+%%   <li><tt>true</tt>: Publication operation is allowed, based on the
+%%   unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
+%%   parameter contains an error, no subscription will actually be
+%%   performed.</li>
+%%   <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
+%%   {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
+%%   in parameter <tt>Item</tt>. The persistance will be performed by the main
+%%   module.</li>
+%%   <li><tt>{true, done}</tt>: Publication operation is allowed, but the
+%%   {@link mod_pubsub:pubsubItem()} will be considered as already stored and
+%%   no further persistance operation will be performed. This case is used,
+%%   when the plugin module is doing the persistance by itself or when it want
+%%   to completly disable persistance.</li></ul>
+%% </p>
+%% <p>In the default plugin module, the record is unchanged.</p>
+publish_item(Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
+    PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
+    State = case get_state(Host, Node, PublisherKey) of
+               {error, ?ERR_ITEM_NOT_FOUND} -> #pubsub_state{stateid={PublisherKey, {Host, Node}}};
+               {result, S} -> S
+           end,
+    #pubsub_state{affiliation = Affiliation,
+                 subscription = Subscription} = State,
+    if
+       not ((PublishModel == open)
+            or ((PublishModel == publishers)
+                and ((Affiliation == owner) or (Affiliation == publisher)))
+            or ((PublishModel == subscribers)
+                and (Subscription == subscribed))) ->
+           %% Entity does not have sufficient privileges to publish to node
+           {error, ?ERR_FORBIDDEN};
+       true ->
+           PubId = {PublisherKey, now()},
+           Item = case get_item(Host, Node, ItemId) of
+                      {error, ?ERR_ITEM_NOT_FOUND} ->
+                          #pubsub_item{itemid = {ItemId, {Host, Node}},
+                                       creation = PubId,
+                                       modification = PubId,
+                                       payload = Payload};
+                      {result, OldItem} ->
+                          OldItem#pubsub_item{modification = PubId,
+                                              payload = Payload}
+                  end,
+           Items = [ItemId | State#pubsub_state.items],
+           {result, {NI, OI}} = remove_extra_items(
+                                  Host, Node, MaxItems, Items),
+           set_item(Item),
+           set_state(State#pubsub_state{items = NI}),
+           {result, {default, broadcast, OI}}
+    end.
+
+%% @spec (Host, Node, MaxItems, ItemIds) -> {NewItemIds,OldItemIds}
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      MaxItems = integer() | unlimited
+%%      ItemIds = [ItemId::string()]
+%%      NewItemIds = [ItemId::string()]
+%% @doc <p>This function is used to remove extra items, most notably when the
+%% maximum number of items has been reached.</p>
+%% <p>This function is used internally by the core PubSub module, as no
+%% permission check is performed.</p>
+%% <p>In the default plugin module, the oldest items are removed, but other
+%% rules can be used.</p>
+%% <p>If another PubSub plugin wants to delegate the item removal (and if the
+%% plugin is using the default pubsub storage), it can implements this function like this:
+%% ```remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+%%        node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).'''</p>
+remove_extra_items(_Host, _Node, unlimited, ItemIds) ->
+    {result, {ItemIds, []}};
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    NewItems = lists:sublist(ItemIds, MaxItems),
+    OldItems = lists:nthtail(length(NewItems), ItemIds),
+    %% Remove extra items:
+    lists:foreach(fun(ItemId) ->
+                         mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
+                 end, OldItems),
+    %% Return the new items list:
+    {result, {NewItems, OldItems}}.
+
+%% @spec (Host, Node, JID, ItemId) ->
+%%               {error, Reason::stanzaError()} |
+%%               {result, []}
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      JID = mod_pubsub:jid()
+%%      ItemId = string()
+%% @doc <p>Triggers item deletion.</p>
+%% <p>Default plugin: The user performing the deletion must be the node owner
+%% or a node publisher e item publisher.</p>
+delete_item(Host, Node, Publisher, ItemId) ->
+    PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
+    State = case get_state(Host, Node, PublisherKey) of
+               {error, ?ERR_ITEM_NOT_FOUND} ->
+                   #pubsub_state{stateid = {PublisherKey, {Host, Node}}};
+               {result, S} ->
+                   S
+           end,
+    #pubsub_state{affiliation = Affiliation, items = Items} = State,
+    Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
+       orelse case get_item(Host, Node, ItemId) of
+                  {result, #pubsub_item{creation = {PublisherKey, _}}} -> true;
+                  _ -> false
+              end,
+    if
+       not Allowed ->
+           %% Requesting entity does not have sufficient privileges
+           {error, ?ERR_FORBIDDEN};
+       true ->
+           case get_item(Host, Node, ItemId) of
+               {result, _} ->
+                   mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}),
+                   NewItems = lists:delete(ItemId, Items),
+                   set_state(State#pubsub_state{items = NewItems}),
+                   {result, {default, broadcast}};
+               _ ->
+                   %% Non-existent node or item
+                   {error, ?ERR_ITEM_NOT_FOUND}
+           end
+    end.
+
+%% @spec (TODO)
+purge_node(Host, Node, Owner) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    case get_state(Host, Node, OwnerKey) of
+       {error, ?ERR_ITEM_NOT_FOUND} ->
+           %% This should not append (case node does not exists)
+           {error, ?ERR_ITEM_NOT_FOUND};
+       {result, #pubsub_state{items = Items, affiliation = owner}} ->
+           lists:foreach(fun(ItemId) ->
+                                 mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
+                         end, Items),
+           {result, {default, broadcast}};
+       _ ->
+           %% Entity is not an owner
+           {error, ?ERR_FORBIDDEN}
+    end.
+
+%% @spec (Host, JID) -> [{Node,Affiliation}]
+%%      Host = host()
+%%      JID = mod_pubsub:jid()
+%% @doc <p>Return the current affiliations for the given user</p>
+%% <p>The default module reads affiliations in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
+get_entity_affiliations(Host, Owner) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    States = mnesia:match_object(
+              #pubsub_state{stateid = {OwnerKey, {Host, '_'}},
+                            _ = '_'}),
+    Tr = fun(#pubsub_state{stateid = {_, {_, N}}, affiliation = A}) ->
+                {N, A}
+        end,
+    {result, lists:map(Tr, States)}.
+
+get_node_affiliations(Host, Node) ->
+    States = mnesia:match_object(
+              #pubsub_state{stateid = {'_', {Host, Node}},
+                            _ = '_'}),
+    Tr = fun(#pubsub_state{stateid = {J, {_, _}}, affiliation = A}) ->
+                {J, A}
+        end,
+    {result, lists:map(Tr, States)}.
+
+get_affiliation(Host, Node, Owner) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    Affiliation = case get_state(Host, Node, OwnerKey) of
+                     {result, #pubsub_state{affiliation = A}} -> A;
+                     _ -> unknown
+                 end,
+    {result, Affiliation}.
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    Record = case get_state(Host, Node, OwnerKey) of
+                {error, ?ERR_ITEM_NOT_FOUND} ->
+                    #pubsub_state{stateid = {OwnerKey, {Host, Node}},
+                                  affiliation = Affiliation};
+                {result, State} ->
+                    State#pubsub_state{affiliation = Affiliation}
+            end,
+    set_state(Record),
+    ok.
+
+%% @spec (Host) -> [{Node,Subscription}]
+%%      Host = host()
+%%      JID = mod_pubsub:jid()
+%% @doc <p>Return the current subscriptions for the given user</p>
+%% <p>The default module reads subscriptions in the main Mnesia
+%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
+%% table, it should return an empty list, as the affiliation will be read by
+%% the default PubSub module. Otherwise, it should return its own affiliation,
+%% that will be added to the affiliation stored in the main
+%% <tt>pubsub_state</tt> table.</p>
+get_entity_subscriptions(Host, Owner) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    States = mnesia:match_object(
+              #pubsub_state{stateid = {OwnerKey, {Host, '_'}},
+                            _ = '_'}),
+    Tr = fun(#pubsub_state{stateid = {_, {_, N}}, subscription = S}) ->
+                {N, S}
+        end,
+    {result, lists:map(Tr, States)}.
+
+get_node_subscriptions(Host, Node) ->
+    States = mnesia:match_object(
+              #pubsub_state{stateid = {'_', {Host, Node}},
+                            _ = '_'}),
+    Tr = fun(#pubsub_state{stateid = {J, {_, _}}, subscription = S}) ->
+                {J, S}
+        end,
+    {result, lists:map(Tr, States)}.
+
+get_subscription(Host, Node, Owner) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    Subscription = case get_state(Host, Node, OwnerKey) of
+                      {result, #pubsub_state{subscription = S}} -> S;
+                      _ -> unknown
+                  end,
+    {result, Subscription}.
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    Record = case get_state(Host, Node, OwnerKey) of
+                {error, ?ERR_ITEM_NOT_FOUND} ->
+                    #pubsub_state{stateid = {OwnerKey, {Host, Node}},
+                                  subscription = Subscription};
+                {result, State} ->
+                    State#pubsub_state{subscription = Subscription}
+            end,
+    set_state(Record),
+    ok.
+
+%% @spec (Host, Node) -> [States] | []
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      Item = mod_pubsub:pubsubItems()
+%% @doc Returns the list of stored states for a given node.
+%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_state table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the states where they wants (for example in a
+%% relational database).</p>
+%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
+%% they can implement this function like this:
+%% ```get_states(Host, Node) ->
+%%        node_default:get_states(Host, Node).'''</p>
+get_states(Host, Node) ->
+    States = mnesia:match_object(
+              #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}),
+    {result, States}.
+
+%% @spec (JID, Host, Node) -> [State] | []
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      JID = mod_pubsub:jid()
+%%      State = mod_pubsub:pubsubItems()
+%% @doc <p>Returns a state (one state list), given its reference.</p>
+get_state(Host, Node, JID) ->
+    case mnesia:read({pubsub_state, {JID, {Host, Node}}}) of
+       [State] when is_record(State, pubsub_state) ->
+           {result, State};
+       _ ->
+           {error, ?ERR_ITEM_NOT_FOUND}
+    end.
+
+%% @spec (State) -> ok | {error, ?ERR_INTERNAL_SERVER_ERROR}
+%%      State = mod_pubsub:pubsubStates()
+%% @doc <p>Write a state into database.</p>
+set_state(State) when is_record(State, pubsub_state) ->
+    mnesia:write(State);
+set_state(_) ->
+    {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+%% @spec (Host, Node) -> [Items] | []
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      Items = mod_pubsub:pubsubItems()
+%% @doc Returns the list of stored items for a given node.
+%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
+%% <p>We can consider that the pubsub_item table have been created by the main
+%% mod_pubsub module.</p>
+%% <p>PubSub plugins can store the items where they wants (for example in a
+%% relational database), or they can even decide not to persist any items.</p>
+%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
+%% they can implement this function like this:
+%% ```get_items(Host, Node) ->
+%%        node_default:get_items(Host, Node).'''</p>
+get_items(Host, Node) ->
+    Items = mnesia:match_object(
+             #pubsub_item{itemid = {'_', {Host, Node}}, _ = '_'}),
+    {result, Items}.
+
+%% @spec (Host, Node, ItemId) -> [Item] | []
+%%      Host = mod_pubsub:host()
+%%      Node = mod_pubsub:pubsubNode()
+%%      ItemId = string()
+%%      Item = mod_pubsub:pubsubItems()
+%% @doc <p>Returns an item (one item list), given its reference.</p>
+get_item(Host, Node, ItemId) ->
+    case mnesia:read({pubsub_item, {ItemId, {Host, Node}}}) of
+       [Item] when is_record(Item, pubsub_item) ->
+           {result, Item};
+       _ ->
+           {error, ?ERR_ITEM_NOT_FOUND}
+    end.
+
+%% @spec (Item) -> ok | {error, ?ERR_INTERNAL_SERVER_ERROR}
+%%      Item = mod_pubsub:pubsubItems()
+%% @doc <p>Write a state into database.</p>
+set_item(Item) when is_record(Item, pubsub_item) ->
+    mnesia:write(Item);
+set_item(_) ->
+    {error, ?ERR_INTERNAL_SERVER_ERROR}.
diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl
new file mode 100644 (file)
index 0000000..e0b9d02
--- /dev/null
@@ -0,0 +1,166 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(node_dispatch).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%%% @doc <p>The <strong>{@module}</strong> module is a PubSub plugin whose
+%%% goal is to republished each published item to all its children.</p>
+%%% <p>Users cannot subscribe to this node, but are supposed to subscribe to
+%%% its children.</p>
+%%% This module can not work with virtual nodetree
+
+%% API definition
+-export([init/3, terminate/2,
+        options/0, features/0,
+        create_node_permission/6,
+        create_node/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost).
+
+options() ->
+    [{node_type, dispatch},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, open},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, true},
+     {presence_based_delivery, false}].
+
+features() ->
+    ["create-nodes",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     %%"purge-nodes",
+     %%"retract-items",
+     %%"retrieve-affiliations",
+     "retrieve-items",
+     %%"retrieve-subscriptions",
+     %%"subscribe",
+     %%"subscription-notifications",
+    ].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+    node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Host, Node, Owner) ->
+    node_default:create_node(Host, Node, Owner).
+
+delete_node(Host, Removed) ->
+    node_default:delete_node(Host, Removed).
+
+subscribe_node(_Host, _Node, _Sender, _Subscriber, _AccessModel,
+              _SendLast, _PresenceSubscription, _RosterGroup) ->
+    {error, ?ERR_FORBIDDEN}.
+
+unsubscribe_node(_Host, _Node, _Sender, _Subscriber, _SubID) ->
+    {error, ?ERR_FORBIDDEN}.
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    lists:foreach(fun(SubNode) ->
+                         node_default:publish_item(
+                           Host, SubNode, Publisher, Model,
+                           MaxItems, ItemId, Payload)
+                 end, nodetree_default:get_subnodes(Host, Node)).
+
+remove_extra_items(_Host, _Node, _MaxItems, ItemIds) ->
+    {result, {ItemsIds, []}}.
+
+delete_item(_Host, _Node, _JID, _ItemId) ->
+    {error, ?ERR_ITEM_NOT_FOUND}.
+
+purge_node(_Host, _Node, _Owner) ->
+    {error, ?ERR_FORBIDDEN}.
+
+get_entity_affiliations(_Host, _Owner) ->
+    {result, []}.
+
+get_node_affiliations(_Host, _Node) ->
+    {result, []}.
+
+get_affiliation(_Host, _Node, _Owner) ->
+    {result, []}.
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(_Host, _Owner) ->
+    {result, []}.
+
+get_node_subscriptions(_Host, _Node) ->
+    {result, []}.
+
+get_subscription(_Host, _Node, _Owner) ->
+    {result, []}.
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl
new file mode 100644 (file)
index 0000000..2ec568f
--- /dev/null
@@ -0,0 +1,173 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
+%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
+
+-module(node_pep).
+-author('christophe.romain@process-one.net').
+
+-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/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts),
+    ok.
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost),
+    ok.
+
+options() ->
+    [{node_type, pep},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, false},
+     {persist_items, false},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, presence},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, on_sub_and_presence},
+     {deliver_notifications, true},
+     {presence_based_delivery, true}].
+
+features() ->
+    ["create-nodes", %*
+     "auto-create", %*
+     "delete-nodes", %*
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish", %*
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items", %*
+     "retrieve-subscriptions",
+     "subscribe", %*
+     "auto-subscribe", %*
+     "filtered-notifications" %*
+    ].
+
+create_node_permission(_Host, _ServerHost, _Node, _ParentNode, _Owner, _Access) ->
+    %% TODO may we check bare JID match ?
+    {result, true}.
+
+create_node(Host, Node, Owner) ->
+    case node_default:create_node(Host, Node, Owner) of
+       {result, _} -> {result, []};
+       Error -> Error
+    end.
+
+delete_node(Host, Removed) ->
+    case node_default:delete_node(Host, Removed) of
+       {result, {_, _, Removed}} -> {result, {[], Removed}};
+       Error -> Error
+    end.
+
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
+              SendLast, PresenceSubscription, RosterGroup) ->
+    node_default:subscribe_node(
+      Host, Node, Sender, Subscriber, AccessModel, SendLast,
+      PresenceSubscription, RosterGroup).
+
+unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
+    case node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID) of
+       {error, Error} -> {error, Error};
+       {result, _} -> {result, []}
+    end.
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
+
+delete_item(Host, Node, JID, ItemId) ->
+    node_default:delete_item(Host, Node, JID, ItemId).
+
+purge_node(Host, Node, Owner) ->
+    node_default:purge_node(Host, Node, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+    node_default:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Host, Node) ->
+    node_default:get_node_affiliations(Host, Node).
+
+get_affiliation(Host, Node, Owner) ->
+    node_default:get_affiliation(Host, Node, Owner).
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+    node_default:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Host, Node) ->
+    node_default:get_node_subscriptions(Host, Node).
+
+get_subscription(Host, Node, Owner) ->
+    node_default:get_subscription(Host, Node, Owner).
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl
new file mode 100644 (file)
index 0000000..9b17cb2
--- /dev/null
@@ -0,0 +1,166 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(node_private).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%% Note on function definition
+%%   included is all defined plugin function
+%%   it's possible not to define some function at all
+%%   in that case, warning will be generated at compilation
+%%   and function call will fail,
+%%   then mod_pubsub will call function from node_default
+%%   (this makes code cleaner, but execution a little bit longer)
+
+%% API definition
+-export([init/3, terminate/2,
+        options/0, features/0,
+        create_node_permission/6,
+        create_node/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost).
+
+options() ->
+    [{node_type, private},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, whitelist},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, false},
+     {presence_based_delivery, false}].
+
+features() ->
+    ["create-nodes",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items",
+     "retrieve-subscriptions",
+     "subscribe",
+     "subscription-notifications"
+    ].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+    node_default:create_node_permission(Host, ServerHost, Node, ParentNode,
+                                       Owner, Access).
+
+create_node(Host, Node, Owner) ->
+    node_default:create_node(Host, Node, Owner).
+
+delete_node(Host, Removed) ->
+    node_default:delete_node(Host, Removed).
+
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast,
+              PresenceSubscription, RosterGroup) ->
+    node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
+                               SendLast, PresenceSubscription, RosterGroup).
+
+unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
+    node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
+
+delete_item(Host, Node, JID, ItemId) ->
+    node_default:delete_item(Host, Node, JID, ItemId).
+
+purge_node(Host, Node, Owner) ->
+    node_default:purge_node(Host, Node, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+    node_default:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Host, Node) ->
+    node_default:get_node_affiliations(Host, Node).
+
+get_affiliation(Host, Node, Owner) ->
+    node_default:get_affiliation(Host, Node, Owner).
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+    node_default:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Host, Node) ->
+    node_default:get_node_subscriptions(Host, Node).
+
+get_subscription(Host, Node, Owner) ->
+    node_default:get_subscription(Host, Node, Owner).
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl
new file mode 100644 (file)
index 0000000..73e78ee
--- /dev/null
@@ -0,0 +1,165 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% $Id: node_public.erl 100 2007-11-15 13:04:44Z mremond $
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+-module(node_public).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_node).
+
+%% Note on function definition
+%%   included is all defined plugin function
+%%   it's possible not to define some function at all
+%%   in that case, warning will be generated at compilation
+%%   and function call will fail,
+%%   then mod_pubsub will call function from node_default
+%%   (this makes code cleaner, but execution a little bit longer)
+
+%% API definition
+-export([init/3, terminate/2,
+        options/0, features/0,
+        create_node_permission/6,
+        create_node/3,
+        delete_node/2,
+        purge_node/3,
+        subscribe_node/8,
+        unsubscribe_node/5,
+        publish_item/7,
+        delete_item/4,
+        remove_extra_items/4,
+        get_entity_affiliations/2,
+        get_node_affiliations/2,
+        get_affiliation/3,
+        set_affiliation/4,
+        get_entity_subscriptions/2,
+        get_node_subscriptions/2,
+        get_subscription/3,
+        set_subscription/4,
+        get_states/2,
+        get_state/3,
+        set_state/1,
+        get_items/2,
+        get_item/3,
+        set_item/1
+       ]).
+
+
+init(Host, ServerHost, Opts) ->
+    node_default:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+    node_default:terminate(Host, ServerHost).
+
+options() ->
+    [{node_type, public},
+     {deliver_payloads, true},
+     {notify_config, false},
+     {notify_delete, false},
+     {notify_retract, true},
+     {persist_items, true},
+     {max_items, ?MAXITEMS div 2},
+     {subscribe, true},
+     {access_model, open},
+     {access_roster_groups, []},
+     {publish_model, publishers},
+     {max_payload_size, ?MAX_PAYLOAD_SIZE},
+     {send_last_published_item, never},
+     {deliver_notifications, true},
+     {presence_based_delivery, false}].
+
+features() ->
+    ["create-nodes",
+     "delete-nodes",
+     "instant-nodes",
+     "item-ids",
+     "outcast-affiliation",
+     "persistent-items",
+     "publish",
+     "purge-nodes",
+     "retract-items",
+     "retrieve-affiliations",
+     "retrieve-items",
+     "retrieve-subscriptions",
+     "subscribe",
+     "subscription-notifications"
+    ].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+    node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Host, Node, Owner) ->
+    node_default:create_node(Host, Node, Owner).
+
+delete_node(Host, Removed) ->
+    node_default:delete_node(Host, Removed).
+
+subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
+    node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
+
+unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
+    node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
+
+publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
+    node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Host, Node, MaxItems, ItemIds) ->
+    node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
+
+delete_item(Host, Node, JID, ItemId) ->
+    node_default:delete_item(Host, Node, JID, ItemId).
+
+purge_node(Host, Node, Owner) ->
+    node_default:purge_node(Host, Node, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+    node_default:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Host, Node) ->
+    node_default:get_node_affiliations(Host, Node).
+
+get_affiliation(Host, Node, Owner) ->
+    node_default:get_affiliation(Host, Node, Owner).
+
+set_affiliation(Host, Node, Owner, Affiliation) ->
+    node_default:set_affiliation(Host, Node, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+    node_default:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Host, Node) ->
+    node_default:get_node_subscriptions(Host, Node).
+
+get_subscription(Host, Node, Owner) ->
+    node_default:get_subscription(Host, Node, Owner).
+
+set_subscription(Host, Node, Owner, Subscription) ->
+    node_default:set_subscription(Host, Node, Owner, Subscription).
+
+get_states(Host, Node) ->
+    node_default:get_states(Host, Node).
+
+get_state(Host, Node, JID) ->
+    node_default:get_state(Host, Node, JID).
+
+set_state(State) ->
+    node_default:set_state(State).
+
+get_items(Host, Node) ->
+    node_default:get_items(Host, Node).
+
+get_item(Host, Node, ItemId) ->
+    node_default:get_items(Host, Node, ItemId).
+
+set_item(Item) ->
+    node_default:set_item(Item).
diff --git a/src/mod_pubsub/nodetree_default.erl b/src/mod_pubsub/nodetree_default.erl
new file mode 100644 (file)
index 0000000..3d05b5b
--- /dev/null
@@ -0,0 +1,164 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
+%%% <p>It is used as a default for all unknown PubSub node type.  It can serve
+%%% as a developer basis and reference to build its own custom pubsub node tree
+%%% types.</p>
+%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
+%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
+%%% development is still a work in progress. However, the system is already
+%%% useable and useful as is. Please, send us comments, feedback and
+%%% improvements.</p>
+
+-module(nodetree_default).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_nodetree).
+
+-export([init/3,
+        terminate/2,
+        options/0,
+        set_node/1,
+        get_node/2,
+        get_nodes/1,
+        get_subnodes/2,
+        get_subnodes_tree/2,
+        create_node/5,
+        delete_node/2
+       ]).
+
+
+%% ================
+%% API definition
+%% ================
+
+%% @spec (Host) -> any()
+%%     Host = mod_pubsub:host()
+%%     ServerHost = host()
+%%     Opts = list()
+%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
+%% implement this function. It can return anything.</p>
+%% <p>This function is mainly used to trigger the setup task necessary for the
+%% plugin. It can be used for example by the developer to create the specific
+%% module database schema if it does not exists yet.</p>
+init(_Host, _ServerHost, _Opts) ->
+    mnesia:create_table(pubsub_node,
+                       [{disc_copies, [node()]},
+                        {attributes, record_info(fields, pubsub_node)},
+                        {index, [type,parentid]}]),
+    NodesFields = record_info(fields, pubsub_node),
+    case mnesia:table_info(pubsub_node, attributes) of
+       NodesFields -> ok;
+       _ -> mnesia:transform_table(pubsub_node, ignore, NodesFields)
+    end,
+    ok.
+terminate(_Host, _ServerHost) ->
+    ok.
+
+%% @spec () -> [Option]
+%%     Option = mod_pubsub:nodetreeOption()
+%% @doc Returns the default pubsub node tree options.
+options() ->
+    [{virtual_tree, false}].
+
+%% @spec (NodeRecord) -> ok | {error, Reason}
+%%     Record = mod_pubsub:pubsub_node()
+set_node(Record) when is_record(Record, pubsub_node) ->
+    mnesia:write(Record);
+set_node(_) ->
+    {error, ?ERR_INTERNAL_SERVER_ERROR}.
+
+%% @spec (Host, Node) -> pubsubNode() | {error, Reason}
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+get_node(Host, Node) ->
+    case catch mnesia:read({pubsub_node, {Host, Node}}) of
+       [Record] when is_record(Record, pubsub_node) -> Record;
+       [] -> {error, ?ERR_ITEM_NOT_FOUND};
+       Error -> Error
+    end.
+
+%% @spec (Key) -> [pubsubNode()] | {error, Reason}
+%%     Key = mod_pubsub:host() | mod_pubsub:jid()
+get_nodes(Key) ->
+    mnesia:match_object(#pubsub_node{nodeid = {Key, '_'}, _ = '_'}).
+
+%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+get_subnodes(Host, Node) ->
+    mnesia:index_read(pubsub_node, {Host, Node}, #pubsub_node.parentid).
+
+%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+get_subnodes_tree(Host, Node) ->
+    mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}}, Acc) ->
+                        case lists:prefix(Node, N) and (H == Host) of
+                            true -> [N | Acc];
+                            _ -> Acc
+                        end
+                end, [], pubsub_node).
+
+%% @spec (Key, Node, Type, Owner, Options) -> ok | {error, Reason}
+%%     Key = mod_pubsub:host() | mod_pubsub:jid()
+%%     Node = mod_pubsub:pubsubNode()
+%%     NodeType = mod_pubsub:nodeType()
+%%     Owner = mod_pubsub:jid()
+%%     Options = list()
+create_node(Key, Node, Type, Owner, Options) ->
+    OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+    case mnesia:read({pubsub_node, {Key, Node}}) of
+       [] ->
+           Parent = lists:sublist(Node, length(Node) - 1),
+           ParentExists =
+               case Key of
+                   {_U, _S, _R} ->
+                       %% This is special case for PEP handling
+                       %% PEP does not uses hierarchy
+                       true;
+                   _ ->
+                       (Parent == []) orelse
+                           case mnesia:read({pubsub_node, {Key, Parent}}) of
+                               [] -> false;
+                               _ -> true
+                           end
+               end,
+           case ParentExists of
+               true ->
+                   %% Service requires registration
+                   %%{error, ?ERR_REGISTRATION_REQUIRED};
+                   mnesia:write(#pubsub_node{nodeid = {Key, Node},
+                                             parentid = {Key, Parent},
+                                             type = Type,
+                                             owners = [OwnerKey],
+                                             options = Options});
+               false ->
+                   %% Requesting entity is prohibited from creating nodes
+                   {error, ?ERR_FORBIDDEN}
+           end;
+       _ ->
+           %% NodeID already exists
+           {error, ?ERR_CONFLICT}
+    end.
+
+%% @spec (Key, Node) -> [mod_pubsub:node()]
+%%     Key = mod_pubsub:host() | mod_pubsub:jid()
+%%     Node = mod_pubsub:pubsubNode()
+delete_node(Key, Node) ->
+    Removed = get_subnodes_tree(Key, Node),
+    lists:foreach(fun(N) ->
+                         mnesia:delete({pubsub_node, {Key, N}})
+                 end, Removed),
+    Removed.
diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl
new file mode 100644 (file)
index 0000000..d3f3de8
--- /dev/null
@@ -0,0 +1,119 @@
+%%% ====================================================================
+%%% This software is copyright 2007, Process-one.
+%%%
+%%% @copyright 2007 Process-one
+%%% @author Christophe Romain <christophe.romain@process-one.net>
+%%%   [http://www.process-one.net/]
+%%% @version {@vsn}, {@date} {@time}
+%%% @end
+%%% ====================================================================
+
+%%% @doc The module <strong>{@module}</strong> is the PubSub node tree plugin that
+%%% allow virtual nodes handling.
+%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
+%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
+%%% development is still a work in progress. However, the system is already
+%%% useable and useful as is. Please, send us comments, feedback and
+%%% improvements.</p>
+
+-module(nodetree_virtual).
+-author('christophe.romain@process-one.net').
+
+-include("pubsub.hrl").
+-include("jlib.hrl").
+
+-behaviour(gen_pubsub_nodetree).
+
+-export([init/3,
+        terminate/2,
+        options/0,
+        set_node/1,
+        get_node/2,
+        get_nodes/1,
+        get_subnodes/2,
+        get_subnodes_tree/2,
+        create_node/5,
+        delete_node/2
+       ]).
+
+%% ================
+%% API definition
+%% ================
+
+%% @spec (Host) -> any()
+%%     Host = mod_pubsub:host()
+%%     ServerHost = host()
+%%     Opts = list()
+%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
+%% implement this function. It can return anything.</p>
+%% <p>This function is mainly used to trigger the setup task necessary for the
+%% plugin. It can be used for example by the developer to create the specific
+%% module database schema if it does not exists yet.</p>
+init(_Host, _ServerHost, _Opts) ->
+    ok.
+terminate(_Host, _ServerHost) ->
+    ok.
+
+%% @spec () -> [Option]
+%%     Option = mod_pubsub:nodetreeOption()
+%% @doc <p>Returns the default pubsub node tree options.</p>
+options() ->
+    [{virtual_tree, true}].
+
+%% @spec (NodeRecord) -> ok | {error, Reason}
+%%     NodeRecord = mod_pubsub:pubsub_node()
+%% @doc <p>No node record is stored on database. Just do nothing.</p>
+set_node(_NodeRecord) ->
+    ok.
+
+%% @spec (Host, Node) -> pubsubNode()
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
+%% as existing. Node record contains default values.</p>
+get_node(Host, Node) ->
+    #pubsub_node{nodeid = {Host, Node}}.
+
+%% @spec (Key) -> [pubsubNode()]
+%%     Host = mod_pubsub:host() | mod_pubsub:jid()
+%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
+%% as existing. Nodes list can not be determined.</p>
+get_nodes(_Key) ->
+    [].
+
+%% @spec (Host, Index) -> [pubsubNode()]
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
+get_subnodes(_Host, _Node) ->
+    [].
+
+%% @spec (Host, Index) -> [pubsubNode()]
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
+get_subnodes_tree(_Host, _Node) ->
+    [].
+
+%% @spec (Host, Node, Type, Owner, Options) -> ok
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+%%     Type = mod_pubsub:nodeType()
+%%     Owner = mod_pubsub:jid()
+%%     Options = list()
+%% @doc <p>No node record is stored on database. Any valid node
+%% is considered as already created.</p>
+%% <p>default allowed nodes: /home/host/user/any/node/name</p>
+create_node(_Host, Node, _Type, {UserName, UserHost, _}, _Options) ->
+    case Node of
+       ["home", UserHost, UserName | _] -> {error, ?ERR_CONFLICT};
+       _ -> {error, ?ERR_NOT_ALLOWED}
+    end.
+
+%% @spec (Host, Node) -> [mod_pubsub:node()]
+%%     Host = mod_pubsub:host()
+%%     Node = mod_pubsub:pubsubNode()
+%% @doc <p>Virtual node tree does not handle parent/child.
+%% node deletion just affects the corresponding node.</p>
+delete_node(_Host, Node) ->
+    [Node].
diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl
new file mode 100644 (file)
index 0000000..e5bbe45
--- /dev/null
@@ -0,0 +1,119 @@
+%%% ====================================================================
+%%% This software is copyright 2006, Process-one.
+%%%
+%%% This file contains pubsub types definition.
+%%% ====================================================================
+
+%% -------------------------------
+%% Pubsub constants
+-define(PUBSUB_JID, {jid, "", Host, "", "", Host, ""}).
+-define(ERR_EXTENDED(E,C), mod_pubsub:extended_error(E,C)).
+
+% TODO: move to jlib.hrl
+-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
+-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
+
+%% this is currently a hard limit.
+%% Would be nice to have it configurable. 
+-define(MAXITEMS, 20).
+-define(MAX_PAYLOAD_SIZE, 60000).
+
+%% -------------------------------
+%% Pubsub types
+
+%%% @type host() = string().
+%%% <p><tt>host</tt> is the name of the PubSub service. For example, it can be
+%%% <tt>"pubsub.localhost"</tt>.</p>
+
+%%% @type pubsubNode() = [string()]. 
+%%% <p>A node is defined by a list of its ancestors. The last element is the name
+%%% of the current node. For example: 
+%%% ```["home", "localhost", "cromain", "node1"]'''</p>
+
+%%% @type stanzaError() = #xmlelement{}.
+%%% Example: 
+%%%    ```{xmlelement, "error",
+%%%        [{"code", Code}, {"type", Type}],
+%%%        [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
+
+%%% @type pubsubIQResponse() = #xmlelement{}.
+%%% Example:
+%%%    ```{xmlelement, "pubsub",
+%%%               [{"xmlns", ?NS_PUBSUB_EVENT}],
+%%%               [{xmlelement, "affiliations", [],
+%%%               []}]}'''
+
+%%% @type nodeOption() = {Option::atom(), Value::term()}.
+%%% Example:
+%%% ```{deliver_payloads, true}'''
+
+%%% @type nodeType() = string().
+%%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
+%%% plugin to use to manage a given node. For example, it can be
+%%% <tt>"default"</tt>, <tt>"collection"</tt> or <tt>"blog"</tt>.</p>
+
+%%% @type jid() = #jid{
+%%%    user = string(),
+%%%    server = string(),
+%%%    resource = string(),
+%%%    luser = string(),
+%%%    lserver = string(),
+%%%    lresource = string()}.
+
+%%% @type usr() = {User::string(), Server::string(), Resource::string()}.
+
+%%% @type affiliation() = none | owner | publisher | outcast.
+%%% @type subscription() = none | pending | unconfigured | subscribed.
+
+%%% @type pubsubNode() = #pubsub_node{
+%%%    nodeid = {Host::host(), Node::pubsubNode()},
+%%%    parentid = {Host::host(), Node::pubsubNode()},
+%%%    type = nodeType(),
+%%%    owners = [usr()],
+%%%    options = [nodeOption()]}.
+%%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
+%%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
+%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
+-record(pubsub_node, {nodeid,
+                     parentid = {},
+                     type = "",
+                     owners = [],
+                     options = []
+                    }).
+
+%%% @type pubsubState() = #pubsub_state{
+%%%    stateid = {jid(), {Host::host(), Node::pubsubNode()}},
+%%%    items = [ItemId::string()],
+%%%    affiliation = affiliation(),
+%%%    subscription = subscription()}.
+%%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
+%%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
+-record(pubsub_state, {stateid,
+                      items = [],
+                      affiliation = none,
+                      subscription = none
+}).
+
+%% @type pubsubItem() = #pubsub_item{
+%%        itemid = {ItemId::string(), {Host::host(),Node::pubsubNode()}},
+%%     creation = {JID::jid(), now()},
+%%     modification = {JID::jid(), now()},
+%%     payload = XMLContent::string()}.
+%%% <p>This is the format of the <tt>published items</tt> table. The type of the
+%%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
+-record(pubsub_item, {itemid,
+                     creation = {unknown,unknown},
+                     modification = {unknown,unknown},
+                     payload = []
+                    }).
+
+
+%% @type pubsubPresence() = #pubsub_presence{
+%%     key = {Host::host(), User::string(), Server::string()},
+%%     presence = list().
+%%% <p>This is the format of the <tt>published presence</tt> table. The type of the
+%%% table is: <tt>set</tt>,<tt>ram</tt>.</p>
+-record(pubsub_presence, {key,
+                         resource
+                        }).
+