Apple Wallet Pass using ColdFusion and Java - coldfusion

I'm creating an Apple Wallet Pass using a combination of ColdFusion and Java. I have a cfc that creates the pass. It utilizes Java to sign and compressed the pass. I'm using these projects on GitHub https://github.com/dawesi/cfwheels-passkit and https://github.com/drallgood/jpasskit
Here I'm trying to create a pass:
<cfset mypass = new passkit()>
<cfset result = mypass.createPassbook()>
The passkit.cfc
<cfcomponent displayname="Apple Passbook Plugin" output="false">
<cffunction name="init" output="false">
<cfscript>
this.version = "1.1.8";
return this;
</cfscript>
</cffunction>
<cffunction name="createPassbook" returntype="any" output="false">
<cfargument name="type" type="string" default="generic"/>
<cfscript>
var loc = {};
loc.returnValue = createObject('component','PassBook').init(arguments.type);
return loc.returnValue;
</cfscript>
</cffunction>
The PassBook.cfc
<cfcomponent displayname="Apple Passbook" output="false">
<cffunction name="init" output="false">
<cfargument name="type" type="string" default="generic"/>
<cfscript>
setType(arguments.type);
return this;
</cfscript>
</cffunction>
<cffunction name="build" returntype="any" output="false">
<cfargument name="file" type="string" required="false"/>
<cfargument name="password" type="string" default=""/>
<cfscript>
var loc = {};
//Set things on the passbook at the end
if(structKeyExists(variables,'barcode'))
$getPassBook().setBarcode(variables.barcode);
//Get the correct type that they set it too
switch(variables.type){
case 'boardingpass':
loc.passClass = 'PKBoardingPass';
loc.passMethod = 'setBoardingPass';
break;
case 'coupon':
loc.passClass = 'PKCoupon';
loc.passMethod = 'setCoupon';
break;
case 'eventticket':
loc.passClass = 'PKEventTicket';
loc.passMethod = 'setEventTicket';
break;
case 'storecard':
loc.passClass = 'PKStoreCard';
loc.passMethod = 'setStoreCard';
break;
default:
loc.passClass = 'PKGenericPass';
loc.passMethod = 'setGeneric';
break;
}
//Create it, and add the fields
loc.pass = $createJavaObject('de.brendamour.jpasskit.passes.#loc.passClass#');
if(structKeyExists(variables,'fields')){
for(loc.type in variables.fields){
loc.method = variables.fields[loc.type].method;
loc.fields = variables.fields[loc.type].fields;
//loc.pass[loc.method](loc.fields);
loc.dynamicMethod = loc.pass[loc.method]; // Get method
loc.dynamicMethod(loc.fields); // Invoke it
}
//$getPassBook()[loc.passMethod](loc.pass);
loc.dynamicMethod2 = $getPassBook()[loc.passMethod];
loc.dynamicMethod2(loc.pass);
//Sign and make the archive
loc.signingUtil = $createJavaObject('de.brendamour.jpasskit.signing.PKSigningUtil');
loc.signingInfo = loc.signingUtil.loadSigningInformationFromPKCS12FileAndIntermediateCertificateFile(
$passBookCertificateLocation(),
arguments.password,
$intermediateCertificateLocation()
);
loc.bytes = loc.signingUtil.createSignedAndZippedPkPassArchive(
$getPassBook(),
variables.templatePath,
loc.signingInfo
);
//See if we are writing this to a file
if(structKeyExists(arguments,'file') && len(arguments.file)){
loc.file = $createJavaObject('java.io.FileOutputStream').init(arguments.file);
loc.file.write(loc.bytes);
loc.file.close();
}
return loc.bytes;
</cfscript>
</cffunction>
</cfcomponent>
But I'm running into this bug. I think it's missing something. Can anyone help?
Invalid CFML construct found on line 354 at column 42.
ColdFusion was looking at the following text:
loc.method
The CFML compiler was processing:
A script statement beginning with loc.pass on line 354, column 33.
A script statement beginning with { on line 351, column 58.
A script statement beginning with for on line 351, column 25.
A script statement beginning with { on line 350, column 56.
A script statement beginning with if on line 350, column 17.
A cfscript tag beginning on line 317, column 10.
A cfscript tag beginning on line 317, column 10.
The error occurred in /Applications/ColdFusion2016/cfusion/wwwroot/passkit/PassBook.cfc: line 354
Called from /Applications/ColdFusion2016/cfusion/wwwroot/passkit/passkit.cfc: line 14
Called from /Applications/ColdFusion2016/cfusion/wwwroot/passkit/test_walletpass.cfm: line 7
352 : loc.method = variables.fields[loc.type].method;
353 : loc.fields = variables.fields[loc.type].fields;
354 : loc.pass[loc.method](loc.fields);
355 : }

If it's not working with the latest CF, it might contain syntax specific to another engine, like Lucee. I didn't review the whole thing, but I think this is the line it's complaining about:
loc.pass[loc.method](loc.fields);
AFAIK, Adobe's ColdFusion doesn't support that kind of dynamic method call. This thread describes a hack that might work around the limitation. Basically, split it into two actions. Store the method reference in a variable. Then invoke the method on that variable. Example:
loc.dynamicMethod = loc.pass[loc.method]; // Get method
loc.dynamicMethod(loc.fields); // Invoke it
// ... same issue a few lines down
//$getPassBook()[loc.passMethod](loc.pass);
loc.dynamicMethod = $getPassBook()[loc.passMethod];
loc.dynamicMethod(loc.pass);
Do note the important caveat
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.
Not having used the component, can't say whether or not it's relevant in this scenario.

Related

queryEach or .each() not Working at CF 11, Why?

I was seaching in google better form to interate over "query" in coldfusion, since im new in the company im working, and im trying to get more from CF
Here my attempts:
My models:
<cffunction hint="Foo" name="Foo" access="public" returntype="query">
<!--- Argumentos --->
<cfargument hint="Something" name="ArgComBus" type="string" required="no" default="">
<cfargument hint="Other thing" name="ArgPar" type="string" required="no" default="">
<cfscript>
queryService = new Query();
queryService.setSql("
SELECT
column1,
column2,
FROM
tab_bar
WHERE
1=1
#arguments.ArgComBus#
");
queryService.setDataSource(session.Dsn);
if(Len(Trim(arguments.ArgPar))){
Evaluate(arguments.ArgPar);
}
queryResult = queryService.execute();
qBus = queryResult.getResult();
</cfscript>
<cfreturn qBus>
</cffunction>
My script
<cfscript>
arrFoo = arrayNew(1);
qFoo = this.Foo(
ArgComBus = " AND column1 = #variables.bar# ");
// First Attempt - The each method was not found.
qFoo.each(function (foo) {
arrFoo.append(foo);
});
// Second Attempt - Variable QUERYEACH is undefined.
queryEach(qFoo, function (foo) {
arrFoo.append(foo);
});
writeDump(arrFoo);
</cfscript>
My Server Dump
InstallKit Native Windows
appserver Tomcat
productlevel Developer
productname ColdFusion Server
productversion 11,0,05,293506
rootdir C:\CFusion11\cfusion
I even used getMetaData() on my query variable qFoo and that return that is array... so when i tried use something like that (trying to convert array in query?)
cfQuery = createObject("java", "coldfusion.sql.QueryTable").init(qFoo);
.each() and queryEach() same answer... i even tried use arrayEach() but return the object is coldfusion.sql.QueryTable and not array
You are running ColdFusion 11.
The queryEach() function was not added until ColdFusion 2016:
reference 1 (from cfdocs)
reference 2 (from Adobe docs)
Originally I had posted that the each() member function was not available in Adobe ColdFusion 11. Aquitaine pointed out in the comments that it actually is. I incorrectly referenced the Each() function for Lucee that works with collections. The Each() function related to this question is actually the script version of the ArrayEach() tag function. Which is available in ColdFusion 11 (it was actually added in ColdFusion 10). Sorry for the confusion.
The documentation may be wrong. I could not get the function to work as Each() except under ColdFusion 2018. For ColdFusion 11 I could only get it to work as ArrayEach().
reference 3 (from cfdocs)
reference 4 (from Adobe docs)
Here are a couple of examples on how to loop over a query in ColdFusion 11 (borrowed from cfdocs):
// Define our query
platform = ["Adobe ColdFusion", "Railo", "Lucee"];
myQuery = queryNew(" ");
queryAddColumn(myQuery, "platform", "CF_SQL_VARCHAR", platform);
// By row index
for (i = 1; i <= myQuery.recordCount; i++) {
writeOutput("<li>#myQuery["platform"][i]#</li>");
}
// By query
for (row in myQuery) {
writeOutput("<li>#row.platform#</li>");
}
// By arrayeach
writeOutput("<h3>By arrayeach:</h3>");
function printArray(vendor, index)
{
writeOutput("<li>#vendor#</li>");
}
arrayEach(platform,printArray);
I created a gist for you on TryCF.com so you can see this code in action and play around with it if you like. Just click here to run the code.

How do I call a second function within the same CFC in the APPLICATION scope?

I am using ColdFusion 9.0.1.
Let me start by stating that I may not be asking the right question. Since each function works independently and fails only when one function calls another, I am thinking that the problem is in how the function is called.
I am creating an application variable that contains a structure. The structure contains the reference to an object, orders.cfc.
if (not isDefined("APPLICATION.AppInfo") or not isStruct(APPLICATION.AppInfo)) {
APPLICATION.AppInfo = structNew();
APPLICATION.AppInfo.objOrders = createObject("component", "globaladmin.orders");
}
I am able to successfully access the methods in the orders.cfc like this:
OrderItemList = APPLICATION.AppInfo.objOrders.orderItemList(URL.Customer);
I have methods in the orders.cfc that call other methods in the order.cfc, kind of like this (faked for simplicity):
<cffunction name="orderItemList">
<cfscript>
LOCAL.RandomNumber = getRandomNumber();
return LOCAL.RandomNumber;
</cfscript>
</cffunction>
<cffunction name="getRandomNumber">
<cfscript>
LOCAL.SomeNumber= randRange(0,10);
return LOCAL.SomeNumber;
</cfscript>
</cffunction>
I get this error:
Entity has incorrect type for being called as a function. The symbol you provided getRandomNumber is not the name of a function.
I figured maybe I can't reference a function within the same CFC without creating an object first, so I do this:
<cffunction name="orderItemList">
<cfscript>
LOCAL.RandomNumber = APPLICATION.AppInfo.objOrders.getRandomNumber();
return LOCAL.RandomNumber;
</cfscript>
</cffunction>
Then, I'd get this error:
Either there are no methods with the specified method name and argument types, or the method getRandomNumber is overloaded with arguments types that ColdFusion can't decipher reliably. If this is a Java object and you verified that the method exists, you may need to use the javacast function to reduce ambiguity.
How should I call a second function within the same CFC?
The first thing I would try is var scoping your all your variables within your functions:
<cffunction name="orderItemList">
<cfscript>
var RandomNumber = getRandomNumber();
return RandomNumber;
</cfscript>
</cffunction>
<cffunction name="getRandomNumber">
<cfscript>
var SomeNumber= randRange(0,10);
return SomeNumber;
</cfscript>
</cffunction>
If that doesn't solve the problem, let me know and we can explore further.
edit
Okay, now that the local scope issue is resolved, try this:
<cffunction name="orderItemList">
<cfscript>
LOCAL.RandomNumber = THIS.getRandomNumber();
return LOCAL.RandomNumber;
</cfscript>
</cffunction>
<cffunction name="getRandomNumber">
<cfscript>
LOCAL.SomeNumber= randRange(0,10);
return LOCAL.SomeNumber;
</cfscript>
</cffunction>

Component Level Properties In Application.cfc

If I have component level properties in my application.cfc will they get evaluated every time a page is it or will they only get evaluated when the application is created?
<cfcomponent>
<cfscript>
this.name = "WARM2_Demo";
this.applicationTimeout = CreateTimeSpan(1,0,0,0);
this.setClientCookies = true;
this.setDomainCookies = false;
this.sessionManagement = true;
this.sessionTimeout = CreateTimeSpan(0,0,30,0);
this.clientManagement = false;
this.scriptProtect = true;
this.appDirectory = getDirectoryFromPath(getCurrentTemplatePath());
this.fileSeparator = createObject("java","java.lang.System").getProperty("file.separator");
....
</cfscript>
<cffunction name="OnApplicationStart" returntype="boolean">
<cfscript>
setupApplication();
return true;
</cfscript>
</cffunction>
....
</cfcomponent>
The pseudo-constructor of Application.cfc is executed every request.
Basically behind the scenes an instance of Application.cfc is created every request, and that instantiation behaves just like any other CFC instantiation: the pseudo-constructor bit - the stuff inside the CFCOMPONENT tags but outwith any CFFUNCTION / function declarations - is run.
After the pseudo-constructor is run, any appropriate event handler / interceptor methods are run, eg: onRequestStart().
NB: you could test this very easily yourself by just outputting something in there. It'll show up on the screen on every request (make it a getTickCount() or createUuid() call so you can see it changing).
evaluated every time a page is requested, I think.
stick them in Application scope in onApplicationStart()
but honestly, fileSeparator?? Just use /, it'll work on Windows and *nix OS just fine. :)

What's the best way to unit test a cfc that uses a Java object for a lot of its functionality?

I have a cfc that relies heavily on a Java object (created via JavaLoader) for a lot of its core functionality that I'd like to write some tests for and I'm not sure what the best way to do this is. Here is an example of a method I'd like to write a test for with instance.note being a java object.
<cffunction name="getNotes" returntype="Array" access="public" output="false" hint="I return a list of a users notebooks" >
<cfargument name="maxCount" type="numeric" required="false" default="9999" hint="The maximum number of notes to get" />
<cfscript>
if(arguments.maxCount)
return instance.note.listNotes(maxCount);
else
return instance.note.listNotes();
</cfscript>
</cffunction>
One thing I thought of doing is creating a stub CFC that has the same method names and similar return values and then mocking that stub and injecting it?
Can't you just write meaningful assertions on the result, i.e. on the array of notes? Looking at that code, the only things I'd test are a) when you pass a maxCount, does your resultant array honor that size? b) without maxCount, is the list of notes the length that you'd expect? Because that's all your code does. I'd test your code, not the code of the underlying java object.
When we needed to unit test CF functions that relied upon Java objects (which we did a LOT of), we used Mockito to mock the Java objects.
So, hoping this code snippet makes sense, its been almost a year since I've done this:
<cfcomponent displayname="TestWhatever" extends="mxunit.framework.TestCase" output="false">
<cffunction name="setUp" access="public" returntype="void">
<cfscript>
// named it mk for keeping it short
variables.mk = createObject("java","org.mockito.Mockito");
//Create the mock object
variables.mockNote = mk.mock(createObject("java","com.company.whatever.note").getClass());
// Mock Data
fullList = {whatever listNotes() returns}
partialList3 = {whatever listNotes(3) returns}
//some common mocking
mk.when(variables.mockNote.listNotes()).thenReturn(fullList);
mk.when(variables.mockNote.listNotes(mk.eq(3))).thenReturn(partialList3);
mk.when(variables.rootOrgObj.guid()).thenReturn("root");
// Assign the mock object to where your CFC expects it.
instance.note = variables.mockNote
</cfscript>
</cffunction>
</cfcomponent>
Having said that, if your sample function is real, there's really no point in unit testing it. Its simply not doing anything but being a proxy to the java object. There is no significant logic in there to test.
Since you have a default on the cfargument, it will always exist (again, I think so, its been a year since I've done CF), so your guard statement isn't even required - the first code path will always be called, with the passed maxCount if specified, or with 9999 if not.
I took Edward's answer and implemented it like so:
I used the JavaLoader library to create my mockito object.
variables.cfEvernote = "";
variables.classLoader = createObject("component", "resources.JavaLoader").
init(["#expandPath('../lib/mockito-all-1.8.5.jar')#",
"#expandPath('../lib/CFEvernote.jar')#",
"#expandPath('../lib/libthrift.jar')#",
"#expandPath('../lib/evernote-api-1.18.jar')#"]);
variables.mockito = variables.classLoader.create("org.mockito.Mockito").init();
Then in the setup method of my munit test I create my new mock java object:
<cffunction name="setUp" access="public" output="false" returntype="void">
<cfscript>
variables.cfEvernote = createObject("component","com.714studios.cfevernote.CFEvernote").
Init(variables.configArray[1],variables.configArray[2],
"sandbox.evernote.com",
"http://localhost/cfevernote/callback.cfm"
"#ExpandPath('../lib')#");
variables.mockCFEvernote = variables.mockito.mock(variables.classLoader.create("com.sudios714.cfevernote.CFEvernote").
Init("123","S1","232","sandbox.evernote.com","mock").getClass());
variables.cfEvernote.setCFEvernote(mockCFEvernote);
</cfscript>
Then in my tests I create my mock behavior like so.
<cffunction name="test..." returntype="void" access="public" output="false" >
<cfscript>
var notebooks = "";
var expected = 12;
var i = 0;
var retArray = createObject("Java","java.util.ArrayList");
var actual = "";
for(i = 1; i lte 12; i = i + 1){
retArray.Add("");
}
variables.mockito.when(mockCFEvernote.listNotebooks(12)).thenReturn(retArray);
notebooks = variables.cfEvernote.getNotebooks(12);
actual = arrayLen(notebooks);
assertEquals(expected,actual);
</cfscript>
I've also blogged about it in a bit more detail here - http://blog.bittersweetryan.com/2011/07/unit-testing-coldfusion-components-that.html.

Downloading large file in ColdFusion using CFHTTP

I'm trying to download a large (600MB) binary file to the server using CFHTTP on Coldfusion 8:
<cfhttp
method="get"
url="#fileURL#"
path="#filePath#"
file="#fileName#"
timeout="600"
getasbinary="auto"
>
It's working fine for smaller files (100something MB) but for the large one's I'm getting the Server 500 error: "java.lang.OutOfMemoryError: Java heap space".
The file is being downloaded from a Dropbox folder - so only available option is to use HTTP GET.
Does anyone have idea how to download it, so it wouldn't kill the server or timeout?
You can do this by calling Java from CF code. The buffered input and output stream classees are designed to hold onto chunks of data, rather than the whole thing, avoiding OutOfMemory errors.
getByteArray() is a helper method because there's not way to declare something like byte buf[]=new byte[1024]; in CF directly.
In the example change the source and destination variables.
Example
<cfset source = 'protocol://domain/path/to/file.ext'>
<cfset destination = getDirectoryFromPath(getCurrentTemplatePath()) & listlast(source, "/")>
<cffunction name="getByteArray" access="private" returnType="binary" output="no">
<cfargument name="size" type="numeric" required="true"/>
<cfset var emptyByteArray =
createObject("java", "java.io.ByteArrayOutputStream").init().toByteArray()/>
<cfset var byteClass = emptyByteArray.getClass().getComponentType()/>
<cfset var byteArray =
createObject("java","java.lang.reflect.Array").newInstance(byteClass, arguments.size)/>
<cfreturn byteArray/>
</cffunction>
<cfscript>
uri = createObject("java", "java.net.URL").init(source);
uis = uri.openStream();
bis = createObject("java", "java.io.BufferedInputStream").init(uis);
fos = createObject("java", "java.io.FileOutputStream").init(destination);
bos = createObject("java", "java.io.BufferedOutputStream").init(fos);
buffer = getByteArray(1024);
len = bis.read(buffer);
while(len > 0) {
bos.write(buffer,0,len);
len = bis.read(buffer);
}
bos.close();
bis.close();
fos.close();
uis.close();
</cfscript>
The problem is is that it's too large. ColdFusion reads the entire thing into memory before writing it to disk.
You'll be better off using some other method to get the file. wget can do http requests from a command-line. That with judicious use of CFEXECUTE is probably a good way to go.