Current section
Files
Jump to
Current section
Files
src/jc_s.erl
%%% ----------------------------------------------------------------------------
%%% @author Jim Rosenblum
%%% @copyright (C) 2011 - 2015, Jim Rosenblum
%%% @doc This module wraps the mnesia-interacting, lower-level functions
%%% implemented in {@link jc_store. jc_store} to provide a public, DIRTY,
%%% set of opperations. It utilizes a singleton gen_server, {@link jc_sequence},
%%% to provide support for serializing opperations.
%%%
%%% A sequence parameter is used to disallow PUT, EVICT and REMOVE
%%% operations whose sequence parameter is less than the highest, map-specific,
%%% sequence number seen thus far.
%%%
%%% s_jc can be called directly by Erlang clients; or,
%%% Java node -> JInterface -> {@link jc_bridge. jc_bridge} -> jc_s; or,
%%% Application -> TPC/IP -> {@link jc_protocol. jc_protocol} -> jc_S
%%%
%%% @version {@version}
%%% @end
%%% Created : 16 December 2011 by Jim Rosenblum
%%% ----------------------------------------------------------------------------
-module(jc_s).
% Put Functions
-export([put/4, put/5,
put_all/3, put_all/4]).
% Delete Functions
-export([evict/3,
evict_all_match/2,
evict_match/3,
remove_items/3]).
% Meta Functions
-export([sequence/1, sequence/0]).
% Record and type definitions.
-include("../include/records.hrl").
-type trx_ret_error() :: {error, badarg | out_of_seq | term()}.
-define(INFINITY, 0).
-define(NO_SEQ, -1).
-define(VALID(X), is_integer(X) andalso (X >= 0)).
%% =============================================================================
%% META API
%% =============================================================================
%% -----------------------------------------------------------------------------
%% @doc Return the sequence number associated with the given map.
%%
-spec sequence(map()) -> non_neg_integer().
sequence(Map) ->
Seq = case sequence_for(Map) of
[] -> 0;
N -> N
end,
Seq.
%% -----------------------------------------------------------------------------
%% @doc Return a sorted list of {map, sequence numbers} for each map.
%%
-spec sequence() -> [] | [{map_name(), non_neg_integer()}].
sequence() ->
sequence_for(all).
%% =============================================================================
%% PUT API
%% =============================================================================
%% -----------------------------------------------------------------------------
%% @doc Put the entry into the cache with a TTL of infinity using the sequence
%% number to ensure serialized operations.
%%
-spec put(map_name(), key(), value(), jc_sequence:seq()) ->
{ok, key()} | trx_ret_error().
put(Map, Key, Value, Seq) when ?VALID(Seq) ->
put(Map, Key, Value, ?INFINITY, Seq);
put(_Map, _Key, _Value, _Seq) ->
{error, badarg}.
%% -----------------------------------------------------------------------------
%% @doc Put the entry into the cache with the TTL using the sequence number
%% to ensure serialized operations.
%%
-spec put(map_name(), key(), value(), ttl(), jc_sequence:seq()) ->
{ok, key()} |
trx_ret_error().
put(Map, Key, Value, TTL, Seq) when ?VALID(TTL) andalso ?VALID(Seq)->
lager:debug("~p: put {~p, ~p} with TTL: ~p and seq: ~p.",
[?MODULE, Map, Key, TTL, Seq]),
F = fun() ->
case test_set(Map, Seq) of
true -> do_put(Map, Key, Value, TTL);
false -> {error, out_of_seq}
end
end,
Message = "~p: out of sequence put (~p, ~p) ~p.",
trans_execute(F, Message, [?MODULE, Map, Key, Seq]);
put(_M, _K, _V, _T, _S) ->
{error, badarg}.
% Need to do the actual put from a number of functions, so pull it out to reduce
% nested transactions.
%
do_put(Map, Key, Value, TTL) ->
Ref = make_ref(),
{ok, {put, Ref}} = jc_store:put(Map, Key, Value, TTL, Ref),
{ok, Key}.
%% -----------------------------------------------------------------------------
%% @doc Put all the {K,V} tuples contained in the list with a TTL of infinity.
%% Use the sequence number to ensure serialized operations. Return the number
%% of successes.
%%
-spec put_all(map_name(), list({key(), value()}), jc_sequence:seq()) ->
{ok, non_neg_integer()} | trx_ret_error().
put_all(Map, KVList, Seq) when ?VALID(Seq) ->
put_all(Map, KVList, ?INFINITY, Seq);
put_all(_M, _K, _S) ->
{error, badarg}.
%% -----------------------------------------------------------------------------
%% @doc Put all the {K,V} pairs contained in the list using the supplied TTL.
%% Use the sequence number to ensure serialized operations. Return the number
%% of successes.
%%
-spec put_all(map_name(), list({key(), value()}), ttl(), jc_sequence:seq()) ->
{ok, non_neg_integer()} | trx_ret_error().
put_all(Map, KVList, TTL, Seq) when ?VALID(TTL) andalso ?VALID(Seq) ->
lager:debug("~p: put_all for map: ~p with TTL: ~p and seq: ~p.",
[?MODULE, Map, TTL, Seq]),
F = fun() ->
case test_set(Map, Seq) of
true ->
[do_put(Map, Key, Value, TTL) || {Key, Value} <- KVList];
false -> {error, out_of_seq}
end
end,
M = "~p: out of sequence put_all Map: ~p, and Seq: ~p.",
case trans_execute(F, M, [?MODULE, Map, Seq]) of
{error, _} = E ->
E;
Results ->
{ok, length([K || {ok, K} <- Results])}
end;
put_all(_M, _K, _T, _S) ->
{error, badarg}.
%% -----------------------------------------------------------------------------
%% @doc Evict {@link map_name(). Map}, {@link key(). Key} if sequence is
%% greater than or equal to the last seen sequence.
%
-spec evict(map_name(), key(), jc_sequence:seq()) -> ok | trx_ret_error().
evict(Map, Key, Seq) ->
lager:debug("~p: evict map:~p, key: ~p, and seq: ~p.).",
[?MODULE, Map, Key, Seq]),
F = fun() ->
case test_set(Map, Seq) of
true ->
jc_store:evict(Map, Key);
false -> {error, out_of_seq}
end
end,
M = "~p: out of sequence evict {~p, ~p}, seq: ~p.",
case trans_execute(F, M, [?MODULE, Map, Key, Seq]) of
{error, _} = E ->
E;
_Result ->
ok
end.
%% -----------------------------------------------------------------------------
%% @doc Evict Map/Key from the cache for Key's whose value matches the criteria
%% if not out of sequence.
%% Assumes the the criteria is a string in the form of "a.b.c=true", where
%% a.b.c is dot-path consisting of dot-separated JSON object-keys or JSON array
%% indexes: "bed.id=10" or "bed.id.2.type.something=\"stringvalue\"".
%%
-spec evict_match(map_name(), Criteria::string(), jc_sequence:seq()) ->
ok | trx_ret_error().
evict_match(Map, Criteria, Seq) when ?VALID(Seq) ->
lager:debug("~p: evict_match with map: ~p, criteria: ~p and seq: ~p.",
[?MODULE, Map, Criteria, Seq]),
F = fun() ->
case test_set(Map, Seq) of
true ->
do_evict_match(Map, Criteria);
false -> {error, out_of_seq}
end
end,
M = "~p: out of sequence evict_match. Map: ~p, Criteria: ~p, and seq ~p.",
case trans_execute(F, M, [?MODULE, Map, Criteria, Seq]) of
{error, _} = E ->
E;
_Result ->
ok
end;
evict_match(_M, _C, _S) ->
{error, badarg}.
% Need to do the actual evict_match from a number of functions, so pull it
% out to reduce nested transactions.
%
do_evict_match(Map, Criteria) ->
Fun = fun(M, K, _, Acc) -> jc_store:evict(M, K), Acc end,
jc:fun_match(Map, Criteria, Fun),
ok.
%% -----------------------------------------------------------------------------
%% @doc Call {@link evict_match/2} for each Map with the given sequence number.
%% Assumes the the criteria is a string in the form of "a.b.c=true", where
%% a.b.c is dot-path consisting of dot-separated JSON object-keys or JSON array
%% indexes: "bed.id=10" or "bed.id.2.type.something=\"stringvalue\"".
%%
-spec evict_all_match(Criteria::string(), jc_sequence:seq()) ->
ok | trx_ret_error().
evict_all_match(Criteria, Seq) when ?VALID(Seq) ->
lager:debug("~p: evict_all_match with ~p and seq ~p:.",
[?MODULE, Criteria, Seq]),
{maps, Maps} = jc:maps(),
_ = [evict_match(M, Criteria, Seq) || M <- Maps],
ok;
evict_all_match(_C, _S) ->
{error, badarg}.
%% -----------------------------------------------------------------------------
%% @doc Evict all K's, return all {K, V} pairs that were found. Enforces that
%% the sequence number is greater than what has been seen.
%%
-spec remove_items(Map::map_name(), Keys::[key()], jc_sequence:seq()) ->
{ok, [{key(), value()}]} | trx_ret_error().
remove_items(Map, Keys, Seq) when ?VALID(Seq) ->
lager:debug("~p: remove_items (~p, ~p, ~p).",[?MODULE, Map, Keys, Seq]),
F = fun(Key, Acc) ->
case jc_store:get(Map, Key) of
{ok, #key_to_value{value = Value}} ->
jc_store:evict(Map, Key),
[{Key, Value} | Acc];
{ok, jc_miss} ->
Acc
end
end,
F2 = fun() ->
case test_set(Map, Seq) of
true ->
lists:foldl(F, [], Keys);
false ->
{error, out_of_seq}
end
end,
M = "~p: out of sequence remove_items. Map: ~p, Keys: ~p, Seq: ~p.",
case trans_execute(F2, M, [?MODULE, Map, Keys, Seq]) of
{error, _} = E ->
E;
Result ->
{ok, Result}
end;
remove_items(_M, _K, _S) ->
{error, badarg}.
%% -----------------------------------------------------------------------------
%% If new sequence is >= to existing one, bump the sequence number and return
%% true.
%%
test_set(Map, Seq) ->
try jc_sequence:test_set(Map, Seq)
catch
exit:{timeout, _R} ->
lager:warning("~p: call to jc_sequence timed out. Retrying.",[?MODULE]),
timer:sleep(100),
test_set(Map, Seq);
C:E ->
lager:warning("~p: call to jc_sequence failed: ~p:~p. Retrying.",
[?MODULE, C, E]),
timer:sleep(100),
test_set(Map, Seq)
end.
%% -----------------------------------------------------------------------------
%% Return sorted [{map, seq_no}] per the input parameter. Hit the table
%% directly, no need to use the singleton, gen_server.
sequence_for(all) ->
case mnesia:dirty_match_object(seq, mnesia:table_info(seq, wild_pattern)) of
[] ->
[];
Records ->
lists:sort([{R#seq.map, R#seq.seq_no} || R <- Records])
end;
sequence_for(Map) ->
case mnesia:dirty_read({seq, Map}) of
[] ->
[];
[#seq{seq_no = No}] ->
No
end.
%% -----------------------------------------------------------------------------
%% Execute F in the context of a transaction, if F is not already executing in
%% the context of a transaction -- Avoids nesting.
%%
trans_execute(F, Message, Params) ->
case mnesia:is_transaction() of
true -> F();
false ->
case mnesia:sync_dirty(F) of
{error, out_of_seq}=SeqError ->
lager:warning(Message, Params),
SeqError;
{exit, R} ->
{error, R};
Result ->
Result
end
end.