Skip to content

Instantly share code, notes, and snippets.

@cstar
Forked from anonymous/snippet.erl
Created October 7, 2010 13:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cstar/615073 to your computer and use it in GitHub Desktop.
Save cstar/615073 to your computer and use it in GitHub Desktop.
%% 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('eric@ohmforce.com').
-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