function node.select(X, path, required)
local elements = path:split('/')
if not elements or #elements == 0 then
return
end
local thisNode = X
for i, v in ipairs(elements) do
local nodeType = xml.ELEMENT
local _, _, nodeName, predicate = v:find("^([^@]%w+)%[(.+)%]$")
if not nodeName then
nodeName = v
if nodeName:sub(1, 1) == '@' then
nodeType = xml.ATTRIBUTE
nodeName = nodeName:sub(2)
end
end
local nextNode
local count = thisNode:childCount(nodeName)
if count > 0 then
for j = 1, count do
local child = thisNode:child(nodeName, j)
if child:nodeType() == nodeType then
if predicate then
-- predicate = child index
local index = tonumber(predicate)
if index then
if index == j then
nextNode = child
break
end
end
-- predicate = child expression
local parts = predicate:split("=")
if parts then
local nodeValue = child:text(parts[1])
local compare = parts[2]:gsub("^['](%w+)[']$", "%1")
if nodeValue == compare then
nextNode = child
break
end
else
error('invalid predicate: ' .. predicate, 2)
end
else
nextNode = child
break
end
end
end
end
trace(nextNode)
-- set thisNode for next loop iteration
thisNode = nextNode
if not thisNode then
if required then
error('required path not found: ' .. v, 2)
end
return
end
end
return thisNode
end
function node.number(X, path, required, default)
if path then
X = X:select(path, required)
end
local nodeValue
if X then
if X:nodeType() == xml.ATTRIBUTE then
nodeValue = X:nodeValue()
else
for i = 1, #X do
if X[i]:nodeType() == xml.TEXT then
nodeValue = X[i]:nodeValue()
break
end
end
end
end
nodeValue = tonumber(nodeValue)
if nodeValue then
return nodeValue
elseif default then
return default
else
return 0
end
end
function node.text(X, path, required, default)
if path then
X = X:select(path, required)
end
local nodeValue
if X then
if X:nodeType() == xml.ATTRIBUTE then
nodeValue = X:nodeValue()
else
for i = 1, #X do
if X[i]:nodeType() == xml.TEXT then
nodeValue = X[i]:nodeValue()
break
end
end
end
end
if nodeValue then
return nodeValue
elseif default then
return default
else
return ''
end
end
function node.children(X, name, matchPath, matchValue)
local list = {}
for i = 1, #X do
if X[i]:nodeType() == xml.ELEMENT then
if X[i]:nodeName() == name then
if matchPath then
if X[i]:text(matchPath) == matchValue then
table.insert(list, X[i])
end
else
table.insert(list, X[i])
end
end
end
end
return list
end
function node.firstChild(X, name, matchPath, matchValue)
for i = 1, #X do
if X[i]:nodeType() == xml.ELEMENT then
if X[i]:nodeName() == name then
if matchPath then
if X[i]:text(matchPath) == matchValue then
return X[i]
end
else
return X[i]
end
end
end
end
end
function node.appendText(X, name, value)
local element = X:append(xml.ELEMENT, name)
element:append(xml.TEXT, value)
return element
end
function node.removeText(X)
for i = #X, 1, -1 do
if X[i]:nodeType() == xml.TEXT then
X[i] = nil
end
end
end
-- validate required parameters (List) are present in table (ignore arg1..argN parameters)
function checkParam(T, List)
if type(T) ~= 'table' then
error('invalid arguments, expected table', 3)
end
for k,v in pairs(List) do
for w,x in pairs(T) do
if w:find('arg') then
if w == 'arg' then error('Unknown parameter "'..w..'"', 3) end
else
if not List[w] then error('Unknown parameter "'..w..'"', 3) end
end
end
end
end
-- convert arg1..argN parameters into table array
function getArgs(P)
local args = {}
for k,v in pairs(P) do
if k:find('arg')==1 then
args[tonumber(k:sub(4))] = P[k]
end
end
return args
end
-- safely retry function
function retry(P)
checkParam(P, {func=0, retry=0, pause=0, funcname=0, errorfunc=0})
if type(P.func) ~= 'function' then
error('invalid func specified, expected pointer to function', 2)
end
local RetryCount = P.retry or -1
local Delay = P.pause or 10
local Fname = P.funcname
local Func = P.func
local ErrorFunc = P.errorfunc
local Success, ErrMsgOrReturnCode
local Args = getArgs(P)
local i = 0
if iguana.isTest() then RetryCount = 2 end
while true do
local R = {pcall(Func, unpack(Args))}
Success = R[1]
ErrMsgOrReturnCode = R[2]
if ErrorFunc then
Success = ErrorFunc(unpack(R))
end
if Success then
if i > 0 then
status('green', 'Operation succeeded on retry after ' .. i .. ' attempts.')
else
status('green')
end
return unpack(R,2)
else
local msg = 'Error executing operation '
if Fname then
msg = msg .. Fname .. '() '
end
if i == 0 then
msg = msg .. 'on first attempt.'
else
msg = msg .. 'on retry attempt ' .. i .. '.'
end
if RetryCount == -1 or i < RetryCount then
msg = msg .. '\nSleeping for ' .. Delay .. ' seconds before next retry.'
msg = msg .. '\nError: ' .. ErrMsgOrReturnCode
status('yellow', msg)
util.sleep(Delay * 1000)
i = i + 1
else
msg = msg .. '\nMax retries reached, stopping retries.'
status('yellow', msg)
return false, msg
end
end
end
end
function iguana.getChannelStatus(channelName)
if not channelName then
channelName = iguana.channelName()
end
local iguanaStatus = iguana.status()
local root = xml.parse(iguanaStatus)
for i = 1, root.IguanaStatus:childCount('Channel') do
local channel = root.IguanaStatus:child('Channel', i)
if channel.Name:nodeValue() == channelName then
return channel
end
end
end
function iguana.throttleChannel(queueSize, channelName)
local channelStatus = iguana.getChannelStatus(channelName)
trace(channelStatus)
if not channelStatus then
return true, 'unable to determine destination channel status'
end
local destChannelStatus = channelStatus.Status:nodeValue()
if not iguana.isTest() and destChannelStatus == 'off' then
return true, 'destination channel is currently off'
end
local destQueueSize = tonumber(channelStatus.MessagesQueued:nodeValue())
if not iguana.isTest() and destQueueSize > queueSize then
return true, 'destination queue size exceeds ' .. queueSize .. ' (currently ' .. destQueueSize .. ')'
end
-- do not throttle channel
return false
end
function status(color, text)
if text then
text = text .. '\n' .. os.date()
end
iguana.setChannelStatus{color = color, text = text}
end
function warning(text)
iguana.logWarning(text)
text = text .. '\n' .. os.date()
iguana.setChannelStatus{color = 'yellow', text = text}
end