1%%% File        : yapp.erl
2%%% @author Mikael Karlsson <mikael@creado.se>
3%%% @since 11 Dec 2005 by Mikael Karlsson <mikael@creado.se>
4%%% @see yapp_handler
5%%% @doc Yaws applications handler.
6%%% <p>An easy way to deploy Yaws applications independently of
7%%% each other.</p>
8%%% <p>This module implements Yaws runmod and arg_rewrite_mod interfaces.
9%%% It has functions to insert and remove Yapps to the Yaws sconfs records.
10%%% It is used by the yapp_handler implementation. The yapp module will make Yaws
11%%% temporarily switch the docroot
12%%% to the applications priv/docroot directory when it encounters the registered
13%%% URL path of the application. One can set another docroot than the default
14%%%  (priv/docroot) by setting the environment variable yapp_docroot in the
15%%% .app file for the application.
16%%% The application may also have own appmods which are put in the
17%%% application environment variable yapp_appmods like: </p>
18%%% <code>  {env, [
19%%%         {yapp_appmods,[{"ctrl",enityme_controller}]}
20%%%        ]}, </code>
21%%% <p>
22%%% In order to include the yapp module, runmod shall be set to yapp.
23%%% The arg_rewrite_mod shall also be set to yapp and the opaque variable
24%%% yapp_server_id shall be set to an unique string for every virtual server
25%%% using yapp, like: </p>
26%%% <pre>
27%%% .
28%%% ebin_dir = /usr/local/yaws/lib/yapp/ebin
29%%% .
30%%% runmod = yapp
31%%% .
32%%% &lt;server aspen4&gt;
33%%%        port = 8001
34%%%        listen = 0.0.0.0
35%%%        dir_listings = true
36%%%        arg_rewrite_mod = yapp
37%%%        docroot = /home/yaws/scripts/../www
38%%%        &lt;opaque&gt;
39%%%                yapp_server_id = aspeninternal
40%%%                bootstrap_yapps = yapp
41%%%        &lt;/opaque&gt;
42%%% &lt;/server&gt;
43%%% </pre>
44%%%
45%%% <p><em>Note:</em> The yapp application is in itself a "Yapp" with a small admin console.
46%%% So by adding the line <code>bootstrap_yapps = yapp</code> as above you will get access
47%%% to the admin console on http://yourservername/yapp/index.html</p>
48%%% <p><em>Note2:</em> The current available registry implementation uses Mnesia so you need
49%%% to create a mnesia schema (if not already done) for the node(s) you are running Yaws on.
50%%% <code>mnesia:create_schema([node()]).</code></p>
51%%%
52%%% @type yawsArg() = term(). This is the #arg record as defined
53%%% by yaws_api.hrl.
54%%%
55%%% @type yawsSconf() = term(). This is the #sconf record as defined
56%%% by yaws.hrl.
57%%%
58%%% @type yawsGconf() = term(). This is the #gconf record as defined
59%%% by yaws.hrl.
60
61-module(yapp).
62-author('mikael@creado.se').
63
64-include("yaws_api.hrl").
65-export([arg_rewrite/1, start/0, prepath/1, insert/1, insert/2, remove/2,
66         log/3,
67         reset_yaws_conf/0, srv_id/1,
68         get_bootstrap_yapps/0, get_yapps/0, get_server_ids/0]).
69 %%     reload_yaws/0,
70
71-define(prepath, yapp_prepath).
72-define(srv_id, "yapp_server_id").
73-define(bootstrap_yapps, "bootstrap_yapps"). %% Opaque key for "bootstrap yapps"
74-define(erlyweb_appname,"appname").
75-define(yapp_list, yapp_list).
76
77-define(priv_docroot, "priv/docroot").
78
79%% This record is stored in the sconf opaque property yapp_list
80-record(yapp, {
81          urlpath,
82          docroot = "",
83          appname = "",
84          appmods = [],
85          opaque = []
86         }).
87
88%% The yapp_reg is stored in Mnesia in the y_registry
89%% and contains a list of {yapp_server_id, yapps} tuples, where
90%% yapps is a list of {UrlPath, AppName} tuples
91%% UrlPath = string()
92%% Interface arg_rewrite_mod
93%% @spec arg_rewrite(Arg::yawsArg()) -> yawsArg()
94%% @doc Interface function for a Yaws arg_rewrite_mod. If
95%% it finds a registered Yapp it will strip of the
96%% path up to the Yapp and redirect the docroot.
97arg_rewrite(Arg) ->
98    case find_registered_yapps(Arg) of
99        undefined ->
100            Arg;
101        {#yapp{urlpath = YappPath, docroot = Docroot,
102               appmods = YappMods, opaque = YOpaque }, _Rest} ->
103
104            DocMount =
105                case string:right(YappPath,1) of
106                    "/" -> YappPath;
107                    _ -> YappPath ++ "/"
108                end,
109
110            VDir = {"vdir", DocMount ++ " " ++ Docroot},
111
112            AddOpaque = [ VDir | YOpaque],
113
114            %% Add Yapp appmods, Yaws uses process dictionary.
115            SC = get(sc),
116            AppMods = yaws:sconf_appmods(SC),
117            Opaque = yaws:sconf_opaque(SC),
118            NYappPath = case YappPath of
119                            [$/|YPTail] ->
120                                YPTail;
121                            _ ->
122                                YappPath
123                        end,
124            RemappedYappMods = lists:map(fun({PE, Mod, Ex}) ->
125                                                 {PE, Mod, [[NYappPath] ++ X || X <- Ex]};
126                                            (AM) ->
127                                                 AM
128                                         end, YappMods),
129            SC2 = yaws:setup_sconf([{docroot, Docroot},
130                                    {appmods, AppMods ++ RemappedYappMods},
131                                    {opaque, AddOpaque ++ Opaque}], SC),
132            put(sc, SC2),
133
134            Opaque2 = Arg#arg.opaque,
135            Arg#arg{docroot=Docroot, docroot_mount=DocMount,
136                    opaque = AddOpaque ++ Opaque2}
137    end.
138
139
140%% Interface run_mod
141%% @spec start() -> void()
142%% @doc Interface function for Yaws run_mod.
143%% This fun is spawned from Yaws so no return val is expected.
144%% All Yapps can expect mnesia to be started, so mnesia is ensured
145%% to be started.
146%% For every server id that has Yapps the yapp module is registered
147%% as an arg_rewrite_mod in #sconf.
148%% Configuration data for mapping Yapp paths to applications is looked
149%% up in a mnesia registry table and stored in Yaws #sconf.opaque record
150%% for the each server id.
151start() ->
152    case wait_for_yaws() of
153	ok ->
154	    log(info, "Starting yapp~n",[]),
155	    application:start(yapp);
156	Error ->
157	    log(error, "Failed waiting for Yaws to start when starting Yapp: ~p~n",[Error])
158    end.
159
160wait_for_yaws() ->  wait_for_yaws(20).
161
162wait_for_yaws(0) -> {error, timeout};
163wait_for_yaws(N) ->
164    WA = application:which_applications(),
165    case lists:keysearch(yaws, 1, WA) of
166	false ->
167	    log(info, "Yapp starting but Yaws not ready - waiting 500 ms",[]),
168	    receive after 500 -> ok end, %% Give Yaws some time to settle own things
169	    wait_for_yaws(N-1);
170	_ ->
171	    ok
172    end.
173
174
175%% @spec prepath(Arg::yawsArg()) -> Path::string()
176%% @doc Get the Yapp root-path. Can be called from a Yapp erl/1
177%% fun or an erl section in a .yaws file to get the Yapp root
178%% path.
179prepath(Arg) ->
180    Arg#arg.docroot_mount.
181
182
183%% @spec log(Level, FormatStr::string(), Args) -> void()
184%%   Level = error | warning | info | debug
185%%   Args = [term()]
186%% @doc Yapp interface to the error_logger.
187log(debug, FormatStr, Args) ->
188    gen_event:notify(error_logger, {debug_msg, group_leader(), {self(), FormatStr, Args}});
189log(info, FormatStr, Args) ->
190    error_logger:info_msg(FormatStr, Args);
191log(warning, FormatStr, Args) ->
192    error_logger:warning_msg(FormatStr, Args);
193log(error, FormatStr, Args) ->
194    error_logger:error_msg(FormatStr, Args);
195log(Level, FormatStr, Args) ->
196    error_logger:error_msg("Unknown logging level ~p  ," ++ FormatStr,[Level|Args]).
197
198%% Utility function
199
200find_registered_yapps(Arg) ->
201    Req = Arg#arg.req,
202    {abs_path, Path} = Req#http_request.path,
203    A = proplists:get_value(?yapp_list, Arg#arg.opaque,[]),
204    find_registered(A, Path).
205
206find_registered([],_Path) ->
207    undefined;
208find_registered([ #yapp{urlpath = RegPath} = Y | T ], Path) ->
209    case string:str(Path, RegPath) of
210        1 ->
211            case string:substr(Path,1+length(RegPath)) of
212                [] -> {Y,[]};
213                [$/ |_] = Rest -> {Y,Rest};
214                _ -> find_registered(T,Path)
215            end;
216        _ ->
217            find_registered(T, Path)
218    end.
219
220%% @hidden
221insert([]) ->
222    ok;
223insert(Yapps) when is_list(Yapps) ->
224    {ok, Gconf, Sconfs} = get_conf(),
225    NewSconfs = (catch insert_yapps_in_sconfs(Yapps, Sconfs)),
226    yaws_api:setconf(Gconf, NewSconfs).
227%% @hidden
228insert(SrvId, Yapp)->
229    {ok, Gconf, SconfGroups} = get_conf(),
230    NewSconfGroups = insert_yapp_in_sconfgroups(SrvId, Yapp, SconfGroups),
231    yaws_api:setconf(Gconf, NewSconfGroups).
232
233
234insert_yapps_in_sconfs([], SconfGroups) ->
235    SconfGroups;
236insert_yapps_in_sconfs([{_SrvId,[]}|T], SconfGroups) ->
237    insert_yapps_in_sconfs(T, SconfGroups);
238insert_yapps_in_sconfs([{SrvId,[Y|YS]}|T], SconfGroups) ->
239    NewSconfGroups = insert_yapp_in_sconfgroups(SrvId,Y,SconfGroups),
240    insert_yapps_in_sconfs([{SrvId,YS}|T], NewSconfGroups).
241
242insert_yapp_in_sconfgroups(_SrvId, _Yapp,[]) ->
243    [];
244insert_yapp_in_sconfgroups(SrvId, Yapp, [SCG|T]) ->
245    [insert_yapp_in_sconfgroup(SrvId,Yapp, SCG) | insert_yapp_in_sconfgroups(SrvId,Yapp,T)].
246
247insert_yapp_in_sconfgroup(_SrvId, _Yapp, []) ->
248    [];
249insert_yapp_in_sconfgroup(SrvId, Yapp, [SC|SCG]) ->
250    case srv_id(SC) of
251        SrvId ->
252            case insert_yapp_in_sconf(Yapp,SC) of
253                no_app ->
254                    [SC | SCG];
255                NewSC ->
256                    [NewSC | SCG]
257            end;
258        _ ->
259            [ SC | insert_yapp_in_sconfgroup(SrvId,Yapp,SCG)]
260    end.
261
262%% the yapp application itself maybe a yapp, treated as special case
263insert_yapp_in_sconf({UrlPath, yapp}, SC) ->
264    insert_yapp_in_sconf0({UrlPath, yapp}, SC);
265insert_yapp_in_sconf({UrlPath, AppName}, SC) ->
266    case start_app([AppName]) of
267        ok ->
268            insert_yapp_in_sconf0({UrlPath, AppName}, SC);
269        Error ->
270            log(error, "yapp:insert_yapp_in_sconf - Error loading Yapp ~p, ~p",
271                [AppName, Error]),
272            no_app
273    end.
274
275start_app([AppName|T]) ->
276    log(info, "Starting app ~p" , [AppName]),
277    case application:start(AppName) of
278       {error,{not_started,RequiredApp}} ->
279           start_app([RequiredApp,AppName|T]);
280       {error,{already_started,AppName}} ->
281           start_app(T);
282       ok ->
283           start_app(T);
284       Error -> Error
285    end;
286start_app([]) -> ok;
287start_app(Error) -> Error.
288
289insert_yapp_in_sconf0({UrlPath, AppName}, SC) ->
290    log(info,"Inserting App ~p in Url ~p~n", [AppName, UrlPath]),
291    OP         = yaws:sconf_opaque(SC),
292    AppEnv     = application:get_all_env(AppName),
293    DocSubRoot = proplists:get_value(yapp_docroot, AppEnv, ?priv_docroot),
294    YAppMods   = proplists:get_value(yapp_appmods, AppEnv, []),
295    YOpaque    = proplists:get_value(yapp_opaque,  AppEnv, []),
296    Y = #yapp{urlpath= UrlPath,
297              docroot = code:lib_dir(AppName) ++ "/" ++ DocSubRoot,
298              appname = AppName,
299              appmods = YAppMods,
300              opaque  = YOpaque },
301
302    OP2 = case proplists:get_value(?yapp_list, OP) of
303              undefined ->
304                  [{?yapp_list,[Y]} | OP];
305              YR ->
306                  YR2 = insert_yapp_in_yapp_list(Y, YR),
307                  lists:keyreplace(?yapp_list, 1, OP, {?yapp_list, YR2})
308          end,
309    yaws:setup_sconf([{opaque, OP2}], SC).
310
311insert_yapp_in_yapp_list(#yapp{} = Y, []) ->
312    [Y];
313insert_yapp_in_yapp_list(#yapp{urlpath= UP} = Y, [#yapp{urlpath=UP} | T])  ->
314    [Y|T];
315insert_yapp_in_yapp_list(Y, [H | T])  ->
316    [ H | insert_yapp_in_yapp_list(Y,T) ].
317
318%% @hidden
319remove(SrvId, RegPath) ->
320    {ok, Gconf, Sconfs} = yaws_api:getconf(),
321    NewSconfs = remove_yapp_from_sconfgroups(SrvId, RegPath, Sconfs),
322    yaws_api:setconf(Gconf, NewSconfs).
323
324remove_yapp_from_sconfgroups(_SrvId, _RegPath, []) ->
325    [];
326remove_yapp_from_sconfgroups(SrvId, RegPath, [SCG|T]) ->
327    [remove_yapp_from_sconfgroup(SrvId, RegPath,SCG) |
328     remove_yapp_from_sconfgroups(SrvId, RegPath, T)].
329
330remove_yapp_from_sconfgroup(_SrvId, _RegPath, []) ->
331    [];
332remove_yapp_from_sconfgroup(SrvId, RegPath, [ H | T ]) ->
333    case srv_id(H) of
334        SrvId ->
335            [ remove_yapp_from_sconf(RegPath, H) | T ];
336        _ ->
337            [ H | remove_yapp_from_sconfgroup(SrvId, RegPath,  T ) ]
338    end.
339
340remove_yapp_from_sconf(RegPath, SC) ->
341    OP  = yaws:sconf_opaque(SC),
342    OP2 = case proplists:get_value(?yapp_list, OP) of
343              undefined ->
344                  OP;
345              YR ->
346                  YR2 = remove_yapp_from_yapp_list(RegPath, YR),
347                  lists:keyreplace(?yapp_list, 1, OP, {?yapp_list, YR2})
348          end,
349    yaws:setup_sconf([{opaque, OP2}], SC).
350
351remove_yapp_from_yapp_list(_, [] ) ->
352    [];
353remove_yapp_from_yapp_list(RegPath, [ #yapp{urlpath = RegPath} | T ] ) ->
354    T;
355remove_yapp_from_yapp_list(RegPath, [H | T]) ->
356    [H | remove_yapp_from_yapp_list(RegPath, T)].
357
358%% by tobbe@tornkvist.org
359reset_yaws_conf() ->
360    case catch yaws_config:load(yaws_sup:get_app_args()) of
361        {ok, Gconf, Sconfs} ->
362            yaws_api:setconf(Gconf, Sconfs);
363        Err ->
364            Err
365    end.
366
367%% @spec get_conf() -> {ok,yawsGconf(), Sconfs}
368%% Sconfs = [ yawsSconf() ]
369get_conf() ->
370    yaws_api:getconf().
371%    yaws_config:load(yaws_sup:get_app_args()).
372
373%% @spec srv_id(Sconf) -> string() | undefined
374%%  Sconf = yawsSconf()
375%% @doc Get the server id from an Sconf if available
376srv_id(SC) ->
377    OP = yaws:sconf_opaque(SC),
378    proplists:get_value(?srv_id, OP).
379
380%% @spec get_bootstrap_yapps() -> [{ ServerId, [ {Path, ApplicationName}]}]
381%%   ServerId = string()
382%%   Path = string()
383%%   Applicationame = atom()
384%% @doc Gets the Yapps defined in each opaque
385%% "bootstrap_yapps = appname1, appname2" for every server id. (If available).
386%% Bootstrap yapps will get the same pathname as their application name
387%% and are "static" meaning that they can not be removed from the server
388%% unless yaws.conf is changed (or if embedded yaws - yaws:setconf/2 is used).
389get_bootstrap_yapps() ->
390
391    {ok, _Gconf, Sconfs} = get_conf(),
392
393    YL = [begin
394              OP = yaws:sconf_opaque(SC),
395              {proplists:get_value(?srv_id, OP),
396               make_yapp_tuples(proplists:get_value(?bootstrap_yapps, OP))}
397          end || SC <- lists:flatten(Sconfs) ],
398
399    [{SrvId, Yapps} ||
400        {SrvId, Yapps} <- YL, SrvId =/= undefined, Yapps =/= []].
401
402
403
404make_yapp_tuples(undefined) ->
405    [];
406make_yapp_tuples(BootStrapYapps) ->
407    [ make_yapp_tuple(A) || A <- string:tokens(BootStrapYapps,",")].
408
409make_yapp_tuple(A) ->
410    B = string:strip(A),
411    {"/" ++ B, list_to_atom(B)}.
412
413%% @spec get_yapps() -> [{ServId,[{yapp, Urlpath, Docroot, Appname , Appmods}]}]
414%%  Urlpath = string()
415%%  Docroot = string()
416%%  Appname = atom()
417%%  Appmods = [atom()]
418%% @doc Gets all Yapps that are configured for the Yaws server.
419get_yapps() ->
420    {ok, _Gconf, Sconfs} = yaws_api:getconf(),
421    Yapps1 = [begin
422                  OP = yaws:sconf_opaque(SC),
423                  {proplists:get_value("yapp_server_id", OP),
424                   proplists:get_value(yapp_list, OP)}
425              end || SC <- lists:flatten(Sconfs)],
426    [{S,Y} || {S,Y} <- Yapps1, Y =/= undefined, S =/= undefined].
427
428%% @spec get_server_ids() -> [string()]
429%% @doc Lists all server ids.
430get_server_ids() ->
431    {ok, _Gconf, Sconfs} = get_conf(),
432    SrvIds1 = [begin
433                   OP = yaws:sconf_opaque(SC),
434                   proplists:get_value("yapp_server_id", OP)
435               end || SC <- lists:flatten(Sconfs)],
436    [S|| S <- SrvIds1,  S =/= undefined].
437
438