riak / src / jiak.erl

%% This file is provided to you 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


%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% KIND, either express or implied.  See the License for the
%% specific language governing permissions and limitations
%% under the License.    

%% @doc Jiak: Riak utilities, focused on JSON-encoded data.
%%      Riak is web-shaped, and one of the most useful
%%      interchange formats on the web is JSON.  The Jiak libraries
%%      are intended to provide a simple system for serving data from
%%      Riak in the JSON format.
%%      Jiak objects take the shape of JSON objects of the form:
%%      {
%%       "bucket":"foo",
%%       "key":"123",
%%       "object":{
%%                 "barField":"bar",
%%                 "bazField":42
%%                }
%%       "links":[
%%                ["quuxbucket","quuxkey","quuxtag"],
%%                ["abcbucket","abckey","qbctag"]
%%               ]
%%       "vclock":"opaque-riak-vclock",
%%       "lastmod":"Mon, 03 Aug 2009 18:49:42 GMT"
%%      }
%%      Within Riak, these fields get broken into their appropriate
%%      places in riak_object, but using the Jiak interface,
%%      applications see only mochijson2-encoded objects.  An
%%      application can choose to either dig through the structure the
%%      Erlang pattern-match way, or by using the interface provided
%%      by the jiak_object module.
%%      Access to these objects as JSON is provided to clients that
%%      can speak HTTP by way of the jiak_resource Webmachine resource
%%      module.  Clients can GET bucket schemas and key lists, as well
%%      as fetch and store objects.  Semantics about which fields are
%%      allowed/readable/editable/etc. are provided by user-defined
%%      Erlang modules named for the bucket of the object.  For an
%%      example of such a module, see {@link jiak_example}.
%%      Further access is provided by means of 'link' mapreduce specs
%%      and jaywalker_resource.  To support each of these, you should
%%      open a jiak_client, then call set_bucket/2 with your bucket
%%      name and the result of default_jiak_bucket_props/0.  Without
%%      that setting, the mapreduce system won't know how to extract
%%      links for following.

-export([local_client/0, local_client/1,
         client_connect/1, client_connect/2]).


%% @spec local_client() -> {ok, jiak_client()}|error_term()
%% @equiv local_client(undefined)
local_client() -> local_client(undefined).

%% @spec local_client(binary()|undefined) ->
%%         {ok, jiak_client()}|error_term()
%% @doc Open a Riak client for modifying Jiak objects.
%% @see riak:local_client/1
local_client(ClientId) -> client_connect(node(), ClientId).

%% @spec client_connect(Node :: node())
%%        -> {ok, Client :: jiak_client()} | exception
%% @equiv client_connect(Node, undefined)
client_connect(Node) -> client_connect(Node, undefined).

%% @spec client_connect(Node :: node(), ClientId :: binary()|undefined)
%%        -> {ok, Client :: jiak_client()} | exception
%% @doc The usual way to get a client.  Timeout often means either a bad
%%      cookie or a poorly-connected distributed erlang network.
client_connect(Node, ClientId) ->
    case riak:client_connect(Node, ClientId) of
        {ok, C} -> {ok, jiak_client:new(C)};
        Error -> Error

%% @spec default_jiak_bucket_props() -> [bucket_prop()]
%% @doc Returns the default additional bucket parameters for Jiak
%%      buckets, suitable for the second parameter in a call to
%%      jiak_client:set_bucket/2.
%%      The only property is currently 'linkfun' which sets up
%%      the given bucket to support the {link, Bucket, Tag, Acc}
%%      mapreduce spec.
default_jiak_bucket_props() ->
    [{linkfun, {modfun, jiak_object, mapreduce_linkfun}}].

%% Utility - Merging siblings

%% @doc basic strategy:
%%<ul><li> set-union all links
%%</li><li>create an object with most-recent version of each field,
%%           according to object's last-modified date - fields missing
%%           in newer versions will have their values taken from older
%%           versions
standard_sibling_merge(Sibs) ->
    [{Ms,{{struct,Os},Ls}}|Rest] = sort_sibs(Sibs),
    lists:foldl(fun merge_sibs/2,

%% @private
sort_sibs(Sibs) ->
      fun({MD1, _}, {MD2, _}) ->
                dict:fetch(<<"X-Riak-Last-Modified">>, MD1),
                dict:fetch(<<"X-Riak-Last-Modified">>, MD2))

%% @private
merge_sibs({Min,  {{struct, Oin},  Lin}},
           {Macc, {{struct, Oacc}, Lacc}}) ->
    %% add keys to M, but do not overwrite
    M = dict:merge(fun(_, Ma, _) -> Ma end, Macc, Min),
    %% add keys to O, but do not overwrite
    O = lists:foldl(fun({Field, Value}, Acc) ->
                            case proplists:is_defined(Field, Acc) of
                                true -> Acc;
                                false -> [{Field, Value}|Acc]
                    Oacc, Oin),
    %% union set of links
    L = sets:to_list(
          sets:union(sets:from_list(Lin), sets:from_list(Lacc))),
    {M, {{struct, O}, L}}.

%% Testing

sib_sort_test() ->
    Dated = fun(D,N) ->
    A = Dated({{2009,8,28},{14,0,0}}, a),
    B = Dated({{2009,8,28},{14,0,1}}, b),
    C = Dated({{2009,8,28},{14,1,0}}, c),
    D = Dated({{2009,8,28},{15,0,0}}, d),
    E = Dated({{2009,8,29},{14,0,0}}, e),
    F = Dated({{2009,9,28},{14,0,0}}, f),
    G = Dated({{2010,8,28},{14,0,0}}, g),
    [G,F,E,D,C,B,A] = sort_sibs([D,G,B,A,C,F,E]).

merge_sibs_test_() ->
    [fun merge_output_structure_t/0,
     fun merge_metadata_t/0,
     fun merge_fields_t/0,
     fun merge_links_t/0].

%% merge didn't fail
merge_output_structure_t() ->
    {_CM, {{struct, _CO}, _CL}} =

%% metadata properly constructed
merge_metadata_t() ->
    {CM,_} = merge_sibs({dict:store(b, b, dict:store(c, b, dict:new())),
                        {dict:store(a, a, dict:store(c, a, dict:new())),
    3 = dict:size(CM),
    a = dict:fetch(a, CM),
    b = dict:fetch(b, CM),
    a = dict:fetch(c, CM).
%% fields properly constructed
merge_fields_t() ->
    {_,{{struct,CO},_}} = merge_sibs({dict:new(),
                                      {{struct, [{b,b},{c,b}]},[]}},
                                      {{struct, [{a,a},{c,a}]},[]}}),
    3 = length(CO),
    a = proplists:get_value(a, CO),
    b = proplists:get_value(b, CO),
    a = proplists:get_value(c, CO).

%% links properly constructed
merge_links_t() ->
    {_,{_,CL}} = merge_sibs({dict:new(),{{struct,[]},[b,c]}},
    3 = length(CL),
    true = lists:member(a, CL),
    true = lists:member(b, CL),
    true = lists:member(c, CL).

standard_sibling_merge_test() ->
    A = {dict:store(<<"X-Riak-Last-Modified">>,
    B = {dict:store(<<"X-Riak-Last-Modified">>,
    C = {dict:store(<<"X-Riak-Last-Modified">>,

    %% structure okay
    {MM, {{struct, MF}, ML}} = standard_sibling_merge([A,B,C]),
    %% metadata okay
    7 = dict:size(MM),
                 dict:fetch(<<"X-Riak-Last-Modified">>, MM)),
    [a,b,c,b,c,c] = [ dict:fetch(Q, MM) || Q <- [a,b,c,x,y,z] ],
    %% fields okay
    6 = length(MF),
    [a,b,c,b,c,c] = [ proplists:get_value(Q, MF) || Q <- [a,b,c,x,y,z] ],
    %% links okay
    4 = length(ML),
    true = lists:all(fun(Q) -> lists:member(Q, ML) end, [a,b,c,z]).
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.