Created
October 7, 2010 13:07
Revisions
-
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.