module Neo4j
module Rest #:nodoc: all
# contains a list of rest node class resources
REST_NODE_CLASSES = {}
class RestException < StandardError
def code;
500;
end
end
class NotAllowed < RestException
def code;
403;
end
end
class Conflict < RestException
def code;
409;
end
end
def self.base_uri
host = Sinatra::Application.host
port = Sinatra::Application.port
"http://#{host}:#{port}"
end
def self.load_class(clazz)
clazz.split("::").inject(Kernel) do |container, name|
container.const_get(name.to_s)
end
rescue NameError
raise Sinatra::NotFound
end
# Extracts query parameters from a URL; e.g. if /resource?foo=bar
was requested,
# {'foo' => 'bar'}
is returned.
def self.query_from_params(params)
query = nil
unless (params.nil?)
query = params.clone
query.delete('neo_id')
query.delete('class')
query.delete('prop')
query.delete('rel')
end
query
end
# -------------------------------------------------------------------------
# /neo
# -------------------------------------------------------------------------
Sinatra::Application.post("/neo") do
body = request.body.read
Object.class_eval body
200
end
Sinatra::Application.get("/neo") do
if request.accept.include?("text/html")
html = "
Neo4j.rb v #{Neo4j::VERSION} is alive !
Defined REST classes
"
REST_NODE_CLASSES.keys.each {|clazz| html << "Class '" + clazz + "'
"}
html << ""
html
else
content_type :json
# make it look like it was a node - todo maybe it should be a real Neo4j::Node ...
properties = {:classes => REST_NODE_CLASSES.keys, :ref_node => Neo4j.ref_node._uri}
{:properties => properties}.to_json
end
end
# -------------------------------------------------------------------------
# /rels/
# -------------------------------------------------------------------------
Sinatra::Application.get("/rels/:id") do
content_type :json
begin
Neo4j::Transaction.run do
rel = Neo4j.load_rel(params[:id].to_i)
return 404, "Can't find relationship with id #{params[:id]}" if rel.nil?
# include hyperlink to end_node if that has an _uri method
end_node_hash = {:uri => rel.end_node._uri}
# include hyperlink to start_node if that has an _uri method
start_node_hash = {:uri => rel.start_node._uri}
{:properties => rel.props, :start_node => start_node_hash, :end_node => end_node_hash}.to_json
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
# -------------------------------------------------------------------------
# /nodes/
# -------------------------------------------------------------------------
# Allows searching for nodes (provided that they are indexed). Supports the following:
# /nodes/classname?search=name:hello~
:: Lucene query string
# /nodes/classname?name=hello
:: Exact match on property
# /nodes/classname?sort=name,desc
:: Specify sorting order
# /nodes/classname?limit=100,20
:: Specify offset and number of nodes (for pagination)
Sinatra::Application.get("/nodes/:class") do
content_type :json
clazz = Neo4j::Rest.load_class(params[:class])
return 404, "Can't find class '#{classname}'" if clazz.nil?
begin
Neo4j::Transaction.run do
resources = clazz.find(Neo4j::Rest.query_from_params(params)) # uses overridden find method -- see below
resources.map{|res| res.props}.to_json
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
Sinatra::Application.post("/nodes/:class") do
content_type :json
clazz = Neo4j::Rest.load_class(params[:class])
return 404, "Can't find class '#{classname}'" if clazz.nil?
begin
uri = Neo4j::Transaction.run do
node = clazz.new
data = JSON.parse(request.body.read)
properties = data['properties']
node.update(properties, Neo4j::Rest.query_from_params(params))
node._uri
end
redirect "#{uri}", 201 # created
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
# -------------------------------------------------------------------------
# /nodes//
# -------------------------------------------------------------------------
Sinatra::Application.get("/nodes/:class/:id") do
content_type :json
begin
Neo4j::Transaction.run do
node = Neo4j.load_node(params[:id])
return 404, "Can't find node with id #{params[:id]}" if node.nil?
node.read(Neo4j::Rest.query_from_params(params))
relationships = node.rels.outgoing.inject({}) do |hash, v|
type = v.relationship_type.to_s
hash[type] ||= []
hash[type] << "#{Neo4j::Rest.base_uri}/rels/#{v.neo_id}"
hash
end
{:rels => relationships, :properties => node.props}.to_json
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
Sinatra::Application.put("/nodes/:class/:id") do
content_type :json
begin
Neo4j::Transaction.run do
body = request.body.read
data = JSON.parse(body)
properties = data['properties'] || {}
node = Neo4j.load_node(params[:id])
node.update(properties, Neo4j::Rest.query_from_params(params).merge({:strict => true}))
node.props.to_json
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
Sinatra::Application.delete("/nodes/:class/:id") do
content_type :json
begin
Neo4j::Transaction.run do
node = Neo4j.load_node(params[:id])
return 404, "Can't find node with id #{params[:id]}" if node.nil?
node.del(Neo4j::Rest.query_from_params(params))
""
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
# -------------------------------------------------------------------------
# /nodes///
# -------------------------------------------------------------------------
Sinatra::Application.get("/nodes/:class/:id/traverse") do
content_type :json
begin
Neo4j::Transaction.run do
node = Neo4j.load_node(params[:id])
return 404, {'error' => "Can't find node with id #{params[:id]}"}.to_json if node.nil?
relationship = params['relationship']
depth = case params['depth']
when nil then
1
when 'all' then
:all
else
params['depth'].to_i
end
return 400, {'error' => "invalid depth parameter - must be an integer"}.to_json if depth == 0
uris = node.traverse.outgoing(relationship.to_sym).depth(depth).collect{|node| node._uri}
{'uri_list' => uris}.to_json
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
Sinatra::Application.get("/nodes/:class/:id/:prop") do
content_type :json
begin
Neo4j::Transaction.run do
node = Neo4j.load_node(params[:id])
return 404, "Can't find node with id #{params[:id]}" if node.nil?
prop = params[:prop].to_sym
# check if prop is a relationship defined by has_n or has_one
if node.class.decl_relationships.keys.include?(prop)
# instead of returning properties return the relationship properties
rels = node.send(prop) || []
# is it a has_n or has_one relationship ?
# if it is a has_n then return an array of properties of that relationship (can be a large json struct)
(rels.respond_to?(:props) ? rels.props : rels.map{|rel| rel.props}).to_json
else
{prop => node[prop]}.to_json
end
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
Sinatra::Application.put("/nodes/:class/:id/:prop") do
content_type :json
begin
Neo4j::Transaction.run do
node = Neo4j.load_node(params[:id])
property = params[:prop]
body = request.body.read
data = JSON.parse(body)
value = data[property]
return 409, "Can't set property #{property} with JSON data '#{body}'" if value.nil?
node[property] = value
200
end
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
URL_REGEXP = Regexp.new '((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+\/[\w\-\.]+)$' #:nodoc:
Sinatra::Application.post("/nodes/:class/:id/:rel") do
content_type :json
begin
new_id = Neo4j::Transaction.run do
node = Neo4j.load_node(params[:id])
return 404, "Can't find node with id #{params[:id]}" if node.nil?
rel = params[:rel]
body = request.body.read
data = JSON.parse(body)
uri = data['uri']
match = URL_REGEXP.match(uri)
return 400, "Bad node uri '#{uri}'" if match.nil?
to_clazz, to_node_id = match[6].split('/')
other_node = Neo4j.load_node(to_node_id.to_i)
return 400, "Unknown other node with id '#{to_node_id}'" if other_node.nil?
if to_clazz != other_node.class.to_s
return 400, "Wrong type id '#{to_node_id}' expected '#{to_clazz}' got '#{other_node.class.to_s}'"
end
rel_obj = node.add_rel(rel, other_node)
return 400, "Can't create relationship to #{to_clazz}" if rel_obj.nil?
rel_obj.neo_id
end
redirect "/rels/#{new_id}", 201 # created
rescue RestException => exception
return exception.code, {'error' => $!}.to_json
rescue Exception => e
return 500, {'error' => $!, 'backtrace' => e.backtrace}.to_json
end
end
end
end