199 intermediate, state navigation

This commit is contained in:
2026-01-22 14:07:05 +03:00
parent 36048692e2
commit d843620292
7 changed files with 703 additions and 8 deletions
+1 -1
View File
@@ -57,7 +57,7 @@
//variables.framework.docs={};
variables.framework.docs.APIName="svc-api";
variables.framework.docs.APIVersion="0.198"; /*$ git config --global --unset user.password*/
variables.framework.docs.APIVersion="0.199"; /*$ git config --global --unset user.password*/
variables.framework.globalHeaders = structNew();
variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location";
+157
View File
@@ -0,0 +1,157 @@
<cfcomponent>
<!--- разбирает и вычисляет выражения из вложенных функций для зависимых CFS параметров --->
<cfset this.args = []/>
<cfproperty name="entity" type="string"/>
<cffunction name="init">
<cfargument name="entity" type="string" default="instance_operation_cfs_param"/>
<cfset this.entity=arguments.entity/>
<cfreturn this>
</cffunction>
<cffunction name="eval" returntype="any" access="public">
<cfargument name="expression" type="string"/>
<cfargument name="context" type="any"/><!--- {component,key,params} --->
<!--- Если параметр сохранен, то вся нужная информация может быть получена по его ключу. Просто и изящно, но.
Но если параметр, операция, инстанс не сохранены в базе - то нам придется предоставить с контекстом все, что может понадобиться
При этом контекст кажется вывернутым наизнанку: не инстанс / содержащий операции /содержащие параметры, а параметр, что-то знающий про операцию и инстанс
А бывают нужны
- параметры-зависимости (мы их специально перечисляем в отдельном поле)
- псевдопараметр resourceRealm, может наследоваться от предка (предок определен как раз параметром, инстансу существовать не обязательно) (нужно ли декларировать параметр-ссылку на предка в зависимостях?)
- стейт (есть только у созданного инстанса и вычисляется по ключу instance_uid)
Еще имя компонента
Например, контекст может содержать инстанс компонента, имя компонента и дополнительные параметры
По имени компонента мы можем сразу создать пустой (неинициализированный инстанс)
Ну и как ему передать параметры? У инстанса есть массив параметров. Точнее, map<any> (удобнее адресоваться именами). Так и передать. А дальше ресолвить по мере сил (это работа самого компонента)
Контекст сам выглядит компонентом, но поскольку его использование кажется локальным, мы можем взять произвольную структуру
Если компонент-параметр инстанциирован, мы можем получить из БД значения всех соседних параметров.
Но даже если компонент инстанциирован, непосредственно переданные значения параметров должны иметь приоритет перед сохраненными (мы сначала редактируем зависимые параметры, и только потом одним чохом записываем коллекцию параметров в БД
Логично использовать ленивую инициализацию
В этом рассуждении я упустил такой момент. Конкретно для параметра (хотя я хотел абстрагироваться от сущности) есть родительские сущности Операция и Инстанс,
и какая-то из них может быть задана. Это не так же точно, как собственный ключ, но несет некоторую полезную информацию. Может быть, передавать массив или map ключей?
--->
<cfset var args=[]/>
<cfset parse(args, tokenize(arguments.expression),0)/>
<cfreturn computeExpression(args[1].function, args[1].args, arguments.context)/>
</cffunction>
<!---
expression parse-evaluate
для навигации по стейтам и конфигурациям платформ
*** размещаем здесь временно. скорее всего, парсер надо оформить компонентом
Отдельный вопрос, как адресоваться к аргументам. Или парсер может возвращать функцию, которая будет работать в адресном пространстве параметра? Тогда ему никакой доступ не нужен.
Было бы интересно сериализовывать скомпилированное выражение
Но сейчас все вычисления происходят в самом парсере
*** сначала мы хотим отработать генерацию зависимых списков в deck-tool
*** нужно обрабатывать исключения разбора
--->
<cffunction name="tokenize" returntype="array" access="private">
<cfargument name="expression" type="string"/>
<cfset var local={}/>
<cfset var pattern='"(.*?)"|\w+(\s*\.\s*\w+)*|\(|\)'/>
<cfset var res=rematch(pattern,expression)/>
<cfset var tokens = []/>
<cfloop array=#res# item="local.t">
<cfset tmp=trim(local.t)/>
<cfif len(tmp)>
<cfset arrayAppend(tokens,tmp)/>
</cfif>
</cfloop>
<cfreturn tokens/>
</cffunction>
<cffunction name="tokenType" output=false access="private">
<cfargument name="token"/>
<cfif token EQ "(">
<cfreturn "("/>
<cfelseif token EQ ")">
<cfreturn ")"/>
<cfelseif left(token,1) EQ '"'><!--- не вдаваясь в лишние подробности... внятнее было парсить строку посимвольно --->
<cfreturn "string"/>
<cfelseif find(".",token)>
<cfreturn "variable"/>
<cfelse>
<cfreturn "function"/><!--- будем проще: если дальше не найдем аргументов - бросим исключение --->
</cfif>
</cffunction>
<cffunction name="parse" returntype="numeric" access="private"><!--- returns last token position --->
<cfargument name="args" type="array"/><!--- puts here parsed tree with nested functions --->
<cfargument name="tokens" type="array"/>
<cfargument name="positionBefore" type="numeric"/><!--- before first processed token, initially 0 --->
<cfset var i = arguments.positionBefore/>
<cfloop condition="++i LE arrayLen(arguments.tokens)">
<cfswitch expression=#tokenType(arguments.tokens[i])#>
<cfcase value="string">
<cfset arrayAppend(arguments.args,mid(arguments.tokens[i],2,len(arguments.tokens[i])-2))/>
</cfcase>
<cfcase value="variable">
<cfset arrayAppend( arguments.args, {"function"="getValue","args"=[tokens[i]]} )/>
</cfcase>
<cfcase value="function">
<cfset var f = {"function"=arguments.tokens[i],"args"=[]}/>
<cfif i GE arrayLen(arguments.tokens)>
<cfthrow message="unexpected end of expression"/>
</cfif>
<cfif #tokenType(arguments.tokens[++i])# NEQ "("><!--- skip opening parenthesis ---><!--- *** possible out of range --->
<cfthrow message="function syntax incorrect" detail="function #arguments.tokens[i-1]#, token='#arguments.tokens[i]#' (#arrayToList(tokens,' ')#"/>
</cfif>
<cfset i = parse(f.args, arguments.tokens, i)/>
<cfset arrayAppend(arguments.args,f)/>
</cfcase>
<cfcase value=")">
<cfreturn i/><!--- returns last token position --->
</cfcase>
</cfswitch>
</cfloop>
</cffunction>
<cffunction name="computeExpression" returntype="any" access="private"><!--- вычисление выражения снаружи внутрь --->
<cfargument name="function" type="string"/>
<cfargument name="args" type="array"/>
<cfargument name="context" type="any"/>
<cfloop index="i" from=1 to=#arrayLen(arguments.args)#><!--- тонкость: цикл item по массиву копирует элемент, а нам нужно его менять по ссылке --->
<cfif isStruct(arguments.args[i]) AND structKeyExists(arguments.args[i],"function")> <!--- и все равно это ненадежный способ отличать функцию от простого значения --->
<cfset arguments.args[i] = computeExpression(
arguments.args[i].function,
arguments.args[i].args,
arguments.context
) />
</cfif>
</cfloop>
<cfreturn executeFunction(arguments.function,arguments.args,arguments.context)/>
</cffunction>
<cffunction name="executeFunction" returntype="any" access="private"><!--- эта функция обращается к прикладным функциям, должна их видеть --->
<cfargument name="functionName"/><!--- а ссылку на функцию передать не получится? (замыкание даже наверно не нужно) --->
<cfargument name="args" type="array"/>
<cfargument name="context" type="any"/>
<!--- <cfoutput>#arguments.functionName#</cfoutput> args<cfdump var=#arguments.args#/> --->
<!--- <cfswitch expression=#arguments.functionName#>
<cfcase value="concat"><cfreturn concat(argumentCollection=arguments.args)/></cfcase>
<cfcase value="getResourceRealmConfig"><cfreturn getResourceRealmConfig(argumentCollection=arguments.args)/></cfcase>
<cfcase value="getKeyListFromStruct"><cfreturn getKeyListFromStruct(argumentCollection=arguments.args)/></cfcase>
<cfcase value="getValue"><cfreturn getValue(argumentCollection=arguments.args)/></cfcase>
<cfdefaultcase>
<cfthrow message="Undefined Function" detail="#arguments.functionName#"/>
</cfdefaultcase>
</cfswitch> --->
<!--- <cfdump var=#arguments#/> --->
<cfset var obj = createObject("component", arguments.context.component).init(arguments.context.keys, arguments.context.params)/>
<!--- <cfdump var=#obj# abort=true/> --->
<cfreturn invoke(obj, functionName, args)/>
</cffunction>
</cfcomponent>
+21 -4
View File
@@ -7,6 +7,7 @@
<!--- v0.04 2025-11-20 columns--->
<!--- v0.05 2025-11-20 multiple field entries in filter, filter became array instead of structure--->
<!--- v0.06 2025-11-22 custom url parser used to prevent comma collision in lists--->
<!--- v0.06 2026-01-22 json-type field validation (isValidX)--->
<cffunction name="empty2null"
access="public"
@@ -102,7 +103,7 @@
<cfset var operator=listGetAt(local.rawValue,1,":")/>
<cfset var value=listGetAt(local.rawValue,2,":")/>
</cfif>
<cfif !isValid(ARGUMENTS.params[local.item].type,value)>
<cfif !isValidX(ARGUMENTS.params[local.item].type,value)>
<cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
</cfif>
@@ -156,7 +157,7 @@
<cfset var operator=listGetAt(local.rawValue,1,":")/>
<cfset var value=listGetAt(local.rawValue,2,":")/>
</cfif>
<cfif !isValid(ARGUMENTS.params[local.item].type,value)>
<cfif !isValidX(ARGUMENTS.params[local.item].type,value)>
<cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
</cfif>
@@ -402,6 +403,22 @@
"queryDurationMs":#queryDurationMs#, <cfset runDurationMs=getTickCount()-request.startTickCount/>
"runDurationMs":#runDurationMs#
}</cfoutput>--->
<cffunction name="isValidX"
access="private"
returntype="boolean"
output="false"
>
<cfargument name="type" required=true/>
<cfargument name="value" required=true/>
<cfif arguments.type EQ 'json'>
<cfreturn isJson(arguments.value)/>
<cfelse>
<cfreturn isValid(arguments.type, arguments.value)/>
</cfif>
</cffunction>
<cffunction name="validateField"
access="public"
returntype="any"
@@ -414,7 +431,7 @@
<cfargument name="required" type="boolean" default="false"/>
<cfif structKeyExists(ARGUMENTS.struct, ARGUMENTS.name)>
<cfif NOT isValid(ARGUMENTS.type, ARGUMENTS.struct[ARGUMENTS.name])>
<cfif NOT isValidX(ARGUMENTS.type, ARGUMENTS.struct[ARGUMENTS.name])>
<cfthrow type="invalidParamValue" message="Parameter validation failed" detail="Supplied value of '#ARGUMENTS.name#' is not a valid #ARGUMENTS.type#"/>
</cfif>
<cfelse>
@@ -434,7 +451,7 @@
<cfif structKeyExists(arguments.struct, arguments.key)>
<cfif structKeyExists(arguments, "type")>
<cfset var x=arguments.struct[arguments.key]/>
<cfif isValid(arguments.type, x)>
<cfif isValidX(arguments.type, x)>
<cfif lCase(arguments.type) EQ "string">
<cfif structKeyExists(arguments, "maxLength")>
<cfif ARGUMENTS.maxLength GE 0>
+370 -2
View File
@@ -2,7 +2,7 @@
<!--- Вот здесь мы могли бы строить URI /instances/{instanceUid}/instanceOperations/{instanceOperationUid}/CfsParams, но это создает избыточность, у нас простые суррогатные первичные ключи. С другой стороны, контекст инстанса и операции у клиента всегда имеется. Но стоит ли передавать избыточную информацию? Это иногда провоцирует путаницу. Для POST мы можем передать необходимый контекст в теле запроса, а можем в URL.
В данном случае никакого смысла смотреть на все CFS параметры нет, только в рамках операции
Но: мы не хотели бы менять положение сущности в дереве. Либо она в корне, либо она ниже. И получится, когда мы выбираем один параметр, нам ни к чему контекст - мы к нему однозначно адресуемся.
Проголосуем за отсутстие избыточности?
Проголосуем против избыточности?
--->
<!--- Все методы неявно получают
@@ -21,6 +21,362 @@ arguments.requestArguments.specificationId=usrCustomerInfo.specificationId; //In
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<!--- cfproperty использовать не надо, это для веб сервисов --->
<cffunction name="init">
<!--- для нового инстанса нам нужно знать хотя бы его класс (сервис) --->
<cfargument name="keys" type="struct" required=true/>
<cfargument name="params" type="struct" required=true/>
<!--- *** возможно, заранее разрешать все ключи - лишнее --->
<cfset this.keys = arguments.keys/>
<cfset this.params = arguments.params/>
<cfset this.instance_operation_cfs_param_uid = "00000000-0000-0000-0000-000000000000"/><!--- *** костыль default=--->
<cfset this.instance_operation_uid = "00000000-0000-0000-0000-000000000000"/><!--- *** костыль default=--->
<cfset this.instance_uid = "00000000-0000-0000-0000-000000000000"/><!--- *** костыль default=--->
<cfif structKeyExists(arguments.keys,"instanceOperationCfsParamUid")>
<cfset this.instance_operation_cfs_param_uid=arguments.keys.instanceOperationCfsParamUid/>
<cfquery name="local.qResolve">
select p.instance_operation_uid, o.instance_uid, p.svc_operation_cfs_param_id
from instance_operation_cfs_param p
join instance_operation o on (p.instance_operation_uid=o.instance_operation_uid)
where p.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.keys.instanceOperationCfsParamUid# null=#!isValid("guid", arguments.keys.instanceOperationCfsParamUid)#/>
</cfquery>
<cfset this.instance_operation_uid=toString(local.qResolve.instance_operation_uid)/><!--- toString, чтобы не засорять вывод cfdump классами --->
<cfset this.instance_uid=toString(local.qResolve.instance_uid)/>
<cfset this.svc_operation_cfs_param_id = local.qResolve.svc_operation_cfs_param_id/>
</cfif>
<!--- *** обратить внимание, тут возможны конфликты противоречивых ключей, мы никак не контролируем. но если ресолвить в обратном порядке, они хотя бы будут согласованными (хотя могут отличаться от аргументов... пойми что лучше... или проверять и бросать исключение --->
<cfif structKeyExists(arguments.keys,"instanceOperationUid")>
<cfset this.instance_operation_uid=arguments.keys.instanceOperationUid/>
<cfquery name="local.qResolve">
select o.instance_uid
from instance_operation o
where o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.keys.instanceOperationUid# null=#!isValid("guid", arguments.keys.instanceOperationUid)#/>
</cfquery>
<cfset this.instance_uid=toString(local.qResolve.instance_uid)/>
</cfif>
<cfif structKeyExists(arguments.keys,"instanceUid")>
<cfset this.instance_uid=arguments.keys.instanceUid/>
</cfif>
<cfif structKeyExists(arguments.keys,"svcOperationCfsParamId")><!--- хотя вот этот ключ известен всегда... но для известного параметра мы же можем его не передавать--->
<cfset this.svc_operation_cfs_param_id=arguments.keys.svcOperationCfsParamId/>
</cfif>
<cfif structKeyExists(arguments.params,"resourceRealm")>
<cfset this.resourceRealm=arguments.params.resourceRealm/>
</cfif>
<!--- <cfdump var=#this# abort=true/> --->
<cfreturn this>
</cffunction>
<!--- ---------------------------------------------------------------------- --->
<!--- Прикладные функции, которые можно использовать в составе выражений. Аргументами им могут служить параметры, от которых мы зависим --->
<!--- Добавить обработку исключений --->
<cffunction name="dummy" returntype="any" access="public">
<cfargument name="arg" type="any"/>
<cfreturn arg/>
</cffunction>
<cffunction name="concat" returntype="string" access="public">
<cfset var local={}/>
<cfset var s=""/>
<cfloop array=#arguments# item="local.chunk">
<cfset s&="#local.chunk#"/>
</cfloop>
<cfreturn s/>
</cffunction>
<cffunction name="getValue"><!--- используем пока только для параметров, в принципе может видеть больше, чем положено --->
<cfargument name="token" type="string"/>
<!--- тут такой странный синтаксис: для скоупа params подставляем getCfsParam --->
<cfset var scope = trim(listGetAt(arguments.token,1,'.'))/>
<cfset var name = trim(listGetAt(arguments.token,2,'.'))/>
<cfif scope EQ "params">
<cfif listLen(arguments.token,'.') GT 2><!--- *** неуклюже. ради субпараметров --->
<cfreturn getCfsParam(name, trim(listGetAt(arguments.token,3,'.')))/>
<cfelse>
<cfreturn getCfsParam(name)/>
</cfif>
</cfif>
<cfreturn variables[scope][name]/><!--- *** вряд ли это отработает для субпараметров --->
</cffunction>
<!--- может быть, обобщить для наследуемых параметров... или для всех вообще --->
<cffunction name="getResourceRealm">
<!--- нужна отдельная функция, потому что resourceRealm может браться из параметров операции, а может наследоваться от родительского инстанса --->
<!--- *** отдельный вопрос - как мы получим предка, если у нас операция еще не сохранена? На фронте мы хотим в один прием сохранить и операцию и параметры (на самом деле для операции create - даже инстанс, операцию и параметры) --->
<!--- если операция не сохранена - мы как узнаем, есть у нее параметр resourceRealm, или нет? наверно, простой джойн тут не поможет
но никто не мешает спросить отдельно--->
<!--- *** тут возникает определенная путаница из-за того, что часть данных берется из параметров операции, а часть из параметров инстанса (кумулятивных) --->
<!--- <cfdump var=#this#/> --->
<cfif structKeyExists(this,"resourceRealm") AND len(this.resourceRealm)><!--- может быть, понятнее было бы брать прямо из параметров --->
<cfreturn this.resourceRealm/>
</cfif>
<!--- ищем у даннной операции CFS параметр resourceRealm ---><!--- <cfdump var=#this#/><cfabort/> --->
<cfquery name="local.qCfsParam">
select p.param_value
from svc_operation_cfs_param s
join instance_operation_cfs_param p on (s.svc_operation_cfs_param_id = p.svc_operation_cfs_param_id)
where s.svc_operation_cfs_param = 'resourceRealm'
AND p.instance_operation_uid = <cfqueryparam cfsqltype="cf_sql_other" value=#this.instance_operation_uid# null=#!isValid('guid',this.instance_operation_uid)#/>
</cfquery>
<!--- <cfdump var=#local.qCfsParam#/> --->
<!--- --->
<cfif local.qCfsParam.recordCount>
<!--- параметр определен в операции сервиса. Если его не нашли - значит, не судьба --->
<cfreturn local.qCfsParam.param_value/>
<cfelse>
<!--- у операции сервиса нет такого параметра - тогда по соглашению он определен в инстансе-предке. Тут нам надо знать хотя бы родительский инстанс --->
<!--- внимание, сложность. Для получения resourceRealm для параметра вновь создаваемого инстанса нам нужно ориентироваться на параметр - ссылку на родительский инстанс (пример - vdc), потому что текущего инстанса еще нет! --->
<!--- напоминание:
не путать родительский инстанс (связан параметром с инстансом - владельцем операции - владельцем данного параметра)
и инстанс-операцию-параметр (регулярная реляционная иерархия)
хотя инстанс и операция есть в параметрах (некоторое дублирование, потому что оркестратору все передается через параметры)
--->
<cfset local.instance_uid = this.instance_uid/>
<cfif !isValid("guid", local.instance_uid) OR local.instance_uid EQ "00000000-0000-0000-0000-000000000000">
<!--- тогда нам нужно найти параметр-референс и взять его значение --->
<!--- тот случай, когда нужно знать класс параметра и операции --->
<cfquery name="local.qRefParam">
select r.svc_operation_cfs_param
from svc_operation_cfs_param r
join svc_operation o on (r.svc_operation_id=o.svc_operation_id)
join svc_operation_cfs_param p on (o.svc_operation_id=p.svc_operation_id)
where p.svc_operation_cfs_param_id = <cfqueryparam cfsqltype="cf_sql_integer" value=#this.svc_operation_cfs_param_id# null=#!isValid('integer',this.svc_operation_cfs_param_id)#/>
AND r.ref_svc_id > 0
</cfquery>
<!--- <cfdump var=#local.qRefParam#/> --->
<cfloop query="local.qRefParam">
<cfif structKeyExists(this.params, local.qRefParam.svc_operation_cfs_param) AND isValid('guid', this.params[local.qRefParam.svc_operation_cfs_param])>
<cfset local.instance_uid = this.params[local.qRefParam.svc_operation_cfs_param]/>
</cfif>
</cfloop>
<!--- можно и более изощренно поступать, вызывая в этом цикле функцию рекурсивного поиска resourceRealm для каждого из референсных параметров, пока не найдем.
но пока если видим находим в параметрах ссылку на родителя - ищем там платформу, и все --->
<!--- вообще идея наследования платформы кажется не очень корректной - мы же можем ссылаться на нескольких предков... и реализация тяжеловесная
*** может быть, нужно метод получения платформы сделать всем инстансам, пусть сам знает, из какого параметра брать... это можно сделать конфигурируемым.
в обычной жизни брать из параметра
Можно сделать дополнительную фильтрацию по типу справочника (resourceRealm)
--->
</cfif>
<!--- внимание! мы тут опрашиваем параметры инстанса, а не операции! --->
<!--- *** тут возникает непонятная ссылка на себя, впрочем, она не мешает --->
<cfquery name="local.qCfsParam">
WITH RECURSIVE r AS (
SELECT
1 AS i
,e.display_name
,s.svc
,p.param
,p.param_value
,p.instance_uid::text
FROM instance e
JOIN instance_cfs_param p on (e.instance_uid=p.instance_uid)
JOIN svc s on (e.service_id=s.svc_id)
JOIN instance_operation_cfs_param op on (p.instance_operation_cfs_param_uid=op.instance_operation_cfs_param_uid)
LEFT JOIN svc_operation_cfs_param sp on (op.svc_operation_cfs_param_id=sp.svc_operation_cfs_param_id)
LEFT JOIN instance_cfs_param rp on (e.instance_uid=rp.instance_uid )
LEFT JOIN instance ri on (rp.instance_uid=ri.instance_uid)
WHERE e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#local.instance_uid# null=#!isValid('guid',local.instance_uid)#/>
UNION
SELECT
i+1 AS i
,e.display_name
,s.svc
,p.param
,p.param_value
,p.instance_uid::text
FROM instance_cfs_param p
JOIN instance e on (p.instance_uid=e.instance_uid)
JOIN svc s on (e.service_id=s.svc_id)
JOIN r on (r.param_value=p.instance_uid::text)
WHERE r.i < 10
)
select param, param_value from r
order by r.i desc
</cfquery>
<cfif local.qCfsParam.recordCount EQ 0>
<!--- тогда зайдем от параметров операции (только в данном экземпляре, зависимости все должны быть уже созданы) --->
<cfquery name="local.qCfsParam">
WITH RECURSIVE r AS (
SELECT
1 AS i
,e.display_name
,s.svc
,sp.svc_operation_cfs_param as param
,p.param_value::text
,o.instance_uid::text
FROM instance e
JOIN instance_operation o ON (e.instance_uid=o.instance_uid AND o.operation='create')
JOIN instance_operation_cfs_param p on (o.instance_operation_uid=p.instance_operation_uid)
JOIN svc s on (e.service_id=s.svc_id)
--JOIN instance_operation_cfs_param op on (p.instance_operation_cfs_param_uid=op.instance_operation_cfs_param_uid)
JOIN svc_operation_cfs_param sp on (p.svc_operation_cfs_param_id=sp.svc_operation_cfs_param_id AND sp.ref_svc_id > 0)
--LEFT JOIN instance_cfs_param rp on (e.instance_uid=rp.instance_uid )
--LEFT JOIN instance ri on (rp.instance_uid=ri.instance_uid)
WHERE e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#local.instance_uid# null=#!isValid('guid',local.instance_uid)#/>
UNION
SELECT
i+1 AS i
,e.display_name
,s.svc
,p.param
,p.param_value
,p.instance_uid::text
FROM instance_cfs_param p
JOIN instance e on (p.instance_uid=e.instance_uid)
JOIN svc s on (e.service_id=s.svc_id)
JOIN r on (r.param_value=p.instance_uid::text)
WHERE r.i < 10
)
select param, param_value from r
order by r.i desc
</cfquery>
</cfif>
<!--- <cfdump var=#local.qCfsParam#/>--->
<cfloop query="local.qCfsParam">
<cfif local.qCfsParam.param EQ "resourceRealm">
<cfreturn local.qCfsParam.param_value/>
</cfif>
</cfloop>
<cfreturn "Resource Realm Not Found"/>
</cfif>
</cffunction>
<cffunction name="getCfsParam">
<cfargument name="param" type="string"/>
<cfargument name="subparam" type="string" required=false/>
<!--- Секретность игнорируется. Все секреты доступны, как есть --->
<!--- правда, мы тут можем работать только с сохраненными параметрами. Можем попробовать полагаться на правило: зависимые параметры всегда позже (не факт, что это получится при конкурентном сохранении) --->
<!--- проблема: нам нужен тут instance_operation_uid, но таскать его в аргументах по всей цепочке вызовов неудобно. Если бы это у нас был метод CFS параметра, все было бы просто - это у нас поле параметра --->
<cfif structKeyExists(this.params,arguments.param)><!--- AND len(this.params[arguments.param]) --->
<cfif structKeyExists(arguments,"subparam")><!--- *** не изящно, но просто и понятно --->
<cfreturn deserializeJson(this.params[arguments.param])[arguments.subparam]/>
<cfelse>
<cfreturn this.params[arguments.param]/>
</cfif>
</cfif>
<cfquery name="local.qCfsParam">
select p.param_value
from instance_operation_cfs_param p
join svc_operation_cfs_param s on (p.svc_operation_cfs_param_id = s.svc_operation_cfs_param_id)
where p.instance_operation_uid = <cfqueryparam cfsqltype="cf_sql_other" value=#this.instance_operation_uid# null=#!isValid('guid',this.instance_operation_uid)#/>
AND s.svc_operation_cfs_param = <cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.param#/>
</cfquery>
<cfif structKeyExists(arguments,"subparam")><!--- *** не изящно, но просто и понятно --->
<cfreturn deserializeJson(local.qCfsParam.param_value)[arguments.subparam]/>
<cfelse>
<cfreturn local.qCfsParam.param_value/>
</cfif>
</cffunction>
<cffunction name="getValueFromStruct" returntype="string" hint="returns scalar">
<cfargument name="struct" type="struct" required="true"/>
<cfargument name="path" type="string" required="true"/>
<cftry>
<cfreturn structGet("arguments.struct.#arguments.path#")/>
<cfcatch type="any">
<!--- можно ожидать ошибки, если по указанному пути расположена не структура --->
<cfreturn cfcatch.message/><!--- халява, для упрощения диагностики --->
</cfcatch>
</cftry>
</cffunction>
<cffunction name="getKeyListFromStruct" returntype="string" hint="returns list">
<cfargument name="struct" type="struct" required="true"/>
<cfargument name="path" type="string" required="true"/>
<cftry>
<cfreturn structKeyList(structGet("arguments.struct.#arguments.path#"))/>
<cfcatch type="any">
<!--- можно ожидать ошибки, если по указанному пути расположена не структура --->
<cfrethrow/>
<cfreturn "#cfcatch.message# #cfcatch.detail#"/><!--- халява, для упрощения диагностики --->
</cfcatch>
</cftry>
</cffunction>
<cffunction name="getArrayAsListFromStruct" returntype="string" hint="returns list">
<cfargument name="struct" type="struct" required="true"/>
<cfargument name="path" type="string" required="true"/>
<cftry>
<cfreturn arrayToList(structGet("arguments.struct.#arguments.path#"))/>
<cfcatch type="any">
<!--- можно ожидать ошибки, если по указанному пути расположена не структура --->
<cfrethrow/>
<cfreturn "#cfcatch.message# #cfcatch.detail#"/><!--- халява, для упрощения диагностики --->
</cfcatch>
</cftry>
</cffunction>
<cffunction name="getInstanceState" returntype="struct">
<cfargument name="instanceUid" type="guid" required="true"/>
<cfset var local={}/>
<cfquery name="local.qInstance" datasource="cmdb">
select g.instance_state_uid, g.version, g.dt_state, g.instance_data, g.is_test, g.instance_uid
from instance_state g
where g.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceUid#/>
order by g.version desc
limit 1
</cfquery>
<cfreturn deserializeJson(local.qInstance.instance_data)/>
<cftry>
<cfset var structConfig = deserializeJson(local.qInstance.instance_data)/>
<cfcatch type="ANY">
<cfreturn {"error":"error parsing state"}/>
</cfcatch>
</cftry>
<cfif len(structConfig)>
<cfreturn structConfig/>
</cfif>
<cfreturn {}/>
</cffunction>
<cffunction name="getResourceRealmConfig" returntype="struct">
<cfargument name="resourceRealm" type="string" required="true"/><!--- платформа может быть определена для родительского инстанса где то в иерархии --->
<cfset var local={}/>
<cfquery name="local.qResourceRealm" datasource="cmdb">
select r.resource_realm_id, r.properties
from resource_realm r
where r.resource_realm=<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.resourceRealm#/>
</cfquery>
<!--- <cfdump var=#arguments#/>
<cfdump var=#local.qResourceRealm#/> --->
<cftry>
<cfset var structConfig = deserializeJson(local.qResourceRealm.properties)/>
<cfcatch type="ANY">
<cfreturn {"error":"error parsing resource realm properties"}/>
<!--- вообще-то такого быть не должно: свойства хранятся в поле jsonb --->
</cfcatch>
</cftry>
<cfif len(structConfig)>
<cfreturn structConfig/>
</cfif>
<cfreturn {}/>
</cffunction>
<!--- ReST methods --->
<!--- ----------------------------------------------------------------------------------------- --->
<!--- ----------------------------------------------------------------------------------------- --->
<!--- ----------------------------------------------------------------------------------------- --->
<cffunction name="get" hint="CFS параметр операции инстанса">
@@ -105,7 +461,7 @@ arguments.requestArguments.specificationId=usrCustomerInfo.specificationId; //In
<cfset local.qCfsParam.value_list = getListFromState(currentState, local.qCfsParam.state_path)/>
</cfif>
<cfif local.qCfsParam.data_type EQ "map" OR data_type EQ "map-fixed" OR data_type EQ "array-map-fixed">
<cfif local.qCfsParam.data_type EQ "map" OR local.qCfsParam.data_type EQ "map-fixed" OR local.qCfsParam.data_type EQ "array-map-fixed">
<cfset processMap(local.qCfsParam)/><!--- query passed by reference --->
</cfif>
@@ -487,6 +843,18 @@ arguments.requestArguments.specificationId=usrCustomerInfo.specificationId; //In
</cffunction>
</cfcomponent>
<!--- [{"b":"12","secret1":"preved"},{"b":"13","secret1":"preved404"},{"b":"14","secret1":"preved402"}] --->
@@ -95,6 +95,8 @@
<m:field>p.config::text</m:field><!--- в таком виде конфиг доступен для кода, но не выводится в респонсе --->
<m:field>null as nested_ref_data</m:field>
<m:field>p.state_path</m:field>
<m:field>p.depends_on_params</m:field>
<m:field>p.expression</m:field>
<m:field formatter=#request.castToBool#>(
select 1 from svc_operation_cfs_param m
join svc_operation om on (m.svc_operation_id=om.svc_operation_id)
+1 -1
View File
@@ -1,4 +1,4 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/param-value-list/{svcOperationCfsParamId}">
<cfcomponent extends="taffy.core.resource" taffy:uri="/param-value-list/{svcOperationCfsParamId}" hint="deprecated">
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
@@ -0,0 +1,151 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/svcOperationCfsParams/compute/{svcOperationCfsParamId}" hint="вычисляет выражение в поле expression">
<!---
вычисляет выражение в поле expression
для этого нужен контекст - как минимум, сохраненные параметры, но это не работает при создании нового инстанса,
поэтому нужно инициализировать контекст и набивать его всей необходимой информацией
что особенно важно для зависимых параметров - нам нужно положить в контекст все параметры, от которых мы зависим
чтобы знать, какие параметры передавать - конструкцией предусмотрено поле depends_on_params
--->
<!--- Все методы неявно получают
arguments.usrId
arguments.contragentId
arguments.contractId
arguments.specificationId
arguments.requestArguments.usrId=usrCustomerInfo.usrId; //Integer!
arguments.requestArguments.contragentId=usrCustomerInfo.contragentId; //Integer
arguments.requestArguments.contractId=usrCustomerInfo.contractId; //Integer
arguments.requestArguments.specificationId=usrCustomerInfo.specificationId; //Integer
--->
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<cffunction name="get" hint="вычисление возможных значений CFS параметра операции инстанса, в текущем контексте. Рекомендуется передавать keys.svcOperationCfsParamId для декларации класса параметра (а не keys.instanceOperationCfsParamUid) - он неизвестен для вновь создаваемых параметров">
<!--- <cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/> а вот ни фига--->
<!--- *** Важно: мы должны уметь посчитать параметр, если он не сохранен, то есть мы знаем его класс, а не инстанс --->
<!--- Вероятно, нам не очень нужно вычислять набор значений для инстанса параметра, мы всегда знаем класс --->
<!--- контест: --->
<cfargument name="svcOperationCfsParamId" type="string" required=true hint="type:integer"/>
<!--- <cfargument name="svcOperationCfsSubparamId" type="string" required=false hint="type:integer"/> --->
<cfargument name="keys" type="string" default={} hint="type:json keys we depend on: of the param uid itself, or operation uid, instance uid, service operation cfs param - if not all entities intstantiated yet"/>
<cfargument name="params" type="string" default="{}" hint="type:json any parameters we depend on"/>
<cftry>
<cfset this.helper.validateField(arguments, "svcOperationCfsParamId", "integer")/>
<cfset this.helper.validateField(arguments, "svcOperationCfsSubparamId", "integer", false)/>
<cfset this.helper.validateField(arguments, "keys", "json")/> <!--- *** может, не лучшая идея передавать ключ в контексте --->
<cfset this.helper.validateField(arguments, "params", "json")/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset local={}/>
<cfset local.expression = ""/> <!--- нет смысла выносить это в поля класса, оно нужно только локально --->
<cfset local.calculatedValueList = ""/>
<cfset local.keys = deserializeJson(arguments.keys)/>
<cfset local.params = deserializeJson(arguments.params)/>
<cfset local.keys.svcOperationCfsParamId = arguments.svcOperationCfsParamId/> <!--- *** сомнительное дело гонять аргументы туда-сюда --->
<!--- Нам нужен либо класс и параметры, либо инстанс с сохраненными параметрами (при этом в контексте параметры имеют приоритет перед сохраненными, но если их нет - хватит нам и сохраненных) --->
<!--- нам нужно выражение. чтобы его достать, нужно знать параметр или класс параметра --->
<!--- для получения resourceRealm нужен параметр,операция,инстанс или сам resourceRealm --->
<!--- <cfif structKeyExists(local.keys,"instanceOperationCfsParamUid") AND isValid("guid",local.keys.instanceOperationCfsParamUid)>
<cfquery name="local.qCfsParam" result="local.result">
select
<m:field_set titleMapOut="local.titleMap" lengthOut="fieldCount">
<m:field>iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid</m:field>
<m:field>iop.instance_operation_uid::text as instance_operation_uid</m:field>
<m:field>iop.svc_operation_cfs_param_id</m:field>
<m:field>sop.data_type</m:field>
<m:field>sop.svc_operation_cfs_param</m:field>
<m:field>sop.expression</m:field>
</m:field_set>
from instance_operation_cfs_param iop
join svc_operation_cfs_param sop on (iop.svc_operation_cfs_param_id=sop.svc_operation_cfs_param_id)
join instance_operation io on (iop.instance_operation_uid=io.instance_operation_uid)
join instance e on (io.instance_uid=e.instance_uid)
join specification_item si on (e.specification_item_id=si.specification_item_id)
left outer join usr u on (iop.creator_id=u.usr_id)
where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.keys.instanceOperationCfsParamUid#" null=#!isValid('guid',local.keys.instanceOperationCfsParamUid)#/>
AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
</cfquery>
<cfif local.qCfsParam.recordCount EQ 0>
<cfreturn representationOf(this.helper.formatMessage("CFS Parameter Not Found Or Not Accessible")).withStatus(404)/>
</cfif>
<cfset local.expression=local.qCfsParam.expression/>
<cfelseif structKeyExists(local.keys,"svcOperationCfsParamId") AND isValid("integer",local.keys.svcOperationCfsParamId)> --->
<cfquery name="local.qCfsParam" result="local.result">
select
<m:field_set titleMapOut="local.titleMap" lengthOut="fieldCount">
<m:field>sop.data_type</m:field>
<m:field>sop.svc_operation_cfs_param</m:field>
<m:field>sop.expression</m:field>
</m:field_set>
from svc_operation_cfs_param sop
where sop.svc_operation_cfs_param_id
= <cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsParamId# null=#!isValid('integer',arguments.svcOperationCfsParamId)#/>
</cfquery>
<cfif local.qCfsParam.recordCount EQ 0>
<cfreturn representationOf(this.helper.formatMessage("Service Operation CFS Parameter Not Found")).withStatus(404)/>
</cfif>
<cfset local.expression=local.qCfsParam.expression/>
<!--- <cfelse>
<cfreturn representationOf(this.helper.formatMessage("CFS parameter class undefined")).withStatus(400)/>
</cfif> --->
<!--- параметра у нас может еще не быть! по минимуму нужен его класс
инициализация построена так, чтобы вытащить ту информацию, которую получается, на основании известных ключей и параметров --->
<!--- но тут мы перехитрили сами себя. нам для вызова парсера нужно выражение.
а получать выражение, зная _некоторый_ набор ключей, умеет как раз компонент, который инстанциируется в методе expression_parser.eval
Получается, инстанциировать его надо снаружи
Таким образом, контекст должен состоять просто из компонента-параметра, инстанциированного в полном фарше, а у нас он пока состоит из набора "собери компонент сам"
Конечно, мы можем сейчас сдублировать тут код инициализации параметра, но криво же
или логика не такая, и нет дублирования?
--->
<!--- минуточку, а как мы управляемся с субпараметром? инстанциируем тот же параметр? Это означает, что класс занимается не своим делом *** --->
<cfif len(local.expression)>
<cfset calculatedValueList=createObject("component","lib.expression_parser")
.eval(
local.expression,
{/*context*/
component:"resources.instance_operation_cfs_param",
keys:#local.keys#,
params:#local.params#
}
)
/>
<cfelse>
<cfreturn representationOf(this.helper.formatMessage("Expression Not Found")).withStatus(404)/>
</cfif>
<!--- Тут нам еще придется сформатировать лист в массив. Но обошлось довольно просто --->
<cfset var out=structNew("linked")/>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.values" = listToArray(local.calculatedValueList)/>
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
<cfreturn representationOf(out) />
</cffunction><!--- get --->
</cfcomponent>