ColdFusion 9 Dynamic Method Call - coldfusion

I am trying to work out the correct <cfscript> syntax for calling a dynamic method within ColdFusion 9. I have tried a number of variations and had a good search around.
<cfinvoke> is clearly the tag I want, sadly however I cannot use this within my pure cfscript component as it was implemented in ColdFusion 10.
i.e coldfusion 9 dynamically call method
I have tried the following within my CFC:
/** Validate the method name **/
var resources = getResources();
if (structKeyExists(variables.resources, name)) {
variables.resourceActive[name] = true;
var reflectionMethod = resources[name];
var result = "#reflectionMethod.getMethodName()#"(argumentCollection = params);
}
Where the return value of reflectionMethod.getMethodName() is the method name I want to call. It is 100% returning the correct value (the name of the method) where that method is correctly defined and accessible,
My error is a syntax error on that line.

You don't want to get the method name, you want to get the actual method, eg something like:
function getMethod(string method){
return variables[method];
}
The call that, thus:
theMethod = getMethod(variableHoldingMethodName);
result = theMethod();
Unfortunately one cannot simply do this:
result = getMethod(variableFoldingMethodName)();
Or:
result = myObject[variableFoldingMethodName]();
As the CF parser doesn't like the double-up of the parentheses or brackets.
The caveat with the method I suggested is that it pulls the method out of the CFC, so it will be running in the context of the calling code, not the CFC instance. Depending on the code in the method, this might or might not matter.
Another alternative is to inject a statically-named method INTO the object, eg:
dynamicName = "foo"; // for example
myObject.staticName = myObject[dynamicName];
result = myObject.staticName(); // is actually calling foo();

Assuming the method is in your current (variables) scope, you could try:
var result = variables[reflectionMethod.getMethodName()](argumentCollection = params);

Related

Java code migration error using com.jayway.jsonpath.JsonPath

I am using Java code and converting the code in ColdFusion. There are some challenges where I am stuck. This is one function I have in Java:
import com.jayway.jsonpath.JsonPath;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONStyle;
private static String getDetails(String instaDetailsElement) {
String jsonResponse = instaDetailsElement.split(" = ")[1];
JSONArray mediaArray = JsonPath.read(jsonResponse, "$.entry_data.PostPage[:1].graphql.shortcode_media");
String returnJsonString = mediaArray.toJSONString(new JSONStyle(JSONStyle.FLAG_IGNORE_NULL));
System.out.println(returnJsonString);
return returnJsonString;
}
These two lines are giving me some trouble:
var mediaArray = JsonPath.read(jsonResponse, "$.entry_data.PostPage[:1].graphql.shortcode_media");
var returnJsonString = mediaArray.toJSONString(new JSONStyle(JSONStyle.FLAG_IGNORE_NULL));
Here is what I attempted so far. I loaded the jar library for JSON path and tried using it like this:
Application.cfc settings
<cfset this.javaSettings = {LoadPaths = ["cfc/jar"], loadColdFusionClassPath = true, reloadOnChange = false}>
CF Code:
public any function getDetails(String instaDetailsElement) {
var jsonResponse = instaDetailsElement.split(" = ")[1];
var JsonPath = Createobject("java","com.jayway.jsonpath.JsonPath");
writedump(application);
var mediaArray = JsonPath.read(jsonResponse, "$.entry_data.PostPage[:1].graphql.shortcode_media");
writedump(mediaArray); abort;
var returnJsonString = mediaArray.toJSONString(new JSONStyle(JSONStyle.FLAG_IGNORE_NULL));
return returnJsonString;
}
I'm able to view the class methods when I dump the JsonPath object (screen shot), but when I try to call JsonPath.read() I get this error:
No matching Method for read(string, string) found for
com.jayway.jsonpath.JsonPath
TL;DR;
No matching method for read(string, string) found for com.jayway.jsonpath.JsonPath
Technically the error message is correct: there is no read() method that accepts two strings (even though that's how it's used in the java code). The method actually expects three arguments:
Pass in an empty array for the 3rd argument:
JsonPath.read(jsonResponse, "$.entry_data.PostPage[:1].graphql.shortcode_media", []);
Explanation:
String jsonResponse = instaDetailsElement.split(" = ")[1];
JsonPath.read(jsonResponse, "$.entry_data.PostPage[:1].graphql.shortcode_media")
If there really is no read(String, String) method, you might wonder why the java code works at all, since that's exactly what it uses. It works due to a special feature of java.
The documentation shows the overloaded read(..) method actually has three parameters, but one of them is special:
read(String json,
String jsonPath,
Predicate... filters)
Notice the ... after the class name (Predicate)? It's a construct called "varargs" (or variable number of arguments):
You can use a construct called varargs to pass an arbitrary number of values to a method. You use varargs when you don't know how many of a
particular type of argument will be passed to the method. It's a
shortcut to creating an array manually ...
To use varargs, you follow the type of the last parameter by an ellipsis (three dots, ...), then a space, and the parameter name. The
method can then be called with any number of that parameter,
including none.
So in java you're allowed to omit the third argument entirely and call read(String, String) with two strings. ColdFusion doesn't support that syntax, because it creates too much ambiguity. So in lieu of omitting the argument, you can pass in an empty array instead:
JsonPath.read(jsonResponse, "$.entry_data.PostPage[:1].graphql.shortcode_media", []);
(Since this has turned into two questions in one thread, I'm separating the second answer out for clarity ...)
var returnJsonString = mediaArray.toJSONString(new JSONStyle(JSONStyle.FLAG_IGNORE_NULL));
As for translating the JSONStyle code, it helps to unpack nested code from the inside out. Then tackle each piece separately:
mediaArray.toJSONString(new JSONStyle( JSONStyle.FLAG_IGNORE_NULL ));
mediaArray.toJSONString( new JSONStyle( JSONStyle.FLAG_IGNORE_NULL ) )
mediaArray.toJSONString( new JSONStyle( JSONStyle.FLAG_IGNORE_NULL ) )
Piece #1
Uses a static field of the JSONStyle class named FLAG_IGNORE_NULL. To access the field, create a reference to that class:
JsonStyle = createObject("java", "net.minidev.json.JSONStyle");
writeDump(JSONStyle.FLAG_IGNORE_NULL);
Piece #2
Creates a brand new instance of the JSONStyle class, using the static field from above. Use createObject() to create the new instance, passing the static field into the psuedo constructor init():
newJsonStyle = createObject("java", "net.minidev.json.JSONStyle").init(JSONStyle.FLAG_IGNORE_NULL);
writeDump( newJsonStyle );
Piece #3
All that's left is calling the JSONArray.toJSONString() method with the JSONStyle object you just created:
result = mediaArray.toJSONString( newJsonStyle );
writeDump(result);

Error while creating large dropdown in excel using ColdFusion

This code I have written for creating large dropdown in ColdFusion, but it is not working on my end. Could any one please help me rectify my problem. The new code is
<cfquery name="getPOP" datasource="l_webalc">
select distinct center_code from alc_pop
</cfquery>
<cfset countryName= ArrayNew(1)>
<cfloop query="getPOP">
<cfset arrayappend(countryName, getPOP.center_code)>
</cfloop>
<script>
workbook = new HSSFWorkbook();
realSheet = workbook.createSheet("Sheet xls");
hidden = workbook.createSheet("hidden");
for (int i = 0, length= countryName.length; i < length; i++) {
String name = countryName[i];
HSSFRow row = hidden.createRow(i);
HSSFCell cell = row.createCell(0);
cell.setCellValue(name);
}
namedCell = workbook.createName();
namedCell.setNameName("hidden");
namedCell.setRefersToFormula("hidden!A1:A" + countryName.length);
constraint = DVConstraint.createFormulaListConstraint("hidden");
addressList = new CellRangeAddressList(0, 0, 0, 0);
validation = new HSSFDataValidation(addressList, constraint);
workbook.setSheetHidden(1, true);
realSheet.addValidationData(validation);
stream = new FileOutputStream("c:\\range.xls");
workbook.write(stream);
stream.close();
</script>
Update 1:
(From other thread) I am getting this error message:
function keyword is missing in FUNCTION declaration. The CFML compiler
was processing: A script statement beginning with HSSFWorkbook on line
32, column 1. A script statement beginning with function on line 31,
column 9. A cfscript tag beginning on line 30, column 2.
Update 2:
Again I have modified this code and now the new error is
"The value hidden not A1:A cannot be converted to a number."
I edited the objects as mentioned in the comments and also changed the script to cfscript. Please help me to rectify this error.
<cfscript>
workbook = createObject("java", "org.apache.poi.hssf.usermodel.HSSFWorkbook");
realSheet = workbook.createSheet("Sheet xls");
hidden = workbook.createSheet("hidden");
for (i = 1; i <= arrayLen(countryName); i++){
name = countryName[i];
row = hidden.createRow(i);
cell = row.createCell(0);
cell.setCellValue(name);
}
namedCell = workbook.createName();
namedCell.setNameName("hidden");
namedCell.setRefersToFormula("hidden!A1:A"+arrayLen(countryName));
dv = createObject("java", "org.apache.poi.hssf.usermodel.DVConstraint");
constraint = dv.createFormulaListConstraint("hidden");
addressList = cellRangeList.init(0, 0, 0, 0);
validation = dataValidation.init(addressList, constraint);
workbook.setSheetHidden(1, true);
realSheet.addValidationData(validation);
stream = new FileOutputStream("c:\\range.xls");
workbook.write(stream);
stream.close();
</cfscript>
Update 3:
I have updated the code to fix the mentioned issues and and am now getting this error
"The setSheetHidden method was not found ..."
on the following line:
workbook.setSheetHidden(1, true);
There are several problems with your code. Though java syntax is similar, you cannot just copy and paste a java example and expect it to run in cfscript. You need to make some adjustments first. (Note: I am assuming script was just a typo for cfscript).
In java, you can instantiate an object using the keyword "new" ie new SomeClassName(). In CF, the new keyword can only be used with cfc's. To create a java object, you must use createObject instead. To instantiate it, call the init(...) method. It is a special method in CF that invoke's the constructor of a java class with whatever parameters you supply, ie
createObject("java", "path.to.SomeClassName").init();
To use static methods such as DVConstraint.createFormulaListConstraint(), you must also use createObject. While the java code does not create an new instance of that class, you must still use createObject to get a reference to the DVConstraint class, in CF, before you can invoke any of its methods. Note: Because it is static, no need to call init() first. ie
dv = createObject("java", "org.apache.poi.hssf.usermodel.DVConstraint");
dv.createFormulaListConstraint(...);
Java classes are organized into packages. In java classes, the full path to any referenced classes are imported at the top of the java code (not visible in the example you are using). In CF you need to use the full path in your createObject call. (Important: Paths are cAsE sEnsItIvE).
For example, instead of new HSSFWorkbook() use:
createObject("java", "org.apache.poi.hssf.usermodel.HSSFWorkbook");
If you are not sure of the path, just do a search on "POI TheClassName". Odds are the first result will be the POI JavaDocs, which show the full path at the top of each page like this:
java.lang.Object
|---org.apache.poi.ss.util.CellRangeAddressList
Unlike CF, java is strongly typed, which means you must declare a variable's type as well as it's name. For example, this line declares a variable row as type HSSFRow
HSSFRow row = hidden.createRow(i);
Since CF is typeless, it does not require a type. So running that same code in cfscript will cause the cryptic error "function keyword is missing...". The solution is to drop the variable type and just do a straight variable assignment:
row = hidden.createRow(i);
Java array indexes start at zero (0), while CF starts at one (1), so you need to fix the indexes in your for loop:
for (i = 1; i <= arrayLen(countryName); i++)
Java uses + to concatenate strings, whereas CF uses &. So you need to change the operator here "hidden!A1:A" + countryName.length. Otherwise CF will think you are trying to add two numbers, which will obviously throw an error because the first part is a string.
Assuming no version conflicts, the java example should work after you make those those adjustments.
"The setSheetHidden method was not found ..."
Just use Javacast function for boolean arguments:
workbook.setSheetHidden(1, javacast("boolean",true));

Coldfusion Proxy confusion

I am using a proxy/delegate pattern in a coldfusion component, and am getting unexpected results (from my point of view). Below is my proxy component - its pretty straight forward, I just init the CFC with the actual component I want to delegate to, and then map the named functions from that CFC through to a proxy function (the below is simplified for this example)
I have created a proxy component as follows:
component output="false"{
/** Constructor for proxy - requires an instance of myFusebox **/
public MyFuseboxProxy function init( Required any myFb ){
variables.myFusebox = arguments.myFb;
return this;
}
this.do = variables.proxy;
this.getApplication = variables.proxy;
this.getApplicationData = variables.proxy;
private any function proxy(){
var local.functionName = getFunctionCalledName();
var local.function = variables.myFusebox[local.functionName];
var local.returnVal = local.function( argumentCollection=arguments );
return local.returnVal;
}
}
From my application I call the following code:
variables.myFusebox = new ab.MyFuseboxProxy( variables.myFusebox );
variables.myFusebox.getApplicationData().startTime = now();
Now, in the above scenario, I would expect my proxy component to map the getApplicationData() function straight through to the original myFusebox component (via my proxy() function).
That function in the underlying component is as follows:
<cffunction name="getApplicationData" returntype="struct" access="public" output="false"
hint="I am a convenience method to return a reference to the application data cache.">
<cfreturn getApplication().getApplicationData() />
</cffunction>
That proxy all works fine, however, once I am in the above function in the original myFusebox I get the following error:
Message: Variable GETAPPLICATION is undefined.
StackTrace: coldfusion.runtime.UndefinedVariableException: Variable GETAPPLICATION is undefined.
And if I dump "this" inside that function, it actually dumps my proxy object.
Can anyone explain this or what I have done wrong? I was expecting that once the function call was inside the underlying object, it would just use its own context from there (my proxy just being a pass through really to the delegate)
I think this is the key point:
I was expecting that once the function call was inside the underlying
object
You've got this:
private any function proxy(){
var local.functionName = getFunctionCalledName();
var local.function = variables.myFusebox[local.functionName];
var local.returnVal = local.function( argumentCollection=arguments );
return local.returnVal;
}
When you do this bit:
var local.function = variables.myFusebox[local.functionName];
you are effectively pulling the function referenced by local.functionName out of variables.myFusebox, and putting it into the current function, within the context of your MyFuseboxProxy instance.
So when you do this:
var local.returnVal = local.function( argumentCollection=arguments );
You are not running variables.myFusebox[local.functionName]() (so in the context of variables.myFusebox), but you are running local.function() (so in the context of your proxy object).
I don't have the patience to try to follow your logic here, but I am still surprised you get that error. I would have expected this to happen:
local.function (a reference to getApplicationData from variables.myFusebox) runs getApplication().
getApplication() in the context of the MyFuseboxProxy instance should be a reference to variables.proxy().
variables.proxy() resolves the proxied function as getApplication(), and pulls that out of variables.myFusebox, and runs it in the context of your MyFuseboxProxy instance.
You do not include the code of the getApplication() function from variables.myFusebox, so I dunno what would happen next, but this is not what you want to be happening.
Anyway, the crux is - I think - that instead of running the functions inside variables.myFusebox, you're running them in your MyFuseboxProxy instance instead. If you want to do this sort of proxying (and ignoring for a moment you have invoke() specifically for doing this), you need to still call the function in its original context, not reference it in some new context.
I guess you're doing all this horsing around because ColdFusion doesn't like this syntax:
someObject[someMethodName]()
It baulks at the []() notation. However the solution is not this:
someOutOfContextReference = someObject[someMethodName]
result = someOutOfContextReference()
It's this:
someObject.someInContextReference = someObject[someMethodName]
result = someObject.someInContextReference()
See the subtle difference?
ColdFusion functions are not intrinsically closures, which is what you'd need them to be to work the way you want.

CF10 Twitter4j lookupUsers method was not found or method is overloaded

I am using CFML and Twitter4j to return timelines and lists.
I want to return the data from a call to lookupUsers(java.lang.String[] screenNames)
via Twitter4j.
I have tried the :-
strList = createObject("java", "java.util.ArrayList");
strList.add(strOriginUser);
originUser = t4j.lookupUsers(strList);
And :-
strUserString = JavaCast("String", strOriginUser);
originUser = t4j.lookupUsers(strUserString);
I know the t4j Object is working as I already use it to get timelines etc but here it is for completeness :-
public function init_twitter() {
//CONFIGURE twitter4j
configBuilder = createObject("java", "twitter4j.conf.ConfigurationBuilder");
configBuilder.setOAuthConsumerKey(#application.twitter_consumer_key#);
configBuilder.setOAuthConsumerSecret(#application.twitter_consumer_secret#);
configBuilder.setOAuthAccessToken(#application.twitter_access_token#);
configBuilder.setOAuthAccessTokenSecret(#application.twitter_access_token_secret#);
configBuilder.setIncludeEntitiesEnabled(true);
configBuilder.setJSONStoreEnabled(true);
config = configBuilder.build();
twitterFactory = createObject("java", "twitter4j.TwitterFactory").init(config);
variables.t4j = twitterFactory.getInstance();
return this;
}
The twitter4j documentations is:-
ResponseList<User> lookupUsers(java.lang.String[] screenNames) throws TwitterException
Return up to 100 users worth of extended information, specified by either ID, screen name, or combination of the two. The author's most recent status (if the authenticating user has permission) will be returned inline.
This method calls http://api.twitter.com/1.1/users/lookup.json
Parameters:
screenNames - Specifies the screen names of the users to return.
Returns:
users
It looks like you are trying to pass an ArrayList object into lookupUsers but that method only accepts String[] (an array of Strings) as an argument. So unless CFML does the conversion, I don't think it's going to work.
From a cursory glance at the ColdFusion docs, it looks like CFML can implicitly convert a CFML Array to a Java array, so perhaps the following would work:
screenNames = arrayNew(1);
screenNames[1] = 'Fry';
originUser = t4j.lookupUsers(screenNames);
Alternatively, if you want to keep on using a list there is an ArrayList#toArray(T[]) which could be useful, although I can't say how useful that would be in the CFML.
N.B. Please excuse my CFML code snippet.

How do I test rendered views when using T4MVC with TestHelper?

How do I test which view was rendered from a controller action if what I get is a T4MVC_ActionResult? Under normal circumstances I should be able to directly use TestHelper's methods, like in the examples:
pooController.Details().AssertViewRendered().ForView("Details")
...but, since through T4MVC I get a T4MVC_ActionResult instead of a ViewResult, the part AssertViewRendered<>().ForView("Details") fails. What alternative do I have if I want to test which view was invoked?
UPDATE:
Here's the test code:
[TestMethod]
public void Theme_Controller_Details_Action_Returns_Details_View()
{
var builder = new TestControllerBuilder();
var mockThemeRepository = new Mock<IThemeRepository>();
var themeController = builder.CreateController<Evalgrid.Website.Controllers.ThemeController>(mockThemeRepository.Object);
builder.InitializeController(themeController);
var result = themeController.Details();
result.AssertViewRendered().ForView("Details");
}
I used the debugger setting a breakpoint after the result line, and its variable type is T4MVC_ActionResult, while themeController is Evalgrid.Website.controllers.ThemeController. Note that I have used the fully qualified name of the controller.
I get this:
Expected result to be of type
ViewResult. It is actually of type
T4MVC_ActionResult.
I don't know what's going on.
Actually, T4MVC should not make a difference here. If you directly instantiate your controller and call an action method, you'll get the same thing back whether you use T4MVC or not. i.e. you won't get a T4MVC_ActionResult.
It's only when you write MVC.Foo.Details() that you'll get a T4MVC_ActionResult. That's because MVC.Foo returns an instance of a derived class which does special thing, and not directly your controller class.
Does that make sense?
Update: I'm confused, as looking at the sources for TestControllerBuilder.CreateController, it has:
public T CreateController<T>(params object[] constructorArgs) where T : Controller
{
var controller = (Controller)Activator.CreateInstance(typeof(T), constructorArgs);
InitializeController(controller);
return controller as T;
}
So it's directly instantiating the type that you pass in, which should just call your normal action.
One question about your code: does your Details action method take any parameters? If so, that would explain the problem, as you're calling it with no params, which would be a T4MVC method added in the partial class.