Skip to content

Instantly share code, notes, and snippets.

Created October 7, 2010 13:07

Revisions

  1. @invalid-email-address Anonymous created this gist Oct 7, 2010.
    308 changes: 308 additions & 0 deletions snippet.erl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,308 @@
    %% A faire
    % - passer en configuration l'arborescence des facette (par type)
    % {mod_search, [{facet, {type, "produit"}, [{"facet.limit",5}], [
    % style,
    % {price, [{query, "price:[*+TO+500]"},
    % {query, "price:[500+TO+*]"}]}
    % ]}
    % {facet, {type, "session"}, [], [style, gangs]}
    % ]
    % }
    % - mettre un superviseur pour esolr
    % - faire un configuration
    -module(mod_search).
    -author('[email protected]').

    -behaviour(gen_server).
    -behaviour(gen_mod).
    -compile(export_all).
    %% API
    -export([start_link/2, start/2, stop/1]).
    -export([process_iq_disco_info/5, process_iq_disco_items/5]).
    %% 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, facet_fields=[], facet_queries=[]}).
    -define(NODEJID(To, Name, Node),
    {xmlelement, "item",
    [{"jid", To},
    {"name", Name},
    {"node", Node}], []}).
    -define(PROCNAME, mod_search).

    %%====================================================================
    %% API
    %%====================================================================
    %%--------------------------------------------------------------------
    %% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
    %% Description: Starts the server
    %%--------------------------------------------------------------------
    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, esolr),
    EsolrOpts = gen_mod:get_opt(esolr_opts, Opts, []),
    ChildSpec =
    {Proc,
    {esolr, start_link, [EsolrOpts]},
    permanent,
    1000,
    worker,
    [esolr]},
    supervisor:start_child(ejabberd_sup, ChildSpec),
    Proc2 = gen_mod:get_module_proc(Host, ?PROCNAME),
    ChildSpec2 =
    {Proc2,
    {?MODULE, start_link, [Host, Opts]},
    permanent,
    1000,
    worker,
    [?MODULE]},
    supervisor:start_child(ejabberd_sup, ChildSpec2).

    stop(Host) ->
    lists:map(fun(Name)->
    Proc = gen_mod:get_module_proc(Host, Name),
    gen_server:call(Proc, stop),
    supervisor:terminate_child(ejabberd_sup, Proc),
    supervisor:delete_child(ejabberd_sup, Proc)
    end, [?PROCNAME, esolr] ).



    %%====================================================================
    %% gen_server callbacks
    %%====================================================================

    %%--------------------------------------------------------------------
    %% Function: init(Args) -> {ok, State} |
    %% {ok, State, Timeout} |
    %% ignore |
    %% {stop, Reason}
    %% Description: Initiates the server
    %%--------------------------------------------------------------------
    init([Host, Opts]) ->
    MyHost = gen_mod:get_opt_host(Host, Opts, "search.@HOST@"),
    Fields = gen_mod:get_opt(facet_fields, Opts, []),
    Queries = gen_mod:get_opt(facet_queries, Opts, []),
    ejabberd_router:register_route(MyHost),
    {ok, #state{host = MyHost, facet_fields=Fields, facet_queries=Queries}}.

    %%--------------------------------------------------------------------
    %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
    %% {reply, Reply, State, Timeout} |
    %% {noreply, State} |
    %% {noreply, State, Timeout} |
    %% {stop, Reason, Reply, State} |
    %% {stop, Reason, State}
    %% Description: Handling call messages
    %%--------------------------------------------------------------------
    handle_call(stop, _From, State) ->
    {stop, normal, ok, State}.

    %%--------------------------------------------------------------------
    %% Function: handle_cast(Msg, State) -> {noreply, State} |
    %% {noreply, State, Timeout} |
    %% {stop, Reason, State}
    %% Description: Handling cast messages
    %%--------------------------------------------------------------------
    handle_cast(_Msg, State) ->
    {noreply, State}.

    %%--------------------------------------------------------------------
    %% Function: handle_info(Info, State) -> {noreply, State} |
    %% {noreply, State, Timeout} |
    %% {stop, Reason, State}
    %% Description: Handling all non call/cast messages
    %%--------------------------------------------------------------------
    handle_info({route, From, To, Packet}, State) ->
    case From#jid.user of
    "" -> jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
    _ -> do_route(From, To, Packet, State)
    end,
    {noreply, State};


    handle_info(_Info, State) ->
    {noreply, State}.
    terminate(_Reason, State) ->
    ejabberd_router:unregister_route(State#state.host),
    ok.

    %%--------------------------------------------------------------------
    %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
    %% Description: Convert process state when code is changed
    %%--------------------------------------------------------------------
    code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

    %%--------
    %% API
    %%--------


    do_route(From, To, Packet, State) ->
    {xmlelement, Name, _Attrs, _Els} = Packet,
    case Name of
    "iq" ->
    process_iq(From, To, jlib:iq_query_info(Packet) , State);
    _ ->
    ohm_misc:send_error(To, From, Packet)
    end .

    process_iq(From, To, #iq{type = get,
    xmlns = ?NS_DISCO_ITEMS,
    sub_el = SubEl}=IQ, State) ->
    {xmlelement, _Q, Attr, _SubEl} = SubEl,
    Node = xml:get_attr_s("node", Attr),
    spawn(?MODULE, process_iq_disco_items, [From, To, Node, IQ, State]);


    process_iq(From, To, #iq{type = get,
    xmlns = ?NS_DISCO_INFO,
    sub_el = SubEl}=IQ, State) ->
    {xmlelement, _Q, Attr, _SubEl} = SubEl,
    Node = xml:get_attr_s("node", Attr),
    spawn(?MODULE, process_iq_disco_info, [From, To, Node, IQ, State]).

    process_iq_disco_info( From, To, _Node, IQ, _State) ->
    Res = IQ#iq{type = result,
    sub_el = [{xmlelement, "query",
    [{"xmlns", ?NS_DISCO_INFO},
    {"category", "none"},
    {"type", "item"}], []
    }]},
    ejabberd_router:route(To,From, jlib:iq_to_xml(Res)).


    process_iq_disco_items(From, To, "/id/"++Node, IQ, _State) ->
    Res = IQ#iq{type = result,
    sub_el = [{xmlelement, "query",
    [{"xmlns", ?NS_DISCO_ITEMS}],
    []}]},
    ejabberd_router:route(To,From, jlib:iq_to_xml(Res));

    process_iq_disco_items(From, To, Node, IQ, State) ->
    RSM = rsm:decode(IQ),
    Nodes = fetch_results_for_node(Node, RSM, jlib:jid_to_string(To), State),
    Res = IQ#iq{type = result,
    sub_el = [{xmlelement, "query",
    [{"xmlns", ?NS_DISCO_ITEMS}],
    Nodes}]},
    ejabberd_router:route(To,From, jlib:iq_to_xml(Res)).



    fetch_results_for_node("", RSM, To, #state{facet_fields=Fields})->
    create_nodes_for_facets("",Fields, To);

    fetch_results_for_node(Node,RSM, To, State)->
    Nodes = string:tokens(Node,"/"),
    {Query, Options, Facets} = build_query(Nodes, State),
    Options2 = build_rsm_q(RSM, Options),
    ?DEBUG("Solar query : ~p, ~p, ~p, ~p~n", [Query, Options2,Facets, RSM]),
    case esolr:search(Query, Options2) of
    {ok, Stats,[], Rest} ->
    {obj, Props} = proplists:get_value("facet_counts", Rest),
    Fields = case proplists:get_value("facet_fields", Props) of
    {obj, [{FName, F}]} -> F;
    {obj, []} ->
    {obj, F} = proplists:get_value("facet_queries", Props),
    F
    end,
    facets_to_items(Fields, [], Node, To);
    {ok, Stats,Docs, Rest}->
    ?DEBUG("query results : ~p, ~p, ~p, ~n", [Stats, Docs,Rest]),
    create_nodes_for_facets(Node,Facets, To)
    ++ lists:map(fun({doc, Proplist})->
    Name = case proplists:get_value("word",Proplist) of
    undefined -> binary_to_list(proplists:get_value("name",Proplist));
    N ->binary_to_list(N)
    end,
    SKU = binary_to_list(proplists:get_value("sku",Proplist)),
    ?NODEJID(To,Name , "/id/"++SKU)
    end, Docs) ++ build_rsm_response(Stats, erlang:length(Docs));
    _ ->
    []
    end.

    build_rsm_response(Stats, Length)->
    RSM = #rsm_out{count=proplists:get_value("numFound", Stats),
    index=proplists:get_value("start", Stats),
    first=i2l(proplists:get_value("start", Stats)),
    last=i2l(proplists:get_value("start", Stats) + Length)},
    rsm:encode(RSM).

    build_rsm_q(#rsm_in{max=Max, direction=Direction, id=Id}, Options)->
    Q1 = case Max of
    undefined -> Options;
    _ -> [{rows, Max}| Options]
    end,
    case Direction of
    undefined ->
    Q1;
    before ->
    [{start, Id - Max}|Q1];
    aft ->
    [{start, Id}|Q1]
    end;
    build_rsm_q(none, Options)->
    Options.

    % /cat:electronic/popularity:6
    % /cat:electronic/popularity:4
    %esolr:search("*:*",[{rows, -1},{facets,[{f, "cat"}, {f,"popularity"}] }]).

    %% build solar query from node name.
    build_query(Nodes, #state{facet_fields=Fields}=State)->
    build_query(Nodes, {"",[], Fields}, State).
    build_query([], {[], Options, Facets}, State)-> {"*:*", Options, Facets};
    build_query([], Acc, State)-> Acc;
    build_query([ Node |R], {Query, Options,Facets }, #state{facet_queries=FQueries}=State)->
    case string:tokens(Node,":" ) of
    ["q", Search]->
    Q=Query ++ " " ++Search,
    build_query(R, {Q, Options, Facets}, State);
    [Facet, Value] ->
    build_query(R, {Query, [{"fq", Facet++":"++Value} | Options], lists:delete(Facet, Facets)}, State);
    [Facet] ->
    Options2 = case proplists:get_value(Facet,FQueries) of
    undefined ->
    [{facets, [{f, Facet}]}, {rows, -1}, {"facet.mincount",1}| Options];
    List ->
    lists:merge([
    [{"facet.query", Facet++":"++Range} || Range <- List],
    [{rows, -1}, {"facet.mincount",1},{"facet", "true"}],
    Options])
    end,
    build_query(R, {Query, Options2, lists:delete(Facet, Facets)}, State)
    end.

    %% transforms facets into nodes
    facets_to_items(Array, Root, To)->
    facets_to_items(Array,[], Root, To).
    facets_to_items([], Items, Root, To)-> Items;
    % Pour les facet.queries
    facets_to_items([{Name, Count}|R], Items, Root, To)->
    [Facet, Range]=string:tokens(Name,":" ),
    Items2=[?NODEJID(To, Name++" ("++ i2l(Count) ++")", Root++":"++Range)| Items],
    facets_to_items(R, Items2, Root, To);
    facets_to_items([BinName, Count|R], Items, Root, To)->
    Name = binary_to_list(BinName),
    Items2=[?NODEJID(To, Name++" ("++ i2l(Count) ++")", Root++":"++Name)| Items],
    facets_to_items(R, Items2, Root, To).

    create_nodes_for_facets(Root,Facets, To)->
    lists:zf(fun(Name) ->
    {true,?NODEJID(To, "->"++Name, Root++"/"++Name)}
    end,Facets).



    i2l(I) when integer(I) -> integer_to_list(I);
    i2l(L) when list(L) -> L.