This commit is contained in:
msyu
2024-10-23 11:17:42 +03:00
commit ab5c944862
380 changed files with 55823 additions and 0 deletions
+620
View File
@@ -0,0 +1,620 @@
<!---
Copyright 2009 Nathan Mische
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--->
<!--- 2024-10-05 modified by msyu: date in ISO 8601 with server timezone --->
<cfcomponent displayname="JSONUtil" output="false">
<cfset this.deserializeJSON = deserializeFromJSON />
<cfset this.serializeJSON = serializeToJSON />
<cfset this.deserialize = deserializeFromJSON />
<cfset this.serialize = serializeToJSON />
<cfset this.isLuceeRailo = structKeyExists(server, "lucee") or structKeyExists(server, "railo") />
<cffunction name="init" output="false">
<cfreturn this />
</cffunction>
<cffunction
name="deserializeFromJSON"
access="public"
returntype="any"
output="false"
hint="Converts a JSON (JavaScript Object Notation) string data representation into CFML data, such as a CFML structure or array.">
<cfargument
name="JSONVar"
type="string"
required="true"
hint="A string that contains a valid JSON construct, or variable that represents one." />
<cfargument
name="strictMapping"
type="boolean"
required="false"
default="true"
hint="A Boolean value that specifies whether to convert the JSON strictly, as follows:
<ul>
<li><code>true:</code> (Default) Convert the JSON string to ColdFusion data types that correspond directly to the JSON data types.</li>
<li><code>false:</code> Determine if the JSON string contains representations of ColdFusion queries, and if so, convert them to queries.</li>
</ul>" />
<!--- DECLARE VARIABLES --->
<cfset var ar = ArrayNew(1) />
<cfset var st = StructNew() />
<cfset var dataType = "" />
<cfset var inQuotes = false />
<cfset var startPos = 1 />
<cfset var nestingLevel = 0 />
<cfset var dataSize = 0 />
<cfset var i = 1 />
<cfset var skipIncrement = false />
<cfset var j = 0 />
<cfset var char = "" />
<cfset var dataStr = "" />
<cfset var structVal = "" />
<cfset var structKey = "" />
<cfset var colonPos = "" />
<cfset var qRows = 0 />
<cfset var qCols = "" />
<cfset var qCol = "" />
<cfset var qData = "" />
<cfset var curCharIndex = "" />
<cfset var curChar = "" />
<cfset var result = "" />
<cfset var unescapeVals = "\\,\"",\/,\b,\t,\n,\f,\r" />
<cfset var unescapeToVals = "\,"",/,#Chr(8)#,#Chr(9)#,#Chr(10)#,#Chr(12)#,#Chr(13)#" />
<cfset var unescapeVals2 = '\,",/,b,t,n,f,r' />
<cfset var unescapetoVals2 = '\,",/,#Chr(8)#,#Chr(9)#,#Chr(10)#,#Chr(12)#,#Chr(13)#' />
<cfset var dJSONString = "" />
<cfset var pos = 0 />
<cfset var _data = Trim(arguments.JSONVar) />
<!--- NUMBER --->
<cfif IsNumeric(_data)>
<cfreturn Val(_data) />
<!--- NULL --->
<cfelseif _data EQ "null">
<cfreturn "null" />
<!--- BOOLEAN --->
<cfelseif ListFindNoCase("true,false", _data)>
<cfreturn _data />
<!--- EMPTY STRING --->
<cfelseif _data EQ "''" OR _data EQ '""'>
<cfreturn "" />
<!--- STRING --->
<cfelseif ReFind('^"[^\\"]*(?:\\.[^\\"]*)*"$', _data) EQ 1 OR ReFind("^'[^\\']*(?:\\.[^\\']*)*'$", _data) EQ 1>
<cfset _data = mid(_data, 2, Len(_data)-2) />
<!--- If there are any \b, \t, \n, \f, and \r, do extra processing
(required because ReplaceList() won't work with those) --->
<cfif Find("\b", _data) OR Find("\t", _data) OR Find("\n", _data) OR Find("\f", _data) OR Find("\r", _data)>
<cfset curCharIndex = 0 />
<cfset curChar = ""/>
<cfset dJSONString = ArrayNew(1) />
<cfloop condition="true">
<cfset curCharIndex = curCharIndex + 1 />
<cfif curCharIndex GT len(_data)>
<cfbreak />
<cfelse>
<cfset curChar = mid(_data, curCharIndex, 1) />
<cfif curChar EQ "\">
<cfset curCharIndex = curCharIndex + 1 />
<cfset curChar = mid(_data, curCharIndex,1) />
<cfset pos = listFind(unescapeVals2, curChar) />
<cfif pos>
<cfset ArrayAppend(dJSONString,ListGetAt(unescapetoVals2, pos)) />
<cfelse>
<cfset ArrayAppend(dJSONString,"\" & curChar) />
</cfif>
<cfelse>
<cfset ArrayAppend(dJSONString,curChar) />
</cfif>
</cfif>
</cfloop>
<cfreturn ArrayToList(dJSONString,"") />
<cfelse>
<cfreturn ReplaceList(_data, unescapeVals, unescapeToVals) />
</cfif>
<!--- ARRAY, STRUCT, OR QUERY --->
<cfelseif ( Left(_data, 1) EQ "[" AND Right(_data, 1) EQ "]" )
OR ( Left(_data, 1) EQ "{" AND Right(_data, 1) EQ "}" )>
<!--- Store the data type we're dealing with --->
<cfif Left(_data, 1) EQ "[" AND Right(_data, 1) EQ "]">
<cfset dataType = "array" />
<cfelseif ReFindNoCase('^\{"ROWCOUNT":[0-9]+,"COLUMNS":\[("[^"]+",?)+\],"DATA":\{("[^"]+":\[.*\],?)+\}\}$', _data, 0) EQ 1 AND NOT arguments.strictMapping>
<cfset dataType = "queryByColumns" />
<cfelseif ReFindNoCase('^\{"COLUMNS":\[("[^"]+",?)+\],"DATA":\[(\[.*\],?)+\]\}$', _data, 0) EQ 1 AND NOT arguments.strictMapping>
<cfset dataType = "query" />
<cfelse>
<cfset dataType = "struct" />
</cfif>
<!--- Remove the brackets --->
<cfset _data = Trim( Mid(_data, 2, Len(_data)-2) ) />
<!--- Deal with empty array/struct --->
<cfif Len(_data) EQ 0>
<cfif dataType EQ "array">
<cfreturn ar />
<cfelse>
<cfreturn st />
</cfif>
</cfif>
<!--- Loop through the string characters --->
<cfset dataSize = Len(_data) + 1 />
<cfloop condition="#i# LTE #dataSize#">
<cfset skipIncrement = false />
<!--- Save current character --->
<cfset char = Mid(_data, i, 1) />
<!--- If char is a quote, switch the quote status --->
<cfif char EQ '"'>
<cfset inQuotes = NOT inQuotes />
<!--- If char is escape character, skip the next character --->
<cfelseif char EQ "\" AND inQuotes>
<cfset i = i + 2 />
<cfset skipIncrement = true />
<!--- If char is a comma and is not in quotes, or if end of string, deal with data --->
<cfelseif (char EQ "," AND NOT inQuotes AND nestingLevel EQ 0) OR i EQ Len(_data)+1>
<cfset dataStr = Mid(_data, startPos, i-startPos) />
<!--- If data type is array, append data to the array --->
<cfif dataType EQ "array">
<cfset arrayappend( ar, deserializeFromJSON(dataStr, arguments.strictMapping) ) />
<!--- If data type is struct or query or queryByColumns... --->
<cfelseif dataType EQ "struct" OR dataType EQ "query" OR dataType EQ "queryByColumns">
<cfset dataStr = Mid(_data, startPos, i-startPos) />
<cfset colonPos = Find('":', dataStr) />
<cfif colonPos>
<cfset colonPos = colonPos + 1 />
<cfelse>
<cfset colonPos = Find(":", dataStr) />
</cfif>
<cfset structKey = Trim( Mid(dataStr, 1, colonPos-1) ) />
<!--- If needed, remove quotes from keys --->
<cfif Left(structKey, 1) EQ "'" OR Left(structKey, 1) EQ '"'>
<cfset structKey = Mid( structKey, 2, Len(structKey)-2 ) />
</cfif>
<cfset structVal = Mid( dataStr, colonPos+1, Len(dataStr)-colonPos ) />
<!--- If struct, add to the structure --->
<cfif dataType EQ "struct">
<cfset StructInsert( st, structKey, deserializeFromJSON(structVal, arguments.strictMapping) ) />
<!--- If query, build the query --->
<cfelseif dataType EQ "queryByColumns">
<cfif structKey EQ "rowcount">
<cfset qRows = deserializeFromJSON(structVal, arguments.strictMapping) />
<cfelseif structKey EQ "columns">
<cfset qCols = deserializeFromJSON(structVal, arguments.strictMapping) />
<cfset st = QueryNew(ArrayToList(qCols)) />
<cfif qRows>
<cfset QueryAddRow(st, qRows) />
</cfif>
<cfelseif structKey EQ "data">
<cfset qData = deserializeFromJSON(structVal, arguments.strictMapping) />
<cfset ar = StructKeyArray(qData) />
<cfloop from="1" to="#ArrayLen(ar)#" index="j">
<cfloop from="1" to="#st.recordcount#" index="qRows">
<cfset qCol = ar[j] />
<cfset QuerySetCell(st, qCol, qData[qCol][qRows], qRows) />
</cfloop>
</cfloop>
</cfif>
<cfelseif dataType EQ "query">
<cfif structKey EQ "columns">
<cfset qCols = deserializeFromJSON(structVal, arguments.strictMapping) />
<cfset st = QueryNew(ArrayToList(qCols)) />
<cfelseif structKey EQ "data">
<cfset qData = deserializeFromJSON(structVal, arguments.strictMapping) />
<cfloop from="1" to="#ArrayLen(qData)#" index="qRows">
<cfset QueryAddRow(st) />
<cfloop from="1" to="#ArrayLen(qCols)#" index="j">
<cfset qCol = qCols[j] />
<cfset QuerySetCell(st, qCol, qData[qRows][j], qRows) />
</cfloop>
</cfloop>
</cfif>
</cfif>
</cfif>
<cfset startPos = i + 1 />
<!--- If starting a new array or struct, add to nesting level --->
<cfelseif "{[" CONTAINS char AND NOT inQuotes>
<cfset nestingLevel = nestingLevel + 1 />
<!--- If ending an array or struct, subtract from nesting level --->
<cfelseif "]}" CONTAINS char AND NOT inQuotes>
<cfset nestingLevel = nestingLevel - 1 />
</cfif>
<cfif NOT skipIncrement>
<cfset i = i + 1 />
</cfif>
</cfloop>
<!--- Return appropriate value based on data type --->
<cfif dataType EQ "array">
<cfreturn ar />
<cfelse>
<cfreturn st />
</cfif>
<!--- INVALID JSON --->
<cfelse>
<cfthrow message="JSON parsing failure." />
</cfif>
</cffunction>
<cffunction
name="serializeToJSON"
access="public"
returntype="string"
output="false"
hint="Converts ColdFusion data into a JSON (JavaScript Object Notation) representation of the data.">
<cfargument
name="var"
type="any"
required="true"
hint="A ColdFusion data value or variable that represents one." />
<cfargument
name="serializeQueryByColumns"
type="boolean"
required="false"
default="false"
hint="A Boolean value that specifies how to serialize ColdFusion queries.
<ul>
<li><code>false</code>: (Default) Creates an object with two entries: an array of column names and an array of row arrays. This format is required by the HTML format cfgrid tag.</li>
<li><code>true</code>: Creates an object that corresponds to WDDX query format.</li>
</ul>">
<cfargument
name="strictMapping"
type="boolean"
required="false"
default="false"
hint="A Boolean value that specifies whether to convert the ColdFusion data strictly, as follows:
<ul>
<li><code>false:</code> (Default) Convert the ColdFusion data to a JSON string using ColdFusion data types.</li>
<li><code>true:</code> Convert the ColdFusion data to a JSON string using underlying Java/SQL data types.</li>
</ul>" />
<!--- VARIABLE DECLARATION --->
<cfset var jsonString = "" />
<cfset var tempVal = "" />
<cfset var arKeys = "" />
<cfset var colPos = 1 />
<cfset var md = "" />
<cfset var rowDel = "" />
<cfset var colDel = "" />
<cfset var className = "" />
<cfset var i = 1 />
<cfset var column = "" />
<cfset var datakey = "" />
<cfset var recordcountkey = "" />
<cfset var columnlist = "" />
<cfset var columnlistkey = "" />
<cfset var columnJavaTypes = "" />
<cfset var dJSONString = "" />
<cfset var escapeToVals = "\\,\"",\/,\b,\t,\n,\f,\r" />
<cfset var escapeVals = "\,"",/,#Chr(8)#,#Chr(9)#,#Chr(10)#,#Chr(12)#,#Chr(13)#" />
<cfset var _data = arguments.var />
<cfif arguments.strictMapping>
<!--- GET THE CLASS NAME --->
<cfset className = getClassName(_data) />
</cfif>
<!--- TRY STRICT MAPPING --->
<cfif Len(className) AND CompareNoCase(className,"java.lang.String") eq 0>
<cfreturn '"' & ReplaceList(_data, escapeVals, escapeToVals) & '"' />
<cfelseif Len(className) AND CompareNoCase(className,"java.lang.Boolean") eq 0>
<cfreturn ReplaceList(ToString(_data), 'YES,NO', 'true,false') />
<cfelseif Len(className) AND CompareNoCase(className,"java.lang.Integer") eq 0>
<cfreturn ToString(_data) />
<cfelseif Len(className) AND CompareNoCase(className,"java.lang.Long") eq 0>
<cfreturn ToString(_data) />
<cfelseif Len(className) AND CompareNoCase(className,"java.lang.Float") eq 0>
<cfreturn ToString(_data) />
<cfelseif Len(className) AND CompareNoCase(className,"java.lang.Double") eq 0>
<cfreturn ToString(_data) />
<!--- BINARY --->
<cfelseif IsBinary(_data)>
<cfthrow message="JSON serialization failure: Unable to serialize binary data to JSON." />
<!--- BOOLEAN --->
<cfelseif IsBoolean(_data) AND NOT IsNumeric(_data)>
<cfreturn ReplaceList(YesNoFormat(_data), 'Yes,No', 'true,false') />
<!--- NUMBER --->
<cfelseif IsNumeric(_data)>
<cfif getClassName(_data) eq "java.lang.String">
<cfreturn Val(_data).toString() />
<cfelse>
<cfreturn _data.toString() />
</cfif>
<!--- DATE --->
<cfelseif IsDate(_data)>
<!--- <cfreturn '"#DateFormat(_data, "mmmm, dd yyyy")# #TimeFormat(_data, "HH:mm:ss")#"' /> --->
<!--- // 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 --->
<cfreturn """" & dateFormat( _data, "yyyy-mm-dd" ) & "T" & timeFormat( _data, "HH:mm:ss.lXX" ) & """" />
<!--- STRING --->
<cfelseif IsSimpleValue(_data)>
<!---<cfreturn '"' & ReplaceList(_data, escapeVals, escapeToVals) & '"' />--->
<cfreturn writeJsonUtf8String(_data) />
<!--- RAILO XML --->
<cfelseif this.isLuceeRailo and IsXML(_data)>
<cfreturn '"' & ReplaceList(ToString(_data), escapeVals, escapeToVals) & '"' />
<!--- CUSTOM FUNCTION --->
<cfelseif IsCustomFunction(_data)>
<cfreturn serializeToJSON( GetMetadata(_data), arguments.serializeQueryByColumns, arguments.strictMapping) />
<!--- OBJECT --->
<cfelseif IsObject(_data)>
<cfreturn "{}" />
<!--- ARRAY --->
<cfelseif IsArray(_data)>
<cfset dJSONString = ArrayNew(1) />
<cfloop from="1" to="#ArrayLen(_data)#" index="i">
<cfset tempVal = serializeToJSON( _data[i], arguments.serializeQueryByColumns, arguments.strictMapping ) />
<cfset ArrayAppend(dJSONString,tempVal) />
</cfloop>
<cfreturn "[" & ArrayToList(dJSONString,",") & "]" />
<!--- STRUCT --->
<cfelseif IsStruct(_data)>
<cfset dJSONString = ArrayNew(1) />
<cfset arKeys = StructKeyArray(_data) />
<cfloop from="1" to="#ArrayLen(arKeys)#" index="i">
<cfif IsDefined("_data.#arKeys[i]#") >
<cfset tempVal = serializeToJSON(_data[ arKeys[i] ], arguments.serializeQueryByColumns, arguments.strictMapping ) />
<cfelse>
<cfset tempVal = "null" />
</cfif>
<cfset ArrayAppend(dJSONString,'"' & arKeys[i] & '":' & tempVal) />
</cfloop>
<cfreturn "{" & ArrayToList(dJSONString,",") & "}" />
<!--- QUERY --->
<cfelseif IsQuery(_data)>
<cfset dJSONString = ArrayNew(1) />
<!--- Add query meta data --->
<cfset recordcountKey = "ROWCOUNT" />
<cfset columnlistKey = "COLUMNS" />
<cfset columnlist = "" />
<cfset dataKey = "DATA" />
<cfset md = GetMetadata(_data) />
<cfset columnJavaTypes = StructNew() />
<cfloop from="1" to="#ArrayLen(md)#" index="column">
<cfset columnlist = ListAppend(columnlist,UCase(md[column].Name),',') />
<cfif StructKeyExists(md[column],"TypeName")>
<cfset columnJavaTypes[md[column].Name] = getJavaType(md[column].TypeName) />
<cfelse>
<cfset columnJavaTypes[md[column].Name] = "" />
</cfif>
</cfloop>
<cfif arguments.serializeQueryByColumns>
<cfset ArrayAppend(dJSONString,'"#recordcountKey#":' & _data.recordcount) />
<cfset ArrayAppend(dJSONString,',"#columnlistKey#":[' & ListQualify(columnlist, '"') & ']') />
<cfset ArrayAppend(dJSONString,',"#dataKey#":{') />
<cfset colDel = "">
<cfloop list="#columnlist#" delimiters="," index="column">
<cfset ArrayAppend(dJSONString,colDel) />
<cfset ArrayAppend(dJSONString,'"#column#":[') />
<cfset rowDel = "">
<cfloop from="1" to="#_data.recordcount#" index="i">
<cfset ArrayAppend(dJSONString,rowDel) />
<cfif (arguments.strictMapping or this.isLuceeRailo) AND Len(columnJavaTypes[column])>
<cfset tempVal = serializeToJSON( JavaCast(columnJavaTypes[column],_data[column][i]), arguments.serializeQueryByColumns, arguments.strictMapping ) />
<cfelse>
<cfset tempVal = serializeToJSON( _data[column][i], arguments.serializeQueryByColumns, arguments.strictMapping ) />
</cfif>
<cfset ArrayAppend(dJSONString,tempVal) />
<cfset rowDel = ",">
</cfloop>
<cfset ArrayAppend(dJSONString,']') />
<cfset colDel = ",">
</cfloop>
<cfset ArrayAppend(dJSONString,'}') />
<cfelse>
<cfset ArrayAppend(dJSONString,'"#columnlistKey#":[' & ListQualify(columnlist, '"') & ']') />
<cfset ArrayAppend(dJSONString,',"#dataKey#":[') />
<cfset rowDel = "">
<cfloop from="1" to="#_data.recordcount#" index="i">
<cfset ArrayAppend(dJSONString,rowDel) />
<cfset ArrayAppend(dJSONString,'[') />
<cfset colDel = "">
<cfloop list="#columnlist#" delimiters="," index="column">
<cfset ArrayAppend(dJSONString,colDel) />
<cfif (arguments.strictMapping or this.isLuceeRailo) AND Len(columnJavaTypes[column])>
<cfset tempVal = serializeToJSON( JavaCast(columnJavaTypes[column],_data[column][i]), arguments.serializeQueryByColumns, arguments.strictMapping ) />
<cfelse>
<cfset tempVal = serializeToJSON( _data[column][i], arguments.serializeQueryByColumns, arguments.strictMapping ) />
</cfif>
<cfset ArrayAppend(dJSONString,tempVal) />
<cfset colDel=","/>
</cfloop>
<cfset ArrayAppend(dJSONString,']') />
<cfset rowDel = "," />
</cfloop>
<cfset ArrayAppend(dJSONString,']') />
</cfif>
<cfreturn "{" & ArrayToList(dJSONString,"") & "}">
<!--- XML --->
<cfelseif IsXML(_data)>
<cfreturn '"' & ReplaceList(ToString(_data), escapeVals, escapeToVals) & '"' />
<!--- UNKNOWN OBJECT TYPE --->
<cfelse>
<cfreturn "{}" />
</cfif>
</cffunction>
<cffunction
name="getJavaType"
access="private"
returntype="string"
output="false"
hint="Maps SQL to Java types. Returns blank string for unhandled SQL types.">
<cfargument
name="sqlType"
type="string"
required="true"
hint="A SQL datatype." />
<cfswitch expression="#arguments.sqlType#">
<cfcase value="bit">
<cfreturn "boolean" />
</cfcase>
<cfcase value="tinyint,smallint,integer">
<cfreturn "int" />
</cfcase>
<cfcase value="bigint">
<cfreturn "long" />
</cfcase>
<cfcase value="real,float">
<cfreturn "float" />
</cfcase>
<cfcase value="double">
<cfreturn "double" />
</cfcase>
<cfcase value="char,varchar,longvarchar">
<cfreturn "string" />
</cfcase>
<cfdefaultcase>
<cfreturn "" />
</cfdefaultcase>
</cfswitch>
</cffunction>
<cffunction
name="getClassName"
access="private"
returntype="string"
output="false"
hint="Returns a variable's underlying java Class name.">
<cfargument
name="data"
type="any"
required="true"
hint="A variable." />
<!--- GET THE CLASS NAME --->
<cftry>
<cfreturn arguments.data.getClass().getName() />
<cfcatch type="any">
<cfreturn "" />
</cfcatch>
</cftry>
</cffunction>
<cffunction
name="writeJsonUtf8String"
access="private"
returntype="string"
output="false"
hint="Returns a variable's underlying java Class name.">
<cfargument
name="data"
type="string"
required="true"
hint="A string to serialze." />
<!--- GET THE CLASS NAME --->
<cfset var json = '"' />
<cfset var end = Len(data) - 1 />
<cfset var i = 0 />
<cfset var c = "" />
<cfset var integer = CreateObject("java","java.lang.Integer") />
<cfset var pad = "" />
<cfset var hex = "" />
<cfloop from="0" to="#end#" index="i">
<cfset c = data.charAt(i) />
<cfif c lt ' '>
<cfif c eq Chr(8)>
<cfset json = json & "\b" />
<cfelseif c eq Chr(9)>
<cfset json = json & "\t" />
<cfelseif c eq Chr(10)>
<cfset json = json & "\n" />
<cfelseif c eq Chr(12)>
<cfset json = json & "\f" />
<cfelseif c eq Chr(13)>
<cfset json = json & "\r" />
<cfelse>
<cfset hex = integer.toHexString(c) />
<cfset json = json & "\u" />
<cfset pad = 4 - Len(hex) />
<cfset json = json & RepeatString("0", pad) />
<cfset json = json & hex />
</cfif>
<cfelseif c eq '\' or c eq '/' or c eq '"'>
<cfset json = json & "\" & c />
<cfelse>
<cfset json = json & c />
</cfif>
</cfloop>
<cfset json = json & '"' />
<cfreturn json />
</cffunction>
</cfcomponent>
+19
View File
@@ -0,0 +1,19 @@
<cfcomponent extends="taffy.core.baseSerializer">
<cffunction
name="getAsJson"
output="false"
taffy:mime="application/json"
taffy:default="true">
<cfreturn variables.jsonSerializer.serialize(variables.data) />
</cffunction>
<cffunction name="setJsonSerializer" output="false">
<cfargument name="jsonSerializer" required="true" />
<cfset variables.jsonSerializer = arguments.jsonSerializer />
<!--- Уже здесь непонятно, как это работает. Похоже на dependency injection?
Почему не вызывается init? --->
<cfset variables.jsonSerializer.init()/>
</cffunction>
</cfcomponent>
+519
View File
@@ -0,0 +1,519 @@
component
extends="taffy.core.baseSerializer"
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
//2024-10-05 modified by msyu: null fix from JSONUtils (yet still no luck with case sens and date format).Вспомнил, что этому сериализатору нужно указывать тип каждого поля
// 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 {
// patch for null, from JSONUtils
if (IsDefined("input.#key#")) {
serializeInput( input[ key ], hint);
} else {
writeOutput("null") ;
}
}
}
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 );
}
}
+16
View File
@@ -0,0 +1,16 @@
<cfcomponent extends="taffy.core.baseSerializer">
<cffunction
name="getAsJson"
output="false"
taffy:mime="application/json"
taffy:default="true">
<cfreturn variables.jsonUtil.serialize(variables.data) />
</cffunction>
<cffunction name="setJSONUtil" output="false">
<cfargument name="JSONUtil" required="true" />
<cfset variables.jsonUtil = arguments.JSONUtil />
</cffunction>
</cfcomponent>
+53
View File
@@ -0,0 +1,53 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/test">
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
</cfsilent>
<cffunction name="get" hint="попробуем авторизоваться у элмы">
<!--- <cfargument name="svcOperationId" required=true hint="int"/> --->
<!--- <cftry>
<cfset this.helper.validateField(arguments, "svcOperationId", "integer")/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry> --->
<!--- <cfdump var=#request#/><cfabort/>--->
<cfset var auth="Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJjb21wYW55IjoiaGVhZCIsInVzZXJJZCI6ImQ3MjUzMGUxLTY2YTQtNDEyZi1hMDQ2LTM4MTUzYzRlNTQwNSIsIm93bmVyIjpmYWxzZSwiaXNQb3J0YWwiOmZhbHNlLCJzZXNzaW9uSWQiOiI0ZTVmNjVkMC00YTdkLTVkZWQtYjNkYi1iY2ZiMzkyNjk5MDIiLCJwcml2aWxlZ2VzIjpbInN5c3RlbSIsImFkbWluaXN0cmF0aW9uIl0sIm5lZWRDaGFuZ2VQYXNzd29yZCI6ZmFsc2UsImNyZWF0ZWRBdCI6MTcyOTI1ODk4NiwiaXNzIjoiYXBpIiwiZXhwIjoxNzMxNjc4MjQ2LCJpYXQiOjE3MjkyNTg5MjZ9.asqG8SBCI7ih_10QDya7H1VlXr3rLDfoxlfvMPwQ7sPO3N1y4_vqMFy7T5kOKkXC495WjHDZZLe169ABUVvpkOD1oyTfXiyaDDlW0hW0BXWXWL-ktm6C_Dejz_GK9toRz2CheADiBdSmjBFQVYWriwrPijWD0nVZW9d5q9yhP7s"/>
<cfif structKeyExists(request._taffyrequest.headers,"Authorization")>
<cfset auth=request._taffyrequest.headers.Authorization/>
</cfif>
<cfhttp method="get"
url="https://personal-cabinet.adl.nubes.ru/api/v1/user"
<!--- multipart=true
multipartType="form-data" --->
timeout="3"
charset="utf-8"
result="userdata"
>
<cfhttpparam type="HEADER" name="Authorization" value="#auth#"/>
<cfhttpparam type="HEADER" name="Accept" value="application/json"/>
<!--- <cfhttpparam type="formfield" name="delay" value="0sec"/>
<cfloop query="qInstanceOperationParam">
<cfhttpparam type="formfield" name="#param#" value="#param_value#"/>
</cfloop> --->
</cfhttp>
<!--- <cfdump var=#userdata#/><cfabort/> --->
<cfset var out=structNew("linked")/>
<cfset "out.test"="ttttttttttttttttttttttttt"/>
<cfset "out.userdata"=deserializeJson(userdata.filecontent)/>
<!--- https://blog.rabu.me/rest-api-taffy-jwt/ --->
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
<cfreturn representationOf(out) />
</cffunction>
</cfcomponent>
+133
View File
@@ -0,0 +1,133 @@
<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>
<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.specification_item_id</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>(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</m:field>
<m:field>(select st.instance_operation_uid::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_operation_uid</m:field>
<m:field>(select io.operation from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as last_operation</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<!--- <m:field>(select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data</m:field> --->
<!--- <m:field>(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</m:field> --->
<!--- <m:field>(select st.is_test from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as is_test</m:field>
<m:field>(select max(version) from instance_state st where st.instance_uid=e.instance_uid) as 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)
where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
</cfquery>
<cfif local.qInstance.recordCount EQ 0>
<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
</cfif>
<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 title="is_test">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="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
order by st.version desc
limit 1
</cfquery>
<!--- операция, создавшая текущее состояние --->
<!--- *** странноватый селект --->
<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 title="v.">status.version as state_version</m:field>
<m:field title="operation">o.operation</m:field>
<!--- <m:field title="instance_uid">o.instance_uid::text as instance_uid</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 title="Рез.">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="dt_created">to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
</m:field_set>
from instance_operation o
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="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
order by status.version
</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> --->
<cfset var out=structNew("linked")/>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<!--- <cfset var rec={}/>
<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qInstance, this.helper.snake2camel)/>
<cfset "out.instance"=rec/> --->
<cfset "out.instance" = this.helper.appendRecord(
structNew("linked"), "", local.titleMap, local.qInstance, this.helper.snake2camel
)/>
<cfset "out.instance.state" = (local.qCurrentState.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.currentStateTitleMap, local.qCurrentState, this.helper.snake2camel) : {}/>
<!--- <cfset "out.instance.operation"=this.helper.appendRecord(structNew("linked"), "", local.operationTitleMap, local.qOperation, 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.runDurationMs"=getTickCount() - request.startTickCount/>
<!--- <cfdump var=#local.qCurrentState#/>
<cfdump var=#out#/>
<cfabort/> --->
<cfreturn representationOf(out) />
</cffunction>
</cfcomponent>
+213
View File
@@ -0,0 +1,213 @@
<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={
dt_created={prefix="e", type="date"},
dt_updated={prefix="e", type="date"},
display_name={prefix="e", type="string"},
service_id={prefix="e", type="integer"},
svc={prefix="v", type="string"},
is_test={prefix="e", type="boolean"}
}
/>
<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=""/>
<!---сюда можно воткнуть спецификацию полей для сортировки и фильтрации, но только ручками, идея компактного кода еще более отдаляется--->
<!---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 var order=this.helper.parseOrderBy(this.fieldsSpec, arguments.orderBy)/> --->
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset var maxrows=arguments.pageSize*arguments.page/>
<cfset var startrow=arguments.pageSize*(arguments.page-1)+1/>
<!--- Такая бодяга. Кастомные сериализаторы не умеют вложенных структур (поразительно, или я не нашел как). Стандартный сериализатор не умеет выводить дату в ISO 8601. Приходится колхозить и форматировать на стороне БД (насилу подобрал формат). Также на стороне БД приходится конвертировать GUID, потому что драйвер pg jdbc его представляет как структуру из 2 чисел.
И то сказать, jsonb как-то странно сериализуется, в 3 поля, вместо одного Value
--->
<cfquery name="local.qRead" 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.specification_item_id</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>(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</m:field>
<m:field>(select instance_operation_uid::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_operation_uid</m:field>
<m:field>(select io.operation from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as last_operation</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field>(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</m:field>
<m:field formatter=#function(x){return (deserializeJson(x))}#>(select instance_data::text from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as instance_data</m:field>
<m:field>(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</m:field>
<m:field>(select st.is_test from instance_state st where st.instance_uid=e.instance_uid order by version desc limit 1) as is_test</m:field>
<m:field>(select max(version) from instance_state st where st.instance_uid=e.instance_uid) as version</m:field>
</m:field_set>
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)
join svc v on (e.service_id=v.svc_id)
where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/>
--AND exists(select * from usr_role x where u.usr_id=x.usr_id AND x.role_id=2)
<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 e
where 1=1
--AND exists(select * from usr_role x where u.usr_id=x.usr_id AND x.role_id=2) -- 2=Согласование нарядов
</cfquery>
<cfset var out=structNew("linked")/>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.total"=#local.qTotal.cnt#/>
<cfset var resultSet=[]/>
<cfloop query=#local.qRead# startRow=#startrow#>
<cfset var rec={}/>
<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
<cfset arrayAppend(resultSet, rec)/>
</cfloop>
<cfset "out.results"=#resultSet#/>
<cfset "out.pageSize"=#arguments.pageSize#/>
<cfset "out.page"=#arguments.page#/>
<cfset "out.orderBy"=#arguments.orderBy#/>
<cfset "out.size"=#arrayLen(resultSet)#/>
<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>
<!---<cfset "out.sql"=#local.result.sql#/>--->
<cfreturn representationOf(out)/>
</cffunction>
<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="usrUid" type="string" required=true hint="type:guid"/>
<cfargument name="companyUid" type="string" required=true hint="type:guid"/> --->
<!--- оказывается, их можно и не декларировать --->
<cfset local={}/>
<cfset local.instanceUid=#createGUID()#/>
<!--- <cfdump var=#arguments#/><cfabort/> --->
<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="#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="#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>
<cffunction name="checkDisplayName">
<cfargument name="displayName" required=true/>
<cfargument name="usrId" type="numeric" required=true/>
<cfargument name="instanceUid" type="guid" required=true/>
<cfset display_name=trim(lcase(display_name))/>
<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="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#/>
</cfquery>
<cfif qUniqueDisplayName.cnt GT 0>
<cfthrow type="InvalidParamValue" message="Instance Display Name not unique" detail="Имя экземпляра должно быть уникальным в рамках договора"/>
</cfif>
</cfif>
</cffunction>
</cfcomponent>
+131
View File
@@ -0,0 +1,131 @@
<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="Операция над экземпляром сервиса"><!--- *** TODO проверка принадлежности тенанту --->
<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.qIinstanceOperation" result="local.result">
select
<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
<m:field title="instance Operation Uid">o.instance_operation_uid::text as instance_operation_uid</m:field>
<m:field title="operation">o.operation</m:field>
<m:field title="instance_uid">o.instance_uid::text as instance_uid</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 title="Рез.">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="Длит.">(coalesce(dt_finish,CURRENT_TIMESTAMP)- dt_start) as duration_interval</m:field> --->
<m:field title="Успешно">o.is_successful</m:field>
<!--- <m:field title="stage">stage</m:field> --->
<!--- <m:field title="UID этапа">instance_operation_stage_uid</m:field> --->
<m:field title="Экземпляр">e.display_name</m:field>
<m:field title="service_id">e.service_id</m:field>
<m:field title="specification_item_id">e.specification_item_id</m:field>
<!--- <m:field title="descr">e.descr</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="Услуга">v.svc</m:field>
<!--- <m:field>(select instance_state_uid::text from instance_state ist where ist.instance_operation_uid=o.instance_operation_uid order by ist.version desc limit 1) as instance_state_uid</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 usr u on (k.contragent_id=u.contragent_id)
join svc v on (e.service_id=v.svc_id)
where u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/>
AND o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
</cfquery>
<cfif local.qIinstanceOperation.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>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>extract('epoch' from coalesce(ios.dt_finish,CURRENT_TIMESTAMP)- ios.dt_start) as duration</m:field>
<!--- <m:field formatter=#function(x){return (deserializeJson(x))}#>ios.stage_data::text as stage_data</m:field> --->
</m:field_set>
from instance_operation_stage ios
where ios.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#qIinstanceOperation.instance_operation_uid#" null=#!isValid('guid',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 title="is_test">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="#qInstanceOperation.instance_operation_uid#" null=#!isValid('guid',qInstanceOperation.instance_operation_uid)#/>
order by st.version desc limit 1
</cfquery>
<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 title="instance_operation_uid">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">iop.param_value</m:field>
<m:field title="dt_created">to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
<m:field title="creator_id">iop.creator_id</m:field>
<m:field title="svc_operation_cfs_param">sop.svc_operation_cfs_param</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)
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#qInstanceOperation.instance_operation_uid#" null=#!isValid('guid',qInstanceOperation.instance_operation_uid)#/>
order by iop.dt_created
</cfquery>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.instance" = this.helper.appendRecord(
structNew("linked"), "", local.titleMap, local.qIinstanceOperation, this.helper.snake2camel
)/>
<cfset "out.instance.cfsParams"=[]/>
<cfloop query=#local.qCfsParam#>
<cfset arrayAppend(out.instance.cfsParams, this.helper.appendRecord(structNew("linked"), "", local.cfsParamTitleMap, local.qCfsParam, this.helper.snake2camel))/>
</cfloop>
<cfset "out.instance.stages"=[]/>
<cfloop query=#local.qStage#>
<cfset arrayAppend(out.instance.stages, this.helper.appendRecord(structNew("linked"), "", local.stageMap, local.qStage, this.helper.snake2camel))/>
</cfloop>
<cfset "out.instance.state"=this.helper.appendRecord(structNew("linked"), "", local.stateTitleMap, local.qState, this.helper.snake2camel)/>
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
<cfreturn representationOf(out) />
</cffunction>
</cfcomponent>
@@ -0,0 +1,97 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams/{instanceOperationCfsParamUid}">
<!--- Вот здесь мы могли бы строить URI /instances/{instanceUid}/instanceOperations/{instanceOperationUid}/CfsParams, но это создает избыточность, у нас простые суррогатные первичные ключи. С другой стороны, контекст инстанса и операции у клиента всегда имеется. Но стоит ли передавать избыточную информацию? Это иногда провоцирует путаницу. Для POST мы можем передать необходимый контекст в теле запроса, а можем в URL.
В данном случае никакого смысла смотреть на все CFS параметры нет, только в рамках операции
Но: мы не хотели бы менять положение сущности в дереве. Либо она в корне, либо она ниже. И получится, когда мы выбираем один параметр, нам ни к чему контекст - мы к нему однозначно адресуемся.
Проголосуем за отсутстие избыточности?
--->
<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"/>
<cftry>
<cfset this.helper.validateField(arguments, "instanceOperationCfsParamUid", "guid")/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfquery name="local.qRead" 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">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">iop.param_value</m:field>
<m:field title="dt_created">to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
<m:field title="creator_id">iop.creator_id</m:field>
<m:field title="svc_operation_cfs_param">sop.svc_operation_cfs_param</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)
where iop.instance_operation_cfs_param_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationCfsParamUid#" null=#!isValid('guid',arguments.instanceOperationCfsParamUid)#/>
</cfquery>
<cfif local.qRead.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.instanceOperationCfsParam" = this.helper.appendRecord(
structNew("linked"), "", local.titleMap, local.qRead, this.helper.snake2camel
)/>
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
<cfreturn representationOf(out) />
</cffunction>
<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.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>
</cfcomponent>
@@ -0,0 +1,331 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperationCfsParams">
<!--- Вот здесь мы могли бы строить URI /instances/{instanceUid}/instanceOperations/{instanceOperationUid}/CfsParams, но это создает избыточность, у нас простые суррогатные первичные ключи. С другой стороны, контекст инстанса и операции у клиента всегда имеется. Но стоит ли передавать избыточную информацию? Это иногда провоцирует путаницу. Для POST мы можем передать необходимый контекст в теле запроса, а можем в URL.
В данном случае никакого смысла смотреть на все CFS параметры нет, только в рамках операции
Но: мы не хотели бы менять положение сущности в дереве. Либо она в корне, либо она ниже. И получится, когда мы выбираем один параметр, нам ни к чему контекст - мы к нему однозначно адресуемся.
Проголосуем за отсутстие избыточности?
--->
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<!---спецификация полей, пригодных для фильтрации --->
<cfset this.fieldsSpec={
dt_created={prefix="e", type="date"}<!--- ,
dt_updated={prefix="e", type="date"},
display_name={prefix="e", type="string"},
service_id={prefix="e", type="integer"},
svc={prefix="v", type="string"},
is_test={prefix="e", type="boolean"} --->
}
/>
<cffunction name="get" hint="Список CFS параметров">
<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="instanceOperationUid" type="string" required=true hint="type:guid"/>
<!---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(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/>
<cfquery name="local.qRead" 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">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">iop.param_value</m:field>
<m:field title="dt_created">to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
<m:field title="creator_id">iop.creator_id</m:field>
<m:field title="svc_operation_cfs_param">sop.svc_operation_cfs_param</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)
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
<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>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.total"=#local.qTotal.cnt#/>
<cfset var resultSet=[]/>
<cfloop query=#local.qRead# startRow=#startrow#>
<cfset var rec={}/>
<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
<cfset arrayAppend(resultSet, rec)/>
</cfloop>
<cfset "out.results"=#resultSet#/>
<cfset "out.pageSize"=#arguments.pageSize#/>
<cfset "out.page"=#arguments.page#/>
<cfset "out.orderBy"=#arguments.orderBy#/>
<cfset "out.size"=#arrayLen(resultSet)#/>
<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>
<!---<cfset "out.sql"=#local.result.sql#/>--->
<cfreturn representationOf(out)/>
</cffunction>
<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 ***Валидацию пока не проводим, а надо"/>
<cftry>
<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
<cfset this.helper.validateField(arguments, "svcOperationCfsParamId", "integer")/>
<!--- Еще возможна ошибка, что мы подадим параметр не от своей операции или сервиса - он останется невидим для всех --->
<!--- *** Проверка: операция не запущена, иначе создавать параметры нельзя --->
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset local={}/>
<cfset local.instanceOperationCfsParamUid=#createGUID()#/>
<cftry>
<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,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#"/>
,<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>
<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 ***Валидацию пока не проводим, а надо"/>
<cftry>
<cfset this.helper.validateField(arguments, "instanceOperationUid", "guid")/>
<cfset this.helper.validateField(arguments, "svcOperationCfsParamId", "integer")/>
<!--- Еще возможна ошибка, что мы подадим параметр не от своей операции или сервиса - он останется невидим для всех --->
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset local={}/>
<cfset local.instanceOperationCfsParamUid=#createGUID()#/>
<cftry>
<cfquery name="local.qCheck" result="local.result">
select io.dt_submit, 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"><!--- 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
from instance_operation io
join instance e on (io.instance_uid=e.instance_uid)
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
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.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>
<cfset var msg=checkParam(arguments.svcOperationCfsParamId, arguments.paramValue)/>
<cfif len(msg)>
<cfreturn representationOf(this.helper.formatMessage("Invalid CFS parameter value", msg)).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,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#"/>
,<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
,<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
);
</cfquery>
<cfcatch type="any"><!--- <cfrethrow/> --->
<cfreturn representationOf(this.helper.formatException(cfcatch, "Internal Error")).withStatus(500)/><!--- это не нужно показывать в продуктиве --->
</cfcatch>
</cftry>
<cfreturn noData()
.withHeaders({location: "./#local.instanceOperationCfsParamUid#"})
.withStatus(201, "Created")
/>
</cffunction>
<cffunction name="checkParam">
<cfargument name="svcOperationCfsParamId" type="numeric" required=true/>
<cfargument name="paramValue" required=true/>
<!--- *** Вероятно, стоит возвращать конкретную ругань --->
<cfquery name="qSvcOperationCfsParam">
select sop.svc_operation_cfs_param
,sop.value_list
,sop.data_type
,sop.is_required
,sop.maxlength
,sop.minlength
,sop.regex
,sop.unique_scope
from svc_operation_cfs_param sop
where sop.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationCfsParamId#"/>
</cfquery>
<cfif qSvcOperationCfsParam.is_required AND len(arguments.paramValue) EQ 0>
<cfreturn "Missing required parameter (#qSvcOperationCfsParam.svc_operation_cfs_param#)"/>
<cfelseif len(arguments.paramValue) AND NOT checkValue(arguments.paramValue,qSvcOperationCfsParam.data_type)>
<cfreturn "Invalid format (#qSvcOperationCfsParam.svc_operation_cfs_param#)"/>
</cfif>
<cfif len(arguments.paramValue) AND listLen(qSvcOperationCfsParam.value_list) GT 0 AND NOT listFind(qSvcOperationCfsParam.value_list, arguments.paramValue)>
<cfreturn "Значение параметра #qSvcOperationCfsParam.svc_operation_cfs_param# отсутствует в списке #qSvcOperationCfsParam.value_list#"/>
</cfif>
<cfif len(arguments.paramValue) AND qSvcOperationCfsParam.maxlength GT 0 AND len(arguments.paramValue) GT qSvcOperationCfsParam.maxlength>
<cfreturn "Длина #qSvcOperationCfsParam.svc_operation_cfs_param# превышает #qSvcOperationCfsParam.maxlength#"/>
</cfif>
<cfif len(arguments.paramValue) AND qSvcOperationCfsParam.minlength GT 0 AND len(arguments.paramValue) LT qSvcOperationCfsParam.minlength>
<cfreturn "Длина #qSvcOperationCfsParam.svc_operation_cfs_param# меньше #qSvcOperationCfsParam.minlength#"/> <!--- *** попробовать cfparam type="regex" --->
</cfif>
<cfif len(arguments.paramValue) AND len(qSvcOperationCfsParam.regex) AND reFind(qSvcOperationCfsParam.regex,arguments.paramValue) EQ 0>
<cfreturn 'Pattern "#qSvcOperationCfsParam.regex#" does not match "#arguments.paramValue#"'/>
</cfif>
<cfswitch expression=#qSvcOperationCfsParam.unique_scope#>
<cfcase value="provider">
<cfquery name="qUnique">
select count(*) as cnt
from instance_operation_cfs_param p
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=#qSvcOperationCfsParam.svc_operation_cfs_param_id#/>
<cfif isValid("guid",qSvcOperationCfsParam.instance_operation_cfs_param_uid)>
AND p.instance_operation_cfs_param_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#qSvcOperationCfsParam.instance_operation_cfs_param_uid# null=#!isValid("guid",qSvcOperationCfsParam.instance_operation_cfs_param_uid)#/>
</cfif>
</cfquery>
<!--- <cfdump var=#qUnique#/> --->
<cfif qUnique.cnt GT 0>
<cfreturn '#qSvcOperationCfsParam.svc_operation_cfs_param# = "#arguments.paramValue#" is not unique in the provider scope'/>
</cfif>
</cfcase>
<cfcase value="tenant">
<cfquery name="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=#qSvcOperationCfsParam.svc_operation_cfs_param_id#/>
AND u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#request.usr_id#/>
<cfif isValid("guid",qSvcOperationCfsParam.instance_operation_cfs_param_uid)>
AND p.instance_operation_cfs_param_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#qSvcOperationCfsParam.instance_operation_cfs_param_uid# null=#!isValid("guid",qSvcOperationCfsParam.instance_operation_cfs_param_uid)#/>
</cfif>
</cfquery>
<!--- <cfdump var=#qUnique#/> --->
<cfif qUnique.cnt GT 0>
<cfreturn '#qSvcOperationCfsParam.svc_operation_cfs_param# = "#arguments.paramValue#" is not unique in the tenant scope'/>
</cfif>
</cfcase>
<cfdefaultcase></cfdefaultcase>
</cfswitch>
<cfreturn ""/>
</cffunction>
<cffunction name="checkValue" 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? Или принимать оба формата и приводить к одному? А как быть с БД--->
</cfcase>
<cfdefaultcase><cfreturn true/>
<cfthrow type="custom" detail="unsupported CFS parameter value type (#arguments.dataType#)"/>
<!--- --->
</cfdefaultcase>
</cfswitch>
<!--- --->
</cffunction>
</cfcomponent>
+67
View File
@@ -0,0 +1,67 @@
<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations">
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<!--- <cffunction name="post" hint="Создание новой операции с параметрами. Принимаются CFS параметры, генерируются RFS параметры. Запускается операция отдельным вызовом. До запуска CFS параметры могут корректироваться (а надо?). Запущенная операция навсегда становится Read Only (при желании можем реализовать ее клонирование). Можем реализовать корректировку параметров, но непонятно, зачем. Когда мы конфигурируем инстанс, на самом деле мы задаем CFS параметры операции create. Можно править параметры, а можно пересоздать операцию с новыми параметрами. Предусмотреть удаление (метод DELETE) незапущенной операции?"> --->
<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"/>
<!--- *** полноценная валидация операции предполагает проверку, что операция поддерживается для данного сервиса и текущего состояния инстанса. Можно представить себе зависимость этого от параметров, но хотелось бы избежать такого усложнения --->
<!--- Можно отметить, что, например, допустимость операции 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>
<!--- *** Добавить проверку разрешенных переходов, например, create нельзя сделать на развернутом экземпляре
*** Вероятно, допустимые переходы нужно либо специфицировать в документации, либо явно опубликовать (инстанс может публиковать список допустимых операций) --->
<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, 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_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>
</cfcomponent>
+201
View File
@@ -0,0 +1,201 @@
<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 local={}/>
<cfset local.instanceUid=#createGUID()#/>
<!--- *** Проверить наличие обязательных CFS параметров
Создать RFS параметры
(валидируем CFS параметры при создании, а не сейчас) --->
<cftry>
<!---NOT NULL fields--->
<cfset this.helper.keyExistsAndValid(arguments, "instanceOperationUid", "guid")/>
<cfset generateRfsParams(arguments.instanceOperationUid, arguments.usrId)/>
<cfset submitJob(arguments.instanceOperationUid)/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<!--- <cfreturn representationOf("Not Implemented yet").withStatus(501)/> --->
<cfreturn noData()
.withHeaders({location: "./#local.instanceUid#"})
.withStatus(201, "Created")
/>
</cffunction>
<cffunction name="generateRfsParams">
<cfargument name="instanceOperationUid" type="guid"/>
<cfargument name="usrId" type="numeric"/>
<!---
-- полезный селект, не выбрасывать
<cfquery name="qSvcOperationCfsParam">
select sop.svc_operation_cfs_param_id
,iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid /*pgjdbc issue fix*/
,iop.param_value
,sop.svc_operation_cfs_param
,sop.data_type
,sop.is_required
,sop.maxlength,sop.minlength,sop.regex,sop.unique_scope
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)
left outer join instance_operation_cfs_param iop
on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id AND io.instance_operation_uid=iop.instance_operation_uid)
where iop.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid# null=#!isValid("guid",arguments.instanceOperationUid)#/>)
</cfquery>
--->
<cfquery name="qSvcOperation">
select so.svc_operation_id, io.instance_uid
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)
where io.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid# null=#!isValid("guid",arguments.instanceOperationUid)#/>
</cfquery>
<!--- поступим проще, по engine/generate_rfs_12.cfm --->
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "instanceUid", qSvcOperation.instance_uid)/>
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "operationUid", arguments.instanceOperationUid)/>
<cfset setInstanceOperationRfsParam(arguments.instanceOperationUid, "modifierId", #arguments.usrId#)/>
<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#"/>
/*PostgreSQL specific*/
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 /***null will be better*/
/*PostgreSQL specific*/
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
from instance_operation o
join instance i on (o.instance_uid=i.instance_uid)
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="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>
<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'
where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
</cfquery>
<cfhttp method="post"
url="#qInstanceOperation.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="formfield" name="delay" value="0sec"/>
<cfloop query="qInstanceOperationParam">
<cfhttpparam type="formfield" name="#param#" value="#param_value#"/>
</cfloop>
</cfhttp>
<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)/><!--- если придется подождать, можно добавить, но мы специально указываем delay=0sec --->
<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="#ORCHESTRATOR_AUTH#">
<cfhttpparam type="HEADER" name="Accept" value="application/json">
</cfhttp>
<cfquery name="qSetStatusUrl" datasource="#request.DS#">
update instance_operation
set status_url=<cfqueryparam cfsqltype="cf_sql_varchar" value="#operation_url#wfapi/describe"/>
where instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#instance_operation_uid#/>
</cfquery>
<cfcatch type="any">
<cfrethrow/><!--- пока не придумал --->
</cfcatch>
</cftry>
</cfif>
</cffunction>
<cffunction name="setInstanceOperationRfsParam">
<cfargument name="instance_operation_uid" 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.instance_operation_uid#"/>
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#ARGUMENTS.param#"/>
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#ARGUMENTS.val#"/>
)
/*PostgreSQL specific*/
ON CONFLICT (instance_operation_uid,param)
DO UPDATE SET param_value = EXCLUDED.param_value;
</cfquery>
<!--- <cfdump var=#ARGUMENTS#/> --->
</cffunction>
</cfcomponent>
+70
View File
@@ -0,0 +1,70 @@
<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 title="Сервис">s.svc</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 title="Описание">s.descr</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 title="ID" cfSqlType="CF_SQL_INTEGER">j.svc_operation_id</m:field>
<!--- <m:field title="ID сервиса">j.svc_id</m:field> --->
<m:field title="Операция">j.operation</m:field>
<m:field title="URL">j.url</m:field>
<m:field title="Описание">j.descr</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>
+83
View File
@@ -0,0 +1,83 @@
<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="s", type="date"},
dt_updated={prefix="s", type="date"},
svc_id={prefix="s", type="integer"},
svc={prefix="s", 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=""/>
<!---сюда можно воткнуть спецификацию полей для сортировки и фильтрации, но только ручками, идея компактного кода еще более отдаляется--->
<!---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(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/>
<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="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 title="Описание">s.descr</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_set>
from svc s
where 1=1 <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 svc
where 1=1
</cfquery>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.total"=#local.qTotal.cnt#/>
<cfset var resultSet=[]/>
<cfloop query=#local.qRead# startRow=#startrow#>
<cfset var rec={}/>
<cfset this.helper.appendRecord(rec, "", local.titleMap, local.qRead, this.helper.snake2camel)/>
<cfset arrayAppend(resultSet, rec)/>
</cfloop>
<cfset "out.results"=#resultSet#/>
<cfset "out.pageSize"=#arguments.pageSize#/>
<cfset "out.page"=#arguments.page#/>
<cfset "out.orderBy"=#arguments.orderBy#/>
<cfset "out.size"=#arrayLen(resultSet)#/>
<cfset "out.runDurationMs"=getTickCount()-request.startTickCount/>
<!---<cfset "out.sql"=#local.result.sql#/>--->
<cfreturn representationOf(out)/>
</cffunction>
</cfcomponent>
+68
View File
@@ -0,0 +1,68 @@
<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_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>p.value_list</m:field>
<m:field cfSqlType="CF_SQL_INTEGER">p.ref_svc_id</m:field>
<m:field cfSqlType="CF_SQL_BIT">p.is_required</m:field>
<m:field>p.default_value</m:field>
<m:field title="Описание">p.descr</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)#/>
</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#>
<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>
</cfcomponent>