src/support/z_module_dummy.erl

%% @author Marc Worrell <marc@worrell.nl>
%% @copyright 2010 Marc Worrell
%% Date: 2010-07-05
%% @doc Dummy gen_server for modules without any gen_server code.
%% We use this dummy gen_server so that we still can use a simple otp supervisor to oversee the
%% running modules.

%% Copyright 2010 Marc Worrell
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.

-module(z_module_dummy).
-author("Marc Worrell <marc@worrell.nl>").
-behaviour(gen_server).

%% gen_server exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([start_link/1]).

-record(state, {module :: atom(), site :: atom()}).

-include("zotonic.hrl").

%%====================================================================
%% API
%%====================================================================
%% @spec start_link(Args) -> {ok,Pid} | ignore | {error,Error}
%% @doc Starts the server
start_link(Args) when is_list(Args) ->
    {ok, Pid} = gen_server:start_link(?MODULE, Args, []),
    gen_server:cast(Pid, init),
    {ok, Pid}.


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

%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore               |
%%                     {stop, Reason}
%% @doc Initiates the server.
init(Args) ->
    process_flag(trap_exit, true),
    {module, Module} = proplists:lookup(module, Args),
    {context, Context} = proplists:lookup(context, Args),
    Site = z_context:site(Context),
    logger:set_process_metadata(#{
        site => Site,
        module => ?MODULE
    }),
    {ok, #state{module=Module, site=Site}}.

%% @spec handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% @doc Trap unknown calls
handle_call(Message, _From, State) ->
    {stop, {unknown_call, Message}, State}.


%% @spec handle_cast(Msg, State) -> {noreply, State} |
%%                                  {noreply, State, Timeout} |
%%                                  {stop, Reason, State}
%% @doc Handle the next step in the module initialization.
handle_cast(init, #state{module=Module, site=Site}=State) ->
    dummy_module_init(Module, z_context:new(Site)),
    {noreply, State};

%% @doc Trap unknown casts
handle_cast(Message, State) ->
    {stop, {unknown_cast, Message}, State}.



%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% @doc Handling all non call/cast messages
handle_info(_Info, State) ->
    {noreply, State}.

%% @spec terminate(Reason, State) -> void()
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
terminate(Reason, #state{module=Module, site=Site}) ->
    dummy_module_terminate(Reason, Module, z_context:new(Site)),
    ok.

%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @doc Convert process state when code is changed

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%====================================================================
%% support functions
%%====================================================================


%% @doc When a module doesn't implement a gen_server then check if it exports an init/1 function,
%% if so then call that function with a fresh sudo context.
dummy_module_init(Module, Context) ->
    case lists:member({init,1}, erlang:get_module_info(Module, exports)) of
        true -> Module:init(z_acl:sudo(Context));
        false -> nop
    end.

%% @doc When a module doesn't implement a gen_server then check if it exports a terminate/2 function,
%% if so then call that function with a fresh sudo context.
dummy_module_terminate(Reason, Module, Context) ->
    case lists:member({terminate,2}, erlang:get_module_info(Module, exports)) of
        true -> Module:terminate(Reason, z_acl:sudo(Context));
        false -> nop
    end.