Files
svc-api-x/results/source-bundle.md
T

417 KiB
Raw Blame History

Исходники svc-api-x

Дата сборки: 2026-04-30

В bundle включен только прикладной слой после cleanup: корневые CFML-файлы, v1/Application.cfc, v1/lib/* и v1/resources/* без vendor Taffy и без backup-каталогов.

health.cfm

v1/Application.cfc

<cfcomponent extends="taffy.core.api">
	
	<cfset this.mappings["/resources"] = expandPath("./resources")/>
	<cfset this.mappings["/taffy"] = expandPath("./taffy")/>
	<cfset this.mappings["/lib"] = expandPath("./lib")/>	
	
	<cflock scope="application" type="exclusive" timeout="3">
		<cftry>
			<cfinclude template="../../conf/prod.cfm"/>
			<cfcatch type="ANY">
				<cftry>
					<cfinclude template="../../conf/stage.cfm"/>		
					<cfcatch type="ANY">
						<cftry> 
							<cfinclude template="../../conf/dev.cfm"/>		
							<cfcatch type="ANY">
								<cfset this.config.environment = "dev-default" />
								<cfset this.config.datasource = "cmdb" />
								<cfset this.config.is_dev_default = "true" />
							</cfcatch>
						</cftry>
						
					</cfcatch>
				</cftry>		
			</cfcatch>
		</cftry>	
		
		<cfset this.datasource = this.config.datasource />	
		<cfset request.config = this.config />	
	</cflock>

	<cfscript>
		this.name = hash(getCurrentTemplatePath());
		variables.framework.debugKey = "debug";
		variables.framework.reloadKey = "reload";
		variables.framework.reloadPassword = "true";
		variables.framework.reloadOnEveryRequest = true;
		variables.framework.serializer = "taffy.core.nativeJsonSerializer";
		variables.framework.dashboardKey = "dashboard";
		variables.framework.disableDashboard = false;
		variables.framework.unhandledPaths = "/flex2gateway";
		variables.framework.allowCrossDomain = true;
		
		variables.framework.docs.APIName="svc-api";
		variables.framework.docs.APIVersion="0.234";
		
		variables.framework.globalHeaders = structNew();
		variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location"; 
		variables.framework.globalHeaders["Access-Control-Allow-Credentials"] = "true";
		variables.framework.globalHeaders["X-Application-Version"] = variables.framework.docs.APIVersion;
		
		request.UNDEFINED_USR_ID=-1;
		request.ANONYMOUS_USR_ID=2;
		request.GUEST_USR_ID=3;
		request.USER_AGENT="#variables.framework.docs.APIName# #variables.framework.docs.APIVersion# stand:#this.getStand()#";		
		
		request.ORCHESTRATOR_AUTH = createObject("java", "java.lang.System").getEnv("ORCHESTRATOR_AUTH");
		if (isNull(request.ORCHESTRATOR_AUTH)) {
			request.ORCHESTRATOR_AUTH = "Basic ...no data";
		} 
		request.vault_login_url="https://vault.lk.adl.nubes.ru/v1/auth/approle/login";
		request.vault_role_id=createObject("java", "java.lang.System").getEnv("VAULT_ROLE_ID");
		request.vault_secret_id=createObject("java", "java.lang.System").getEnv("VAULT_SECRET_ID");
		
		request.auth_header="";
		
	</cfscript>
	
	
	<cffunction name="rethrow" returntype="void">
		<cftry>
			<cfcatch>
				<cfrethrow/>
			</cfcatch>
		</cftry>
		<cfthrow type="Context validation error" message="RETHROW() called outside TRY-CATCH"/>
	</cffunction>	
	
	<cffunction name="castToBool" returntype="any">
		<cfargument name="x"/>		
		<cfif isNull(arguments.x) OR isEmpty(arguments.x)> 
			<cfreturn arguments.x/>
		<cfelse>
			<cfreturn (arguments.x NEQ 0)/>
		</cfif>
	</cffunction>
	<cfset request.castToBool=#castToBool#/>		
	
	<cffunction name="castToBoolSimple" returntype="any">
		<cfargument name="x"/>		
		<cfreturn (arguments.x NEQ 0)/>
	</cffunction>
	<cfset request.castToBoolSimple=#castToBoolSimple#/>	

 	<cffunction
        name="getStand"
        access="private"
        returntype="string"
        output="true">
		
 		<cftry>		
			<cfquery name=qConfig>
				select value as stand from config 				
				where name='STAND'
			</cfquery>
			<cfreturn qConfig.stand/>
			
			<cfcatch type="ANY">
				
			</cfcatch>
		</cftry> 
        <cfreturn ""/>
    </cffunction>
	

	<cffunction name="onApplicationStart">		
		<cfset this.iamServiceUrl=locateIamService()/>
		<cfreturn super.onApplicationStart() />
	</cffunction>
	


	<cffunction name="locateIamService">
		<cfset var stand=""/>
	 	<cftry>		
			<cfquery name=qConfig>
				select value as stand from config 				
				where name='STAND'
			</cfquery>
			<cfset stand = qConfig.stand/>			
		
			<cfswitch expression=#stand#>
				<cfcase value=",dev">
					<cfreturn "https://auth-api-dev.ngcloud.ru/api/v1" />
				</cfcase>
				<cfcase value="test">
					<cfreturn "https://auth-api-test.ngcloud.ru/api/v1" />
				</cfcase>			
				<cfcase value="prod">
					<cfreturn "https://auth-api.ngcloud.ru/api/v1" />
				</cfcase>			
				<cfdefaultcase></cfdefaultcase>
			</cfswitch> 
		
			<cfcatch type="ANY">
				<cfthrow message="IAM service unavailable" detail="there is no IAM for stand #stand# defined"/>
			</cfcatch>
		</cftry>
	</cffunction>		
	<cfscript>
	
	function onTaffyRequest(verb, cfc, requestArguments, mimeExt, headers){
		
		request.stand=this.getStand();
		request.iam_service_url=this.iamServiceUrl;
		
		if (uCase(arguments.verb) EQ 'OPTIONS') return true;
		if (lCase(arguments.cfc) EQ 'err') return true;
		if (lCase(arguments.cfc) EQ 'throw') return true;
	
		
		if (structKeyExists(headers,"Authorization")) {
			request.auth_header=headers.Authorization;
		} else {
			return representationOf("Authorization header expected").withStatus(401);
		}
		var result="";
		try {
			var authUrl =  "#this.iamServiceUrl#/auth/user";
			var httpService = new http(method = "GET", charset = "utf-8", url = #authUrl#, timeout="5");
			httpService.addParam(type = "HEADER", name = "Accept",  value = "application/json");
			httpService.addParam(type = "HEADER", name = "Authorization",  value = "#request.auth_header#");
			var resp = httpService.send();	
			var prefix = resp.getPrefix();
			if (prefix.status_code NEQ 200) {
				var iamStatusCode = (isValid("integer", prefix.status_code)) ? val(prefix.status_code) : 500;
				return representationOf( {"IAM URL"=#authUrl#, "idpResponse"=resp} ).withStatus(iamStatusCode);
			}
			result = prefix.filecontent;
		} catch (e) { 
			
			return representationOf( {"exception"=e, "idpResponse"=result} ).withStatus(200);
		}
		
		try {	
			var idpUserData=deserializeJson(result);	
			"arguments.requestArguments.companyUid"=idpUserData.userInfo.companyId;
			"arguments.requestArguments.usrUid"=idpUserData.userInfo.contactId;
			
			var usrCustomerInfo=getUsrCustomerInfo(idpUserData.userInfo.contactId, idpUserData.userInfo.companyId);
		} catch (e) {
			return representationOf( {"exception.Message"=e.Message, "exception.Detail"=e.Detail, "idpResponse"=result} ).withStatus(422);
		}		
		
		if (lCase(arguments.cfc) EQ 'user') {
			if (structIsEmpty(usrCustomerInfo)) return representationOf("User information not found").withStatus(404);
		}
		
		if (lCase(arguments.cfc) EQ 'notification_ls') {
			if (structIsEmpty(usrCustomerInfo)) {
				"arguments.requestArguments.usrId"=-1;
				"arguments.requestArguments.contragentId"=-1;
				"arguments.requestArguments.contractId"=-1;
				"arguments.requestArguments.specificationId"=-1;
				"arguments.requestArguments.isImpersonated"=false; 
				"arguments.requestArguments.clientID"=""; 
				"arguments.requestArguments.login"=""; 
				
				return true;
			}
		}
		
		if (structIsEmpty(usrCustomerInfo)) return representationOf("Cannot find default specification for current user #result#").withStatus(422);
		
		"arguments.requestArguments.usrId"=usrCustomerInfo.usrId;
		"arguments.requestArguments.contragentId"=usrCustomerInfo.contragentId;
		"arguments.requestArguments.contractId"=usrCustomerInfo.contractId;
		"arguments.requestArguments.specificationId"=usrCustomerInfo.specificationId;
		
		try {
			"arguments.requestArguments.isImpersonated"=idpUserData.impersonation.is_impersonated; 
		} catch (e) {
			"arguments.requestArguments.isImpersonated"=false;
		}	
		
		try {
			"arguments.requestArguments.clientID"=idpUserData.userInfo.clientID;
		} catch (e) {
			"arguments.requestArguments.clientID"="";
		}	
		
		try {
			"arguments.requestArguments.login"=idpUserData.userInfo.login;
		} catch (e) {
			"arguments.requestArguments.login"="";
		}
	
		return true;
	}

	</cfscript>

	<cffunction name="checkAuth">	
		<cfset var jwtHelper=CreateObject("component","lib.jwt").init()/>		
		<cfset var headers=#GetHttpRequestData().headers#/>
		<cftry>	
			<cfset var rawToken=right(headers.Authorization,len(headers.Authorization)-len('bearer '))/>
			<cfset var token=jwtHelper.decode(token=rawToken, key=request.config.IDP_certificate.keys[1], algorithms='RS256')/>
			
			<cfset var login=token.preferred_username/>
			<cfset login=ReplaceNoCase(login,"#request.config.auth_domain_suffix#","")/>
			<cfquery name="local.qUsr">
				select usr_id from usr where login=<cfqueryparam cfsqltype="cf_sql_varchar" value="#login#"/>
			</cfquery>
			
			<cfif #local.qUsr.recordCount# EQ 0>
				<cfheader statuscode="401" statustext="User not found in local database"/>		
				<cfreturn false/>
			</cfif>
			
			<cfset request.usr_id=local.qUsr.usr_id/>
			
			<cfcatch type="database">
				<cfheader statuscode="500" statustext="Internal Server Error - Database Error"/>
				<cfoutput>#cfcatch.message# : #cfcatch.detail#</cfoutput>
				<cfreturn false/>
			</cfcatch>
			
			<cfcatch type="ANY">
				<cfif !GetHttpRequestData().method EQ "OPTIONS">				
					<cfoutput>#cfcatch.message# : #cfcatch.detail#</cfoutput>
					<cfreturn false/>
				</cfif>
			</cfcatch>
		</cftry>
				
		<cfreturn true/>
	</cffunction>
	
	
	<cffunction name="getUsrCustomerInfo">
		при этом если имперсонируется компания, то спецификацию и контракт нужно брать по компании
		Кстати, зачем нужен вообще юзер, если компания всегда доступна, а вся информация висит на ней 
		Для того, чтобы разрешить ключ в целочисленный ключ CMDB--->
		<cfargument name="usrUid"/>
		<cfargument name="contragentUid"/>
		
		<cfif !isValid('guid',arguments.usrUid)>
		</cfif>		
		<cfif !isValid('guid',arguments.contragentUid)>
			<cfthrow message="Invalid contragent uuid" detail="uuid=(#arguments.contragentUid#) is not a valid UUID"/>
		</cfif>
		
		<cfset local={}/>
		

		<cfquery name="local.qGetContragentInfo">
			select z.contragent_id, c.contract_id, s.specification_id
			from contragent z 
			join contract c on (z.contragent_id=c.contragent_id)
			join specification s on (c.contract_id=s.contract_id)
			where z.external_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.contragentUid#" null=#!isValid('guid',arguments.contragentUid)#/>
			order by s.specification_id desc 
			limit 1;
		</cfquery>
		
		<cfif local.qGetContragentInfo.recordCount EQ 0>
			<cfquery name="local.qGetContragent">
				select z.contragent_id
				from contragent z 
				where z.external_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.contragentUid#" null=#!isValid('guid',arguments.contragentUid)#/>
			</cfquery>
			<cfif local.qGetContragent.recordCount EQ 0>			
				<cfthrow message="Contragent Not Found in CMDB" detail="Contragent with uuid=(#arguments.contragentUid#) not found in CMDB"/>
			</cfif>
			
			<cfquery name="local.qGetContract">
			select z.contragent_id, c.contract_id
			from contragent z 
			join contract c on (z.contragent_id=c.contragent_id)
			where z.external_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.contragentUid#" null=#!isValid('guid',arguments.contragentUid)#/>
			</cfquery>
			<cfif local.qGetContract.recordCount EQ 0>			
				<cfthrow message="Contragent does not have a contract in CMDB" detail="No contract for contragent with uuid=(#arguments.contragentUid#) found in CMDB (contragent_id=#local.qGetContragent.contragent_id#)"/>
			</cfif>
			
			<cfthrow message="Contragent does not have a specification in CMDB" detail="No specification for contragent with uuid=(#arguments.contragentUid#) found in CMDB (contragent_id=#local.qGetContragent.contragent_id#)"/>
		</cfif>
		
		<cfquery name="local.qGetUserInfo">
			select u.usr_id, u.contragent_id, k.contragent_id as c_contragent_id
			from usr u 
			left outer join contragent k on (u.contragent_id=k.contragent_id)
			where u.idp_usr_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.usrUid#" null=#!isValid('guid',arguments.usrUid)#/>
			limit 1;
		</cfquery>	
		
		<cfif local.qGetUserInfo.recordCount EQ 0>
			<cfthrow message="User Not Found in CMDB" detail="User with uuid=(#arguments.usrUid#) not found in CMDB"/>
		</cfif>	
		<cfif !len(local.qGetUserInfo.contragent_id)>
			<cfthrow message="User is not mapped to Contragent in CMDB" detail="Contragent for user with uuid=(#arguments.usrUid#) not specified in CMDB"/>
		</cfif>			
		<cfif !len(local.qGetUserInfo.c_contragent_id)>
			<cfthrow message="User is not mapped to Contragent in CMDB" detail="Contragent for user with uuid=(#arguments.usrUid#) not found in CMDB (specified contragent_id=#local.qGetUserInfo.contragent_id#)"/>
		</cfif>			


		<cfreturn {
			"usrId"=#local.qGetUserInfo.usr_id#,
			"contragentId"=#local.qGetContragentInfo.contragent_id#, 
			"contractId"=#local.qGetContragentInfo.contract_id#, 
			"specificationId"=#local.qGetContragentInfo.specification_id#			
		}/>

	</cffunction>
	
	
	<cffunction name="corsHeaders">
		<cfset var _taffyRequest=request._taffyRequest/>
		<cfset local={}/>		
		
		<cfset local.allowVerbs = uCase(structKeyList(_taffyRequest.matchDetails.methods)) />
		<cfif true 
				AND listFindNoCase('PUT,PATCH,DELETE,OPTIONS',_taffyRequest.verb)
				AND NOT listFind(local.allowVerbs,'OPTIONS')>
			<cfset local.allowVerbs = listAppend(local.allowVerbs,'OPTIONS') />
		</cfif>
		<cfif structKeyExists(_taffyRequest.headers, "origin") >

			<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
			
			<cfheader name="Access-Control-Allow-Methods" value="#local.allowVerbs#" />
			<cfif not structKeyExists(_taffyRequest.headers, "Access-Control-Request-Headers")>
				<cfheader name="Access-Control-Allow-Headers" value="Origin, Authorization, X-CSRF-Token, X-Requested-With, Content-Type, X-HTTP-Method-Override, Accept, Referrer, User-Agent" />
			<cfelse>
				<cfset local.allowedHeaders = {} />
				<cfloop list="Origin,Authorization,X-CSRF-Token,X-Requested-With,Content-Type,X-HTTP-Method-Override,Accept,Referrer,User-Agent" index="local.h">
					<cfset local.allowedHeaders[local.h] = 1 />
				</cfloop>
				<cfset local.requestedHeaders = _taffyRequest.headers['Access-Control-Request-Headers'] />
				<cfloop list="#local.requestedHeaders#" index="local.i">
					<cfset local.allowedHeaders[ local.i ] = 1 />
				</cfloop>
				<cfheader name="Access-Control-Allow-Headers" value="#structKeyList(local.allowedHeaders)#" />
			</cfif>
		</cfif>
	</cffunction>
	
	<cffunction name="onRequest" output="true" returntype="boolean">
		<cfargument name="targetPage" type="string" required="true" />
		

		<cfset var _taffyRequest = {} />
		<cfset var local = {} />
		<cfset var m = '' />
		<cfset request._taffyRequest = _taffyRequest />
		<cfset local.debug = false />

		<cfset _taffyRequest.metrics = {} />
		<cfset m = _taffyRequest.metrics />
		<cfset m.init = getTickCount() />

		<cfif not structKeyExists(url, application._taffy.settings.debugKey)>
			<cfsetting showdebugoutput="false" />
		</cfif>

		<cfif
			NOT structKeyExists(url,application._taffy.settings.endpointURLParam)
			AND NOT structKeyExists(form,application._taffy.settings.endpointURLParam)
			AND len(cgi.path_info) lte 1
			AND listFindNoCase(cgi.script_name, "index.cfm", "/") EQ listLen(cgi.script_name, "/")>
			<cfif NOT application._taffy.settings.disableDashboard>
				<cfif StructKeyExists( URL, "docs" )>
					<cfinclude template="#application._taffy.settings.docsPath#" />
				<cfelse>
					<cfinclude template="../dashboard/dashboard.cfm" />
				</cfif>
				<cfabort />
			<cfelse>
				<cfif len(application._taffy.settings.disabledDashboardRedirect)>
					<cflocation url="#application._taffy.settings.disabledDashboardRedirect#" addtoken="false" />
					<cfabort />
				<cfelseif application._taffy.settings.showDocsWhenDashboardDisabled>
					<cfinclude template="#application._taffy.settings.docsPath#" />
					<cfabort />
				<cfelse>
					<cfset throwError(403, "Forbidden") />
				</cfif>
			</cfif>
		</cfif>

		<cfset m.beforeParse = getTickCount() />
		<cfset local.parsed = parseRequest() />
		<cfset m.afterParse = getTickCount() />
		<cfset structAppend(_taffyRequest, local.parsed) />
		<cfset m.parseTime = m.afterParse - m.beforeParse />

		<cfset local.allowVerbs = uCase(structKeyList(_taffyRequest.matchDetails.methods)) />
		<cfif (application._taffy.settings.allowCrossDomain eq true or len(application._taffy.settings.allowCrossDomain) gt 0)
				AND listFindNoCase('PUT,PATCH,DELETE,OPTIONS',_taffyRequest.verb)
				AND NOT listFind(local.allowVerbs,'OPTIONS')>
		    <cfset local.allowVerbs = listAppend(local.allowVerbs,'OPTIONS') />
		</cfif>
		<cfif structKeyExists(_taffyRequest.headers, "origin") AND (application._taffy.settings.allowCrossDomain eq true or len(application._taffy.settings.allowCrossDomain) gt 0)>
			<cfif application._taffy.settings.allowCrossDomain eq true>
				<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
			<cfelse>
				<cfset local.domains = listToArray( application._taffy.settings.allowCrossDomain, ', ;' )>
				<cfif structKeyExists(_taffyRequest.headers, "origin")>
					<cfloop from="1" to="#arrayLen( local.domains )#" index="local.i">
						<cfif lcase( rereplace( _taffyRequest.headers.origin, "(http|https):\/\/", "", "all" ) ) EQ lcase( rereplace( local.domains[ local.i ], "(http|https):\/\/", "", "all" ) ) >
							<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
							<cfheader name="Access-Control-Allow-Credentials" value="true" />
							<cfbreak>
						</cfif>
					</cfloop>
				</cfif>
			</cfif>
			<cfheader name="Access-Control-Allow-Methods" value="#local.allowVerbs#" />
			<cfif not structKeyExists(_taffyRequest.headers, "Access-Control-Request-Headers")>
				<cfheader name="Access-Control-Allow-Headers" value="Origin, Authorization, X-CSRF-Token, X-Requested-With, Content-Type, X-HTTP-Method-Override, Accept, Referrer, User-Agent" />
			<cfelse>
				<cfset local.allowedHeaders = {} />
				<cfloop list="Origin,Authorization,X-CSRF-Token,X-Requested-With,Content-Type,X-HTTP-Method-Override,Accept,Referrer,User-Agent" index="local.h">
					<cfset local.allowedHeaders[local.h] = 1 />
				</cfloop>
				<cfset local.requestedHeaders = _taffyRequest.headers['Access-Control-Request-Headers'] />
				<cfloop list="#local.requestedHeaders#" index="local.i">
					<cfset local.allowedHeaders[ local.i ] = 1 />
				</cfloop>
				<cfheader name="Access-Control-Allow-Headers" value="#structKeyList(local.allowedHeaders)#" />
			</cfif>
		</cfif>

		<cfset addHeaders(getGlobalHeaders()) />

			Now we know everything we need to know to service the request. let's service it!
		--->

		<cfset m.beforeOnTaffyRequest = getTickCount() />
		<cfset _taffyRequest.continue = onTaffyRequest(
			_taffyRequest.verb
			,_taffyRequest.matchDetails.beanName
			,_taffyRequest.requestArguments
			,_taffyRequest.returnMimeExt
			,_taffyRequest.headers
			,_taffyRequest.methodMetadata
			,local.parsed.matchDetails.srcUri
		) />
		<cfset m.afterOnTaffyRequest = getTickCount() />
		<cfset m.otrTime = m.afterOnTaffyRequest - m.beforeOnTaffyRequest />

		<cfif not structKeyExists(_taffyRequest, "continue")>
			<cfthrow
				message="Error in your onTaffyRequest method"
				detail="Your onTaffyRequest method returned no value. Expected: Return TRUE or call noData()/representationOf()."
				errorcode="400"
			/>
		</cfif>

		<cfif isObject(_taffyRequest.continue)>
			<cfset _taffyRequest.result = duplicate(_taffyRequest.continue) />
			<cfset structDelete(_taffyRequest, "continue")/>
			<cfset m.resourceTime = 0 />
		<cfelse>

			<cfif structKeyExists(_taffyRequest.requestArguments, application._taffy.settings.simulateKey) and _taffyRequest.requestArguments[application._taffy.settings.simulateKey] eq application._taffy.settings.simulatePassword>
				<cfset sampler = 'sample#_taffyRequest.method#Response' />
				<cfif structKeyExists(_taffyRequest.matchDetails.metadata, sampler)>
					<cfinvoke
						component="#application._taffy.factory.getBean(_taffyRequest.matchDetails.beanName)#"
						method="#sampler#"
						returnvariable="_taffyRequest.result"
					/>
					<cfset _taffyRequest.result = rep(_taffyRequest.result) />
				<cfelse>
					<cfset _taffyRequest.result = noData().withStatus(400, "No Sample Response Available") />
				</cfif>
			<cfelse>
				<cfif structKeyExists(_taffyRequest.matchDetails.methods, _taffyRequest.verb)>
					<cfset m.cacheCheckTime = getTickCount() />
					<cfset local.cacheKey = getCacheKey(
						_taffyRequest.matchDetails.beanName
						,_taffyRequest.requestArguments
						,local.parsed.matchDetails.srcUri
					) />
					<cfif ucase(_taffyRequest.verb) eq "GET" and validCacheExists(local.cacheKey)>
						<cfset m.cacheCheckTime = getTickCount() - m.cacheCheckTime />
						<cfset m.cacheGetTime = getTickCount() />
						<cfset _taffyRequest.result = getCachedResponse(local.cacheKey) />
						<cfset m.cacheGetTime = m.cacheGetTime - getTickCount() />
					<cfelse>
						<cfif ucase(_taffyRequest.verb) eq "GET">
							<cfset m.cacheCheckTime = getTickCount() - m.cacheCheckTime />
						<cfelse>
							<cfset structDelete(m, "cacheCheckTime") />
						</cfif>
						<cfset m.beforeResource = getTickCount() />
						<cfinvoke
							component="#application._taffy.factory.getBean(_taffyRequest.matchDetails.beanName)#"
							method="#_taffyRequest.method#"
							argumentcollection="#_taffyRequest.requestArguments#"
							returnvariable="_taffyRequest.result"
						/>
						<cfset m.afterResource = getTickCount() />
						<cfset m.resourceTime = m.afterResource - m.beforeResource />
						<cfif !isDefined("_taffyRequest.result")>
							<cfthrow
								message="Resource did not return a value"
								detail="The resource is expected to return a call to rep()/representationOf() or noData(). It appears there was no return at all."
								errorcode="taffy.resources.ResourceReturnsNothing"
							/>
						</cfif>
						This way we can directly return the object instead of a serializer from resource actions. --->
						<cfif !isInstanceOf(_taffyRequest.result, "taffy.core.baseSerializer")>
							<cfset _taffyRequest.result = rep(_taffyRequest.result) />
						</cfif>
						<cfif ucase(_taffyRequest.verb) eq "GET" and structKeyExists(local, "cacheKey")>
							<cfset m.cacheSaveStart = getTickCount() />
							<cfset setCachedResponse(local.cacheKey, _taffyRequest.result) />
							<cfset m.cacheSaveTime = getTickCount() - m.cacheSaveStart />
						</cfif>
					</cfif>
				<cfelseif NOT listFind(local.allowVerbs,_taffyRequest.verb)>
					<cfheader name="ALLOW" value="#local.allowVerbs#" />
					<cfset throwError(405, "Method Not Allowed") />
				<cfelse>
					<cfset _taffyRequest.resultHeaders = structNew() />
					<cfset _taffyRequest.statusArgs = structNew() />
					<cfset _taffyRequest.statusArgs.statusCode = 200 />
					<cfset _taffyRequest.statusArgs.statusText = 'OK' />
				</cfif>
			</cfif>

		</cfif>
		<cfif not mimeSupported(_taffyRequest.returnMimeExt)>
			<cfset throwError(400, "Requested format not available (#_taffyRequest.returnMimeExt#)") />
		</cfif>

		<cfif structKeyExists(_taffyRequest,'result')>
			<cfset _taffyRequest.statusArgs = structNew() />
			<cfset _taffyRequest.statusArgs.statusCode = _taffyRequest.result.getStatus() />
			<cfset _taffyRequest.statusArgs.statusText = _taffyRequest.result.getStatusText() />
			<cfinvoke
				component="#_taffyRequest.result#"
				method="getHeaders"
				returnvariable="_taffyRequest.resultHeaders"
			/>
		</cfif>

		<cfsetting enablecfoutputonly="true" />
		<cfcontent reset="true" type="#getReturnMimeAsHeader(_taffyRequest.returnMimeExt)#; charset=utf-8" />
		<cfheader statuscode="#_taffyRequest.statusArgs.statusCode#" statustext="#_taffyRequest.statusArgs.statusText#" />

		<cfset addHeaders(_taffyRequest.resultHeaders) />

		<cfheader name="ALLOW" value="#local.allowVerbs#" />

		<cfheader name="X-TIME-IN-PARSE" value="#m.parseTime#" />
		<cfheader name="X-TIME-IN-ONTAFFYREQUEST" value="#m.otrTime#" />
		<cfif structKeyExists(m, "resourceTime")>
			<cfheader name="X-TIME-IN-RESOURCE" value="#m.resourceTime#" />
		</cfif>
		<cfif structKeyExists(m, "cacheCheckTime")>
			<cfheader name="X-TIME-IN-CACHE-CHECK" value="#m.cacheCheckTime#" />
		</cfif>
		<cfif structKeyExists(m, "cacheGetTime")>
			<cfheader name="X-TIME-IN-CACHE-GET" value="#m.cacheGetTime#" />
		</cfif>
		<cfif structKeyExists(m, "cacheSaveTime")>
			<cfheader name="X-TIME-IN-CACHE-SAVE" value="#m.cacheSaveTime#" />
		</cfif>

		<cfif application._taffy.settings.exposeHeaders>
			<cfset local.exposeHeaderList = structKeyList(_taffyRequest.resultHeaders) />
			<cfset local.exposeHeaderValue = "" />
			<cfif application._taffy.settings.useEtags and _taffyRequest.verb eq "GET" and _taffyRequest.result.getType() eq "textual">
				<cfset local.exposeHeaderList = listAppend(local.exposeHeaderList, "Etag") />
			</cfif>
			<cfloop list="#local.exposeHeaderList#" index="local.exposeHeader">
				<cfif not listFindNoCase("Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma", local.exposeHeader)>
					<cfset local.exposeHeaderValue = listAppend(local.exposeHeaderValue, local.exposeHeader) />
				</cfif>
			</cfloop>
			<cfif listLen(local.exposeHeaderValue) gt 0>
				<cfheader name="Access-Control-Expose-Headers" value="#local.exposeHeaderValue#" />
			</cfif>
		</cfif>

		<cfif structKeyExists(_taffyRequest,'result')>
			<cfset _taffyRequest.resultType = _taffyRequest.result.getType() />
			<cfset local.resultSerialized = '' />

			<cfif _taffyRequest.resultType eq "textual">
				<cfset _taffyRequest.metrics.beforeSerialize = getTickCount() />
				<cfinvoke
					component="#_taffyRequest.result#"
					method="getAs#_taffyRequest.returnMimeExt#"
					returnvariable="_taffyRequest.resultSerialized"
				/>
				<cfset _taffyRequest.metrics.afterSerialize = getTickCount() />
				<cfset m.serializeTime = m.afterSerialize - m.beforeSerialize />
				<cfheader name="X-TIME-IN-SERIALIZE" value="#m.serializeTime#" />

				<cfif structKeyExists(_taffyRequest, "jsonpCallback")>
					<cfset _taffyRequest.resultSerialized = _taffyRequest.jsonpCallback & "(" & _taffyRequest.resultSerialized & ");" />
				</cfif>

				<cfif application._taffy.settings.useEtags and _taffyRequest.verb eq "GET">
					<cfif structKeyExists(server, "lucee")>
						<cfset _taffyRequest.serverEtag = '"' & hash(_taffyRequest.resultSerialized) & '"' />
					<cfelse>
						<cfset _taffyRequest.serverEtag = '"' & _taffyRequest.result.getData().hashCode() & '"' />
					</cfif>
					<cfif structKeyExists(_taffyRequest.headers, "If-None-Match")>
						<cfset _taffyRequest.clientEtag = _taffyRequest.headers['If-None-Match'] />

						<cfif len(_taffyRequest.clientEtag) gt 0 and _taffyRequest.clientEtag eq _taffyRequest.serverEtag>
							<cfheader statuscode="304" statustext="Not Modified" />
							<cfcontent reset="true" type="#application._taffy.settings.mimeExtensions[_taffyRequest.returnMimeExt]#; charset=utf-8" />
							<cfreturn true />
						<cfelse>
							<cfheader name="Etag" value="#_taffyRequest.serverEtag#" />
						</cfif>
					<cfelse>
						<cfheader name="Etag" value="#_taffyRequest.serverEtag#" />
					</cfif>
				</cfif>

				<cfset m.done = getTickCount() />
				<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.serializeTime />
				<cfif structKeyExists(m, "resourceTime")>
					<cfset m.taffyTime -= m.resourceTime />
				</cfif>
				<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />

				<cfcontent reset="true" type="#application._taffy.settings.mimeExtensions[_taffyRequest.returnMimeExt]#; charset=utf-8" />
				<cfif _taffyRequest.resultSerialized neq ('"' & '"')>
					<cfset local.resultSerialized = _taffyRequest.resultSerialized />
				</cfif>
				<cfif structKeyExists(url, application._taffy.settings.debugKey)>
					<cfset local.debug = true />
				</cfif>

			<cfelseif _taffyRequest.resultType eq "filename">
				<cfset m.done = getTickCount() />
				<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
				<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
				<cfcontent reset="true" file="#_taffyRequest.result.getFileName()#" type="#_taffyRequest.result.getFileMime()#" deletefile="#_taffyRequest.result.getDeleteFile()#" />

			<cfelseif _taffyRequest.resultType eq "filedata">
				<cfset m.done = getTickCount() />
				<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
				<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
				<cfcontent reset="true" variable="#_taffyRequest.result.getFileData()#" type="#_taffyRequest.result.getFileMime()#" />

			<cfelseif _taffyRequest.resultType eq "imagedata">
				<cfset m.done = getTickCount() />
				<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
				<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
				<cfcontent reset="true" variable="#_taffyRequest.result.getImageData()#" type="#_taffyRequest.result.getFileMime()#" />

			</cfif>
		</cfif>

		<cfset local.resultSerialized = "" />
		<cfif structKeyExists( _taffyRequest, "resultSerialized" )>
			<cfset local.resultSerialized = _taffyRequest.resultSerialized />
		</cfif>

		<cfset local.result = StructNew() />
		<cfif structKeyExists( _taffyRequest, "result" )>
			<cfset local.result = _taffyRequest.result.getData() />
		</cfif>

		<cfset m.beforeOnTaffyRequestEnd = getTickCount() />
		<cfset onTaffyRequestEnd(
			_taffyRequest.verb
			,_taffyRequest.matchDetails.beanName
			,_taffyRequest.requestArguments
			,_taffyRequest.returnMimeExt
			,_taffyRequest.headers
			,_taffyRequest.methodMetadata
			,local.parsed.matchDetails.srcUri
			,local.resultSerialized
			,local.result
			,_taffyRequest.statusArgs.statusCode
			) />
		<cfset m.otreTime = getTickCount() - m.beforeOnTaffyRequestEnd />
		<cfheader name="X-TIME-IN-ONTAFFYREQUESTEND" value="#m.otreTime#" />

		<cfif len(trim(local.resultSerialized))>
			<cfoutput>#local.resultSerialized#</cfoutput>
		</cfif>
		<cfif local.debug>
			<cfoutput><h3>Request Details:</h3><cfdump var="#_taffyRequest#"></cfoutput>
		</cfif>

		<cfreturn true />
	</cffunction>	
	
	
</cfcomponent>

v1/index.cfm

this file index.cfm is a placeholder for Tomcat, it's content doesn't matter

v1/lib/JsonSerializer.cfc

component
	output = false
	hint = "I provide a way to serialize complex ColdFusion data values as case-sensitive JavaScript Object Notation (JSON) strings."
	{
	//2024-06-27 modified by msyu: added server timezone 
	
	// I return the initialized component.
	public any function init() {

		// Every key is added to the full key list - used for one-time key serialization.
		fullKeyList = {};

		// Every key is added to the hint list so that we don't have to switch on the lists.
		fullHintList = {};

		// These key lists determine special data serialization.
		stringKeyList = {};
		booleanKeyList = {};
		integerKeyList = {};
		floatKeyList = {};
		dateKeyList = {};

		// These keys will NOT be used in serialization (ie. the key/value pairs will not be
		// added to the serialized output).
		blockedKeyList = {};

		// Return the initialized component.
		return( this );

	}


	// ---
	// PUBLIC METHODS.
	// ---


	// I define the given key without a type. This is here to provide key-casing without caring 
	// about why type of data conversion takes place. Returns serializer.
	public any function asAny( required string key ) {

		return( defineKey( fullKeyList, key, "any" ) );

	}


	// I define the given key as a boolean. Returns serializer.
	public any function asBoolean( required string key ) {

		return( defineKey( booleanKeyList, key, "boolean" ) );

	}


	// I define the given key as a date. Returns serializer.
	public any function asDate( required string key ) {

		return( defineKey( dateKeyList, key, "date" ) );

	}


	// I define the given key as a float / decimal. Returns serializer.
	public any function asFloat( required string key ) {

		return( defineKey( floatKeyList, key, "float" ) );

	}


	// I define the given key as an integer. Returns serializer.
	public any function asInteger( required string key ) {

		return( defineKey( integerKeyList, key, "integer" ) );

	}


	// I define the given key as a string. Returns serializer.
	public any function asString( required string key ) {

		return( defineKey( stringKeyList, key, "string" ) );

	}


	// I define the key as one that should not be included in the serialized response.
	public any function exclude( required string key ) {

		blockedKeyList[ key ] = true;

		return( this );

	}


	/**
	* I serialize the given input as JavaScript Object Notation (JSON) using the case-sensitive
	* values defined in the key-list.
	* 
	* @output false
	*/
	public string function serialize( required any input ) {

		// Write the serialized value to the output buffer.
		savecontent variable = "local.serializedInput" {

			serializeInput( input, "any" );

		}

		return( serializedInput );

	}


	// ---
	// PRIVATE METHODS.
	// ---


	// I define the given key within the given key list.
	private any function defineKey(
		required struct keyList,
		required string key,
		required string hint
		) {

		if ( structKeyExists( fullKeyList, key ) ) {

			throw( 
				type = "DuplicateKey",
				message = "The key [#key#] has already been defined within the serializer.",
				detail = "The current key list is: #structKeyList( fullKeyList, ', ' )#"
			);

		}

		// Add to the appropriate data-type lists. This one is used for existence checking.
		keyList[ key ] = key;

		// Add all keys to the full key list as well. This one is used to store the serialization
		// of the key so that it doesn't have to be recalculated each time the object is serialized.
		fullKeyList[ key ] = serializeString( key );

		// If we have a specific type, then add the hint to the full hint list as well. This will 
		// allow us to quickly look up the pass-through data type hint during serialization.
		// --
		// NOTE: The reason we don't want to pass through "any" is that we want parent types to be
		// able to "fall through" during the object traversal. If we added "any" to the type list, 
		// then it would always overwrite the parent data type.
		if ( hint != "any" ) {

			fullHintList[ key ] = hint;
			
		}

		// Return this reference for method chaining.
		return( this );

	}


	// I walk the given object, writing the serialized value to the output (which is expected to 
	// be a content buffer).
	// ---
	// NOTE: THIS METHOD IS HUGE - this is on purpose. Since serialization is a rather intense 
	// process, I am trying to cut out as much overhead as possible. In this case, we're cutting 
	// out extra stack space by inlining and duplicating a lot of functionality. This is being done 
	// at the COST of clarity and non-repetitive code.
	private void function serializeInput(
		required any input,
		required string hint
		) {

		// Serialize the data base on the type of input. We are organizing this in terms of the 
		// most commonly-used values first. The anticipation is that the vast majority of data 
		// types will be simple values. 
		if ( isSimpleValue( input ) ) {

			if ( ( hint == "string" ) || ( hint == "any" ) ) {

				// If the string appears to be numeric, then we have to prefix it to make sure 
				// ColdFusion doesn't accidentally convert it to a number.
				if ( isNumeric( input ) ) {

					writeOutput( """" & input & """" );

				} else {

					serializeInputString( input );

				}

			} else if ( ( hint == "boolean" ) && isBoolean( input ) ) {

				writeOutput( input ? "true" : "false" );

			} else if ( ( ( hint == "integer" ) || ( hint == "float" ) ) && isNumeric( input ) ) {

				writeOutput( input );

			} else if ( ( ( hint == "integer" ) || ( hint == "float" ) ) && isBoolean( input ) ) {

				writeOutput( input ? "1" : "0" );

			} else if ( ( hint == "date" ) && ( isDate( input ) || isNumericDate( input ) ) ) {

				// Write the date in ISO 8601 time string format. We're going to assume that the 
				// date is already in the desired timezone. 
				///writeOutput( """" & dateFormat( input, "yyyy-mm-dd" ) & "T" & timeFormat( input, "HH:mm:ss.l" ) & "Z""" );
				//2024-06-27 modified by msyu: added server timezone 
				writeOutput( """" & dateFormat( input, "yyyy-mm-dd" ) & "T" & timeFormat( input, "HH:mm:ss.lXX" ) & """" );

			} else {

				serializeInputString( input );

			}

			return;

		} // END: isSimpleValue().


		// I'm expecting the struct to be the next most common data type since it will likely be 
		// the container for the majority of data values.
		if ( isStruct( input ) ) {

			writeOutput( "{" );

			var isFirst = true;

			for ( var key in input ) {

				// Skip any black-listed keys.
				if ( structKeyExists( blockedKeyList, key ) ) {

					continue;

				}

				// Handle the item delimiter.
				if ( isFirst ) {

					isFirst = false;

				} else {

					writeOutput( "," );

				}

				// Ensure that the given key can be referenced on the full-key list. This way,
				// the subsequent logic will be easier.
				if ( ! structKeyExists( fullKeyList, key ) ) {

					asAny( lcase( key ) );

				}

				writeOutput( fullKeyList[ key ] & ":" );

				// Pass in the most appropriate data-type hint based on the parent key.
				if ( structKeyExists( fullHintList, key ) ) {

					serializeInput( input[ key ], fullHintList[ key ] );

				// If the given key is unknown, just pass through the most recent hint as 
				// it may be defining the type for an entire structure.
				} else {

					serializeInput( input[ key ], hint );

				}

			}

			writeOutput( "}" );

			return;

		} // END: isStruct().


		if ( isArray( input ) ) {

			writeOutput( "[" );

			var isFirst = true;

			// Handle the item delimiter.
			for ( var value in input ) {

				if ( isFirst ) {

					isFirst = false;

				} else {

					writeOutput( "," );

				}

				// Since we don't have a key to go off of, pass-through the most recent hint.
				serializeInput( value, hint );

			}

			writeOutput( "]" );

			return;

		} // END: isArray().


		// When we serialize a query, we're going to treat it like an array of structs.
		if ( isQuery( input ) ) {

			var keys = listToArray( input.columnList );

			// Make sure each column is defined as a known key - makes the subsequent logic easier.
			for ( var key in keys ) {

				if ( ! structKeyExists( fullKeyList, key ) ) {

					asAny( lcase( key ) );

				}

			}

			writeOutput( "[" );

			// Serialize each row of the query as a struct.
			for ( var i = 1 ; i <= input.recordCount ; i++ ) {

				// Handle the row delimiter.
				if ( i > 1 ) {

					writeOutput( "," );

				}

				writeOutput( "{" );

				var isFirst = true;

				for ( var key in keys ) {

					// Skip any black-listed keys.
					if ( structKeyExists( blockedKeyList, key ) ) {

						continue;

					}

					// Handle the item delimiter (in the current row).
					if ( isFirst ) {

						isFirst = false;

					} else {

						writeOutput( "," );

					}

					writeOutput( fullKeyList[ key ] & ":" );

					// Pass in the most appropriate data-type hint based on the parent key.
					if ( structKeyExists( fullHintList, key ) ) {

						serializeInput( input[ key ][ i ], fullHintList[ key ] );

					// If the given key is unknown, just pass through the most recent hint as 
					// it may be defining the type for an entire structure.
					} else {

						serializeInput( input[ key ][ i ], hint );

					}

				} // END: Key list.

				writeOutput( "}" );

			} // END: Row list.

			writeOutput( "]" );
			
			return;

		} // END: isQuery().


		// If we made it this far, we were given a data type that we're not actively supporting.
		// As such, we just have to hand this off to the native serializer.
		writeOutput( serializeJson( input ) );

	}


	/**
	* I serialize and write the given string to the current output context, escaping all appropriate
	* characters for the JSON specification.
	* 
	* NOTE: We are using this manual-encoding process rather than the built-in serializeJson() function
	* as there is a rather nasty bug that corrupts certain patterns in the output. Read more:
	* 
	* http://www.bennadel.com/blog/2842-serializejson-and-the-input-and-output-encodings-are-not-same-errors-in-coldfusion.htm
	* 
	* @input I am the string being serialized.
	*/
	private void function serializeInputString( required string input ) {

		// While this may not be technically needed, this will ensure that we are not using any 
		// "undocumented features" of the language. If we explicitly cast to  a Java string, and
		// something goes wrong due to odd type-casting, it's a ColdFusion bug, at that point, not 
		// a logic error ;)
		input = javaCast( "string", input );

		var length = input.length();

		writeOutput( """" );

		for ( var i = 1 ; i <= length ; i++ ) {

			var charCode = input.codePointAt( javaCast( "int", i - 1 ) );

			// Check for the most common case first (normal characters).
			if ( 
				( charCode >= 32 ) &&
				( charCode != 34 ) &&
				( charCode != 47 ) &&
				( charCode != 92 ) &&
				( charCode != 8232 ) &&
				( charCode != 8233 )
				) {

				writeOutput( chr( charCode ) );

			// Check for the special cases next (control characters, characters that
			// need to be escaped, and characters that need to be encoded nicely).
			} else if ( charCode == 8 ) {

				writeOutput( "\b" );
				
			} else if ( charCode == 9 ) {

				writeOutput( "\t" );

			} else if ( charCode == 10 ) {

				writeOutput( "\n" );

			} else if ( charCode == 12 ) {

				writeOutput( "\f" );

			} else if ( charCode == 13 ) {

				writeOutput( "\r" );

			} else if ( 
				( charCode < 32 ) ||
				( charCode == 8232 ) ||
				( charCode == 8233 )
				) {

				// For Unicode hex values, we need to enforce a 4-digit code.
				writeOutput( "\u" & right( ( "000" & formatBaseN( charCode, 16 ) ), 4 ) );

			} else if ( charCode == 34 ) {

				writeOutput( "\""" );

			} else if ( charCode == 47 ) {

				writeOutput( "\/" );

			} else if ( charCode == 92 ) {

				writeOutput( "\\" );

			}

		}

		writeOutput( """" );

	}


	/**
	* I serialize and return the given string, escaping all appropriate characters for the 
	* JSON specification.
	* 
	* @input I am the string being serialized.
	* @output false
	*/
	private string function serializeString( required string input ) {

		savecontent variable = "local.json" {

			serializeInputString( input );

		}

		return( json );

	}

}

v1/lib/TokenGenerator.cfc

component
	output = false
	hint = "Генерирует случайные токены с помощью Java SecureRandom."
	{

	public any function init() {
		generator = createObject( "java", "java.security.SecureRandom" )
			.getInstance(
				javaCast( "string", "SHA1PRNG" ),
				javaCast( "string", "SUN" )
			)
		;
		generator.nextBytes( charsetDecode( " ", "utf-8" ) );
		reseedAt = getNextReseedAt();

		return( this );
	}

	public string function nextToken( numeric byteCount = 32 ) {
		if ( now() >= reseedAt ) {
			lock
				name = "TokenGenerator.reseedCheck"
				type = "exclusive"
				timeout = 1
				throwOnTimeout = false
				{
				if ( now() >= reseedAt ) {

					reseedAt = getNextReseedAt();
					generator.setSeed( generator.generateSeed( javaCast( "int", 32 ) ) );
				}
			}
		}

		var byteBuffer = charsetDecode( repeatString( " ", byteCount ), "utf-8" );

		generator.nextBytes( byteBuffer );

		return( encodeBytes( byteBuffer ) );
	}

	private string function encodeBytes( required binary bytes ) {

		var token = binaryEncode( bytes, "base64" );
		token = replace( token, "+", "a", "all" );
		token = replace( token, "/", "b", "all" );
		token = replace( token, "=", "c", "all" );

		return( token );
	}

	private date function getNextReseedAt() {

		return( dateAdd( "h", 1, now() ) );
	}

}

v1/lib/encodingUtils.cfc

component {

    public any function init() {
        variables.utcBaseDate = createObject( 'java', 'java.util.Date' ).init( javacast( 'int', 0 ) );
        variables.ECParameterSpecCache = { };
    }

    function convertDateToUnixTimestamp( required date dateToConvert ) {
        return dateDiff( 's', utcBaseDate, parseDateTime( dateToConvert ) );
    }

    function convertUnixTimestampToDate( required numeric timestamp ) {
        return dateAdd( 's', timestamp, utcBaseDate );
    }

    function base64UrlToBinary( base64url ) {
        var base64 = base64url.replace( '-', '+', 'all' ).replace( '_', '/', 'all' );
        var padded = base64 & repeatString( '=', 4 - ( len( base64 ) % 4 ) );
        return binaryDecode( padded, 'base64' );
    }

    function binaryToBase64Url( source ) {
        return binaryEncode( source, 'base64' )
            .replace( '+', '-', 'all' )
            .replace( '/', '_', 'all' )
            .replace( '=', '', 'all' );
    }

    /**
    * The INTEGER encoding for DER consists of a 02 tag a length encoding of the value
    * and then a signed, minimum sized, big endian encoding of the encoded number.
    *
    * - https://stackoverflow.com/questions/54718741/how-to-der-encode-an-ecdsa-signature
    */
    function derEncodeIntegerBytes( byteArray ) {
        // first remove any padding
        for ( var i = 1; i <= arrayLen( byteArray ); i++ ) {
            if ( byteArray[ i ] != 0 ) break;
        }
        var unpadded = arraySlice( byteArray, i );

        // add sign if negative
        if ( unpadded[ 1 ] < 0 ) {
            unpadded.prepend( 0 );
        }

        // if len > 127 the length encoding will be wrong, but that won't happen for the supported signature sizes
        var derEncoded = [ 2, unpadded.len() ];

        derEncoded.append( unpadded, true );

        return derEncoded;
    }


    /**
    * The SEQUENCE encoding is simply a tag set to the byte value 30, the
    * length encoding and then the concatenation of the two INTEGER
    * structures.
    *
    * https://stackoverflow.com/questions/54718741/how-to-der-encode-an-ecdsa-signature
    *
    * Also see:
    * https://crypto.stackexchange.com/questions/57731/ecdsa-signature-rs-to-asn1-der-encoding-question
    */
    function convertP1363ToDER( signature ) {
        var split = len( signature ) / 2;
        var r = derEncodeIntegerBytes( arraySlice( signature, 1, split ) );
        var s = derEncodeIntegerBytes( arraySlice( signature, split + 1, split ) );

        var DERSignature = [ 48 ];

        var length = r.len() + s.len();

        if ( length > 255 ) {
            throw(
                type = 'jwtcfml.InvalidSignature',
                message = 'Invalid P1363 key.',
                detail = 'The P1363 signature is too long.'
            );
        }

        /*
            The length is simply a single byte if it is smaller than 128 (or hex 80)
            of the size. If it is larger then it is two byte: one byte set to 81,
            which indicates that one length byte will follow, and one byte
            containing the actual value.

            https://stackoverflow.com/questions/54718741/how-to-der-encode-an-ecdsa-signature
        */
        if ( length > 127 ) {
            DERSignature.append( -127 );
            length -= 256;
        }
        DERSignature.append( length );

        DERSignature.append( r, true );
        DERSignature.append( s, true );

        return javacast( 'byte[]', DERSignature );
    }

    function convertDERtoP1363( required any signature, required string algorithm ) {
        // extract the two integers from the DER signature
        // assuming a 02 tag byte followed by a single length byte since we should not see
        // anything larger in the supported algorithms
        var start = 3;
        while ( signature[ start ] != 2 ) start++;
        var r = arraySlice( signature, start + 2, signature[ start + 1 ] );
        var s = arraySlice( signature, start + 2 + r.len() + 2 );

        if ( r[ 1 ] == 0 ) r = arraySlice( r, 2 );
        if ( s[ 1 ] == 0 ) s = arraySlice( s, 2 );

        var lengthMap = {
            ES256: 32,
            ES384: 48,
            ES512: 64
        };

        var P1363Signature = [ ];

        for ( var i = 1; i <= lengthMap[ algorithm ] - r.len(); i++ ) P1363Signature.append( 0 );
        P1363Signature.append( r, true );

        for ( var i = 1; i <= lengthMap[ algorithm ] - s.len(); i++ ) P1363Signature.append( 0 );
        P1363Signature.append( s, true );

        return javacast( 'byte[]', P1363Signature );
    }

    function parsePEMEncodedKey( required string pemKey ) {
        if ( reFind( '^-----BEGIN (RSA|EC) (PARAMETERS|PRIVATE)', pemKey ) ) {
            throw(
                type = 'jwtcfml.InvalidPrivateKey',
                message = 'Invalid private key format.',
                detail = 'Please encode your private key in PKCS8 format, e.g.: `openssl pkcs8 -topk8 -nocrypt -in privatekey.pem -out privatekey.pk8'
            )
        }

        var binaryKey = binaryDecode(
            trim( pemKey ).reReplace( '-----[A-Z\s]+-----', '', 'all' ).reReplace( '[\r\n]', '', 'all' ),
            'base64'
        );

        if ( find( '-----BEGIN CERTIFICATE-----', pemKey ) ) {
            var bis = createObject( 'java', 'java.io.ByteArrayInputStream' ).init( binaryKey );
            return createObject( 'java', 'java.security.cert.CertificateFactory' )
                .getInstance( 'X.509' )
                .generateCertificate( bis )
                .getPublicKey();
        }

        if ( find( '-----BEGIN PUBLIC KEY-----', pemKey ) ) {
            var publicKeySpec = createObject( 'java', 'java.security.spec.X509EncodedKeySpec' ).init( binaryKey );
            try {
                return createObject( 'java', 'java.security.KeyFactory' )
                    .getInstance( 'RSA' )
                    .generatePublic( publicKeySpec );
            } catch ( any e ) {
            }
            try {
                return createObject( 'java', 'java.security.KeyFactory' )
                    .getInstance( 'EC' )
                    .generatePublic( publicKeySpec );
            } catch ( any e ) {
            }
        }

        if ( find( '-----BEGIN PRIVATE KEY-----', pemKey ) ) {
            var privateKeySpec = createObject( 'java', 'java.security.spec.PKCS8EncodedKeySpec' ).init( binaryKey );
            try {
                return createObject( 'java', 'java.security.KeyFactory' )
                    .getInstance( 'RSA' )
                    .generatePrivate( privateKeySpec );
            } catch ( any e ) {
            }
            try {
                return createObject( 'java', 'java.security.KeyFactory' )
                    .getInstance( 'EC' )
                    .generatePrivate( privateKeySpec );
            } catch ( any e ) {
            }
        }

        throw(
            type = 'jwtcfml.InvalidPEMKey',
            message = 'Invalid PEM key.',
            detail = 'Please ensure you are using an RSA or EC public or private key or certificate.'
        )
    }

    function parseJWK( required struct jwk ) {
        if ( jwk.kty == 'RSA' ) {
            if ( jwk.keyExists( 'd' ) ) {
                try {
                    var bigInts = bigIntegers( jwk, [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ] );
                    var keySpec = createObject( 'java', 'java.security.spec.RSAPrivateCrtKeySpec' ).init(
                        bigInts.n,
                        bigInts.e,
                        bigInts.d,
                        bigInts.p,
                        bigInts.q,
                        bigInts.dp,
                        bigInts.dq,
                        bigInts.qi
                    );
                    var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'RSA' );
                    return kf.generatePrivate( keySpec );
                } catch ( any e ) {
                }

                try {
                    var bigInts = bigIntegers( jwk, [ 'n', 'd' ] );
                    var keySpec = createObject( 'java', 'java.security.spec.RSAPrivateKeySpec' ).init(
                        bigInts.n,
                        bigInts.d
                    );
                    var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'RSA' );
                    return kf.generatePrivate( keySpec );
                } catch ( any e ) {
                }
            } else {
                try {
                    var bigInts = bigIntegers( jwk, [ 'n', 'e' ] );
                    var ks = createObject( 'java', 'java.security.spec.RSAPublicKeySpec' ).init( bigInts.n, bigInts.e );
                    var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'RSA' );
                    return kf.generatePublic( ks );
                } catch ( any e ) {
                }
            }
        }

        if ( jwk.kty == 'EC' ) {
            var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'EC' );
            var ECParameterSpec = getECParameterSpec( jwk.crv );

            if ( jwk.keyExists( 'd' ) ) {
                var bigInts = bigIntegers( jwk, [ 'd' ] );
                var ks = createObject( 'java', 'java.security.spec.ECPrivateKeySpec' ).init(
                    bigInts.d,
                    ECParameterSpec
                );
                return kf.generatePrivate( ks );
            } else {
                var bigInts = bigIntegers( jwk, [ 'x', 'y' ] );
                var ECPoint = createObject( 'java', 'java.security.spec.ECPoint' ).init( bigInts.x, bigInts.y );
                var ks = createObject( 'java', 'java.security.spec.ECPublicKeySpec' ).init( ECPoint, ECParameterSpec );
                return kf.generatePublic( ks );
            }
        }

        throw(
            type = 'jwtcfml.InvalidJWK',
            message = 'Invalid JWK key.',
            detail = 'Please ensure you are using an valid JWK RSA or EC public or private key.'
        )
    }

    private function bigIntegers( jwk, keys ) {
        var bigInts = { };
        for ( var key in keys ) {
            bigInts[ key ] = createObject( 'java', 'java.math.BigInteger' ).init( 1, base64UrlToBinary( jwk[ key ] ) );
        }
        return bigInts;
    }

    private function getECParameterSpec( crv ) {
        if ( !variables.ECParameterSpecCache.keyExists( crv ) ) {
            var kpg = createObject( 'java', 'java.security.KeyPairGenerator' ).getInstance( 'EC' );
            var ecgp = createObject( 'java', 'java.security.spec.ECGenParameterSpec' ).init(
                'secp#crv.listLast( '-' )#r1'
            );
            kpg.initialize( ecgp );
            variables.ECParameterSpecCache[ crv ] = kpg
                .generateKeyPair()
                .getPublic()
                .getParams();
        }
        return variables.ECParameterSpecCache[ crv ];
    }

}

v1/lib/expression_parser.cfc

<cfcomponent>
	<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"/>
		
		<cfset var args=[]/>
		<cfset parse(args, tokenize(arguments.expression),0)/>
		<cfreturn computeExpression(args[1].function, args[1].args, arguments.context)/>
	</cffunction>

	<cffunction name="extractParams" returntype="any" access="public">		
		<cfargument name="expression" type="string"/>
				
		<cfset var params=[]/>
		<cfset parseForParams(params, tokenize(arguments.expression),0)/>
		<cfreturn params/>
	</cffunction>	

<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">
	<cfargument name="args" type="array"/>
	<cfargument name="tokens" type="array"/>
	<cfargument name="positionBefore" type="numeric"/>
	
	<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 "(">
					<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/>
			</cfcase>	
		</cfswitch>		
	</cfloop>
</cffunction>

<cffunction name="parseForParams" returntype="numeric" access="private">
	<cfargument name="args" type="array"/>
	<cfargument name="tokens" type="array"/>
	<cfargument name="positionBefore" type="numeric"/>
	
	<cfset var i = arguments.positionBefore/>
	<cfloop condition="++i LE arrayLen(arguments.tokens)">
		<cfswitch expression=#tokenType(arguments.tokens[i])#>
			<cfcase value="string">
			</cfcase>
			<cfcase value="variable">
				<cfset arrayAppend( arguments.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 "(">
					<cfthrow message="function syntax incorrect" detail="function #arguments.tokens[i-1]#, token='#arguments.tokens[i]#' (#arrayToList(tokens,' ')#"/>
				</cfif>					
				<cfset i = parseForParams(f.args, arguments.tokens, i)/>
			</cfcase>
			<cfcase value=")">
				<cfreturn i/>
			</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)#>
		<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"/>
	<cfset var obj = createObject("component", arguments.context.component).init(arguments.context.keys, arguments.context.params)/>
	<cfreturn invoke(obj, functionName, args)/>	
</cffunction>

</cfcomponent>

v1/lib/field.cfm

<cfsilent>
<cffunction name="passThrough"	
	returntype="any" 
	output="false"
	hint="Возвращает аргумент без изменений">	
	<cfargument name="x" type="ANY" required="true"/>
	<cfreturn #ARGUMENTS.x#/>	
</cffunction>
	
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
	<cfassociate basetag="cf_field_set" datacollection="fieldsArray"/>

	<cfparam name="ATTRIBUTES.expression" default=""/>
	<cfparam name="ATTRIBUTES.title" default=""/>
	<cfparam name="ATTRIBUTES.name" default=""/>	
	<cfparam name="ATTRIBUTES.type" default="string"/>	
	<cfparam name="ATTRIBUTES.cfSqlType" default="#getCfSqlType(ATTRIBUTES.type)#"/>
	<cfparam name="ATTRIBUTES.container" default=""/>
	<cfparam name="ATTRIBUTES.formatter" default=#passThrough#/>

	<cfif ATTRIBUTES.expression IS "">
		<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
	</cfif>

	<cfif ATTRIBUTES.name IS "">
		<cfset regex="([[:word:]]+)[[:space:]]*$"/>
		<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
		<cfif arrayLen(matches.pos) GT 1>
			<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
		</cfif>
	</cfif>
</cfif>

<cffunction name="getCfSqlType">
	<!--- Преобразует тип CF, понимаемый isValid, в CF_SQL_* --->
	<cfargument name="type"/>

		<cfswitch expression=#ARGUMENTS.type#>
			<cfcase value="string,list">
				<cfreturn "CF_SQL_VARCHAR"/>
			</cfcase>
			<cfcase value="integer">
				<cfreturn "CF_SQL_INTEGER"/>
			</cfcase>
			<cfcase value="boolean">
				<cfreturn "CF_SQL_BIT"/>
			</cfcase>
			<cfcase value="numeric">
				<cfreturn "CF_SQL_NUMERIC"/>
			</cfcase>
			<cfcase value="date,time">
				<cfreturn "CF_SQL_TIMESTAMP"/>
			</cfcase>
			<cfdefaultcase>
				<cfthrow type="custom" message="Unsupported type" detail="Type #ARGUMENTS.type# not supported"/>
			</cfdefaultcase>
		</cfswitch>

</cffunction>
</cfsilent>

v1/lib/field_set.cfm

<cfsilent></cfsilent>
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую и экспортирует Map со структурами expression+title с ключом, соответствующим имени колонки в селекте. --->

	<cfset var local={}/>
	<cfif thisTag.executionMode is "end">	
	
		<cfparam name="ATTRIBUTES.titleMapOut" default=""/>	
		<cfparam name="ATTRIBUTES.lengthOut" default=""/>
		<cfparam name="ATTRIBUTES.listOut" default=""/><!---возвращаемый	список полей через запятую для селекта. Если не задан (чаще всего так), этот список возвращается в виде контента тега --->
		<cfparam name="ATTRIBUTES.nameListOut" default=""/><!---возвращаемый	список имен (алиасов) через запятую--->
		<cfparam name="ATTRIBUTES.listOutKeepContent" default=false/><!--- указывает listOut не очищать контент --->
		<cfparam name="ATTRIBUTES.fieldsToInclude" default=""/><!--- список нужных полей, если не пустой - выбрасывать все поля, кроме тех, что в списке --->

		<cfparam name="thisTag.fieldsArray" type="array"/><!--- Вложенные теги field отдают сюда свои данные --->
		
		<cfset titleMap=structNew("linked")/><!---railo/lucee syntax--->
		<cfset local.expressionList=""/>
		<cfset local.nameList=""/>
		
		<cfset i=0/>
		<cfloop array=#thisTag.fieldsArray# index="field">
			<cfif len(#field.name#) EQ 0 
				OR listLen(ATTRIBUTES.fieldsToInclude) EQ 0 
				OR listFindNoCase(ATTRIBUTES.fieldsToInclude,field.name)> 
				<cfset i=i+1/>			
				<cfset local.expressionList=listAppend(#local.expressionList#, #field.expression#)/>
				<cfset local.nameList=listAppend(#local.nameList#, #field.name#)/>
			
				<cfif len(#field.name#)>					
					<cfset structInsert(#titleMap#, #field.name#, structNew())/>
					<cfset "titleMap.#field.name#.ordinal"=#i#/>
					<cfset "titleMap.#field.name#.title"=#field.title#/>
					<cfset "titleMap.#field.name#.type"=#field.type#/>
					<cfset "titleMap.#field.name#.cfSqlType"=#field.cfSqlType#/>
					<cfset "titleMap.#field.name#.container"=#field.container#/>
					<cfset "titleMap.#field.name#.formatter"=#field.formatter#/>
				</cfif>
			</cfif>
		</cfloop>

		<cfif len(ATTRIBUTES.lengthOut)>
			<cfset "CALLER.#ATTRIBUTES.lengthOut#"= i/>
		</cfif>
		
		<cfif len(ATTRIBUTES.titleMapOut)>
			<cfset "CALLER.#ATTRIBUTES.titleMapOut#"=#titleMap#/>
		</cfif>		
		
		<cfif len(ATTRIBUTES.nameListOut)>
			<cfset "CALLER.#ATTRIBUTES.nameListOut#"=#local.nameList#/>
		</cfif>
		
		<cfif len(ATTRIBUTES.listOut)>
			<cfset "CALLER.#ATTRIBUTES.listOut#"=local.expressionList/>
			<cfif ATTRIBUTES.listOutKeepContent>
				<cfset thisTag.generatedContent=preserveSingleQuotes(local.expressionList)/>
			<cfelse>
				<cfset thisTag.generatedContent=""/>
			</cfif>
		<cfelse>
			<cfset thisTag.generatedContent=preserveSingleQuotes(local.expressionList)/>
		</cfif>
		
	</cfif>

v1/lib/filter_build.cfm

<!--- Формирует строку фильтра для запроса --->

<cfparam name="ATTRIBUTES.filter" type="array"/>
<cfloop array=#ATTRIBUTES.filter# index="fltr"><!--- неинтуитивный синтаксис, в индексе не индекс массива, а значение --->
	<cfsilent>
		<cfparam name="fltr.ftype"/>	
		<cfparam name="fltr.field"/>
		<cfparam name="fltr.compare" default="EQ"/> 
		<cfparam name="fltr.val" default=""/>
		<cfparam name="fltr.prefix" default=""/>
		<cfparam name="fltr.suffix" default=""/>
		<cfparam name="fltr.list" default="No"/>		
	</cfsilent><!---	
	
---> AND <cfif structKeyExists(fltr,"expression") AND len(fltr.expression)><!---
			---><cfset len=listLen(#fltr.expression#,"?")><!---
			---><cfloop from=1 to=#len# index="i"><!---
				---><cfoutput>#listGetAt(fltr.expression,i,"?")#</cfoutput><!---
				---><cfif i LT #len#><cfqueryparam cfsqltype="#getCfSqLType(fltr.ftype)#" list="#fltr.list#" value="#fltr.prefix##fltr.val##fltr.suffix#"/></cfif><!---
			---></cfloop><!---
		---><cfelse><!---
		---><cfoutput>#fltr.field#</cfoutput><cfswitch expression=#uCase(fltr.compare)#>
		<cfcase value="EQ"> = </cfcase><!--- 
		 ---><cfcase value="NE,NEQ"> <> </cfcase><!--- 
		 ---><cfcase value="LE,LTE"> <= </cfcase><!--- 
		 ---><cfcase value="LT"> < </cfcase><!--- 
		 ---><cfcase value="GE,GTE"> >= </cfcase><!--- 
		 ---><cfcase value="GT"> > </cfcase><!--- 
		 ---><cfcase value="LIKE"> LIKE </cfcase><!--- 
		 ---><cfcase value="ILIKE"> ILIKE </cfcase><!--- 
		 ---><cfcase value="LIKE%,LIKEP"> LIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><!--- 
		 ---><cfcase value="ILIKE%,ILIKEP"> ILIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><!--- 
		 ---><cfcase value="IN"> IN (<cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# list=true value="#fltr.val#"/>)<cfcontinue/></cfcase><!--- 
		 ---><cfdefaultcase> = <cfoutput> #fltr.field#</cfoutput><cfcontinue/><!---***криво---></cfdefaultcase><!--- 
		 ---></cfswitch><cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="#fltr.val#"/><cfcontinue/>
		</cfif><!--- ILIKE зависит от PostgreSQL --->
</cfloop>
<cfexit method="exittag"/>

<cffunction name="getCfSqlType" output="No">
	<cfargument name="fieldType" default=""/>
	<cfswitch expression=#ARGUMENTS.fieldType#>
		<cfcase value="numeric">
			<cfreturn "CF_SQL_NUMERIC"/>
		</cfcase>		
		<cfcase value="integer">
			<cfreturn "CF_SQL_INTEGER"/>
		</cfcase>
		<cfcase value="boolean">
			<cfreturn "CF_SQL_BIT"/>
		</cfcase>			
		<cfcase value="date,time,datetime">
			<cfreturn "CF_SQL_TIMESTAMP"/>
		</cfcase>		
		<cfcase value="string">
			<cfreturn "CF_SQL_VARCHAR"/>
		</cfcase>
		<cfdefaultcase>
			<cfreturn "CF_SQL_VARCHAR"/>
		</cfdefaultcase>
	</cfswitch>
</cffunction>

v1/lib/index.cfm

<cfoutput>index ***</cfoutput>

v1/lib/jwt.cfc

component {

    variables.algorithmMap = {
        HS256: 'HmacSHA256',
        HS384: 'HmacSHA384',
        HS512: 'HmacSHA512',
        RS256: 'SHA256withRSA',
        RS384: 'SHA384withRSA',
        RS512: 'SHA512withRSA',
        ES256: 'SHA256withECDSA',
        ES384: 'SHA384withECDSA',
        ES512: 'SHA512withECDSA'
    };

    public any function init() {
        variables.encodingUtils = new encodingUtils();
        variables.jss = createObject( 'java', 'java.security.Signature' );
        variables.messageDigest = createObject( 'java', 'java.security.MessageDigest' );
        return this;
    }

    public string function encode(
        required struct payload,
        required any key,
        required string algorithm,
        struct headers = { }
    ) {
        if ( !algorithmMap.keyExists( algorithm ) ) {
            throw(
                type = 'jwtcfml.InvalidAlgorithm',
                message = 'Invalid JWT Algorithm.',
                detail = 'The passed in algorithm is not supported.'
            );
        }

        var header = { };
        header.append( headers );
        header.append( {
            'typ': 'JWT',
            'alg': algorithm
        } );

        var duplicatedPayload = duplicate( payload );
        for ( var claim in [ 'iat', 'exp', 'nbf' ] ) {
            if ( duplicatedPayload.keyExists( claim ) && isDate( duplicatedPayload[ claim ] ) ) {
                duplicatedPayload[ claim ] = encodingUtils.convertDateToUnixTimestamp( duplicatedPayload[ claim ] );
            }
        }

        var stringToSignParts = [
            encodingUtils.binaryToBase64Url( charsetDecode( serializeJSON( header ), 'utf-8' ) ),
            encodingUtils.binaryToBase64Url( charsetDecode( serializeJSON( duplicatedPayload ), 'utf-8' ) )
        ];
        var stringToSign = stringToSignParts.toList( '.' );

        return stringToSign & '.' & encodingUtils.binaryToBase64Url( sign( stringToSign, key, algorithm ) );
    }

    public struct function decode(
        required string token,
        any key,
        any algorithms = [ ],
        struct claims = { },
        boolean verify = true
    ) {
        var parts = listToArray( token, '.' );

        if ( arrayLen( parts ) != 3 ) {
            throw(
                type = 'jwtcfml.InvalidToken',
                message = 'Invalid JWT.',
                detail = 'The passed in token does not have three `.` delimited parts.'
            );
        }

        algorithms = isArray( algorithms ) ? algorithms : [ algorithms ];

        var decoded = {
            header: deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( parts[ 1 ] ), 'utf-8' ) ),
            payload: deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( parts[ 2 ] ), 'utf-8' ) )
        };

        if ( verify ) {
            if (
                !algorithms.find( decoded.header.alg ) ||
                !algorithmMap.keyExists( decoded.header.alg )
            ) {
                throw(
                    type = 'jwtcfml.InvalidAlgorithm',
                    message = 'Unsupported or invalid algorithm',
                    detail = 'The passed in token does not have an algorithm declaration or its declared algorithm (#decoded.header.alg#) does not match the specified algorithms of #serializeJSON( algorithms )#.'
                );
            }

            var stringToSign = parts[ 1 ] & '.' & parts[ 2 ];
            var signature = encodingUtils.base64UrlToBinary( parts[ 3 ] );

            if (
                !verifySignature(
                    stringToSign,
                    key,
                    signature,
                    decoded.header.alg
                )
            ) {
                throw(
                    type = 'jwtcfml.InvalidSignature',
                    message = 'Signature is Invalid',
                    detail = 'The signature of the passed in token is invalid.'
                );
            }

            var baseClaims = {
                'exp': true,
                'nbf': true
            };
            baseClaims.append( claims );
            verifyClaims( decoded.payload, baseClaims );
        }

        for ( var claim in [ 'iat', 'exp', 'nbf' ] ) {
            if ( decoded.payload.keyExists( claim ) ) {
                decoded.payload[ claim ] = encodingUtils.convertUnixTimestampToDate( decoded.payload[ claim ] );
            }
        }

        return decoded.payload;
    }

    public struct function getHeader( required string token ) {
        return deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( listFirst( token, '.' ) ), 'utf-8' ) );
    }

    public function parsePEMEncodedKey( required string pemKey ) {
        return encodingUtils.parsePEMEncodedKey( pemKey );
    }

    public function parseJWK( required struct jwk ) {
        return encodingUtils.parseJWK( jwk );
    }

    private function sign( message, key, algorithm ) {
        if ( left( algorithm, 1 ) == 'H' ) {
            var sig = binaryDecode(
                hmac(
                    message,
                    key,
                    algorithmMap[ algorithm ],
                    'utf-8'
                ),
                'hex'
            );
        } else {
            if ( isSimpleValue( key ) ) {
                key = encodingUtils.parsePEMEncodedKey( key );
            } else if ( isStruct( key ) ) {
                key = encodingUtils.parseJWK( key );
            }

            var jssInstance = variables.jss.getInstance( algorithmMap[ algorithm ] );
            jssInstance.initSign( key );
            jssInstance.update( charsetDecode( message, 'utf-8' ) );
            var sig = jssInstance.sign();
            if ( left( algorithm, 1 ) == 'E' ) {
                sig = encodingUtils.convertDERtoP1363( sig, algorithm );
            }
        }
        return sig;
    }

    private function verifySignature( message, key, signature, algorithm ) {
        if ( left( algorithm, 1 ) == 'H' ) {
            var sig = binaryDecode(
                hmac(
                    message,
                    key,
                    algorithmMap[ algorithm ],
                    'utf-8'
                ),
                'hex'
            );
            return MessageDigest.isEqual( signature, sig );
        }

        if ( left( algorithm, 1 ) == 'E' ) {
            signature = encodingUtils.convertP1363ToDER( signature );
        }

        if ( isSimpleValue( key ) ) {
            key = encodingUtils.parsePEMEncodedKey( key );
        } else if ( isStruct( key ) ) {
            key = encodingUtils.parseJWK( key );
        }

        var jssInstance = variables.jss.getInstance( algorithmMap[ algorithm ] );
        jssInstance.initVerify( key );
        jssInstance.update( charsetDecode( message, 'utf-8' ) );
        return jssInstance.verify( signature );
    }

    private function verifyClaims( payload, claims ) {
        if (
            structKeyExists( payload, 'exp' )
             && !verifyDateClaim( payload.exp, claims.exp, -1 )
        ) {
            throw(
                type = 'jwtcfml.ExpiredSignature',
                message = 'Token has expired',
                detail = 'The passed in token has expired.'
            );
        }

        if (
            structKeyExists( payload, 'nbf' )
             && !verifyDateClaim( payload.nbf, claims.nbf, 1 )
        ) {
            throw(
                type = 'jwtcfml.NotBeforeException',
                message = 'Token is not valid',
                detail = 'The passed in token has not yet become valid.'
            );
        }



        if ( structKeyExists( claims, 'iss' ) ) {
            if ( !structKeyExists( payload, 'iss' ) || compare( payload.iss, claims.iss ) != 0 ) {
                throw(
                    type = 'jwtcfml.InvalidIssuer',
                    message = 'Token has an invalid issuer',
                    detail = 'The passed in token either does not specify an issuer or the claimed issuer is not valid.'
                );
            }
        }

        if ( structKeyExists( claims, 'aud' ) ) {
            var audArray = isArray( claims.aud ) ? claims.aud : [ claims.aud ];
            if ( !structKeyExists( payload, 'aud' ) || !audArray.find( payload.aud ) ) {
                throw(
                    type = 'jwtcfml.InvalidAudience',
                    message = 'Token has an invalid audience',
                    detail = 'The passed in token either does not specify an audience or the claimed audience is not valid.'
                );
            }
        }
    }

    private function verifyDateClaim( payloadDate, claim, failState ) {
        var pd = encodingUtils.convertUnixTimestampToDate( payloadDate );
        var cd = claim;
        if ( !isBoolean( cd ) || cd ) {
            if ( isNumeric( cd ) ) {
                cd = encodingUtils.convertUnixTimestampToDate( cd );
            } else if ( !isDate( cd ) ) {
                cd = now();
            }
            return dateCompare( pd, cd ) != failState;
        }
        return true;
    }

}

v1/lib/notifier.cfc

<cfcomponent
    displayname="Notifier"
    output="true"
    hint="Notification helper, to be refactored">

<!---Внимание! необходимо зарегистрировать на сервере сертификат хоста maker.ifttt.com, иначе SSL соединение не установится и будет Connection Failure--->	
<!---	Нужно реализовывать нормальную систему с подпиской на события, по типу 
	https://tonytruong.net/event-emitter-pattern-in-coldfusion/--->	
	
<cffunction name="notify" 
	access="public" 
	returntype="any" 
	output="false"
	hint="send notification">
	
	<cfargument name="value1" type="string" required="true" />
	<cfargument name="value2" type="string" required="true" />
	<cfargument name="value3" type="string" required="true" />
	
	<cftry>
		<cfhttp name="ifttt" method="post" url="https://maker.ifttt.com/trigger/mms_task_event/with/key/drWn5g6pNjyP_XRru0tMrQ" result="resp" timeout="10">
			<cfhttpparam type="header" name="Content-Type" value="application/json" />
			<cfhttpparam type="body" value="#serializeJSON(arguments)#">
		</cfhttp>	
		<cfreturn resp.filecontent/>
		<cfcatch type="ANY">
			<cfreturn "#cfcatch.message# #cfcatch.detail#"/>
		</cfcatch>
	</cftry>

</cffunction>
	
</cfcomponent>

v1/lib/order_build.cfm

<cfsilent>
<!--- Формирует строку сортировки для запроса --->

	<cfparam name="ATTRIBUTES.sortCollection" type="any">
	<cfparam name="ATTRIBUTES.fieldCount" type="integer" default=0>
	<cfparam name="ATTRIBUTES.defaultOrder" type="string" default="1 desc">
	
	<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>		

		<cfset querySort="">
		<cfset i=1 />
		<cfloop collection=#ATTRIBUTES.sortCollection# index="item">
			<cftry>
				<cfset sort=ATTRIBUTES.sortCollection[item] />
				<cfif (i LE ATTRIBUTES.fieldCount) OR (ATTRIBUTES.fieldCount LE 0)>
					<cfif sort.asc>
						<cfset querySort=listAppend(querySort," #sort.fld# asc")/>
					<cfelse>	
						<cfset querySort=listAppend(querySort," #sort.fld# desc")/>
					</cfif>
				</cfif>
				<cfcatch type="Any">
					<cfrethrow/>
				</cfcatch>
			</cftry>
			<cfset i=i+1 />
		</cfloop>

		<cfif len(trim(querySort)) EQ 0>
			<cfset querySort="#ATTRIBUTES.defaultOrder#">
		</cfif> 

		<cfset querySort=trim(querySort)>

		<cfif isDefined("ATTRIBUTES.output")>
			<cfset "CALLER.#ATTRIBUTES.output#"=querySort/>
		<cfelse>
			<cfset thisTag.generatedContent=querySort/>
		</cfif>
	</cfif>
</cfsilent>

v1/lib/rest_api_helper.cfc

<cfcomponent
    displayname="ReST API helper"
    output="true"
	hint="Статические вспомогательные методы для ReST API">
	
<cffunction name="empty2null" 
	access="public" 
	returntype="any" 
	output="false"
	hint="Заменяет пустое поле на null">
	
	<cfargument name="value" type="any" required="true" />
	
	<cfif isSimpleValue(ARGUMENTS.value) AND isEmpty(ARGUMENTS.value)>
		<cfreturn javacast('null','')/>
	<cfelse>
		<cfreturn ARGUMENTS.value />
	</cfif>
</cffunction>


<cffunction name="passThrough"	
	returntype="any" 
	output="false"
	hint="Возвращает аргумент без изменений">	
	<cfargument name="x" type="ANY" required="true"/>
	<cfreturn #ARGUMENTS.x#/>	
</cffunction>
	

<cffunction name="appendRecord" 
	access="public" 
	returntype="any" 
	output="false"
	hint="Записывает данные в структуру, заменяя пустые поля на null">
	
	<cfargument name="struct" type="struct" required="true" />
	<cfargument name="key" type="string" required="true" />
	<cfargument name="fieldMap" type="struct" required="true" />
	<cfargument name="query" type="query" required="true" />
	<cfargument name="fieldNameDecorator" type="function" required="false" default=#passThrough#/>
	
	<cfif len(ARGUMENTS.key)>		
		<cfset ARGUMENTS.struct[ARGUMENTS.key]=structNew()/>
		<cfset var container=ARGUMENTS.struct[ARGUMENTS.key]/>
	<cfelse>
		<cfset var container=ARGUMENTS.struct/>
	</cfif>

	<cfloop list=#structKeyList(ARGUMENTS.fieldMap)# index="local.col">
		<cfset var obj=container/>
		<cfset var formatter = structFind(ARGUMENTS.fieldMap[local.col], "formatter")/>
		<cfset var type = structFind(ARGUMENTS.fieldMap[local.col], "type")/>
		<cfif structKeyExists(ARGUMENTS.fieldMap[#local.col#],"container")>
			<cfset var subContainerName = structFind(ARGUMENTS.fieldMap[local.col], "container")/>
			
			<cfif len(subContainerName)>
				<cfif !structKeyExists(container, subContainerName)>
					<cfset structInsert(container, subContainerName, structNew())/>					
				</cfif>
				<cfset obj=container[subContainerName]/>
			</cfif>
		</cfif>
		
		<cfif queryColumnExists(ARGUMENTS.query, local.col)>
			<cfset obj[ARGUMENTS.fieldNameDecorator(local.col)]=empty2null(formatter(ARGUMENTS.query[local.col][ARGUMENTS.query.currentRow]))/>
		</cfif>
	</cfloop>	
	
	<cfreturn ARGUMENTS.struct/>
		
</cffunction>


<cffunction name="parseFilterParams"	
	returntype="array" 
	output="true"
	hint="Разбирает и собирает параметры фильтра из URL. Операторы задаются в filter_build">
	<cfargument name="params" type="struct" required="true" />
	
		<cfset var local = {}/>
	<cfset var out = []/>
	<cfset var urlParams = parseQs()/>
	
	<cfloop collection=#params# index="local.item">

		<cfset var urlParamName=snake2camel(lCase(local.item))/>
		<cfif structKeyExists(urlParams,urlParamName)>
			
			<cfloop array=#urlParams[urlParamName]# item="local.rawValue">
				<cfset var operator="EQ"/>
				<cfset var value=#local.rawValue#/>
				<cfif listLen(local.rawValue,":") GT 1>
					<cfset var operator=listGetAt(local.rawValue,1,":")/>			
					<cfset var value=listGetAt(local.rawValue,2,":")/>			
				</cfif>
				<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#"/>
				</cfif>
							
				<cfif structKeyExists(ARGUMENTS.params[local.item], "expression") AND len(ARGUMENTS.params[local.item].expression)>
					<cfset var expr="#ARGUMENTS.params[local.item].expression#"/>
				<cfelse>
					<cfset var expr=local.item/>
				</cfif>
				
				<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)>
					<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#expr#"/>
				<cfelse>
					<cfset var fieldName=#expr#/>
				</cfif>
				
				<cfif structKeyExists(ARGUMENTS.params[local.item], "list") AND ARGUMENTS.params[local.item].list GT 0>
					<cfset var list=ARGUMENTS.params[local.item].list />
				<cfelse>
					<cfset var list=false />
				</cfif>
				
				<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#, list=#list#}/>
				<cfset arrayAppend(out, rec)/>
			</cfloop>
		</cfif>
		
	</cfloop>
	<cfreturn out/>
</cffunction>

<cffunction name="parseFilterParamsV1"	
	returntype="array" 
	output="true"
		hint="Разбирает и собирает параметры фильтра; операторы задаются в filter_build">
	<cfargument name="params" type="struct" required="true" />
	
		<cfset var local = {}/>
	<cfset var out = []/>
	
	<cfloop collection=#params# index="local.item">

		<cfset var urlParamName=snake2camel(lCase(local.item))/>
		<cfif structKeyExists(URL,urlParamName)>
			
			<cfloop list=#URL[urlParamName]# item="local.rawValue">
				<cfset var operator="EQ"/>
				<cfset var value=#local.rawValue#/>
				<cfif listLen(local.rawValue,":") GT 1>
					<cfset var operator=listGetAt(local.rawValue,1,":")/>			
					<cfset var value=listGetAt(local.rawValue,2,":")/>			
				</cfif>
				<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#"/>
				</cfif>
							
				<cfif structKeyExists(ARGUMENTS.params[local.item], "expression") AND len(ARGUMENTS.params[local.item].expression)>
					<cfset var expr="#ARGUMENTS.params[local.item].expression#"/>
				<cfelse>
					<cfset var expr=local.item/>
				</cfif>
				
				<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)>
					<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#expr#"/>
				<cfelse>
					<cfset var fieldName=#expr#/>
				</cfif>
				
				<cfif structKeyExists(ARGUMENTS.params[local.item], "list") AND ARGUMENTS.params[local.item].list GT 0>
					<cfset var list=ARGUMENTS.params[local.item].list />
				<cfelse>
					<cfset var list=false />
				</cfif>
				
				<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#, list=#list#}/>
				<cfset arrayAppend(out, rec)/>
			</cfloop>
		</cfif>
		
	</cfloop>
	<cfreturn out/>
</cffunction>


<cffunction name="parseOrderBy"	
	returntype="struct" 
	output="false"
	hint="Разбирает и собирает параметр сортировки">

	<cfargument name="params" type="struct" required="true" />
	<cfargument name="orderBy" type="string" required="true" />
	
	<cfset var out={}/>

	<cfloop list=#ARGUMENTS.orderBy# index="local.item">
		<cfif listLen(local.item,".") GT 1>
			<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
			<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
				<cfcase value="ASC"><cfset var asc=true/></cfcase>
				<cfcase value="DESC"><cfset var asc=false/></cfcase>
				<cfdefaultcase>
					<cfthrow  type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" />
				</cfdefaultcase>
			</cfswitch>
		<cfelse>
			<cfset var fld=camel2snake(local.item)/>
			<cfset var asc=true/>			
		</cfif>
		
		<cfif structKeyExists(ARGUMENTS.params, fld)>
			<cfset structInsert(out,"#ARGUMENTS.params[fld].prefix#.#fld#", {fld="#ARGUMENTS.params[fld].prefix#.#fld#",asc=#asc#}, true)/>
		<cfelse>
			<cfthrow  type="InvalidParamValue" message="Invalid orderBy field"/>
		</cfif>		
	</cfloop>
	<cfreturn out/>
</cffunction>

<cffunction name="parseNumericOrder"	
	returntype="array" 
	output="false"
	hint="Разбирает и собирает параметр сортировки в числовой нотации">

	<cfargument name="fieldSet" type="struct" required="true" />
	<cfargument name="orderBy" type="string" required="true" />
	
	<cfset var out=[]/>

	<cfloop list=#ARGUMENTS.orderBy# index="local.item">
		<cfif listLen(local.item,".") GT 1>
			<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
			<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
				<cfcase value="ASC"><cfset var asc=true/></cfcase>
				<cfcase value="DESC"><cfset var asc=false/></cfcase>
				<cfdefaultcase>
					<cfthrow  type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" />
				</cfdefaultcase>
			</cfswitch>
		<cfelse>
			<cfset var fld=camel2snake(local.item)/>
			<cfset var asc=true/>			
		</cfif>
		<cfif structKeyExists(ARGUMENTS.fieldSet, fld)>
			<cfset arrayAppend(out,{fld="#ARGUMENTS.fieldSet[fld].ordinal#",asc=#asc#})/>
		<cfelse>
			<cfthrow  type="InvalidParamValue" message="Invalid orderBy field"/>
		</cfif>		
	</cfloop>
	<cfreturn out/>
</cffunction>
<cffunction name="query4json" access="public" returntype="any" output="false"
	hint="Преобразует query в массив структур. Имена ключей приводятся к нижнему регистру, пустые поля считаются null">

	<cfargument name="Query" type="query" required="true" />
	
	<cfset var out=arrayNew(1)/>
	<cfset var i=0/>
	
	<cfloop query=ARGUMENTS.Query>
		<cfset i=i+1/>
		<cfset out[i]=structNew()/>
		<cfloop list=#ARGUMENTS.Query.ColumnList# index="local.col">
			<cfset var value=ARGUMENTS.Query[local.col][currentRow]/>
			<cfif isEmpty(value)>
				<cfset out[i][local.col]=javacast('null','')/>
			<cfelse>
				<cfset out[i][local.col]=ARGUMENTS.Query[local.col][currentRow]/>
			</cfif>
		</cfloop>
	</cfloop>	
	
	<cfreturn(out)/>
</cffunction>


<cffunction name="snake2camel" 
	access="public" 
	returntype="any" 
	output="false"
	hint="Преобразует имя в стиле snake_case в camelCase">	
	<cfargument name="snake" type="string" required="true" />
	<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>	
</cffunction>


<cffunction name="camel2snake" 
	access="public" 
	returntype="any" 
	output="false"
	hint="Преобразует имя в стиле camelCase в snake_case">	
	<cfargument name="snake" type="string" required="true" />
	<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>	
</cffunction>	

<cffunction name="formatMessage"
	access="public" 
	returntype="any" 
	output="true"
	hint="Форматирует сообщение для вывода">		
	
	<cfargument name="message" type="string" required="true" />
	<cfargument name="title" type="string" required="false" />

	<cfset var title=structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.message# />	
	
	<cfreturn {type="about:blank", title="#title#", detail="#ARGUMENTS.message#"}/>
</cffunction>


<cffunction name="formatException" 
	access="public" 
	returntype="any" 
	output="true"
	hint="Форматирует исключение для вывода">		
	
	<cfargument name="ex" type="struct" required="true" />
	<cfargument name="title" type="string" required="false" />
	
	<cfset var detail = listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,": " )/>
	<cfset var title = structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.ex.message# />	
	
	<cfreturn formatMessage(detail, title) />
</cffunction>	

<cffunction name="formatBadRequestError" 
	access="public" 
	returntype="any" 
	output="true"
	hint="Форматирует ошибку Bad Request">		
	
	<cfargument name="ex" type="struct" required="true" />
	
	<cfset var detail=listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,":" )/>
	
	<cfreturn {type="about:blank", title="Bad Request", detail="#detail#"}/>
</cffunction>	

	<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" 
		output="false"
		hint="Проверяет поле структуры, если оно существует">	
		
		<cfargument name="struct" type="struct" required=true/>
		<cfargument name="name" required=true/>
		<cfargument name="type" default="string"/>
		<cfargument name="required" type="boolean" default="false"/>
		
		<cfif structKeyExists(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>
			<cfif ARGUMENTS.required>
				<cfthrow type="invalidParamValue" message="Required field not supplied" detail="Required field #ARGUMENTS.name# not found"/>
			</cfif>
		</cfif>
	</cffunction>
	
	<cffunction name="keyExistsAndValid" access="public" returntype="boolean">
		<cfargument name="struct" type="struct" required=true/>
		<cfargument name="key" type="string" required=true/>
		<cfargument name="type" type="string" required=false/>
		<cfargument name="maxLength" type="numeric" required=false hint="Checked for strings only, negative value means no limit"/>
		
		<cfif structKeyExists(arguments.struct, arguments.key)>
			<cfif structKeyExists(arguments, "type")>
				<cfset var x=arguments.struct[arguments.key]/>
				<cfif isValidX(arguments.type, x)>
					<cfif lCase(arguments.type) EQ "string">
						<cfif structKeyExists(arguments, "maxLength")>
							<cfif ARGUMENTS.maxLength GE 0>
								<cfif len(x) GT #arguments.maxLength#>
									<cfthrow type="invalidParamValue" message="Invalid parameter value (string too long)" detail="Parameter #arguments.key# length #len(x)# is greater than #arguments.maxLength#" errorcode=400/>
								</cfif>
							</cfif>
						</cfif>
					</cfif>					
				<cfelse>
					<cfthrow type="invalidParamValue" message="Invalid parameter value" detail="Parameter #arguments.key# is not a valid #arguments.type#" errorcode=400/>
				</cfif>
			</cfif>
			<cfreturn true/>
		<cfelse>
			<cfreturn false/>
		</cfif>
	</cffunction>
	
	<cffunction name="keyExistsAndValidGraceful" access="public" returntype="boolean">
		<cfargument name="struct" type="struct" required=true/>
		<cfargument name="key" type="string" required=true/>
		<cfargument name="type" type="string" required=false/>
		<cfargument name="maxLength" type="numeric" required=false default="-1" hint="Checked for strings only"/>		
		
		<cftry>
			<cfreturn keyExistsAndValid(ARGUMENTS.struct, ARGUMENTS.key, ARGUMENTS.type, ARGUMENTS.maxLength)/>
			<cfcatch type="ANY"></cfcatch>
		</cftry>
		<cfreturn false/>
	</cffunction>
	
	
	<cffunction name="parseQsToNVArray"	
		returntype="array" 
		output="false"
		hint="Parse query string. Returns an array of name-value pairs">	
		<cfargument name="qs" type="string" default=#CGI.QUERY_STRING#/>
		<cfreturn 
			arrayMap(
				CreateObject("java", "org.apache.http.client.utils.URLEncodedUtils")
					.parse(
						CreateObject("java", "java.net.URI").init("http://localhost?#arguments.qs#"),
						'UTF-8'
					),
					function(v) {
						return {"name"=v.getName(),"value"=v.getValue()}
					}
			)
		/>	
	</cffunction>
	
	<cffunction name="parseQs"	
		returntype="struct" 
		output="false"
		hint="Parse query string. Returns a map of arrays instead of lists to prevent delimiter problem">	
		<cfargument name="qs" type="string" default=#CGI.QUERY_STRING#/>
		<cfreturn 
			arrayReduce(
				CreateObject("java", "org.apache.http.client.utils.URLEncodedUtils")
					.parse(
						CreateObject("java", "java.net.URI").init("http://localhost?#arguments.qs#"),
						'UTF-8'
					),
					function(struct, nvPair) {
						if (structKeyExists(struct, nvPair.getName())) {
							arrayAppend(struct[nvPair.getName()],nvPair.getValue())
						} else {
							structInsert(struct, nvPair.getName(), [nvPair.getValue()])
						}
						return struct;
					},
					{}
			)
		/>	
	</cffunction>
	
</cfcomponent>

v1/resources/available_resource_realms.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/resourceRealms/available">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cfset this.fieldsSpec={
		svc_id={prefix="r", type="integer"},
		resource_realm_id={prefix="r", type="integer"},
		resource_realm_type_id={prefix="r", type="integer"},
		parent_id={prefix="r", type="integer"},
		resource_realm_type={prefix="r", type="string"},
		resource_realm={prefix="r", type="string"},
		mgmt_api_url={prefix="r", type="string"}
	}
	/>

	 <cffunction name="get" hint="Список доступных ресурсных платформ">
		<cfargument name="svcId" type="string" hint="type:integer" default="-1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset local={}/>
		
		
		<cftry>
			<cfset this.helper.validateField(arguments, "svcId", "integer")/>

			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>	

			<cfquery name="local.qSpec">
				select 
				<m:field_set titleMapOut="local.specTitleMap" lengthOut="local.fieldCount">
					<m:field cfSqlType="CF_SQL_INTEGER">s.specification_id</m:field>
					<m:field>s.specification</m:field>
					<m:field>c.contract_id</m:field>
					<m:field>c.contract</m:field>
					<m:field>u.usr_id</m:field>
					<m:field>u.login</m:field>
					<m:field>z.contragent_id</m:field>
					<m:field>z.contragent</m:field>
					<m:field>z.external_code</m:field>
				</m:field_set>	 
				from specification s
				left outer join contract c on (s.contract_id=c.contract_id)
				left outer join usr u on (c.contragent_id=u.contragent_id)
				left outer join contragent z on (u.contragent_id=z.contragent_id)
				where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#ARGUMENTS.usrId#/>
				order by specification_id desc;				
			</cfquery>		
		
		
		<cfset var out=structNew("linked")/>
		
		<cfset var args = {
			"functionName":"getAvailableResourceRealms", 
			"svcId":#arguments.svcId#, 
			"contractId":#arguments.contractId#
		}/>
		

		<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.value_list"/>

		

		<cfset "out.arguments"=#arguments#/>		
		<cfset "out.args"=#args#/>		
		<cfset "out.results"=#local.value_list#/>
		
		<cfset "out.spec"=[]/>

		<cfloop query=#local.qSpec#>			
			<cfset var rec = this.helper.appendRecord(structNew("linked"), "", local.specTitleMap, local.qSpec, this.helper.snake2camel)/>			
			<cfset arrayAppend(out.spec, rec)/>
		</cfloop>
	
		<cfreturn representationOf(out)/>		
		
	</cffunction> 

</cfcomponent>

v1/resources/bookmark.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/bookmarks/bookmarks/{bookmarkUid}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cffunction name="get" hint="Букмарк">
		<cfargument name="bookmarkUid" type="string" required=true hint="type:guid"/>	

		<cfset var local={}/>	
		<cftry>		
			<cftry>
				<cfset this.helper.validateField(arguments, "bookmarkUid", "guid")/>			
				<cfcatch type="invalidParamValue">
					<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
				</cfcatch>
			</cftry>				
			
			<cfset var out={}/>
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field>b.bookmark_uid::text as bookmark_uid</m:field>
					<m:field>b.bookmark</m:field>
					<m:field>b.descr</m:field>
					<m:field>b.url</m:field>
					<m:field>b.icon_url</m:field>
					<m:field>b.contragent_id</m:field>
					<m:field>b.sort</m:field>
					<m:field formatter=#request.castToBool#>b.is_enabled</m:field>
					<m:field>to_char(b.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
					<m:field>to_char(b.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
				</m:field_set>	
				from bookmark b 
				where b.bookmark_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.bookmarkUid#" null=#!isValid('guid',arguments.bookmarkUid)#/>
				AND (b.contragent_id = <cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.contragentId# null=#!isNumeric(arguments.contragentId)#/> OR b.contragent_id=-1)
			</cfquery>

			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
			<cfset "out.bookmark" = this.helper.appendRecord(structNew("linked"), "", local.titleMap, local.qRead, this.helper.snake2camel)/>			
			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	

			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>
			</cfcatch>
		</cftry>
		
	</cffunction> 	
	
	<cffunction name="patch">
		<cfargument name="bookmarkUid" type="string" required=true hint="type:guid"/>		
		<cfargument name="bookmark" type="string" required=false hint="type:string"/>		
		<cfargument name="descr" type="string" required=false hint="type:string (no cleanup here!)"/>
		<cfargument name="url" type="string" required=false hint="type:string description: url"/>
		<cfargument name="iconUrl" type="string" required=false hint="type:string description: url иконки"/>
		<cfargument name="sort" type="string" required=false hint="type:integer description: целое число для сортировки"/>
		<cfargument name="isEnabled" type="string" required=false hint="type:boolean description: вкл"/>
		
		<cftry>			
	
			<cfset this.helper.keyExistsAndValid(arguments, "bookmarkUid", "guid")/>
			<cfquery name="local.qSave">
				update bookmark set
				 dt_updated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#"/>
				,updater_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#"/>
				<cfif structKeyExists(arguments,"bookmark")>
					,bookmark=<cfqueryparam cfsqltype="cf_sql_varchar" value="#cleanInput(arguments.bookmark)#"/>
				</cfif>
				<cfif structKeyExists(arguments,"descr")>
					,descr=<cfqueryparam cfsqltype="cf_sql_varchar" value="#cleanInput(arguments.descr)#"/>		
				</cfif>				
				<cfif structKeyExists(arguments,"url")>
					,url=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.url#"/>		
				</cfif>				
				<cfif structKeyExists(arguments,"iconUrl")>
					,icon_url=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.iconUrl#"/>		
				</cfif>				
				<cfif structKeyExists(arguments,"sort")>
					,sort=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.sort#"/>		
				</cfif>				
				<cfif structKeyExists(arguments,"isEnabled")>
					,is_enabled=<cfqueryparam cfsqltype="cf_sql_bit" value="#arguments.isEnabled#"/>		
				</cfif>
				where 
					contragent_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.contragentId#"/>
					AND bookmark_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.bookmarkUid#"/>;
			</cfquery>
		
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>

		<cfreturn noData().withStatus(204, "No Content") />
	</cffunction>
	
	<cffunction name="cleanInput">
		<cfargument name="s" type="string"/>	
		<cfreturn htmlEditFormat(s)/>
	</cffunction>	
	
</cfcomponent>

v1/resources/bookmark_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/bookmarks/bookmarks">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cfset this.fieldsSpec={
		bookmark_uid={prefix="b", type="guid"},
		dt_created={prefix="b", type="date"},
		dt_updated={prefix="b", type="date"},
		contragent_id={prefix="b", type="integer"},
		bookmark={prefix="b", type="string"},
		url={prefix="b", type="string"},
		icon_url={prefix="b", type="string"},
		descr={prefix="b", type="string"},
		is_enabled={prefix="b", type="boolean"},	
		sort={prefix="b", type="integer"}	
	}
	/>

	 <cffunction name="get" hint="Список букмарков">
		<cfargument name="pageSize" type="string" hint="type:integer" default="100"/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset var local={}/>
		<cftry>
						
		
			<cftry>
				<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
				<cfset this.helper.validateField(arguments, "page", "integer")/>
				
				<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
				
				<cfcatch type="invalidParamValue">
					<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
				</cfcatch>
				<cfcatch type="any">
					<cfreturn representationOf(cfcatch)/>				
				</cfcatch>
			</cftry>				
			
			<cfset var out={}/>
			<cfset var maxrows=arguments.pageSize*arguments.page/>
			<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field>b.bookmark_uid::text as bookmark_uid</m:field>
					<m:field>b.bookmark</m:field>
					<m:field>b.descr</m:field>
					<m:field>b.url</m:field>
					<m:field>b.icon_url</m:field>
					<m:field>b.contragent_id</m:field>
					<m:field>b.sort</m:field>
					<m:field formatter=#request.castToBool#>b.is_enabled</m:field>
					<m:field>to_char(b.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
					<m:field>to_char(b.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
				</m:field_set>	
				from bookmark b 
				where 1=1 <m:filter_build filter=#filter#/>
				AND (b.contragent_id = <cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.contragentId# null=#!isNumeric(arguments.contragentId)#/>
				OR b.contragent_id=-1)
				order by b.contragent_id desc, b.sort asc
				limit #maxrows#
			</cfquery>

			<cfquery name="local.qTotal">
				select count(*) as cnt
				from bookmark b 
				where 1=1				
				AND (b.contragent_id = <cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.contragentId# null=#!isNumeric(arguments.contragentId)#/>)
			</cfquery> 

			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
			<cfset "out.total"=#local.qTotal.cnt#/>
			<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
			<cfset "out.pageSize"=#arguments.pageSize*1#/>
			<cfset "out.page"=#arguments.page*1#/>
			<cfset "out.orderBy"=#arguments.orderBy#/>
			
			<cfset var resultCollection=[]/>
			<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
				<cfset var rec={}/>				
				<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
				<cfset arrayAppend(resultCollection, rec)/>
			</cfloop>
			
			<cfset "out.size"=#arrayLen(resultCollection)#/>
			<cfset "out.results"=#resultCollection#/>
			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>
			</cfcatch>
		</cftry>
		
	</cffunction> 
	

	<cffunction name="post" hint="Создание нового букмарка.">
		<cfargument name="bookmark" type="string" required=true hint="type:string description: заголовок"/>
		<cfargument name="descr" type="string" required=false default="" hint="type:boolean description: пользовательское примечание"/>
		<cfargument name="url" type="string" required=false default="" hint="type:string description: url"/>
		<cfargument name="iconUrl" type="string" required=false default="" hint="type:string description: url иконки"/>
		<cfargument name="sort" type="string" required=false default="100" hint="type:integer description: целое число для сортировки"/>
		<cfargument name="isEnabled" type="string" required=false default="1" hint="type:boolean description: вкл"/>

		<cftry>
			<cfset this.helper.validateField(arguments, "bookmarkUid", "guid")/>
			<cfset this.helper.validateField(arguments, "sort", "integer")/>	
			<cfset this.helper.validateField(arguments, "isEnabled", "boolean")/>	
					
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>
		<cfset local.bookmarkUid=#createGUID()#/>
		
		<cftry>
			<cfquery name="local.qSave">
				insert into bookmark (	
					bookmark_uid,contragent_id, bookmark,url,icon_url,descr,sort,is_enabled,dt_created,creator_id
				) values (
					 <cfqueryparam cfsqltype="cf_sql_other" value="#local.bookmarkUid#"/>
					,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.contragentId#"/>
					,<cfqueryparam cfsqltype="cf_sql_varchar" value="#cleanInput(arguments.bookmark)#"/>
					,<cfqueryparam cfsqltype="cf_sql_varchar" value="#cleanInput(arguments.url)#"/>
					,<cfqueryparam cfsqltype="cf_sql_varchar" value="#cleanInput(arguments.iconUrl)#"/>
					,<cfqueryparam cfsqltype="cf_sql_varchar" value="#cleanInput(arguments.descr)#"/>					
					,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.sort#"/>
					,<cfqueryparam cfsqltype="cf_sql_bit" value="#arguments.isEnabled#" null=#!isValid("boolean",arguments.isEnabled)#/>
					,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
					,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				);
			</cfquery>				

			<cfcatch type="any">
				<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/>
			</cfcatch>
		</cftry>
		
		<cfreturn noData()
			.withHeaders({location: "./#local.bookmarkUid#"})
			.withStatus(201, "Created") 
		/>				
	</cffunction> 
	
	<cfscript>
	function plain2htm(s) {
		return replace(replace(s, chr(13),'',"ALL"),chr(10),'<br/>', "ALL"); 
	}

	function htm2plain(s) {
		return replaceNoCase(s, '<br/>', '#chr(13)##chr(10)#', "ALL"); 
	}

	function cleanHtm(s) {
		return replaceList(s, '<,>,"', '&lt;,&gt;,&quot;'); 
	}
	</cfscript>
	
	<cffunction name="plain2HtmClean">
		<cfargument name="s" type="string"/>	
		<cfreturn plain2htm(cleanHtm(s))/>
	</cffunction>

	<cffunction name="cleanInput">
		<cfargument name="s" type="string"/>	
		<cfreturn htmlEditFormat(s)/>
	</cffunction>

</cfcomponent>

v1/resources/catalog_service_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/service-catalog/services">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<!--- Спецификация полей, пригодных для фильтрации и сортировки --->
	<cfset this.fieldsSpec={
		 service_id={prefix="s", type="cf_sql_integer"}
		,business_line_id={prefix="s", type="cf_sql_integer"}
		,business_line={prefix="s", type="cf_sql_varchar"}
		,area_id={prefix="s", type="cf_sql_integer"}
		,area={prefix="s", type="cf_sql_varchar"}
		,area_code={prefix="s", type="cf_sql_varchar"}
		,analytic_code={prefix="s", type="cf_sql_varchar"}
		,abstract_service_id={prefix="s", type="cf_sql_integer"}
		,abstract_service={prefix="s", type="cf_sql_varchar"}
		,abstract_service_code={prefix="s", type="cf_sql_varchar"}
		,abstract_service_status_id={prefix="s", type="cf_sql_integer"}
		,abstract_service_status={prefix="s", type="cf_sql_varchar"}
		,service_code={prefix="s", type="cf_sql_varchar"}
		,service={prefix="s", type="cf_sql_varchar"}
		,modifier={prefix="s", type="cf_sql_varchar"}
		,modifier_code={prefix="s", type="cf_sql_varchar"}
		,measure_id={prefix="s", type="cf_sql_integer"}
		,service_status_id={prefix="s", type="cf_sql_integer"}
		,service_status={prefix="s", type="cf_sql_varchar"}
		,measure_short={prefix="s", type="cf_sql_varchar"}
		,sort={prefix="s", type="cf_sql_integer"}
		,vat_perc={prefix="s", type="cf_sql_integer"}
		,vat_free={prefix="s", type="cf_sql_bit"}
		,vat_rate={prefix="s", type="cf_sql_numeric"}
		,capacity_legend={prefix="s", type="cf_sql_integer"}
		,commercial_note={prefix="s", type="cf_sql_varchar"}
		,is_published={prefix="s", type="cf_sql_bit"}	
	}
	/>
	
	 <cffunction name="get" hint="Каталог конкретных услуг">
		<cfargument name="pageSize" type="string" hint="type:integer" default="100"/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset var local={}/>
		<cftry>
						
		
			<!--- Разбор и проверка параметров запроса --->
			<cftry>
				<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
				<cfset this.helper.validateField(arguments, "page", "integer")/>
				
				<!--- Поля вне спецификации игнорируются, чтобы не добавлять отдельные исключения для orderBy и похожих параметров. --->
				<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
				
				<cfcatch type="invalidParamValue">
					<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
				</cfcatch>
				<cfcatch type="any">
					<cfreturn representationOf(cfcatch)/>				
				</cfcatch>
			</cftry>				
			
			<cfset var out={}/>
			<cfset var maxrows=arguments.pageSize*arguments.page/>
			<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field title="service_id">service_id</m:field>
					<m:field title="business_line_id">business_line_id</m:field>
					<m:field title="business_line">business_line</m:field>
					<m:field title="area_id">area_id</m:field>
					<m:field title="area">area</m:field>
					<m:field title="area_code">area_code</m:field>
					<m:field title="analytic_code">analytic_code</m:field>
					<m:field title="abstract_service_id">abstract_service_id</m:field>
					<m:field title="abstract_service">abstract_service</m:field>
					<m:field title="abstract_service_code">abstract_service_code</m:field>
					<m:field title="abstract_service_status_id">abstract_service_status_id</m:field>
					<m:field title="abstract_service_status">abstract_service_status</m:field>
					<m:field title="service_code">service_code</m:field>
					<m:field title="service">service</m:field>
					<m:field title="modifier">modifier</m:field>
					<m:field title="modifier_code">modifier_code</m:field>
					<m:field title="measure_id">measure_id</m:field>
					<m:field title="service_status_id">service_status_id</m:field>
					<m:field title="service_status">service_status</m:field>
					<m:field title="measure_short">measure_short</m:field>
					<m:field title="sort">sort</m:field>
					<m:field title="vat_perc">vat_perc</m:field>
					<m:field title="vat_free">vat_free</m:field>
					<m:field title="vat_rate">vat_rate</m:field>
					<m:field title="capacity_legend">capacity_legend</m:field>
					<m:field title="commercial_note">commercial_note</m:field>
					<m:field title="is_published">is_published</m:field>
					<m:field title="dt_load">dt_load</m:field>
				</m:field_set>	
				from service_catalog.service s
				where 1=1 <m:filter_build filter=#filter#/>
				order by analytic_code
				, abstract_service
				, service_id
				, coalesce(sort,0)
				limit #maxrows#
			</cfquery>
			<cfquery name="local.qTotal">
				select count(*) as cnt
				from service_catalog.service s		
			</cfquery>				


			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
			<cfset "out.total"=#local.qTotal.cnt#/>
			<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
			<cfset "out.pageSize"=#arguments.pageSize*1#/>
			<cfset "out.page"=#arguments.page*1#/>
			<cfset "out.orderBy"=#arguments.orderBy#/>
			
			<cfset var resultCollection=[]/>
			<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
				<cfset var rec=structNew("linked")/>					
				<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
				<cfset arrayAppend(resultCollection, rec)/>
			</cfloop>
			
			<cfset "out.size"=#arrayLen(resultCollection)#/>
			<cfset "out.results"=#resultCollection#/>
			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>
			</cfcatch>
		</cftry>
		
	</cffunction> 
	

	
	
	<cfscript>
	function plain2htm(s) {
		return replace(replace(s, chr(13),'',"ALL"),chr(10),'<br/>', "ALL"); 
	}

	function htm2plain(s) {
		return replaceNoCase(s, '<br/>', '#chr(13)##chr(10)#', "ALL"); 
	}

	function cleanHtm(s) {
		return replaceList(s, '<,>,"', '&lt;,&gt;,&quot;'); 
	}
	</cfscript>
	
	<cffunction name="plain2HtmClean">
		<cfargument name="s" type="string"/>	
		<cfreturn plain2htm(cleanHtm(s))/>
	</cffunction>

	<cffunction name="cleanInput">
		<cfargument name="s" type="string"/>	
		<cfreturn htmlEditFormat(s)/>
	</cffunction>

</cfcomponent>

v1/resources/catalog_service_param_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/service-catalog/service-params">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cfset this.fieldsSpec={
		 service_param_id={prefix="p", type="cf_sql_integer"}
		,service_id={prefix="p", type="cf_sql_integer"}
		,param={prefix="p", type="cf_sql_varchar"}
		,component_code={prefix="p", type="cf_sql_varchar"}
		,component={prefix="p", type="cf_sql_varchar"}
		,measure_id={prefix="p", type="cf_sql_integer"}
		,measure_short={prefix="p", type="cf_sql_varchar"}
		,min_value={prefix="p", type="cf_sql_numeric"}
		,max_value={prefix="p", type="cf_sql_numeric"}
		,incr={prefix="p", type="cf_sql_numeric"}
		,param_class_sort={prefix="p", type="cf_sql_integer"}
		,param_sort={prefix="p", type="cf_sql_integer"}
		,sku={prefix="p", type="cf_sql_varchar"}
		,is_multiple={prefix="p", type="cf_sql_bit"}

		,business_line_id={prefix="s", type="cf_sql_integer"}
		,business_line={prefix="s", type="cf_sql_varchar"}
		,area_id={prefix="s", type="cf_sql_integer"}
		,area={prefix="s", type="cf_sql_varchar"}
		,area_code={prefix="s", type="cf_sql_varchar"}
		,analytic_code={prefix="s", type="cf_sql_varchar"}
		,abstract_service_id={prefix="s", type="cf_sql_integer"}
		,abstract_service={prefix="s", type="cf_sql_varchar"}
		,abstract_service_code={prefix="s", type="cf_sql_varchar"}
		,abstract_service_status_id={prefix="s", type="cf_sql_integer"}
		,abstract_service_status={prefix="s", type="cf_sql_varchar"}
		,service_code={prefix="s", type="cf_sql_varchar"}
		,service={prefix="s", type="cf_sql_varchar"}
		,modifier={prefix="s", type="cf_sql_varchar"}
		,modifier_code={prefix="s", type="cf_sql_varchar"}
		
		,service_status_id={prefix="s", type="cf_sql_integer"}
		,service_status={prefix="s", type="cf_sql_varchar"}
		,sort={prefix="s", type="cf_sql_integer"}
		,vat_perc={prefix="s", type="cf_sql_integer"}
		,vat_free={prefix="s", type="cf_sql_bit"}
		,vat_rate={prefix="s", type="cf_sql_numeric"}
		,capacity_legend={prefix="s", type="cf_sql_integer"}
		,commercial_note={prefix="s", type="cf_sql_varchar"}
		,is_published={prefix="s", type="cf_sql_bit"}
	}
	/>

	 <cffunction name="get" hint="Каталог конкретных услуг">
		<cfargument name="pageSize" type="string" hint="type:integer" default="100"/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset var local={}/>
		<cftry>
						
		
			<cftry>
				<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
				<cfset this.helper.validateField(arguments, "page", "integer")/>
				
				<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
				
				<cfcatch type="invalidParamValue">
					<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
				</cfcatch>
				<cfcatch type="any">
					<cfreturn representationOf(cfcatch)/>				
				</cfcatch>
			</cftry>				
			
			<cfset var out={}/>
			<cfset var maxrows=arguments.pageSize*arguments.page/>
			<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field title="service_param_id">p.service_param_id</m:field>
					<m:field title="service_id">p.service_id</m:field>
					<m:field title="param">p.param</m:field>
					<m:field title="component_code">p.component_code</m:field>
					<m:field title="component">p.component</m:field>
					<m:field title="measure_id">p.measure_id</m:field>
					<m:field title="measure_short">p.measure_short</m:field>
					<m:field title="min_value">p.min_value</m:field>
					<m:field title="max_value">p.max_value</m:field>
					<m:field title="incr">p.incr</m:field>
					<m:field title="param_class_sort">p.param_class_sort</m:field>
					<m:field title="param_sort">p.param_sort</m:field>
					<m:field title="sku">p.sku</m:field>
					<m:field title="is_multiple">p.is_multiple</m:field>
					<m:field title="dt_load">p.dt_load</m:field>
					
					<m:field title="business_line_id">s.business_line_id</m:field>
					<m:field title="business_line">s.business_line</m:field>
					<m:field title="area_id">s.area_id</m:field>
					<m:field title="area">s.area</m:field>
					<m:field title="area_code">s.area_code</m:field>
					<m:field title="analytic_code">s.analytic_code</m:field>
					<m:field title="abstract_service_id">s.abstract_service_id</m:field>
					<m:field title="abstract_service">s.abstract_service</m:field>
					<m:field title="abstract_service_code">s.abstract_service_code</m:field>
					<m:field title="abstract_service_status_id">s.abstract_service_status_id</m:field>
					<m:field title="abstract_service_status">s.abstract_service_status</m:field>
					<m:field title="service_code">s.service_code</m:field>
					<m:field title="service">s.service</m:field>
					<m:field title="modifier">s.modifier</m:field>
					<m:field title="modifier_code">s.modifier_code</m:field>
					<m:field title="service_status_id">s.service_status_id</m:field>
					<m:field title="service_status">s.service_status</m:field>
					<m:field title="sort">s.sort</m:field>
					<m:field title="vat_perc">s.vat_perc</m:field>
					<m:field title="vat_free">s.vat_free</m:field>
					<m:field title="vat_rate">s.vat_rate</m:field>
					<m:field title="capacity_legend">s.capacity_legend</m:field>
					<m:field title="commercial_note">commercial_note</m:field>
					<m:field title="is_published">s.is_published</m:field>
				</m:field_set>	
				from service_catalog.service_param p
				join service_catalog.service s on (p.service_id=s.service_id)
				where 1=1 <m:filter_build filter=#filter#/>
				order by analytic_code
				, abstract_service
				, service_id
				, coalesce(sort,0)
				limit #maxrows#
			</cfquery>
			<cfquery name="local.qTotal">
				select count(*) as cnt
				from service_catalog.service_param p
				join service_catalog.service s on (p.service_id=s.service_id)			
			</cfquery>

			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
			<cfset "out.total"=#local.qTotal.cnt#/>
			<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
			<cfset "out.pageSize"=#arguments.pageSize*1#/>
			<cfset "out.page"=#arguments.page*1#/>
			<cfset "out.orderBy"=#arguments.orderBy#/>
			
			<cfset var resultCollection=[]/>
			<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
				<cfset var rec=structNew("linked")/>				
				<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
				<cfset arrayAppend(resultCollection, rec)/>
			</cfloop>
			
			<cfset "out.size"=#arrayLen(resultCollection)#/>
			<cfset "out.results"=#resultCollection#/>
			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>
			</cfcatch>
		</cftry>
		
	</cffunction> 
	

	
	
	<cfscript>
	function plain2htm(s) {
		return replace(replace(s, chr(13),'',"ALL"),chr(10),'<br/>', "ALL"); 
	}

	function htm2plain(s) {
		return replaceNoCase(s, '<br/>', '#chr(13)##chr(10)#', "ALL"); 
	}

	function cleanHtm(s) {
		return replaceList(s, '<,>,"', '&lt;,&gt;,&quot;'); 
	}
	</cfscript>
	
	<cffunction name="plain2HtmClean">
		<cfargument name="s" type="string"/>	
		<cfreturn plain2htm(cleanHtm(s))/>
	</cffunction>

	<cffunction name="cleanInput">
		<cfargument name="s" type="string"/>	
		<cfreturn htmlEditFormat(s)/>
	</cffunction>

</cfcomponent>

v1/resources/err.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/error">

	 <cffunction name="get" hint="Имитация ошибки HTTP для тестирования">
		<cfargument name="statusCode" type="string" hint="type:integer" default="200"/>	
		<cfargument name="statusText" type="string" hint="type:string" default=""/>	

		<cfreturn representationOf(arguments.statusText).withStatus(arguments.statusCode)/>				
	</cffunction> 

</cfcomponent>

v1/resources/instance.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instances/{instanceUid}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>


	<cffunction name="get" hint="Экземпляр сервиса"><!--- *** TODO проверка принадлежности тенанту --->
		<cfargument name="instanceUid" type="string" required=true hint="type:guid"/>
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>

		<cfset var local={}/>		

		<cfquery name="local.qInstance" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field>e.instance_uid::text as instance_uid</m:field>
				<m:field>e.display_name</m:field>
				<m:field>e.service_id</m:field>
				<m:field>e.descr</m:field>
				<m:field>e.specification_item_id</m:field>
				<m:field formatter=#request.castToBool#>e.is_auxiliary</m:field>
				<m:field>to_char(e.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_created</m:field><!--- to_json(e.dt_created)##>>'{}' --->
				<m:field>to_char(e.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_updated</m:field>
				<m:field>d.contract_id</m:field>
				<m:field>i.specification_id</m:field>
				<m:field>s.specification</m:field>
				<m:field>i.quantity</m:field>
				<m:field>i.price</m:field><!--- *** не цена, а скидка (Считая ссылку на базовый тариф атрибутом сервиса) --->
				<m:field>v.svc</m:field>
				<m:field>v.code</m:field>
				<m:field>v.man
				
				</m:field>
				<m:field>e.updater_id</m:field>
				<m:field>u.login as updater_login</m:field>
				<m:field>u.shortname as updater_shortname</m:field>
				<m:field>cast(st.instance_data->>'params' as json)->>'resourceRealm' as resource_realm</m:field>				
				<m:field formatter=#request.castToBool#>coalesce((io.dt_start IS NOT NULL AND io.dt_finish IS NULL AND io.submit_result='201'),false) as operation_is_in_progress</m:field>
				<m:field formatter=#request.castToBool#>coalesce((io.dt_start IS NULL AND io.submit_result='201'),false) as operation_is_pending</m:field>	
				<m:field>case 
					when io.operation IN ('delete', 'suspend') AND io.is_successful then 0 /*null*/ 
					when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP)) 
					else 0 end 
				 as uptime</m:field>				
				<m:field>(case 					
					when st.instance_uid IS NULL then 'not created'
					when st.is_deleted = 'true' then 'deleted'
					when st.is_suspended = 'true' then 'suspended'
					/*when coalesce(st.is_deleted,'false') = 'false' AND coalesce(st.is_suspended, 'false') = 'false' then 'running'*/	
					when coalesce(st.is_deleted,'false') = 'false' AND coalesce(st.is_suspended, 'false') = 'false' then 'running'
					when io.instance_uid IS NULL then 'not configured'				
					else null end
				) as explained_status
				</m:field>	
				<m:field>(select count(distinct r.resource_realm)
				from resource_realm r
				join resource_realm_access a on (
				r.resource_realm_id=a.resource_realm_id 
				AND (a.contract_id IN (
					select contract_id 
					from contract d
					where d.contragent_id=k.contragent_id AND NOT d.is_closed	
				) OR a.contract_id=0) /*0 means access to any contract*/
				AND a.is_enabled)
				where r.resource_realm_type_id=v.resource_realm_type_id) as resource_realm_cnt 
				</m:field>
				<!--- <m:field>st.version</m:field> --->
			</m:field_set>	
			from instance e
			left outer join specification_item i on (e.specification_item_id=i.specification_item_id)		
			left outer join specification s on (i.specification_id=s.specification_id)		
			left outer join contract d on (s.contract_id=d.contract_id)		
			left outer join contragent k on (d.contragent_id=k.contragent_id)		
			left outer join svc v on (e.service_id=v.svc_id)
			left outer join usr u on (e.updater_id=u.usr_id)
			left outer join (select ist.instance_state_uid, ist.instance_uid, ist.version, ist.dt_state, ist.is_test
					,(ist.instance_data->>'isSuspended') as is_suspended
					,(ist.instance_data->>'isDeleted') as is_deleted 
					,ist.instance_data
					from instance_state ist join (select instance_uid, max(version) as version from instance_state group by 1) lastv 
						on (ist.instance_uid=lastv.instance_uid AND ist.version=lastv.version) ) st
					on (e.instance_uid=st.instance_uid)
				left outer join (select o.instance_operation_uid, o.instance_uid, o.operation, o.dt_submit, o.submit_result, o.dt_start, o.dt_finish, o.is_successful
					from instance_operation o join (select instance_uid, max(dt_submit) as dt_submit from instance_operation group by 1) lastv 
						on (o.instance_uid=lastv.instance_uid AND o.dt_submit=lastv.dt_submit) ) io
					on (e.instance_uid=io.instance_uid)
			where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
			AND s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/><!--- access protection --->
		</cfquery>	<!--- *** правильнее при попытке доступа к чужому инстансу (НСД) выбрасывать 403, а тут будет 404, но так тоже делают, например, в gitea --->
		
		<cfif local.qInstance.recordCount EQ 0>
			<cfreturn representationOf("Instance not found or no permission to view").withStatus(404)/>
		</cfif> 
		
		<!--- instanceData?.suspended
		instanceData?.out?.monitoring?.allDashboards
		instanceData?.params 
		instanceData?.out --->
		
		<cfquery name="local.qCurrentState" result="local.result">
			select 
			<m:field_set titleMapOut="local.currentStateTitleMap" lengthOut="local.fieldCount">
				<m:field>st.instance_state_uid::text as instance_state_uid</m:field>
				<m:field>st.version</m:field>
				<m:field>st.creator_id</m:field>
				<m:field>to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
				<m:field formatter=#request.castToBool#>st.is_test</m:field>
				<m:field>st.instance_operation_uid::text as instance_operation_uid</m:field>
				<!--- <m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field> --->				
				<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'out' as out</m:field>
				<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'params' as params</m:field>
				<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'vault' as vault</m:field>
				<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field>
				<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isSuspended')::boolean as is_suspended</m:field>
			</m:field_set>	
			from instance_state st
			where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
			order by st.version desc
			limit 1
		</cfquery><!--- берем local.qInstance.instance_uid ради изоляции (access protection) --->
		
	 
		<cfset local.isCreated = (local.qCurrentState.recordCount GT 0)/>
		<cfset local.isDeleted = (local.qCurrentState.is_deleted GT 0)/>
		
		<!--- <cfdump var=#local.qInstance#/><cfabort/> --->
		
		<!--- операция, создавшая текущее состояние --->
		<!--- *** странноватый селект --->
		<cfquery name="local.qOperation" result="local.result">
			select 
			<m:field_set titleMapOut="local.operationTitleMap" lengthOut="local.fieldCount">
				<m:field>o.instance_operation_uid::text as instance_operation_uid</m:field>
				<m:field>status.instance_state_uid::text as instance_state_uid</m:field>
				<m:field>status.version as state_version</m:field>
				<m:field>o.operation</m:field>
				<m:field>o.resource_realm_id</m:field>
				<m:field>r.resource_realm</m:field>
				<m:field>to_char(o.dt_submit, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_submit</m:field>
				<m:field>o.submit_result</m:field>
				<m:field>to_char(o.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
				<m:field>to_char(o.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
				<m:field>extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start) as duration</m:field>
				<m:field>round(extract('epoch' from CURRENT_TIMESTAMP - coalesce(o.dt_finish,CURRENT_TIMESTAMP)),1) as seconds_passed</m:field>
				<m:field formatter=#request.castToBool#>o.is_successful</m:field>
				<m:field title="dt_created">to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>o.error_log</m:field>
				<m:field>o.note</m:field>
				<m:field>u.login as updater_login</m:field>
				<m:field>u.shortname as updater_shortname</m:field>
				<m:field formatter=#request.castToBool#>coalesce((o.dt_start IS NOT NULL AND o.dt_finish IS NULL AND o.submit_result='201'),false) as is_in_progress</m:field>
				<m:field formatter=#request.castToBool#>coalesce((o.dt_start IS NULL AND o.submit_result='201'),false) as is_pending</m:field>
			</m:field_set>	
			from instance_operation o
			left outer join usr u on (o.updater_id=u.usr_id)
			left outer join resource_realm r on o.resource_realm_id=r.resource_realm_id
			left outer join (select instance_operation_uid, max(version) as version from instance_state
			group by instance_operation_uid) sts on (o.instance_operation_uid=sts.instance_operation_uid)
			left outer join instance_state status on (sts.instance_operation_uid=status.instance_operation_uid AND sts.version=status.version)
			where o.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
			--order by status.version desc
			order by o.dt_created desc
		</cfquery>
		<!--- *** А тут у нас не получится, что только операции с состояниями? Или в неправильном порядке? --->
		<!--- ?Добавить параметры к операции --->
		<!--- Разобраться в отличии текущей от последней. Может, нужна только одна? --->
		
		<!--- <d:field title="Операция">(select operation from instance_state st join instance_operation io on (st.instance_operation_uid=io.instance_operation_uid) where st.instance_uid=e.instance_uid order by version desc limit 1) as operation</d:field>
		<!--- текущая операция. Она еще не создала стейта и не завершена --->
		<d:field title="Текущая операция">(select io.operation from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as current_operation</d:field>	 --->
		
		<!--- *** Отработать блокировку --->
		<cfquery name="local.qAvailableOperation" result="local.result">
			select 
			<m:field_set titleMapOut="local.availableOperationTitleMap" lengthOut="local.fieldCount">
				<m:field>so.svc_operation_id</m:field>
				<m:field>so.operation</m:field>
				<m:field>
					(select instance_operation_uid::text
					from instance_operation io 
					where io.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
					AND io.operation=so.operation AND io.dt_submit IS NULL
					order by io.dt_created desc
					limit 1
					)as instance_operation_uid
				</m:field>
			</m:field_set>	
			from svc_operation so
			where so.svc_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#local.qInstance.service_id#" null=#!isValid('integer',local.qInstance.service_id)#/>
			<cfif local.isCreated>
			AND NOT so.operation='create'
			<cfelse>
			AND so.operation='create'
			</cfif>
			<cfif local.isDeleted>
			AND 1=0
			</cfif>
			order by so.svc_operation_id 
		</cfquery>

		<!--- Проблема зависимостей. Чтобы их увидеть, нам нужно состояние, а его нет в реляционном виде. 
		Значит, придется парсить стейт 
		но чтобы получить все сервисы, которые зависят от данного, придется им отпарсить все стейты, что нереально
		Значит, придется заняться нормализаций стейта (приведением в реляционный вид) - впрочем, ничего сильно сложного в этом нет
		А есть смысл делать это в одну строну? наверно, есть - чтобы не забираться в операцию
		--->
		
	
	
		
		<!--- *** здесь уместна параноидальная проверка прав --->
		<cfquery name="local.qDependency">		
			select 
			<m:field_set titleMapOut="local.dependencyTitleMap" lengthOut="local.fieldCount">
				<m:field>p.param</m:field>
				<m:field>sop.descr</m:field>
				<m:field>s.svc_id</m:field>
				<m:field>s.svc</m:field>
				<m:field>s.code</m:field>
				<m:field>sop.label</m:field>
				<m:field>p.param_value as uid</m:field>
				<m:field>r.display_name</m:field>
				<m:field>r.is_auxiliary</m:field>		
				<m:field>c.contract_id</m:field>
				<m:field>c.contract</m:field>
				<m:field>to_char(c.dt_contract, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_contract</m:field>
				<m:field>k.external_code</m:field>				
			</m:field_set>
			from instance_cfs_param p
			left outer join instance_operation_cfs_param iop on (p.instance_operation_cfs_param_uid=iop.instance_operation_cfs_param_uid)
			left outer join instance_operation o on (iop.instance_operation_uid=o.instance_operation_uid)
			left outer join instance e on (o.instance_uid=e.instance_uid)
			left outer join svc_operation so on (o.operation=so.operation AND e.service_id=so.svc_id)
			left outer join svc_operation_cfs_param sop on (so.svc_operation_id=sop.svc_operation_id AND p.param=sop.svc_operation_cfs_param)
			left outer join svc s on (sop.ref_svc_id=s.svc_id)
			
			join instance r on (p.param_value=r.instance_uid::text)
			left outer join specification_item si on (r.specification_item_id=si.specification_item_id)
			left outer join specification sp on (si.specification_id=sp.specification_id)
			left outer join contract c on (sp.contract_id=c.contract_id)
			left outer join contragent k on (c.contragent_id=k.contragent_id)
			where p.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#"/>
			AND sop.ref_svc_id > 0
			order by sop.sort, p.param
		</cfquery>
		
		<!--- *** здесь уместна параноидальная проверка прав 
		потому что можно сослаться на чужой инстанс и увидеть какие-то лишние детали--->
		<cfquery name="local.qDependentInstance">
			select 
			<m:field_set titleMapOut="local.dependentInstanceTitleMap" lengthOut="local.fieldCount">
				<m:field>p.param</m:field>
				<m:field>sop.descr</m:field>	
				<m:field>s.svc_id</m:field>
				<m:field>s.svc</m:field>				
				<m:field>s.code</m:field>
				<m:field>sop.label</m:field>
				<m:field>e.instance_uid::text as uid</m:field>
				<m:field>e.display_name</m:field>
				<m:field>e.is_auxiliary</m:field>		
				<m:field>c.contract_id</m:field>
				<m:field>c.contract</m:field>
				<m:field>to_char(c.dt_contract, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_contract</m:field>
				<m:field>k.external_code</m:field>
			</m:field_set>
			from instance_cfs_param p
			left outer join instance_operation_cfs_param iop on (p.instance_operation_cfs_param_uid=iop.instance_operation_cfs_param_uid)
			left outer join instance_operation o on (iop.instance_operation_uid=o.instance_operation_uid)
			left outer join instance e on (o.instance_uid=e.instance_uid)
			left outer join svc s on (e.service_id=s.svc_id)
			left outer join svc_operation so on (o.operation=so.operation AND e.service_id=so.svc_id)
			left outer join svc_operation_cfs_param sop on (so.svc_operation_id=sop.svc_operation_id AND p.param=sop.svc_operation_cfs_param)
			left outer join svc r on (sop.ref_svc_id=r.svc_id)			
			left outer join specification_item si on (e.specification_item_id=si.specification_item_id)
			left outer join specification sp on (si.specification_id=sp.specification_id)
			left outer join contract c on (sp.contract_id=c.contract_id)
			left outer join contragent k on (c.contragent_id=k.contragent_id)
			where 
			p.param_value=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.instanceUid#"/>
			AND r.svc_id=(select i.service_id 
				from instance i 
				where i.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
			)
		</cfquery>
		

		
		<!--- ---------------------------------------------------------------------------------- --->
		<!--- маскируем секреты *** это не очень похоже на то же самое в списке, там мы просто удаляем параметры --->
		<!--- *** подгоняем запрос под структуру из instance_operation_cfs_param --->
		<!--- а мы тут упускаем, что может быть несколько операций create, как правило из-за сломавшихся. Но параметры мы берем из текущего стейта, и операцию возьмем оттуда же --->
		
		<cfquery name="local.qCfsParam" result="local.result">
			select 
			 p.svc_operation_cfs_param
			,p.svc_operation_cfs_param_id
			,p.label
			,p.descr
			,p.is_sensitive
			,p.data_type
			,null as data_descriptor
			,(select instance_operation_cfs_param_uid 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)				
				where sop.svc_operation_cfs_param_id=p.svc_operation_cfs_param_id AND
					io.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/> <!--- уже лишнее ---> AND 
					io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qCurrentState.instance_operation_uid#" null=#!isValid('guid',local.qCurrentState.instance_operation_uid)#/>
				--limit 1 <!--- *** hotfix --->
			)
			from svc_operation_cfs_param p
			join svc_operation o on (p.svc_operation_id=o.svc_operation_id)
			join svc on (o.svc_id=svc.svc_id)			
			where svc.svc_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#qInstance.service_id#" null=#!isValid('integer',qInstance.service_id)#/>
			AND o.operation='create'	
		</cfquery>	
		
		<!--- *** сделано не так, как в instance_operation_cfs_param (а там тоже сделано так себе) --->
		<cfset local.params = structNew("linked")/>
		<cftry>
			<cfset local.params=deserializeJson(local.qCurrentState.params)/>
			<cfloop query="local.qCfsParam">
				<cfset var paramName = local.qCfsParam.svc_operation_cfs_param/>
				<cfif structKeyExists(local.params, paramName)>		
					<!--- маскируем секреты --->
					<cfif local.qCfsParam.is_sensitive>
						<cfset local.params[paramName]="********"/>
					<cfelseif local.qCfsParam.data_type EQ "map" OR local.qCfsParam.data_type EQ "map-fixed" OR local.qCfsParam.data_type EQ "array-map-fixed">
						<!--- Не разбираем вариант "sensitive map" --->
						<!--- <cfset processMap(local.qCfsParam)/> ---><!--- query passed by reference --->
						<!--- маскируем секреты в map --->
						<!--- *** сделано ненадежно, с расчетом на структуру qCfsParam (ради этого ее пришлось дополнить) --->
						<!--- что характерно, мы передаем запрос (с фокусом, установленным на текущую строку) по ссылке и прямо там ее патчим --->
						<cfset CreateObject("component", "instance_operation_cfs_param").processMap(local.qCfsParam)/>
					</cfif>					
				</cfif>	
			</cfloop>
			
			<!--- грубо и цинично патчим запрос --->
			<cfset local.qCurrentState.params = serializeJson(local.params)/>
			<cfcatch type="ANY"><!--- *** <cfrethrow/>---></cfcatch>
		</cftry>
		
		<!--- ---------------------------------------------------------------------------------- --->

		
		<cfset var out=structNew("linked")/>	
		

		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		
		<cfset "out.instance" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qInstance, this.helper.snake2camel
		)/>	
		<!--- <cfset "out.instance.uptime"=(local.qOperation.is_successful GT 0) ? local.qOperation.seconds_passed : 0/> --->
		<cfset "out.instance.isCreated"=local.isCreated/>
		<cfset "out.instance.isDeleted"=local.qCurrentState.is_deleted GT 0/>
		<!--- <cfset "out.instance.vaultData"=local.vaultData/> --->
		
		 <cfset "out.instance.state" = (local.qCurrentState.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.currentStateTitleMap, local.qCurrentState, this.helper.snake2camel) : {}/>	

		<cfset "out.instance.operations"=[]/>
		<cfloop query=#local.qOperation#>
			<cfset arrayAppend(out.instance.operations, this.helper.appendRecord(structNew("linked"), "", local.operationTitleMap, local.qOperation, this.helper.snake2camel))/>
		</cfloop>		
		
		<cfset "out.instance.availableOperations"=[]/>
		<cfloop query=#local.qAvailableOperation#>
			<cfset arrayAppend(out.instance.availableOperations, this.helper.appendRecord(structNew("linked"), "", local.availableOperationTitleMap, local.qAvailableOperation, this.helper.snake2camel))/>
		</cfloop>
		
		<!--- <cfset "out.instance.dependencies0"=#dependencies#/> --->
		
		<cfset "out.instance.dependentInstances"=[]/>
		<cfloop query=#local.qDependentInstance#>
			<cfset arrayAppend(out.instance.dependentInstances, this.helper.appendRecord(structNew("linked"), "", local.dependentInstanceTitleMap, local.qDependentInstance, this.helper.snake2camel))/>
		</cfloop>
		
		<cfset "out.instance.dependencies"=[]/>
		<cfloop query=#local.qDependency#>
			<cfset arrayAppend(out.instance.dependencies, this.helper.appendRecord(structNew("linked"), "", local.dependencyTitleMap, local.qDependency, this.helper.snake2camel))/>
		</cfloop>
		<!--- <cfset "out.instance.availableResourceRealms"=[]/>
		<cfloop query=#local.qAvailableResourceRealm#>
			<cfset arrayAppend(out.instance.availableResourceRealms, this.helper.appendRecord(structNew("linked"), "", local.realmTitleMap, local.qAvailableResourceRealm, this.helper.snake2camel))/>
		</cfloop> --->

		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<!--- <cfdump var=#local.qCurrentState#/>
		<cfdump var=#out#/>
		<cfabort/> --->
		<cfreturn representationOf(out) />
	</cffunction>	
	
	
	<cffunction name="patch">
		<cfargument name="instanceUid" type="string" required=true hint="type:guid"/>		
		<cfargument name="displayName" type="string" required=false hint="type:string"/>		
		<!--- <cfargument name="resourceRealmId" type="string" required=false hint="type:integer"/> --->		
		<cfargument name="descr" type="string" required=false default="" hint="type:varchar (no cleanup here!)"/>
		
		<!--- ******  проверка уникальности --->
		<cftry>			
			<cfset checkDisplayName(arguments.displayName, arguments.usrId, arguments.instanceUid)/><!---  как идея - офорить как обертку, возвращающую то же значение. Но будет соблазн еще оттримить иначе пофиксить, а это уже избыточная функция и должно усложнить название --->			
			<cfset this.helper.keyExistsAndValid(arguments, "instanceUid", "guid")/>
			<cfquery name="local.qSave">
				update instance set
				 dt_updated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,updater_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				<cfif structKeyExists(arguments,"displayName")>
					,display_name=<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.displayName)#" />
				</cfif>
<!--- 				<cfif structKeyExists(arguments,"displayName")>
					,resource_realm_id=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceRealmId#" />
				</cfif> --->
				<cfif structKeyExists(arguments,"descr")>
					,descr=<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.descr)#" />		
				</cfif>
				where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" />;
				--select @@rowcount as cnt;
			</cfquery>
			
<!--- 			<cfif local.qSave.cnt EQ 0>
				<cfreturn noData().withStatus(404, "Record Not Found") />
			</cfif> --->
		
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>

		<cfreturn noData().withStatus(204, "No Content") />
	</cffunction>
	
	<cffunction name="delete" hint="Удаление экземпляра сервиса по ключу. Допускается в статусе not configured">
		<cfargument name="instanceUid" type="string" required=true hint="type:guid"/>
		
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
				
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>		
		<cftry>		

			<cfquery name="local.qCheckAccess" result="local.result">
				select count(*) as cnt
				from instance e 
				join specification_item si on (e.specification_item_id=si.specification_item_id)
				where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
				AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
			</cfquery>	
			<cfif local.qCheckAccess.cnt EQ 0>
				<cfreturn representationOf(this.helper.formatMessage(
					"Instance not accessible or does not exist", 
					"Instance is not accessible for the current user (possibly does not belong to the default specification)")
				).withStatus(404)/>
			</cfif>		
			
			<!--- халява какая-то --->
			<cfquery name="local.qCheckOperation" result="local.result">
				select count(*) as cnt 
				from instance_operation io 		
				where io.dt_finish IS NULL AND
				io.submit_result like '2%' /*201 etc*/ AND
				io.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
			</cfquery>				
			<cfif local.qCheckOperation.cnt GT 0>
				<cfreturn representationOf(this.helper.formatMessage(
					"Operation already started", "Instance deletion disabled when operation is started")
				).withStatus(422)/>
			</cfif>			
			
			<cfquery name="local.qCheckState" result="local.result">
				select count(*) as cnt
				from instance_state st		
				where 
				st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
			</cfquery>				
			<cfif local.qCheckState.cnt GT 0>
				<cfreturn representationOf(this.helper.formatMessage(
					"Instance has a state", "Instance deletion disabled when having state")
				).withStatus(422)/>
			</cfif>	
		
			<!--- мы не включаем проверку доступа в саму операцию удаления, а выполняем отдельно --->
			<cfquery name="local.qSave">
				delete from instance_state
				where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>;
				delete from instance_operation
				where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>;
				delete from instance
				where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>;
			</cfquery>		
			
			<cfcatch type="any">
				<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/><!--- это не нужно показывать в продуктиве --->
			</cfcatch>
		</cftry>
		
		<cfreturn noData().withStatus(204, "No Content") />				
	</cffunction><!--- delete --->
	

	
	<!--- 	неудобно бегать из одного компонента в соседний при том, что разделение ответственности между ними получается довольно странное
	Решил, что логичнее повесить проверки уникальности и генерацию дефолтов на коллекцию, тем более и связей так меньше --->
	
	<cffunction name="checkDisplayName">	
		<cfargument name="displayName" required=true/>
		<cfargument name="usrId" type="numeric" required=true/>
		<cfargument name="instanceUid" type="guid" required=true/> 
		
		<cfset CreateObject("component", "instance_ls").checkDisplayName(arguments.displayName, arguments.usrId, arguments.instanceUid)/>
	</cffunction>	
	
</cfcomponent>

v1/resources/instance_default.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instances/default/{svcId}" hint="template for default instance">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cffunction name="get" hint="Шаблон экземпляра">
		<cfargument name="svcId" type="string" required=true hint="type:integer"/>
	
		<cftry>
			<cfset this.helper.validateField(arguments, "svcId", "integer")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfquery name="local.qSvc" result="local.result">
			select 
			<m:field_set titleMapOut="local.realmTitleMap" lengthOut="local.realmFieldCount">
				<m:field>s.svc_id</m:field>
				<m:field>s.svc</m:field>
				<m:field>s.descr</m:field>
				<m:field>s.man</m:field>
			</m:field_set>	
			from svc s			
			where s.svc_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#arguments.svcId#/> 
			order by 1
		</cfquery>
		
		
		<cfset var out = structNew("linked")/>
		<cfset "out.instance.svcId"="#arguments.svcId*1#"/>
		<cfset "out.instance.svc"="#local.qSvc.svc#"/>
		<cfset "out.instance.displayName"="#generateDefaultName(arguments.svcId,arguments.usrId)#"/>
		<cfset "out.instance.descr"="#local.qSvc.descr#"/>
		<cfset "out.instance.man"="#local.qSvc.man#"/>
		
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>	
		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>	
	
	<cffunction name="generateDefaultName">	
		<cfargument name="service_id" type="numeric"/> 
		<cfargument name="usr_id" type="numeric"/> 
		
		<cfreturn CreateObject("component", "instance_ls").generateDefaultName(arguments.service_id,arguments.usr_id)/>
	</cffunction> 

</cfcomponent>

v1/resources/instance_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instances">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>

	<!---спецификация полей, пригодных для фильтрации (?и сортировки)--->
	<cfset this.fieldsSpec={
		"instance_uid"={prefix="inr", type="guid"},
		"instance_config_dt_created"={prefix="inr", type="date"},
		"instance_config_dt_updated"={prefix="inr", type="date"},
		"display_name"={prefix="inr", type="string"},
		"service_id"={prefix="inr", type="integer"},
		"specification_item_id"={prefix="inr", type="integer"},
		"specification"={prefix="inr", type="string"},		
		"specification_id"={prefix="inr", type="integer"},
		"contract_id"={prefix="inr", type="integer"},
		"svc"={prefix="inr", type="string"},
		"code"={prefix="inr", type="string"},
		"updater_id"={prefix="inr", type="integer"},
		"updater_shortname"={prefix="inr", type="string"},
		"updater_login"={prefix="inr", type="string"},
		"last_operation"={prefix="inr", type="string"},
		"last_operation_uid"={prefix="inr", type="guid"},
		"submit_result"={prefix="inr", type="string"},
		"operation_dt_start"={prefix="inr", type="date"},
		"operation_dt_finish"={prefix="inr", type="date"},
		"duration"={prefix="inr", type="numeric"},
		"uptime"={prefix="inr", type="numeric"},
		"dt_deploy"={prefix="inr", type="date"},
		<!--- "instance_data"={prefix="inr", type="string"}, --->
		"monitoring_url"={prefix="inr", type="string"},
		"instance_state_uid"={prefix="inr", type="guid"},
		"is_deleted"={prefix="inr", type="string"},<!--- not bool --->
		"is_suspended"={prefix="inr", type="string"},<!--- not bool --->
		"job_is_pending"={prefix="inr", type="string"},<!--- not bool --->
		"resource_realm"={prefix="inr", type="string"},
		"resource_realm_type_id"={prefix="inr", type="integer"},
		"operation_is_in_progress"={prefix="inr", type="boolean"},
		"operation_is_pending"={prefix="inr", type="boolean"},
		"is_test"={prefix="inr", type="boolean"},
		"is_auxiliary"={prefix="inr", type="boolean"},
		"explained_status"={prefix="inr", type="string"},
		"dt_state"={prefix="inr", type="date"},
		"version"={prefix="inr", type="integer"}<!--- , 
		"io_instance_uid"={prefix="inr", type="integer"}, 
		"st_instance_uid"={prefix="inr", type="integer"} ---> 
	}
	/>
			

	<cffunction name="get" hint="Список экземпляров сервисов, принадлежащих данному клиенту">
		<cfargument name="pageSize" type="string" hint="type:integer" required=false default="100"/>	
		<cfargument name="page" type="string" hint="type:integer" required=false default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC (performance not assured on arbitrary sorting)" required=false default=""/>
		<cfargument name="fields" type="string" hint="type:string, description:comma-separated list of fields to limit output to, empty string or omitted for default field set"  required=false default=""/>
		<cfargument name="search" type="string" hint="type:string, free search (black box)" required=false default=""/>
		<!---сюда можно воткнуть спецификацию полей для сортировки и фильтрации, но только ручками, идея компактного кода еще более отдаляется--->
		
		<!---parse and validate request parameters--->
		<cfset local={}/>
		<cftry>
			<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
			<cfset this.helper.validateField(arguments, "page", "integer")/>
			
			<!---мы мирно игнорируем поля, отсутствующие в спецификации, что позволяет не делать исключения для orderBy и т.п.--->
			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			<cfset var order=this.helper.parseOrderBy(this.fieldsSpec, arguments.orderBy)/>	
	
			<cfset local.fieldsToOutput=""/>
			<!--- <cfif listLen(arguments.fields)> ---><!--- *** если идти от arguments.fields, будет лаконичнее --->
				<cfloop collection=#this.fieldsSpec# item="fld">
					<!--- <cfoutput>#this.helper.snake2camel(trim(fld))#</cfoutput> --->
					<!--- *** в данном случае мы некорректно ограничиваем состав выводимых полей
					полями, перечисленными в fieldSpec --->
					<cfif listFind(arguments.fields, this.helper.snake2camel(trim(fld)))
					OR len(arguments.fields) EQ 0 
					/* OR structKeyExists(order,this.helper.snake2camel(trim(fld))) */>
						<cfset local.fieldsToOutput=listAppend(local.fieldsToOutput,trim(fld))/>
					</cfif>
				</cfloop>
			<!--- </cfif> --->
			
			<cfset local.fieldsToQueryFor = local.fieldsToOutput/>
			<cfset local.fieldsShortList=""/>
			
			<!--- теперь добавим в перечень полей, которые нужно оставить, поля для фильтрации и сортировки --->
			<cfloop array=#filter# index="fltr">
				<!--- <cfset var fltr = structFind(filter,item)/> --->
				<cfif listLen(fltr.field,".") EQ 2>
					<cfset var fld = listGetAt(fltr.field,2,".")/>
				<cfelseif listLen(fltr.field,".") EQ 1>
					<cfset var fld = fltr.field/>
				<cfelse>
					<cfreturn representationOf(this.helper.formatBadRequestError("Ошибка разбора фильтра")).withStatus(500)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFind(local.fieldsToQueryFor,fld)>
					<cfset local.fieldsToQueryFor=listAppend(local.fieldsToQueryFor,fld)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFind(local.fieldsShortList,fld)>
					<cfset local.fieldsShortList=listAppend(local.fieldsShortList,fld)/>
				</cfif>
			</cfloop>
			<!--- *** Не нужно ли сохранять префикс? (на случай одноименных полей) --->
			<cfloop collection=#order# item="item">
				<cfset var ord = structFind(order,item)/>
				<cfif listLen(ord.fld,".") EQ 2>
					<cfset var fld = listGetAt(ord.fld,2,".")/>
				<cfelseif listLen(ord.fld,".") EQ 1>
					<cfset var fld = ord.fld/>
				<cfelse>
					<cfreturn representationOf(this.helper.formatBadRequestError("Ошибка разбора порядка сортировки")).withStatus(500)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFind(local.fieldsToQueryFor,fld)>
					<!--- в данном случае мы не пытаемся поддерживать позиционную сортировку --->
					<cfset local.fieldsToQueryFor=listAppend(local.fieldsToQueryFor,fld)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFind(local.fieldsShortList,fld)>
					<cfset local.fieldsShortList=listAppend(local.fieldsShortList,fld)/>
				</cfif>	
			</cfloop>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>		
	
		<cftry><!--- *** времянка, пока глотается ошибка 500 --->										
			<!--- Такая бодяга. Кастомные сериализаторы не умеют вложенных структур (поразительно, или я не нашел как). 
			Стандартный сериализатор не умеет выводить дату в ISO 8601. 
			Приходится колхозить и форматировать на стороне БД (насилу подобрал формат). 
			Также на стороне БД приходится конвертировать GUID, потому что драйвер pg jdbc его представляет как структуру из 2 чисел. 
			Еще jsonb как-то странно сериализуется, в 3 поля, вместо одного Value (решается преобразованием ::text)
			--->			
				
			<m:field_set titleMapOut="local.titleMap" 
					lengthOut="local.fieldCount" 
					nameListOut="local.definedRecordFields"
					listOut="local.definedQueryFields"
					fieldsToInclude=#local.fieldsToQueryFor#>				
				<m:field>to_char(e.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_created</m:field>
				<m:field>to_char(e.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_updated</m:field>
				<m:field>e.instance_uid::text as instance_uid</m:field>
				<m:field>e.display_name</m:field>
				<m:field>e.service_id</m:field>
				<m:field>e.specification_item_id</m:field>				
				<m:field formatter=#request.castToBool#>e.is_auxiliary</m:field>				
				<m:field>d.contract_id</m:field>
				<m:field>i.specification_id</m:field>
				<m:field>s.specification</m:field>
				<m:field>i.quantity</m:field>
				<m:field>i.price</m:field>
				<m:field>v.svc</m:field>
				<m:field>v.code</m:field>
				<m:field>v.resource_realm_type_id</m:field>
				<!--- <m:field>p.param_value as resource_realm</m:field> --->
				<m:field>e.updater_id</m:field>
				<m:field>u.login as updater_login</m:field>
				<m:field>u.shortname as updater_shortname</m:field>		
				<m:field>io.operation as last_operation</m:field>
				<m:field>io.instance_operation_uid::text as last_operation_uid</m:field>
				<m:field>io.submit_result as submit_result</m:field>
				
				<m:field formatter=#request.castToBool#>coalesce((io.dt_start IS NOT NULL AND io.dt_finish IS NULL AND io.submit_result='201'),false) as operation_is_in_progress</m:field>
				<m:field formatter=#request.castToBool#>coalesce((io.dt_start IS NULL AND io.submit_result='201'),false) as operation_is_pending</m:field>
				
				<m:field>to_char(io.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as operation_dt_start</m:field>
				<m:field>to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as operation_dt_finish</m:field>
				<m:field>round(extract('epoch' from coalesce(io.dt_finish,CURRENT_TIMESTAMP)- io.dt_start),1) as duration</m:field>
				
				<m:field>case 
					when io.operation IN ('delete', 'suspend') AND io.is_successful then 0 /*null*/ 
					when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP)) 
					else 0 end 
				 as uptime</m:field>
				
				<m:field>to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_deploy</m:field>
				<!--- <m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field> ---><!--- *** операция выглядит очень накладно, но можно это поле в дельнейшем не выводить --->
				<m:field>st.instance_state_uid::text as instance_state_uid</m:field>
				<m:field formatter=#request.castToBool#>st.is_test as is_test</m:field>
				<m:field formatter=#request.castToBool#>coalesce(st.instance_data->>'isDeleted','false') as is_deleted</m:field><!--- attention it is not bool in state --->
				<m:field formatter=#request.castToBool#>st.instance_data->>'isSuspended' as is_suspended</m:field>
				<m:field formatter=#request.castToBool#>st.instance_data->'job'->>'pending' as job_is_pending</m:field>
				<m:field>st.instance_data->'out'->'monitoring'->>'resourceMetrics' as monitoring_url</m:field>
				<m:field>st.instance_data->'params'->>'resourceRealm' as resource_realm</m:field>
				<m:field>to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>	
<!--- 				<m:field>io.instance_uid::text as io_instance_uid</m:field>	
				<m:field>st.instance_uid::text as st_instance_uid</m:field>	 --->
				<m:field>(case 					
					when st.instance_uid IS NULL then 'not created'
					when job_is_pending = 'true' then 'pending'
					when st.is_deleted = 'true' then 'deleted'
					when st.is_suspended = 'true' then 'suspended'
					when coalesce(st.is_deleted,'false') = 'false' AND coalesce(st.is_suspended, 'false') = 'false' then 'running'
					when io.instance_uid IS NULL then 'not configured'				
					else null end
				) as explained_status</m:field>		
				<m:field>st.version</m:field>
			</m:field_set>	

		
			<cfquery name="local.qCnt" result="local.result">
			select count(*) as cnt
			from (
				select 
				<!--- из-за фильтраци по вложенному селекту нам нужны значения колонок --->
				<cfif arrayIsEmpty(filter)>
					'' as placeholder
				<cfelse>
					#preserveSingleQuotes(definedQueryFields)#
				</cfif>
				from instance e
				join specification_item i on (e.specification_item_id=i.specification_item_id)		
				join specification s on (i.specification_id=s.specification_id)		
				join contract d on (s.contract_id=d.contract_id)		
				join contragent k on (d.contragent_id=k.contragent_id)
				join svc v on (e.service_id=v.svc_id)
				left outer join usr u on (e.updater_id=u.usr_id)
				left outer join (select ist.instance_state_uid, ist.instance_uid, ist.version, ist.dt_state, ist.is_test
					,(ist.instance_data->>'isSuspended') as is_suspended
					,(ist.instance_data->>'isDeleted') as is_deleted 
					,(ist.instance_data->'job'->>'pending') as job_is_pending
					,(ist.instance_data->'out'->'monitoring'->>'resourceMetrics') as monitoring_url			
					,ist.instance_data
					from instance_state ist 
					join (select instance_uid, max(version) as version from instance_state group by 1) lastv 
						on (ist.instance_uid=lastv.instance_uid AND ist.version=lastv.version) 
					) st on (e.instance_uid=st.instance_uid)
				left outer join (select o.instance_operation_uid, o.instance_uid, o.operation, o.dt_submit, o.submit_result, o.dt_start, o.dt_finish, o.is_successful
					from instance_operation o join (select instance_uid, max(dt_submit) as dt_submit from instance_operation group by 1
					) lastv on (o.instance_uid=lastv.instance_uid AND o.dt_submit=lastv.dt_submit) 
				) io on (e.instance_uid=io.instance_uid)
				<!--- left outer join instance_cfs_param p on (e.instance_uid=p.instance_uid AND p.param='resourceRealm') --->
				where s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
				<cfif len(arguments.search)>
					AND (
					(e.display_name) ilike (<cfqueryparam cfsqltype="cf_sql_varchar" value='%#arguments.search#%'/>)
					OR ((coalesce(v.svc,'') ||' '|| coalesce(v.svc_short,'') ||' '|| coalesce(v.synonyms,''))) ilike (<cfqueryparam cfsqltype="cf_sql_varchar" value='%#arguments.search#%'/>)
					)
				</cfif>			
			) inr
			where 1=1 <m:filter_build filter=#filter#/>
		</cfquery>
		
		<!--- <cfdump var=#filter#/><cfabort/> --->
		
		
		<cfquery name="local.qRead" result="local.result">
			select 
			<cfif listLen(local.fieldsToOutput)>#local.fieldsToOutput#<cfelse>*</cfif> 
			from (
			select
				#preserveSingleQuotes(definedQueryFields)#	
			from instance e
			join specification_item i on (e.specification_item_id=i.specification_item_id)		
			join specification s on (i.specification_id=s.specification_id)		
			join contract d on (s.contract_id=d.contract_id)		
			join contragent k on (d.contragent_id=k.contragent_id)
			join svc v on (e.service_id=v.svc_id)
			left outer join usr u on (e.updater_id=u.usr_id)
			left outer join (select ist.instance_state_uid, ist.instance_uid, ist.version, ist.dt_state, ist.is_test
				,(ist.instance_data->>'isSuspended') as is_suspended
				,(ist.instance_data->>'isDeleted') as is_deleted 
				,(ist.instance_data->'job'->>'pending') as job_is_pending
				,ist.instance_data
				from instance_state ist join (select instance_uid, max(version) as version from instance_state group by 1) lastv 
					on (ist.instance_uid=lastv.instance_uid AND ist.version=lastv.version) ) st
				on (e.instance_uid=st.instance_uid)
			left outer join (select o.instance_operation_uid, o.instance_uid, o.operation, o.dt_submit, o.submit_result, o.dt_start, o.dt_finish, o.is_successful
				from instance_operation o join (select instance_uid, max(dt_submit) as dt_submit from instance_operation group by 1) lastv 
					on (o.instance_uid=lastv.instance_uid AND o.dt_submit=lastv.dt_submit) ) io
				on (e.instance_uid=io.instance_uid)				
			<!--- left outer join instance_cfs_param p on (e.instance_uid=p.instance_uid AND p.param='resourceRealm') --->
			where s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/><!--- *** make project entity --->
			<!--- u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/> --->			
			<cfif len(arguments.search)>
				AND (
					(e.display_name) ilike (<cfqueryparam cfsqltype="cf_sql_varchar" value='%#arguments.search#%'/>)
					OR ((coalesce(v.svc,'') ||' '|| coalesce(v.svc_short,'') ||' '|| coalesce(v.synonyms,''))) ilike (<cfqueryparam cfsqltype="cf_sql_varchar" value='%#arguments.search#%'/>)
					)
			</cfif>
			
			) inr
			where 1=1 <m:filter_build filter=#filter#/>
			<!--- order by <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/> ---><!--- из-за нефиксированного числа колонок труднее становится использовать цифровую нотацию --->
			order by <m:order_build sortCollection=#order# fieldCount=0 defaultOrder="1 desc"/><!---no sort length limit--->
			limit #arguments.pageSize#
			offset #arguments.pageSize*(arguments.page-1)#
		</cfquery>
		<!---<cfdump var=#arguments#/><cfdump var=#local.qRead#/><cfabort/>  --->
		
		<cfquery name="local.qTotal">
			select count(*) as cnt
			from instance e
			join specification_item i on (e.specification_item_id=i.specification_item_id)		
			join specification s on (i.specification_id=s.specification_id)		
			join contract d on (s.contract_id=d.contract_id)		
			join contragent k on (d.contragent_id=k.contragent_id)
			join svc v on (e.service_id=v.svc_id)
			
			left outer join (select ist.instance_state_uid, ist.instance_uid, ist.version, ist.dt_state, ist.is_test
				,(ist.instance_data->>'isSuspended') as is_suspended
				,(ist.instance_data->>'isDeleted') as is_deleted 
				,ist.instance_data
				from instance_state ist join (select instance_uid, max(version) as version from instance_state group by 1) lastv 
				on (ist.instance_uid=lastv.instance_uid AND ist.version=lastv.version) ) st
			on (st.instance_uid=e.instance_uid)
			left outer join (select o.instance_operation_uid, o.instance_uid, o.operation, o.dt_submit, o.submit_result, o.dt_start, o.dt_finish, o.is_successful
				from instance_operation o join (select instance_uid, max(dt_submit) as dt_submit from instance_operation group by 1) lastv 
				on (o.instance_uid=lastv.instance_uid AND o.dt_submit=lastv.dt_submit) ) io
			on (e.instance_uid=io.instance_uid)
			
			<!--- specificationId инжектируется в arguments scope в Application.cfc --->
			where  s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
			<!--- u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/>	 --->
		</cfquery> 
		
		<cfset var out=structNew("linked")/>

		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		<cfset "out.total"=#local.qTotal.cnt#/>
		<cfset "out.resultSetSize"=#local.qCnt.cnt#/>
		<cfset "out.pageSize"=#arguments.pageSize*1#/>
		<cfset "out.page"=#arguments.page*1#/>
		<cfset "out.orderBy"=#arguments.orderBy#/>	
		
		<cfset var resultCollection=[]/>
		<cfloop query=#local.qRead# <!--- startRow=#startrow# endRow=#(startrow+maxrows-1)# --->>
			<cfset var rec=structNew("linked")/>				
			<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
			<cfset arrayAppend(resultCollection, rec)/>
		</cfloop>
		
		<cfset "out.size"=#arrayLen(resultCollection)#/>		
		<cfset "out.results"=#resultCollection#/>		
		<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
		<!---<cfset "out.sql"=#local.result.sql#/>--->	
		<cfreturn representationOf(out)/>
		
		<cfcatch type="any">		<!--- {"message"=cfcatch.message, "detail"=cfcatch.detail} --->
				<cfreturn representationOf(cfcatch).withStatus(500)/>				
		</cfcatch>
		</cftry>
	</cffunction><!--- get --->	
	
	
	<cffunction name="post" hint="ВНИМАНИЕ! У текущего контрагента должна быть спецификация, в которую будет вписан создаваемый экземпляр. Новая пустая запись экземпляра. Фактическое развертывание выполняется отдельно,асинхронной операцией create сконфигурированного экземпляра. Одновременно мы создаем строку в спецификации по умолчанию и связываем экземпляр с этой строкой. Первичный ключ генерируется. Текущий пользователь, контракт, спецификация получаются из контекста.">
		<cfargument name="displayName" type="string" required=true hint="type:string"/>		
		<cfargument name="serviceId" type="string" required=true hint="type:integer"/>	
		<cfargument name="descr" type="string" required=false default="" hint="type:varchar (no cleanup here!)"/>	
		<cfargument name="resourceRealmId" type="string" required=false default="-1" hint="type:integer"/>	
		<!--- <cfargument name="instanceUid" type="string" required=false default="" hint="type:guid)"/> 		
		<cfargument name="usrUid" type="string" required=true hint="type:guid"/>		
		<cfargument name="companyUid" type="string" required=true hint="type:guid"/>	 --->
		<!--- оказывается, их можно и не декларировать	 --->
		
		
		<cfif !structKeyExists(arguments, "specificationId")>
			<cfreturn representationOf("arguments.specificationId not defined").withStatus(500)/>
		</cfif>		
		<cfif !isValid("integer", arguments.specificationId)>
			<cfreturn representationOf("arguments.specificationId is not a valid integer (specification not defined)").withStatus(500)/>
		</cfif>		
		<cfif !(arguments.specificationId GT 0)>
			<cfreturn representationOf("specification not defined (arguments.specificationId should be a posititve integer)").withStatus(500)/>
		</cfif>
		
		<cfset var local={}/>
		

		<cfset local.instanceUid=#createGUID()#/>		
		
		<cftry>
			<cfset this.helper.keyExistsAndValid(arguments, "serviceId", "integer")/>	
			<cfset checkDisplayName(arguments.displayName, arguments.usrId, local.instanceUid)/>
			
			<!--- делаем вставку в спецификацию и таблицу экземпляров --->
			<cfquery name="local.qSpecItem">
				insert into specification_item
				(specification_id, specification_item, svc_id, quantity, price, dt_created, creator_id, dt_updated, updater_id) 
				values
				(
				 <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.specificationId#" />				 
				,<cfqueryparam cfsqltype="cf_sql_varchar" value=""/>	
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.serviceId#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="1" /> <!--- это типично для всех PAYG --->
				,<cfqueryparam cfsqltype="cf_sql_numeric" null=true />
				,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				) returning specification_item_id;
			</cfquery>

			<cfquery name="local.qSave">
				insert into instance
				(instance_uid, display_name, service_id, specification_item_id, descr, dt_created, creator_id, dt_updated, updater_id) 
				values
				(
				 <cfqueryparam cfsqltype="cf_sql_other" value="#local.instanceUid#" />
				,<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.displayName)#"/>	
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.serviceId#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#local.qSpecItem.specification_item_id#" />
				,<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.descr)#" />
				,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				);
			</cfquery>					
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>

		<cfreturn noData()
			.withHeaders({location: "./#local.instanceUid#"})
			.withStatus(201, "Created") 
		/>		
	</cffunction><!--- post --->	

	
	
	<cffunction name="checkDisplayName">
		<cfargument name="displayName" required=true/>
		<cfargument name="usrId" type="numeric" required=true/>
		<cfargument name="instanceUid" type="guid" required=true/>
		
		<cfset var local={}/>
		<cfset var display_name=trim(lcase(arguments.displayName))/>		
		
		<cfif len(display_name) LT 3>
			<cfthrow type="InvalidParamValue" message="Instance Display Name too short" detail="Имя должно быть не короче 3 символов"/>
		<cfelseif len(display_name) GT 80>
			<cfthrow type="InvalidParamValue" message="Instance Display Name too long" detail="Имя должно быть не длиннее 80 символов"/>	
		<cfelse>
			<cfquery name="local.qUniqueDisplayName">
				select count(*) as cnt from instance e
				join specification_item i on (e.specification_item_id=i.specification_item_id)
				join specification s on (i.specification_id=s.specification_id)
				join contract d on (s.contract_id=d.contract_id)
				join contragent k on (d.contragent_id=k.contragent_id)
				join usr u on (k.contragent_id=u.contragent_id)	
				where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/>
				AND lower(e.display_name)=lower(<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.displayName#/>)
				AND e.instance_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceUid#/>
				AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) IS NOT TRUE
				/*postgre specific. should take into account instances without state, ignoring explicitly deleted only*/
			</cfquery>
			<cfif local.qUniqueDisplayName.cnt GT 0>
				<cfthrow type="InvalidParamValue" message="Instance Display Name not unique" detail="Имя экземпляра должно быть уникальным в рамках договора"/>
			</cfif>
		</cfif>
	</cffunction>
	
	<cffunction name="generateDefaultName" returnType="string">
	<!--- *** Это некорректно, потому что имя может содержать пробелы и т.п. --->
	<!--- *** Неуклюже и медленно --->
	<!--- Эти валидации и генерации занимают удивительно много рабочего времени --->
		<cfargument name="service_id" type="numeric"/>
		<cfargument name="usr_id" type="numeric"/>
		
		<cfset var local={}/>
		
		<cfquery name="local.qSvc">
			select coalesce(svc_short,svc) as svc_short from svc where svc_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.service_id#/>	
		</cfquery>
		
		<cfquery name="local.qInst">
			select display_name 
			from instance e
			join specification_item i on (e.specification_item_id=i.specification_item_id)
			join specification s on (i.specification_id=s.specification_id)
			join contract d on (s.contract_id=d.contract_id)
			join contragent k on (d.contragent_id=k.contragent_id)
			join usr u on (k.contragent_id=u.contragent_id)	
			where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usr_id#/>
			AND lower(e.display_name) like lower(<cfqueryparam cfsqltype="cf_sql_varchar" value="#local.qSvc.svc_short#%"/>)
			AND service_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.service_id#/>
		</cfquery>	
		
		<cfset var prefixLen=len(local.qSvc.svc_short)+1/>
		<cfset var i=1/>	
		<cfloop query="local.qInst">
			<cfif len(local.qInst.display_name) GT prefixLen>
				<cfset var suffix=right(local.qInst.display_name, len(local.qInst.display_name)-prefixLen)/>
				<cfif isValid("integer",suffix)>
					<cfset i=(i GT int(suffix))?i:int(suffix)/>
				</cfif>
			</cfif>
		</cfloop>
		
		<cfreturn "#local.qSvc.svc_short#-#i+1#"/>
	</cffunction>

</cfcomponent>

<!--- 
select
updater_shortname,duration,specification_id,display_name,contract_id,instance_config_dt_created,operation_is_pending,code,updater_login,operation_is_in_progress,instance_config_dt_updated,is_auxiliary,svc,service_id,dt_state,last_operation,submit_result,instance_state_uid,dt_deploy,is_test,specification_item_id,is_deleted,version,resource_realm,updater_id,operation_dt_start,operation_dt_finish,explained_status,last_operation_uid,uptime,instance_uid,suspended,instance_data,specification
from (
select
to_char(e.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_created,to_char(e.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_updated,e.instance_uid::text as instance_uid,e.display_name,e.service_id,e.specification_item_id,e.is_auxiliary,d.contract_id,i.specification_id,s.specification,v.svc,v.code,e.updater_id,u.login as updater_login,u.shortname as updater_shortname,(select io.operation from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as last_operation,(select io.instance_operation_uid::text from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as last_operation_uid,(select io.submit_result from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as submit_result,(select coalesce((io.dt_start IS NOT NULL AND io.dt_finish IS NULL AND io.submit_result='201'),false) from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_is_in_progress,(select coalesce((io.dt_start IS NULL AND io.submit_result='201'),false) from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_is_pending,(select to_char(io.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_dt_start,(select to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as operation_dt_finish,(select round(extract('epoch' from coalesce(io.dt_finish,CURRENT_TIMESTAMP)- io.dt_start),1) from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as duration,(select case
when io.operation IN ('delete', 'suspend') AND io.is_successful then 0
when io.is_successful then extract('epoch' from CURRENT_TIMESTAMP - coalesce(io.dt_finish,CURRENT_TIMESTAMP))
else 0 end
from instance_operation io
where io.instance_uid=e.instance_uid
order by dt_submit desc
limit 1)
as uptime,(select to_char(io.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_operation io where io.instance_uid=e.instance_uid AND io.is_successful AND io.operation='create' order by dt_submit desc limit 1) as dt_deploy,(select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data,(select st.instance_state_uid::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_state_uid,(select st.is_test from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as is_test,coalesce((select st.instance_data->>'isDeleted' from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1),'false') as is_deleted,(select st.instance_data->>'isSuspended' from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as suspended,(select cast(st.instance_data->>'params' as json)->>'resourceRealm' from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as resource_realm,(select to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as dt_state,
(case
when
(select st.instance_data->>'isDeleted' from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) = 'true' then 'deleted'
when
(select st.instance_data->>'isSuspended' from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) = 'true' then 'suspended'
when
(select coalesce(st.instance_data->>'isDeleted','false') from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) = 'false'
AND
(select coalesce(st.instance_data->>'isSuspended','false') from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) = 'false'
then 'running'
when
not exists (select * from instance_operation o where o.instance_uid=e.instance_uid) then 'not configured'
when
not exists (select * from instance_state st where st.instance_uid=e.instance_uid) then 'not created'
else null end
) as explained_status
,(select max(version) from instance_state st where st.instance_uid=e.instance_uid) as version
from instance e
join specification_item i on (e.specification_item_id=i.specification_item_id)
join specification s on (i.specification_id=s.specification_id)
join contract d on (s.contract_id=d.contract_id)
join contragent k on (d.contragent_id=k.contragent_id)
join svc v on (e.service_id=v.svc_id)
left outer join usr u on (e.updater_id=u.usr_id)
where s.specification_id=2
) inr
where 1=1
order by 1 desc
limit 100
offset 100
 --->

v1/resources/instance_operation.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations/{instanceOperationUid}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>


	<cffunction name="get" hint="Операция над экземпляром сервиса">
		<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
		
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationUid", "guid")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset var out={}/>

		<cfquery name="local.qInstanceOperation" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field>o.instance_operation_uid::text as instance_operation_uid</m:field>
				<m:field>o.operation</m:field>
				<m:field>o.instance_uid::text as instance_uid</m:field>
				<m:field>to_char(o.dt_submit, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_submit</m:field>
				<m:field>o.submit_result</m:field>
				<m:field>o.error_log</m:field>
				<m:field>o.note</m:field>
				<m:field>to_char(o.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
				<m:field>to_char(o.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
				<m:field title="Длит. сек">extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start) as duration</m:field>
				<m:field formatter=#request.castToBool#>o.is_successful</m:field>
				<m:field>e.display_name</m:field>
				<m:field>e.service_id</m:field>
				<m:field>e.specification_item_id</m:field>
				<m:field>to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>	
				<m:field>to_char(o.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>	
				<m:field>o.updater_id</m:field>
				<m:field>u.login as updater_login</m:field>
				<m:field>u.shortname as updater_shortname</m:field>
				<m:field>v.svc</m:field>
				<m:field>so.man</m:field>
				<m:field>so.svc_operation_id</m:field>
				<m:field formatter=#request.castToBool#>coalesce((o.dt_start IS NOT NULL AND o.dt_finish IS NULL AND o.submit_result='201'),false) as is_in_progress</m:field>
				<m:field formatter=#request.castToBool#>coalesce((o.dt_start IS NULL AND o.submit_result='201'),false) as is_pending</m:field>
			</m:field_set>	
			from instance_operation o
			join instance e on (o.instance_uid=e.instance_uid)
			join specification_item i on (e.specification_item_id=i.specification_item_id)		
			join specification s on (i.specification_id=s.specification_id)		
			join contract d on (s.contract_id=d.contract_id)		
			join contragent k on (d.contragent_id=k.contragent_id)
			join svc v on (e.service_id=v.svc_id)
			join svc_operation so on (v.svc_id=so.svc_id AND o.operation=so.operation)
			left outer join usr u on (o.updater_id=u.usr_id)
			where
			 o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
			AND s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
		</cfquery>	
		
 		<cfif local.qInstanceOperation.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
		</cfif> 
		
		<cfquery name="local.qStage" result="local.result">
			select 
			<m:field_set titleMapOut="local.stageMap" lengthOut="local.fieldCount">
				<m:field>ios.instance_operation_stage_uid::text as instance_operation_stage_uid</m:field>
				<m:field>ios.stage</m:field>
				<m:field formatter=#request.castToBool#>ios.is_successful</m:field>	
				<m:field>to_char(ios.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
				<m:field>to_char(ios.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
				<m:field>round(extract('epoch' from coalesce(ios.dt_finish,CURRENT_TIMESTAMP)- ios.dt_start),1) as duration</m:field>
				<m:field>ios.stage_msg</m:field>
			</m:field_set>	
			from instance_operation_stage ios
			where ios.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstanceOperation.instance_operation_uid#" null=#!isValid('guid',local.qInstanceOperation.instance_operation_uid)#/>
			order by dt_start asc
		</cfquery>
		
 		<cfquery name="local.qState" result="local.result">
			select 
			<m:field_set titleMapOut="local.stateTitleMap" lengthOut="local.fieldCount">
				<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
				<m:field title="v.">st.version</m:field>
				<m:field title="creator_id">st.creator_id</m:field>
				<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
				<m:field formatter=#request.castToBool#>st.is_test</m:field>
				<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
				<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
			</m:field_set>		
			from instance_state st
			where st.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstanceOperation.instance_operation_uid#" null=#!isValid('guid',local.qInstanceOperation.instance_operation_uid)#/>
			order by st.version desc limit 1
		</cfquery>  		
		
		<cfquery name="local.qCurrentState" result="local.result">
			select 
			<m:field_set titleMapOut="local.stateTitleMap" lengthOut="local.fieldCount">
				<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
				<m:field title="v.">st.version</m:field>
				<m:field title="creator_id">st.creator_id</m:field>
				<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
				<m:field formatter=#request.castToBool#>st.is_test</m:field>
				<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
				<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
			</m:field_set>		
			from instance_state st
			where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstanceOperation.instance_uid#" null=#!isValid('guid',local.qInstanceOperation.instance_uid)#/>
			order by st.version desc limit 1
		</cfquery> 
		
		<cfset var currentState = {}/>
		<cftry>
			<cfset currentState = deserializeJson(local.qCurrentState.instance_data)/>
			<cfcatch type="ANY">
				<cfset currentState = {}/>
			</cfcatch>
		</cftry>
		
		 <cfquery name="local.qCfsParam" result="local.result">
			select 
				<m:field_set titleMapOut="local.cfsParamTitleMap" lengthOut="fieldCount">
				<m:field title="instance_operation_cfs_param_uid">iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid</m:field>
				<m:field>sop.svc_operation_cfs_param_id</m:field>
				<m:field>case when sop.is_sensitive then '********' else iop.param_value end as param_value </m:field>
				<m:field>to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>iop.creator_id</m:field>
				<m:field>sop.svc_operation_cfs_param</m:field>				
				<m:field>sop.label</m:field>
				<m:field>sop.data_type</m:field>
				<m:field>null as data_descriptor</m:field>
				<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#>sop.value_list</m:field>
				<m:field>sop.ref_svc_id</m:field>
				
				<m:field>sop.func</m:field>
				<m:field>sop.nested_ref</m:field>
				<m:field>sop.state_path</m:field>
				<m:field>sop.expression</m:field>
				<m:field>sop.depends_on_cfs_params</m:field>
				
				<m:field formatter=#request.castToBool#>sop.is_required</m:field>
				<m:field>sop.default_value</m:field>
				<m:field>sop.regex</m:field>
				<m:field>sop.unique_scope</m:field>
				<m:field>sop.maxlength</m:field>
				<m:field>sop.minlength</m:field>				
				<m:field>sop.maxvalue</m:field>
				<m:field>sop.minvalue</m:field>
				<m:field>sop.descr</m:field>
				<m:field>sop.man</m:field>
				<m:field>sop.sort</m:field>
				<m:field>iop.note</m:field>				
				<m:field>sop.config::text as config</m:field>
				<m:field>null as nested_ref_data</m:field>
				<m:field formatter=#request.castToBool#>sop.is_sensitive</m:field>
			</m:field_set>
			from svc_operation_cfs_param sop
			left outer join instance_operation_cfs_param iop 
			on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id 
				AND iop.instance_operation_uid = <cfqueryparam cfsqltype="cf_sql_other" 
					value="#local.qInstanceOperation.instance_operation_uid#" 
					null=#!isValid('guid',local.qInstanceOperation.instance_operation_uid)#/>
				)
			where sop.svc_operation_id = <cfqueryparam cfsqltype="cf_sql_integer" 
					value=#local.qInstanceOperation.svc_operation_id# 
					null=#!isValid('integer',local.qInstanceOperation.svc_operation_id)#/>
			order by sop.sort, sop.svc_operation_cfs_param_id
		</cfquery> 		
		
		<cfloop query="local.qCfsParam">
			<cfif len(func)>
				<cfset var args = {
					"functionName":"#func#", 
					"svcId":#local.qInstanceOperation.service_id#, 
					"contractId":#arguments.contractId#
				}/>
				<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.qCfsParam.value_list"/>
				<cfif len(trim(local.qCfsParam.value_list)) AND listLen(local.qCfsParam.value_list) EQ 1>
					<cfset local.qCfsParam.default_value=local.qCfsParam.value_list/>
				</cfif>
			<cfelseif len(local.qCfsParam.nested_ref)>
				<cfset var args = {
					"functionName":"#local.qCfsParam.nested_ref#", 
					"config":"#local.qCfsParam.config#"
				}/>
				<cfinvoke component="instance_operation_cfs_param" method="generateClosure" argumentCollection=#args# returnVariable="local.fNestedRef"/>
				<cfset local.qCfsParam.nested_ref_data = local.fNestedRef() />
			<cfelseif len(local.qCfsParam.state_path) AND isStruct(currentState)>
				<cfset var args = {
					"state":#currentState#,
					"path":"#local.qCfsParam.state_path#"
				}/>
				<cfinvoke component="instance_operation_cfs_param" 
					method="getListFromState" 
					argumentCollection=#args#
					returnVariable="local.qCfsParam.value_list"
				/> 
			<cfelseif len(local.qCfsParam.expression) AND len(local.qCfsParam.instance_operation_cfs_param_uid)>
				<cfset local.qCfsParam.value_list = createObject("component","lib.expression_parser")
				.eval(
					local.qCfsParam.expression,
					{/*context*/
						component:"resources.instance_operation_cfs_param",
						keys:{"instanceOperationCfsParamUid"=#local.qCfsParam.instance_operation_cfs_param_uid#},
						params:{}
					}
				)
			/>
			</cfif>

			<cfif (len(local.qCfsParam.func) OR len(local.qCfsParam.expression) OR len(local.qCfsParam.state_path) OR len(local.qCfsParam.nested_ref)) 
				AND len(local.qCfsParam.value_list) EQ 0>				
				<cfset local.qCfsParam.value_list = []/>
			</cfif>
		
			<cfif data_type EQ "map" OR data_type EQ "map-fixed" OR data_type EQ "array-map-fixed">
				<cfinvoke component="instance_operation_cfs_param" method="processMap" qParam=#local.qCfsParam#/>				
			</cfif>
		</cfloop>	
		
		
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>		
		
		<cfset "out.instanceOperation" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qInstanceOperation, this.helper.snake2camel
		)/>	
		
		<cfset "out.instanceOperation.cfsParams"=[]/>
		<cfloop query=#local.qCfsParam#>
			<cfset arrayAppend(out.instanceOperation.cfsParams, this.helper.appendRecord(structNew("linked"), "", local.cfsParamTitleMap, local.qCfsParam, this.helper.snake2camel))/>
		</cfloop>
				
		<cfset "out.instanceOperation.stages"=[]/>
		<cfloop query=#local.qStage#>
			<cfset arrayAppend(out.instanceOperation.stages, this.helper.appendRecord(structNew("linked"), "", local.stageMap, local.qStage, this.helper.snake2camel))/>
		</cfloop>
		
		<cfset "out.instanceOperation.state"=(local.qState.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.stateTitleMap, local.qState, this.helper.snake2camel) : {}/>
		
		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>	
	
	
	<cffunction name="validateCfsParams" returntype="string">
		<cfargument name="instanceOperationUid" type="guid" required="true"/>
		<cfset var local={}/>
		<cfquery name="local.qSvcOperationParam">
			select sop.svc_operation_cfs_param_id, sop.svc_operation_cfs_param, sop.unique_scope 
			from svc_operation_cfs_param sop 
			join instance_operation_cfs_param iop on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id)
			where iop.instance_operation_uid = <cfqueryparam cfsqltype="CF_SQL_OTHER" value=#arguments.instanceOperationUid#/>
		</cfquery>
		<cfquery name="local.qInstance">
			select io.instance_uid
			from instance_operation io
			join instance_operation_cfs_param iop on (io.instance_operation_uid=iop.instance_operation_uid)
			where iop.instance_operation_uid = <cfqueryparam cfsqltype="CF_SQL_OTHER" value=#arguments.instanceOperationUid#/>
		</cfquery>
		
		<cfloop query="local.qSvcOperationParam">
			<cfswitch expression=#local.qSvcOperationParam.unique_scope#>
				<cfcase value="parent">
					<cfquery name="local.qUnique">
						SELECT p.param_value, sp.svc_operation_cfs_param, re.display_name
						FROM instance_operation o
						JOIN instance_operation_cfs_param p on (o.instance_operation_uid=p.instance_operation_uid)
						join svc_operation_cfs_param sp on (p.svc_operation_cfs_param_id=sp.svc_operation_cfs_param_id)
						
						JOIN instance_operation_cfs_param p2 on (p.instance_operation_uid=p2.instance_operation_uid)
						JOIN instance re on (p2.param_value=re.instance_uid::text)
						LEFT OUTER JOIN instance_cfs_param p3 on (re.instance_uid::text=p3.param_value)
						LEFT OUTER JOIN instance_cfs_param p4 on (p3.instance_uid=p4.instance_uid AND sp.svc_operation_cfs_param=p4.param)

						WHERE p.param_value=p4.param_value
						AND p.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
						AND p.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#local.qSvcOperationParam.svc_operation_cfs_param_id#/>
						AND p4.instance_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#qInstance.instance_uid# null=#!isValid("guid",qInstance.instance_uid)#/>
						AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=p4.instance_uid order by version desc limit 1) IS NOT TRUE
					</cfquery>
					<cfif qUnique.recordCount GT 0>
						<cfreturn '#local.qUnique.svc_operation_cfs_param# = "#local.qUnique.param_value#" is not unique in the parent instance (#local.qUnique.display_name#) scope'/>
					</cfif>
				</cfcase> 
				<cfdefaultcase></cfdefaultcase>
			</cfswitch>		
		</cfloop>
		<cfreturn ""/>
		
	</cffunction> 


</cfcomponent>

v1/resources/instance_operation_cfs_param.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams/{instanceOperationCfsParamUid}">
<!--- Вот здесь мы могли бы строить URI /instances/{instanceUid}/instanceOperations/{instanceOperationUid}/CfsParams, но это создает избыточность, у нас простые суррогатные первичные ключи. С другой стороны, контекст инстанса и операции у клиента всегда имеется. Но стоит ли передавать избыточную информацию? Это иногда провоцирует путаницу. Для POST мы можем передать необходимый контекст в теле запроса, а можем в URL.  
В данном случае никакого смысла смотреть на все CFS параметры нет, только в рамках операции
Но: мы не хотели бы менять положение сущности в дереве. Либо она в корне, либо она ниже. И получится, когда мы выбираем один параметр, нам ни к чему контекст - мы к нему однозначно адресуемся.
Проголосуем против избыточности?
--->

<!--- Все методы неявно получают 
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>
	<!--- cfproperty использовать не надо, это для веб сервисов --->

	<cffunction name="init">
		<!--- *** ---------------- !!! note camelCase in keys naming!!!	----------------- --->	
		<!--- для нового инстанса нам нужно знать хотя бы его класс (сервис) --->
		<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") AND isValid("guid", 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") AND isValid("guid", 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") AND isValid("guid",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/>  --->
		<!--- <cfdump var=#this#/> ---> 
		<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 параметра, все было бы просто - это у нас поле параметра --->
		<!--- ошибка: в строке параметров у нас ключ param.subparam, а в аргументах он разобран по частям
		при этом мы ожидаем в параметрах структуру paramName:{subparamName:subparamValue}
		но почему не находим в сохраненных? возможно такое совпадение, что в сценарии у нас как раз надо выбрать svcId а они не --->
		<!--- {"startupConfiguration":{"vdcUid":"ea81db31-2197-407d-b32b-e60577c2671c"}} --->
		<!--- this.params <cfdump var=#this.params#/>
		arguments <cfdump var=#arguments# /> --->
		<cfif structKeyExists(this.params,arguments.param)><!--- AND len(this.params[arguments.param]) --->	
			<cfif structKeyExists(arguments,"subparam")><!--- *** не изящно, но просто и понятно --->
				<cfreturn (this.params[arguments.param])[arguments.subparam]/>
			<cfelse>this.params[arguments.param] <!--- <cfdump var=#this.params[arguments.param]#/> --->
				<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>
		<!--- <cfdump var=#this.keys#/>  --->
		<cfif structKeyExists(arguments,"subparam")><!--- *** не изящно, но просто и понятно --->
			<cfset var value = deserializeJson(local.qCfsParam.param_value)/>
			<cfif isStruct(value) AND structKeyExists(value, arguments.subparam)>
				<cfreturn value[arguments.subparam]/>
			<cfelse>
				<cfreturn ""/><!--- *** а надо выбросить 404 --->
			</cfif>
		<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="getValueFromStruct" returntype="any">
		<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="getStringFromStruct" returntype="string" hint="returns scalar">
		<cfargument name="struct" type="struct" required="true"/>
		<cfargument name="path" type="string" required="true"/>

		<cfset var value = getValueFromStruct(arguments.struct,arguments.path)/>
		<cfif isStruct(value) OR isArray(value)>
			<cfreturn serializeJson(value)/>
		</cfif>
		<cfreturn toString(value)/>
	</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="getInstanceState" returntype="struct">
		<cfargument name="instanceUid"  default=#this.instance_uid#/><!--- type="guid" --->
		
		<cfif !isValid("guid",arguments.instanceUid)>
			<cfreturn {}/><!--- иначе при неопределенном параметре мы получаем исключение --->
		</cfif>

		<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)/><cfabort/> --->
		<cftry>
			<cfset var structConfig = deserializeJson(local.qInstance.instance_data)/>	
			<cfcatch type="ANY">
				<cfreturn {"error":"error parsing state"}/>
			</cfcatch>
		</cftry>		
		<cfif isEmpty(structConfig)>
			<cfreturn {}/>
		</cfif>
		<cfreturn structConfig/>
	</cffunction>
	

	<cffunction name="getResourceRealmConfig" returntype="struct">
		<cfargument name="resourceRealm" type="string" default=#getResourceRealm()#/><!--- платформа может быть определена для родительского инстанса где то в иерархии  --->

		<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 параметр операции инстанса">
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>
		
		<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>iop.param_value</m:field> --->
				<m:field>case when sop.is_sensitive then '********' else iop.param_value end as param_value </m:field><!--- внимание! если параметр типа MAP, то значение нужно разобрать и замаскировать секреты --->
				<m:field>iop.note</m:field>
				<m:field>to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>iop.creator_id</m:field>
				<m:field>sop.svc_operation_cfs_param</m:field>
				<m:field>sop.data_type</m:field>
				<m:field>null as data_descriptor</m:field>
				<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#>sop.value_list</m:field>
				<m:field>u.login as creator_login</m:field>
				<m:field>u.shortname as creator_shortname</m:field>
				<m:field formatter=#request.castToBool#>sop.is_sensitive</m:field>
				<m:field formatter=#request.castToBool#>sop.is_hidden</m:field>
				<m:field formatter=#request.castToBool#>sop.is_disabled</m:field>
				<m:field>sop.man</m:field>
				
				<m:field>sop.func</m:field>
				<m:field>sop.expression</m:field>
				<m:field>sop.nested_ref</m:field>				
				<m:field>sop.state_path</m:field>
				<m:field>sop.depends_on_cfs_params</m:field>
				
				
				<m:field>sop.config::text as config</m:field>
				<m:field>e.service_id</m:field>
				<m:field>e.instance_uid::text as instance_uid</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="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.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("Not Found")).withStatus(404)/>
		</cfif>	
		
		<cfif len(local.qCfsParam.func)><!--- приоритет отдается функции перед списком значений--->
			<cfset var args = {
				"functionName":"#local.qCfsParam.func#", 
				"svcId":#local.qCfsParam.service_id#, 
				"contractId":#arguments.contractId#
			}/> <!--- если появятся новые аргументы у новых функций, будем добавлять их сюда --->
			<cfset local.qCfsParam.value_list=generateValueList(argumentCollection=#args#)/> <!--- можно перечислить именованные аргументы обычным порядком, но с коллекцией потенциально более гибко --->
			<!--- <cfdump var=#local.qCfsParam#/><cfabort/> --->
			<cfif len(trim(local.qCfsParam.value_list)) AND listLen(local.qCfsParam.value_list) EQ 1>
				<cfset local.qCfsParam.default_value=local.qCfsParam.value_list/>
			</cfif>
		<cfelseif len(local.qCfsParam.nested_ref)>
			<cfset var fNestedRef = generateClosure(local.qCfsParam.nested_ref, local.qCfsParam.config)/>
			<cfset local.qCfsParam.nested_ref_data = fNestedRef() />
		<cfelseif len(local.qCfsParam.state_path)>
			<!--- для экономии читаем стейт только при необходимости --->
			<cfquery name="local.qCurrentState">
				select g.instance_state_uid, g.version, g.instance_data, g.is_test, g.instance_uid	
				from instance_state g
				where g.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#local.qCfsParam.instance_uid#/>
				order by g.version desc
				limit 1
			</cfquery>
			<cfset var currentState = {}/>
			<cftry>
				<cfset currentState = deserializeJson(local.qCurrentState.instance_data)/>
				<cfcatch type="ANY"></cfcatch>
			</cftry>
			<cfset local.qCfsParam.value_list = getListFromState(currentState, local.qCfsParam.state_path)/>
		</cfif>	
		
		<cfif (len(local.qCfsParam.func) OR len(local.qCfsParam.expression) OR len(local.qCfsParam.state_path) OR len(local.qCfsParam.nested_ref)) 
			AND len(local.qCfsParam.value_list) EQ 0>				
			<cfset local.qCfsParam.value_list = []/>
		</cfif>		
		
		<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>
		
		<cfset var out=structNew("linked")/>
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		
		<cfset "out.instanceOperationCfsParam" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qCfsParam, this.helper.snake2camel
		)/>	

		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction><!--- get --->	
	
	
	<cffunction name="delete" hint="Удаление CFS параметра операции по ключу.">
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>
			<!--- *** Нужна проверка, что операция не была выполнена, иначе она становится архивной --->
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>		
		<cftry><!--- 	*** без проверок существования	 --->
		
			<cfquery name="local.qCheck" result="local.result">
				select 
				io.dt_submit, io.submit_result  
				from instance_operation_cfs_param iop
				join instance_operation io on iop.instance_operation_uid=io.instance_operation_uid			
				where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			</cfquery>				
			<cfif left(local.qCheck.submit_result,1) EQ "2"><!--- 201 etc --->
				<cfreturn representationOf(this.helper.formatMessage("Operation already started", "Parameter deletion disabled for started operation")).withStatus(422)/>
			</cfif>
			
			<!--- <cfquery name="local.qCheckSpecification" result="local.result">
				select si.specification_id
				from instance_operation_cfs_param iop
				join instance_operation io on iop.instance_operation_uid=io.instance_operation_uid	
				join instance e on (io.instance_uid=e.instance_uid)
				left outer join specification_item si on (e.specification_item_id=si.specification_item_id)				
				where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
				AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
			</cfquery>
 			<cfif local.qCheckSpecification.recordCount EQ 0>
				<cfreturn representationOf(this.helper.formatMessage("Instance not accessible", "Instance does not belong to the current specification, contract or contragent")).withStatus(403)/>
			</cfif>	 --->
			<cfquery name="local.qCheckAccess" result="local.result">
				select count(*) as cnt
				from instance_operation_cfs_param iop 
				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)
				where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
				AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
			</cfquery>	
			<cfif local.qCheckAccess.cnt EQ 0>
				<cfreturn representationOf(this.helper.formatMessage("Instance not accessible", "Instance of this CFS parameter is not accessible to the current user (at least does not belong to the default specification)")).withStatus(403)/>
			</cfif>			
		
			<cfquery name="local.qSave">
				delete from instance_operation_cfs_param 
				where instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			</cfquery>		
			
			<cfcatch type="any">
				<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/><!--- это не нужно показывать в продуктиве --->
			</cfcatch>
		</cftry>
		
		<cfreturn noData().withStatus(204, "No Content") />				
	</cffunction><!--- delete --->	
	
	
	<cffunction name="put" hint="запись CFS параметра операции инстанса">
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cfargument name="paramValue" type="string" required=true/>
		<cfargument name="note" type="string" required=false default=""/>

		<cfset var local={}/>
		<cftry>		
			<cfset checkInstanceParam(arguments.instanceOperationCfsParamUid, arguments.paramValue, arguments.usrId)/>
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfquery name="local.qSave" result="local.result">
		update instance_operation_cfs_param set
		 dt_created=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
		,creator_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
		,param_value=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.paramValue#" />	<!--- htmlEditFormat --->
		,note=<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.note)#" />	
		where instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
		</cfquery>
		<!--- *** проверить существование - 404 ---> 
		<cfreturn noData().withStatus(204, "No Content") />		
	</cffunction><!--- put --->
	
	
	
	<cffunction name="checkInstanceParam">
		<cfargument name="instanceOperationCfsParamUid" type="guid" required=true/>
		<cfargument name="paramValue" required=true/>
		<cfargument name="usrId" type="numeric" required=true/>
		
		<cfset var local={}/>
		<cfquery name="local.qCheck" result="local.result">
			select svc_operation_cfs_param_id
			from instance_operation_cfs_param
			where instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
		</cfquery>
		<cfif local.qCheck.recordCount EQ 0>
			<cfthrow type="invalidParamValue" message="Cannot find CFS parameter by instanceOperationCfsParamUid=#arguments.instanceOperationCfsParamUid#"/>
		</cfif>
		
		<cfset CreateObject("component", "instance_operation_cfs_param_ls").checkParam(local.qCheck.svc_operation_cfs_param_id, arguments.paramValue, arguments.usrId, arguments.instanceOperationCfsParamUid)/>
	</cffunction>
	
	
	<cffunction name="processMap" returntype="void" access="public">
		<cfargument name="qParam" type="query" required=true/><!--- работаем с текущей строкой query. передача query это скорее колхоз, всего-то надо 4 понятных аргумента. 
		Конечно, то, что часть из них - id, не упрощает восприяие кода --->
		<!--- Выполняем 2 задачи (*** вероятно, правильно их разделить по разным функциям)
		- сформировать дескриптор
		- замаскировать секреты --->
		<!--- здесь могут использоваться 2 критерия: формат аргумента, array/struct, либо тип  --->
		
		<cfset local.spContainer = {}/><!--- может быть map или array of map --->
		<cfset local.spMapArray = []/>
		<!--- мы хотим обработать и случай array-map-fixed --->
		<cftry>			
			<cfset local.spContainer = deserializeJSON(arguments.qParam.param_value)/> <!--- а собственно зачем нам тут local? помогает чем-то? --->
			<cfif (arguments.qParam.data_type EQ "array-map-fixed")>
				<cfset local.spMapArray = isEmpty(local.spContainer) ? [] : local.spContainer />
			<cfelse>
				<cfset local.spMapArray = [local.spContainer]/>
			</cfif>
			<cfcatch type="any">
				<cfset local.spMapArray = [{"error":"#cfcatch.message#"}]/><!--- теряем все данные --->
			</cfcatch>
		</cftry>
		
		<cfquery name="local.qSubparam">
		select 
			 sp.svc_operation_cfs_param_id
			,sp.svc_operation_cfs_subparam_id
			,sp.svc_operation_cfs_subparam
			,sp.label
			,sp.data_type
			,sp.value_list
			,sp.is_required
			,sp.descr
			,sp.man
			,sp.default_value
			,sp.minlength
			,sp.maxlength				
			,sp.regex
			,sp.sort
			,sp.is_sensitive
			,sp.depends_on_cfs_params
			,sp.expression
			,sp.ref_svc_id
			,sp.unique_scope
			,sp.default_compute
			,sp.is_hidden
			,sp.is_disabled
		from svc_operation_cfs_subparam sp
		where sp.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.qParam.svc_operation_cfs_param_id#"/>
		order by sp.sort
		</cfquery>
		<!--- <cfdump var=#local.qSubparam#/><cfabort/> --->
		
		<cfquery name="local.qInstSubparam">
		select 
			 sp.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid
			,sp.instance_operation_cfs_subparam								
			,sp.note								
			,sp.sort
			,sp.is_sensitive
		from instance_operation_cfs_subparam sp
		where sp.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.qParam.instance_operation_cfs_param_uid#" null=#!isValid("guid",arguments.qParam.instance_operation_cfs_param_uid)#/>
		order by sp.sort
		</cfquery>
	
		<!--- формируем дескриптор --->
		<cfset var mapDescriptor=structNew("linked")/>	
		<cfset var parser=createObject("component","lib.expression_parser")/>
		
		<cfloop query="local.qSubparam">
			<cfset structInsert(mapDescriptor, local.qSubparam.svc_operation_cfs_subparam,
					{
						"svcOperationCfsSubparamId":"#local.qSubparam.svc_operation_cfs_subparam_id#", 
						"svcOperationCfsSubparam":"#local.qSubparam.svc_operation_cfs_subparam#", 
						"label":"#local.qSubparam.label#", 
						"dataType":"#local.qSubparam.data_type#", 
						"valueList":'#(len(local.qSubparam.expression)?
							(parser.eval(
								qSubparam.expression,
								{
									component:"resources.instance_operation_cfs_param",
									keys:{						
										instanceOperationCfsParamUid:"#toString(arguments.qParam.instance_operation_cfs_param_uid)#", /*note camelCase*/
										svcOperationCfsParamId:"#toString(arguments.qParam.svc_operation_cfs_param_id)#" /*note camelCase*/
									},
									params:{}
								}
							))
							: local.qSubparam.value_list)#',
						"dependsOnCfsParams":"#local.qSubparam.depends_on_cfs_params#",
						"expression":"#local.qSubparam.expression#", 
						"minlength":"#local.qSubparam.minlength#", 
						"maxlength":"#local.qSubparam.maxlength#", 
						"regex":"#local.qSubparam.regex#", 
						"man":"#local.qSubparam.man#", 
						"descr":"#local.qSubparam.descr#", 
						"defaultValue":"#local.qSubparam.default_value#", 
						"isRequired":#request.castToBool(local.qSubparam.is_required)#,
						"isSensitive":#request.castToBool(local.qSubparam.is_sensitive)#,
						"isHidden":#request.castToBool(local.qSubparam.is_hidden)#,	
						"isHidden":#request.castToBool(local.qSubparam.is_disabled)#,	
						"refSvcId":"#local.qSubparam.ref_svc_id#",
						"uniqueScope":"#local.qSubparam.unique_scope#",
						"defaultCompute":"#local.qSubparam.default_compute#",
						"isModifiableDefinition":false
						
					}, true
				)
			/>
		</cfloop>
		
		<cfloop index="local.i" from=1 to=#arrayLen(local.spMapArray)#><!--- *** искусственное оборачивание в массив вот для этого, не придумал ничего умнее
			чтобы не дублировать цикл по субпараметрам. Сделано некрасиво, но работать должно. 
			Ну а как сделать? Если массив - делать цикл по массиву, а если map-сразу обрабатыввать map. При этом запрос к таблице сабпараметров не хочется делать лишний раз.
			--->
			<!--- может быть, лучше смотрелось бы arrayMap, но тут в ней будет цикл по query, и надо это делать в cfscript... строчек будет явно больше,
			и в cfscript циклы по query какие-то я не помню	...	кстати, вместо цикла по query можно наверно использовать structMap по контенту --->
			<!--- интересная и неожиданная разница между циклом по массиву и циклом по индексу массива: 
			в первом случае возвращается копия элемента, во втором мы естественно имеем дело со ссылокой на элемент,
			а нам сейчас нужно второе --->
			<cfif isStruct(local.spMapArray[local.i])>
				<cfloop query="local.qSubparam">
					<cfif local.qSubparam.is_sensitive AND structKeyExists(local.spMapArray[local.i],local.qSubparam.svc_operation_cfs_subparam)>
						<cfset local.spMapArray[local.i][local.qSubparam.svc_operation_cfs_subparam] = "********"/>
					</cfif>
				</cfloop>
			</cfif>
		</cfloop>
		
		<!--- *** проверку, что это не map-fixed, можно добавить здесь --->
		<!--- чтобы не вставляли в параметр что попало --->
		<!--- здесь мы не делаем цикл по массиву, поскольку не поддерживаем массив расширяемых map --->
		<cfif arrayLen(spMapArray) AND isStruct(local.spMapArray[1])>
			<cfloop query="local.qInstSubparam">
				<cfset structInsert(mapDescriptor, local.qInstSubparam.instance_operation_cfs_subparam,
						{
							"note":"#local.qInstSubparam.note#", 
							"isSensitive":#request.castToBool(local.qInstSubparam.is_sensitive)#,
							"isModifiableDefinition":true
						}, true
					)
				/>
				<cfif local.qInstSubparam.is_sensitive AND structKeyExists(local.spMapArray[1],local.qInstSubparam.instance_operation_cfs_subparam)>
					<cfset local.spMapArray[1]["#local.qInstSubparam.instance_operation_cfs_subparam#"] = "********"/>
				</cfif>
			</cfloop>	
		</cfif>		
		
		<cfset arguments.qParam.data_descriptor = mapDescriptor/>	
		
		<cfif (arguments.qParam.data_type EQ "array-map-fixed")>	
			<cfset arguments.qParam.param_value = serializeJson(local.spMapArray)/><!--- как есть --->	
		<cfelse>
			<cfset arguments.qParam.param_value = arrayLen(local.spMapArray) ? serializeJson(local.spMapArray[1]) : ""/><!--- вытаскиваем из массива-обертки --->
		</cfif>
	</cffunction>
	
		
	<cffunction name="generateValueList" returntype="string" access="public">
		<cfargument name="functionName" type="string" required="true"/>
		<!--- остальные атрибуты необязательные, в argumentCollection (еще вопрос, насколько безопасно объявлять один из аргументов без остальных) --->		
		
		<cfswitch expression=#arguments.functionName#>
			<cfcase value="">
				<cfreturn ""/>
			</cfcase>	
			<cfcase value="getAvailableResourceRealms">
				<cfreturn getAvailableResourceRealms(arguments.svcId, arguments.contractId)/><!--- *** неочевидно, но ожидаются в argumentCollection --->
			</cfcase>						
			<cfcase value="getSampleList"><!--- for debug --->
				<cfreturn "dummy1,dummy2"/>
			</cfcase>
			<cfdefaultcase>
				<cfreturn "function not found"/>
			</cfdefaultcase>
		</cfswitch>		
	</cffunction>
	
	
	<cffunction name="getAvailableResourceRealms" returnType="string" access="public">
		<cfargument name="svcId"/>
		<cfargument name="contractId"/>
		
		<cfset var local={}/>

		<cfquery name="local.qAvailableResourceRealm"><!--- *** distinct халява, но придумывать корректную формулировку времени мало --->
			select distinct r.resource_realm, r.sort
			from resource_realm r
			join resource_realm_access a on 
				(r.resource_realm_id=a.resource_realm_id 
				AND (a.contract_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#arguments.contractId# null=#!isValid("integer", arguments.contractId)#/> 
				OR a.contract_id=0) /*0 means access to any contract*/
				AND a.is_enabled)
			join svc s on r.resource_realm_type_id=s.resource_realm_type_id
			where s.svc_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#arguments.svcId# null=#!isValid("integer", arguments.svcId)#/> 
			order by r.sort, r.resource_realm
		</cfquery>	<!--- <cfdump var=#arguments#/> cfdump здесь провоцирует NPE --->
		<cfif local.qAvailableResourceRealm.recordCount>
			<cfreturn valueList(local.qAvailableResourceRealm.resource_realm)/>
		<cfelse>
			<cfreturn ""/><!--- иначе для пустого резалтсета генерируется null --->
		</cfif>
	</cffunction>
	

	
	<!--- для вложенных справочников --->
	<!--- дублировано: deck-tool/instance_operation.cfm --->
	
	<cffunction name="getSelectedResourceRealm" returnType="string">
	
		<cfset var local={}/>
		<cfquery name="local.qSelectedResourceRealm">
			select iop.param_value
			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)
			where iop.instance_operation_uid=<cfqueryparam cfsqltype="CF_SQL_OTHER" value=#d.instance_operation_uid# null=#!isValid("guid", d.instance_operation_uid)#/>
			AND	sop.svc_operation_cfs_param='resourceRealm'	
			order by 1
		</cfquery>	<!--- <cfdump var=#arguments#/> cfdump здесь провоцирует NPE --->
		<cfreturn local.qSelectedResourceRealm.param_value/>
	</cffunction>

	<cffunction name="generateClosure" returntype="function">
		<cfargument name="functionName" type="string" required="true"/>
		<cfargument name="config" type="string" required="true"/>
		<!--- остальные атрибуты необязательные, в argumentCollection --->		

		<cfswitch expression=#arguments.functionName#>
			<cfcase value="">
				<cfreturn function(){}/>
			</cfcase>	
			<cfcase value="getRefFromRRealmConfig">
				<cfreturn function(){return getRefFromRRealmConfig(getSelectedResourceRealm(),config)}/>
				<!--- тут нам внезапно нужен контекст ресурсной платформы. При первичной отрисовке он может быть неизвестен (кроме специальных случаев, когда есть дефолтная платформа, или она вообще одна --->
				<!--- теперь даже не знаю, хорошая ли практика - explicitly scope variables: с closures, похоже, так не принято --->
			</cfcase>						
			<cfcase value="getSampleConfig"><!--- for debug --->
				<cfreturn function(){return deserializeJson('{"providerVdc": [ { "hystax-pvdc": { "storageVdcProfile": [ ] } }, { "v1cl1-vsan-pvdc": { "albSegroup": [ "SEGROUP-V1CL1-VSAN-SHARED-01", "SEGROUP-V1CL1-VSAN-SHARED-02" ], "storageVdcProfile": [ "v1-backup-itprotect" ] } }, { "v1cl2-pvdc": { "albSegroup": [ "SEGROUP-V1CL2-SHARED-01", "SEGROUP-V1CL2-SHARED-02" ], "storageVdcProfile": [ "V1-SATA", "V1CL1-F1R1-SSD", "V1CL1-F2R6-SSD", "V1CL1-SAS" ] } }, { "v1cl3-vsan-pvdc": { "albSegroup": [ "SEGROUP-V1CL3-VSAN-SHARED-01", "SEGROUP-V1CL3-VSAN-SHARED-02" ], "storageVdcProfile": [ "V1-SATA", "v1-sata-cl3", "v1-sata-migr", "V1CL1-SAS", "V1CL3-F1R5-SSD", "V1CL3-F1R6-SSD_new", "V1CL3-F2R1-SSD", "V1CL3-F2R6-SSD" ] } }, { "v1cl4-vsan-pvdc": { "albSegroup": [ "SEGROUP-V1CL4-VSAN-SHARED-01" ], "storageVdcProfile": [ "V1-SATA", "v1-sata-migr", "V1CL3-F1R6-SSD_new", "V1CL4-F1R1-SSD", "V1CL4-F1R5-SSD", "V1CL4-F2R1-SSD" ] } }, { "v2cl2-1c-pvdc": { "storageVdcProfile": [ "V1CL1-F2R1-SSD", "V1CL1-F2R6-SSD" ] } } ]}')} />
			</cfcase>
			<cfdefaultcase>
				<cfreturn "function not found"/>
			</cfdefaultcase>
		</cfswitch>		
	</cffunction>

	<cffunction name="getRefFromRRealmConfig" returntype="any">
		<cfargument name="resourceRealm" type="string" required="true"/>
		<cfargument name="config" type="string" required="true"/>

		<cfquery name="qResourceRealmConfig">
			select properties::text 
			from resource_realm 
			where resource_realm='ngcloud.ru'
			<!--- <cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.resourceRealm#/> --->	
		</cfquery>
		
		<!--- <cfset var local={}/> ---> <!--- паранойя... и есть шансы, что я неправильно понимаю эту химию скоупов--->
		<cfset var props = deserializeJson(qResourceRealmConfig.properties)/>
		<cfset var cfg = deserializeJson(arguments.config)/> <!--- удобнее было бы передавать сразу структуру впр --->
		<cfset cfg={"path":"vcd.providerVdc"}/><!--- *** временный пример для отладки --->
		<!--- засада на ровном месте: у нас в конфиге оказался массив вместо структуры --->
		<!--- вернуть нам нужно не просто содержимое по пути, а вместе с контейнером --->
		<cfset var path=cfg.path/><!--- *** обработать исключение --->
		<cfset var containerName = listLast(path,".")/>
		<cfset var result = structNew()/>
		<cfset structInsert(result, containerName, structGet("props.#path#"))/>	
		<cfreturn result/>
	</cffunction>
	
	<!--- duplicated in deck-tool --->
	<cffunction name="getListFromState" returntype="string" hint="returns list">
		<cfargument name="state" type="struct" required="true"/>
		<cfargument name="path" type="string" required="true"/>

		<cftry>
		<cfreturn structKeyList(structGet("arguments.state.#arguments.path#"))/>
			<cfcatch type="any">
				<!--- можно ожидать ошибки, если по указанному пути расположена не структура --->
				<cfreturn cfcatch.message/><!--- для упрощения диагностики --->
			</cfcatch>
		</cftry>
	</cffunction>
	
	
	
	
	
	
	
	
	
	
	
	
	
	

</cfcomponent>
<!--- [{"b":"12","secret1":"preved"},{"b":"13","secret1":"preved404"},{"b":"14","secret1":"preved402"}] --->

v1/resources/instance_operation_cfs_param_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>
	
		<!---спецификация полей, пригодных для фильтрации --->
	<cfset this.fieldsSpec={
		dt_created={prefix="iop", type="date"},
		svc_operation_cfs_param={prefix="sop", type="string"},
		login={prefix="iop", type="string"},
		creator_id={prefix="iop", type="integer"},
		sort={prefix="sop", type="integer"},
		is_sensitive={prefix="sop", type="boolean"},
		depends_on_cfs_params={prefix="sop", type="string"}
	}
	/>

	<cffunction name="get" hint="Список CFS параметров">
		<cfargument name="pageSize" type="string" hint="type:integer" default="1000"/>	<!--- *** тут на самом деле есть лимит --->
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>
		
		<cfargument name="instanceOperationUid" type="string" required=false hint="type:guid"/>
		
		<cftry>
			<cfif !this.helper.keyExistsAndValid(arguments,"instanceOperationUid","guid")>
				<cfthrow type="invalidParamValue" message="Missing required parameter" detail="instanceOperationUid is missing or not a valid GUID"/>
			</cfif>
			<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
			<cfset this.helper.validateField(arguments, "page", "integer")/>
			
			<!---мы мирно игнорируем поля, отсутствующие в спецификации, что позволяет не делать исключения для orderBy и т.п.--->
			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset var out={}/>
		<cfset var maxrows=arguments.pageSize*arguments.page/>
		<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>			
		
		<cfset var local={}/>		
		<cfquery name="local.qCfsParam" result="local.result">
			select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="fieldCount">
				<m:field title="instance_operation_cfs_param_uid">iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid</m:field>
				<m:field title="instance_operation_uid">iop.instance_operation_uid::text as instance_operation_uid</m:field>
				<m:field title="svc_operation_cfs_param_id">iop.svc_operation_cfs_param_id</m:field>
				<m:field title="param_value">case when sop.is_sensitive then '********' else iop.param_value end as param_value </m:field>
				<m:field>iop.note</m:field>
				<m:field>to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>iop.creator_id</m:field>				
				<m:field>u.login as creator_login</m:field>
				<m:field>u.shortname as creator_shortname</m:field>
				<m:field>sop.svc_operation_cfs_param</m:field>
				<m:field>sop.sort</m:field>
				<m:field formatter=#request.castToBool#>sop.is_sensitive</m:field>
				<m:field formatter=#request.castToBool#>sop.is_required</m:field>
				<m:field formatter=#request.castToBool#>sop.is_hidden</m:field>
				<m:field formatter=#request.castToBool#>sop.is_disabled</m:field>
				<m:field>sop.data_type</m:field>
				<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#>sop.value_list</m:field>
				<m:field>sop.default_value</m:field>
				
				<m:field>sop.func</m:field>				
				<m:field>p.state_path</m:field>
				<m:field>p.nested_ref</m:field>
				<m:field>p.expression</m:field>
				<m:field>sop.depends_on_cfs_params</m:field>
				
				<m:field>sop.man</m:field>				
				<m:field>null as data_descriptor</m:field>
				<m:field>e.service_id</m:field>
			</m:field_set>
			from instance_operation_cfs_param iop
			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)
			join svc_operation_cfs_param sop on (iop.svc_operation_cfs_param_id=sop.svc_operation_cfs_param_id)
			left outer join usr u on (iop.creator_id=u.usr_id)
			where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
			AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
			<m:filter_build filter=#filter#/>
			order by <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/><!---no sort length limit--->
			--limit #maxrows#
		</cfquery>
		
		<cfquery name="local.qTotal">
			select count(*) as cnt
			from instance_operation_cfs_param iop
			where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
		</cfquery> 
		
		<cfloop query="local.qCfsParam">
			<cfif len(func)><!--- приоритет отдается функции перед списком значений--->
				<cfset var args = {
					"functionName":"#func#", 
					"svcId":#local.qCfsParam.service_id#, 
					"contractId":#arguments.contractId#
				}/>
				<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.qCfsParam.value_list"/>
				<cfif (len(local.qCfsParam.func) OR len(local.qCfsParam.expression) OR len(local.qCfsParam.state_path) OR len(local.qCfsParam.nested_ref)) 
					AND len(local.qCfsParam.value_list) EQ 0>				
					<cfset local.qCfsParam.value_list = []/>
				</cfif>	
				<cfif len(trim(local.qCfsParam.value_list)) AND listLen(local.qCfsParam.value_list) EQ 1>
					<cfset local.qCfsParam.default_value=local.qCfsParam.value_list/>
				</cfif>				
			</cfif>		
			
			<cfif data_type EQ "map" OR data_type EQ "map-fixed" OR data_type EQ "array-map-fixed">
				<cfinvoke component="instance_operation_cfs_param" method="processMap" qParam=#local.qCfsParam#/>
			</cfif>
		</cfloop>	
				

		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		<cfset "out.total"=#local.qTotal.cnt#/>
		<cfset "out.resultSetSize"=#local.qCfsParam.recordCount#/>
		<cfset "out.pageSize"=#arguments.pageSize*1#/>
		<cfset "out.page"=#arguments.page*1#/>
		<cfset "out.orderBy"=#arguments.orderBy#/>
		
		<cfset var resultCollection=[]/>
		<cfloop query=#local.qCfsParam# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
			<cfset var rec={}/>				
			<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qCfsParam, this.helper.snake2camel)/>
			<cfset arrayAppend(resultCollection, rec)/>
		</cfloop>
		
		<cfset "out.size"=#arrayLen(resultCollection)#/>
		<cfset "out.results"=#resultCollection#/>		
		<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
		<cfreturn representationOf(out)/>
	</cffunction><!--- get --->	
	
	
	 <cffunction name="post" hint="Создание нового CFS параметра операции.">
		<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
		<cfargument name="svcOperationCfsParamId" type="string" required=true hint="type:integer"/>
		<cfargument name="paramValue" type="string" required=true hint="type:string ***Валидацию пока не проводим, а надо"/>
		<cfargument name="note" type="string" required=false default="" hint="type:string description: комментарий пользователя"/>


		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationUid", "guid")/>
			<cfset this.helper.validateField(arguments, "svcOperationCfsParamId", "integer")/>	
			<cfset checkParam(arguments.svcOperationCfsParamId, arguments.paramValue, arguments.usrId)/>
			<cfset checkResourceRealmAccess(arguments.instanceOperationUid, arguments.svcOperationCfsParamId, arguments.paramValue)/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset var local={}/>
		<cfset local.instanceOperationCfsParamUid=#createGUID()#/>
		
		<cftry>
			<cfquery name="local.qCheck" result="local.result">
				select io.dt_submit, io.dt_finish, io.submit_result  
				from instance_operation io 		
				where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
			</cfquery>	
			<cfif left(local.qCheck.submit_result,1) EQ "2" AND len(local.qCheck.dt_finish) EQ 0><!--- 201 etc --->
				<cfreturn representationOf(this.helper.formatMessage("Operation already started", "Parameter creation disabled for started operation")).withStatus(422)/>
			</cfif>
			
			<!--- Проверка: параметр от нашего ли сервиса *** и операции --->
 			<cfquery name="local.qInstanceService" result="local.result">
				select e.service_id, io.operation, si.specification_id  
				from instance_operation io 
				join instance e on (io.instance_uid=e.instance_uid)
				left outer join specification_item si on (e.specification_item_id=si.specification_item_id)
				where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
			</cfquery>
			
			<cfquery name="local.qTemplateSvc" result="local.result">
				select so.svc_id, so.operation, sop.is_disabled 
				from svc_operation_cfs_param sop 
				join svc_operation so on (sop.svc_operation_id=so.svc_operation_id)
				where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
			</cfquery>	
			
 			<cfif local.qInstanceService.specification_id NEQ arguments.specificationId>
				<cfreturn representationOf(this.helper.formatMessage("Instance not accessible", "Instance does not belong to the current specification, contract or contragent")).withStatus(403)/>
			</cfif>	
			<cfif local.qInstanceService.service_id NEQ local.qTemplateSvc.svc_id>
				<cfreturn representationOf(this.helper.formatMessage("Invalid CFS parameter", "Parameter specified does not belong to this service")).withStatus(400)/>
			</cfif>	
			<cfif local.qInstanceService.operation NEQ local.qTemplateSvc.operation>
				<cfreturn representationOf(this.helper.formatMessage("Invalid CFS parameter", "Parameter specified does not belong to this operation")).withStatus(400)/>
			</cfif>		
	
			<cfquery name="local.qSave">
				insert into instance_operation_cfs_param (	
					instance_operation_cfs_param_uid,instance_operation_uid,svc_operation_cfs_param_id,param_value,note,dt_created,creator_id
				) values (
					<cfqueryparam cfsqltype="cf_sql_other" value="#local.instanceOperationCfsParamUid#"/>
					,<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
					,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
					,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.paramValue#"/><!--- htmlEditFormat --->
					,<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.note)#"/>
					,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
					,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				);
			</cfquery>
			<cfcatch type="any">
				<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/>
			</cfcatch>
		</cftry>
		
		<cfreturn noData()
			.withHeaders({location: "./#local.instanceOperationCfsParamUid#"})
			.withStatus(201, "Created") 
		/>				
	</cffunction><!--- post --->
	
	
	
	<cffunction name="checkParam" returnType="void">
		<!--- проверка параметров по метаданным, независимо от семантики самого параметра --->
		<cfargument name="svcOperationCfsParamId" type="numeric" required = true/>
		<cfargument name="paramValue" required = true/>
		<cfargument name="usrId" type="numeric"/>
		<cfargument name="instanceOperationCfsParamUid" type="guid" default="00000000-0000-0000-0000-000000000000"/><!--- используется при проверке уникальности, чтобы не сравнивать с собой --->
		<!--- *** Вероятно, стоит возвращать конкретную ругань --->
		<!--- принадлежит ли параметр данному сервису, проверяется выше  --->		
		
		<!--- получаем метаданные параметра --->
		<cfset var local={}/>
		<cfquery name="local.qSvcOperationCfsParam">
			select sop.svc_operation_cfs_param_id
			,sop.svc_operation_cfs_param
			,sop.value_list			
			,sop.data_type			
			,sop.is_required
			,sop.maxlength
			,sop.minlength
			,sop.regex
			,sop.unique_scope 	
			,sop.maxvalue
			,sop.minvalue
			from svc_operation_cfs_param sop	
			where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>	
		</cfquery>
		
		<cfif local.qSvcOperationCfsParam.is_required GT 0 AND len(arguments.paramValue) EQ 0>
			<cfthrow type="invalidParamValue" message="Missing required parameter (#local.qSvcOperationCfsParam.svc_operation_cfs_param#)"/>		
		<cfelseif len(arguments.paramValue) AND NOT validateDataType(arguments.paramValue,local.qSvcOperationCfsParam.data_type)>
			<cfthrow type="invalidParamValue" message="Invalid format #local.qSvcOperationCfsParam.data_type# #local.qSvcOperationCfsParam.svc_operation_cfs_param#"/>
		</cfif>
		
		<cfif len(arguments.paramValue) AND listLen(local.qSvcOperationCfsParam.value_list) GT 0 AND NOT listFind(local.qSvcOperationCfsParam.value_list, arguments.paramValue)>
			<cfthrow type="invalidParamValue" message="Значение параметра #local.qSvcOperationCfsParam.svc_operation_cfs_param# отсутствует в списке '#local.qSvcOperationCfsParam.value_list#'" detail="listLen:#listLen(local.qSvcOperationCfsParam.value_list)#"/>			
		</cfif>
		<cfif len(arguments.paramValue) AND local.qSvcOperationCfsParam.maxlength GT 0 AND len(arguments.paramValue) GT local.qSvcOperationCfsParam.maxlength>
			<cfthrow type="invalidParamValue" message="Длина #local.qSvcOperationCfsParam.svc_operation_cfs_param# превышает #local.qSvcOperationCfsParam.maxlength#"/>
		</cfif>
		<cfif len(arguments.paramValue) AND local.qSvcOperationCfsParam.minlength GT 0 AND len(arguments.paramValue) LT local.qSvcOperationCfsParam.minlength>
			<cfthrow type="invalidParamValue" message="Длина #local.qSvcOperationCfsParam.svc_operation_cfs_param# меньше #local.qSvcOperationCfsParam.minlength#"/>
		</cfif>			
		<cfif len(arguments.paramValue) AND len(local.qSvcOperationCfsParam.regex) AND reFind(local.qSvcOperationCfsParam.regex,arguments.paramValue) EQ 0>
			<cfthrow type="invalidParamValue" message='Parameter "#local.qSvcOperationCfsParam.svc_operation_cfs_param#" value "#arguments.paramValue#" does not match pattern "#local.qSvcOperationCfsParam.regex#" '/>
		</cfif>
		<cfif isNumeric(arguments.paramValue) AND isNumeric(local.qSvcOperationCfsParam.maxvalue) AND arguments.paramValue GT local.qSvcOperationCfsParam.maxvalue>
			<cfthrow type="invalidParamValue" message='Parameter "#local.qSvcOperationCfsParam.svc_operation_cfs_param#" value "#arguments.paramValue#" is greater than "#local.qSvcOperationCfsParam.maxvalue#" '/>
		</cfif>	
		<cfif isNumeric(arguments.paramValue) AND isNumeric(local.qSvcOperationCfsParam.minvalue) AND arguments.paramValue LT local.qSvcOperationCfsParam.minvalue>
			<cfthrow type="invalidParamValue" message='Parameter "#local.qSvcOperationCfsParam.svc_operation_cfs_param#" value "#arguments.paramValue#" is less than "#local.qSvcOperationCfsParam.minvalue#" '/>
		</cfif>	
		
		<cfset checkUniqueness(
			arguments.svcOperationCfsParamId,
			qSvcOperationCfsParam.svc_operation_cfs_param,
			arguments.paramValue,
			qSvcOperationCfsParam.unique_scope,
			arguments.instanceOperationCfsParamUid,
			arguments.usrId
		)/>		
	</cffunction>
	
	<cffunction name="checkUniqueness" returnType="void">		
		<!--- использую void, потому что с исключением проще передавать информацию о конкретном характере ошибки. Чем возвращать объект ошибки - проше бросить исключение --->
		<cfargument name="svcOperationCfsParamId" type="numeric"/>
		<cfargument name="svcOperationCfsParamName" type="string"/><!--- нужен только для формирования текста сообщения об ошибке --->
		<cfargument name="paramValue" type="string"/>
		<cfargument name="scope" type="string"/>
		<cfargument name="instanceOperationCfsParamUid" type="guid"/>
		<cfargument name="usrId" type="numeric"/>
		
		<cfset var local={}/>
		<cfswitch expression=#arguments.scope#>
			<cfcase value="provider">
				<cfquery name="local.qUnique">
					select count(*) as cnt 
					from instance_operation_cfs_param p
					join instance_operation o on (p.instance_operation_uid=o.instance_operation_uid)
					join instance e on (o.instance_uid=e.instance_uid)
					where p.param_value=<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.paramValue#/>
					AND p.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsParamId#/>/*limit to the current svc param*/
					<cfif arguments.instanceOperationCfsParamUid NEQ "00000000-0000-0000-0000-000000000000">
						AND p.instance_operation_cfs_param_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationCfsParamUid#/>/*exclude itself*/
					</cfif>
					AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) IS NOT TRUE /*ignore deleted instances*/
				</cfquery>
				<cfif local.qUnique.cnt GT 0>
					<cfthrow type="invalidParamValue" message='#arguments.svcOperationCfsParamName# = "#arguments.paramValue#" is not unique in the provider scope'/>
				</cfif>				
			</cfcase>
			
			<cfcase value="tenant">
				<cfquery name="local.qUnique">
					select count(*) as cnt 
					from instance_operation_cfs_param p
					join instance_operation o on (p.instance_operation_uid=o.instance_operation_uid)
					join instance e on (o.instance_uid=e.instance_uid)
					join specification_item i on (e.specification_item_id=i.specification_item_id)
					join specification s on (i.specification_id=s.specification_id)
					join contract d on (s.contract_id=d.contract_id)
					join contragent k on (d.contragent_id=k.contragent_id)
					join usr u on (k.contragent_id=u.contragent_id)	
					where p.param_value=<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.paramValue#/>
					AND svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsParamId#/>/*limit to the current svc param*/
					AND u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/>/*limit to the contragent of current user*/
					<cfif arguments.instanceOperationCfsParamUid NEQ "00000000-0000-0000-0000-000000000000">
						AND p.instance_operation_cfs_param_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationCfsParamUid#/>/*exclude itself*/
					</cfif>
					AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) IS NOT TRUE /*ignore deleted instances*/
				</cfquery>
				<cfif local.qUnique.cnt GT 0>
					<cfthrow type="invalidParamValue" message='#arguments.svcOperationCfsParamName# = "#arguments.paramValue#" is not unique in the tenant scope'/>
				</cfif>				
			</cfcase>
			
			<cfdefaultcase></cfdefaultcase>			
		</cfswitch>			
	
	</cffunction>
	
	
	<cffunction name="validateDataType" returnType="boolean">
		<cfargument name="value" type="string"/>
		<cfargument name="dataType" type="string"/>
		
		<cfswitch expression=#arguments.dataType#>
			<cfcase value=",string">
				<cfreturn true/>
			</cfcase>
			<cfcase value="boolean">
				<cfreturn IsBoolean(arguments.value)/><!--- можно было сделать и единообразно --->
			</cfcase>		
			<cfcase value="integer">
				<cfreturn isValid("integer",arguments.value)/>
			</cfcase>			
			<cfcase value="integer &gt; 0">
				<cfreturn isValid("integer",arguments.value) AND arguments.value GT 0/>
			</cfcase>			
			<cfcase value="integer &gt;= 0">
				<cfreturn isValid("integer",arguments.value) AND arguments.value GE 0/>
			</cfcase>		
			<cfcase value="numeric">
				<cfreturn isNumeric(arguments.value)/>
			</cfcase>			
			<cfcase value="uuid">
				<cfreturn isValid("guid",arguments.value)/><!--- *** или GUID? Или принимать оба формата GUID UUID  и приводить к одному? А как быть с БД--->
			</cfcase>
			<cfcase value="map,map-fixed">
				<cftry>
					<cfset var v = deserializeJson(arguments.value)/>
					<cfreturn isStruct(v)/><!--- массив нас не устроит --->
					<cfcatch type="ANY">
						<cfreturn false/>
					</cfcatch>
				</cftry>
			</cfcase>	
			<cfcase value="array-map-fixed">
				<cftry>
					<cfset var v = deserializeJson(arguments.value)/>
					<cfreturn isArray(v)/>
					<cfcatch type="ANY">
						<cfreturn false/>
					</cfcatch>
				</cftry>
			</cfcase>	
			<cfdefaultcase><cfreturn true/>
				<cfthrow type="custom" detail="unsupported CFS parameter value type (#arguments.dataType#)"/>
			</cfdefaultcase>		
		</cfswitch>	
	</cffunction>
	
	<cffunction name="checkResourceRealmAccess" returnType="void">
		<!---*** В отличие от других методов, этому небезразлично имя параметра --->
		<cfargument name="instanceOperationUid" type="guid"/>
		<cfargument name="svcOperationCfsParamId" type="numeric"/>
		<cfargument name="paramValue" type="string"/>
		
		<cfset var local={}/>
		
		<cfquery name="local.qSvcOperationCfsParam">
			select sop.svc_operation_cfs_param_id
			,sop.svc_operation_cfs_param			
			from svc_operation_cfs_param sop	
			where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>	
		</cfquery>
		<cfif lcase(trim(local.qSvcOperationCfsParam.svc_operation_cfs_param)) NEQ lcase("resourceRealm")>
			<cfreturn/>
		</cfif>
		
		<cfquery name="local.qContract">
			select c.contract_id
			from instance_operation io
			join instance e on (io.instance_uid=e.instance_uid)
			join specification_item si on (e.specification_item_id=si.specification_item_id)
			join specification s on (si.specification_id=s.specification_id)
			join contract c on (s.contract_id=c.contract_id)
			where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
		</cfquery>
		
		<cfquery name="local.qCheckResourceRealmAccess">
			select r.resource_realm_id, r.resource_realm, a.contract_id, a.is_enabled
			from resource_realm r 
			join resource_realm_access a on (r.resource_realm_id=a.resource_realm_id)
			where r.resource_realm = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.paramValue#"/>  
			AND (a.contract_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#local.qContract.contract_id# null=#!isValid("integer",local.qContract.contract_id)#/> OR a.contract_id=0)
			AND a.is_enabled
		</cfquery>
		
		<cfif local.qCheckResourceRealmAccess.recordCount EQ 0>
			<cfthrow message="Resource realm specified in CFS param is not available for the current contract" detail="CFS resource realm #arguments.paramValue# unawailable. Instance operation UID #arguments.instanceOperationUid#. Contract ID #local.qContract.contract_id#"/>
		</cfif>
		
	</cffunction>
		
	
</cfcomponent>

v1/resources/instance_operation_cfs_subparam.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams/{instanceOperationCfsParamUid}/subparams/{subparam}">


	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>
	

	<cffunction name="get" hint="Метаданные субпараметра CFS-параметра операции экземпляра (значение субпараметра находится в теле параметра)"><!--- *** спросить, может, метод не нужен, а если нужен - наверно, нужно значение --->
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cfargument name="subparam" type="string" required=true hint="type:string"/>
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>
		
		<cfquery name="local.qRead" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="fieldCount">
				<m:field>ios.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid</m:field>
				<m:field>ios.instance_operation_cfs_subparam</m:field>
				<m:field formatter=#request.castToBool#>ios.is_sensitive</m:field>
				<m:field>ios.note</m:field>
				<!--- <m:field>iop.data_type</m:field> --->
				<m:field>to_char(ios.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>				
			</m:field_set>
			from instance_operation_cfs_subparam ios
			join instance_operation_cfs_param iop on (ios.instance_operation_cfs_param_uid=iop.instance_operation_cfs_param_uid)
			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)
			where ios.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			AND ios.instance_operation_cfs_subparam=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.subparam#"/>
			AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> /*tenant isolation, из конфиденциального тут заметки... может быть, имена переменных окружения*/
		</cfquery>	
		
 		<cfif local.qRead.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
		</cfif> 		
		
<!--- 		<cfif local.qRead.data_type NEQ 'map'>
			<cfreturn representationOf(this.helper.formatMessage("Parent parameter type does not have subparameters")).withStatus(400)/>
		</cfif>		 --->
		
		<cfset var out=structNew("linked")/>
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		
		<cfset "out.instanceOperationCfsSubparam" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qRead, this.helper.snake2camel
		)/>	

		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction><!---/get --->	
	
	
	<cffunction name="delete" hint="Удаление метаданных субпараметра CFS-параметра операции экземпляра по ключу.">
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cfargument name="subparam" type="string" required=true hint="type:string"/>
		
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>		
		<cftry>
			<!--- проверка существования одновременно может служить для изоляции тенантов --->
			<!--- без проверки стартовавшей операции --->
			<!--- нужна проверка на изоляцию тенантов --->
		
			<cfquery name="local.qCheckExistence" result="local.result">
				select count(*) as cnt
				from instance_operation_cfs_subparam ios
				where ios.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
				AND ios.instance_operation_cfs_subparam=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.subparam#"/>
			</cfquery>			
			<cfif local.qCheckExistence.cnt EQ 0>
				<cfreturn representationOf(this.helper.formatMessage("Not Found", "Subparameter specified does not exist")).withStatus(404)/>
			</cfif>
			
			<cfquery name="local.qCheckAccess" result="local.result">
				select count(*) as cnt
				from instance_operation_cfs_param iop 
				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)
				where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
				AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
			</cfquery>	
			<cfif local.qCheckAccess.cnt EQ 0>
				<cfreturn representationOf(this.helper.formatMessage("Not Accessible", "CFS parameter specified is not accessible by the current user")).withStatus(403)/>
			</cfif>
		
			<cfquery name="local.qSave">
				delete from instance_operation_cfs_subparam 
				where instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
				AND instance_operation_cfs_subparam=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.subparam#"/>
			</cfquery>		
			
			<cfcatch type="any">
				<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/><!--- *** это не нужно показывать в продуктиве --->
			</cfcatch>
		</cftry>
		
		<cfreturn noData().withStatus(204, "No Content") />				
	</cffunction><!--- delete --->	
	
	
	<cffunction name="put" hint="запись метаданных субпараметра CFS-параметра операции инстанса">
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cfargument name="subparam" type="string" required=true hint="type:string"/>		
		<cfargument name="isSensitive" type="boolean" required=false default=false hint="type:boolean"/>
		<cfargument name="note" type="string" required=false default=""/> <!--- *** XSS!!! --->
		
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>
			<cfset this.helper.validateField(arguments, "isSensitive", "boolean")/>
			<!--- *** Нужна проверка, что операция не была выполнена, иначе она становится архивной --->
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>

		<cfset var local={}/>
		
		<cfquery name="local.qCheckAccess" result="local.result">
			select count(*) as cnt
			from instance_operation_cfs_param iop 
			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)
			where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
		</cfquery>	
		<cfif local.qCheckAccess.cnt EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Accessible", "Instance CFS parameter not found or access denied")).withStatus(404)/>
		</cfif>
		
		<cfquery name="local.qCheckExistence" result="local.result">
			select count(*) as cnt
			from instance_operation_cfs_subparam iop
			where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			AND iop.instance_operation_cfs_subparam=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.subparam#"/>
		</cfquery>			
		<cfif local.qCheckExistence.cnt EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found", "Subparameter specified does not exist")).withStatus(404)/>
		</cfif>
		
		<cfquery name="local.qSave" result="local.result">
			update instance_operation_cfs_subparam set
			 dt_updated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
			,updater_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
			,is_sensitive=<cfqueryparam cfsqltype="cf_sql_bit" value="#arguments.isSensitive#" />	
			,note=<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.note)#" />	<!--- *** XSS!!! --->
			where instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			AND instance_operation_cfs_subparam=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.subparam#"/>
		</cfquery>
		<!--- *** проверить существование - 404 ---> 
		<cfreturn noData().withStatus(204, "No Content") />		
	</cffunction><!--- put --->

</cfcomponent>

v1/resources/instance_operation_cfs_subparam_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams/{instanceOperationCfsParamUid}/subparams">
<!--- забавно, что мы организуем эндпойнт коллекции только ради операции создания. Список (коллекция) отдельно не используется, а выгружается в составе более высокоуровневых сущностей --->


	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>
	

	<cffunction name="post" hint="создание метаданных субпараметра CFS параметра операции инстанса">
		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cfargument name="subparam" type="string" required=true hint="type:string"/>
		<cfargument name="isSensitive" type="boolean" required=false default=false hint="type:boolean"/>
		<cfargument name="note" type="string" required=false default="" hint="type:string"/>
		<!--- arguments.specificationId injected implicitly --->
		
<!--- 		<cfargument name="instanceOperationCfsParamUid" type="string" required=true hint="type:guid"/>
		<cfargument name="subparam" type="string" required=true hint="type:string"/>		
		<cfargument name="isSensitive" type="boolean" required=true/>
		<cfargument name="note" type="string" required=false default=""/> --->
		
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>			
			<cfset this.helper.validateField(arguments, "isSensitive", "boolean")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>		
		
		<cfquery name="local.qCheckAccess" result="local.result">
			select count(*) as cnt 
			from instance_operation_cfs_param iop
			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)
			where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
		</cfquery>			
 		<cfif local.qCheckAccess.cnt EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Instance CFS parameter not found or access denied")).withStatus(404)/>
		</cfif>			
		
		<cfquery name="local.qCheckDuplicates" result="local.result">
			select count(*) as cnt 
			from instance_operation_cfs_subparam ios
			where ios.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
			AND ios.instance_operation_cfs_subparam=<cfqueryparam cfsqltype="cf_sql_varchar" value=#arguments.subparam#/>
		</cfquery>		
		 <cfif local.qCheckDuplicates.cnt GT 0>
			<cfreturn representationOf(this.helper.formatMessage("Subparameter already exists")).withStatus(400)/>
		</cfif>	
		
		<cfquery name="local.qSave" result="local.result">
			insert into instance_operation_cfs_subparam
			(instance_operation_cfs_param_uid, instance_operation_cfs_subparam, is_sensitive, note, dt_created, creator_id, dt_updated, updater_id) 
			values
			(
			 <cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" />
			,<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.subparam)#" />
			,<cfqueryparam cfsqltype="cf_sql_bit" value="#arguments.isSensitive#" />
			,<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.note)#" />
			,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
			,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
			,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
			,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
			);
		</cfquery>
	

		
		<cfreturn noData()
			.withStatus(201, "Created") 
		/>	
	</cffunction><!--- get --->	
	
	

</cfcomponent>

v1/resources/instance_operation_default.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations/{instanceUid}/{svcOperationId}" hint="template for default instance operation. Legacy URL: /instanceOperations/default/{svcOperationId}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

		
	<cffunction name="get" hint="Шаблон операции экземпляра">
		<cfargument name="instanceUid" type="string" required=true hint="type:guid|'default'"/>
		<cfargument name="svcOperationId" type="string" required=true hint="type:integer"/>
					
		<cftry>
			<cfif arguments.instanceUid NEQ 'default'>
				<cfset this.helper.validateField(arguments, "instanceUid", "guid")/> 
			</cfif>
			<cfset this.helper.validateField(arguments, "svcOperationId", "integer")/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset var local={}/>
		
		<cfquery name="local.qSvcOperation" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">o.svc_operation_id</m:field>
				<m:field>o.svc_id</m:field>
				<m:field title="operation">o.operation</m:field>
				<m:field title="URL">o.url</m:field>
				<m:field title="Описание">o.descr</m:field>
				<m:field title="Руководство">o.man</m:field>
			</m:field_set>	
			from svc_operation o
			where o.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationId#" null=#!isValid('integer',arguments.svcOperationId)#/>
		</cfquery>	
		
		<cfif local.qSvcOperation.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
		</cfif>
		
		<!--- получаем параметры из свежего стейта --->
		<cfset local.instanceDataParams = {}/>		
		<cfset var currentState = {}/>		
		<cfquery name="local.qState" result="local.result">
			select instance_data->'params' as params,
			instance_data::text as instance_data
			from instance_state
			where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
			order by version desc
			limit 1
		</cfquery>	
		<cfif local.qState.recordCount GT 0>
			<cftry>
				<cfset var currentState = deserializeJson(local.qState.instance_data)/>
				<cfset local.instanceDataParams = deserializeJson(local.qState.params)/>
				<cfcatch type="ANY">
					<cfset currentState = {}/>
					<cfset local.instanceDataParams = {}/>
				</cfcatch>
			</cftry>
		</cfif>

		
		<cfquery name="local.qCfsParam" result="local.result">
			select 
			<m:field_set titleMapOut="local.cfsParamMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">p.svc_operation_cfs_param_id</m:field>
				<!--- <m:field cfSqlType="CF_SQL_INTEGER">p.svc_operation_id</m:field> --->
				<m:field>p.svc_operation_cfs_param</m:field>
				<m:field>p.label</m:field>
				<m:field>p.data_type</m:field>
				<m:field>null as data_descriptor</m:field> 
				<!--- стоит помнить, что formatter выполняется при окончательном форматировании резалтсета в json --->
				<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#> p.value_list</m:field><!--- ниже мы обрабатываем резалтсет и в случае, когда подразумевается список, заменяем пустое значение пустым массивом, который интерпретируется как пустой список (по крайней мере не пользуемся специальным текстовым значением) --->
				<m:field <!--- formatter=#function(x){return listToArray(generateValueList(x));}# --->>p.func</m:field><!--- *** отчаявшись сделать красиво --->
				<m:field>p.ref_svc_id</m:field>
				<m:field formatter=#request.castToBool#>p.is_required</m:field>
				<m:field formatter=#request.castToBool#>p.is_hidden</m:field>
				<m:field formatter=#request.castToBool#>p.is_disabled</m:field>
				<m:field>p.default_value</m:field><!--- can be overridden by default_compute --->
				<m:field>p.default_compute</m:field>
				<m:field>p.regex</m:field>
				<m:field>p.unique_scope</m:field>
				<m:field>p.maxlength</m:field>
				<m:field>p.minlength</m:field>				
				<m:field>p.maxvalue</m:field>
				<m:field>p.minvalue</m:field>
				<m:field>p.descr</m:field>
				<m:field>p.man</m:field>
				<m:field>p.sort</m:field>
				<m:field>p.nested_ref</m:field>
				<!--- <m:field>p.config::text as config</m:field> --->
				<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_cfs_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)
					join svc_operation oc on (om.svc_id=oc.svc_id AND om.operation='modify' AND oc.operation='create')
					join svc_operation_cfs_param c on (oc.svc_operation_id=c.svc_operation_id AND m.svc_operation_cfs_param=c.svc_operation_cfs_param)
					where c.svc_operation_cfs_param_id=p.svc_operation_cfs_param_id
					order by c.svc_operation_cfs_param_id
					limit 1
				) as is_modifiable</m:field>
				<m:field formatter=#request.castToBool#>p.is_sensitive</m:field>
			</m:field_set>	
			from svc_operation_cfs_param p
			where p.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationId#" null=#!isValid('integer',arguments.svcOperationId)#/>
			AND p.is_actual
			order by p.sort, p.svc_operation_cfs_param_id
		</cfquery>		

		
 		<cfloop query="local.qCfsParam">
			<cfif len(local.qCfsParam.func)><!--- приоритет отдается функции перед списком значений--->
				<cfset var args = {
					"functionName":"#local.qCfsParam.func#", 
					"svcId":#local.qSvcOperation.svc_id#, 
					"contractId":#arguments.contractId#
				}/> <!--- если появятся новые аргументы у новых функций, будем добавлять их сюда --->
				<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.qCfsParam.value_list"/>
				
				<cfif len(trim(local.qCfsParam.value_list)) AND listLen(local.qCfsParam.value_list) EQ 1>
					<cfset local.qCfsParam.default_value=local.qCfsParam.value_list/><!--- *** не очень красиво перетирать query, но делаем это довольно часто --->
				</cfif>
			<cfelseif len(local.qCfsParam.nested_ref)><!--- *** спрашивается, зачем нам изгаляться и возвращать замыкание, когда нам нужен json --->
				<cfset var args = {
					"functionName":"#local.qCfsParam.nested_ref#", 
					"config":"#local.qCfsParam.config#"
				}/>
				<cfinvoke component="instance_operation_cfs_param" method="generateClosure" argumentCollection=#args# returnVariable="local.fNestedRef"/>
				<cfset local.qCfsParam.nested_ref_data = local.fNestedRef() />
			<cfelseif len(local.qCfsParam.state_path) AND isStruct(currentState)><!--- deserializeJson может вернуть пустую строку вместо структуры, 
				по крайней мере, если получает поле пустого резалтсета --->
				<cfset var args = {
					"state":#currentState#,
					"path":"#local.qCfsParam.state_path#"
				}/>
				<cfinvoke component="instance_operation_cfs_param" 
					method="getListFromState" 
					argumentCollection=#args#
					returnVariable="local.qCfsParam.value_list"
				/> 
			</cfif>	
			
			<cfif (len(local.qCfsParam.func) OR len(local.qCfsParam.expression) OR len(local.qCfsParam.state_path) OR len(local.qCfsParam.nested_ref)) 
				AND len(local.qCfsParam.value_list) EQ 0>				
				<cfset local.qCfsParam.value_list = []/>
			</cfif>			
		
			<cfif data_type EQ "map" OR data_type EQ "map-fixed" OR data_type EQ "array-map-fixed"><!--- *** частичное дублирование с instance_operation_cfs_param.processMap --->
				<!--- без полей инстанса, только шаблон --->
				<cfquery name="local.qSubparam">
				select 
					 sp.svc_operation_cfs_param_id
					,sp.svc_operation_cfs_subparam_id
					,sp.svc_operation_cfs_subparam
					,sp.label
					,sp.data_type
					,sp.value_list
					,sp.depends_on_cfs_params
					,sp.expression
					,sp.is_required
					,sp.is_hidden
					,sp.is_disabled
					,sp.descr
					,sp.man
					,sp.default_value
					,sp.minlength
					,sp.maxlength				
					,sp.regex
					,sp.sort
					,sp.is_sensitive
					,sp.ref_svc_id
					,sp.unique_scope
					,sp.default_compute
					,sp.func
				from svc_operation_cfs_subparam sp
				where sp.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#svc_operation_cfs_param_id#"/>
				order by sp.sort
				</cfquery>		

								
			
				<cfset var mapDescriptor=structNew("linked")/>
				<!--- *** выдавать на фронт expression нам ни к чему, это временное явление --->
				<cfloop query="local.qSubparam">
				
					<cfif len(local.qSubparam.func)><!--- приоритет отдается функции перед списком значений--->
						<cfset var args = {
							"functionName":"#local.qSubparam.func#", 
							"svcId":#local.qSvcOperation.svc_id#, 
							"contractId":#arguments.contractId#
						}/> 
						
						<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.qSubparam.value_list"/>
						
						<cfif len(trim(local.qSubparam.value_list)) AND listLen(local.qSubparam.value_list) EQ 1>
							<cfset local.qSubparam.default_value=local.qSubparam.value_list/><!--- *** не очень красиво перетирать query, но делаем это довольно часто --->
						</cfif>
					</cfif>	
					
					<cfif (len(local.qSubparam.func) OR len(local.qSubparam.expression) ) 
						AND len(local.qSubparam.value_list) EQ 0>				
						<cfset local.qSubparam.value_list = []/>
					</cfif>	
					
					<cfset var def = computeDefault(local.qSubparam.default_compute, local.qSubparam.default_value, qSvcOperation.svc_id, arguments.usrId)/>
					<!--- *** пока не пытаемся восстановить значения из стейта
					<cfif len(local.qSubparam.default_value) EQ 0>
						<!--- и вот наконец мы извлекаем старое значение из стейта --->
						<cfset var paramName = this.helper.snake2camel(local.qSubparam.svc_operation_cfs_subparam)/>
						<cfif structKeyExists(local.instanceDataParams, paramName)>
							<cfset var oldValue = structFind(local.instanceDataParams, paramName)/>
							<cfset def = isStruct(oldValue) ? serializeJson(oldValue) : oldValue/> <!--- потому что передаем строку, а не объект --->
						</cfif>
					</cfif> --->
					
					<cfset structInsert(mapDescriptor, local.qSubparam.svc_operation_cfs_subparam,
							{
								"svcOperationCfsSubparamId":"#local.qSubparam.svc_operation_cfs_subparam_id#", 
								"svcOperationCfsSubparam":"#local.qSubparam.svc_operation_cfs_subparam#", 
								"label":"#local.qSubparam.label#", 
								"dataType":"#local.qSubparam.data_type#", 
								"valueList":"#local.qSubparam.value_list#", 
								"dependsOnCfsParams":"#local.qSubparam.depends_on_cfs_params#", 
								"expression":"#local.qSubparam.expression#", 
								"minlength":"#local.qSubparam.minlength#", 
								"maxlength":"#local.qSubparam.maxlength#", 
								"regex":"#local.qSubparam.regex#", 
								"man":"#local.qSubparam.man#", 
								"descr":"#local.qSubparam.descr#", 
								"defaultValue":"#def#", 
								"isRequired":#request.castToBool(local.qSubparam.is_required)#,
								"isHidden":#request.castToBool(local.qSubparam.is_hidden)#,
								"isHidden":#request.castToBool(local.qSubparam.is_disabled)#,
								"isSensitive":#request.castToBool(local.qSubparam.is_sensitive)#,
								"refSvcId":#local.qSubparam.ref_svc_id#,
								"uniqueScope":"#local.qSubparam.unique_scope#",
								<!--- "defaultCompute":"#local.qSubparam.func#",
								"func":"#local.qSubparam.default_compute#", --->
								"isModifiableDefinition":false
							}, true
						)
					/><!--- *** вообще-то передавать в дескриптор func и default_compute не нужно, это для не забыть --->
					
				</cfloop>	
				
				<cfset local.qCfsParam.data_descriptor = mapDescriptor/>				
			</cfif><!---/map --->
			
		</cfloop>
		
		<!--- <cfdump var=#local.qCfsParam# abort=true/> --->
		<!--- <cfset queryDeleteColumn(local.qCfsParam, "func")/> --->
		
		<cfset var out=structNew("linked")/>
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		
		<cfset "out.svcOperation" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qSvcOperation, this.helper.snake2camel
		)/>	
		
		<cfset "out.svcOperation.cfsParams"=[]/>
		<!--- подставляем значение из стейта, если оно там есть (некорректно, потому что в стейте RFS параметры) --->			
		
		<cfloop query=#local.qCfsParam#>			
			<cfset local.qCfsParam.default_value = computeDefault(local.qCfsParam.default_compute, local.qCfsParam.default_value, qSvcOperation.svc_id, arguments.usrId)/><!--- *** не очень хорошо модифицировать запрос --->
			<cfif len(local.qCfsParam.default_value) EQ 0>
				<!--- и вот наконец мы извлекаем старое значение из стейта --->
				<cfset var paramName = this.helper.snake2camel(local.qCfsParam.svc_operation_cfs_param)/>
				<cfif structKeyExists(local.instanceDataParams, paramName)>
					<cfset var oldValue = structFind(local.instanceDataParams, paramName)/>
					<cfset local.qCfsParam.default_value = isStruct(oldValue) ? serializeJson(oldValue) : oldValue/> <!--- потому что передаем строку, а не объект --->
				</cfif>
			</cfif>
			<cfset var rec = this.helper.appendRecord(structNew("linked"), "", local.cfsParamMap, local.qCfsParam, this.helper.snake2camel)/>
			<!--- <cfdump var=#rec# abort=true/> --->
			
			<cfset arrayAppend(out.svcOperation.cfsParams, rec)/>
		</cfloop>
		
		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
		
	</cffunction>	
	
	<!--- *** выглядит ужасно, но красивый вариант не придумывается ряд месяцев --->
<!--- 	*** Внимание! нужно учитывать скоуп уникальности --->
	<cffunction name="computeDefault">
		<cfargument name="defaultCompute"/>
		<cfargument name="original"/>
		<cfargument name="service_id" type="numeric"/> 
		<cfargument name="usr_id" type="numeric"/>
		
		<cfswitch expression=#arguments.defaultCompute#>
			<cfcase value="display_name">
				<cfset var dnGenerator = CreateObject("component", "instance_ls")/>
				<cfreturn dnGenerator.generateDefaultName(arguments.service_id,arguments.usr_id)/>			
			</cfcase>			
			<cfcase value="append_pseudorandom">
				<cfset var tGenerator = new lib.TokenGenerator()/>
				<cfreturn "#arguments.original##lcase(tGenerator.nextToken(6))#"/><!--- почему-то длина отличается, чтобы получить 8 знаков, пишу 6	 --->
			</cfcase>
			<cfcase value="display_name_pseudorandom">
				<cfset var tGenerator = new lib.TokenGenerator()/>
				<cfset var dnGenerator = CreateObject("component", "instance_ls")/>
				<cfreturn "#dnGenerator.generateDefaultName(arguments.service_id,arguments.usr_id)##lcase(tGenerator.nextToken(6))#"/>	
			</cfcase>
			<cfdefaultcase>
				<cfreturn #arguments.original#/>
			</cfdefaultcase>
		</cfswitch>
		
	</cffunction>

</cfcomponent>

v1/resources/instance_operation_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>
	
	<!---спецификация полей, пригодных для фильтрации (и сортировки)--->
	<cfset this.fieldsSpec = {
		"instance_operation_uid"={prefix="o", type="guid"},
		"instance_uid"={prefix="o", type="guid"},
		"dt_created"={prefix="o", type="date"},
		"dt_updated"={prefix="o", type="date"},		
		"creator_id"={prefix="o", type="integer"},
		"creator_email"={prefix="c", expression="email", type="string"},
		"creator_shortname"={prefix="c", expression="shortname", type="string"},
		"updater_id"={prefix="o", type="integer"},
		"updater_email"={prefix="u", expression="email", type="string"},
		"updater_shortname"={prefix="u", expression="shortname", type="string"},
		"dt_submit"={prefix="o", type="date"},
		"submit_result"={prefix="o", type="string"},		
		"dt_start"={prefix="o", type="date"},
		"dt_finish"={prefix="o", type="date"},
		"specification_id"={prefix="s", type="integer"},
		"specification_item_id"={prefix="i", type="integer"},
		"contract_id"={prefix="inr", type="integer"},
		"display_name"={prefix="e", type="string"},
		"service_id"={prefix="e", type="integer"},
		"svc"={prefix="v", type="string"},
		"error_log"={prefix="o", type="string"},
		"note"={prefix="o", type="string"},
		"duration"={expression="extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start)", type="numeric"}	
	}/>


	<!--- <cffunction name="post" hint="Создание новой операции с параметрами. Принимаются CFS параметры, генерируются RFS параметры. Запускается операция отдельным вызовом. До запуска CFS параметры могут корректироваться (а надо?). Запущенная операция навсегда становится Read Only (при желании можем реализовать ее клонирование). Можем реализовать корректировку параметров, но непонятно, зачем. Когда мы конфигурируем инстанс, на самом деле мы задаем CFS параметры операции create. Можно править параметры, а можно пересоздать операцию с новыми параметрами. Предусмотреть удаление (метод DELETE) незапущенной операции?"> --->
	
	<cffunction name="get" hint="Список операций">
		<cfargument name="pageSize" type="string" hint="type:integer" default="100"/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset var local={}/>
		<cftry>
						
		
			<!---разбор и проверка параметров запроса--->
			<cftry>
				<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
				<cfset this.helper.validateField(arguments, "page", "integer")/>
				
				<!---мы мирно игнорируем поля, отсутствующие в спецификации, что позволяет не делать исключения для orderBy и т.п.--->
				<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
				
				<cfcatch type="invalidParamValue">
					<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
				</cfcatch>
				<cfcatch type="any">
					<cfreturn representationOf(cfcatch)/>				
				</cfcatch>
			</cftry>				
					
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field title="operation">o.operation</m:field>
					<m:field title="instance_operation_uid">o.instance_operation_uid::text as instance_operation_uid</m:field>
					<m:field title="instance_uid">o.instance_uid::text as instance_uid</m:field>
					<m:field title="dt_created">to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
					<m:field title="dt_updated">to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
					<m:field title="Отправка">to_char(o.dt_submit, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_submit</m:field>
					<m:field>o.submit_result</m:field>					
					<m:field title="Начало">to_char(o.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
					<m:field title="Завершение">to_char(o.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
					<m:field title="Длит. сек">extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start) as duration</m:field>
					<m:field title="Успешно">o.is_successful</m:field>
					<m:field title="Экземпляр">e.display_name</m:field>
					<m:field title="service_id">e.service_id</m:field>
					<m:field title="Услуга">v.svc</m:field>		
					<m:field>o.error_log</m:field>
					<m:field>o.note</m:field>
					<m:field>i.specification_item_id</m:field>	
					<m:field>s.specification_id</m:field>
					<m:field>d.contract_id</m:field>		
					<m:field>o.creator_id</m:field>				
					<m:field>o.updater_id</m:field>				
					<m:field>c.login as creator_login</m:field>		
					<m:field>c.email as creator_email</m:field>					
					<m:field>c.shortname as creator_shortname</m:field>					
					<m:field>u.login as updater_login</m:field>		
					<m:field>u.email as updater_email</m:field>	
					<m:field>u.shortname as updater_shortname</m:field>	
				</m:field_set>	
				<cfsavecontent variable="local.fromClause">
					from instance_operation o 
					join instance e on (o.instance_uid=e.instance_uid)
					left outer join specification_item i on (e.specification_item_id=i.specification_item_id)
					left outer join specification s on (i.specification_id=s.specification_id)
					left outer join contract d on (s.contract_id=d.contract_id)
					left outer join svc v on (e.service_id=v.svc_id)
					left outer join usr c on (e.creator_id=c.usr_id)
					left outer join usr u on (e.updater_id=u.usr_id)
				</cfsavecontent>#local.fromClause#
				where 
					s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
					<m:filter_build filter=#filter#/>
				order by 
					<m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/><!---no sort length limit--->
				<cfif arguments.pageSize GT 0>
					limit #arguments.pageSize#
					offset #arguments.pageSize*(arguments.page-1)#
				</cfif>
			</cfquery>
			
			<cfquery name="local.qCnt">
				select count(*) as cnt
				#local.fromClause#
				where 
				s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
				<m:filter_build filter=#filter#/>
			</cfquery> 

			<cfquery name="local.qTotal">
				select count(*) as cnt
				#local.fromClause#
				<!--- from instance_operation o 
				join instance e on (o.instance_uid=e.instance_uid)
				left outer join specification_item i on (e.specification_item_id=i.specification_item_id)
				left outer join specification s on (i.specification_id=s.specification_id) --->
				where 
				s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
			</cfquery> 

			<cfset var out=structNew("linked")/>

			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
			<cfset "out.total"=#local.qTotal.cnt#/>
			<cfset "out.resultSetSize"=#local.qCnt.cnt#/>
			<cfset "out.pageSize"=#arguments.pageSize*1#/>
			<cfset "out.page"=#arguments.page*1#/>
			<cfset "out.orderBy"=#arguments.orderBy#/>	
			
			<cfset var resultCollection=[]/>
			<cfloop query=#local.qRead#>
				<cfset var rec=structNew("linked")/>				
				<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
				<cfset arrayAppend(resultCollection, rec)/>
			</cfloop>
			
			<cfset "out.size"=#arrayLen(resultCollection)#/>		
			<cfset "out.results"=#resultCollection#/>		
			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>
			</cfcatch>
		</cftry>
		
	</cffunction> 
	
	
	
	
	
	<cffunction name="post" hint="Создание новой операции. Отдельно загружаются CFS параметры, генерируются RFS параметры. Запускается операция отдельным вызовом. До запуска CFS параметры могут корректироваться (а надо?). Запущенная операция вместе с параметрами навсегда становится Read Only (при желании можем реализовать ее клонирование). Можем реализовать корректировку параметров, но непонятно, зачем. Когда мы конфигурируем инстанс, на самом деле мы задаем CFS параметры операции create. Можно править параметры, а можно пересоздать операцию с новыми параметрами. Предусмотреть удаление (метод DELETE) незапущенной операции?">
		<cfargument name="instanceUid" type="string" required=true hint="type:guid"/>
		<cfargument name="operation" type="string" required=true hint="type:string decsiption: operation name, e.g. create"/>
		<cfargument name="note" type="string" required=false default="" hint="type:string decsiption: user notes"/>
		<!--- <cfargument name="resourceRealmId" type="string" default=-1 hint="type:integer decsiption: идентификатор ресурсной платформы"/> ---><!--- *** --->
		<!--- *** полноценная валидация операции предполагает проверку, что операция поддерживается для данного сервиса и текущего состояния инстанса. Можно представить себе зависимость этого от параметров, но хотелось бы избежать такого усложнения --->
		<!--- Можно отметить, что, например, допустимость операции delete зависит от контекста - сколько времени сервис остановлен --->
		<cftry>
			<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
			
			<cfquery name="local.qCheckOperation" result="local.result">
				select count(*) as cnt 
				from svc_operation so
				join svc s on so.svc_id=s.svc_id	
				join instance e on (s.svc_id=e.service_id)
				where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
				AND so.operation=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.operation#"/>
			</cfquery>
			<cfif local.qCheckOperation.cnt EQ 0>
				<cfthrow type="invalidParamValue" message="Operation unsupported for this service/instance" detail="#arguments.operation#"/>
			</cfif>
			
			<cfquery name="local.qCheckAccess" result="local.result">
				select count(*) as cnt
				from instance e 
				join specification_item si on (e.specification_item_id=si.specification_item_id)
				where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
				AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
			</cfquery>	
			<cfif local.qCheckAccess.cnt EQ 0>
				<!--- *** вместо выбрасывания исключения мы сразу прерываем выполнение метода --->
				<cfreturn representationOf(this.helper.formatMessage("Instance not accessible", "Instance is not accessible to the current user (at least does not belong to the default specification)")).withStatus(403)/>
			</cfif>
			<!--- *** Добавить проверку разрешенных переходов, например, create нельзя сделать на развернутом экземпляре
			*** Вероятно, допустимые переходы нужно либо специфицировать в документации, либо явно опубликовать (инстанс может публиковать список допустимых операций) --->
			
			<!--- <cfif listFind("create,redeploy",arguments.operation)>
				<cfset checkResourceRealmId(arguments.resourceRealmId, arguments.instanceUid)/>
			</cfif> --->
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset local={}/>
		<cfset local.instanceOperationUid=#createGUID()#/>
		
		<cftry>		
			<cfquery name="local.qSave">
				insert into instance_operation
				(instance_uid, instance_operation_uid, operation, note, dt_created, creator_id, dt_updated, updater_id) 
				values 
				(
				 <cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" />
				,<cfqueryparam cfsqltype="cf_sql_other" value="#local.instanceOperationUid#" />
				,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.operation#"/>	
				,<cfqueryparam cfsqltype="cf_sql_varchar" value="#htmlEditFormat(arguments.note)#"/>	
				,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
				,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
				);
			</cfquery>		
			
			<cfcatch type="any">
				<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/><!--- это не нужно показывать в продуктиве --->
			</cfcatch>
		</cftry>
		
		<cfreturn noData()
			.withHeaders({location: "./#local.instanceOperationUid#"})
			.withStatus(201, "Created") 
		/>				
	</cffunction>	
	
	<!--- *** NOT USED --->
	<cffunction name="checkResourceRealmId" returntype="void" hint="deprecated">
	<!--- В данном случае мы проверяем соответствие платформы сервису, а не операции, поэтому вопрос, нужен ли для данной операции параметр resourceRealm, надо решать вне этой функции  --->
		<cfargument name="resourceRealmId" type="numeric" required=true/>
		<cfargument name="instanceUid" type="guid" required=true/>	
		
		<cfset var local={}/>
		
		<cfif NOT resourceRealmId GT 0>
			<cfthrow type="InvalidParamValue" message="Resource realm undefined" detail="Не выбрана ресурсная платформа"/><!--- *** для многих операций она и не нужна --->
		</cfif>
		
		<cfquery name="local.qContract">
			select c.contract_id
			from instance e 
			join specification_item si on (e.specification_item_id=si.specification_item_id)
			join specification s on (si.specification_id=s.specification_id)
			join contract c on (s.contract_id=c.contract_id)
			where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid("guid",arguments.instanceUid)#/> 
		</cfquery>	
		
		<cfquery name="local.qCheckResourceRealmType">
			select r.resource_realm_type_id
			from resource_realm r 
			join resource_realm_type t on (r.resource_realm_type_id=t.resource_realm_type_id)
			join svc s on (t.resource_realm_type_id=s.resource_realm_type_id)
			join instance e on (s.svc_id=e.service_id)
			where r.resource_realm_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.resourceRealmId# null=#!isValid("integer",arguments.resourceRealmId)#/> 
			AND e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceUid# null=#!isValid("guid", arguments.instanceUid)#/> 
		</cfquery>
		<cfif local.qCheckResourceRealmType.recordCount EQ 0>
			<cfthrow type="InvalidParamValue" message="Resource realm does not match service" detail="Ресурсная платформа не соответствует сервису"/>
		</cfif>
		
		<cfquery name="local.qCheckResourceRealmAccess">
			select r.resource_realm_id
			from resource_realm r 
			join resource_realm_access a on (r.resource_realm_id=a.resource_realm_id)
			where  r.resource_realm_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.resourceRealmId# null=#!isValid("integer",arguments.resourceRealmId)#/> 
			AND (a.contract_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#local.qContract.contract_id#/> 
				OR a.contract_id=0)
			AND a.is_enabled
		</cfquery>

		<cfif local.qCheckResourceRealmAccess.recordCount EQ 0>
			<cfthrow type="InvalidParamValue" message="Access to the resource realm denied for current contract" detail="У текущего договора id=#local.qContract.contract_id# нет доступа к выбранной ресурсной платформе"/>
		</cfif>
		<!--- 
		Проверить доступ (это предварительная проверка! настоящая проверка перед запуском операции) 
		Итого: фильтрация выпадающего списка
		Проверка атрибута операции (из которого получается RFS параметр)
		Проверка RFS параметров перед запуском операции
		(Можно еще вставить параноидальную проверку при сабмите, когда мы формируем параметры запроса)
		(Можно еще вставить параноидальную проверку в cmdb-api, чтобы застраховаться от дефектов админки)
		--->		
		<cfreturn/>
	</cffunction>

</cfcomponent>

v1/resources/instance_operation_run.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations/{instanceOperationUid}/run">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>


	<cffunction name="post" hint="Запуск выполнения операции">
		<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>

		<cfset var local={}/>
		
		<cftry>
			<cfset this.helper.keyExistsAndValid(arguments, "instanceOperationUid", "guid")/>
			
			<cfquery name="local.qCheckAccess" result="local.result">
				select count(*) as cnt
				from instance_operation io 
				join instance e on (io.instance_uid=e.instance_uid)
				join specification_item si on (e.specification_item_id=si.specification_item_id)
				where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
				AND si.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/> 
			</cfquery>	
			<cfif local.qCheckAccess.cnt EQ 0>
				<cfreturn representationOf(this.helper.formatMessage("Instance not accessible", "Instance is not accessible to the current user (at least does not belong to the default specification)")).withStatus(403)/>
			</cfif>
			
			<cfquery name="local.qSvcOperation" result="local.result">
				select io.operation, s.svc_id, so.svc_operation_id  
				from instance_operation io 
				join instance e on (io.instance_uid=e.instance_uid)
				join svc s on (e.service_id=s.svc_id)
				join svc_operation so on (s.svc_id=so.svc_id AND io.operation=so.operation)
				where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
			</cfquery>
			
			<cfquery name="local.qInstance" result="local.result">
				select io.instance_uid 
				from instance_operation io 
				where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
			</cfquery>
			
			<cfquery name="local.qCheckStartedCurrentOp" result="local.result">
				select io.dt_submit, io.dt_finish, io.dt_start, io.submit_result, io.status_url  
				from instance_operation io 		
				where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
			</cfquery>	
			<cfif local.qCheckStartedCurrentOp.recordCount EQ 0>				
				<cfreturn representationOf(this.helper.formatMessage(
					"There is no configured instance operation with uid #arguments.instanceOperationUid#",
					"Instance operation not found")
					).withStatus(404)/>
			</cfif>
			
			<cfquery name="local.qCheckStarted" result="local.result">
				select io.dt_submit, io.dt_finish, io.dt_start, io.submit_result, io.status_url  
				from instance_operation io 		
				where io.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
				AND left(io.submit_result,1)='2' AND io.dt_finish IS NULL
			</cfquery>
			
			<cfloop query="local.qCheckStarted">
				<cfset var jobStatus = getJobStatus(local.qCheckStarted.status_url)/>
				<cfif (jobStatus EQ 'FAILED') OR (jobStatus EQ 'ABORTED')>
					<cfquery name="local.qMarkFailed">
						update instance_operation 
						set is_successful=false, dt_finish=CURRENT_TIMESTAMP
						where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
					</cfquery>
				<cfelse>
					<cfreturn representationOf(this.helper.formatMessage(
					"There is a started operation on this instance", 
					"Concurrent operations are not supported (job status: #jobStatus#)")
					).withStatus(422)/>
				</cfif>
			</cfloop>
			<cfquery name="local.qCheckCfsParams" result="local.result">
				select sop.svc_operation_cfs_param_id, sop.svc_operation_cfs_param, sop.is_required, 
					iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid, iop.param_value
				from svc_operation_cfs_param sop
				left outer join instance_operation_cfs_param iop on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id AND iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>)
				where sop.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#local.qSvcOperation.svc_operation_id#"/>				
			</cfquery>
			
			<cfset local.cfsParamChecker = CreateObject("component", "instance_operation_cfs_param_ls")/>
			
			<cfloop query=#local.qCheckCfsParams#>
				<cfif (local.qCheckCfsParams.is_required GT 0 AND NOT len(local.qCheckCfsParams.instance_operation_cfs_param_uid) GT 0)>
					<cfreturn representationOf(this.helper.formatMessage("required CFS parameter #local.qCheckCfsParams.svc_operation_cfs_param# (#local.qCheckCfsParams.svc_operation_cfs_param_id#) is missing", "Missing required CFS parameter")).withStatus(422)/>
				<cfelse>
					<cfif isValid("guid",qCheckCfsParams.instance_operation_cfs_param_uid)>
						<cfset local.cfsParamChecker.checkParam(local.qCheckCfsParams.svc_operation_cfs_param_id, qCheckCfsParams.param_value, arguments.usrId, qCheckCfsParams.instance_operation_cfs_param_uid)/>
					<cfelse>
						<cfset local.cfsParamChecker.checkParam(local.qCheckCfsParams.svc_operation_cfs_param_id, qCheckCfsParams.param_value, arguments.usrId)/>
					</cfif>
				</cfif>
			</cfloop>

			<cfset checkCfsResourceRealmAccess(arguments.instanceOperationUid)/>
			
			<cfset var validationMessage = createObject("component","instance_operation").validateCfsParams(arguments.instanceOperationUid)/>
			<cfif len(validationMessage)>
				<cfthrow type="inconsistentCfsParams" message="Some CFS parameters are incompatible" detail="#validationMessage#"/>
			</cfif>
			
			<cfset generateRfsParams(arguments.instanceOperationUid, arguments.usrId, arguments.contragentId, arguments.contractId)/>
			
			<cfset checkRfsResourceRealmAccess(arguments.instanceOperationUid)/>
			
			<cfset submitJob(arguments.instanceOperationUid)/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>			
			<cfcatch type="inconsistentCfsParams">
				<cfreturn representationOf(this.helper.formatException(cfcatch,'Inconsistent CFS parameters')).withStatus(422)/>
			</cfcatch>			
			<cfcatch type="missingDependency">
				<cfreturn representationOf(this.helper.formatException(cfcatch,"Missing Dependency")).withStatus(424)/>
			</cfcatch>
		</cftry> 
		
		<cfset generateRfsParams(arguments.instanceOperationUid, arguments.usrId, arguments.contragentId, arguments.contractId)/>
		
		<cfreturn noData().withStatus(201, "Created") />
	</cffunction>
	
		
	<cffunction name="generateRfsParams">
		<cfargument name="instanceOperationUid" type="guid"/>
		<cfargument name="usrId" type="numeric"/>
		<cfargument name="contragentId" type="numeric"/>
		<cfargument name="contractId" type="numeric"/>		

		<cfquery name="qSvcOperation">
			select so.svc_operation_id, io.instance_uid, so.operation, r.resource_realm, io.resource_realm_id 
			from instance_operation io
			join instance e on (io.instance_uid=e.instance_uid)
			join svc_operation so on (e.service_id=so.svc_id AND io.operation=so.operation)
			left outer join resource_realm r on io.resource_realm_id=r.resource_realm_id
			where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid# null=#!isValid("guid",arguments.instanceOperationUid)#/>
		</cfquery>	
		<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "instanceUid", qSvcOperation.instance_uid)/>
		<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "operationUid", arguments.instanceOperationUid)/>
		<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "modifierId", arguments.usrId)/>

		
		<cfif qSvcOperation.operation EQ "create">
			<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "billingObject", "underconstruction")/>
			<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "contragentCode", getContragentCode(arguments.contragentId))/>
		</cfif>
		

		<cfquery name="qWriteRfsFromCfsParams">
			insert into instance_operation_param (instance_operation_uid,param,param_value)
			select  
				 iocp.instance_operation_uid
				,sop.svc_operation_param
				,iocp.param_value
				from instance_operation_cfs_param iocp
				join svc_operation_cfs_param socp on (iocp.svc_operation_cfs_param_id=socp.svc_operation_cfs_param_id)
				join svc_operation_param sop on (socp.svc_operation_id=sop.svc_operation_id AND socp.svc_operation_cfs_param=sop.svc_operation_param)
				where iocp.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
			ON CONFLICT (instance_operation_uid,param) 
			DO UPDATE SET param_value = EXCLUDED.param_value;
		</cfquery>
		
		<cfquery name="qWriteRfsParamsFromDefaults">
			insert into instance_operation_param (instance_operation_uid,param,param_value)
			select  
				 <cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
				,sop.svc_operation_param
				,sop.default_value
				from svc_operation_param sop 
				where sop.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#qSvcOperation.svc_operation_id#"/> AND length(sop.default_value)>0
			ON CONFLICT (instance_operation_uid,param) 
			DO NOTHING;
		</cfquery>	
	</cffunction>
	

	
	<cffunction name="submitJob">
		<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
		
		<cfquery name="qInstanceOperation">
			select so.url_prefix, so.url_suffix, o.operation, svc.version
			from instance_operation o 
			join instance i on (o.instance_uid=i.instance_uid)
			join svc on (i.service_id=svc.svc_id)
			left outer join svc_operation so on (i.service_id=so.svc_id AND o.operation=so.operation)
			where o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
		</cfquery>
		
		<cfquery name="qState">
			select s.instance_data->'job'->>'jobAppVersion' as jobAppVersion
			from instance_operation o 
			join instance_state s on (o.instance_uid=s.instance_uid)
			where o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
			order by s.version desc
			limit 1
		</cfquery>

		<cfquery name="qInstanceOperationParam">
			select iop.param, iop.param_value
			from instance_operation_param iop 
			where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
			AND length(iop.param)>0
		</cfquery>
		
		
		<cfif len(qState.jobAppVersion)>
			<cfset var submit_url="#qInstanceOperation.url_prefix##qState.jobAppVersion##qInstanceOperation.url_suffix#"/>
		<cfelse>
			<cfset var submit_url="#qInstanceOperation.url_prefix##qInstanceOperation.version##qInstanceOperation.url_suffix#"/>
		</cfif>
		
		<cfquery name="qMarkOperationStart">
			update instance_operation 
			set dt_submit=<cfqueryparam cfsqltype="cf_sql_timestamp" value=#now()#/>
			,dt_start=null, dt_finish=null, submit_result='0'
			,url=<cfqueryparam cfsqltype="cf_sql_varchar" value="#submit_url#"/>
			where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
		</cfquery>				
		
		<cftry>
			<cfhttp method="post" 
				url="#submit_url#" 
				multipart=true 
				multipartType="form-data"
				timeout="3"
				charset="utf-8"	
				result="orchestrator"
			>	
				<cfhttpparam type="HEADER" name="Authorization" value="#request.ORCHESTRATOR_AUTH#"/>
				<cfhttpparam type="HEADER" name="Accept" value="application/json"/>
				<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
				<cfhttpparam type="formfield" name="delay"  value="0sec"/>
				<cfhttpparam type="formfield" name="authHeader"  value=#request.auth_header#/>
				<cfloop query="qInstanceOperationParam">
					<cfhttpparam type="formfield" name="#param#"  value="#param_value#"/>
				</cfloop>		
			</cfhttp>
			
			<cfcatch type="any">
				<cfthrow message="Cannot initiate connection to the orchestrator #cfcatch.message#" detail="#cfcatch.detail#"/>
			</cfcatch>
		</cftry>
		
		<cfif len(orchestrator.errorDetail)>
			<cfthrow type="missingDependency" message="Orchestrator communication error" detail="#orchestrator.errorDetail#"/>
		</cfif>
		
		<cfquery name="qMarkOperationSubmit">
			update instance_operation 
			set submit_result=<cfqueryparam cfsqltype="cf_sql_varchar" value=#orchestrator.responseHeader.status_code#/>
			where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
		</cfquery>

		<cfset sleep(0)/>
		<cfif listFind("200,201",#orchestrator.responseHeader.status_code#)>
			<cftry>
				<cfset var buildURL="#orchestrator.responseHeader.Location#/api/json"/>
				<cfhttp method="get" 
					url="#buildUrl#" 
					timeout="3"
					charset="utf-8"	
					result="job"
				>
					<cfhttpparam type="HEADER" name="Authorization" value="#request.ORCHESTRATOR_AUTH#">
					<cfhttpparam type="HEADER" name="Accept" value="application/json">
				</cfhttp>	

				<cfset var operation_url = ""/>
				<cfif structKeyExists(job,"filecontent")>
					<cfset var jobData=#deserializeJson(job.filecontent)#/>
					<cfset operation_url=jobData.executable.url />	
					<cfquery name="qSetStatusUrl">
						update instance_operation 
							set status_url=<cfqueryparam cfsqltype="cf_sql_varchar" value="#operation_url#api/json" null=#(operation_url EQ "")#/>
						where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
					</cfquery>
				</cfif>				
				
				<cfcatch type="any">
						<cfrethrow/>
				</cfcatch>
			</cftry>		
		</cfif>				
	</cffunction>
	
	<cffunction name="setInstanceOperationRfsParam">
		<cfargument name="instanceOperationUid" type="guid"/>
		<cfargument name="param"/>	
		<cfargument name="val"/>	
		
		<cfquery name="qSaveRfsParam">
			insert into instance_operation_param (instance_operation_uid,param,param_value)
			values ( 
				 <cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#"/>
				,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.param#"/>
				,<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.val#"/>
			)
			ON CONFLICT (instance_operation_uid,param) 
			DO UPDATE SET param_value = EXCLUDED.param_value;
		</cfquery>
	</cffunction>
	
	<cffunction name="getJobStatus">
		<cfargument name="status_url">		
	
		<cftry>
			<cfhttp method="get" 
				url="#arguments.status_url#" 
				timeout="3"
				charset="utf-8"	
				result="local.jobStatus"
			>
				<cfhttpparam type="HEADER" name="Authorization" value="#request.ORCHESTRATOR_AUTH#">
				<cfhttpparam type="HEADER" name="Accept" value="application/json">
			</cfhttp>

			<cftry>
				<cfset var jobStatusData=#deserializeJson(local.jobStatus.filecontent)#/>				
					<cfreturn jobStatusData.result/>
				<cfcatch type="any">
					<cfreturn "cannot obtain job result"/>
				</cfcatch>
			</cftry>
			<cfcatch type="any">
				<cfthrow message="Cannot connect to the orchestrator. #cfcatch.message#" detail="#cfcatch.detail#"/>
			</cfcatch>
		</cftry>
	</cffunction>
	
	<cffunction name="getContragentCode">
		<cfargument name="contragentId">	
		
		<cfquery name="qContragentCode">
			select external_code 
			from contragent
			where contragent_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.contragentId# null=#!isNumeric(arguments.contragentId)#/>
		</cfquery>
	
		<cfreturn #qContragentCode.external_code#/>
	</cffunction>
	
	
	<cffunction name="checkCfsResourceRealmAccess">
		<cfargument name="instanceOperationUid" type="guid">
		
		<cfset var local={}/>
		
 		<cfquery name="local.qParam">
			select sop.svc_operation_cfs_param
			from instance_operation io
			join instance e on (io.instance_uid=e.instance_uid)
			join svc_operation so on (e.service_id=so.svc_id AND io.operation=so.operation)
			join svc_operation_cfs_param sop on (so.svc_operation_id=sop.svc_operation_id)
			where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
			AND LOWER(sop.svc_operation_cfs_param)=LOWER('resourceRealm')
		</cfquery>
		
		<cfif local.qParam.recordCount EQ 0>
			<cfreturn/>
		</cfif>
		
		<cfquery name="local.qOperation">
			select io.operation
			from instance_operation io
			where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
		</cfquery>	
		
		<cfquery name="local.qContract">
			select c.contract_id
			from instance_operation io
			join instance e on (io.instance_uid=e.instance_uid)
			join specification_item si on (e.specification_item_id=si.specification_item_id)
			join specification s on (si.specification_id=s.specification_id)
			join contract c on (s.contract_id=c.contract_id)
			where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
		</cfquery>
			
		<cfquery name="local.qCheckResourceRealmAccess">
			select r.resource_realm_id, r.resource_realm, sop.svc_operation_cfs_param, iop.param_value, a.contract_id, a.is_enabled
			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 resource_realm r on (sop.svc_operation_cfs_param='resourceRealm' AND iop.param_value=r.resource_realm)
			join resource_realm_access a on (r.resource_realm_id=a.resource_realm_id)
			where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
			AND (a.contract_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#local.qContract.contract_id# null=#!isValid("integer",local.qContract.contract_id)#/> OR a.contract_id=0)
			AND a.is_enabled
		</cfquery>
		
		<cfif local.qCheckResourceRealmAccess.recordCount EQ 0>
			<cfthrow message="resource realm specified in CFS params is not available for current contract" detail="CFS resource realm unawailable. Instance operation UID #arguments.instanceOperationUid#. Contract ID #local.qContract.contract_id#"/>
		</cfif>
	</cffunction>
	
	
	<cffunction name="checkRfsResourceRealmAccess">
		<cfargument name="instanceOperationUid" type="guid">
		
		<cfset var local={}/>
		<cfquery name="local.qParamRRExists">
			select count(*) as cnt
			from instance_operation_param iop 
			where iop.param='resourceRealm' AND 
			iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
		</cfquery>
		<cfif local.qParamRRExists.cnt EQ 0>
			<cfreturn/>
		</cfif>
		
		<cfquery name="local.qContract">
			select c.contract_id
			from instance_operation io
			join instance e on (io.instance_uid=e.instance_uid)
			join specification_item si on (e.specification_item_id=si.specification_item_id)
			join specification s on (si.specification_id=s.specification_id)
			join contract c on (s.contract_id=c.contract_id)
			where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
		</cfquery>
			
		<cfquery name="local.qCheckResourceRealmAccess">
			select r.resource_realm_id, r.resource_realm, iop.param, iop.param_value, a.contract_id, a.is_enabled
			from resource_realm r 
			join resource_realm_access a on (r.resource_realm_id=a.resource_realm_id)
			join instance_operation_param iop on (r.resource_realm=iop.param_value AND iop.param='resourceRealm')
			where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid("guid",arguments.instanceOperationUid)#/> 
			AND (a.contract_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#local.qContract.contract_id# null=#!isValid("integer",local.qContract.contract_id)#/> OR a.contract_id=0)
			AND a.is_enabled
		</cfquery>
		
		<cfif local.qCheckResourceRealmAccess.recordCount EQ 0>
			<cfthrow message="resource realm specified in RFS params is not available for current contract" detail="RFS resource realm unawailable. Instance operation UID #arguments.instanceOperationUid#. Contract ID #local.qContract.contract_id#"/>
		</cfif>
	</cffunction>

</cfcomponent>

v1/resources/instance_operation_validate.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations/{instanceOperationUid}/validate-cfs">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>


	<cffunction name="get" hint="Проверка корректности CFS">
		<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>

		<!--- <cfset var local={}/> --->
		
		<!--- *** мы не проверяем доступ к инстансу и существование инстанса 2DO --->

		<cfset var validationMessage = createObject("component","instance_operation").validateCfsParams(arguments.instanceOperationUid)/>
		<cfif len(validationMessage)>
			<cfreturn representationOf(this.helper.formatMessage("Inconsistent CFS parameters", #validationMessage#)).withStatus(422)/>
		</cfif>
		
		<cfreturn noData().withStatus(204, "OK") />
	</cffunction>

</cfcomponent>

v1/resources/notification_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/notifications">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cfset this.fieldsSpec={
		dt_created={prefix="n", type="date"},
		dt_updated={prefix="n", type="date"},
		dt_show_from={prefix="n", type="date"},
		dt_show_to={prefix="n", type="date"},
		contragent_id={prefix="n", type="integer"},
		timeout_sec={prefix="n", type="integer"},
		external_uid={prefix="n", type="guid"},
		external_code={prefix="n", type="string"}
	}
	/>
	 <cffunction name="get" hint="Список уведомлений">
		<cfargument name="pageSize" type="string" hint="type:integer" default="100"/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cftry>
			<cfset local={}/>			
		
			<cftry>
				<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
				<cfset this.helper.validateField(arguments, "page", "integer")/>
				
				<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
				
				<cfcatch type="invalidParamValue">
					<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
				</cfcatch>
				<cfcatch type="any">
					<cfreturn representationOf(cfcatch)/>				
				</cfcatch>
			</cftry>				
			
			<cfset var out={}/>
			<cfset var maxrows=arguments.pageSize*arguments.page/>
			<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field>n.notification_uid::text as notification_uid</m:field>
					<m:field>n.notification</m:field>
					<m:field>n.descr</m:field>
					<m:field>n.url</m:field>
					<m:field>n.timeout_sec</m:field>
					<m:field>n.contragent_id</m:field>
					<m:field>k.external_uid</m:field>
					<m:field>to_char(n.dt_show_from, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_show_from</m:field>
					<m:field>to_char(n.dt_show_to, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_show_to</m:field>
				</m:field_set>	
				from notification n 
				left outer join contragent k on (n.contragent_id=k.contragent_id)
				where 1=1 <m:filter_build filter=#filter#/>
				AND (n.dt_show_from <= CURRENT_TIMESTAMP OR n.dt_show_from IS NULL)
				AND (n.dt_show_to >= CURRENT_TIMESTAMP OR n.dt_show_to IS NULL)
				AND (k.external_uid = <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.companyUid#/> OR n.contragent_id IS NULL)
				order by dt_show_from desc
				limit #maxrows#
			</cfquery>

			<cfquery name="local.qTotal">
				select count(*) as cnt
				from notification n 
				left outer join contragent k on (n.contragent_id=k.contragent_id)
				where 1=1
				AND (n.dt_show_from <= CURRENT_TIMESTAMP OR n.dt_show_from IS NULL)
				AND (n.dt_show_to >= CURRENT_TIMESTAMP OR n.dt_show_to IS NULL)
				AND (k.external_uid = <cfqueryparam cfsqltype="cf_sql_other" value=#arguments.companyUid#/> OR n.contragent_id IS NULL)
			</cfquery> 

			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
			<cfset "out.total"=#local.qTotal.cnt#/>
			<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
			<cfset "out.pageSize"=#arguments.pageSize*1#/>
			<cfset "out.page"=#arguments.page*1#/>
			<cfset "out.orderBy"=#arguments.orderBy#/>
			
			<cfset var resultCollection=[]/>
			<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
				<cfset var rec={}/>				
				<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
				<cfset arrayAppend(resultCollection, rec)/>
			</cfloop>
			
			<cfset "out.size"=#arrayLen(resultCollection)#/>
			<cfset "out.results"=#resultCollection#/>
			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>
			</cfcatch>
		</cftry>
		
	</cffunction> 
</cfcomponent>

v1/resources/param_value_list.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/param-value-list/{svcOperationCfsParamId}" hint="deprecated">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>


	<cffunction name="get" hint="Список значений CFS параметра операции">
		<cfargument name="svcOperationCfsParamId" type="string" required=true hint="type:integer"/>
		<!--- сначала нам известен только параметр сервиса. вся остальная инфа поступает через аргументы --->

		<cfset var local={}/>

		<cfquery name="local.qParam">
			select p.depends_on_params
			from svc_operation_cfs_param p
			where p.svc_operation_cfs_param_id = <cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsParamId#/>
		</cfquery>
		
		
		<cfreturn noData().withStatus(204, "OK") />
	</cffunction>

</cfcomponent>

v1/resources/resource_realm.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/resourceRealms/{resourceRealmId}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>


	<cffunction name="get" hint="Сервис">
		<cfargument name="resourceRealmId" type="string" required=true hint="type:integer"/>
		<cftry>
			<cfset this.helper.validateField(arguments, "resourceRealmId", "integer")/>
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>

		<cfquery name="local.qResourceRealm" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field>r.resource_realm_id</m:field>
				<m:field>r.resource_realm_type_id</m:field>
				<m:field>r.parent_id</m:field>
				<m:field>r.resource_realm</m:field>
				<m:field>r.mgmt_api_url</m:field>
				<m:field>p.resource_realm as parent</m:field>
				<m:field>t.resource_realm_type</m:field>
				<m:field>r.descr</m:field>
				<m:field>r.man</m:field>
				<m:field>to_char(r.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>to_char(r.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
			</m:field_set>	
			from resource_realm r
			join resource_realm_type t on (r.resource_realm_type_id=t.resource_realm_type_id)
			left outer join resource_realm p on (r.parent_id=p.resource_realm_id)
			where r.resource_realm_id=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.resourceRealmId#" null=#!isValid('integer',arguments.resourceRealmId)#/>
		</cfquery>	
		
		<cfquery name="local.qMetric" result="local.result">
			select 
			<m:field_set titleMapOut="local.metricMap" lengthOut="local.fieldCount">
				<m:field>m.resource_realm_metric_id</m:field>
				<m:field>m.resource_realm_type_metric_id</m:field>
				<m:field>m.dashboard_url</m:field>
				<m:field>t.metric</m:field>
				<m:field>u.measure</m:field>
				<m:field>u.measure_short</m:field>
			</m:field_set>	
			from resource_realm_metric m
			join resource_realm_type_metric t on (m.resource_realm_type_metric_id=t.resource_realm_type_metric_id)
			join measure u on (t.measure_id=u.measure_id)			
			where m.resource_realm_id=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.resourceRealmId#" null=#!isValid('integer',arguments.resourceRealmId)#/>
		</cfquery>			
		
 		<cfif local.qResourceRealm.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
		</cfif> 

		<cfset var out=structNew("linked")/>
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
	
		<cfset "out.resourceRealm" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qResourceRealm, this.helper.snake2camel
		)/>	
		
		<cfset "out.resourceRealm.metrics"=[]/>
		<cfloop query=#local.qMetric#>
			<cfset arrayAppend(out.resourceRealm.metrics, this.helper.appendRecord(structNew("linked"), "", local.metricMap, local.qMetric, this.helper.snake2camel))/>
		</cfloop>

		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>	

</cfcomponent>

v1/resources/resource_realm_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/resourceRealms">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cfset this.fieldsSpec={
		resource_realm_id={prefix="r", type="integer"},
		resource_realm_type_id={prefix="r", type="integer"},
		parent_id={prefix="r", type="integer"},
		resource_realm_type={prefix="r", type="string"},
		resource_realm={prefix="r", type="string"},
		mgmt_api_url={prefix="r", type="string"}
	}
	/>

	 <cffunction name="get" hint="Список ресурсных платформ">
		<cfargument name="pageSize" type="string" hint="type:integer" default=100/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset local={}/>
		
		
		<cftry>
			<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
			<cfset this.helper.validateField(arguments, "page", "integer")/>
			
			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>		
		
		
		<cfset var out=structNew("linked")/>
		<cfset var maxrows=arguments.pageSize*arguments.page/>
		<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
		
 		<cfquery name="local.qRead" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">r.resource_realm_id</m:field>
				<m:field>r.resource_realm_type_id</m:field>
				<m:field>r.parent_id</m:field>
				<m:field>r.resource_realm</m:field>
				<m:field>r.mgmt_api_url</m:field>
				<m:field>p.resource_realm as parent</m:field>
				<m:field>t.resource_realm_type</m:field>
				<m:field>r.descr</m:field>
				<m:field>r.man</m:field>
				<m:field>to_char(r.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>to_char(r.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
			</m:field_set>	
			from resource_realm r
			join resource_realm_type t on (r.resource_realm_type_id=t.resource_realm_type_id)
			left outer join resource_realm p on (r.parent_id=p.resource_realm_id)
			join resource_realm_access a on 
				(r.resource_realm_id=a.resource_realm_id 
				AND (a.contract_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#arguments.contractId#/> 
					OR a.contract_id=0)
				AND a.is_enabled)
			where 1=1 <m:filter_build filter=#filter#/>
			order by <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/>
			limit #maxrows#
		</cfquery>  
		
		<cftry>
			<cfquery name="local.qTotal">
				select count(*) as cnt
				from resource_realm 
				where 1=1 
			</cfquery> 
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>

		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		<cfset "out.total"=#local.qTotal.cnt#/>
		<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
		<cfset "out.pageSize"=#arguments.pageSize*1#/>
		<cfset "out.page"=#arguments.page*1#/>
		<cfset "out.orderBy"=#arguments.orderBy#/>	
		
		<cfset var resultCollection=[]/>
		<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
			<cfset var rec=structNew("linked")/>				
			<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
			<cfset arrayAppend(resultCollection, rec)/>
		</cfloop>		

		<cfset "out.size"=#arrayLen(resultCollection)#/>		
		<cfset "out.results"=#resultCollection#/>
		<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
		<cfreturn representationOf(out)/>		
		
	</cffunction> 

</cfcomponent>

v1/resources/resource_realm_type_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/resourceRealmTypes">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cfset this.fieldsSpec={
		resource_realm_type_id={prefix="r", type="integer"},
		resource_realm_type={prefix="r", type="string"}
	}
	/>

	 <cffunction name="get" hint="Список типов ресурсных платформ">
		<cfargument name="pageSize" type="string" hint="type:integer" default=100/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>

		<cfset local={}/>
		
		
		<cftry>
			<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
			<cfset this.helper.validateField(arguments, "page", "integer")/>
			
			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>		
		
		
		<cfset var out=structNew("linked")/>
		<cfset var maxrows=arguments.pageSize*arguments.page/>
		<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
		
 		<cfquery name="local.qRead" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">t.resource_realm_type_id</m:field>
				<m:field>t.resource_realm_type</m:field>
				<m:field>t.man</m:field>
			</m:field_set>	
			from resource_realm_type t
			where 1=1 <m:filter_build filter=#filter#/>
			order by <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/>
			limit #maxrows#
		</cfquery>  
		
		<cftry>
			<cfquery name="local.qTotal">
				select count(*) as cnt
				from resource_realm_type
				where 1=1 
			</cfquery> 
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>

		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		<cfset "out.total"=#local.qTotal.cnt#/>
		<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
		<cfset "out.pageSize"=#arguments.pageSize*1#/>
		<cfset "out.page"=#arguments.page*1#/>
		<cfset "out.orderBy"=#arguments.orderBy#/>	
		
		<cfset var resultCollection=[]/>
		<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
			<cfset var rec=structNew("linked")/>				
			<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
			<cfset arrayAppend(resultCollection, rec)/>
		</cfloop>		

		<cfset "out.size"=#arrayLen(resultCollection)#/>		
		<cfset "out.results"=#resultCollection#/>
		<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
		<cfreturn representationOf(out)/>		
		
	</cffunction> 

</cfcomponent>

v1/resources/svc.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/services/{svcId}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>


	<cffunction name="get" hint="Сервис"><!--- *** TODO проверка принадлежности тенанту --->
		<cfargument name="svcId" type="string" required=true hint="type:integer"/>
		<cftry>
			<cfset this.helper.validateField(arguments, "svcId", "integer")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<!--- <cfdump var=#arguments#/> <cfabort/> --->		
		

		<cfquery name="local.qSvc" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">s.svc_id</m:field>
				<m:field>s.svc</m:field>
				<m:field>s.svc_short</m:field>
				<m:field>s.code</m:field>
				<m:field>s.version</m:field>
				<m:field>s.orchestrator_name</m:field>
				<m:field>(select url from svc_operation so where so.operation='create' AND so.svc_id=s.svc_id limit 1) as create_url</m:field>
				<m:field formatter=#request.castToBool#>s.is_production_ready</m:field>
				<m:field>s.resource_realm_type_id</m:field>
				<m:field>s.icon_src</m:field>
				<m:field>s.descr</m:field>
				<m:field>s.man</m:field>
			</m:field_set>	
			from svc s
			where svc_id=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.svcId#" null=#!isValid('integer',arguments.svcId)#/>
		</cfquery>	
		
		<cfquery name="local.qSvcOperation" result="local.result">
			select 
			<m:field_set titleMapOut="local.operationsMap" lengthOut="local.fieldCount">
				<m:field>j.svc_operation_id</m:field>
				<m:field>j.operation</m:field>
				<m:field>j.url</m:field>
				<m:field>j.descr</m:field>
				<m:field>j.man</m:field>
			</m:field_set>	
			from svc_operation j
			where j.svc_id=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.svcId#" null=#!isValid('integer',arguments.svcId)#/>
		</cfquery>			
		
 		<cfif local.qSvc.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
		</cfif> 

		<cfset var out=structNew("linked")/>
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		
<!--- 		<cfset var rec={}/>	
		<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qSvc, this.helper.snake2camel)/>
		<cfset "out.svc"=rec/> --->
		
		<cfset "out.svc" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qSvc, this.helper.snake2camel
		)/>	
		
		<cfset "out.svc.operations"=[]/>
		<cfloop query=#local.qSvcOperation#>
			<cfset arrayAppend(out.svc.operations, this.helper.appendRecord(structNew("linked"), "", local.operationsMap, local.qSvcOperation, this.helper.snake2camel))/>
		</cfloop>

		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>	

</cfcomponent>

v1/resources/svc_default.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/services/{svcId}/default" hint="template for default instance">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

	<cffunction name="get" hint="DEPRECATED. Шаблон экземпляра">
		<cfargument name="svcId" type="string" required=true hint="type:integer"/>
	
		<cftry>
			<cfset this.helper.validateField(arguments, "svcId", "integer")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfset var svc = {
			"svcId":"#arguments.svcId*1#",
			"displayName":"#generateDefaultName(arguments.svcId,arguments.usrId)#",
			"descr":""	
		}/>
		
		<cfset var out = structNew("linked")/>
		
		<cfset "out.service" = svc/>
		
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>	
		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>	
	
	<cffunction name="generateDefaultName">	
		<cfargument name="service_id" type="numeric"/> 
		<cfargument name="usr_id" type="numeric"/> 
		
		<cfreturn CreateObject("component", "instance_ls").generateDefaultName(arguments.service_id,arguments.usr_id)/>
	</cffunction> 

</cfcomponent>

v1/resources/svc_grouped_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/servicesGrouped">

<!--- Внимание! Дублирование семантики с svc_ls (как правило, вносить изменения параллельно --->

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>

	<!---спецификация полей, пригодных для фильтрации (?и сортировки)--->
	<cfset this.fieldsSpec={
		dt_created={prefix="s", type="date"},
		dt_updated={prefix="s", type="date"},
		svc_id={prefix="s", type="integer"},		
		resource_realm_type_id={prefix="s", type="integer"},
		sort={prefix="s", type="integer"},
		is_production_ready={prefix="s", type="boolean"},
		svc={prefix="s", type="string"},
		svc_short={prefix="s", type="string"},
		svc_extended_name={type="string"},
		<!--- orchestrator_name={prefix="s", type="string"}, --->
		code={prefix="s", type="string"},
		version={prefix="s", type="string"},
		descr={prefix="s", type="string"},
		man={prefix="s", type="string"},
		svc_group_id={prefix="sg", type="integer"},
		svc_group={prefix="g", type="string"},
		resource_realm_cnt={type="integer"}
	}
	/>

	 <cffunction name="get" hint="Список сервисов с группами для каталога. Без управления страницами и сортировкой (сортировка по порядку группы, сервисы вне групп в конце списка). Если сервис входит в несколько групп, он будет повторяться">
<!--- 		<cfargument name="pageSize" type="string" hint="type:integer" default=500/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/> --->

		<cfset var local={}/>		
		
		<!---parse and validate request parameters--->
		<cftry>
<!--- 			<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
			<cfset this.helper.validateField(arguments, "page", "integer")/> --->			
			<!---мы мирно игнорируем поля, отсутствующие в спецификации, что позволяет не делать исключения для orderBy и т.п.--->
			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			<!--- <cfset var order=this.helper.parseOrderBy(this.fieldsSpec, arguments.orderBy)/> --->
			
			<cfcatch type="invalidParamValue"><!--- <cfreturn representationOf("Проверка связи 3")> --->
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>				
		
		<cfset var out=structNew("linked")/>
	<!--- 	<cfset var maxrows=arguments.pageSize*arguments.page/>
		<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/> --->
		
 		<cfquery name="local.qRead" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">s.svc_id</m:field>
				<m:field title="Сервис">s.svc</m:field>
				<m:field title="Сокращение">s.svc_short</m:field>
				<!--- <m:field>s.orchestrator_name</m:field> --->
				<m:field title="Код">s.code</m:field>
				<m:field title="URL создания">(select url from svc_operation so where so.operation='create' AND so.svc_id=s.svc_id limit 1) as create_url</m:field>
				<m:field formatter=#function(x){return x NEQ 0}#>s.is_production_ready</m:field>
				<m:field>s.resource_realm_type_id</m:field>
				<m:field>s.sort</m:field>
				<m:field>s.version</m:field>
				<m:field>s.icon_src</m:field>
				<m:field>s.descr</m:field>
				<m:field>s.man</m:field>
				<m:field>to_char(s.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
				<m:field>to_char(s.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
				<m:field>sg.svc_group_id</m:field>
				<m:field>g.svc_group</m:field>
				<m:field>(coalesce(s.svc,'') ||' '|| coalesce(s.svc_short,'') ||' '|| coalesce(s.synonyms,'')) as svc_extended_name</m:field>
				<m:field>(select count(distinct r.resource_realm)
				from resource_realm r
				join resource_realm_access a on (
				r.resource_realm_id=a.resource_realm_id 
				AND (a.contract_id IN (
					select contract_id 
					from contract d
					where d.contragent_id=k.contragent_id AND NOT d.is_closed	
				) OR a.contract_id=0) /*0 means access to any contract*/
				AND a.is_enabled)
				where r.resource_realm_type_id=s.resource_realm_type_id) as resource_realm_cnt 
				</m:field> <!--- количество доступных ресурсных платформ --->
			</m:field_set>	
			from svc s 
			left outer join svc_group_svc sg on (s.svc_id=sg.svc_id AND sg.is_enabled)
			left outer join svc_group g on (sg.svc_group_id=g.svc_group_id)
			left outer join contragent k on (
					k.external_uid=<cfqueryparam cfsqltype="CF_SQL_OTHER" value=#arguments.companyUid# null=#!isValid("guid", arguments.companyUid)#/> 
				)
			where 1=1 <m:filter_build filter=#filter#/>
			order by g.sort, g.svc_group_id
			<!--- <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=0/><!---no sort  --->length limit--->
			<!--- limit #maxrows# --->
		</cfquery>  
		
<!--- 		<cftry>
			<cfquery name="local.qTotal">
				select count(*) as cnt
				from svc 
				where 1=1 
			</cfquery> 
			<cfcatch type="any">		
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry> --->
		<!--- <cfdump var=#qRead#/><cfabort/> --->


		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		<!--- <cfset "out.total"=#local.qTotal.cnt#/> --->
		<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
<!--- 		<cfset "out.pageSize"=#arguments.pageSize*1#/>
		<cfset "out.page"=#arguments.page*1#/>
		<cfset "out.orderBy"=#arguments.orderBy#/> --->
		
		<cfset var resultCollection=[]/>
		<cfset var svcMap = structCopy(local.titleMap)/><!--- ради аккуратности, дальше local.titleMap все равно не используем --->
		<cfset var groupMap = withdrawStructKeys(svcMap, "svc_group_id,svc_group")/>		
		
		<cfloop query=#local.qRead# group="svc_group_id"<!--- startRow=#startrow# endRow=#(startrow+maxrows-1)# --->>
			<cfset var grp = structNew("linked")/>				
			<cfset this.helper.appendRecord(grp, "", groupMap, local.qRead, this.helper.snake2camel)/>
			
			<cfset "grp.services"=[]/>
			<cfset var svcRec = structNew("linked")/>
			<cfloop>
				<cfset this.helper.appendRecord(svcRec, "", svcMap, local.qRead, this.helper.snake2camel)/>
				<cfset arrayAppend(grp.services, structCopy(svcRec))/>
<!--- 				<cfdump var=#svcRec#/> --->
			</cfloop>
			
			<cfset arrayAppend(resultCollection, structCopy(grp))/>почему-то здесь работает без structCopy
		</cfloop>		

		<cfset "out.size"=#arrayLen(resultCollection)#/>
		<cfset "out.results"=#resultCollection#/>
		<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	<!--- <cfabort/> --->
		<!---<cfset "out.sql"=#local.result.sql#/>--->	
		<cfreturn representationOf(out)/>		
		
	</cffunction> 
	
	<cffunction name="withdrawStructKeys" access="private" returntype="struct">
		<!--- переносим ключи из входной структуры в возвращаемую (во входной удаляем) --->
		<cfargument name="structFrom" type="struct"/>	
		<cfargument name="keyList" type="string"/>	
		
		<cfset var structOut = {}/>
		
		<cfloop list=#arguments.keyList# item="key">
			<cfif structKeyExists(arguments.structFrom, key)>
				<cfset structInsert(structOut, key, structFind(structFrom, key))/>
				<cfset structDelete(arguments.structFrom, key)/>
			</cfif>			
		</cfloop>
		
		<cfreturn structOut/>		
	</cffunction>

</cfcomponent>

v1/resources/svc_ls.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/services">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>

	<!---спецификация полей, пригодных для фильтрации (?и сортировки)--->
	<!---здесь для простоты без префикса, потому что мы используем вложенный селект, чтобы сделать композитное поле--->
	<cfset this.fieldsSpec={
		"dt_created"={prefix="sv", type="date"},
		"dt_updated"={prefix="sv", type="date"},
		"svc_id"={prefix="sv", type="integer"},
<!--- 		svc_group_id={type="integer"}, --->
		"resource_realm_type_id"={prefix="sv", type="integer"},
		"sort"={prefix="sv", type="integer"},
		"is_production_ready"={prefix="sv", type="boolean"},
		"svc"={prefix="sv", type="string"},
		"svc_short"={prefix="sv", type="string"},
		"svc_extended_name"={prefix="sv", type="string"},
		"orchestrator_name"={prefix="sv", type="string"},
		"synonyms"={prefix="sv", type="string"},
		"code"={prefix="sv", type="string"},
		"version"={prefix="sv", type="string"},
		"descr"={prefix="sv", type="string"},
		"man"={prefix="sv", type="string"},
		"resource_realm_cnt"={prefix="sv", type="integer"}
	}
	/>

	<!--- *** пока сортировка, пагинация и состав полей компонуются так, что приходится добавлять поле сортировки в резалтсет --->
	 <cffunction name="get" hint="Список сервисов">
		<cfargument name="pageSize" type="string" hint="type:integer" default=100/>	
		<cfargument name="page" type="string" hint="type:integer" default="1"/>	
		<cfargument name="orderBy" type="string" hint="type:string, description:comma-separated list of fields to sort by, example:fld1.ASC,fld2.DESC,fld3.ASC" default=""/>
		<cfargument name="fields" type="string" hint="type:string, description:comma-separated list of fields to limit output to, empty string or omitted for default field set"  required=false default=""/>
		<cfargument name="search" type="string" hint="type:string, free search (black box)" required=false default=""/>

		<cfset local={}/>
		<!--- Фильтрация не униицирована (разница с instance_ls) --->
		
		<!---parse and validate request parameters--->
		<cftry>
			<cfset this.helper.validateField(arguments, "pageSize", "integer")/>
			<cfset this.helper.validateField(arguments, "page", "integer")/>
			
			<!---мы мирно игнорируем поля, отсутствующие в спецификации, что позволяет не делать исключения для orderBy и т.п.--->
			
			<cfset var filter=this.helper.parseFilterParams(this.fieldsSpec)/>
			<cfset var order=this.helper.parseOrderBy(this.fieldsSpec, arguments.orderBy)/>	
	
			<cfset local.fieldsToOutput=""/>

			<cfloop collection=#this.fieldsSpec# item="fld">
				<!--- <cfoutput>#this.helper.snake2camel(trim(fld))#</cfoutput> --->
				<!--- *** в данном случае мы некорректно ограничиваем состав выводимых полей
				полями, перечисленными в fieldSpec --->
				<cfif listFindNoCase(arguments.fields, this.helper.snake2camel(trim(fld)))
				OR len(arguments.fields) EQ 0>
					<cfset local.fieldsToOutput=listAppend(local.fieldsToOutput,trim(fld))/>
				</cfif>
			</cfloop>
			
			<cfset local.fieldsToQueryFor = local.fieldsToOutput/>
			<cfset local.fieldsShortList=""/>
			
			<!--- теперь добавим в перечень полей, которые нужно оставить, поля для фильтрации и сортировки --->
			<cfloop array=#filter# index="fltr">
				<!--- <cfset var fltr = structFind(filter,item)/> --->
				<cfif listLen(fltr.field,".") EQ 2>
					<cfset var fld = listGetAt(fltr.field,2,".")/>
				<cfelseif listLen(fltr.field,".") EQ 1>
					<cfset var fld = fltr.field/>
				<cfelse>
					<cfreturn representationOf(this.helper.formatBadRequestError("Ошибка разбора фильтра")).withStatus(500)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFindNoCase(local.fieldsToQueryFor,fld)>
					<cfset local.fieldsToQueryFor=listAppend(local.fieldsToQueryFor,fld)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFindNoCase(local.fieldsShortList,fld)>
					<cfset local.fieldsShortList=listAppend(local.fieldsShortList,fld)/>
				</cfif>
			</cfloop>

			
			<!--- *** Не нужно ли сохранять префикс? (на случай одноименных полей) --->
			<cfloop collection=#order# item="item">
				<cfset var ord = structFind(order,item)/>
				<cfif listLen(ord.fld,".") EQ 2>
					<cfset var fld = listGetAt(ord.fld,2,".")/>
				<cfelseif listLen(ord.fld,".") EQ 1>
					<cfset var fld = ord.fld/>
				<cfelse>
					<cfreturn representationOf(this.helper.formatBadRequestError("Ошибка разбора порядка сортировки")).withStatus(500)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFindNoCase(local.fieldsToQueryFor,fld)>
					<!--- в данном случае мы не пытаемся поддерживать позиционную сортировку --->
					<cfset local.fieldsToQueryFor=listAppend(local.fieldsToQueryFor,fld)/>
				</cfif>
				<cfif structKeyExists(this.fieldsSpec,fld) AND NOT listFindNoCase(local.fieldsShortList,fld)>
					<cfset local.fieldsShortList=listAppend(local.fieldsShortList,fld)/>
				</cfif>	
			</cfloop>
			
			<cfcatch type="invalidParamValue"><!--- <cfreturn representationOf("Проверка связи 3")> --->
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
			
			
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>			
		
		<cfset var out=structNew("linked")/>
		<cfset var maxrows=arguments.pageSize*arguments.page/>
		<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
		
		<m:field_set 
				titleMapOut="local.titleMap" 
				lengthOut="local.fieldCount" 
				nameListOut="local.definedRecordFields"
				listOut="local.definedQueryFields"
				fieldsToInclude=#local.fieldsToQueryFor#
		>				
			<m:field title="ID" cfSqlType="CF_SQL_INTEGER">s.svc_id</m:field>
			<m:field title="Сервис">s.svc</m:field>
			<m:field title="Сокращение">s.svc_short</m:field>
			<m:field>s.orchestrator_name</m:field>
			<m:field>s.synonyms</m:field>
			<m:field title="Код">s.code</m:field>
			<m:field title="URL создания">(select url from svc_operation so where so.operation='create' AND so.svc_id=s.svc_id limit 1) as create_url</m:field>
			<m:field formatter=#function(x){return x NEQ 0}#>s.is_production_ready</m:field>
			<m:field>s.resource_realm_type_id</m:field>
			<m:field>s.sort</m:field>
			<m:field>s.version</m:field>
			<m:field>s.icon_src</m:field>
			<m:field>s.descr</m:field>
			<m:field>s.man</m:field>
			<m:field>(select count(distinct r.resource_realm)
				from resource_realm r
				join resource_realm_access a on (
				r.resource_realm_id=a.resource_realm_id 
				AND (a.contract_id IN (
					select contract_id 
					from contract d
					where d.contragent_id=k.contragent_id AND NOT d.is_closed							
				) OR a.contract_id=0) /*0 means access to any contract*/
				AND a.is_enabled)
				where r.resource_realm_type_id=s.resource_realm_type_id) as resource_realm_cnt </m:field> <!--- количество доступных ресурсных платформ --->
			<m:field>to_char(s.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
			<m:field>to_char(s.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
			<m:field>(coalesce(s.svc,'') ||' '|| coalesce(s.svc_short,'') ||' '|| coalesce(s.synonyms,'')) as svc_extended_name</m:field>
		</m:field_set>
		
<!--- 		<cfdump var=#local.titleMap#/>
		<cfdump var=#arguments.orderBy#/>
		<cfdump var=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)#/>
		<cfdump var=#local.fieldsToOutput#/>
		<m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=1/>
		<cfabort/> --->
		
		<!--- *** проблема: сортировка строится некорректно. пока добавим поля сортировки в резалтсет --->
 		<cfquery name="local.qRead" result="local.result">
			select <cfif listLen(local.definedRecordFields)>#local.definedRecordFields#<cfelse>*</cfif> from 
			(
				select 
				<cfif listLen(local.definedQueryFields)>
					#preserveSingleQuotes(local.definedQueryFields)#
				<cfelse>
					'' as placeholder
				</cfif>
				from svc s 
				left outer join contragent k on (
					k.external_uid=<cfqueryparam cfsqltype="CF_SQL_OTHER" value=#arguments.companyUid# null=#!isValid("guid", arguments.companyUid)#/> 
				) /*эти хитрости для того, чтобы втащить cfqueryparam внутрь cfqery*/
				where 1=1 
				<cfif len(arguments.search)>
					AND (
					lower((coalesce(s.svc,'') ||' '|| coalesce(s.svc_short,'') ||' '|| coalesce(s.synonyms,''))) like lower(<cfqueryparam cfsqltype="cf_sql_varchar" value='%#arguments.search#%'/>)
					)
				</cfif>	
			) sv			
			where 1=1 <m:filter_build filter=#filter#/>			
			order by <m:order_build sortCollection=#this.helper.parseNumericOrder(local.titleMap, arguments.orderBy)# fieldCount=1/>
		</cfquery>  
		
		
		<cftry>
			<cfquery name="local.qTotal">
				select count(*) as cnt
				from svc 
				where 1=1 
			</cfquery> 
			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>
		
<!--- 		<cfreturn representationOf("Проверка связи 7")/> --->

		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		<cfset "out.total"=#local.qTotal.cnt#/>
		<cfset "out.resultSetSize"=#local.qRead.recordCount#/>
		<cfset "out.pageSize"=#arguments.pageSize*1#/>
		<cfset "out.page"=#arguments.page*1#/>
		<cfset "out.orderBy"=#arguments.orderBy#/>
		
		<cfset var resultCollection=[]/>
		<cfloop query=#local.qRead# startRow=#startrow# endRow=#(startrow+maxrows-1)#>
			<cfset var rec={}/>				
			<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
			<cfset arrayAppend(resultCollection, rec)/>
		</cfloop>		

		<cfset "out.size"=#arrayLen(resultCollection)#/>
		<cfset "out.results"=#resultCollection#/>
		<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
		<!---<cfset "out.sql"=#local.result.sql#/>--->	
		<cfreturn representationOf(out)/>		
		
	</cffunction> 

</cfcomponent>

v1/resources/svc_operation.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/serviceOperation/{svcOperationId}">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>


	<cffunction name="get" hint="Операция сервиса"><!--- *** TODO проверка принадлежности тенанту --->
		<cfargument name="svcOperationId" type="string" required=true hint="type:integer"/>
		<cftry>
			<cfset this.helper.validateField(arguments, "svcOperationId", "integer")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry>
		
		<cfquery name="local.qSvcOperation" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">o.svc_operation_id</m:field>
				<m:field title="operation">o.operation</m:field>
				<m:field title="URL">o.url</m:field>
				<m:field title="Описание">o.descr</m:field>
				<m:field title="Руководство">o.man</m:field>
			</m:field_set>	
			from svc_operation o
			where o.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.svcOperationId#" null=#!isValid('integer',arguments.svcOperationId)#/>
		</cfquery>	
		
		<cfquery name="local.qCfsParam" result="local.result">
			select 
			<m:field_set titleMapOut="local.cfsParamMap" lengthOut="local.fieldCount">
				<m:field title="ID" cfSqlType="CF_SQL_INTEGER">p.svc_operation_cfs_param_id</m:field>
				<!--- <m:field cfSqlType="CF_SQL_INTEGER">p.svc_operation_id</m:field> --->
				<m:field>p.svc_operation_cfs_param</m:field>
				<m:field>p.label</m:field>
				<m:field>p.data_type</m:field>
				<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#>p.value_list</m:field>
				<m:field>p.ref_svc_id</m:field>
				<m:field formatter=#request.castToBool#>p.is_required</m:field>
				<m:field formatter=#request.castToBool#>p.is_hidden</m:field>
				<m:field formatter=#request.castToBool#>p.is_disabled</m:field>
				<m:field>p.default_value</m:field><!--- can be overridden bu default_compute --->
				<m:field>p.default_compute</m:field>
				<m:field>p.func</m:field>
				<m:field>p.expression</m:field>
				<m:field>p.state_path</m:field>
				<m:field>p.nested_ref</m:field>
				<m:field>p.regex</m:field>
				<m:field>p.unique_scope</m:field>
				<m:field>p.maxlength</m:field>
				<m:field>p.minlength</m:field>				
				<m:field>p.maxvalue</m:field>
				<m:field>p.minvalue</m:field>
				<m:field>p.descr</m:field>
				<m:field>p.man</m:field>
				<m:field>p.sort</m:field>
				<m:field>p.depends_on_cfs_params</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)
					join svc_operation oc on (om.svc_id=oc.svc_id AND om.operation='modify' AND oc.operation='create')
					join svc_operation_cfs_param c on (oc.svc_operation_id=c.svc_operation_id AND m.svc_operation_cfs_param=c.svc_operation_cfs_param)
					where c.svc_operation_cfs_param_id=p.svc_operation_cfs_param_id
					order by c.svc_operation_cfs_param_id
					limit 1
				) as is_modifiable</m:field>
				<m:field formatter=#request.castToBool#>p.is_sensitive</m:field>
			</m:field_set>	
			from svc_operation_cfs_param p
			where p.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.svcOperationId#" null=#!isValid('integer',arguments.svcOperationId)#/>
			AND p.is_actual
			order by p.sort, p.svc_operation_cfs_param_id
		</cfquery>			
		
 		<cfif local.qSvcOperation.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
		</cfif>
		
		<cfset var out=structNew("linked")/>
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
		
		<cfset "out.svcOperation" = this.helper.appendRecord(
			structNew("linked"), "", local.titleMap, local.qSvcOperation, this.helper.snake2camel
		)/>	
		
		<cfset "out.svcOperation.cfsParams"=[]/>
		<cfloop query=#local.qCfsParam#>
				<cfif (len(local.qCfsParam.func) OR len(local.qCfsParam.expression) OR len(local.qCfsParam.state_path) OR len(local.qCfsParam.nested_ref)) 
				AND len(local.qCfsParam.value_list) EQ 0>				
				<cfset local.qCfsParam.value_list = []/>
			</cfif>	
			<cfset arrayAppend(out.svcOperation.cfsParams, this.helper.appendRecord(structNew("linked"), "", local.cfsParamMap, local.qCfsParam, this.helper.snake2camel))/>
		</cfloop>

		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>	
	
	<!--- *** выглядит ужасно, но красивый вариант не придумывается ряд месяцев --->
	<cffunction name="computeDefault">
		<cfargument name="defaultCompute"/>
		<cfargument name="original"/>
		
		<cfswitch expression=#arguments.defaultCompute#>
			<cfcase value="display_name">
			</cfcase>
			<cfdefaultcase>
				<cfreturn #arguments.original#/>
			</cfdefaultcase>
		</cfswitch>
		
	</cffunction>

</cfcomponent>

v1/resources/svc_operation_cfs_param_compute.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/svcOperationCfsParams/compute/{svcOperationCfsParamId}" hint="вычисляет выражение в поле expression">
	<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="svcOperationCfsParamId" type="string" required=true 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, "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.keys.svcOperationCfsParamId=arguments.svcOperationCfsParamId/>
		<cfset local.params = deserializeJson(arguments.params)/>
		
			<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/>
		
		<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>
	
	

</cfcomponent>

v1/resources/svc_operation_cfs_subparam_compute.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/svcOperationCfsSubparams/compute/{svcOperationCfsSubparamId}" hint="вычисляет выражение в поле expression">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>
	

	<cffunction name="get" hint="вычисление возможных значений cубпараметра CFS параметра операции, в текущем контексте">
		<cfargument name="svcOperationCfsSubparamId" type="string" required=true 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 posted parameters we depend on"/>
				
		<cftry>
			<cfset this.helper.validateField(arguments, "svcOperationCfsSubparamId", "integer")/>
			<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.svcOperationCfsSubparamId" = arguments.svcOperationCfsSubparamId/>
		
		<cfquery name="local.qCfsSubparam" result="local.result">
			select 
			<m:field_set titleMapOut="local.titleMap" lengthOut="fieldCount">				
				<m:field>sop.svc_operation_cfs_param_id</m:field>
				<m:field>sop.data_type</m:field>
				<m:field>sop.svc_operation_cfs_subparam</m:field>
				<m:field>sop.expression</m:field>
			</m:field_set>
			from svc_operation_cfs_subparam sop 				
			where sop.svc_operation_cfs_subparam_id
				= <cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.svcOperationCfsSubparamId# null=#!isValid('integer',arguments.svcOperationCfsSubparamId)#/>
		</cfquery>	
		
		<cfif local.qCfsSubparam.recordCount EQ 0>
			<cfreturn representationOf(this.helper.formatMessage("Service Operation CFS Parameter Not Found")).withStatus(404)/>
		</cfif>	 
		
		<cfset "local.expression"=local.qCfsSubparam.expression/>
		<cfset "local.keys.svcOperationCfsParamId" = local.qCfsSubparam.svc_operation_cfs_param_id/>
		
		<cfif len(local.expression)>
			<cfset local.calculatedValueList=createObject("component","lib.expression_parser")
				.eval(
					local.expression,
					{
						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>

</cfcomponent>

v1/resources/user.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/user">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
	</cfsilent>

<!--- *** Внимание! Особая обработка в Application.cfc по имени user.cfc --->

	
	 <cffunction name="get" hint="Контрольная информация по текущему пользователю для проверки контекста. Предполагается использовать только для интеграционной отладки. Исключения перехватываются, всегда возвращается код 200, структура ответа при этом не предназначена для разбора">
		<cftry>
		
			<cfset var local={}/><!--- *** а точно нужно var? --->
			<cfset var out=structNew("linked")/>
			
			<cfset "out.usrId"=#ARGUMENTS.usrId#/> 
			<!--- <cfset "out.iamServiceUrl"=request.locateIamService()/>  --->
			
			<cfset "out.serviceLocationDurationMs"=getTickCount() - request.startTickCount/>
			
<!--- 			<cftry>
				<cfset var sAuth=listGetAt(request.ORCHESTRATOR_AUTH,2," ")/>
				<cfset "out.svcAccount"=listGetAt(toString(ToBinary(sAuth)),1,":")/>
				<cfcatch type="any"></cfcatch>
			</cftry>
			
			<cfset "out.environmentVariables"=structKeyArray(createObject("java", "java.lang.System").getEnv()).sort("textnocase")/> --->
			<!--- <cfdump var=#arguments#/>
			<cfabort/> --->

			<cfquery name="local.qSpec">
				select 
				<m:field_set titleMapOut="local.specTitleMap" lengthOut="local.fieldCount">
					<m:field cfSqlType="CF_SQL_INTEGER">s.specification_id</m:field>
					<!--- <m:field>s.specification</m:field>
					<m:field>(select count(*) from specification_item i where i.specification_id=s.specification_id) as specification_item_count</m:field> --->
					<m:field>c.contract_id</m:field>
					<m:field>c.contragent_id</m:field>
					<!--- <m:field>c.contract</m:field>
					<m:field>to_char(c.dt_contract, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_contract</m:field> --->
				</m:field_set>	 
				from specification s
				join contract c on (s.contract_id=c.contract_id)
				join usr u on (c.contragent_id=u.contragent_id)
				where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#ARGUMENTS.usrId#/><!--- *** undeclared --->
				order by specification_id desc 
				limit 1;
			</cfquery>
			
			
			<cfquery name="local.qRead" result="local.result">
				select 
				<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
					<m:field cfSqlType="CF_SQL_INTEGER">u.usr_id</m:field>
					<!--- <m:field>u.login</m:field> --->
					<m:field>k.contragent_id</m:field>
					<!--- <m:field><cfqueryparam cfsqltype="cf_sql_other" value=#arguments.companyUid#/> as arguments_company_uid</m:field> --->
					<!--- <m:field>k.external_uid::text as external_uid</m:field> --->
					<!--- <m:field>k.contragent</m:field> --->
				</m:field_set>	
				from usr u 
				left outer join contragent k on u.contragent_id=k.contragent_id
				where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#ARGUMENTS.usrId#/>
			</cfquery>  
			

			<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount- out.serviceLocationDurationMs/>
			
			<cfset var resultCollection=[]/>
			<cfset "out.usr" = (local.qRead.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.titleMap, local.qRead, this.helper.snake2camel) : {}/>
			<cfset "out.arguments" = #arguments#/>
			<cfset "out.defaultSpecification" = (local.qSpec.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.specTitleMap, local.qSpec, this.helper.snake2camel) : {}/>

			<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>	
			<!---<cfset "out.sql"=#local.result.sql#/>--->	
			<cfreturn representationOf(out)/>	

			<cfcatch type="any">
				<cfreturn representationOf(cfcatch)/>				
			</cfcatch>
		</cftry>		
		
	</cffunction> 

<!--- 	<cffunction name="get" hint="проверка">
	<cfreturn representationOf("Проверка связи")/>
	</cffunction> --->

</cfcomponent>

v1/resources/vault_record.cfc

<cfcomponent extends="taffy.core.resource" taffy:uri="/instances/{instanceUid}/vault/{secretName}" hint="getting secrets from the vault">

	<cfsilent>
		<cfimport prefix="m" taglib="../lib"/>
		<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
	</cfsilent>

 	<cffunction name="get" hint="Получить секрет">
		<cfargument name="instanceUid" type="string" required=true hint="type:UUID"/>
		<cfargument name="secretName" type="string" required=true hint="type:string"/>
		<!--- <cfargument name="usrId" type="string" hint="type:integer; no need to provide this argument in request, it is injected by IAM"/> --->
		<!--- arguments.usrId неявно вбрасывается из Application.cfm --->
	
 		<cftry>
			<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>			
			<cfcatch type="invalidParamValue">
				<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
			</cfcatch>
		</cftry> 
		
		<cfset var local={}/>
		<cfset local.vaultData={}/>
		
		<!--- <cfset logVaultAccess(
			"#request.iam_service_url#/events",
			request.auth_header,
			request.stand,
			arguments.instanceUid,
			arguments.secretName,
			"",			
			arguments.login,
			arguments.clientID,
			arguments.usrId,
			arguments.contragentId,
			arguments.isImpersonated)/> --->
		
		<!--- *** Добавить проверку, что секрет перечислен в fields - стало быть, доступен пользователю --->
		
		<!--- тут необходимо проконтролировать, что мы смотрим не чужие данные --->
		<!--- в других местах мы молча возврашаем пустой резалтсет на запрос чужих данных --->
		<!--- варианты: 403, 404, пустой набор данных --->
		<cfquery name="local.qCurrentState" result="local.result">
			select 
			<m:field_set titleMapOut="local.currentStateTitleMap" lengthOut="local.fieldCount">
				<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
				<m:field title="v.">st.version</m:field>
				<m:field title="creator_id">st.creator_id</m:field>
				<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
				<m:field  formatter=#request.castToBool#>st.is_test</m:field>
				<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
				<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
				<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field>
			</m:field_set>	
			from instance_state st
			join instance e on (st.instance_uid=e.instance_uid)		
			join specification_item i on (e.specification_item_id=i.specification_item_id)		
			where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#"/>
			AND i.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/><!--- access protection --->
			order by st.version desc
			limit 1
		</cfquery>
		
		<cfif local.qCurrentState.recordCount EQ 0>
			<cfreturn representationOf("Cannot find instance current state or no permission to view").withStatus(404)/>
		</cfif>
		
		<cfif len(local.qCurrentState.instance_data) EQ 0>
			<cfreturn representationOf("Instance current state is empty").withStatus(404)/>
		</cfif>
		
		<!--- <cfdump var=#local.qCurrentState#/><cfabort/> --->
		<cfset var vaultUrl=""/>
		<cftry>					
			<cfset var instanceData=#deserializeJson(local.qCurrentState.instance_data)#/>
			<cfif structKeyExists(instanceData,"vaultUrl")>
				<cfset vaultUrl=instanceData.vaultUrl/>	<!--- v001 --->
			<cfelseif structKeyExists(instanceData,"vault")>
				<cfif !structKeyExists(instanceData.vault,"Url")>
					<cfreturn representationOf("instanceData.vault does not contain url").withStatus(422)/>
				</cfif>
				<cfset vaultUrl=instanceData.vault.Url/><!--- v002 --->	
			<cfelse>
				<cfreturn representationOf("Instance data do not contain vault url").withStatus(422)/>
			</cfif>
			
			<cfhttp method="post" 
				url="#request.vault_login_url#" 
				timeout="3"
				charset="utf-8"	
				result="local.res"
			>
				<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
				<cfhttpparam type="BODY" value='{"role_id":"#request.vault_role_id#","secret_id":"#request.vault_secret_id#"}'/>
			</cfhttp>
			
			<cfif local.res.status_code NEQ 200>
				<cfreturn representationOf(local.res).withStatus(422)/>
			</cfif>
			
			<cfset resData=#deserializeJson(local.res.filecontent)#/>
			<cfset client_token=#resData.auth.client_token#/>	
			
			<cftry>	
				<cfhttp method="get" 
					url="#vaultUrl#" 
					timeout="3"
					charset="utf-8"	
					result="local.cred">
					<cfhttpparam type="HEADER" name="X-Vault-Token" value="#client_token#">
					<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
				</cfhttp>				
				<cfif local.cred.status_code NEQ 200>
					<cfreturn representationOf(local.cred).withStatus(422)/>
				</cfif>
				<!--- <cfdump var=#cred#/><cfabort/> --->
				<!--- *** костыль для перехода от формата v001 к v002 --->				
				<cfif structKeyExists(instanceData,"vault") 
						AND structKeyExists(instanceData.vault,"fields") 
						AND arrayFind(instanceData.vault.fields, arguments.secretName)>					
					<cfset "local.tempVaultData" = #deserializeJson(local.cred.filecontent).data.data#/>
					<cfif !structKeyExists(local.tempVaultData, arguments.secretName)>
						<cfreturn representationOf("Secret is declared but does not exist").withStatus(404)/>
					</cfif>
					<cfset "local.vaultData" = local.tempVaultData[arguments.secretName]/>
				<cfelse>
					<cfreturn representationOf("Secret not found or no permission to view").withStatus(404)/>
				</cfif>

				
				<cfcatch type="ANY">
					<cfreturn representationOf(cfcatch).withStatus(500)/>
				</cfcatch>
			</cftry>
			
			<cfcatch type="ANY">
				<cfreturn representationOf(cfcatch).withStatus(500)/>
			</cfcatch>
		</cftry>
		
		<!--- логируем успешную попытку доступа. (если логировать сначала - неизвестен результат. *** Можно в catch логировать неуспешный --->
		<cfset logVaultAccess(
			"#request.iam_service_url#/events",
			request.auth_header,
			request.stand,
			arguments.instanceUid,
			arguments.secretName,
			vaultUrl,			
			arguments.login,
			arguments.clientID,
			arguments.usrId,
			arguments.contragentId,
			arguments.isImpersonated)/>
		
		<cfset var out = structNew("linked")/>
		<cfset "out.name" = #arguments.secretName#/>
		<cfset "out.value" = #isSimpleValue(local.vaultData)? local.vaultData : serializeJson(local.vaultData)#/><!--- cast any to string --->
		<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>	
		<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
		<cfreturn representationOf(out) />
	</cffunction>

	<cffunction name="logVaultAccess" returntype="void">
		<cfargument name="loggerUrl" type="string" required=true hint=""/>
		<cfargument name="authHeader" type="string" required=true hint="type:string"/>
		<cfargument name="stand" type="string" required=true hint="type:string"/>
		<cfargument name="instanceUid" type="string" required=true hint="type:UUID"/>
		<cfargument name="secretName" type="string" required=true hint="type:string"/>
		<cfargument name="vaultUrl" type="string" required=true hint="type:string"/>		
		<cfargument name="login" type="string" required=true hint="type:string"/>
		<cfargument name="wz" type="string" required=true hint="type:string WZ"/>
		<cfargument name="usrId" type="string" required=true hint="type:string WZ"/>
		<cfargument name="contragentId" type="string" required=true hint="type:string WZ"/>
		<cfargument name="isImpersonated" type="string" required=true hint="type:boolean"/>
		
		<cftry>
		<cfset var local={}/>
		<!--- расшифруем некоторые данные пользователя для читабельности --->
		<!--- нужно еще учесть имперсонирование --->
<!--- 		<cfquery name="local.qUsr" result="local.result">
			select 
			u.usr_id
			u.login
			u.email
			k.contragent_id
			k.external_uid::text as external_uid
			k.contragent
			from usr u 
			left outer join contragent k on (u.contragent_id=k.contragent_id)
			where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#ARGUMENTS.usrId#/>
		</cfquery> --->
		
		
			<cfset var msg = {
				"event_type": "credentials_access",
				"ip_address": "#CGI.REMOTE_ADDR#",
				"user_agent": "#CGI.HTTP_USER_AGENT#",
				"metadata": {
					"credentials_type": "instance_secret",
					"vault_url": "#arguments.vaultUrl#",
					"secretName": "#arguments.secretName#",
					"action": "view",
					"instance_uid": "#arguments.instanceUid#",
					"environment": "#arguments.stand#",
					"usr_id": "#arguments.usrId#",
					"contragent_id": "#arguments.contragentId#",
					"login": "#arguments.login#",
					"wz": "#arguments.wz#",
					"isImpersonated": "#arguments.isImpersonated#"
				}
			}/>
			
			<!--- 
			https://cfguide.io/logging-observability 
			https://www.bennadel.com/blog/4150-writing-to-the-standard-out-console-using-writedump-in-adobe-coldfusion-2021.htm
			--->
			<!--- write to stdout --->
			<cftry>
				<cfset SystemOutput(serializeJson(msg))/><!--- *** lucee specific incompatible syntax --->
				<cfcatch type="ANY"></cfcatch>
			</cftry>
			
			<cfhttp method="post" 
				url="#arguments.loggerUrl#" 
				timeout="3"
				charset="utf-8"	
				result="local.res"
			>				
				<cfhttpparam type="HEADER" name="Authorization" value="#arguments.authHeader#">
				<cfhttpparam type="HEADER" name="Content-Type" value="application/json"/>
				<cfhttpparam type="BODY" value="#serializeJson(msg)#"/>					
			</cfhttp>
		
			<!---  
			arguments:<cfdump var=#arguments#/>
			msg:<cfdump var=#msg#/>
			local.res:<cfdump var=#local.res#/>
			<cfabort/>
			--->
			<cfcatch type="any">
				<cfrethrow/><!--- на этапе отладки лучше тут падать --->
			</cfcatch>
		</cftry>
		
	</cffunction>	

</cfcomponent>