Why is CFTHREAD not running query and creating a file? - coldfusion

I have a function that should check the date of a file. If the file is greater than sixty seconds, a query should run and create a new file. The query takes sixty seconds to run.
This process works perfectly when it's not wrapped in a CFTHREAD. When CFTHREAD is used, nothing seems to happen. I get no errors. What I expect to see, is a new file being made. I never see that new file.
Where should I look for an error? What am I missing? Why is CFTHREAD not working?
<!--- GET CATEGORIES --->
<cffunction name="getCategories" access="remote">
<cfscript>
LOCAL.MaxFileAge = 60;
LOCAL.MaxFileUnits = 's';
// THE FILE
LOCAL.TheFileDaily = "#VARIABLES.JSDir#\#VARIABLES.DayMonth#-categories.json";
// THE FILE DOES NOT EXIST
if (fileExists(LOCAL.TheFileDaily) == false) {
LOCAL.MakeNewFile = true;
// THE FILE EXISTS
} else {
// GET THE DATE OF THE FILE
LOCAL.LastModified = getFileInfo(LOCAL.TheFileDaily).LastModified;
// GET FILE AGE
LOCAL.FileAge = dateDiff(LOCAL.MaxFileUnits, LOCAL.LastModified, now());
// FILE IS OLD
if (LOCAL.FileAge > LOCAL.MaxFileAge) {
LOCAL.MakeNewFile = true;
} else {
LOCAL.MakeNewFile = false;
}
}
</cfscript>
<cfif LOCAL.MakeNewFile eq true>
<cfthread action="run" priority="HIGH">
<cfquery name="Q">
SELECT Stuff
FROM Tables
</cfquery>
<!--- MAKE THE DAILY FILE --->
<cffile action="write" file="#LOCAL.TheFileDaily#" output="#serializeJSON(Q)#">
</cfthread>
</cfif>
</cffunction>

You can't write to and share the local scope to a seperate thread, you need to share them via the request scope (request is the ideal scope for this as developers have very tight control over what data is contained within). You might try something like this:
Create a struct within the request scope and write to that.
In fairness, only variables you need to transfer need be in the request scope's struct. This is just a generic update because I don't know what the contents of your CFTHREAD really looks like. In this case, it actually looks like TheFileDaily is the only variable you're sharing, so that would be the only thing that needed to be in the request scope.
<!--- GET CATEGORIES --->
<cffunction name="getCategories" access="remote">
<cfscript>
request.lData = StructNew();
request.lData.MaxFileAge = 60;
request.lData.MaxFileUnits = 's';
// THE FILE
request.lData.TheFileDaily = "#VARIABLES.JSDir#\#VARIABLES.DayMonth#-categories.json";
// THE FILE DOES NOT EXIST
if (fileExists(request.lData.TheFileDaily) == false) {
request.lData.MakeNewFile = true;
// THE FILE EXISTS
} else {
// GET THE DATE OF THE FILE
request.lData.LastModified = getFileInfo(request.lData.TheFileDaily).LastModified;
// GET FILE AGE
request.lData.FileAge = dateDiff(request.lData.MaxFileUnits, request.lData.LastModified, now());
// FILE IS OLD
if (request.lData.FileAge > request.lData.MaxFileAge) {
request.lData.MakeNewFile = true;
} else {
request.lData.MakeNewFile = false;
}
}
</cfscript>
<cfif request.lData.MakeNewFile eq true>
<cfthread action="run" priority="HIGH">
<cfquery name="Q">
SELECT Stuff
FROM Tables
</cfquery>
<!--- MAKE THE DAILY FILE --->
<cffile action="write" file="#request.lData.TheFileDaily#" output="#serializeJSON(Q)#">
</cfthread>
</cfif>
</cffunction>
Useful sources:
Working with Threads
Adam Cameron's post on the topic

Related

Function that includes JSP file is returning an empty result in Lucee but not ACF

I have a function utilizing an include, of a JSP file, to retrieve thread information which is then converted into a query object. The function returns an empty query Lucee, but it executes properly in ColdFusion.
CFML:
<cffunction name="mainThreads" output="false" returntype="query" access="public">
<cfargument name="filterPages" type="boolean" required="true">
<cfscript>
var threadStackDump = "";
var thread = 0;
var stackTrace = "";
request.threads = arraynew(1);
GetPageContext().include("putParentThreadInRequestScope.jsp");
ThreadQuery = QueryNew("id, name, group, stacktrace, alive", "Integer, VarChar, VarChar, VarChar, Bit");
QueryAddRow(ThreadQuery, arrayLen(request.threads));
for ( thread = 1; thread lte arrayLen(request.threads); thread = thread + 1 )
{
QuerySetCell(ThreadQuery, "id", request.threads[thread].getId(), thread);
QuerySetCell(ThreadQuery, "name", request.threads[thread].getName(), thread);
QuerySetCell(ThreadQuery, "group", request.threads[thread].getThreadGroup().getName(), thread);
QuerySetCell(ThreadQuery, "alive", request.threads[thread].isAlive(), thread);
threadStackDump = "";
stackTrace = request.threads[thread].getStackTrace();
for ( element = 1; element lte arrayLen(stackTrace); element = element + 1 )
if ( arguments.filterPages )
{
if ( findNoCase('runPage',stackTrace[element]) neq 0 or findNoCase('runFunction',stackTrace[element]) neq 0 )
threadStackDump = threadStackDump & stackTrace[element] & "#chr(13)#";
}
else
threadStackDump = threadStackDump & stackTrace[element] & "#chr(13)#";
QuerySetCell(ThreadQuery, "stacktrace", threadStackDump, thread);
}
return ThreadQuery;
</cfscript>
</cffunction>
JSP
<%
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Thread threadList[]=new Thread[Thread.activeCount()];
threadGroup.enumerate(threadList);
request.setAttribute("threads", threadList);
%>
The code is not working in Lucee, but I'm not sure why. Does it have something to do with the java versions?
I'd guess you forgot to enable handling of .jsp in your web.xml, but ... you're not aware of it because the problem code executes inside a cffunction that suppresses all output!
Take a leaf out of Troubleshooting 101 and test the problem code in small chunks. Start by executing the JSP include separately. If it displays the JSP code on screen, instead of executing it, then you know JSP handling isn't enabled, and that's your problem.
<cfscript>
GetPageContext().include("putParentThreadInRequestScope.jsp");
writeDump( request );
</cfscript>

Coldfusion Struct getting only numeric key list

I have a coldfusion Struct containing mix keys numeric and alpha, alphanumerics
I need to access only the numeric keys.
My code looks like
<cfset ids = structkeyList(st ) />
<cfset numericIDs = "" />
<cfloop list="#ids#" index="i">
<cfif IsNumeric(i)>
<cfset numericIDs = ListAppend( numericIDs , i ) />
</cfif>
</cfloop>
Is there a better method to solve such problems?
Is there a better method to solve such problems?
I would use something like this:
<cfset numericIDs = arrayToList(reMatch('\b\d+(?=,|$)\b', structKeyList(st)))>
Is there a better method to solve such problems?
I'd generally recommend working with arrays instead of lists.
In CF9 a loop similar to yours is as good as it gets. You can make a utility function out of it if you need it more than once. This one avoids StructKeyList() to be able to deal with all kinds of keys, independent of a separator character:
<cfscript>
function GetNumericKeys(struct) {
var keys = struct.keys();
var result = ArrayNew(1);
var key = "";
while (keys.hasNext()) {
key = keys.next();
if (IsNumeric(key)) ArrayAppend(result, key);
}
return result;
}
</cfscript>
and
<cfset nkeys = GetNumericKeys(st)>
In CF11 you can get a little more sophisticated (tested on CF11, can't say how CF10 handles this code).
<cfscript>
numericIDs = arrayFilter(structKeyArray(st), function (key) {
return IsNumeric(key);
});
</cfscript>
To ensure integer keys, use:
<cfscript>
numericIDs = arrayFilter(structKeyArray(st), function (key) {
return Int(key) eq key;
});
</cfscript>
I really don't see what's wrong with this. It should work quite well already, and it is very readable.
Sometimes working with a List is faster than an Array.
I had this:
<cfscript> function ListNumeric(principal) {
a=principal;
cleanlist = ''; for (i=1; i <= ListLen(a);i=i+1) { if(IsNumeric(ListGetAt(a,i))){ cleanlist = ListAppend(cleanlist,ListGetAt(a,i)); } } Return cleanlist; } </cfscript>
Also possible to work with regular expression:
inList2 = REReplace(inList,"[^0-9.]", "","ALL");

QueryAddRow throwing error in Coldfusion Webservice

I am facing a weird issue.
When I am consuming the below snippet of code as a webservice residing in a CF9 server I am getting the error "The value coldfusion.runtime.Struct cannot be converted to a number."
The call returns an array of structures. I would like to create a query from this array of structure. When I place this code as a standalone code in my local server(CF10) it works fine. But as soon i place it in the remote server to be invoked i get the error.
I almost pulled out my hair when I got the same error message even when I replaced the variable 'tempstruct' with a hard coded structure. As soon I remove the QueryAddRow I am able to return anything.
Any help is appreciated.
<cfset myquery=querynew("category,category_id,event_description","varchar,integer,varchar")>
<cfinvoke
webservice="http://199.99.99.999/vod_queries.cfc?wsdl"
method="getAllCategoryByResort"
returnvariable="arrAllSpaEvents"
refreshwsdl="true" >
<cfinvokeargument name="Resort" value="SRB" >
</cfinvoke>
<cfif arraylen(arrAllSpaEvents) GT 0>
<cfloop array="#arrAllSpaEvents#" index="cur_row">
<cfset tempstruct=StructNew()>
<cfset tempstruct.CATEGORY=cur_row.CATEGORY>
<cfset tempstruct.CATEGORY_ID=cur_row.CATEGORY_ID>
<cfset tempstruct.EVENT_DESCRIPTION=cur_row.EVENT_DESCRIPTION>
<cfset QueryAddRow(myquery,#tempstruct#)>
</cfloop>
</cfif>
<cfreturn myquery>
You almost got.
However, indeed you are using new CF10 overloading in CF9. What's more, if you were using CF10, it looks like you could stuff the whole top array in with looping like that.
But you can almost do the same thing. CF9 will take an array overload for the value.
Not quite as clean as CF10 but you do what you can.
Also, the extra # signs are superfluous.
Here is an example with something that your data might look like:
<cfscript> // I did this all in a cfscript block for simplicity
Your retrieved data might look something like this guessin from example
arrAllSpaEvents = [
{category='fun', category_id=1, event_description='massage'},
{category='work', category_id=2, event_description='spinning'},
{category='beauty', category_id=3, event_description='mani'},
{category='beauty', category_id=3, event_description='pedi'}
];
Create a more useful struct to build the query dynamically
s = {
category = {colType = 'varchar', colVals = []},
category_id = {colType = 'integer', colVals = []},
event_description = {colType = 'varchar', colVals = []}
};
This is looping the data to fill the colVals arrays
for(c = 1; c <= arrAllSpaEvents.size(); c++ ) {
for(k in arrAllSpaEvents[c]) {
s[k].colVals[c] = arrAllSpaEvents[c][k];
}
}
This is the short form of the same double loop above in a single line
for(c = 1; c <= arrAllSpaEvents.size(); c++ ) for(k in arrAllSpaEvents[c]) s[k].colVals[c] = arrAllSpaEvents[c][k];
Now build your query. Start with an empty query (pass in a blank);
q = queryNew('');
Then loop your struct and using the keys for the column names (for simplicity they are the same key)
for(k in s ) queryAddColumn(q,k,s[k].colType,s[k].colVals);
Verify your struct and query:
writedump(s);
writedump(q);
</cfscript>
I ran this in CF9 so should work fine for you.
This should get you going.

How do you call a cffunction in a cfc from another cfm page using cfscript?

I have a test.cfm page and would like to call a cfc with a <cffunction> named errorEmail using <cfscript> from that page (test.cfm) instead of
<cfinvoke component = "#cfcPath#" method = "errorEmail" returnVariable = "myReturn"
description = "get list of projman">
</cfinvoke>
I have tried:
<cfscript>
errorEmail(cfcPath);
</cfscript>
I do this all the time.
1) Create the object:
<cfscript>
// CREATE OBJECT
TheCFC = createObject("component", "thecfc");
</cfscript>
2) Call the function:
<cfscript>
// CALL THE FUNCTION
SomeVariable = TheCFC .theFunction();
</cfscript>
Your version would look like this
<cfscript>
// CREATE OBJECT
TheObject = createObject("component", "cfcPath");
// CALL THE FUNCTION
myReturn = TheObject.errorEmail();
</cfscript>

How to Deep Copy (clone) a structure while ignoring components

In versions of ColdFusion prior to 8 the duplicate function throws an error if there are any components in the structure. In 8 and beyond it will work, but there are issues when copying components.
So, What I need is a way to create a deep copy of a structure that ignores components. For my purposes it's for debugging, I need a snapshot of the variables scope at a particular point in the code, so efficiency doesn't really matter as this will never make it out of the development environment. Currently using CF 7, I would take what 8 offers if only to solve this immediate issue, but I don't control upgrade :(
While you were off killing brain cells, I took a stab at a recursive function ;) It excludes components and java/com objects. Neither of which MX7 can duplicate. I threw the functions into a component to avoid tampering with the variables scope. Then stored the instance in the request scope.
It is not rigorously tested. So I am sure there is room for improvement.
Usage
<cfset request.util = createObject("component", "Util")>
<cfset request.copy = request.util.duplicateStructMinusObjects(variables)>
<cfdump var="#request.copy#">
Util.cfc
<cfcomponent>
<cfscript>
function duplicateArrayMinusObjects(input) {
var x = "";
var value = "";
var output = arrayNew(1);
for (x = 1; x lte arrayLen(arguments.input); x = x + 1) {
value = arguments.input[x];
// note components are considered structures
if (IsStruct(value) and not IsObject(value)) {
arrayAppend(output, duplicateStructMinusObjects(value));
}
else if (IsArray(value)) {
arrayAppend(output, duplicateArrayMinusObjects(value));
}
else if (not IsObject(value)){
arrayAppend(output, duplicate(value));
}
}
return output;
}
function duplicateStructMinusObjects(input) {
var key = "";
var value = "";
var output = structNew();
for (key in arguments.input) {
value = arguments.input[key];
// note components are considered structures
if (IsStruct(value) and not IsObject(value)) {
output[key] = duplicateStructMinusObjects(value);
}
else if (IsArray(value)) {
output[key] = duplicateArrayMinusObjects(value);
}
else if (not IsObject(value)){
output[key] = duplicate(value);
}
}
return output;
}
</cfscript>
</cfcomponent>
Doesn't matter how long you think/search, you always come up with the answer right after you ask the question.
I was able to solve this by deliberately mis-using try/catch, so I looped through the structure, did a try on creating an object out of a each item as if it were a component, and on error, copied it to my snapshot structure. I also had to store it in a different scope, in my case I used session, since if I let it go to the default variables, there would be a circular reference that cause a structure with an infinite number of children.
EDIT: THIS DOES NOT DO WHAT I THOUGHT IT DID, SEE BELOW
<cfset session.varSnapShot = StructNew()>
<cfset loopList = StructKeyList(variables)>
<cfloop from="1" to="#ListLen(loopList)#" index="i">
<cftry>
<cfobject name="x#i#" component="#variables[ListGetAt(loopList,i)]#">
<cfcatch>
<cfset session.varSnapShot[ListGetAt(loopList,i)]= variables[ListGetAt(loopList,i)]>
</cfcatch>
</cftry>
</cfloop>
EDIT: Since the above doesn't actually do a deep copy (thanks Leigh) I came up with this:
<cfloop from="1" to="#ListLen(loopList)#" index="i">
<cfset metaData = GetMetaData(variables[ListGetAt(loopList,i)])>
<cfif isStruct(metaData) AND isDefined("metaData.type") AND metaData.type EQ "component">
<cfelse>
<cfset session.varSnapShot[ListGetAt(loopList,i)]= duplicate(variables[ListGetAt(loopList,i)])>
</cfif>
</cfloop>
This does make a deep copy but will still be a problem if a component is below the first level of an object. I wanted to create a recursive method, but It's an hour and a half past quitting time on a Friday. I will instead kill brain cells at the pub and maybe update this with the recursive method on Monday if I don't forget.
a modern update to this question:
why not just use #serializeJSON(deserializeJson(theObject))#
that way you get deep copy with no components.