Quantcast
Channel: Adam Cameron's Dev Blog
Viewing all 1333 articles
Browse latest View live

Ye Olde XMLSearche Bugge

$
0
0
G'day:
Henry mentioned this last night:

That sounded like something I could get my teeth into, so had a look at it this morning.



The gist of it is that xmlSearch() is "unreliable" when it's called simultaneously more than once on the same XML doc; for example if the doc is in the application scope, or it's being hit with <cfthread> or something.

There's a bunch of stuff written about it, but I could not find a complete, stand-alone repro case for it anywhere. So I've written one. It was easy enough.

Firstly I build some XML to work with:

<cfparam name="URL.seed" type="integer" default="0">
<cfset id = 0>
<cfoutput>
<cfxml variable="x">
<aaa id="#++id#">
<cfloop index="i" from="1" to="#URL.seed#">
<bbb id="#++id#">
<cfloop index="i" from="1" to="#URL.seed\2#">
<ccc id="#++id#">
<cfloop index="i" from="1" to="#URL.seed\4#">
<ddd id="#++id#" />
</cfloop>
</ccc>
</cfloop>
</bbb>
</cfloop>
</aaa>
</cfxml>
</cfoutput>
<cffile action="write" file="#expandPath('./text.xml')#" output="#x#">

I run this with a seed param of 20, which gives me 1000-odd nodes.

Next I have some code that searches for a specific ID in the XML, and call that from within some threaded code:

param name="URL.threads" type="integer" default=1;

request.x = xmlParse(expandPath('./test.xml'));
nodes = xmlSearch(request.x, "//ddd");
maxId = nodes[arrayLen(nodes)].xmlAttributes["id"];

request.results = [];
threads = [];
for (i=1; i <=URL.threads; i++){
threadName = "t#i#";
arrayAppend(threads, threadName);
thread name="#threadName#" action="run" max="#maxId#" {
for (i=1; i <=attributes.max; i++){
try {
xpath = '//*[@id="#i#"]';
result = xmlSearch(request.x, xpath);
arrayAppend(request.results, {
thread =thread.name,
index = i,
resultID= result[1].xmlAttributes["id"]
});
}
catch (any e){
writeLog(file="xmlSearchBug", text="#thread.name# #i# #xpath# #e.type# #e.message# #e.detail#");
abort;
}
}
}
}
thread action="join" name="#arrayToList(threads)#";
writeOutput("Processing complete<br>");
writeOutput("Validating&hellip;<br>");

if (arrayLen(request.results) != URL.threads * maxId){
writeOutput("Processing did not complete<br>");
}


for (result in request.results){
if (result.index != result.resultId){
writeDump(result);
break;
}
}
writeOutput("Validation complete<br>");

This code basically just pounds the XML looking for each ID, and "logs" where it found it. This runs A-OK if I run it with just a single thread. But if I run it using multiple threads (and I mean even "2") it quickly craps out with:

"Information","cfthread-1","04/09/14","13:43:22",,"T1 1 //*[@id=""1""] Expression Unable to process the result of the XMLSearch for ''. ColdFusion is unable to process the result of the XPath search. You may have an undefined variable in the xpath expression."
"Information","cfthread-0","04/09/14","13:43:22",,"T2 2 //*[@id=""2""] Expression Unable to process the result of the XMLSearch for ''. ColdFusion is unable to process the result of the XPath search. You may have an undefined variable in the xpath expression."

Note that CF is trying to xmlSearch() for "", whereas I had just told it to search for '//*[@id="1"]'.

In the application.log file we have this:

"Error","cfthread-1","04/09/14","13:43:22",,"T1: null"
"Error","cfthread-0","04/09/14","13:43:22",,"T2: null"

So... yeah, there's something amiss there. There's no such problem on Railo, FWIW.

Henry raised a bug for this: 3739102, but it seems to have vanished. If/when it comes back, I'll add this repro case.

Anyway, that's all I have to say on that. Time to east some lunch.

--
Adam

Oh, hohoho. You got me, CFHour

$
0
0
G'day:
In slightly cliched fashion, I shall continue the overuse of this (mis)quote:
The reports of [CFHour's] death are greatly exaggerated.
It seems it was all a joke (not CFHour, I mean the announcement of their discontinuation), as they released another installment just now. I've not listened to it yet.

Yeah, so ignore this: "CF(Hour) is dead :-(".

Righto.

--
Adam

ColdFusion 11 release date confirmed to be no later than...

$
0
0
G'day:
This is the first "official" statement from Adobe I've heard about ColdFusion 11's release date. I was watching Ray's presentation that he gave to the Salt Lake City UG: "Recording and demos from my ColdFusion 11 presentation", and he let a tentative timeframe slip...

... he confirmed it would be "before CF.Objective()". And made a point of saying it would not be released at CF.Objective(), but before it. CF.Objective() is on May 13-16.

So that means ColdFusion 11 will be release within the next month, for all intents and purposes. ColdFusionMX 7, ColdFusion 8 and ColdFusion 9 were all released on a Monday; ColdFusion 10 on a Tuesday. So I'm gonna pick Mon May 12, 2014 as the release date for ColdFusion 11.

On one hand that's good news. On the other hand there's still a huge number of bugs that haven't been even looked at yet, so... I'm pretty disappointed they're not giving it a second round of beta. Well: I guess the released version will be treating the public as continued beta testers. It also means all the outstanding bugs will just get closed as "deferred, not enough time", and we won't see any potential action on them until I guess 2016.

I am still checking bits and pieces of the current beta, and every time I look at something, I find a bug. Not generally hugely significant things, but equally I'm not actually looking for bugs either, they're just sitting there ready to find. There is no way I'd advocate anyone use it until 11.0.1 at the very least.

BTW: I learned a few good things about CF11 from Ray's presentation, so it's worth a watch.

Cheers.

--
Adam

Railo 4.2.0.007 is out: more new iteration methods

$
0
0
G'day:
Micha posted on the Railo Google Group the other day "(Last) Railo beta release (4.2.0.007)". You can get this via the in-admin updater if you're set to use the "Development releases (Bleeding Edge)" update channel.

Staying true to form, the upgrade process is seamless, and it also offers a bunch of excellent new stuff, as well as bug fixes. This is definitely the way CFML should be heading... not waiting around two years between features, as we have been with ColdFusion.

There's a swag of new stuff, but I'm just gonna focus on the added iteration methods: for queries and lists.

ColdFusion has had some list iteration functions since ColdFusion 10, and adds more in ColdFusion 11. I discuss them in "ColdFusion 11: .map() and .reduce()". Railo hadn't implemented them, I think because they - somewhat dogmatically - don't consider lists to be a "type", therefore only consider string functions to be appropriate for strings. This is slightly specious IMO because Railo does have all the other list functions, after all. Anyway, they've added them now.

Here's example usage of the list-iteration member functions: .each(), .filter(), .every(), .some(), .map(), .reduce(). They have not implemented a list-sorting iteration function, for some reason. This code is also on GitHub: list.cfm.

rainbow = "whero,karaka,kowhai,kakariki,kikorangi,tawatawa,mawhero"

"first,second".each(function(){
dump(arguments)
})

rainbow.each(function(element,index,list){
echo("#index#/#listLen(list)#: #element#<br>")
})

Output:
Scope Arguments
11
stringfirst
22
number1
33
stringfirst,second
Scope Arguments
11
stringsecond
22
number2
33
stringfirst,second

1/7: whero
2/7: karaka
3/7: kowhai
4/7: kakariki
5/7: kikorangi
6/7: tawatawa
7/7: mawhero


There's a few things to note here:

  • Note how one can call a method directly on a string literal here? Cool.
  • Micha said he'd not implemented the list iteration functions as methods, but it seems he has.
  • And all the list iteration callback functions receive three arguments: the current element value, the index of the value in the list, and the list itself. There's a slight bug here in that they really need to pass the list delimiter into the callback too: all list functions need to know the list delimiter; otherwise one can't really do anything with that list argument.

result = rainbow.filter(function(element,index){
return !element.startsWith("k")
})
echo("The colours not starting with k are: #result#<br>")

Output:
The colours not starting with k are: whero,tawatawa,mawhero

filter() is fairly straight forward, and works exactly how one would expect.

/*
result = rainbow.sort(function(e1,e2){
return sgn(e1.len() - e2.len())
})
echo("sorted colours: #result#<br>")
*/

.sort() (nor listSort()) have been implemented to have a callback-centric flavour. This is a strange omission. ColdFusion has implemented one either. Needless to say if there's a case of any iteration functions, there's a case for all of them.

longest = 0
result = rainbow.every(function(element,index){
echo("Checking: #element#<br>")
var length = element.len()
var longer = length >= longest
longest = max(longest,length)
return longer
})

echo("Every colour is at least as long as the previous: #result#<br>")

Output:
Checking: whero
Checking: karaka
Checking: kowhai
Checking: kakariki
Checking: kikorangi
Checking: tawatawa
Every colour is at least as long as the previous: false


Again, this is just demonstrating that the function works as per all the other every() implementations. I've not much to add.

result = rainbow.some(function(element,index){
echo("Checking: #element#; ")
var vowels = element.reReplaceNoCase("[^aeiou]", "", "all")
var vowelsLen = vowels.len()
echo("#vowels#: #vowelsLen#; #vowelsLen >= 4#<br>")
return vowelsLen >= 4
})

echo("Some maori colours have at least four vowels in them: #result#<br>")

Output:
Checking: whero; eo: 2; false
Checking: karaka; aaa: 3; false
Checking: kowhai; oai: 3; false
Some maori colours have at least four vowels in them: true


I'm outputing some telemetry here, because I was verifying what seems to be a bug. As far as I can determine, I should also be seeing this:

kakariki; aaii: 4; true

But it's never showing up. So that looks like a bug to me?


newRainbow = listChangeDelims(rainbow, "|")
backwards = newRainbow.map(function(element,index){
return element.reverse()
}, "|")
dump(backwards)

Output:

stringorehw|akarak|iahwok|ikirakak|ignarokik|awatawat|orehwam

Here I'm just making sure that these functions can also take a delimiter. Even if it's not then passed through to the callback.


"first,second".reduce(function(){
dump(arguments)
return ""
},"")

tally = rainbow.reduce(function(prev,current){
return prev + current.len()
}, 0)
dump(tally)

Output:

Scope Arguments
11
string
22
stringfirst
33
number1
44
stringfirst,second
Scope Arguments
11
string
22
stringsecond
33
number2
44
stringfirst,second
number49

Just a reminder: the reduce() function also takes an optional starting value, and the callback doesn't receive the "usual suspects", it receives the starting value (or the previous iteration's result), the current element, its index, and the whole list.

Here my example just sums up the length of all the elements.

That's all cool. I was gonna go through the query iteration functions too, but I'm out of lunch time, so need to press "send". I'll raise some bugs and cross-ref them back here once I get a moment.

Bugs:

  • the callbacks need to receive the list delimiter: RAILO-3030;
  • listSome() premature exit: RAILO-3029;
  • I'd already raise the fact listSort() is missing: RAILO-2934.
--
Adam

Well that's nice

$
0
0
G'day:
I had a nice comment posted against one of my UDFs @ cflib.org today:

Sting (Guest):
Well, This is He, even if his functions make no sense, he adds them here, because he is administrator of this site..
This is in regards to the savecontent() UDF I submitted a few weeks ago ("How about this for savecontent?").


TBH, the UDF is of minimal merit: it's definitely a proof of concept thing and simply replicates existing functionality. Although I happen to think it's a better approach to doing a <cfsavecontent> operation in CFScript, compared to the options Railo and ColdFusion have offered.

The reason why I posted is threefold:

  • it's a reasonable demonstration of doing less-obvious things with inline function expressions, so might prompt people to think about them more;
  • it demonstrates how "closing tag" operations can be done in CFScript without needing special "block" syntax (which I deeply dislike);
  • CFLib is a bit traffic-slow these days, so I thought it might remind people it's there.
Now to "Sting":
  • if you're gonna have a go at someone, at least have the balls to use your real name;
  • I am indeed the "editor" of CFLib, in that I am the one that approves incoming UDFs. And I did approve this function. Had you written it, I'd've approved it too. have a look through some of the other functions on there: there's a lot of pointless cruft. I really don't think this is pointless cruft though;
  • go on then... where's your contribution to the community (btw, I know your IP address, and having looked it up I think I know you perhaps have contributed a lot to the "community")?
BTW, Sting... your comment was deleted, but not by me. I'm not the only admin on the site. If it was up to me, I'd've left it there as a testament to you.

Not a gripping blog article, but I am down the pub in Galway again - if you're watching my Untappd feed, you'll've noticed this - and I am amidst a more useful / on-topic one. And sampling lots of different beers. And, hey, it's Saturday.

-- 
Adam

Bugs in iterator functions in both Railo and ColdFusion

$
0
0
G'day:
I decided to "do my bit" for the cfbackport project, and am looking at implementing the new collection iteration functions for older versions of ColdFusion. I'm aiming for CMFX6.0 onwards, but am having to guess at some of the language restrictions as I'm on my back-up laptop and only have CF10 & 11 to test with.

Anyway, I decided to start with the list iteration functions, and quickly came across a bug in both Railo and ColdFusion with listFilter().

Here's my repro case:

original = "11,23;31:43^53-61";
delims = ",;:^-";

filtered = listFilter(original,function(v){
return reFind("3$", v);
},delims);

writeDump([original,filtered]);

On both Railo (4.2.0.007) and ColdFusion (11 beta) we get this:

Array
1
string11,23;31:43^53-61
2
string23,;:^-43,;:^-53

Guys?... no. You both seem to be forgetting that when passing the delimiters argument to a list function, it's a multi-char value, not a string. So one should not simply bung the entire string between each list element.

If we "fake" the filtering... just remove the list entries by hand:

viaDeletions = listDeleteAt(original, listFind(original, 11, delims), delims);
viaDeletions = listDeleteAt(viaDeletions, listFind(viaDeletions, 31, delims), delims);
viaDeletions = listDeleteAt(viaDeletions, listFind(viaDeletions, 61, delims), delims);

writeDump([original,viaDeletions]);

We get what we should expect here:

Array
1
string11,23;31:43^53-61
2
string23;43^53

The filter operation seems to be rebuilding the list, not simply conditionally removing elements from it.

So... to be clear... everyone should be aiming to end up the the dump I show immediately above this sentence. I'll raise a bug for both Railo and ColdFusion here (and cross ref here when done).

Next I had a look at how both platforms respected the idea of "empty elements". By default list operations completely ignore empty elements - which is wrong, and very poor language design on the part of Allaire - but since... what... CFMX7?... there's been an optional argument on list functions to respect empty elements. So the list iteration functions would have this argument too, right? Test code:

(btw... Jesus this is hard work. I'm at the pub sampling beers like they're going out of fashion, wrote a different blog article in the middle of doing this and chatting with Gavin on Google at the same time. My keystroke error rate is about 50%), Anyway, test code:

list = ",2,,4,,,7,,,,";
lengthNoEmpties = listLen(list);
lengthWithEmpties = listLen(list,",",true);

writeDump([list,lengthNoEmpties,lengthWithEmpties]);

writeOutput("<br>listFilter() with empties: ");
filtered = listFilter(list, function(v){
if (isNumeric(v)){
return v mod 2; // ie: odd numbers
}
return true;
}, ",", true);
writeDump([filtered]);

And results (Railo first):

Array
1
string,2,,4,,,7,,,,
2
number3
3
number11

listFilter() with empties:
Array
1
string,,,,7,,,,

So Railo gets it dead right.

ColdFusion:

The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request

Parameter validation error for the LISTFILTER function.

The function accepts 2 to 3 parameters.

That's not right. The list iteration functions need to - across the board - also expect both a "delimiters" and a "includeEmptyValues" argument.

OK. I surrender. My schoolboy error at the pub today was - whilst drinking 1/3 pints - focusing mostly on beers which an alcohol percentage of around 8-9%. So each third pint is like 2/3pint,  and that's forgiving the very real truth than alcohol percentage in beer does not affect a person in a linear fashion (whether that's fact or anecdotal remains to be proven). But I'm well and truly arseholed, so I am gonna give up and post banal interjections on Twitter instead.

--
Adam (hic)

#ColdFusion 11 is not ready for release in one month's time. Simple.

$
0
0
G'day:
This is a short adjunct, designed to encourage the Adobe ColdFusion Team to respond to their community. It'd be helpful if you could retweet it.

In my opinion, CF11 is not ready to release for one very good reason:

67 ColdFusion  bugs not even looked at yet? Yer having a fucking laugh, Rakshith.

I wrote a speculative article the other day: "ColdFusion 11 release date confirmed to be no later than...", which intuits that we're about four weeks shy of ColdFusion 11 coming out. I think this is borne out by an increase of bug closures with "can't be arsed... maybe in two years time" (I think it actually said "Closed/EnchancementRequired" or "Closed/NotEnoughTime").

However as far as I can tell, Adobe haven't even bothered to look at a whole bunch of the issues their paying customers have raised with them.

I really don't understand how Adobe can be so dismissive of their clients. I seriously can't see how this is even "minimum professional" behaviour, let alone appropriate behaviour when they're in one of their very rare development cycles.

In contrast, Railo has the "luxury" of being able to do continuous development, so continuous issue triage/resolution, and they very rarely leave an issue untriaged (and even unfixed!).

Adobe choose to only do development on ColdFusion once in a blue moon, so they really have to at least triage all the currently outstanding issues. And, if they were to have any sense of professional integrity, fix the bugs, implement the features, or explain why they don't.

Adobe have done a bunch of good stuff for ColdFusion 11, but they are way more than a month away from delivering a professional product. Part of being professional is listening to one's clients.

I would like Rakshith to respond to this article. And what's up with their bug-fixing...

--
Adam

Odd behaviour with struct keys with dots in their names

$
0
0
G'day:
I've been looking at this one due to an interesting question on Stack Overflow: "Confusion in dynamic variable access in ColdFusion". I could not immediately see what the problem was, and it's taken me a coupla hours to work out what's going on.

Here's some code that demonstrates the issue at hand:

key1 = "prefix.key1";
key2 = "prefix.key2";

st = {};
"st.#key1#" = "set via quoted variable name";
st[key2] = "set via associative array notation";
writeDump(st);

writeDump(var=[
{key1=isDefined("st.prefix.key1")},
{key2=isDefined("st.prefix.key2")}
], label="isDefined()");
writeDump(var=[
{prefix=structKeyExists(st, "prefix")},
{"prefix.key1"=structKeyExists(st, "prefix") && structKeyExists(st.prefix, "key1")},
{key1=structKeyExists(st, "prefix.key1")},
{key2=structKeyExists(st, "prefix.key2")}
], label="structKeyExists()");

safe(function(){
writeOutput("st.prefix.key1: #st.prefix.key1#<br>");
});

safe(function(){
writeOutput("st.prefix.key2: #st.prefix.key2#<br>");
});

safe(function(){
writeOutput("st['prefix.key2']: #st['prefix.key2']#<br>");
});

function safe(f){
try {
f();
}catch (any e){
writeOutput("#e.type# #e.message# #e.detail#<br>");
}
}

Here we set to values in a struct: one using erm... dunno what the syntax is call, but the syntax of using a quoted dynamic value as a dynamic variable name; and the other using associative array notation.

Then we output various odds 'n' sods like whether the variable is defined, whether various keys exist, and then the variables' values.

The safe() function is just there to de-clutter the code somewhat.

And this is the output...

ColdFusion 10 (and lower):

struct
prefix
struct
key1set via quoted variable name
prefix.key2undefined
isDefined() - array
1
isDefined() - struct
KEY1YES
2
isDefined() - struct
KEY2NO
structKeyExists() - array
1
structKeyExists() - struct
PREFIXYES
2
structKeyExists() - struct
prefix.key1YES
3
structKeyExists() - struct
KEY1NO
4
structKeyExists() - struct
KEY2YES
st.prefix.key1: set via quoted variable name
Expression Element PREFIX.KEY2 is undefined in ST.
st['prefix.key2']: set via associative array notation


See how the dump can't see the value of the prefix.key2 key, nor can isDefined(), and nor can actually outputting it using dot notation.

But the value is actually there.

ColdFusion 11 is much the same, except for the dump working properly:

struct
prefix
struct
key1set via quoted variable name
prefix.key2set via associative array notation

And Railo works the same as ColdFusion 11.

Interestingly, if I comment-out this:

"st.#key1#" = "set via quoted variable name";

Then prefix.key2 is completely visible across the board.

The thing is that once there's a substruct called prefix created, ColdFusion / Railo gets confused when trying to access prefix.key2. They decide that it's not a key name, but a reference to a subkey key2 within the prefix substruct. Which - of course - does not exist.

I can see how this happens, but it possibly shouldn't? What do you think?

--
Adam

Official word from Adobe PSIRT re Heartbleed and ColdFusion

$
0
0
G'day:
Adobe have completed their analysis of the Heartbleed issue in regards to their products, including ColdFusion, and have offered some guidance: "Heartbleed Update".

The relevant bits are as follows:

Some Adobe products and services do not bundle OpenSSL (such as ColdFusion** , Experience Manager and Experience Manager On-Demand) but are frequently deployed by customers on-premise or with third party web servers. We advise these customers to test for the Heartbleed vulnerability (CVE-2014-0160) against their deployment and configuration. If necessary, follow the recommendations provided by the OpenSSL security advisory as appropriate.
[...]
** Update: ColdFusion does ship a version of OpenSSL that is not vulnerable to the Heartbleed vulnerability.

I can't help but think that Aaron Foote and Brad Wood had a hand on getting this report updated to reflect reality regarding ColdFusion shipping with OpenSSL libraries:
Brad's blog article: "Adobe Product Security Incident Response Team (PSIRT) On ColdFusion And HeartBleed".

after the initial Twitter reports from Adobe ColdFusion Team members that it didn't:



I guess it's down to the interpretation of "uses". And to be clear, as per the PSIRT article: the OpenSSL libraries ColdFusion does use are not vulnerable.

I think Adobe took a bit longer than they should have to release this news, but all in all they got their in the end, so that's cool.

What I chiefly find disappointing in all this is the reaction from Rakshith in reaction to efforts on the part of the community to try to extract some accurate information out of him:

And:

I also feel some of the ColdFusion Community "usual suspects" let themselves down on this issue by being completely in denial that it might perhaps be a good idea for Adobe to clarify this situation, as it perhaps warrants more communication than a coupla vague (and as it turns out not very accurate) Twitter messages from Adobe. I'm pleased Adobe has followed this up.



In other news Railo too took a wee while to comment on this, but their eventual response seems well researched and thorough: "Railo Server and the Heartbleed vulnerability". In contrast to Rakshtih's reaction, this was Gert's reaction, after I quizzed him about when their response would present itself:

The bottom line is Railo's in the clear too.

The lesson for both Adobe and Railo here are that when serious security issues like this present themselves, not everyone is expert enough to just "know" that their products aren't effected (this was also suggested by usually-reliable ColdFusion community members), and even if the answer is a simple "no, we don't use that stuff, you're fine", then that messaging is necessary. And the quicker the message gets out there: the better. It was ten days from announcement the vulnerability existed to either Adobe or Railo clarifying, which I can't help but think is "quite a long time"? I dunno... what do you think?

Anyway, it's all good that it's just been a bit of a storm in the CFML teacup.

--
Adam

ColdFusion REST services and restSetResponse() revisited

$
0
0
G'day:
Ages ago I wrote an article lamenting the way restSetResponse() has been implemented: "restSetResponse() requires the method to be returntype void. What?". At the time I was looking at how it was instrumental in how "ColdFusion takes something that should be easy and makes it hard" in the context of exception handling. That's slightly edge-case-y, I'll admit it.

But I think I've encountered a standard-operating-procedure situation today which demonstrates the implementation of restSetResponse() is not fit for purpose. Literally: it's not fit for the purpose it has been implemented for.

Today has been a frickin' frustrating day. I sat down to do another backbone.js tutorial ("Backbone.js Beginner Video Tutorial"), having finished "Anatomy of Backbone.js" and "Anatomy of Backbone.js Part 2" from CodeSchool yesterday. I started watching the video @ around 10am, and was inspecting the code on Github at 10:20am. At that point in time I figured I had better knock together the server-side code the tutorial will need: basically some RESTful web services for get-all, get, create, update and delete. Easy. I set out to implement this code using Railo, and had it all operational by 11:30am. At that juncture I started reading up on exactly what I should be returning for the less-obvious situations: the response for a GET is obvious: return the object(s) concerned. But what do I return for a POST? And a DELETE? So I started reading the HTTP spec ("Hypertext Transfer Protocol -- HTTP/1.1 - 9 Method Definitions"), which explained it all clearly.

One interesting thing I read on Stack Overflow which had me going "oh yeah! (duh)", was in answer to this question:

However I am wondering what should be the HTTP status code is the request sent by the client is valid (DELETE mySite/entity/123) and the entity to delete does not exist.
Because I was facing the same question (and with GET operations too). The answer is the very obvious:

In that case, the service should return an HTTP 404. Strictly speaking, a DELETE or a GET request for a resource that does not exist is not a "valid" request - ie. the client should not re-attempt that request because it will never succeed... The HTTP protocol defines 2 categories of problems - those with a 4xx status code, where the client must modify the request before retrying it, and those with a 5xx status code, which indicate that the service ran into trouble and the client should/could retry the same exact request without changing it.
As I'm perpetually wont to say to people who marvel that REST is some kind of wonderous thing... it's not. It's just "making HTTP requests". We do this every day in our browser. REST requests are no different. So if a resource doesn't exist... 404 the request.

This is all good for DELETE operations, as one just needs to do this:

/**
* @httpmethod DELETE
* @restPath {id}
* @id.restargsource path
*/
remote void function deleteById(required numeric id){
var user = entityLoad("User", id, true)
if (!isNull(user)){
entityDelete(user)
restSetResponse({status=204})
}
restSetResponse({status=404})
}

So here I return appropriate status codes for the result of the request: 204 means:
204 No Content
The server successfully processed the request, but is not returning any content. Usually used as a response to a successful delete request.
And 404 is the familiar "not found" response. Cool.

I had finished reading that at 11:40am.

I can use restSetResponse() for the DELETE method because my returntype is void. Which is a restriction of restSetResponse() (not in the docs, but in this DevNet article: "Getting started with RESTful web services in ColdFusion").

But what about my GET method?

Here it is:

/**
* @httpmethod GET
* @restPath {id}
* @id.restargsource path
*/
remote User function getById(required numeric id){
var user = entityLoad("User", arguments.id, true)
if (!isNull(user)) {
return user
}
restSetResponse({status=404}) //this errors due to stupidity in CFML
}

As noted in the comment... nuh-uh. I cannot do this.

On Railo (that's Railo code above, I realise it won't run on ColdFusion - no semi-colons - but the equivalent in ColdFusion dun't work either), it gets it partially right:


It gets the response code correct, but still actually errors. And on ColdFusion I get this:


It doesn't see fit to pay attention to the response code I asked for, it just follows its own stupid internal rules and goes "nup, you can't do that! I have rules you know!".

Stupid bloody thing.

However I think this finally demonstrates that this rule that restSetResponse() can only work on void functions is just... wrong. There is a very good use case where it needs to work on functions which quite legitimately return something in most situations, but one needs to use restSetResponse() in abnormal situations. Well a 404 isn't even a very "abnormal" situation.

This is where I refer to the bug I raised with Adobe on this (3546046), where Paul offered this up:

  • Paul Nibin K J
    5:25:45 AM GMT+00:00 Jan 3, 2014
    RestSetResponse is not like return statement. RestSetResponse does not actually return anything. It just sets the custom response, which ColdFusion gets and sends the response.

    In ColdFusion, cffunctions should adhere to the return type. If you have specified a return type, you should return some value of that type.
    [etc]
This is kind of the problem. Not returning an object of a given type should not violate the type-restriction on the function. For example this:

string function conditionallyReturnString(required boolean returnAString){
if (returnAString) {
return "";
}
return;
}

emptyString = conditionallyReturnString(true);

nullString = conditionallyReturnString(false);
writeOutput(isnull(nullString));
writeDump(var=[variables]);

Running this, one gets:

YES
array
1
struct
CONDITIONALLYRETURNSTRING
EMPTYSTRING[empty string]

So it's fine to not return a string from a function which has a type string. For some reason that's the only data type in ColdFusion that follows that rule: I tested numeric, dates, arrays, CFC instances etc, and they all go:

The value returned from the conditionallyReturnArray function is not of type array.

Or some variation on that theme. Railo behaves much the same, except one can also return null from a numeric function too, and it doesn't error.

But WhyTF is CFML - a loosely-typed language - doing this? Java - strongly-typed - thinks its fine to return a null string or null object reference from a method:

public class TestReturningNullObject {

public static void main(String[] args) {
}

public C conditionallyReturnObject(boolean returnAnObject) {
if (returnAnObject){
return new C();
}
return null;
}

class C{}

}

o = createObject("java", "TestReturningNullObject");
emptyC = o.conditionallyReturnObject(true);

nullC = o.conditionallyReturnObject(false);
writeOutput(isnull(nullC));
writeDump(var=[variables]);

Output:

YES
array
1
struct
EMPTYC
object of TestReturningNullObject$C
Class NameTestReturningNullObject$C
O

So the problem here really is not with restSetResponse(), I suppose. The problem is that there's this bung rule in CFML that one cannot return null from a function that expects to return an object type. And this is wrong.

However that's a bit more of an architectural change than Adobe are gonna want to do. Even if they alter the rules for REST services wherein the response can quite legitimately take the form of [some data in the response], or just some headers. CFML REST services need to respect this.

On the other hand... I need to get this stuff working. Anyone know a way of being able to write REST services so they are capable of making a correct response for both happy and unhappy paths?

Someone else must've worked around this?


Oh... why was I deliberately giving you the times at which I completed various steps of this investigation?

Because I decided to write this blog article at midday. It's now 6pm and I've been writing for about an hour.

All the time from 12pm - 5pm was simply trying to coerce ColdFusion into actually running this RESTful CFC. It works fine in Railo and took less than an hour to write. To convert it to something ColdFusion would work reliably with took almost 5hrs. The obvious syntax changes took about half an hour to iron out (to get to a point where the could would even compile on ColdFusion 10), the rest of the time was spent going "WTF?"... "FFS, will you work?"... "Oh for goodness sake, what's wrong now?"... "Oh... you need it to be like that do you?"... "um... OK, I'll restart ColdFusion entirely then to see if that helps" (sometimes yes, sometimes no, btw), "but that's just... wrong". Four hours of that. And reading on StackOverflow (because the Adobe docs are a waste of frickin' space) hoping someone else had seen a given error message (generally: no).

The ColdFusion REST implementation has had a bit of a bad rap in this regard, I know. One thing I can say is that it's not a syntactical issue, it's just a bad implementation. Railo is far more stable, doesn't have any unexpected idiosyncracies, and - as long as yer code is syntactically correct - just works (without restarts or reboots too, I hasten to add). I really wonder how Adobe managed to make such a pig's ear of this stuff.

All I was hoping to do today was to do that backbone.js tutorial. I guess I should simply have stuck with the working code in Railo and got on with it, rather than trying to get this code working in CF so I can blog about it. My bad. But now I've been completely sapped of the will to live, so I'm going to grab a glass of wine and be done with it.  I might document the shortfalls I encountered with CF's REST implementation tomorrow. But... I'm not sure I can be arsed.

I will follow-up the CF bug and observe that not being able to use restSetResponse() on methods which aren't void is not a starter, and something needs to be done there.

Where's that wine?

--
Adam

Regex for simplifying string manipulation logic

$
0
0
G'day:
An interesting blog article fell in front of me this morning: "Capitalization for us Mc’s and Mac’s!", by Brian McGarvie. It mentions a UDF on CFLib.org which handles... well as per his blog title: captialising his name as "McGarvie" rather than "Mcgarvie" like other capitalise() functions might do.

The UDF is thus:

function celticMcCaps(lastName) {
var capLastName = lCase(lastName);
if (left(lastName,2) eq "Mc") {
capLastName = uCase(left(lastName,1)) & lCase(mid(lastName,2,1)) & uCase(mid(lastName,3,1)) & lCase(right(lastName,len(lastName)-3));
return capLastName;
}
else if (left(lastName,3) eq "Mac") {
capLastName = uCase(left(lastName,1)) & lCase(mid(lastName,2,1)) & lCase(mid(lastName,3,1)) & uCase(mid(lastName,4,1)) & lCase(right(lastName,len(lastName)-4));
return capLastName;
}
else if (left(lastName,2) eq "O'") {
capLastName = uCase(left(lastName,1)) & "'"& uCase(mid(lastName,3,1)) & lCase(right(lastName,len(lastName)-3));
return capLastName;
}
else return lastName;
}

(thanks to Kyle MacNamara for submitting it, btw).

I had a look at that, and thought "that's a lot of logic when all we're doing is string manipulation".

I have to admit I didn't spot the fact it handles the "O'" prefix at first, and very quickly came out with this:

function celticMcCaps(name){
reReplaceNoCase(name, "^([M])([a]?c)([a-z])(.*)$", "\U\1\E\L\2\E\U\3\E\L\4\E", "ONE")
}

Which does 2/3rds of the trick. Then when writing this article I spotted the "O'" handling, so revised it to this:

function celticMcCapsRevised(name){
return reReplaceNoCase(name, "^([MO])((?:[a]?c)|')([a-z])(.*)$", "\u\1\L\2\E\u\3\L\4\E", "ONE");
}

The trick to all this is regular expression replacements can perform case-conversion. \u and \l will convert the next character to their respective cases; \U and \L will convert all subsequent characters to their respective cases, until a \E is encountered. So I use \u to upper-case the first letter, plus the one after the prefix, and \L to lowercase the rest.

Running a test compare on this and the old one suggests it covers the same ground:

writeOutput('<table border="1"><thead><tr><th>Value</th><th>Original function</th><th>Revised function</th></tr></thead><tbody>');
for (name in [
"cameron", // control
"CAMERON", // control
"Cameron", // control
"Oswald", // control
"oswald", // control
"OSWALD", // control
"McGarvie", // already OK
"MacDonald", // already OK
"O'Shea", // already OK
"Mcgarvie", // should change
"Macdonald", // should change
"O'shea", // should change
"mcgarvie", // should change
"macdonald", // should change
"o'shea", // should change
"MCGARVIE", // should change
"MACDONALD", // should change
"O'SHEA" // should change

]){
writeOutput("<tr><td>#name#</td><td>#celticMcCaps(name)#</td><td>#celticMcCapsRevised(name)#</td></tr>");
}
writeOutput("</tbody></table>");

(I'm in a rush today, so didn't bother with TDD... oops!)

This outputs:

ValueOriginal functionRevised function
cameroncameroncameron
CAMERONCAMERONCAMERON
CameronCameronCameron
OswaldOswaldOswald
oswaldoswaldoswald
OSWALDOSWALDOSWALD
McGarvieMcGarvieMcGarvie
MacDonaldMacDonaldMacDonald
O'SheaO'SheaO'Shea
McgarvieMcGarvieMcGarvie
MacdonaldMacDonaldMacDonald
O'sheaO'SheaO'Shea
mcgarvieMcGarvieMcGarvie
macdonaldMacDonaldMacDonald
o'sheaO'SheaO'Shea
MCGARVIEMcGarvieMcGarvie
MACDONALDMacDonaldMacDonald
O'SHEAO'SheaO'Shea

All good?

This just demonstrates that when one is manipulating text... using regular expressions is probably the place to start, before writing a bunch of string-manipulation logic.

And also - from a TDD perspective - this would cut down the number of tests from four (one for each branch of the logic) to one. Obviously I'd still run an "eyeball" test like the one I wrote above.

Anyway... that's it. Unless anyone spots any shortfalls in the revised approach, I might update the UDF on CFLib.

I'm hoping Peter Boughton reads this and sets me straight about any dodginess in my regex. If you think Ben Nadel knows a thing or two about regular expressions (and, hey, he does), then he seems like a journeyman compared to Peter, who is a true regex guru.

--
Adam

ColdFusion 11: quick look at what data-type built-in functions are, in the context of using them as first-class functions

$
0
0
G'day:
That was a foolishly long title. Oh well. The article itself is gonna be a quicky as I haven't had dinner yet and I'm getting hungry. Once I've cooked and got wine in hand... I'm not gonna be interest in writing this up.

Awdhesh and Rakshith gave an online presentation today on some of the language features of ColdFusion 11, and the subject of built-in functions being elevated to first class status came up. I thought I had covered this, but I'm buggered if I can find the article. If this is a double-up: sorry.

Firstly: what's a first-class function? Wikipedia explains it succinctly ("First-class function"):

In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. Specifically, this means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.
Make sense? Basically you know how you can do this with bespoke functions:

function doStuff(){
// stuff here
}

result = doStuffWithAHandler(dataToProcess, doStuff){
// use doStuff() to assist processing dataToProcess
}

Or even inline with a function expression:

result = doStuffWithAHandler(dataToProcess, function doStuff(){
// stuff here
}){
// use doStuff() to assist processing dataToProcess
}

Until ColdFusion 11, one could pass one's own functions around like that, but one could not pass CFML functions around like that, eg:

function decorateText(text, decorator){
return decorator(text);
}

message = "G'day world!";
shoutyMessage = decorateText(message, ucase);

writeDump([message, shoutyMessage]);

Yielding:

array
1G'day world!
2G'DAY WORLD!

On ColdFusion 10 and earlier, one would get this instead:


Variable UCASE is undefined.

So, anyway, there you go: ColdFusion's built-in functions are now first-class.

-ish.

Ray wondered out loud during the presentation what datatype a built-in function is, and I had tested this but not finalised the code. So I've done-so this evening. here we go, here's a bit of discovery. In the code below I basically run two tests on three things.

The tests are:


  • what datatype things are;
  • how those datatypes stack-up when used as a function.

And the three things I test are:
  • a bespoke function defined by function statement
  • a bespoke function defined by function expression
  • the built-in CFML function mid()

I've wrapped some calls in a safe() function so that I don't need to try/catch everything all over the place.

The red text is where I encountered problems, I'll discuss below.


writeOutput("<h2>Data types</h2>");
writeOutput("<h3>Function declared by statement</h3>");
function declaredViaStatement(){}
writeDump(var={object=declaredViaStatement, type=declaredViaStatement.getClass().getName()});

writeOutput("<h3>Function declared by expression</h3>");
declaredViaExpression=function(){};
writeDump(var={object=declaredViaExpression, type=declaredViaExpression.getClass().getName()});

writeOutput("<h3>Built-in function</h3>");
writeOutput("<h4>Via direct reference</h4>");
safe(function(){
writeDump(var={object=mid, type=midref.getClass().getName()});
});


writeOutput("<h4>Via indirect reference</h4>");
safe(function(){
var midRef = mid;
writeDump(var={object=midref, type=midref.getClass().getName()});
});


writeOutput("<h4>Via indirect reference outside of closure</h4>");
midRef = mid;
safe(function(){
writeDump(var={object=midref, type=midref.getClass().getName()});
});


writeOutput("<h2>return types</h2>");

function function regurgitateFunction(required function f){
return f;
}

writeOutput("<h3>Function declared by statement</h3>");
safe(function(){
resultWithDeclaredViaStatement = regurgitateFunction(declaredViaStatement);
writeDump(var={object=resultWithDeclaredViaStatement, type=resultWithDeclaredViaStatement.getClass().getName()});
});

writeOutput("<h3>Function declared by expression</h3>");
safe(function(){
resultWithDeclaredViaExpression = regurgitateFunction(declaredViaExpression);
writeDump(var={object=resultWithDeclaredViaExpression, type=resultWithDeclaredViaExpression.getClass().getName()});
});


writeOutput("<h3>Built-in function</h3>");
midref = mid;
safe(function(){
resultWithBuiltInFunction = regurgitateFunction(midref);
writeDump(var={object=resultWithBuiltInFunction, type=resultWithBuiltInFunction.getClass().getName()});
});


function safe(f){
try {
f();
}catch(any e){
writeOutput("#e.type#: #e.message#; #e.detail#<br>");
}
}


The output here is as follows:


Data types


Function declared by statement

struct
OBJECT
function declaredViaStatement
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$funcDECLAREDVIASTATEMENT

Function declared by expression

struct
OBJECT
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$func_CF_ANONYMOUSCLOSURE_0

Built-in function

Via direct reference

Expression: Variable MID is undefined.; 

Via indirect reference

Expression: Variable MID is undefined.; 

Via indirect reference outside of closure

struct
OBJECT
object of coldfusion.runtime.CFPageMethod
Class Namecoldfusion.runtime.CFPageMethod
Methods
MethodReturn Type
getInstance(java.lang.String)coldfusion.runtime.CFPageMethod
getName()java.lang.String
invoke(java.lang.Object, java.lang.String, java.lang.Object, java.lang.Object[])java.lang.Object
TYPEcoldfusion.runtime.CFPageMethod

return types

Function declared by statement

struct
OBJECT
function declaredViaStatement
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$funcDECLAREDVIASTATEMENT

Function declared by expression

struct
OBJECT
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$func_CF_ANONYMOUSCLOSURE_0

Built-in function

function: The value returned from the regurgitateFunction function is not of type function.; If the component name is specified as a return type, it is possible that either a definition file for the component cannot be found or is not accessible.

We'd seen before that passing a built-in function as a callback works OK. But there are some other issues which are a bit bung.

Firstly there's this code:

writeOutput("<h4>Via direct reference</h4>");
safe(function(){
writeDump(var={object=mid, type=midref.getClass().getName()});
});

writeOutput("<h4>Via indirect reference</h4>");
safe(function(){
var midRef = mid;
writeDump(var={object=midref, type=midref.getClass().getName()});
});

writeOutput("<h4>Via indirect reference outside of closure</h4>");
midRef = mid;
safe(function(){
writeDump(var={object=midref, type=midref.getClass().getName()});
});

The first two approaches to using mid() as a first class function here failed:


Via direct reference

Expression: Variable MID is undefined.;

Via indirect reference

Expression: Variable MID is undefined.;


So I cannot reference mid()directly in this context - in the context of within some closure code, it seems - it's not until I create the reference outside the closure code that the reference works.

So that's weird.

And then at the bottom, I have a function which both takes and returns an object of type function:


function function regurgitateFunction(required function f){
return f;
}

And it seems I can pass mid()into this function - so when it goes into the function it qualifies as a function - but by the time it gets passed out: not a function any more. Weird.

function: The value returned from the regurgitateFunction function is not of type function.; If the component name is specified as a return type, it is possible that either a definition file for the component cannot be found or is not accessible.

That doesn't make a whole heap of sense to me.

To be clear... anywhere a function should be allowed in my CFML: a built-in function should "pass" type checking.

So I think there's a coupla bugs to fix here. I'll raise 'em and update this once I've got my dinner sorted out.

Righto.

--
Adam

CFDUMP, and how I'm a bit thick

$
0
0
G'day:
This isn't even a case of me not RTFMing and then discovering something I didn't know. I knew this already. But am just a bit dense.

I'm perennially doing this sort of thing:

private function logArgs(required struct args, required string from){
savecontent variable="local.dump" {
writeDump(var=args, label=from);
}
fileWrite(getDirectoryFromPath(getCurrentTemplatePath()) & "dump_#from#.html", local.dump);

(As case in point being: I just wrote that code for a new blog article that I should have done and dusted by this evening)

However I don't need to do that. I can just do this:

private function logArgs(required struct args, required string from){
var dumpFile = getDirectoryFromPath(getCurrentTemplatePath()) & "dump_#from#.html";
if (fileExists(dumpFile)){
fileDelete(dumpFile);
}
writeDump(var=args, label=from, output=dumpFile, format="html");
}

I'm so accustomed to only every using the var argument, and occasionally the label one, I tend to forget there's a bunch of other options on <cfdump> / writeDump() as well (from the docs):

AttributeReq/OptDefaultDescription
varRequiredVariable to display. Enclose a variable name in number signs. These kinds of variables yield meaningful cfdump output
expandOptionalyes
  • yes: in Internet Explorer and Mozilla, expands views.
  • no: contracts expanded views.
formatOptionaltextUse with the output attribute to specify whether to save the results of a cfdump to a file in text or HTML format.
hideOptionalallFor a query, this is a column name or a comma-delimited list of column names. For a structure, this is a key or a comma-delimited list of keys.
If you specify a structure element that doesn't exist, ColdFusion ignores it and does not generate an error.
keysOptional9999For a structure, the number of keys to display.
labelOptionalA string; header for the dump output. Ignored if the value of the var attribute is a simple types.
metainfoOptionalyes for query
no for persistence CFCs
For use with queries and persistence CFCs. Includes information about the query in the cfdump results, including whether the query was cached, the execution time, and the SQL. Specify metainfo="no" to exclude this information from the query result. For persistence CFCs, if metainfo="yes", returns property attributes such as getters and setters.
outputOptionalbrowserWhere to send the results of cfdump. The following values are valid:
  • browser
  • console
  • filename
    The filename must include the full pathname of the file. You can specify an absolute path, or a path that is relative to the ColdFusion temporary directory. You can use the GetTempDirectory() function to determine the ColdFusion temporary directory.
showOptionalallFor a query, this is a column name or a comma-delimited list of column names. For a structure, this is a key or a comma-delimited list of keys.
showUDFsOptionalyes
  • yes: includes UDFs, with the methods collapsed.
  • no: excludes UDFs.
topOptional9999The number of rows to display. For a structure, this is the number of nested levels to display.
abortOptionalfalseIf this attribute is set to "true", it stops processing the current page at the tag location.

Perhaps everyone else in the world remembers all this stuff and uses it all the time. I'm simply not that clever.

--
Adam

ColdFusion 11: custom serialisers. More questions than answers

$
0
0
G'day:
I've been wanting to write an article about the new custom serialiser one can have in ColdFusion 11, but having looked at it I have more questions than I have answers, so I have put it off. But, equally, I have no place to ask the questions, so I'm stymied. So I figured I'd write an article covering my initial questions. Maybe someone can answer then.

ColdFusion 11 has added the notion of a custom serialiser a website can have (docs: "Support for pluggable serializer and deserializer"). The idea is that whilst Adobe can dictate the serialisation rules for its own data types, it cannot sensibly infer how a CFC instance might get serialised: as each CFC represents a different data "schema", there is no "one size fits all" approach to handling it. So this is where the custom serialiser comes in. Kind of. If it wasn't a bit rubbish. Here's my exploration thusfar.

One can specify a custom serialiser by adding a setting to Application.cfc:

component {

this.name = "serialiser01";
this.customSerializer="Serialiser";

}

In this case the value - Serialiser - is the name of a CFC, eg:

// Serialiser.cfc
component {

public function canSerialize(){
logArgs(args=arguments, from=getFunctionCalledName());
return true;
}

public function canDeserialize(){
logArgs(args=arguments, from=getFunctionCalledName());
return true;
}

public function serialize(){
logArgs(args=arguments, from=getFunctionCalledName());
return "SERIALISED";
}

public function deserialize(){
logArgs(args=arguments, from=getFunctionCalledName());
return "DESERIALISED";
}

private function logArgs(required struct args, required string from){
var dumpFile = getDirectoryFromPath(getCurrentTemplatePath()) & "dump_#from#.html";
if (fileExists(dumpFile)){
fileDelete(dumpFile);
}
writeDump(var=args, label=from, output=dumpFile, format="html");
}
}

This CFC needs to implement four methods:
  • canSerialize() - indicates whether something can be serialised by the serialiser;
  • canDeserialize() - indicates whether something can be deserialised by the serialiser;
  • serialize() - the function used to serialise something
  • deserialize() - the function used to deserialise something
I'm being purposely vague on those functions for a reason. I'll get to that.

The first cock-up in the implementation here is that for the custom serialisation to work, all four of those methods must be implemented in the serisalisation CFC. So common sense would dictate that a way to enforce that would be to require the CFC to implement an interface. That's what interfaces are for. Now I know people will argue the merit of having interfaces in CFML, but I don't really give a shit about that: CFML has interfaces, and this is what they're for. So when one specifies the serialiser in Application.cfc and it doesn't fulfil the interface requirement, it should error. Right then. When one specifies the inappropriate tool for the job. What instead happens is if the functions are omitted, one will get erratic behaviour in the application, through to outright errors when ColdFusion goes to call the functions and cannot find it. EG: if I have canSerialize() but no serialize() method, CF will error when it comes to serialise something:

JSON serialization failure: Unable to serialize to JSON.

Reason : The method serialize was not found in component C:/wwwroot/scribble/shared/git/blogExamples/coldfusion/CF11/customerserialiser/Serialiser.cfc.
The error occurred inC:/wwwroot/scribble/shared/git/blogExamples/coldfusion/CF11/customerserialiser/testBasic.cfm: line 4
2 : o = new Basic();
3 :
4 : serialised = serializeJson(o);
5 : writeDump([serialised]);
6 :

Note that the error comes when I go to serialise something, not when ColdFusion is told about the serialiser in the first place. This is just lazy/thoughtless implementation on the part of Adobe. It invites bugs, and is just sloppy.

The second cock-up follows immediately on from this.

Given my sample serialiser above, I then run this test code to examine some stuff:

o = new Basic();

serialised = serializeJson(o);
writeDump([serialised]);

deserialised = deserializeJson(serialised);
writeDump([deserialised]);

So all I'm doing is using (de)serializeJson() as a baseline to see how the functions work. here's Basic.cfc, btw:

component {

}

And the test output:

array
1SERIALISED
array
1DESERIALISED

This is as one would expect. OK, so that "works". But now... you'll've noted I am logging the arguments each of the serialisation methods receives, as I got.

Here's the arguments passed to canSerialize():

canSerialize - struct
1XML

My reaction to that is: "WTF?" Why is canSerialize() being passed the string "XML" when I'm trying to serialise an object of type Basic.cfc?

Here's the docs for canSerialize() (from the page I linked to earlier):
CanSerialize - Returns a boolean value and takes the "Accept Type" of the request as the argument. You can return true if you want the customserialzer to serialize the data to the passed argument type. 
Again, back to "WTF?" What's the "Accept type" of the request? And what the hell has the request got to do with a call to serializeJson()? You might think that "Accept type" references some HTTP header or something, but there is no "Accept type" header in the HTTP spec (that I can find: "Hypertext Transfer Protocol -- HTTP/1.1: 14 Header Field Definitions"). There's an "Accept" header (in this case: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"), and other ones like "Accept-Encoding", "Accept-Language"... but none of which contain a value of "XML". Even if there was... how would it be relevant to the question as to whether a Basic.cfc instance can be serialised? Raised as bug: 3750730.

serialize() gets more sensible arguments:

serialize - struct
1
serialize - component scribble.shared.git.blogExamples.coldfusion.CF11.customerserialiser.Basic
2JSON

So the first is the object to serialise (which surely should be part of the question canSerialize() is supposed to ask, and the format to serialise to. Cool.

canDeserialize() is passed this:

canDeserialize - struct
1JSON

I guess it's because it's being called from deserializeJson(), so it's legit to expect the input value is indeed JSON. Fair enough. (Note: I'm not actually passing it JSON, but that's beside the point here).

And deserialize() is passed this:

deserialize - struct
1SERIALISED
2JSON
3[empty string]

The first argument is the value to work on, and the second is the type of deserialisation to do. I have no idea what the third argument is for, and it's not mentioned directly or indirectly on that docs page. So dunno what the story is there.

The next issue isn't a code-oriented one, but an implementation one: how the hell are we expected to work with this?

The only way to work here is for each function to have a long array of IF/ELSEIF statements which somehow identify each object type that is serialisable, and then return true from canSerialise(), or in the case of serialize(), go ahead and do the serialisation. So this means this one CFC needs to know about everything which can be serialised in the entire application. Talk about a failure in "separation of concerns".

You know the best way of determining if an object can be seriaslised? Ask it! Don't rely on something else needing to know. This can be achieved very easily in one of two ways:

  • Check to see if the object implements a "Serializable" interface, which requires a serialize() method to exist.
  • Or simply take the duck-typing approach: if a CFC implements a serialize() method: it can be serialised. By calling that method. Job done.


Either approach would work fine, keeps things nicely encapsulated, and I see merits in both. And either make far more sense than Adobe's approach. Which is like something from the "OO Failures Special Needs" class.

Deserialisation is trickier. Because it relies on somehow working out how to deserialise() an object. I'm not sure of the best approach here, but - again - how to deserialise something should be as close to the thing needing deserialisation as possible. IE: something in the serialised data itself which can be used to bootstrap the process.

This could simply be a matter of specifying a CFC type at a known place in the serialised data. EG: Adobe stipulates that if the serialised data is JSON, and at the top level of the JSON is a key eg: type, and the value is an extant CFC... use that CFC's deserialize() method. Or it could look for an object which contains a type and a method, or whatever. But Adobe can specify a contract there.

The only place I see a centralised CFC being relevant here is for a mechanism for handling serialised data that is neither a ColdFusion internal type, nor identifiable as above. In this case, perhaps they could provide a mechanism for a serialisation router, which basically has a bunch of routes (if/elseifs if need be) which contains logic as to how to work out how to deserialise the data. But it should not be the actual deserialiser, it should simply have the mechanism to find out how to do it. This is actually pretty much the same in operation as the deserialize() approach in the current implementation, but it doesn't need the canDeserialize() method (it can return false at the end of the routing), and it doesn't need to know about serialising. And also it's not the main mechanism to do the deserialisation, it's just the fall back if the prescribed approach hasn't been used.

TBH, this still sounds a bit jerry-built, and I'm open for better suggestions. This is probably a well-trod subject in other languages, so it might be worth looking at how the likes of Groovy, Ruby or even PHP (eek!) achieve this.

There's still another issue with the current approach. And this demonstrates that the Adobe guys don't actually work with either CFML applications or even modern websites. This approach only works for a single, stand-alone website (like how we might have done in 2001). What if I'm not in the business of building websites, but I build applications such as FW/1 or ColdBox or the like? Or any sort of "helper" application. They cannot use the current Adobe implementation of the customserializer. Why? Because the serialisation code needs to be in a website-specific CFC. There's no way for Luis to implement a custom serialiser in ColdBox (for example), and then have it work for someone using ColdBox. Because it relies on either editing Application.cfc to specify a different CFC, or editing the existing customSerializer CFC. Neither of which are very good solutions. This should have been immediately apparent to the Adobe engineer(s) implementing this stuff had they actually had any experience with modern web applications (which generally aren't just a single monolithic site, but an aggregation of various other sub applications). Equally, I know it's not a case of having thought about this and [I'm just missing something], because when I asked them the other day, at first they didn't even get what I was asking, but when I clarified were just like "oh yeah... um... err... yeah, you can't do that. We'll... have to... ah yeah". This has been raised as bug 3750731.

So I declare the intent here valid, but the implementation to be more alpha- / pre-release- quality, not release-ready.

Still: it could be easily deprecated and rework fairly easily. I've raised this as bug 3750732.

Or am I missing something?

--
Adam

ColdFusion 11: cfclient in the context of the CFML language, not the tooling

$
0
0
G'day:
This article could end up being a complete waste of space, as I am operating under more real-world re-interpretation of the raison d'être of <cfclient>, as described by Ram:
ColdFusion [11] has added support for client side CFML (<cfclient>) and this code is translated to JavaScript
Mike Henke has pointed out that the first part of this "CF11 has added support for client-side CFML" is actually bullshit (my wording, not his):
"ColdFusion Splendor has added support for client side CFML" is probably phrased wrong and gave me flash backs to VBScript or CFML running in the client browser.
Maybe this is better verbiage. "ColdFusion Splendor has added support for CFML to generate JS".
Because... there's not such thing as "client-side CFML". That would imply CFML actually running on the client (ie: the browser, or the mobile device, as is the target of <cfclient> as a concept). This is absolutely not true. I know Ram went on to qualify what he meant there, but the messaging from Adobe on <cfclient> has been inaccurate. Possibly I think to the point of actual misrepresentation (in the legal sense, I mean).

What <cfclient> does is... convert CFML code to JavaScript. So let's look at that idea.

Why I say this article could be a complete waste of space is that I dunno if this is the intent of <cfclient>; because, really, the messaging from Adobe as to what <cfclient> is for is very muddied. In general the narrative about it is bundled-in with various features of ColdFusion Builder 3.0, in regards to mobile application creation and packaging (and debugging, and all sort of stuff that sounds quite nice but I don't care about as I don't use CFB).

However <cfclient> has to stand on its own merit in the CFML language, completely decoupled from CFB features. And as far as I can tell, Mike is right: as far as CFML goes, <cfclient> is a tag which tells ColdFusion to - instead of compiling to byte code - compiles the enclosed CFML code to JavaScript instead.

This of course just sets my alarm klaxons going.  Because Adobe are (I choose my words carefully) shit at converting CFML to client-side code. This has been borne out by <cfform>, <cflayout>, <cfpod> etc. Back in the days of CFMX7 the "CFML-to-client-side" wizards had some merit, because the client-side arena was still immature, and the team at Macromedia actually had some exposure to the idea of "website development", so they knew where the pain points were that they could possible ameliorate. Times have changed, and the client-side of things is probably now more mature an environment than CFML is - Javascript is probably are more coherent language than CFML is, now - and it is very very very clear that the current Adobe ColdFusion Team don't know the first thing about web development (they can't even write decent CFML for their own documentation, before we get onto how they'd be doing web work!), so they are completely out of their depth when it comes to addressing pain points that web devs might have these days. Not only have they not walked a mile in a web dev's shoes, they don't even - as far as I can tell - know what shoes are, or why someone would be wearing them.

And <cfclient> is a far more "meta" solution than <cfform> or <cfpod> were... at least the CFML-to-client-side solution there had a tangible target in mind. <cfclient> has removed the targets, and is just a tag designed for converting general CFML to general JS. Yikes.

600-odd words in, here's some code. This is the first-ever code I have written using <cfclient>:

<cfclient>
<cfset msg = "G'day World">
<cfoutput>#message#</cfoutput>
</cfclient>

So I'm off to a false start: I admit I'm sitting in the pub and I've had a few pints (probably four, I guess; this is "having some drinks before deciding to actually drink this evening" sort of level for me), and I inadvertently ballsed-up my variable names here. And this did not yield a compile error. The error just fell through to JS, where it did error


  1. Uncaught ReferenceError: message is not definedgdayWorld.cfm:4

If, in contrast, I tried this in CoffeeScript, I'd get this:

coffee> msg = "G'day world"
'G\'day world'
coffee> alert msg
undefined
coffee>
alert message
ReferenceError: message is not defined
    at eval (eval at  (http://larryng.github.io/coffeescript-repl/js/main.js:96:26), :1:9)

Important note: I know nothing about CoffeeScript - it seems like a solution to a non-existent problem to me - so I just googled "hello world coffeescript" and REPLed the code on http://larryng.github.io/coffeescript-repl/.

The point is here is that ColdFusion, during the "compile" process should actually notice I am referencing a variable that doesn't exist. I'm OK with this being a runtime error in the normal CFML-compile process, because there could be many moving parts (and other files) involved in the runtime environment, but I don't think this is a valid notion with <cfclient>? Or maybe it is?

Update:

As Ray points out and Aaron agrees with below: I was being unduly harsh here... it's entirely reasonable for variables to be declared in other JS code elsewhere in the response.

Anyway, once I actually write my code properly, I get this:

G'day World

On the screen. What CF actually sends back in the response is this:


<script type="text/javascript" src="/CFIDE/cfclient/cfclient_main.js"></script>
<script type="text/javascript" src="/CFIDE/cfclient/cffunctions.js"></script>
<meta name="viewport" content="width=device-width">
<script type='text/javascript'>globalDivStruct=null;var _$gdayWorld_func=function(){var self=this;var variables={};self.__init=function(){var localdivstruct=globalDivStruct;var __output_var="";var tmpVarArray={};message="G'day World";localdivstruct.outputvar+=message;return""}};
function __startPage__$gdayWorld(){document.write("\x3cdiv id\x3d'__cfclient_0'\x3e\x3c/div\x3e");window.ispgbuild=false;var clientDivStruct={divId:"__cfclient_0",outputvar:""};globalDivStruct=clientDivStruct;try{_$gdayWorld=new _$gdayWorld_func;_$gdayWorld.__init()}catch(__eArg){if(__eArg!=="$$$cfclient_abort$$$")throw __eArg;}__$cf.__flush(clientDivStruct)}__startPage__$gdayWorld();
</script>

Holy fucking christ. To output a string variable? Really? That's what you compile my CFML down to?

Gobsmacked.

And screw emotive things like "gobsmacked"... if one looks at the size of the response, it's 128kB to represent "G'day World" on the screen. 128kB might not be a great deal these days when it comes to a web page, but when it comes to rendering "G'day world", it's lunacy. And on a mobile phone - which intrinsically might be in an area in which one is paying a premium for data - it's just irresponsible. This is before we get to the point that it seriously should not take 128kB of data to render 11 bytes.



Blimey. To be honest I was gonna try some more tricky CFML and see how it compiled, but I've run out of will to live (and I have to concede the beer is getting the better of me, and my laptop battery is almost dead). I'll continue this, but I will let this article stand or fall on its own merit; something <cfclient> certainly seems to be unable to do, thusfar.

I'll be back on the case with this tomorrow.

--
Adam

ColdFusion 11: seems to require /CFIDE to be available

$
0
0
G'day:
This will be a short one... I just want to trot out various observations I make about <cfclient> as I make them. I don't want to waste too much effort on it.

I also feel a bit dirty round about now... I'm about to use a <cfform> tag.

As should everyone, I do not expose /CFIDE to the outside world. This is a terrible vector for security vulnerabilities. One that Adobe seems to be dragging its heels about resolving once and for all ("Isolate the /CFIDE/scripts directory from the rest of /CFIDE" (3732913)).

So when I'm using <cfform> (which is only ever when I am writing example code like this), I need to redirect ColdFusion to look in my isolated directory which just has the script stuff in it:

<cfajaximport scriptsrc="/lib/js/CF/scripts">
<cfform method="post" action="#CGI.script_name#">
<cfinput type="dateField" name="date">
<input type="submit" name="submitButton" value="Go">
</cfform>
<cfdump var="#form#">

Note: do not use <cfform> or any of ColdFusion's other UI wizard tags. Just don't. See here: "ColdFusion UI the Right Way", here: "I'm not going to tell you to stop using ColdFusion UI tags anymore...", and here: "Oi! You bloody wankers! Stop using ColdFusion UI controls".

Anyway, back to the code. The poorly-named <cfajaximport> tag allows one to point ColdFusion at a different, isolated directory. That's quite handy.

Now I try it with my <cfclient> code:

<cfajaximport scriptsrc="/lib/js/CF/scripts">
<cfclient>
<cfset message = "G'day World">
<cfoutput>#message#</cfoutput>
</cfclient>

and I get this:

14:46:11.292GEThttp://localhost:8511 /shared/scratch/blogExamples/coldfusion
/CF11/cfclient/moveScriptDir.cfm
[HTTP/1.1 200 OK 284ms]
14:46:11.655GEThttp://localhost:8511 /lib/js/CF/scripts/ajax/messages/cfmessage.js[HTTP/1.1 200 OK 5ms]
14:46:11.655GEThttp://localhost:8511 /lib/js/CF/scripts/ajax/package/cfajax.js[HTTP/1.1 200 OK 10ms]
14:46:11.656GEThttp://localhost:8511/CFIDE/cfclient/cfclient_main.js[HTTP/1.1 404 /CFIDE/cfclient/cfclient_main.js 3ms]
14:46:11.656GEThttp://localhost:8511/CFIDE/cfclient/cffunctions.js[HTTP/1.1 404 /CFIDE/cfclient/cffunctions.js 3ms]
14:46:11.657GEThttp://localhost:8511/CFIDE/cfclient/cfclient_main.js[HTTP/1.1 404 /CFIDE/cfclient/cfclient_main.js 1ms]
14:46:11.657GEThttp://localhost:8511/CFIDE/cfclient/cffunctions.js[HTTP/1.1 404 /CFIDE/cfclient/cffunctions.js 0ms]

Groan. Because <cfclient> doesn't put its script files in the scripts dir, does it? No. Of course it doesn't. Muppets.

But this could still be solved if one could specify some other way of pointing it to a different location for its resources, but... no. There's not a way of doing this that I can find. Short of pissing around on the web server and setting up virtual directories pointing to /CFIDE/cfclient. This isn't really a hardship, but still, it just shouldn't be necessary, and I can't help but think this is a bit sloppy on the part of Adobe.

It also demonstrates that for their internal testing they don't test this sort of thing. Which is odd, as it's the second thing I tested: you know, that it even works on a secured server.

That's it. I've nothing else to say on this topic. I'll raise a bug for this shortly, once I have the article published so I can refer back to it: 3750729.

--
Adam

ColdFusion 11: ... how does normal CFML code and code interact?

$
0
0
G'day:
Another quick one. I'm raising these quick-fire questions here because Adobe have declined to suggest a better place to raise them, other than as comments on one of their blog entries. Well that was Ram's suggestion (which I don't think is terribly-well thought out). He declined to react to my suggestion that the Adobe ColdFusion forums might be a good place. Anit suggested Twitter or just emailing him, but I think there'd be public interest in this stuff, so don't want to resort to email.

As I'm the master of what goes on on this blog: I'll clutter this thing up.

Say I want to have a mix of "normal" CFML and <cfclient>-based CFML in the same file. I can only presume the intent is to allow this, otherwise having <cfclient> as a tag rather than just a file extension seems like a poor approach. Obviously if one can have a start tag and an end tag, then code can come before (and I guess after) the <cfclient> tags themselves.

So I'd expect this to work:

<cfset message = "G'day World">
<cfclient>
<cfoutput>#message#</cfoutput>
</cfclient>

However all I get is an error in JS:


  1. Uncaught ReferenceError: message is not definedvariablesScopeVariable.cfm:4

And, indeed, the only mention of message in the JS source is the one that's erroring (as it's on the right-hand side of an assignment).

So I thought perhaps <cfclient> worked like <cfthread> and I needed to pass attributes into it:

<cfset message = "G'day World">
<cfclient message="#message#">
<cfoutput>#message#</cfoutput>
</cfclient>

This doesn't compile:

Attribute validation error for the client tag.

The tag does not have an attribute called message. The valid attribute(s) are ''.
ColdFusion cannot determine the line of the template that caused this error.This is often caused by an error in the exception handling subsystem.

Note also there's an error in the error message itself. It's not the <client> tag, it's the <cfclient> tag.

Rightio then, so I tried just using the request scope instead (the code's the same as the variables-scoped example, except using the request scope). No dice: same JS error.

As a last ditch effort, I just tried to see if <cfclient> was aware of anything going on around it, by passing a value on the URL, and seeing if <cfclient> saw that, eg:

<cfclient>
<cfoutput>#URL.message#</cfoutput>
</cfclient>

This behaved differently from the variables- / request- scoped examples, in that I didn't get a JS error, I just got this on the screen:

undefined

And no JS error. It pains me to have to do this, but let's look at the generated JS to see why the behaviour is different:

Variables scope example:

<script type="text/javascript" src="/CFIDE/cfclient/cfclient_main.js"></script>
<script type="text/javascript" src="/CFIDE/cfclient/cffunctions.js"></script>
<meta name="viewport" content="width=device-width">
<script type='text/javascript'>
globalDivStruct=null;
var _$variablesScopeVariable_func=function(){
var self=this;
var variables={};
self.__init=function(){
var localdivstruct=globalDivStruct;
var __output_var="";
var tmpVarArray={};
localdivstruct.outputvar+=message;
return""
}
};
function __startPage__$variablesScopeVariable(){
document.write("\x3cdiv id\x3d'__cfclient_0'\x3e\x3c/div\x3e");
window.ispgbuild=false;
var clientDivStruct={
divId : "__cfclient_0",
outputvar :""
};
globalDivStruct=clientDivStruct;
try{
_$variablesScopeVariable=new _$variablesScopeVariable_func;
_$variablesScopeVariable.__init()
}
catch(__eArg){
if(__eArg!=="$$$cfclient_abort$$$")
throw __eArg;
}
__$cf.__flush(clientDivStruct)
}
__startPage__$variablesScopeVariable();
</script>

The only significant difference (other than function names, based on the file names) between this and the URL-scoped example is the indicated line above is replaced by this in the URL example:

localdivstruct.outputvar+=__$cf.__arrayGet(URL,"message",true);

So it's like it's trying to do the right thing, but just failing. I thought it might be because CF does stupid thinks with scope-key casing, and changed the <cfclient> code to expect URL.MESSAGE not URL.message, but this didn't work either.

So I'm flummoxed. I can't find anything in any documentation which might point me in the right direction, so anyone know what the story is here?

Update:

At Joel's suggestion I tried this:
<script>
message = "G'day World";
</script>
<cfclient>
<cfoutput>#message#</cfoutput>
</cfclient>

This worked. Which elicits from me a mixture of "heh: cute" and "this is an abomination".

--
Adam

ColdFusion 11: cfclient ports a lot of CFML functions to JS

$
0
0
G'day:
I will start this article - which won't be a long one - by stating I am an adequate JavaScript developer, but I am by no means an expert. I'm at that stage wherein I'm au fait with the syntax and the nuts and bolts of writing OO-esque JS, but I don't spend enough time doing it to know the minutiae of "best practice" and don't automatically know the differences between the "best" way of doing something, and just "a way of doing something". Hence this article asks a question, rather than making any concrete statements.

A few days ago I wrote my first <cfclient> hello world (or in my case "G'day World") example. I'm not compiling it to a mobile app as I'm not really testing the ColdFusion Builder app packagings stuff (I simply don't care about that), I'm only interested in how <cfclient> performs / works in the context of a tag in the CFML language. See this article for more on that: "ColdFusion 11: <cfclient> in the context of the CFML language, not the tooling".

One thing I noticed in my "G'day World" code, is that to output those 11 bytes on the screen, <cfclient> loads around 128kB of data, in the form of various tracts of line code and external libraries. Describing this as "egregious" is an understatement.

One of the libraries particularly caught my interest, as it's called "cffunctions.js", and - as the name suggests - contains a port of alla lot of CFML's built-in functions to JS.

My first observation is its inclusion is completely unnecessary as I'm not using any functions in my code, and it would be reasonable to think that the <cfclient> CFML to JS compiler might notice that and save people a download. Secondly... it's a single, monolithic 106kB file. Let's have a look at it:



Well actually when I suggest we should have a look at it, I am actually inviting the JavaScript aficionados amongst us to offer their input. I'm not going to be able to lend much expert analysis to it.

Observations I will make:
  1. as I said above: including this file when it's not necessary seems wrong.
  2. Every single function is just slapped into one huge file. This to me seems equivalent to having a CFC called AllMyCode.cfc, containing... everything, including kitchen sinks and family pets.
  3. Connected to that: the functions are all declared straight in the global scope, which seems a bit "pollute-y" to me. I can't help but think they ought to be grouped together in a CF namespace, within that some sort of type-centric namespace like CF.List.listAppend(), CF.Math.abs() etc?
  4. A lot of the code seems rather "longhand", eg: someVar = SomeVar+1 instead of perhaps someVar++; using an indexed for() loop to loop over arrays etc.
  5. The file's not minified: this would almost halve its size. I dunno if this is such a consideration these days with responses being GZipped?

Is there anything else you've spotted?

I might sound a bit nit-picky here (hey: it would not be the first time, and someone needs to ask these questions), but equally if I want someone writing a wizard to write my JS for me, then I want to know that person actually knows what they're doing. And if I can raise code-quality questions... I suspect the answer is I don't want this person writing my JS for me.

But I completely accept that perhaps there's not an issue to answer here... this article is more soliciting other - more expert - people's opinions.

What do you think?

--
Adam

"ColdFusion today is a uniquely irrelevant product"

$
0
0
G'day:
Some of my elves put me onto this (no, I do not really have elves, but the person probably doesn't want to be identified as the person bringing this to my attention).

Here's a video presentation by Gojko Adžić entitled "How I Learned to Stop Worrying and Love Flexible Scope", which is about - at least in part - on how to plan a software road map. That in itself is moderately interesting. I have not watched the whole lot, as the streaming quality for me is very choppy, and it's a bit frustrating to watch/listen to.

But what is fascinating is that he's chosen Adobe ColdFusion's roadmap, and their general approach to software releases as the model "how not to" situation.

This is the document he points to:


(from this URL: "ColdFusion RoadMap" (pdf))

And this is what he has to say about it (apologies if I don't transcribe it 100% accurately... I was just stop/starting the video and noting it down). This is from about the 28:30 mark. All emphasis is mine.

An interesting thing about ColdFusion that... is really really kind of pointing towards this is that... did any here build websites in the 90s? There's a coupla people. Do you remember ColdFusion in the 90s? ColdFusion was the platform: it was Ruby on Rails times 500 in the 90s. ColdFusion today is a uniquely irrelevant product. And I like to think it's because of that: it's because they plan their roadmaps like this.

And this is brilliant kind of for many ways, because if you look at all the buzzwords today it's kind of enterprise, social, mobile and cloud. Kind of in the first they do mobile and they do enterprise. In the second they do kind of ah ColdFusion.... the third one is cloud and AWS and... the second is mobile again and kind of there's "social" somewhere over there. So it's brilliant: it's all the buzzwords in one. It's a real nice PowerPoint slide; people can agree and tap themselves on the back and then you children can go play in scrum. Come back in nine months.

So. This is not a road map. What Parchinksi [sp.?] says is kind of roadmaps should have a relationship should have a concept of selection, a concept of survivability. And this is not a roadmap. The reason why this is not a road map is because ther road map as a word has a very precise meaning. It's this [picture of a streetmap of Budapest]. The trick is in the name: it's a map of roads. It has lots of options. If I wanna cross from this side of Budapest to the other side. Which one is Buda and which one is Pest? Or is this? This is Buda, yeah? So if I want to cross to Buda from Pest, I can take this bridge or I can take that bridge or I can take that bridge. If this bridge is conjested, you know there is a traffic jam, that's OK! I can just go and take the next bridge. If everybody is expecting this bridge to be conjested so everybody goes over there [...] there is competitive advantage: I can be there earlier. It doesn't matter which one I take; there's lots of options. Now we need to be much better at planning our software like this; with roadmaps like this.

And this is not a roadmap it's a road. And this is a road that's going through a tunnel, where you go in on one end and nine months later you come out and then you realise you're at the wrong place. This is not giving people a competitive advantage. Lot's of unexpected things can happen. Meanwhile we won't even know about that. The let [road(?)] turn into competitive advantage. Where we kind of if we can plan our software roadmaps like this, we can actually get our companies to benefit from flexible scope [...]
I'm pleased he didn't know the original version of that "road map" suggested CF11 would be out last year:


And that would be more like a nine month journey through the tunnel. As it happens we're up to more like... what 15 months? It was about a year ago I joined the pre-release (and quit six weeks later in disgust), and it'd been going for a coupla months or so before I joined. That's a long tunnel journey.

Anyway, I posted this because I couldn't agree more with what Gojko says here. Adobe have completely the wrong approach to software development for the 21stC, and thet also seem to be careening headlong, lights off, through a tunnel to the wrong destination. And it's interesting that of all the possible products Gojko could have picked on here, ColdFusion was the best example of how a brilliant product became irrelevant due to mismanagement of its direction.

--
Adam

So I was out by a fortnight: ColdFusion 11 ships

$
0
0
G'day:
I originally guessed that CF11 would be released on May 12: "ColdFusion 11 release date confirmed to be no later than...", but I was wrong. It's just been released: "Announcing the launch for ColdFusion 11 and ColdFusion Builder 3".

I've not much else to add on that topic, other than to say I don't think it's ready: it needed another round of beta.

It'll be interesting to see how many of the bugs I've raised in it have been fixed. I'll start looking into that this evening.

Anyway, there you go. That's the "breaking news".

--
Adam

Viewing all 1333 articles
Browse latest View live