-
-
Save cstar/615073 to your computer and use it in GitHub Desktop.
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 characters
| %% 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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment