Created
October 7, 2010 13:07
-
-
Save anonymous/615072 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