Skip to content

Commit f25ad52

Browse files
committed
Start of the full sentinel structure
1 parent 77e03f1 commit f25ad52

File tree

6 files changed

+176
-28
lines changed

6 files changed

+176
-28
lines changed

src/CloudGraphsDFG/CloudGraphsDFG.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using Neo4j
22

33
# Entities
44
include("entities/CloudGraphsDFG.jl")
5+
include("entities/CGStructure.jl")
56

67
# Services
78
include("services/CommonFunctions.jl")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Very simple initial sentinel structure for Graff elements in DFG.
2+
# TODO: Need to flesh out further in next release.
3+
4+
export User, Robot, Session
5+
6+
abstract type AbstractCGNode
7+
end
8+
9+
mutable struct User <: AbstractCGNode
10+
id::Symbol
11+
name::String
12+
description::String
13+
# labels::Vector{Symbol}
14+
data::Dict{Symbol, String}
15+
end
16+
17+
mutable struct Robot <: AbstractCGNode
18+
id::Symbol
19+
userId::Symbol
20+
name::String
21+
description::String
22+
# labels::Vector{Symbol}
23+
data::Dict{Symbol, String}
24+
end
25+
26+
mutable struct Session <: AbstractCGNode
27+
id::Symbol
28+
robotId::Symbol
29+
userId::Symbol
30+
name::String
31+
description::String
32+
# labels::Vector{Symbol}
33+
data::Dict{Symbol, String}
34+
end

src/CloudGraphsDFG/services/CGStructure.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,91 @@ export copySession!
33
# Please be careful with these
44
# With great power comes great "Oh crap, I deleted everything..."
55
export clearSession!!, clearRobot!!, clearUser!!
6+
export createSession, createRobot, createUser
7+
export existsSession, existsRobot, existsUser
8+
export getSession, getRobot, getUser
9+
export updateSession, updateRobot, updateUser
10+
export listSessions, listRobots, listUsers
611

12+
function _isValid(abstractNode::N)::Bool where N <: AbstractCGNode
13+
invalidIds = ["USER", "ROBOT", "SESSION", "VARIABLE", "FACTOR", "ENVIRONMENT"]
14+
return all(t -> t != uppercase(String(abstractNode.id)), invalidIds)
15+
end
16+
17+
# Fastest way I can think to convert the data into a dict
18+
#TODO: Probably should be made more efficient...definitely should be made more efficient
19+
function _convertNodeToDict(abstractNode::N)::Dict{String, Any} where N <: AbstractCGNode
20+
cp = deepcopy(abstractNode)
21+
data = length(cp.data) != 0 ? JSON2.write(cp.data) : "{}"
22+
ser = JSON2.read(JSON2.write(abstractNode), Dict{String, Any})
23+
ser["data"] = data
24+
return ser
25+
end
26+
27+
#TODO: Refactor, #HACK :D (but it works!)
28+
function _convertDictToSession(dict::Dict{String, Any})::Session
29+
sessionData = JSON2.read(dict["data"], Dict{Symbol, String})
30+
session = Session(
31+
Symbol(dict["id"]),
32+
Symbol(dict["robotId"]),
33+
Symbol(dict["userId"]),
34+
dict["name"],
35+
dict["description"],
36+
sessionData)
37+
return session
38+
end
39+
40+
function createUser(dfg::CloudGraphsDFG, user::User)::User
41+
Symbol(dfg.userId) != user.id && error("DFG user ID must match user's ID")
42+
!_isValid(user) && error("Node cannot have an ID '$(user.id)'.")
43+
44+
props = _convertNodeToDict(user)
45+
retNode = _createNode(dfg.neo4jInstance, ["USER", String(user.id)], props, nothing)
46+
return user
47+
end
48+
49+
function createRobot(dfg::CloudGraphsDFG, robot::Robot)::Robot
50+
Symbol(dfg.robotId) != robot.id && error("DFG robot ID must match robot's ID")
51+
Symbol(dfg.userId) != robot.userId && error("DFG user ID must match robot's user ID")
52+
!_isValid(robot) && error("Node cannot have an ID '$(robot.id)'.")
53+
54+
# Find the parent
55+
parents = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER:$(dfg.userId))")
56+
length(parents) == 0 && error("Cannot find user '$(dfg.userId)'")
57+
length(parents) > 1 && error("Found multiple users '$(dfg.userId)'")
58+
59+
# Already exists?
60+
length(_getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.userId):$(robot.id))")) != 0 &&
61+
error("Robot '$(robot.id)' already exists for user '$(robot.userId)'")
62+
63+
props = _convertNodeToDict(robot)
64+
retNode = _createNode(dfg.neo4jInstance, ["ROBOT", String(robot.userId), String(robot.id)], props, parents[1], :ROBOT)
65+
return robot
66+
end
67+
68+
function createSession(dfg::CloudGraphsDFG, session::Session)::Session
69+
Symbol(dfg.robotId) != session.robotId && error("DFG robot ID must match session's robot ID")
70+
Symbol(dfg.userId) != session.userId && error("DFG user ID must match session's->robot's->user ID")
71+
!_isValid(session) && error("Node cannot have an ID '$(session.id)'.")
72+
73+
# Find the parent
74+
parents = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.robotId):$(dfg.userId))")
75+
length(parents) == 0 && error("Cannot find robot '$(dfg.robotId)' for user '$(dfg.userId)'")
76+
length(parents) > 1 && error("Found multiple robots '$(dfg.robotId)' for user '$(dfg.userId)'")
77+
78+
# Already exists?
79+
length(_getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(session.userId):$(session.robotId):$(session.id))")) != 0 &&
80+
error("Session '$(session.id)' already exists for robot '$(session.robotId)' and user '$(session.userId)'")
81+
82+
props = _convertNodeToDict(session)
83+
retNode = _createNode(dfg.neo4jInstance, ["SESSION", String(session.userId), String(session.robotId), String(session.id)], props, parents[1], :SESSION)
84+
return session
85+
end
86+
87+
function listSessions(dfg::CloudGraphsDFG)::Vector{Session}
88+
sessionNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(dfg.robotId):$(dfg.userId))")
89+
return map(s -> _convertDictToSession(Neo4j.getnodeproperties(s)), sessionNodes)
90+
end
791

892
"""
993
$(SIGNATURES)

src/CloudGraphsDFG/services/CloudGraphsDFG.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ function addVariable!(dfg::CloudGraphsDFG, variable::DFGVariable)::Bool
106106
variable._internalId = neo4jNode.id
107107
Neo4j.updatenodelabels(neo4jNode, union([string(variable.label), "VARIABLE", dfg.userId, dfg.robotId, dfg.sessionId], variable.tags))
108108

109-
# Graphs.add_vertex!(dfg.g, v)
109+
# Make sure that if there exists a SESSION sentinel that it is attached.
110+
# TODO: Optimize this.
111+
_bindSessionNodeToInitialVariable(dfg.neo4jInstance, dfg.userId, dfg.robotId, dfg.sessionId, string(variable.label))
112+
113+
# Update our internal dictionaries.
110114
push!(dfg.labelDict, variable.label=>variable._internalId)
111115
push!(dfg.variableCache, variable.label=>variable)
112116
# Track insertion

src/CloudGraphsDFG/services/CommonFunctions.jl

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ function _queryNeo4j(neo4jInstance::Neo4jInstance, query::String)
1616
return result
1717
end
1818

19+
"""
20+
$(SIGNATURES)
21+
Create a node and optionally specify a parent.
22+
Note: Using symbols so that the labels obey Neo4j requirements
23+
"""
24+
function _createNode(neo4jInstance::Neo4jInstance, labels::Vector{String}, properties::Dict{String, Any}, parentNode::Union{Nothing, Neo4j.Node}, relationshipLabel::Symbol=:NOTHING)::Neo4j.Node
25+
createdNode = Neo4j.createnode(neo4jInstance.graph, properties)
26+
addnodelabels(createdNode, labels)
27+
parentNode == nothing && return createdNode
28+
# Otherwise create the relationship
29+
createrel(parentNode, createdNode, String(relationshipLabel))
30+
return createdNode
31+
end
32+
1933
"""
2034
$(SIGNATURES)
2135
Returns the list of CloudGraph nodes that matches the Cyphon query.
@@ -61,6 +75,7 @@ function _getNodeCount(neo4jInstance::Neo4jInstance, nodeLabels::Vector{String})
6175
length(result.results[1]["data"]) != 1 && return 0
6276
return parse(Int, result.results[1]["data"][1]["row"][1])
6377
end
78+
6479
"""
6580
$(SIGNATURES)
6681
Returns the list of CloudGraph nodes that matches the Cyphon query.
@@ -126,7 +141,7 @@ end
126141
"""
127142
$(SIGNATURES)
128143
Bind the SESSION node to the inital variable.
129-
Doesn't check existence so please don't call twice.
144+
Checks for existence.
130145
"""
131146
function _bindSessionNodeToInitialVariable(neo4jInstance::Neo4jInstance, userId::String, robotId::String, sessionId::String, initialVariableLabel::String)::Nothing
132147
# 2. Perform the transaction
@@ -135,6 +150,7 @@ function _bindSessionNodeToInitialVariable(neo4jInstance::Neo4jInstance, userId:
135150
match (session:SESSION:$userId:$robotId:$sessionId),
136151
(var:VARIABLE:$userId:$robotId:$sessionId
137152
{label: '$initialVariableLabel'})
153+
WHERE NOT (session)-[:VARIABLE]->()
138154
CREATE (session)-[:VARIABLE]->(var) return id(var)
139155
""";
140156
loadtx(query; submit=true)
@@ -393,32 +409,6 @@ function _validateHttpInputs(requiredInputs::Vector{Symbol}, params::Dict{Symbol
393409
return rets
394410
end
395411

396-
function _isCallIsFromApiGateway(params::Dict{Symbol, Any})::Bool
397-
# Simple security for making sure only API gateway is used because it populates this header.
398-
testEndpoint = true
399-
if haskey(ENV, "securityDisabled")
400-
if ENV["securityDisabled"] == "true"
401-
testEndpoint = false
402-
end
403-
end
404-
if testEndpoint == true
405-
requestKey = :REQUEST
406-
if !haskey(params, requestKey)
407-
return false
408-
end
409-
request = params[requestKey]
410-
request.headers
411-
if !haskey(request.headers, "secapi")
412-
return false #Response(401)
413-
end
414-
if request.headers["secapi"] != "001ac"
415-
return false
416-
end
417-
end
418-
# Otherwise ok.
419-
return true
420-
end
421-
422412
function readAndReturnFile(filename::String, mimeType::String="application/octet-stream")::HTTP.Response
423413
if !isfile(filename)
424414
error("The file to be returned does not exist - $filename")

test/CGStructureTests.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using DistributedFactorGraphs
2+
using IncrementalInference
3+
using Test
4+
5+
blank() = return nothing
6+
dfg = CloudGraphsDFG{NoSolverParams}("localhost", 7474, "neo4j", "test",
7+
"Bob", "testRobot", "testSession",
8+
nothing,
9+
nothing,
10+
blank,
11+
blank,
12+
solverParams=NoSolverParams())
13+
# Nuke the user
14+
clearUser!!(dfg)
15+
16+
# User, robot, and session
17+
user = User(:Bob, "Bob Zack", "Description", Dict{String, String}())
18+
robot = Robot(:testRobot, user.id, "Test robot", "Description", Dict{String, String}())
19+
session = Session(:testSession, robot.id, user.id, "Test Session", "Description", Dict{String, String}())
20+
21+
# Test of the 'serializer' and 'deserializer'
22+
dictUser = DistributedFactorGraphs._convertNodeToDict(user)
23+
24+
createUser(dfg, user)
25+
createRobot(dfg, robot)
26+
createSession(dfg, session)
27+
28+
# Add some nodes.
29+
v1 = addVariable!(dfg, :a, ContinuousScalar, labels = [:POSE])
30+
v2 = addVariable!(dfg, :b, ContinuousScalar, labels = [:POSE])
31+
v3 = addVariable!(dfg, :c, ContinuousScalar, labels = [:LANDMARK])
32+
f1 = addFactor!(dfg, [:a; :b], LinearConditional(Normal(50.0,2.0)) )
33+
f1 = addFactor!(dfg, [:a; :b; :c], LinearConditional(Normal(50.0,2.0)) )
34+
35+
listSessions(dfg)

0 commit comments

Comments
 (0)