I have found a strange bug in Coldfusion when adding two different components into some form of data structure and dumping them out.
For the purpose of this question I have simplified my code. I have Component A and Component B, both have one property, one constructor, and one function.
I create an instance of A, and an instance of B. When I dump the instances out separately I see the correct metadata.
However when I add the two instances to a data structure (Component B in first slot, A in second), it appears as if the methods from the instance in slot 1 are duplicated in the instance in slot 2, even though they are different objects. i.e. I see CompBFunction, GetCompBProp, SetCompBProp listed as methods for Comp A. The properties however look correct.
It is worth mentioning, that I cannot call the methods for Comp B on Comp A - This seems to be a display issue. I tried calling CompBFunction on my object 'a' and it failed as expected.
Any help here would be greatly appreciated. I understand it's not a huge issue - but it can be rather misleading to see these methods here when they don't exist. There are many instances in my code where I would want to build up a Data Structure of mixed components.
Apologies if this question has been asked before - I searched high and low, but could not find any info on it. I am using CF9.
Here is my example code:-
Component A
component name="CompA" output="false" cache="false" accessors="true" {
property name="CompAprop" type="Numeric" getter="true" setter="true";
public CompA function init() output="false" {
return this;
}
public String function CompAFunction() output="false" {
return "All";
}
}
Component B
component name="CompB" output="false" cache="false" accessors="true" {
property name="CompBprop" type="Numeric" getter="true" setter="true";
public CompB function init() output="false" {
return this;
}
public String function CompBFunction() output="false" {
return "All";
}
}
index.cfm
<cfset a = new CompA() />
<cfset b = new CompB() />
<cfset test = ArrayNew(1) />
<cfset test[1] = b />
<cfset test[2] = a />
<cfdump var="#test#" /> <!--- METHODS FOR B APPEAR IN METADATA FOR A --->
<cfdump var="#test[1]#" /> <!--- METADATA CORRECT --->
<cfdump var="#test[2]#" /> <!--- METADATA CORRECT --->
<cfabort />
Images
It works fine for me on CF 9.0.1 (update: and on CF10), and I have never encountered anything like the problem you're describing in the past. People would have raised this as a bug if it was an actual problem with ColdFusion, because it'd crop up fairly frequently, I reckon.
You have noticed that you're putting CompB into the array at index 1, and CompA into element 2, yeah? This conflicts with what you say in your question "However when I add the two instances to a data structure (Component A in first slot, B in second),". So one would expect them to be the "wrong" way round?
Related
I've never seen this before I have a component in a cfc file and I use
<cfset request = CreateObject("component","path/to/component") />
When I set request to the above or:
<cfset request = CreateObject("component","path/to/component").init() /> or
<cfset request = CreateObject("component","path/to/component").from_request() />
etc I always get a struct with a single item that is the equivilant of
{cfdumpinited = false}
I've never see this before. The from_request method reuturns init and init returns this.
When I <cfdump this> right before the <cfreturn this> I get the full object output on the screen. But when I <cfdump request> I get the struct stated above. Anyone know what causes Coldfusion to return this type of struct. I can post the entire cfc but I don't think that will help as I stated, right before the return I can output this and it is the entire object/component.
request is a scope in ColdFusion, therefore you should use another variable name.
I am using CF10. I have a Select:
<cfselect name="company" id="company" query="qcompany" display="placename" value="placeid" queryposition="below">
<option value="0">--Select--
</cfselect>
I have another cfselect that is bound to the first:
<cfselect name="People" bind="cfc:schedule.GetPeopleArray({company})" ></cfselect>
I cannot get the second cfselect to display any results. To test whether or not I am receiving data from my component (which I will display at the bottom), I bound a text box:
<cfinput name="test" bind="cfc:schedule.GetPeopleArray({company})" bindonload="false"/>
This text box is displaying the results of the call to my component every time, but the cfselect never displays anything.
What could I possibly be doing wrong?
I have tried returning arrays and queries from my component. No help. I have tried adding display and value attributes to the second cfselect. No help.
Here is my component:
<cfcomponent output="false">
<cffunction name="GetPeopleArray" access="remote" returnType="array" output="false">
<cfargument name="company" type="string" >
<!--- Define variables --->
<cfset var data="">
<cfset var result=ArrayNew(2)>
<cfset var i=0>
<cfquery name="qEmployee" datasource="texas" >
SELECT 0 as personid,'Select Person' as fullname,0 as sortorder
UNION
SELECT p.personid ,concat(firstname,' ',lastname) as fullname,3 as sortorder
FROM person p
INNER JOIN placeperson pp
ON p.personid=pp.personid
where personstatus='ACT'
and pp.placeid=#arguments.company#
order by sortorder,fullname
</cfquery>
<!--- Convert results to array --->
<cfloop index="i" from="1" to="#qEmployee.RecordCount#">
<cfset result[i][1]=qEmployee.personid[i]>
<cfset result[i][2]=qEmployee.fullname[i]>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
</cfcomponent>
Ultimately, you may want use jQuery anyway, but FWIW your existing code worked fine with CF10. (The only change was removing the JOIN for simplicity) So either you are using different code, or there is something else going on we are unaware of ..
Truthfully the Ajax functionality does have some "quirks". However, you should not have any problem with a simple case like this. Aside from adding a text field, what other debugging or troubleshooting steps did you perform? What I usually recommend is:
Test the CFC independently first. Access it directly in your browser with a variety of sample values:
http://localhost/path/to/schedule.cfc?method=GetPeopleArray&company=someValue
I did this with the original code and discovered an error occurred when the company value is not numeric, like an empty string. (I suspect that might have been the problem) You can prevent that error by substituting an invalid ID like 0 instead. Note, be sure to use cfqueryparam to prevent sql injection.
AND pp.placeid = <cfqueryparam value="#val(arguments.company)#"
cfsqltype="cf_sql_integer">
Enable the CF AJAX debugger in the CF Administrator. Then append ?cfdebug to your test script so you can view the console and check for problems/errors.
http://localhost/path/to/yourTestForm.cfm?cfdebug
Again, I did this after tweaking the query. But there were no errors. Your existing cfform code worked perfectly.
Usually those two steps are enough to pinpoint any problems. If not, make sure your Application.cfc file (if you are using one) is not interfering with the Ajax request. That is a common gotcha. Test the code in a separate directory that is outside any Application files.
EDIT: Also, you may as well set bindonload="false" for the select list too. Since you do not want to call the function when the page first loads. Only when the user selects something from the list.
When I try to instantiate one CFC from 2 different CFCs, ColdFusion returns a 500 error. I tried making one of the CFC extend the other, but it did not solve the issue. Is this possible, or am I simply doing something incorrectly?
<!--- one.cfc --->
<cfcomponent name="FirstCFC">
<cfset this.Tools = createObject('component', 'toolbox').init()>
....
</cfcomponent>
<!--- two.cfc --->
<cfcomponent name="SecondFC">
<cfset this.Tools = createObject('component', 'toolbox').init()>
....
</cfcomponent>
<!--- toolbox.cfc --->
<cfcomponent name="Toolbox">
<cffunction name="init" access="public">
<cfreturn this>
</cffunction>
<cffunction name="someFunc" access="public">
</cffunction>
</cfcomponent>
Here is a screenshot of the 500 error
Here is a more "full" code sample
http://pastebin.com/zJ1zpHYy
The java stack trace you've included is indicitive of an infinite recursion error. Take care when creating objects of type A, which have a new object of type B in their pseudo-constructor. If the object B itself creates an object of type A in its pseudo-constructor, you have yourself an infinite recursion of objects being created, ending in an ugly java stack trace.
Shawn is right. He posted more code and you can see two.cfc makes tools.cfc and tools.cfc makes two.cfc.
You need to move to an injection style setup (ala ColdSpring for example).
I am using the technique detailed in this answer to manage a library of small utility functions. (Essentially, each function is loaded as a "mix-in" using cfinclude.)
I have need, however, to know the names of the functions that the object has (once instantiated). A cfdump on the object only shows the init function which is written directly in the CFC.
Some more detail:
I am creating the object in the application scope in OnApplicationStart().
<cfset application.udfs=createObject("component","extensions.udfs").init()>
However, to save the developers from having to constantly write application.udfs.foo(), I thought I'd grab all of the functions and drop them in to the variables scope in OnRequestStart(), so that these hypothetical developers could just write foo().
<cfset foo=application.udfs.foo>
Obviously, though, this needs to be dynamic and to happen for each of the functions in the object, no matter how many there are. If I repeat this line for every function I've lost whatever I'd gained by having a library that is dynamically generated.
I thought perhaps I could use a collection loop, but that was invalid. I am fairly certain there's a way to get the list of methods in an object, but I have not yet been able to find it.
Any clues?
By the by, my fallback is going to be to copy the application.udfs object to a local object with a nice short name (like "u") so that the developers can simply type u.foo(), so no need to suggest that if what I want to do can't be done.
This should allow you to import all your udfs into the global variables scope:
StructAppend(variables, application.udfs);
I think GetMetaData should help you.
Here another interesting option suggested by Ben Nadel:
Check out the detail in his blog entry: http://www.bennadel.com/blog/1776-Creating-Globally-Accessible-User-Defined-Functions-In-ColdFusion-Safer-Version-.htm
UDF.cfc
<cfcomponent
output="false"
hint="I define user defined functions.">
<cffunction
name="getMessage"
access="public"
returntype="string"
output="false"
hint="I return a test message.">
<cfreturn "I am defined in the UDF component" />
</cffunction>
</cfcomponent>
Application.cfc
<!--- Define the application. --->
<cfset this.name = hash( getCurrentTemplatePath() ) />
<cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) />
<!---
Add all of our "global" methods to the URL scope. Since
ColdFusion will automatically seach the URL scope for
non-scoped variables, it will find our non-scoped method
names.
--->
<cfset structAppend(
url,
createObject( "component", "UDF" )
) />
Key points before reading further
All variables are properly var'ed (you'll have to trust me)
Scopes are not being reset while these long-running processes are happening
When dumping the metadata for the supposedly missing/invalid method, I get the right information
There are only two places where the name of this method are referenced in the application. Once where it's defined, and once were the method is called in the code below.
I have a very strange intermittent error that I can't seem to track down. Here's the background (these are severely trimmed down to simplify for posting).
FeedService.cfc:
<cfcomponent output="false" extends="FeedDAO">
<cffunction name="processXmlFile" access="public" output="false" returntype="struct">
<cfset Var local = StructNew() />
/***************************************
THE VARIABLES ARE ALL VAR'D - PROMISE!!!
Lots of other stuff goes on in here to get the ultimate set of XML nodes to loop through
*****************************************/
<cfloop from="1" to="#ArrayLen(local.arrChannels)#" index="local.currentChannelItem">
... Lots of XML parsing and stuff and things going on here ...
<cfset LOCAL.invCheck = checkCustomerListing(
Acct_ID = local.invStruct.AcctID
, CustomerListingID = local.invStruct.CustomerListingID
) />
... Lots more stuff going on here ...
</cfloop>
</cffunction>
</cfcomponent>
FeedDAO:
<cfcomponent output="false">
<cffunction name="checkCustomerListing" access="public" output="false" returntype="numeric" hint="Returns the numeric inventory ID for an existing inventory listing, or 0 if the listing doesn't exist.">
<cfargument name="Acct_ID" type="numeric" required="true" hint="" />
<cfargument name="CustomerListingID" type="string" required="true" hint="" />
<cfset var rs = "">
<cfquery name="rs" datasource="#Variables.DSNs.Primary#">
SELECT ID FROM TheTable
WHERE
Acct_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#Arguments.Acct_ID#" />
AND Customer_Listing_ID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Arguments.CustomerListingID#" />
</cfquery>
<cfif rs.RecordCount>
<cfreturn rs.Inv_ID />
<cfelse>
<cfreturn 0 />
</cfif>
</cffunction>
</cfcomponent>
I'm calling the initial function like so:
<cfset processStruct = Server.FeedService.processXmlFile(filePath) />
So, when a feed gets submitted to the processXMLFile function, it looks through all of the items in the file. A feed file may have 10, 100, or even 1000 entries. I get occasional error messages like this while a file is getting processed:
[struct]
Detail: The symbol you provided checkCustomerListing is not the name of a function.
Message: Entity has incorrect type for being called as a function.
StackTrace: coldfusion.runtime.CfJspPage$UninvocableEntityException: Entity has incorrect type for being called as a function.
at coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:2441)
at coldfusion.runtime.SuperScope.invoke(SuperScope.java:18)
at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2222)
More stack dump information
Type: Application
symbolName: checkCustomerListing
[object of coldfusion.runtime.CfJspPage$UninvocableEntityException]
Class Name: coldfusion.runtime.CfJspPage$UninvocableEntityException
Fields:
java.lang.String symbolName: checkCustomerListing
Parent Class: [object of coldfusion.runtime.ApplicationException]
Class Name: coldfusion.runtime.ApplicationException
Parent Class: [object of coldfusion.runtime.NeoException]
Class Name: coldfusion.runtime.NeoException
Methods:
findAdvancedCFTarget(coldfusion.runtime.AdvancedCFException, java.lang.String[]) returns int
findCustomTarget(coldfusion.runtime.CustomException, java.lang.String[]) returns int
findThrowableTarget(java.lang.Throwable, java.lang.String[]) returns int
getDetail() returns java.lang.String
getLocalizedMessage() returns java.lang.String
getMessage() returns java.lang.String
getRootCause() returns java.lang.Throwable
getString(java.lang.Throwable, java.lang.String, java.util.Locale) returns java.lang.String
getType() returns java.lang.String
setLocale(java.util.Locale) returns void
unwrap(java.lang.Throwable) returns java.lang.Throwable
Parent Class: [object of java.lang.RuntimeException]
Class Name: java.lang.RuntimeException
Parent Class: [object of java.lang.Exception]
Class Name: java.lang.Exception
Parent Class: [object of java.lang.Throwable]
Class Name: java.lang.Throwable
Methods:
fillInStackTrace() returns java.lang.Throwable
getCause() returns java.lang.Throwable
getLocalizedMessage() returns java.lang.String
getMessage() returns java.lang.String
getStackTrace() returns java.lang.StackTraceElement[]
initCause(java.lang.Throwable) returns java.lang.Throwable
printStackTrace(java.io.PrintWriter) returns void
printStackTrace(java.io.PrintStream) returns void
printStackTrace() returns void
setStackTrace(java.lang.StackTraceElement[]) returns void
toString() returns java.lang.String
I may get one error in 1000 entries, or I may get a small batch of errors at one time, and the rest of the feed processes just fine (due to some try/catch logic to prevent the entire thing from crapping out). At one point, the checkCustomerListing was in a completely different Server scoped object, and I never had a problem. I moved it into the FeedDAO and started calling it via the Super scope, and that's when these intermittent errors began.
UPDATE: I have everything properly var'ed, I just chopped it all out for the sake of brevity.
UPDATE AGAIN: Changed code sample comments to make it clear that there is a lot of stuff going on before the first loop begins, including setting all LOCAL variables that will be used in the loop.
More Code Information:
I should note that there are only two places in our entire application (thousands upon thousands of lines of code) where the string 'checkCustomerListing' exists. One is where the function is called, and two is where the function is declared. There are no other instances of the string checkCustomerListing anywhere.
Update: 6 September, 2011
I added some additional error checking to see if I could find out what the app thought checkCustomerListing was (thanks Adam and Ryan). Here's my new try/catch statement:
<cfcatch type="any">
<cfset local.tmpError.cfcatch = cfcatch>
<cfif isDefined("checkCustomerListing")>
<cfset local.tmpError.customerListing = checkCustomerListing />
<cfset local.tmpError.customerListingMeta = getMetaData(checkCustomerListing) />
<cfelse>
<cfset local.tmpError.customerListing = "Checkcustomerlisting is not defined" />
</cfif>
<cfset Server.Utilities.Errors.emailCaughtError(local.tmpError)>
</cfcatch>
So I got an error this morning, and in the email I received, there is no customerListing node in the dump, but there is a meta node:
CUSTOMERLISTINGMETA:
[struct]
ACCESS: public
HINT: Returns the numeric inventory ID for an existing inventory listing, or 0 if the listing doesn't exist.
NAME: checkCustomerListing
OUTPUT: false
PARAMETERS:
[array]
1) [struct]
HINT: [empty string]
NAME: Acct_ID
REQUIRED: true
TYPE: numeric
2) [struct]
HINT: [empty string]
NAME: CustomerListingID
REQUIRED: true
TYPE: string
RETURNTYPE: numeric
All of that meta information is exactly correct... so if it can find the metadata for the function, why can't it find the function itself?
This usually crops up as a result of lack of VARing as others have alluded to. Quite often people have a private function called "getStuff" and within that they have a query called "getStuff". If the variable isn't VARed, then the getStuff query result goes into the CFC's variables scope, which will overwrite the function getStuff because that also resides in the variables scope.
So check your usage of checkCustomerListing for any same-named variables that aren't VARed (or in the local scope).
(and note that the checkCustomerListing variables don't need to be within the same method for this to happen... it could be anywhere in the CFC or any extending or super CFCs...)
Why are you doing super.checkCustomerListing? You would only do that if you overrode the function in your service and wanted to run the 'parent'. Just call checkCustomerListing().
It may be in your abridged code but are you validating that local.invStruct.AcctID and local.invStruct.CustomerListingID both exist and are of the proper types? Sometimes in Java "function doesn't exist" means "method signature doesn't exist". I am not sure when CF validates datatypes but if you are looping over the same two variables if might be shortcutting and not validating the types in later loops.
Might also toss a val() on the return value just in case.
i've seen this before when you have an object cached in a scope that is taking a while to run and you either overwrite or remove the object in the cache. my guess is this is what is happening, another application is doing something to the object in the cache and it's casuing the other applications to trip over each other.
a good way to test this is to duplicate the object cached in the server scope to a local variable and then use that local variable to run your process:
<cfset _feedservice = duplicate(Server.FeedService)>
<cfset processStruct = _feedservice.processXmlFile(filePath) />
now even if the object in the server scope gets overwritten or removed, you still have a copy of the original object that your process will use. it also won't be a bad idea to use locks around the above code:
<cflock scope="server" timeout="5" type="readonly">
<cfset _feedservice = duplicate(Server.FeedService)>
<cfset processStruct = _feedservice.processXmlFile(filePath) />
</cflock>
now personally an even better practice (in my opinion) would be to add something to your onApplicationOnStart() event that will copy all the objects that you are caching to the server scope into the application scope. the only reason i can see anyone ever using the server scope is because you want all the applications on the server sharing the same objects. doing this will still allow your applications to share the same code, but will prevent other applications from accidentally tripping over each other