Permalink
Browse files

Extract the rest client and neo4j rest api

  • Loading branch information...
1 parent fc0eef2 commit 2c798915aaa9e92bac76cbabcab39b61bfba2e90 Uwe Dauernheim committed Jan 4, 2012
Showing with 340 additions and 11 deletions.
  1. BIN bb_rest_api.beam
  2. +0 −1 src/bb.app.src
  3. +9 −3 src/bb.erl
  4. +1 −7 src/bb_util.erl
  5. +136 −0 src/neo4j_api.erl
  6. +63 −0 src/rest_client.erl
  7. +131 −0 src/wadl.erl
View
Binary file not shown.
View
@@ -6,7 +6,6 @@
, {applications, [kernel, stdlib, inets]}
, {env, [ {server_uri, "http://localhost:7474/"}
, {server_data_path, "db/data/"}
- , {server_timeout, 3000}
, {user_me, 0}
]}
]}.
View
@@ -6,17 +6,20 @@
]).
%%%_* Defines ==========================================================
--define(USER_ME, bb_util:env(user_me)).
+-define(SERVER_URI, bb_util:env(server_uri)).
+-define(SERVER_DATA_PATH, bb_util:env(server_data_path)).
+-define(USER_ME, bb_util:env(user_me)).
%%%_* Code =============================================================
%%%_* API --------------------------------------------------------------
start() ->
bb_util:ensure_started(bb),
- bb_rest:init().
+ ensure_server_running().
get_node(me) -> get_node(?USER_ME);
get_node(Id) when is_integer(Id) ->
- {ok, Res} = bb_rest:request(get, ["node", Id]),
+ API = neo4j_api:new(?SERVER_URI++?SERVER_DATA_PATH),
+ {ok, Res} = API:getNode(bb_util:s(Id)),
bb_util:kf(bb_util:b("data"), Res).
add_node(FromId, RelProps, NodeProps) when is_integer(FromId) ->
@@ -36,6 +39,9 @@ create_rel(RelProps) -> nyi.
connect_nodes(FromId, ToId) -> nyi.
%%%_* Internals --------------------------------------------------------
+ensure_server_running() ->
+ {ok, Config} = rest_client:request(get, ?SERVER_URI),
+ _DataPath = bb_util:kf(bb_util:b("data"), Config).
%%% Mode: Erlang
%%% End.
View
@@ -58,13 +58,7 @@ env(Key) ->
Value.
priv_file(Filename) ->
- %% PrivDir = code:priv_dir(bb),
- %% filename:join(PrivDir, Filename).
- filename:join([ filename:dirname(code:which(?MODULE))
- , ".."
- , "priv"
- , Filename
- ]).
+ filename:join([ filename:dirname(code:which(?MODULE)), "../priv", Filename]).
%%% Mode: Erlang
%%% End.
View
@@ -0,0 +1,136 @@
+%%% @doc Provides client for Neo4j's REST API
+-module(neo4j_api, [URI]).
+
+%%%_* Exports ==========================================================
+-export([ addToNodeIndex/1
+ , addToRelationshipIndex/1
+ , allPaths/1
+ , createNode/0
+ , createPagedTraverser/3
+ , createRelationship/1
+ , deleteAllNodeProperties/1
+ , deleteAllRelationshipProperties/1
+ , deleteFromNodeIndex/4
+ , deleteFromNodeIndexNoKeyValue/2
+ , deleteFromNodeIndexNoValue/3
+ , deleteFromRelationshipIndex/2
+ , deleteFromRelationshipIndex/4
+ , deleteFromRelationshipIndexNoValue/3
+ , deleteNode/1
+ , deleteNodeIndex/1
+ , deleteNodeProperty/2
+ , deleteRelationship/1
+ , deleteRelationshipIndex/1
+ , deleteRelationshipProperty/2
+ , getAllNodeProperties/1
+ , getAllRelationshipProperties/1
+ , getAutoIndexedNodesByQuery/1
+ , getAutoIndexedRelationshipsByQuery/1
+ , getExtensionList/1
+ , getExtensionsList/0
+ , getGraphDatabaseExtensionDescription/2
+ , getHtmlBrowseJavascript/0
+ , getIndexedNodes/2
+ , getIndexedNodes/3
+ , getIndexedNodesByQuery/2
+ , getIndexedNodesByQuery/3
+ , getIndexedRelationships/2
+ , getIndexedRelationships/3
+ , getIndexedRelationshipsByQuery/2
+ , getIndexedRelationshipsByQuery/3
+ , getNode/1
+ , getNodeExtensionDescription/3
+ , getNodeFromIndexUri/4
+ , getNodeIndexRoot/0
+ , getNodeProperty/2
+ , getNodeRelationships/2
+ , getNodeRelationships/3
+ , getRelationship/1
+ , getRelationshipExtensionDescription/3
+ , getRelationshipFromIndexUri/4
+ , getRelationshipIndexRoot/0
+ , getRelationshipProperty/2
+ , getRelationshipTypes/0
+ , getRoot/0
+ , invokeGraphDatabaseExtension/2
+ , invokeNodeExtension/3
+ , invokeRelationshipExtension/3
+ , jsonCreateNodeIndex/0
+ , jsonCreateRelationshipIndex/0
+ , pagedTraverse/2
+ , performBatchOperations/0
+ , removePagedTraverser/2
+ , setAllNodeProperties/1
+ , setAllRelationshipProperties/1
+ , setNodeProperty/2
+ , setRelationshipProperty/2
+ , singlePath/1
+ , traverse/2
+ ]).
+
+%%%_* Code =============================================================
+%%%_* API --------------------------------------------------------------
+addToNodeIndex(PindexName) -> rest_client:request(post, URI++"index/node/"++PindexName++"").
+addToRelationshipIndex(PindexName) -> rest_client:request(post, URI++"index/relationship/"++PindexName++"").
+allPaths(PnodeId) -> rest_client:request(post, URI++"node/"++PnodeId++"/paths").
+createNode() -> rest_client:request(post, URI++"node").
+createPagedTraverser(PnodeId, PreturnType, Data) -> rest_client:request(post, URI++"node/"++PnodeId++"/paged/traverse/"++PreturnType++"", Data).
+createRelationship(PnodeId) -> rest_client:request(post, URI++"node/"++PnodeId++"/relationships").
+deleteAllNodeProperties(PnodeId) -> rest_client:request(delete, URI++"node/"++PnodeId++"/properties").
+deleteAllRelationshipProperties(PrelationshipId) -> rest_client:request(delete, URI++"relationship/"++PrelationshipId++"/properties").
+deleteFromNodeIndex(Pid, Pvalue, PindexName, Pkey) -> rest_client:request(delete, URI++"index/node/"++PindexName++"/"++Pkey++"/"++Pvalue++"/"++Pid++"").
+deleteFromNodeIndexNoKeyValue(Pid, PindexName) -> rest_client:request(delete, URI++"index/node/"++PindexName++"/"++Pid++"").
+deleteFromNodeIndexNoValue(Pid, PindexName, Pkey) -> rest_client:request(delete, URI++"index/node/"++PindexName++"/"++Pkey++"/"++Pid++"").
+deleteFromRelationshipIndex(Pid, PindexName) -> rest_client:request(delete, URI++"index/relationship/"++PindexName++"/"++Pid++"").
+deleteFromRelationshipIndex(Pid, Pvalue, PindexName, Pkey) -> rest_client:request(delete, URI++"index/relationship/"++PindexName++"/"++Pkey++"/"++Pvalue++"/"++Pid++"").
+deleteFromRelationshipIndexNoValue(Pid, PindexName, Pkey) -> rest_client:request(delete, URI++"index/relationship/"++PindexName++"/"++Pkey++"/"++Pid++"").
+deleteNode(PnodeId) -> rest_client:request(delete, URI++"node/"++PnodeId++"").
+deleteNodeIndex(PindexName) -> rest_client:request(delete, URI++"index/node/"++PindexName++"").
+deleteNodeProperty(PnodeId, Pkey) -> rest_client:request(delete, URI++"node/"++PnodeId++"/properties/"++Pkey++"").
+deleteRelationship(PrelationshipId) -> rest_client:request(delete, URI++"relationship/"++PrelationshipId++"").
+deleteRelationshipIndex(PindexName) -> rest_client:request(delete, URI++"index/relationship/"++PindexName++"").
+deleteRelationshipProperty(PrelationshipId, Pkey) -> rest_client:request(delete, URI++"relationship/"++PrelationshipId++"/properties/"++Pkey++"").
+getAllNodeProperties(PnodeId) -> rest_client:request(get, URI++"node/"++PnodeId++"/properties").
+getAllRelationshipProperties(PrelationshipId) -> rest_client:request(get, URI++"relationship/"++PrelationshipId++"/properties").
+getAutoIndexedNodesByQuery(Data) -> rest_client:request(get, URI++"index/auto/node", Data).
+getAutoIndexedRelationshipsByQuery(Data) -> rest_client:request(get, URI++"index/auto/relationship", Data).
+getExtensionList(Pname) -> rest_client:request(get, URI++"ext/"++Pname++"").
+getExtensionsList() -> rest_client:request(get, URI++"ext").
+getGraphDatabaseExtensionDescription(Pname, Pmethod) -> rest_client:request(get, URI++"ext/"++Pname++"/graphdb/"++Pmethod++"").
+getHtmlBrowseJavascript() -> rest_client:request(get, URI++"resource/htmlbrowse.js").
+getIndexedNodes(Pvalue, PindexName, Pkey) -> rest_client:request(get, URI++"index/node/"++PindexName++"/"++Pkey++"/"++Pvalue++"").
+getIndexedNodes(Pvalue, Pkey) -> rest_client:request(get, URI++"index/auto/node/"++Pkey++"/"++Pvalue++"").
+getIndexedNodesByQuery(PindexName, Data) -> rest_client:request(get, URI++"index/node/"++PindexName++"", Data).
+getIndexedNodesByQuery(PindexName, Pkey, Data) -> rest_client:request(get, URI++"index/node/"++PindexName++"/"++Pkey++"", Data).
+getIndexedRelationships(Pvalue, PindexName, Pkey) -> rest_client:request(get, URI++"index/relationship/"++PindexName++"/"++Pkey++"/"++Pvalue++"").
+getIndexedRelationships(Pvalue, Pkey) -> rest_client:request(get, URI++"index/auto/relationship/"++Pkey++"/"++Pvalue++"").
+getIndexedRelationshipsByQuery(PindexName, Data) -> rest_client:request(get, URI++"index/relationship/"++PindexName++"", Data).
+getIndexedRelationshipsByQuery(PindexName, Pkey, Data) -> rest_client:request(get, URI++"index/relationship/"++PindexName++"/"++Pkey++"", Data).
+getNode(PnodeId) -> rest_client:request(get, URI++"node/"++PnodeId++"").
+getNodeExtensionDescription(PnodeId, Pname, Pmethod) -> rest_client:request(get, URI++"ext/"++Pname++"/node/"++PnodeId++"/"++Pmethod++"").
+getNodeFromIndexUri(Pid, Pvalue, PindexName, Pkey) -> rest_client:request(get, URI++"index/node/"++PindexName++"/"++Pkey++"/"++Pvalue++"/"++Pid++"").
+getNodeIndexRoot() -> rest_client:request(get, URI++"index/node").
+getNodeProperty(PnodeId, Pkey) -> rest_client:request(get, URI++"node/"++PnodeId++"/properties/"++Pkey++"").
+getNodeRelationships(PnodeId, Pdirection) -> rest_client:request(get, URI++"node/"++PnodeId++"/relationships/"++Pdirection++"").
+getNodeRelationships(PnodeId, Pdirection, Ptypes) -> rest_client:request(get, URI++"node/"++PnodeId++"/relationships/"++Pdirection++"/"++Ptypes++"").
+getRelationship(PrelationshipId) -> rest_client:request(get, URI++"relationship/"++PrelationshipId++"").
+getRelationshipExtensionDescription(PrelationshipId, Pname, Pmethod) -> rest_client:request(get, URI++"ext/"++Pname++"/relationship/"++PrelationshipId++"/"++Pmethod++"").
+getRelationshipFromIndexUri(Pid, Pvalue, PindexName, Pkey) -> rest_client:request(get, URI++"index/relationship/"++PindexName++"/"++Pkey++"/"++Pvalue++"/"++Pid++"").
+getRelationshipIndexRoot() -> rest_client:request(get, URI++"index/relationship").
+getRelationshipProperty(PrelationshipId, Pkey) -> rest_client:request(get, URI++"relationship/"++PrelationshipId++"/properties/"++Pkey++"").
+getRelationshipTypes() -> rest_client:request(get, URI++"relationship/types").
+getRoot() -> rest_client:request(get, URI++"").
+invokeGraphDatabaseExtension(Pname, Pmethod) -> rest_client:request(post, URI++"ext/"++Pname++"/graphdb/"++Pmethod++"").
+invokeNodeExtension(PnodeId, Pname, Pmethod) -> rest_client:request(post, URI++"ext/"++Pname++"/node/"++PnodeId++"/"++Pmethod++"").
+invokeRelationshipExtension(PrelationshipId, Pname, Pmethod) -> rest_client:request(post, URI++"ext/"++Pname++"/relationship/"++PrelationshipId++"/"++Pmethod++"").
+jsonCreateNodeIndex() -> rest_client:request(post, URI++"index/node").
+jsonCreateRelationshipIndex() -> rest_client:request(post, URI++"index/relationship").
+pagedTraverse(PtraverserId, PreturnType) -> rest_client:request(get, URI++"node/{nodeId}/paged/traverse/"++PreturnType++"/"++PtraverserId++"").
+performBatchOperations() -> rest_client:request(post, URI++"batch").
+removePagedTraverser(PtraverserId, PreturnType) -> rest_client:request(delete, URI++"node/{nodeId}/paged/traverse/"++PreturnType++"/"++PtraverserId++"").
+setAllNodeProperties(PnodeId) -> rest_client:request(put, URI++"node/"++PnodeId++"/properties").
+setAllRelationshipProperties(PrelationshipId) -> rest_client:request(put, URI++"relationship/"++PrelationshipId++"/properties").
+setNodeProperty(PnodeId, Pkey) -> rest_client:request(put, URI++"node/"++PnodeId++"/properties/"++Pkey++"").
+setRelationshipProperty(PrelationshipId, Pkey) -> rest_client:request(put, URI++"relationship/"++PrelationshipId++"/properties/"++Pkey++"").
+singlePath(PnodeId) -> rest_client:request(post, URI++"node/"++PnodeId++"/path").
+traverse(PnodeId, PreturnType) -> rest_client:request(post, URI++"node/"++PnodeId++"/traverse/"++PreturnType++"").
View
@@ -0,0 +1,63 @@
+-module(rest_client).
+
+%%%_* Exports ==========================================================
+-export([ request/2
+ , request/3
+ ]).
+
+%%%_* Defines ==========================================================
+-define(SERVER_TIMEOUT, 3000).
+
+%%%_* Code =============================================================
+%%%_* API --------------------------------------------------------------
+request(Method, Path) ->
+ Request = {Path, []},
+ Response = send_request(Method, Request),
+ parse_response(Response).
+
+request(Method, Path, Data) ->
+ Payload = percent:url_encode(Data),
+ Request = {Path, [], "application/x-www-form-urlencoded", Payload},
+ Response = send_request(Method, Request),
+ parse_response(Response).
+
+%%%_* Internals --------------------------------------------------------
+send_request(Method, {Url, []}) ->
+ httpc:request(Method, {Url, []}, [{timeout, ?SERVER_TIMEOUT}], []).
+
+parse_response({ok, {{"HTTP/1.1", 200, "OK"}, Headers, Body}}) ->
+ case kf("content-type", Headers) of
+ "application/vnd.sun.wadl+xml" ->
+ Schema = priv_file("wadl.xsd"),
+ {ok, Model} = erlsom:compile_xsd_file(Schema),
+ {ok, _WADL} = erlsom:parse(Body, Model);
+ "application/json" ->
+ Payload = mochijson2:decode(Body, [{format, proplist}]),
+ {ok, Payload};
+ _ ->
+ parse_response({error, invalid_content_type})
+ end;
+parse_response({ok, {{"HTTP/1.1", 404, "Not Found"}, Headers, Body}}) ->
+ case kf("content-type", Headers) of
+ "application/json" ->
+ Payload = mochijson2:decode(Body, [{format, proplist}]),
+ Message = kf(b("message"), Payload),
+ {error, Message};
+ _ ->
+ parse_response({error, invalid_method})
+ end;
+parse_response({error, _Reason}=Error) -> Error.
+
+%%%_* Helpers ----------------------------------------------------------
+kf(Key, List) ->
+ {Key, Value} = lists:keyfind(Key, 1, List),
+ Value.
+
+b(B) when is_binary(B) -> unicode:characters_to_list(B);
+b(S) when is_list(S) -> unicode:characters_to_binary(S).
+
+priv_file(Filename) ->
+ filename:join([ filename:dirname(code:which(?MODULE)), "../priv", Filename]).
+
+%%% Mode: Erlang
+%%% End.
View
@@ -0,0 +1,131 @@
+%%% @doc Generates a REST API module out of a WADL file.
+-module(wadl).
+
+%%%_* Exports ==========================================================
+-export([ generate_module/2
+ , parse_wadl/1
+ ]).
+
+-compile({nowarn_unused_function, [ {create_wadl_hrl, 0}
+ , {priv_file, 1}
+ ]}).
+
+-ignore_xref([ create_wadl_hrl/0 ]).
+
+%%%_* Includes =========================================================
+-include_lib("bb/include/wadl.hrl").
+
+%%%_* Code =============================================================
+%%%_* API --------------------------------------------------------------
+parse_wadl(WADL) ->
+ collect_application(WADL).
+
+generate_module(WADL, Filename) ->
+ Module = filename:basename(Filename, ".erl"),
+ APIModule = "-module("++Module++").\n-compile(export_all).\n\n" ++
+ lists:flatmap(fun(API) -> format_api(API)++"\n" end, WADL),
+ file:write_file(Filename, APIModule).
+
+%%%_* Internals --------------------------------------------------------
+format_api({Name, Method, Path0, [], []}) ->
+ Path = io_lib:format("\"~s\"", [Path0]),
+ io_lib:format("~p() -> ", [Name]) ++
+ io_lib:format("rest_client:request(~p, ~s).", [Method, Path]);
+format_api({Name, Method, Path0, [], _ReqParams}) ->
+ Path = io_lib:format("\"~s\"", [Path0]),
+ io_lib:format("~p(Data) -> ", [Name]) ++
+ io_lib:format("rest_client:request(~p, ~s, Data).", [Method, Path]);
+format_api({Name, Method, Path0, ResParams, []}) ->
+ Params = format_params(ResParams),
+ Path = format_path(Path0, ResParams),
+ io_lib:format("~p(~s) -> ", [Name, string:join(Params, ", ")]) ++
+ io_lib:format("rest_client:request(~p, \"~s\").", [Method, Path]);
+format_api({Name, Method, Path0, ResParams, _ReqParams}) ->
+ Params = format_params(ResParams),
+ Path = format_path(Path0, ResParams),
+ io_lib:format("~p(~s, Data) -> ", [Name, string:join(Params, ", ")]) ++
+ io_lib:format("rest_client:request(~p, \"~s\", Data).", [Method, Path]).
+
+format_params(Params) ->
+ ["P"++Key || {Key, _} <- Params].
+
+format_path(Path, Params) ->
+ Fun = fun({Key, _}, P) ->
+ RegExp = "{"++Key++"}",
+ Replace = "\"++P"++Key++"++\"",
+ re:replace(P, RegExp, Replace, [{return, list}])
+ end,
+ lists:foldl(Fun, Path, Params).
+
+collect_application(#application{resources=Resources}) ->
+ lists:flatten(collect_resources(Resources)).
+
+collect_resources([]) -> [];
+collect_resources([#resources{ base=Base
+ , resource=Resource}|T]) ->
+ Resources = collect_resource(Base, Resource),
+ [Resources|collect_resources(T)].
+
+collect_resource(_, []) -> [];
+collect_resource(Base, [#resource{ path=Path
+ , param=Params
+ , choice=Choices}|T]) ->
+ Resource = collect_resource(join_path(Base, Path), Params, Choices),
+ [Resource|collect_resource(Base, T)].
+
+collect_resource(_, _, []) -> [];
+collect_resource(Path, Params, [#resource{}=H|T]) ->
+ Resource = collect_resource(Path, [H]),
+ [Resource|collect_resource(Path, Params, T)];
+collect_resource(Path, Params, [#method{ id=Id
+ , name=Name
+ , request=Request}|T]) ->
+ ResourceParams = collect_param(Params),
+ RequestParams = collect_request(Request),
+ Resource = { erlang:list_to_atom(Id)
+ , map_method(Name)
+ , Path
+ , ResourceParams
+ , RequestParams
+ },
+ [Resource|collect_resource(Path, Params, T)].
+
+collect_request(undefined) -> [];
+collect_request(#request{param=Param}) ->
+ collect_param(Param).
+
+collect_param([]) -> [];
+collect_param(undefined) -> [];
+collect_param([#param{style="query", name=Name, type=Type}|T]) ->
+ [{Name, map_type(Type)}|collect_param(T)];
+collect_param([#param{style="template", name=Name, type=Type}|T]) ->
+ [{Name, map_type(Type)}|collect_param(T)].
+
+%% FIXME Should be a record, but wasn't generated by erlsom.
+map_type({qname, _href, "int", "xs", "xsd"}) -> integer;
+map_type({qname, _href, "long", "xs", "xsd"}) -> integer;
+map_type({qname, _href, "string", "xs", "xsd"}) -> string.
+
+%% map_method("OPTIONS") -> options;
+map_method("GET") -> get;
+%% map_method("HEAD") -> head;
+map_method("POST") -> post;
+map_method("PUT") -> put;
+map_method("DELETE") -> delete.
+%% map_method("TRACE") -> trace.
+%% map_method("CONNECT") -> connect.
+
+%%%_* Helpers ----------------------------------------------------------
+join_path(Path1, Path2) ->
+ string:strip(Path1, right, $/) ++ "/" ++ string:strip(Path2, left, $/).
+
+create_wadl_hrl() ->
+ SchemaFilename = priv_file("wadl.xsd"),
+ RecDefFilename = "include/wadl.hrl",
+ erlsom:write_xsd_hrl_file(SchemaFilename, RecDefFilename).
+
+priv_file(Filename) ->
+ filename:join([ filename:dirname(code:which(?MODULE)), "../priv", Filename]).
+
+%%% Mode: Erlang
+%%% End.

0 comments on commit 2c79891

Please sign in to comment.