PageRenderTime 15ms CodeModel.GetById 2ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/src/support/z_service.erl

http://github.com/zotonic/zotonic
Erlang | 192 lines | 117 code | 30 blank | 45 comment | 7 complexity | 1b2f8ad96dadf54cfeeba7490f938112 MD5 | raw file
  1%% @author Arjan Scherpenisse <arjan@scherpenisse.net>
  2%% @copyright 2009-2012 Arjan Scherpenisse
  3%% Date: 2009-10-03
  4%% @doc Support functions for API calls.
  5
  6%% Copyright 2009-2012 Arjan Scherpenisse
  7%%
  8%% Licensed under the Apache License, Version 2.0 (the "License");
  9%% you may not use this file except in compliance with the License.
 10%% You may obtain a copy of the License at
 11%% 
 12%%     http://www.apache.org/licenses/LICENSE-2.0
 13%% 
 14%% Unless required by applicable law or agreed to in writing, software
 15%% distributed under the License is distributed on an "AS IS" BASIS,
 16%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 17%% See the License for the specific language governing permissions and
 18%% limitations under the License.
 19
 20-module(z_service).
 21-author("Arjan Scherpenisse <arjan@scherpenisse.net>").
 22
 23-export([
 24         needauth/1, 
 25         title/1, 
 26         all/1, 
 27         all/2, 
 28         serviceinfo/2,
 29         http_methods/1,
 30         handler/1,
 31         grouped/1,
 32         applies/2,
 33         module_to_api_prefix/1,
 34         api_prefix_to_module/1
 35        ]).
 36
 37-include_lib("zotonic.hrl").
 38
 39%%
 40%% Give information about all API services.
 41%%
 42all(Context) ->
 43    F = fun() ->
 44            [ ServiceModule || #module_index{erlang_module=ServiceModule} <- z_module_indexer:find_all(service, true, Context) ]
 45        end,
 46    z_depcache:memo(F, {z_services}, ?HOUR, [z_modules, module_index], Context).
 47
 48%%
 49%% All services grouped by module
 50%%
 51grouped(Context) ->
 52    grouped(all(info, Context), Context).
 53
 54grouped(Services, _Context) ->
 55    P = [{proplists:get_value(module, Service), Service} || Service <- Services],
 56    [{Mod, proplists:get_all_values(Mod, P)} || Mod <- proplists:get_keys(P) ].
 57
 58%%
 59%% All services augmented as serviceinfo/1 record.
 60%%
 61all(info, Context) ->
 62    F = fun() ->
 63            Info = z_module_indexer:find_all(service, true, Context),
 64            [ serviceinfo(Name, Module, ErlangModule) || #module_index{key=#module_index_key{name=Name}, module=Module, erlang_module=ErlangModule} <- Info ]
 65        end,
 66    z_depcache:memo(F, {z_services_info}, ?HOUR, [z_modules, module_index], Context);
 67
 68
 69%%
 70%% All services as authentication values
 71%%
 72all(authvalues, Context) ->
 73    All = all(info, Context),
 74    All2 = lists:filter( fun(S) -> proplists:get_value(needauth, S) end, All),
 75    Grouped = grouped(All2, Context),
 76    lists:flatten([ {"*", "Everything"} | 
 77                    [ [authvalue_module(Module) | [authvalue_service(S) || S <- Services]]   || {Module, Services} <- Grouped]]).
 78
 79authvalue_module(Module) ->
 80    ModuleTitle = proplists:get_value(mod_title, Module:module_info(attributes)),
 81    {module_to_api_prefix(Module) ++ "/*", ModuleTitle}.
 82
 83authvalue_service(ServiceInfo) ->
 84    ServiceTitle = title(proplists:get_value(service, ServiceInfo)),
 85    {proplists:get_value(method, ServiceInfo), ServiceTitle}.
 86    
 87
 88module_to_api_prefix(ZotonicModule) when is_atom(ZotonicModule) ->
 89    case atom_to_list(ZotonicModule) of
 90        [$m, $o, $d, $_ | Mod] -> Mod;
 91        Site -> Site
 92    end.
 93    
 94api_prefix_to_module(Base) when is_list(Base) ->
 95    case z_utils:ensure_existing_module([$m, $o, $d, $_ | Base]) of
 96        {ok, M} -> M;
 97        {error, _} ->
 98            {ok, M2} = z_utils:ensure_existing_module(Base),
 99            M2
100    end.
101
102
103%%
104%% All information about a service
105%%
106serviceinfo(ServiceModule, Context) ->
107    All = all(info, Context),
108    case lists:filter(fun(I) -> proplists:get_value(service, I) =:= ServiceModule end, All) of
109        [Info] -> Info;
110        _ -> undefined
111    end.
112
113serviceinfo(Method, ZotonicModule, ServiceModule) ->
114    ZotonicModuleName = module_to_api_prefix(ZotonicModule),
115    [ {method, string:join([ZotonicModuleName, atom_to_list(Method)], "/")},
116      {module, ZotonicModule}, 
117      {service, ServiceModule},
118      {title,  title(ServiceModule)},
119      {needauth, needauth(ServiceModule)},
120      {http, string:join([atom_to_list(MM) || MM <- http_methods(ServiceModule)],",")} 
121    ].
122    
123
124%%
125%% Whether a service needs an authenticated user. Defaults to false.
126%%
127needauth(Service) ->
128    module_attr(Service, svc_needauth, false, atom).
129
130
131%%
132%% Title of the service
133%%    
134title(Service) ->
135    module_attr(Service, svc_title, "(untitled)", list).
136
137
138%%
139%% Which HTTP methods does this API support?
140%%
141http_methods(Service) ->
142    F = Service:module_info(functions),
143    lists:filter(fun (M) -> lists:member(handler(M), F) end, ['GET', 'POST', 'HEAD', 'PUT', 'DELETE']).
144
145%% define the handler mapping for the module.
146handler('POST') ->
147    {process_post, 2};
148handler('GET') -> 
149    {process_get, 2};
150handler('HEAD') ->
151    {process_get, 2};
152handler('PUT') ->
153    {process_post, 2};
154handler('DELETE') ->
155    {process_post, 2}.
156
157
158module_attr(Service, Attr, Default, T) ->
159    Info = Service:module_info(attributes),
160    V = proplists:get_value(Attr, Info),
161    case T of
162        list -> 
163            case V of
164                undefined -> Default;
165                V -> V
166            end;
167        atom ->
168            case V of
169                [X] -> X;
170                _ -> Default
171            end
172    end.
173
174%%
175%% Whether a services applies to a pattern.  applies(Pattern, Service).
176%%
177applies([Pattern|Rest], ServiceMethod) when is_list(Pattern) ->
178    applies(Pattern, ServiceMethod) orelse applies(Rest, ServiceMethod);
179applies(Pattern, ServiceMethod) ->
180    applies1(string:tokens(Pattern, "/"), string:tokens(ServiceMethod, "/")).
181
182applies1(["*"], _) ->
183    true;
184applies1([], _) ->
185    false;
186applies1([Part], [Part]) ->
187    true;
188applies1([Part|Rest], [Part|Rest2]) ->
189    applies1(Rest, Rest2);
190applies1(_, _) ->
191    false.
192