Commits

Anonymous committed b5ff098 Merge

merge

  • Participants
  • Parent commits b5408d6, 1767034

Comments (0)

Files changed (32)

 383abb86f65dc78f47af1265997ce021875c7bb4 riak-0.2
 383abb86f65dc78f47af1265997ce021875c7bb4 riak-0.2
 f98d13b442f6ebf14645afd878919edacd9f1a98 riak-0.2
+661615e510dda3fdf251aac4bd5bd648671c2fa4 riak-0.3

client_lib/java/Makefile

 clean:
 	@rm -rf build dist
 
+test: jar
+	java -cp dist/jiak_client.jar com.basho.riak.JiakTest
+
 javadoc: compile
 	@javadoc -public -sourcepath src com.basho.riak -classpath build -d javadoc -noqualifier java.lang:java.io:java.util:org.json

client_lib/java/src/com/basho/riak/JiakClient.java

 import java.util.List;
 import java.util.Map;
 
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 		return requestConn;
 	}
 
+	/**
+	 * Perform a map/reduce link walking operation and return a list of lists of
+	 * JiakObjects for which the "accumulate" flag is true. (see documentation
+	 * on walkSpec below).
+	 * 
+	 * @param bucket
+	 *            The bucket of the "starting object"
+	 * @param key
+	 *            The key of the "starting object"
+	 * @param walkSpec
+	 *            A URL-path (omit beginning /) of the form
+	 *            <code>bucket,tag-spec,accumulateFlag</code> The
+	 *            <code>tag-spec "_"</code> matches all tags.
+	 *            <code>accumulateFlag</code> is either the String "1" or "0".
+	 * @return An <code>ArrayList</code> of <code>ArrayLists</code>, where each
+	 *         sub-list corresponds to a <code>walkSpec</code> element that had
+	 *         <code>accumulateFlag</code> equal to 1.
+	 * @throws IOException
+	 *             If an error occurs communicating with the Riak server.
+	 * @throws JSONException
+	 *             If an error occurs unparsing JSON requests or responses.
+	 * @throws JiakException
+	 *             If the Riak server returns an error or unexpected response
+	 *             code.
+	 */
+	public ArrayList<ArrayList<JiakObject>> walk(final String bucket,
+			final String key, final String walkSpec) throws IOException,
+			JSONException, JiakException {
+		final ArrayList<ArrayList<JiakObject>> results = new ArrayList<ArrayList<JiakObject>>();
+		final String reqURI = makeURI(bucket + "/" + key + "/" + walkSpec);
+		final Map<String, String> reqHeaders = new HashMap<String, String>();
+		reqHeaders.put("Accept", "application/json");
+		final HttpURLConnection requestConn = doRequest("GET", reqURI, null,
+				reqHeaders);
+		final int responseCode = requestConn.getResponseCode();
+		if (responseCode == 404)
+			return null;
+		final JSONArray jsonResults = expect(200, requestConn).getJSONArray(
+				"results");
+		for (int i = 0; i < jsonResults.length(); ++i) {
+			final ArrayList<JiakObject> ra = new ArrayList<JiakObject>();
+			final JSONArray ja = jsonResults.getJSONArray(i);
+			for (int j = 0; j < ja.length(); ++j) {
+				final JSONObject json = ja.getJSONObject(j);
+				final JiakObject jo = new JiakObject(json.getString("bucket"),
+						json.getString("key"));
+				jo.update(json);
+				ra.add(jo);
+			}
+			results.add(ra);
+		}
+		return results;
+	}
+
 	protected JSONObject expect(final int responseCode,
 			final HttpURLConnection connection) throws JSONException,
 			JiakException, IOException {

client_lib/java/src/com/basho/riak/JiakTest.java

+package com.basho.riak;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONArray;
+
+public class JiakTest {
+
+	public static void main(String[] args) throws Exception {
+		JiakClient client = new JiakClient("127.0.0.1", "8098");
+		ArrayList<String> allKeys = new ArrayList<String>();
+		allKeys.add("testkey");
+		allKeys.add("jroot");
+		allKeys.add("jleaf1");
+		allKeys.add("jleaf2");
+		allKeys.add("jleaf3");
+		for (String k : allKeys) {
+			try {
+				client.delete("jiak_example", k);
+			}
+			catch (Exception e) {}
+		}
+		JiakObject jo = new JiakObject("jiak_example", "testkey");
+		jo.set("foo", 2);
+		client.store(jo);
+		jo = client.fetch("jiak_example", "testkey");
+		assert(jo.get("foo").equals(2));
+		JiakObject jRoot = new JiakObject("jiak_example", "jroot");
+		jRoot.set("foo", 0);
+		JiakObject jLeaf1 = new JiakObject("jiak_example", "jleaf1");
+		jLeaf1.set("foo", "in results");
+		JiakObject jLeaf2 = new JiakObject("jiak_example", "jleaf2");
+		jLeaf2.set("foo", "in results");
+		JiakObject jLeaf3 = new JiakObject("jiak_example", "jleaf3");
+		jLeaf3.set("foo", "not in results");
+		JSONArray links = new JSONArray();
+		links.put(new String[]{"jiak_example", "jleaf1", "tag_one"});
+		links.put(new String[]{"jiak_example", "jleaf2", "tag_one"});		
+		links.put(new String[]{"jiak_example", "jleaf3", "tag_other"});				
+		jRoot.setLinks(links);
+		client.store(jRoot);
+		client.store(jLeaf1);
+		client.store(jLeaf2);
+		client.store(jLeaf3);
+		ArrayList<ArrayList<JiakObject>> res = client.walk("jiak_example", "jroot", "jiak_example,tag_one,1");
+		for (ArrayList<JiakObject> i : res) {
+			for (JiakObject j: i) {
+				assert(j.get("foo").equals("in results"));
+			}
+		}
+		for (String k : allKeys) {
+			try {
+				client.delete("jiak_example", k);
+			}
+			catch (Exception e) {}
+		}
+		System.out.println("all tests passed");
+	}
+}

config/riak-demo.erlenv

 {cluster_name, "default"}.
 {ring_state_dir, "priv/ringstate"}.
+%% note: make sure you configure your operating system limits for maximum
+%% open files (using ulimit/sysctl) when creating larger ring sizes. 
 {ring_creation_size, 16}.
 {gossip_interval, 60000}.
 {doorbell_port, 9000}.

config/riak-dets.erlenv

 {cluster_name, "default"}.
 {ring_state_dir, "priv/ringstate"}.
+%% note: make sure you configure your operating system limits for maximum
+%% open files (using ulimit/sysctl) when creating larger ring sizes. 
 {ring_creation_size, 16}.
 {gossip_interval, 60000}.
 {doorbell_port, 9000}.

config/riak-ets-jiak.erlenv

 {cluster_name, "default"}.
 {ring_state_dir, "priv/ringstate"}.
+%% note: make sure you configure your operating system limits for maximum
+%% open files (using ulimit/sysctl) when creating larger ring sizes. 
 {ring_creation_size, 16}.
 {gossip_interval, 60000}.
 {doorbell_port, 9000}.

config/riak-osmos.erlenv

 !{cluster_name, "default"}.
 {ring_state_dir, "priv/ringstate"}.
+%% note: make sure you configure your operating system limits for maximum
+%% open files (using ulimit/sysctl) when creating larger ring sizes. 
 {ring_creation_size, 16}.
 {gossip_interval, 60000}.
 {doorbell_port, 9000}.

config/riak.erlenv

+%% cluster_name can be any short string; it should be the same for all
+%%  nodes that will be participating in the same cluster.
 {cluster_name, "default"}.
+%% ring_state_dir is where ring membership information will be stored.
 {ring_state_dir, "priv/ringstate"}.
+%% ring_creation_size determines the number of partitions used for hashing
+%%  document keys.  This cannot be changed after cluster initialization.
+%%  Ensure that it is at least 10x the number of hosts you might ever
+%%  add to the cluster.
+%% note: make sure you configure your operating system limits for maximum
+%% open files (using ulimit/sysctl) when creating larger ring sizes. 
 {ring_creation_size, 16}.
+%% gossip_interval (milliseconds) is the frequency of gossip messages.
+%%  Making it larger will reduce chatter, but will cause greater delays
+%%  in ring convergence between nodes.
 {gossip_interval, 60000}.
+%% doorbell_port is the UDP port used by clients and other nodes to connect
+%%  to the cluster via this node.  If this is undefined the node will be
+%%  storage-only and cannot be an entry point for API requests or joins.
 {doorbell_port, 9000}.
+%% storage_backend specifies the Erlang module defining the storage mechanism
+%%  that will be used on this node. 
 {storage_backend, riak_dets_backend}.
+%% Different storage backends can use other configuration variables.  For
+%%  instance, riak_dets_backend_root determines the directory under which
+%%  dets files will be placed.
 {riak_dets_backend_root, "/path/to/riak/store"}.
+%% riak_cookie is the Erlang cookie that the cluster shares.  This must be
+%%  the same across all nodes.
 {riak_cookie, default_riak_cookie}.
+%% riak_heart_command will be used by heart to restart the system in case of
+%%  a lockup or certain types of crashes.  Make sure to fix the paths in
+%%  the command if you wish for automatic restarts to work.
 {riak_heart_command, "(cd /path/to/riak; ./start-restart.sh /path/to/riak/config/riak.erlenv)"}.
+%% riak_nodename is the first part of the name that this node will use to
+%%  identify itself.  This must be different for all ndoes with the same
+%%  value of riak_hostname.
 {riak_nodename, riak}.
+%% riak_hostname is the second part of the name that this node will use to
+%%  identify itself.  This must be an IP or hostname that the other nodes
+%%  can use to contact the system.
 {riak_hostname, "127.0.0.1"}.
+%% riak_web_ip is the IP address that Riak's HTTP interface will bind to.
+%%  If this is undefined, the HTTP interface will not run.
 {riak_web_ip, "127.0.0.1"}.
+%% riak_web_port is the TCP port that Riak's HTTP interface will bind to.
 {riak_web_port, 8098}.
+%% jiak_name is the first part of all URLs used by Riak's HTTP interface.
+%%  See the HTTP interface documents for more details.  If you change this,
+%%  the defaults used by some Riak HTTP clients may not work.
 {jiak_name, "jiak"}.
+#!/usr/bin/env bash
+# debug-join <configfile> <ip> <port>
+# join an existing riak ring via a node known to be listening on ip:port
+# run in interactive debug shell mode
+. riak-env.sh
+NODENAME=$(erl -noshell -pa ebin -eval "error_logger:tty(false), riak_app:read_config(\"$1\"), io:format(\"~p~n\",[riak:get_app_env(riak_nodename)])" -run init stop)
+RHOSTNAME=$(erl -noshell -pa ebin -eval "error_logger:tty(false), riak_app:read_config(\"$1\"), io:format(\"~s~n\",[riak:get_app_env(riak_hostname)])" -run init stop)
+if [ "$NODENAME" = "no_riak_nodename_undefined" ]; then
+    echo "riak_nodename not set in config file, cannot start";
+else
+    exec erl -connect_all false -pa deps/*/ebin -pa ebin -name ${NODENAME}@${RHOSTNAME} -run riak start $1 -run riak_startup join_cluster $2 $3
+fi
 #!/usr/bin/env bash
 # debug-restart <configfile>
 # assuming we've run a node from here before, start back up in debug mode
+. riak-env.sh
 NODENAME=$(erl -noshell -pa ebin -eval "error_logger:tty(false), riak_app:read_config(\"$1\"), io:format(\"~p~n\",[riak:get_app_env(riak_nodename)])" -run init stop)
 RHOSTNAME=$(erl -noshell -pa ebin -eval "error_logger:tty(false), riak_app:read_config(\"$1\"), io:format(\"~s~n\",[riak:get_app_env(riak_hostname)])" -run init stop)
 if [ "$NODENAME" = "no_riak_nodename_undefined" ]; then

deps/webmachine/demo/mochiweb/src/mochijson.erl

            end,
     [NewC | json_encode_string_utf8_1(Cs)];
 json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF ->
-    json_encode_string_unicode(xmerl_ucs:from_utf8(All));
+    [?Q | Rest] = json_encode_string_unicode(xmerl_ucs:from_utf8(All)),
+    Rest;
 json_encode_string_utf8_1([]) ->
     "\"".
 
     end.
 
 test_all() ->
+    test_issue33(),
     test_one(e2j_test_vec(utf8), 1).
 
+test_issue33() ->
+    %% http://code.google.com/p/mochiweb/issues/detail?id=33
+    Js = {struct, [{"key", [194, 163]}]},
+    Encoder = encoder([{input_encoding, utf8}]),
+    "{\"key\":\"\\u00a3\"}" = lists:flatten(Encoder(Js)).
+
 test_one([], _N) ->
     %% io:format("~p tests passed~n", [N-1]),
     ok;

deps/webmachine/demo/mochiweb/src/mochijson2.erl

 %% @type json_term() = json_string() | json_number() | json_array() |
 %%                     json_object()
 
--record(encoder, {handler=null}).
+-record(encoder, {handler=null,
+                  utf8=false}).
 
 -record(decoder, {object_hook=null,
                   offset=0,
 
 %% @spec encoder([encoder_option()]) -> function()
 %% @doc Create an encoder/1 with the given options.
+%% @type encoder_option() = handler_option() | utf8_option()
+%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
 encoder(Options) ->
     State = parse_encoder_options(Options, #encoder{}),
     fun (O) -> json_encode(O, State) end.
 parse_encoder_options([], State) ->
     State;
 parse_encoder_options([{handler, Handler} | Rest], State) ->
-    parse_encoder_options(Rest, State#encoder{handler=Handler}).
+    parse_encoder_options(Rest, State#encoder{handler=Handler});
+parse_encoder_options([{utf8, Switch} | Rest], State) ->
+    parse_encoder_options(Rest, State#encoder{utf8=Switch}).
 
 parse_decoder_options([], State) ->
     State;
     [$, | Acc1] = lists:foldl(F, "{", Props),
     lists:reverse([$\} | Acc1]).
 
-json_encode_string(A, _State) when is_atom(A) ->
+json_encode_string(A, State) when is_atom(A) ->
     L = atom_to_list(A),
     case json_string_is_safe(L) of
         true ->
             [?Q, L, ?Q];
         false ->
-            json_encode_string_unicode(xmerl_ucs:from_utf8(L), [?Q])
+            json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
     end;
-json_encode_string(B, _State) when is_binary(B) ->
+json_encode_string(B, State) when is_binary(B) ->
     case json_bin_is_safe(B) of
         true ->
             [?Q, B, ?Q];
         false ->
-            json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q])
+            json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
     end;
 json_encode_string(I, _State) when is_integer(I) ->
     [?Q, integer_to_list(I), ?Q];
-json_encode_string(L, _State) when is_list(L) ->
+json_encode_string(L, State) when is_list(L) ->
     case json_string_is_safe(L) of
         true ->
             [?Q, L, ?Q];
         false ->
-            json_encode_string_unicode(L, [?Q])
+            json_encode_string_unicode(L, State, [?Q])
     end.
 
 json_string_is_safe([]) ->
             false
     end.
 
-json_encode_string_unicode([], Acc) ->
+json_encode_string_unicode([], _State, Acc) ->
     lists:reverse([$\" | Acc]);
-json_encode_string_unicode([C | Cs], Acc) ->
+json_encode_string_unicode([C | Cs], State, Acc) ->
     Acc1 = case C of
                ?Q ->
                    [?Q, $\\ | Acc];
                    [$r, $\\ | Acc];
                $\t ->
                    [$t, $\\ | Acc];
-               C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+               C when C >= 0, C < $\s ->
+                   [unihex(C) | Acc];
+               C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
+                   [xmerl_ucs:to_utf8(C) | Acc];
+               C when  C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
                    [unihex(C) | Acc];
                C when C < 16#7f ->
                    [C | Acc];
                _ ->
                    exit({json_encode, {bad_char, C}})
            end,
-    json_encode_string_unicode(Cs, Acc1).
+    json_encode_string_unicode(Cs, State, Acc1).
 
 hexdigit(C) when C >= 0, C =< 9 ->
     C + $0;
 
 test_all() ->
     [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
+    test_encoder_utf8(),
     test_one(e2j_test_vec(utf8), 1).
 
 test_one([], _N) ->
      {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
       "[-123,\"foo\",{\"bar\":[]},null]"}
     ].
+
+%% test utf8 encoding
+test_encoder_utf8() ->
+    %% safe conversion case (default)
+    [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = 
+        encode(<<1,"\321\202\320\265\321\201\321\202">>),
+
+    %% raw utf8 output (optional)
+    Enc = mochijson2:encoder([{utf8, true}]),
+    [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
+        Enc(<<1,"\321\202\320\265\321\201\321\202">>).

deps/webmachine/demo/mochiweb/src/mochiweb_cookies.erl

 %% @spec cookie(Key::string(), Value::string(), Options::[Option]) -> header() 
 %% where Option = {max_age, integer()} | {local_time, {date(), time()}} 
 %%                | {domain, string()} | {path, string()}
-%%                | {secure, true | false}
+%%                | {secure, true | false} | {http_only, true | false}
 %%
 %% @doc Generate a Set-Cookie header field tuple.
 cookie(Key, Value, Options) ->
             Path ->
                 ["; Path=", quote(Path)]
         end,
-    CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart],
+    HttpOnlyPart =
+        case proplists:get_value(http_only, Options) of
+            true ->
+                "; HttpOnly";
+            _ ->
+                ""
+        end,
+    CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart, HttpOnlyPart],
     {"Set-Cookie", lists:flatten(CookieParts)}.
 
 

deps/webmachine/demo/mochiweb/src/mochiweb_html.erl

      {data, <<" A= B <= C ">>, false},
      {end_tag, <<"script">>}] =
         tokens(<<"<script type=\"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type =\"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type = \"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type= \"text/javascript\"> A= B <= C </script>">>),
     [{start_tag, <<"textarea">>, [], false},
      {data, <<"<html></body>">>, false},
      {end_tag, <<"textarea">>}] =
     O = S1#decoder.offset,
     case B of
         <<_:O/binary, "=", _/binary>> ->
-            tokenize_word_or_literal(B, ?INC_COL(S1));
+            S2 = skip_whitespace(B, ?INC_COL(S1)),
+            tokenize_word_or_literal(B, S2);
         _ ->
             {Attr, S1}
     end.

deps/webmachine/demo/mochiweb/src/mochiweb_request.erl

 recv_body(MaxBody) ->
     % we could use a sane constant for max chunk size
     Body = stream_body(?MAX_RECV_BODY, fun
-        ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> 
+        ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) ->
             iolist_to_binary(lists:reverse(BinAcc));
         ({Length, Bin}, {LengthAcc, BinAcc}) ->
             NewLength = Length + LengthAcc,
             if NewLength > MaxBody ->
                 exit({body_too_large, chunked});
-            true -> 
+            true ->
                 {NewLength, [Bin | BinAcc]}
             end
         end, {0, []}, MaxBody),
 
 stream_body(MaxChunkSize, ChunkFun, FunState) ->
     stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
-    
+
 stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
-
-    case get_header_value("expect") of
+    Expect = case get_header_value("expect") of
+                 undefined ->
+                     undefined;
+                 Value when is_list(Value) ->
+                     string:to_lower(Value)
+             end,
+    case Expect of
         "100-continue" ->
             start_raw_response({100, gb_trees:empty()});
         _Else ->
                 exit({body_too_large, content_length});
             _ ->
                 stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState)
-            end;     
+            end;
         Length ->
             exit({length_not_integer, Length})
     end.
 
 read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) ->
     Fun({Length, read_chunk(Length)}, FunState).
-    
+
 %% @spec serve_file(Path, DocRoot) -> Response
 %% @doc Serve a file relative to DocRoot.
 serve_file(Path, DocRoot) ->
     %% valid, multiple range
     io:format("Testing parse_range_request with valid multiple ranges~n"),
     io:format("1"),
-    [{20, 30}, {50, 100}, {110, 200}] = 
+    [{20, 30}, {50, 100}, {110, 200}] =
         parse_range_request("bytes=20-30,50-100,110-200"),
     io:format("2"),
-    [{20, none}, {50, 100}, {none, 200}] = 
+    [{20, none}, {50, 100}, {none, 200}] =
         parse_range_request("bytes=20-,50-100,-200"),
     io:format(".. ok~n"),
-    
+
     %% no ranges
     io:format("Testing out parse_range_request with no ranges~n"),
     io:format("1"),
     [] = parse_range_request("bytes="),
     io:format(".. ok~n"),
-    
+
     Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
     BodySize = size(Body), %% 60
     BodySize = 60,
     io:format("4"),
     {30, 30} = range_skip_length({30, none}, BodySize), %% 30-
     io:format(".. ok ~n"),
-    
+
     %% valid edge cases for range_skip_length
     io:format("Testing out range_skip_length on valid edge case ranges~n"),
     io:format("1"),
     invalid_range = range_skip_length({BodySize, none}, BodySize),
     io:format(".. ok ~n"),
     ok.
-    
+

deps/webmachine/demo/mochiweb/src/mochiweb_skel.erl

     "skel".
 
 skelcopy(Src, DestDir, Name, LDst) ->
-    {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name),
+    Dest = re:replace(filename:basename(Src), skel(), Name,
+                      [global, {return, list}]),
     case file:read_file_info(Src) of
         {ok, #file_info{type=directory, mode=Mode}} ->
             Dir = DestDir ++ "/" ++ Dest,
         {ok, #file_info{type=regular, mode=Mode}} ->
             OutFile = filename:join(DestDir, Dest),
             {ok, B} = file:read_file(Src),
-            {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name),
+            S = re:replace(binary_to_list(B), skel(), Name,
+                           [{return, list}, global]),
             ok = file:write_file(OutFile, list_to_binary(S)),
             ok = file:write_file_info(OutFile, #file_info{mode=Mode}),
             io:format("    ~s~n", [filename:basename(Src)]),

deps/webmachine/deps/mochiweb/src/mochijson.erl

            end,
     [NewC | json_encode_string_utf8_1(Cs)];
 json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF ->
-    json_encode_string_unicode(xmerl_ucs:from_utf8(All));
+    [?Q | Rest] = json_encode_string_unicode(xmerl_ucs:from_utf8(All)),
+    Rest;
 json_encode_string_utf8_1([]) ->
     "\"".
 
     end.
 
 test_all() ->
+    test_issue33(),
     test_one(e2j_test_vec(utf8), 1).
 
+test_issue33() ->
+    %% http://code.google.com/p/mochiweb/issues/detail?id=33
+    Js = {struct, [{"key", [194, 163]}]},
+    Encoder = encoder([{input_encoding, utf8}]),
+    "{\"key\":\"\\u00a3\"}" = lists:flatten(Encoder(Js)).
+
 test_one([], _N) ->
     %% io:format("~p tests passed~n", [N-1]),
     ok;

deps/webmachine/deps/mochiweb/src/mochijson2.erl

 %% @type json_term() = json_string() | json_number() | json_array() |
 %%                     json_object()
 
--record(encoder, {handler=null}).
+-record(encoder, {handler=null,
+                  utf8=false}).
 
 -record(decoder, {object_hook=null,
                   offset=0,
 
 %% @spec encoder([encoder_option()]) -> function()
 %% @doc Create an encoder/1 with the given options.
+%% @type encoder_option() = handler_option() | utf8_option()
+%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
 encoder(Options) ->
     State = parse_encoder_options(Options, #encoder{}),
     fun (O) -> json_encode(O, State) end.
 parse_encoder_options([], State) ->
     State;
 parse_encoder_options([{handler, Handler} | Rest], State) ->
-    parse_encoder_options(Rest, State#encoder{handler=Handler}).
+    parse_encoder_options(Rest, State#encoder{handler=Handler});
+parse_encoder_options([{utf8, Switch} | Rest], State) ->
+    parse_encoder_options(Rest, State#encoder{utf8=Switch}).
 
 parse_decoder_options([], State) ->
     State;
     [$, | Acc1] = lists:foldl(F, "{", Props),
     lists:reverse([$\} | Acc1]).
 
-json_encode_string(A, _State) when is_atom(A) ->
+json_encode_string(A, State) when is_atom(A) ->
     L = atom_to_list(A),
     case json_string_is_safe(L) of
         true ->
             [?Q, L, ?Q];
         false ->
-            json_encode_string_unicode(xmerl_ucs:from_utf8(L), [?Q])
+            json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
     end;
-json_encode_string(B, _State) when is_binary(B) ->
+json_encode_string(B, State) when is_binary(B) ->
     case json_bin_is_safe(B) of
         true ->
             [?Q, B, ?Q];
         false ->
-            json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q])
+            json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
     end;
 json_encode_string(I, _State) when is_integer(I) ->
     [?Q, integer_to_list(I), ?Q];
-json_encode_string(L, _State) when is_list(L) ->
+json_encode_string(L, State) when is_list(L) ->
     case json_string_is_safe(L) of
         true ->
             [?Q, L, ?Q];
         false ->
-            json_encode_string_unicode(L, [?Q])
+            json_encode_string_unicode(L, State, [?Q])
     end.
 
 json_string_is_safe([]) ->
             false
     end.
 
-json_encode_string_unicode([], Acc) ->
+json_encode_string_unicode([], _State, Acc) ->
     lists:reverse([$\" | Acc]);
-json_encode_string_unicode([C | Cs], Acc) ->
+json_encode_string_unicode([C | Cs], State, Acc) ->
     Acc1 = case C of
                ?Q ->
                    [?Q, $\\ | Acc];
                    [$r, $\\ | Acc];
                $\t ->
                    [$t, $\\ | Acc];
-               C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+               C when C >= 0, C < $\s ->
+                   [unihex(C) | Acc];
+               C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
+                   [xmerl_ucs:to_utf8(C) | Acc];
+               C when  C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
                    [unihex(C) | Acc];
                C when C < 16#7f ->
                    [C | Acc];
                _ ->
                    exit({json_encode, {bad_char, C}})
            end,
-    json_encode_string_unicode(Cs, Acc1).
+    json_encode_string_unicode(Cs, State, Acc1).
 
 hexdigit(C) when C >= 0, C =< 9 ->
     C + $0;
 
 test_all() ->
     [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
+    test_encoder_utf8(),
     test_one(e2j_test_vec(utf8), 1).
 
 test_one([], _N) ->
      {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
       "[-123,\"foo\",{\"bar\":[]},null]"}
     ].
+
+%% test utf8 encoding
+test_encoder_utf8() ->
+    %% safe conversion case (default)
+    [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = 
+        encode(<<1,"\321\202\320\265\321\201\321\202">>),
+
+    %% raw utf8 output (optional)
+    Enc = mochijson2:encoder([{utf8, true}]),
+    [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
+        Enc(<<1,"\321\202\320\265\321\201\321\202">>).

deps/webmachine/deps/mochiweb/src/mochiweb_cookies.erl

 %% @spec cookie(Key::string(), Value::string(), Options::[Option]) -> header() 
 %% where Option = {max_age, integer()} | {local_time, {date(), time()}} 
 %%                | {domain, string()} | {path, string()}
-%%                | {secure, true | false}
+%%                | {secure, true | false} | {http_only, true | false}
 %%
 %% @doc Generate a Set-Cookie header field tuple.
 cookie(Key, Value, Options) ->
             Path ->
                 ["; Path=", quote(Path)]
         end,
-    CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart],
+    HttpOnlyPart =
+        case proplists:get_value(http_only, Options) of
+            true ->
+                "; HttpOnly";
+            _ ->
+                ""
+        end,
+    CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart, HttpOnlyPart],
     {"Set-Cookie", lists:flatten(CookieParts)}.
 
 

deps/webmachine/deps/mochiweb/src/mochiweb_html.erl

      {data, <<" A= B <= C ">>, false},
      {end_tag, <<"script">>}] =
         tokens(<<"<script type=\"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type =\"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type = \"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type= \"text/javascript\"> A= B <= C </script>">>),
     [{start_tag, <<"textarea">>, [], false},
      {data, <<"<html></body>">>, false},
      {end_tag, <<"textarea">>}] =
     O = S1#decoder.offset,
     case B of
         <<_:O/binary, "=", _/binary>> ->
-            tokenize_word_or_literal(B, ?INC_COL(S1));
+            S2 = skip_whitespace(B, ?INC_COL(S1)),
+            tokenize_word_or_literal(B, S2);
         _ ->
             {Attr, S1}
     end.

deps/webmachine/deps/mochiweb/src/mochiweb_request.erl

 recv_body(MaxBody) ->
     % we could use a sane constant for max chunk size
     Body = stream_body(?MAX_RECV_BODY, fun
-        ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> 
+        ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) ->
             iolist_to_binary(lists:reverse(BinAcc));
         ({Length, Bin}, {LengthAcc, BinAcc}) ->
             NewLength = Length + LengthAcc,
             if NewLength > MaxBody ->
                 exit({body_too_large, chunked});
-            true -> 
+            true ->
                 {NewLength, [Bin | BinAcc]}
             end
         end, {0, []}, MaxBody),
 
 stream_body(MaxChunkSize, ChunkFun, FunState) ->
     stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
-    
+
 stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
-
-    case get_header_value("expect") of
+    Expect = case get_header_value("expect") of
+                 undefined ->
+                     undefined;
+                 Value when is_list(Value) ->
+                     string:to_lower(Value)
+             end,
+    case Expect of
         "100-continue" ->
             start_raw_response({100, gb_trees:empty()});
         _Else ->
                 exit({body_too_large, content_length});
             _ ->
                 stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState)
-            end;     
+            end;
         Length ->
             exit({length_not_integer, Length})
     end.
 
 read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) ->
     Fun({Length, read_chunk(Length)}, FunState).
-    
+
 %% @spec serve_file(Path, DocRoot) -> Response
 %% @doc Serve a file relative to DocRoot.
 serve_file(Path, DocRoot) ->
     %% valid, multiple range
     io:format("Testing parse_range_request with valid multiple ranges~n"),
     io:format("1"),
-    [{20, 30}, {50, 100}, {110, 200}] = 
+    [{20, 30}, {50, 100}, {110, 200}] =
         parse_range_request("bytes=20-30,50-100,110-200"),
     io:format("2"),
-    [{20, none}, {50, 100}, {none, 200}] = 
+    [{20, none}, {50, 100}, {none, 200}] =
         parse_range_request("bytes=20-,50-100,-200"),
     io:format(".. ok~n"),
-    
+
     %% no ranges
     io:format("Testing out parse_range_request with no ranges~n"),
     io:format("1"),
     [] = parse_range_request("bytes="),
     io:format(".. ok~n"),
-    
+
     Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
     BodySize = size(Body), %% 60
     BodySize = 60,
     io:format("4"),
     {30, 30} = range_skip_length({30, none}, BodySize), %% 30-
     io:format(".. ok ~n"),
-    
+
     %% valid edge cases for range_skip_length
     io:format("Testing out range_skip_length on valid edge case ranges~n"),
     io:format("1"),
     invalid_range = range_skip_length({BodySize, none}, BodySize),
     io:format(".. ok ~n"),
     ok.
-    
+

deps/webmachine/deps/mochiweb/src/mochiweb_skel.erl

     "skel".
 
 skelcopy(Src, DestDir, Name, LDst) ->
-    {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name),
+    Dest = re:replace(filename:basename(Src), skel(), Name,
+                      [global, {return, list}]),
     case file:read_file_info(Src) of
         {ok, #file_info{type=directory, mode=Mode}} ->
             Dir = DestDir ++ "/" ++ Dest,
         {ok, #file_info{type=regular, mode=Mode}} ->
             OutFile = filename:join(DestDir, Dest),
             {ok, B} = file:read_file(Src),
-            {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name),
+            S = re:replace(binary_to_list(B), skel(), Name,
+                           [{return, list}, global]),
             ok = file:write_file(OutFile, list_to_binary(S)),
             ok = file:write_file_info(OutFile, #file_info{mode=Mode}),
             io:format("    ~s~n", [filename:basename(Src)]),

deps/webmachine/src/webmachine.app

 {application, webmachine,
  [{description, "webmachine"},
-  {vsn, "1.3"},
+  {vsn, "1.4"},
   {modules, [
     webmachine,
     webmachine_app,

deps/webmachine/src/webmachine_decision_core.erl

     ReqDate = get_header_val("if-modified-since"),    
     ReqErlDate = webmachine_util:convert_request_date(ReqDate),
     ResErlDate = resource_call(last_modified),
-    decision_test(ResErlDate > ReqErlDate,
+    decision_test(ResErlDate =:= undefined orelse ResErlDate > ReqErlDate,
                   true, v3m16, 304);
 %% "POST?"
 decision(v3m5) ->

deps/webmachine/src/webmachine_request_srv.erl

 -include("webmachine_logger.hrl").
 -include_lib("include/wm_reqdata.hrl").
 
--define(WMVSN, "1.3").
--define(QUIP, "scale automagically into the cloud").
+-define(WMVSN, "1.4").
+-define(QUIP, "our dis is finally out").
 
 % 120 second default idle timeout
 -define(IDLE_TIMEOUT, infinity).

releasenotes/riak-0.3.txt

+riak-0.3
+
+This release includes a backwards-incompatible change to riak_object,
+enforcing all object keys to be binaries.  This only effects users of
+the Erlang native interface.  Users of the HTTP/Jiak interface should
+not be affected.
+
+riak_object:
+  - Enforcement of binary-only object keys.       
+
+HTTP Interface:
+  - new Java client.
+  - HTTP results now include Link: headers (http://tools.ietf.org/html/draft-nottingham-http-link-header-06).
+  - Upgraded to Webmachine-1.4 and mochiweb-trunk.
+  - Fixed default web access log directory.
+
+Eventer:
+  - Performance improvements (only calculate eventers on ring-state changes).
+  - Add a local logfile facility via the riak_local_logfile configuration 
+  parameter.  This should only be used for debugging as the profuse logging
+  can compete with the I/O demands of Riak itself.
+
+Internals:
+  - Fix for a ring-state gossip bug encountered when running large clusters.
+  - Increased value of ERL_MAX_PORTS to 4096 to support larger ring sizes.
+
+Performance:
+  - Turn riak_bucketkeys into a gen_server2 process with tuned GC parameters.
+  - Enable kernel-poll in Erlang VM by default.
+  
+Documentation:
+  - Documented all configuration parameters in config/riak.erlenv
+
-export ERL_MAX_ETS_TABLES=100000
+export ERL_FLAGS="+K true"
+export ERL_MAX_PORTS=4096
+
 
 %% @doc Jiak: Riak utilities, focused on JSON-encoded data.
 %%
-%%      Riak is meant is web-shaped, and one of the most useful
+%%      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.

src/riak_ring.erl

 % @spec reconcile(ExternState :: hstate(), MyState :: hstate()) ->
 %       {no_change, hstate()} | {new_ring, hstate()}
 reconcile(ExternState, MyState) ->
-    case ancestors([ExternState, MyState]) of
-        [OlderState] ->
-          case vclock:equal(OlderState#hstate.vclock,MyState#hstate.vclock) of
-              true -> {new_ring, #hstate{nodename=MyState#hstate.nodename,
-                                         vclock=ExternState#hstate.vclock,
-                                         ring=ExternState#hstate.ring,
-                                         meta=ExternState#hstate.meta}};
-              false -> {no_change, MyState}
-          end;
-	[] -> 
-            case equal_rings(ExternState,MyState) of
-                true -> {no_change, MyState};
-                false -> {new_ring, reconcile(MyState#hstate.nodename,
-                                              ExternState, MyState)}
+    case vclock:equal(MyState#hstate.vclock, vclock:fresh()) of
+        true -> 
+            {new_ring, #hstate{nodename=MyState#hstate.nodename,
+                               vclock=ExternState#hstate.vclock,
+                               ring=ExternState#hstate.ring,
+                               meta=ExternState#hstate.meta}};
+        false ->
+            case ancestors([ExternState, MyState]) of
+                [OlderState] ->
+                    case vclock:equal(OlderState#hstate.vclock,
+                                      MyState#hstate.vclock) of
+                        true ->
+                            {new_ring, #hstate{nodename=MyState#hstate.nodename,
+                                               vclock=ExternState#hstate.vclock,
+                                               ring=ExternState#hstate.ring,
+                                               meta=ExternState#hstate.meta}};
+                        false -> {no_change, MyState}
+                    end;
+                [] -> 
+                    case equal_rings(ExternState,MyState) of
+                        true -> {no_change, MyState};
+                        false -> {new_ring, reconcile(MyState#hstate.nodename,
+                                                      ExternState, MyState)}
+                    end
             end
     end.
 

src/riak_ring_gossiper.erl

     after Interval ->
             riak_eventer:notify(riak_ring_gossiper, interval, interval),
             {ok, MyRing} = riak_ring_manager:get_my_ring(),
-            Indices = [I || {I,_} <- riak_ring:all_owners(MyRing)],
-            gen_server:cast({riak_vnode_master, node()}, {start_vnode, 
-               lists:nth(crypto:rand_uniform(1, length(Indices)+1), Indices)}),
+            VNodes2Start = case length(riak_ring:all_members(MyRing)) of
+               1 -> riak_ring:my_indices(MyRing);
+               _ -> [riak_ring:random_other_index(MyRing)|
+                     riak_ring:my_indices(MyRing)]
+            end,
+            [gen_server:cast({riak_vnode_master, node()},
+                   {start_vnode, I}) || I <- VNodes2Start],                             
             case Write of
                 no_write -> nop;
                 write -> riak_ring_manager:write_ringfile()
 
 			<p>One of the effects of exposing the consistency, availability, and partition-tolerance (<a href="cap.html">CAP</a>) controls to the application developer is that the developer can work with operations like never before in making decisions about tuning the relative availability of the whole system under different circumstances -- including circumstances under which any traditional system would provide no choice but to become unavailable.</p>
 
-			<p>Riak is also easy to administer.  If you wish to add nodes to <a href="scale.html">add capacity</a> or remove nodes to either reclaim resources or repair broken systems, there is very little operational impact.  You can leave the whole cluster running continuously, and the content will rebalance itself to best suit the new size of the cluster.</p>
+			<p>Riak is also easy to administer.  If you wish to add nodes to <a href="scale.html">add capacity</a> or remove nodes to either reclaim resources or repair broken systems, there is very little operational impact.  You can leave the whole cluster running continuously, and the content will rebalance itself to best suit the new size of the cluster.  Restarting individual nodes is rarely necessary, but can be done whenever you wish without any fear of adversely affecting the other nodes in the system.</p>
 
 			<br />