riak / src / jiak.erl

justin 1c37b76 















jus...@basho.com 661615e 
justin 1c37b76 











































Rusty Klophaus a092ca3 
justin 1c37b76 


Bryan Fink 22dded0 

justin 1c37b76 


Rusty Klophaus d6ac059 
justin 1c37b76 
Rusty Klophaus a092ca3 

justin 1c37b76 

Rusty Klophaus d6ac059 




justin 1c37b76 






















































Bryan Fink 22dded0 




























































































%% 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

%%   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.    

%% @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:
%%<pre>
%%      {
%%       "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"
%%      }
%%</pre>
%%      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.
-module(jiak).

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

-include_lib("eunit/include/eunit.hrl").

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

%% @spec client_connect(Node :: node())
%%        -> {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) -> 
    case riak:client_connect(Node) of
        {ok, C} -> {ok, jiak_client:new(C)};
        Error -> Error
    end.

%% @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
%%</li></ul>
standard_sibling_merge(Sibs) ->
    [{Ms,{{struct,Os},Ls}}|Rest] = sort_sibs(Sibs),
    lists:foldl(fun merge_sibs/2,
                {Ms,{{struct,lists:keysort(1,Os)},Ls}},
                Rest).

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

%% @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]
                            end
                    end,
                    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) ->
                    {dict:store(<<"X-Riak-Last-Modified">>,
                                httpd_util:rfc1123_date(D),
                                dict:new()),
                     N}
            end,
    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}} =
        merge_sibs({dict:new(),{{struct,[]},[]}},
                   {dict:new(),{{struct,[]},[]}}).

%% metadata properly constructed
merge_metadata_t() ->
    {CM,_} = merge_sibs({dict:store(b, b, dict:store(c, b, dict:new())),
                         {{struct,[]},[]}},
                        {dict:store(a, a, dict:store(c, a, dict:new())),
                         {{struct,[]},[]}}),
    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}]},[]}},
                                     {dict:new(),
                                      {{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]}},
                            {dict:new(),{{struct,[]},[a,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">>,
                    httpd_util:rfc1123_date({{2009,8,28},{14,0,0}}),
                    dict:store(a,a,dict:store(x,a,dict:store(y,a,dict:new())))),
         {{struct,[{a,a},{x,a},{y,a}]},[a,z]}},
    B = {dict:store(<<"X-Riak-Last-Modified">>,
                    httpd_util:rfc1123_date({{2009,8,28},{14,0,1}}),
                    dict:store(b,b,dict:store(x,b,dict:store(z,b,dict:new())))),
         {{struct,[{b,b},{x,b},{z,b}]},[b,z]}},
    C = {dict:store(<<"X-Riak-Last-Modified">>,
                    httpd_util:rfc1123_date({{2009,8,28},{14,0,2}}),
                    dict:store(c,c,dict:store(y,c,dict:store(z,c,dict:new())))),
         {{struct,[{c,c},{y,c},{z,c}]},[c,z]}},

    %% structure okay
    {MM, {{struct, MF}, ML}} = standard_sibling_merge([A,B,C]),
    
    %% metadata okay
    7 = dict:size(MM),
    ?assertEqual(httpd_util:rfc1123_date({{2009,8,28},{14,0,2}}),
                 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 ProjectModifiedEvent.java.
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.