Skip to content

Commit 8bf38a9

Browse files
authored
Merge pull request #181 from JuliaRobotics/feature/177_sentinels
CGDFG Sentinel Structure
2 parents a824889 + 50bad37 commit 8bf38a9

File tree

11 files changed

+441
-83
lines changed

11 files changed

+441
-83
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ uuid = "b5cc3c7e-6572-11e9-2517-99fb8daf2f04"
33
version = "0.5.0"
44

55
[deps]
6+
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
67
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
78
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
89
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
@@ -29,9 +30,9 @@ Requires = "0.5, 0.6, 0.7, 0.8, 0.9, 0.10, 1"
2930
julia = "0.7, 1"
3031

3132
[extras]
32-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3333
GraphPlot = "a2cc645c-3eea-5389-862e-a155d0052231"
3434
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
35+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3536

3637
[targets]
3738
test = ["Test", "GraphPlot", "Pkg"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ docker pull neo4j
6969
To run the image with user `neo4j` and password `test`:
7070

7171
```bash
72-
docker run --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/test neo4j
72+
docker run -d --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/test neo4j
7373
```
7474

7575
> **Note** If you just installed docker and having permission issues, please see [this ask Ubuntu forum](https://askubuntu.com/questions/941816/permission-denied-when-running-docker-after-installing-it-as-a-snap).

src/CloudGraphsDFG/CloudGraphsDFG.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Neo4j
2+
using Base64
23

34
# Entities
45
include("entities/CloudGraphsDFG.jl")
6+
include("entities/CGStructure.jl")
57

68
# Services
79
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: 249 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,237 @@ 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, createDfgSessionIfNotExist
7+
export existsSession, existsRobot, existsUser
8+
export getSession, getRobot, getUser
9+
export updateSession, updateRobot, updateUser
10+
export lsSessions, lsRobots, lsUsers
11+
12+
global _invalidIds = ["USER", "ROBOT", "SESSION", "VARIABLE", "FACTOR", "ENVIRONMENT", "PPE", "BIGDATA"]
13+
global _validLabelRegex = r"^[a-zA-Z]\w*$"
14+
15+
function _isValid(id::Union{Symbol, String})::Bool
16+
if typeof(id) == Symbol
17+
id = String(id)
18+
end
19+
return all(t -> t != uppercase(id), _invalidIds) && match(_validLabelRegex, id) != nothing
20+
end
21+
22+
function _isValid(abstractNode::N)::Bool where N <: AbstractCGNode
23+
id = String(abstractNode.id)
24+
return all(t -> t != uppercase(id), _invalidIds) && match(_validLabelRegex, id) != nothing
25+
end
26+
27+
# Fastest way I can think to convert the data into a dict
28+
#TODO: Probably should be made more efficient...definitely should be made more efficient
29+
function _convertNodeToDict(abstractNode::N)::Dict{String, Any} where N <: AbstractCGNode
30+
cp = deepcopy(abstractNode)
31+
data = length(cp.data) != 0 ? JSON2.write(cp.data) : "{}"
32+
ser = JSON2.read(JSON2.write(abstractNode), Dict{String, Any})
33+
ser["data"] = base64encode(data)
34+
return ser
35+
end
36+
37+
#TODO: Refactor, #HACK :D (but it works!)
38+
function _convertDictToSession(dict::Dict{String, Any})::Session
39+
data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
40+
session = Session(
41+
Symbol(dict["id"]),
42+
Symbol(dict["robotId"]),
43+
Symbol(dict["userId"]),
44+
dict["name"],
45+
dict["description"],
46+
data)
47+
return session
48+
end
49+
#TODO: Refactor, #HACK :D (but it works!)
50+
function _convertDictToRobot(dict::Dict{String, Any})::Robot
51+
data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
52+
robot = Robot(
53+
Symbol(dict["id"]),
54+
Symbol(dict["userId"]),
55+
dict["name"],
56+
dict["description"],
57+
data)
58+
return robot
59+
end
60+
#TODO: Refactor, #HACK :D (but it works!)
61+
function _convertDictToUser(dict::Dict{String, Any})::User
62+
data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
63+
user = User(
64+
Symbol(dict["id"]),
65+
dict["name"],
66+
dict["description"],
67+
data)
68+
return user
69+
end
70+
71+
function createUser(dfg::CloudGraphsDFG, user::User)::User
72+
Symbol(dfg.userId) != user.id && error("DFG user ID must match user's ID")
73+
!_isValid(user) && error("Node cannot have an ID '$(user.id)'.")
74+
75+
props = _convertNodeToDict(user)
76+
retNode = _createNode(dfg.neo4jInstance, ["USER", String(user.id)], props, nothing)
77+
return user
78+
end
79+
80+
function createRobot(dfg::CloudGraphsDFG, robot::Robot)::Robot
81+
Symbol(dfg.robotId) != robot.id && error("DFG robot ID must match robot's ID")
82+
Symbol(dfg.userId) != robot.userId && error("DFG user ID must match robot's user ID")
83+
!_isValid(robot) && error("Node cannot have an ID '$(robot.id)'.")
84+
85+
# Find the parent
86+
parents = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER:$(dfg.userId))")
87+
length(parents) == 0 && error("Cannot find user '$(dfg.userId)'")
88+
length(parents) > 1 && error("Found multiple users '$(dfg.userId)'")
89+
90+
# Already exists?
91+
length(_getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.userId):$(robot.id))")) != 0 &&
92+
error("Robot '$(robot.id)' already exists for user '$(robot.userId)'")
93+
94+
props = _convertNodeToDict(robot)
95+
retNode = _createNode(dfg.neo4jInstance, ["ROBOT", String(robot.userId), String(robot.id)], props, parents[1], :ROBOT)
96+
return robot
97+
end
98+
99+
function createSession(dfg::CloudGraphsDFG, session::Session)::Session
100+
Symbol(dfg.robotId) != session.robotId && error("DFG robot ID must match session's robot ID")
101+
Symbol(dfg.userId) != session.userId && error("DFG user ID must match session's->robot's->user ID")
102+
!_isValid(session) && error("Node cannot have an ID '$(session.id)'.")
103+
104+
# Find the parent
105+
parents = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.robotId):$(dfg.userId))")
106+
length(parents) == 0 && error("Cannot find robot '$(dfg.robotId)' for user '$(dfg.userId)'")
107+
length(parents) > 1 && error("Found multiple robots '$(dfg.robotId)' for user '$(dfg.userId)'")
108+
109+
# Already exists?
110+
length(_getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(session.userId):$(session.robotId):$(session.id))")) != 0 &&
111+
error("Session '$(session.id)' already exists for robot '$(session.robotId)' and user '$(session.userId)'")
112+
113+
props = _convertNodeToDict(session)
114+
retNode = _createNode(dfg.neo4jInstance, ["SESSION", String(session.userId), String(session.robotId), String(session.id)], props, parents[1], :SESSION)
115+
return session
116+
end
117+
118+
"""
119+
$(SIGNATURES)
120+
Shortcut method to create the user, robot, and session if it doesn't already exist.
121+
"""
122+
function createDfgSessionIfNotExist(dfg::CloudGraphsDFG)::Session
123+
strip(dfg.userId) == "" && error("User ID is not populated in DFG.")
124+
strip(dfg.robotId) == "" && error("Robot ID is not populated in DFG.")
125+
strip(dfg.sessionId) == "" && error("Session ID is not populated in DFG.")
126+
user = User(Symbol(dfg.userId), dfg.userId, "Description for $(dfg.userId)", Dict{Symbol, String}())
127+
robot = Robot(Symbol(dfg.robotId), Symbol(dfg.userId), dfg.robotId, "Description for $(dfg.userId):$(dfg.robotId)", Dict{Symbol, String}())
128+
session = Session(Symbol(dfg.sessionId), Symbol(dfg.robotId), Symbol(dfg.userId), dfg.sessionId, "Description for $(dfg.userId):$(dfg.robotId):$(dfg.sessionId)", Dict{Symbol, String}())
129+
130+
_getNodeCount(dfg.neo4jInstance, [dfg.userId, "USER"]) == 0 && createUser(dfg, user)
131+
_getNodeCount(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"]) == 0 && createRobot(dfg, robot)
132+
if _getNodeCount(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"]) == 0
133+
return createSession(dfg, session)
134+
else
135+
return getSession(dfg)
136+
end
137+
end
138+
139+
"""
140+
$(SIGNATURES)
141+
List all sessions for the specified DFG's robot and user.
142+
Returns nothing if it isn't found.
143+
"""
144+
function lsSessions(dfg::CloudGraphsDFG)::Vector{Session}
145+
sessionNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(dfg.robotId):$(dfg.userId))")
146+
return map(s -> _convertDictToSession(Neo4j.getnodeproperties(s)), sessionNodes)
147+
end
148+
149+
"""
150+
$(SIGNATURES)
151+
List all robots for the specified DFG's user.
152+
Returns nothing if it isn't found.
153+
"""
154+
function lsRobots(dfg::CloudGraphsDFG)::Vector{Robot}
155+
robotNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.userId))")
156+
return map(s -> _convertDictToRobot(Neo4j.getnodeproperties(s)), robotNodes)
157+
end
158+
159+
"""
160+
$(SIGNATURES)
161+
List all users.
162+
Returns nothing if it isn't found.
163+
"""
164+
function lsUsers(dfg::CloudGraphsDFG)::Vector{User}
165+
userNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER)")
166+
return map(s -> _convertDictToUser(Neo4j.getnodeproperties(s)), userNodes)
167+
end
168+
169+
"""
170+
$(SIGNATURES)
171+
Get a session specified by userId:robotId:sessionId.
172+
Returns nothing if it isn't found.
173+
"""
174+
function getSession(dfg::CloudGraphsDFG, userId::Symbol, robotId::Symbol, sessionId::Symbol)::Union{Session, Nothing}
175+
!_isValid(userId) && error("Can't receive session with user ID '$(userId)'.")
176+
!_isValid(robotId) && error("Can't receive session with robot ID '$(robotId)'.")
177+
!_isValid(sessionId) && error("Can't receive session with session ID '$(sessionId)'.")
178+
sessionNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(sessionId):$(robotId):$(userId))")
179+
length(sessionNode) == 0 && return nothing
180+
length(sessionNode) > 1 && error("There look to be $(length(sessionNode)) sessions identified for $(sessionId):$(robotId):$(userId)")
181+
return _convertDictToSession(Neo4j.getnodeproperties(sessionNode[1]))
182+
end
183+
184+
"""
185+
$(SIGNATURES)
186+
Get the session specified by the DFG object.
187+
Returns nothing if it isn't found.
188+
"""
189+
function getSession(dfg::CloudGraphsDFG)::Union{Nothing, Session}
190+
return getSession(dfg, Symbol(dfg.userId), Symbol(dfg.robotId), Symbol(dfg.sessionId))
191+
end
192+
193+
"""
194+
$(SIGNATURES)
195+
Get a robot specified by userId:robotId.
196+
Returns nothing if it isn't found.
197+
"""
198+
function getRobot(dfg::CloudGraphsDFG, userId::Symbol, robotId::Symbol)::Union{Robot, Nothing}
199+
!_isValid(userId) && error("Can't receive session with user ID '$(userId)'.")
200+
!_isValid(robotId) && error("Can't receive session with robot ID '$(robotId)'.")
201+
robotNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(robotId):$(userId))")
202+
length(robotNode) == 0 && return nothing
203+
length(robotNode) > 1 && error("There look to be $(length(robotNode)) robots identified for $(robotId):$(userId)")
204+
return _convertDictToRobot(Neo4j.getnodeproperties(robotNode[1]))
205+
end
206+
207+
"""
208+
$(SIGNATURES)
209+
Get the robot specified by the DFG object.
210+
Returns nothing if it isn't found.
211+
"""
212+
function getRobot(dfg::CloudGraphsDFG)::Union{Nothing, Robot}
213+
return getRobot(dfg, Symbol(dfg.userId), Symbol(dfg.robotId))
214+
end
215+
216+
"""
217+
$(SIGNATURES)
218+
Get a user specified by userId.
219+
Returns nothing if it isn't found.
220+
"""
221+
function getUser(dfg::CloudGraphsDFG, userId::Symbol)::Union{User, Nothing}
222+
!_isValid(userId) && error("Can't receive session with user ID '$(userId)'.")
223+
userNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER:$(userId))")
224+
length(userNode) == 0 && return nothing
225+
length(userNode) > 1 && error("There look to be $(length(userNode)) robots identified for $(userId)")
226+
return _convertDictToUser(Neo4j.getnodeproperties(userNode[1]))
227+
end
228+
229+
"""
230+
$(SIGNATURES)
231+
Get the user specified by the DFG object.
232+
Returns nothing if it isn't found.
233+
"""
234+
function getUser(dfg::CloudGraphsDFG)::Union{Nothing, User}
235+
return getUser(dfg, Symbol(dfg.userId))
236+
end
6237

7238

8239
"""
@@ -72,18 +303,27 @@ DANGER: Copies the source to a new unique destination.
72303
copySession!(sourceDFG::CloudGraphsDFG)::CloudGraphsDFG = copySession!(sourceDFG, nothing)
73304

74305

75-
getUserData(dfg::CloudGraphsDFG)::Dict{Symbol, String} = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, "USER"])
306+
function getUserData(dfg::CloudGraphsDFG)::Dict{Symbol, String}
307+
propVal = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, "USER"], "data")
308+
return JSON2.read(String(base64decode(propVal)), Dict{Symbol, String})
309+
end
76310
function setUserData(dfg::CloudGraphsDFG, data::Dict{Symbol, String})::Bool
77-
error("Not implemented yet")
78-
return true
311+
count = _setNodeProperty(dfg.neo4jInstance, [dfg.userId, "USER"], "data", base64encode(JSON2.write(data)))
312+
return count == 1
313+
end
314+
function getRobotData(dfg::CloudGraphsDFG)::Dict{Symbol, String}
315+
propVal = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"], "data")
316+
return JSON2.read(String(base64decode(propVal)), Dict{Symbol, String})
79317
end
80-
getRobotData(dfg::CloudGraphsDFG)::Dict{Symbol, String} = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"])
81318
function setRobotData(dfg::CloudGraphsDFG, data::Dict{Symbol, String})::Bool
82-
error("Not implemented yet")
83-
return true
319+
count = _setNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"], "data", base64encode(JSON2.write(data)))
320+
return count == 1
321+
end
322+
function getSessionData(dfg::CloudGraphsDFG)::Dict{Symbol, String}
323+
propVal = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"], "data")
324+
return JSON2.read(String(base64decode(propVal)), Dict{Symbol, String})
84325
end
85-
getSessionData(dfg::CloudGraphsDFG)::Dict{Symbol, String} = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"])
86326
function setSessionData(dfg::CloudGraphsDFG, data::Dict{Symbol, String})::Bool
87-
error("Not implemented yet")
88-
return true
327+
count = _setNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"], "data", base64encode(JSON2.write(data)))
328+
return count == 1
89329
end

src/CloudGraphsDFG/services/CloudGraphsDFG.jl

Lines changed: 8 additions & 6 deletions
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
@@ -568,7 +572,7 @@ function isFullyConnected(dfg::CloudGraphsDFG)::Bool
568572
# Total connected nodes - thank you Neo4j for 0..* awesomeness!!
569573
query = """
570574
match (n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(varIds[1]))-[FACTORGRAPH*]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId))
571-
WHERE n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR
575+
WHERE (n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR) and not (node:SESSION)
572576
WITH collect(n)+collect(node) as nodelist
573577
unwind nodelist as nodes
574578
return count(distinct nodes)"""
@@ -662,11 +666,9 @@ Note: By default orphaned factors (where the subgraph does not contain all the r
662666
function getSubgraphAroundNode(dfg::CloudGraphsDFG, node::DFGNode, distance::Int64=1, includeOrphanFactors::Bool=false, addToDFG::AbstractDFG=_getDuplicatedEmptyDFG(dfg))::AbstractDFG
663667
distance < 1 && error("getSubgraphAroundNode() only works for distance > 0")
664668

665-
# Making a copy session if not specified
666-
#moved to parameter addToDFG::AbstractDFG=_getDuplicatedEmptyDFG(dfg)
667-
668669
# Thank you Neo4j for 0..* awesomeness!!
669-
neighborList = _getLabelsFromCyphonQuery(dfg.neo4jInstance, "(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(node.label))-[FACTORGRAPH*0..$distance]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId))")
670+
neighborList = _getLabelsFromCyphonQuery(dfg.neo4jInstance,
671+
"(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(node.label))-[FACTORGRAPH*0..$distance]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId)) WHERE (n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR) and not (node:SESSION)")
670672

671673
# Copy the section of graph we want
672674
_copyIntoGraph!(dfg, addToDFG, neighborList, includeOrphanFactors)

0 commit comments

Comments
 (0)