Showing posts with label CFML. Show all posts
Showing posts with label CFML. Show all posts

Sunday, 1 September 2024

Design patterns: singleton

G'day:

(Long time no see, etc)

The other day on the CFML Slack channel, whilst being in a pedantic mood, I pointed out to my mate John Whish (OG CFMLer) that he was using the term "singleton" when all he really meant was "an object that is reused". A brief chat in the thread and in DM ensued, and was all good. We both revisited the docs on the Singleton Pattern, and refreshed & improved our underatanding of it, and were better for it. Cool. The end. Short article.

...

..

Then the "well ackshually" crowd showed up, and engaged in a decreasingly meritorious diatribe in how there's no difference between a class that implements the Singleton Pattern, and an object that one happens to decide to reuse: they're both just singletons.

OK so the reasoning wasn't quite that daft (well from one quarter, anyhow), but the positioning was missing a degree of nuance, and there was too much doubling down in the "I'm right" dept that I just gave up: fine fellas, you do you. In the meantime I was still talking to John in DM, and I mentioned I was now keen to see how an actual singleton might come together in CFML, so there was likely a blog article ensuing. I predicted CFML would throw up some barriers to doing this smoothly, which is interesting to me; and hopefully other readers - if I have any still? - can improve their understanding of the design pattern.

I'll put to bed the "debate" first.

The notion of a singleton comes from the Singleton Pattern, which is one of the perennial GoF Design Patterns book. It's a specific technique to achieve an end goal.

What's the end goal? Basically one of code organisation around the notion that some objects are intended to be re-used, and possibly even more strongly: must be re-used in the context of the application they are running in. One should not have more than one of these objects in play. An obvious, oft-cited, and as it turns out: unhelpful, example might be a Logger. Or a DatabaseConnection. One doesn't need to create and initialise a new DatabaseConnection object every time one wants to talk to the DB: one just wants to get on with it. If one needed to instantiate the DatabaseConnection every time it was used, the code gets unwieldy, breaks the Single Responsibility Principle, and is prone to error. Here's a naïve example:

numbers = new DatabaseConnection({
    host = "localhost",
    port = "3306",
    user = "root",
    password = "123", // eeeeek
    database = "myDb"
}.execute("SELECT * FROM numbers"))

One does not wanna be doing all that every time one wants to make a call to the DB. It means way too much code is gonna need to know minutiae of how to connect to the DB. One will quickly point out the deets don't need to be inline like that (esp the password!), and can be in variables. But then you still need to pass the DB credentials about the place.

There's plenty of ways to solve this, but the strategy behind the Singleton Pattern is to create a class that controls the usage of itself, such that when an instance is called for, the calling code always gets the same instance. The key bit here is a class that controls the usage of itself[…] always[…] the same instance. That is what defines a singleton.

My derision in the Slack conversation was that the other participants were like "yeah but one can do that with a normal object just by only creating one of them and reusing it (muttermutterDIcontainer)". Yes. Absolutely. One can def do that, and often it's exactly what is needed. And DI containers are dead useful! But that's "a normal object […] and reusing it". Not a singleton. Words have frickin meanings(*). I really dunno why this is hard to grasp.

It's like someone pointing to white vase with blue art work on it, and going "this is my Ming vase". And when one then points out it says "IKEA" on the bottom, they go "doesn't matter. White with blue detail, and can put flowers in it. That's what a Ming vase is, for all intensive purposes (sic)". "You know a Ming vase is a specific sort of vase, right?". "DOESN'T MATTER STILL HOLDS FLOWERS". OK mate.

Digression, around that (*) I put above. I'm a firm believer in words are defined by their usage, not by some definition written down somewhere. A dictionary, for example, catalogues usage, it doesn't dictate usage. This is fine, but it's also less than ideal that words like "irregardless" end up in the dictionary because people are too…erm… "lacking a sense of nuance"… to get easy things right. This is pretty much where I am coming from here: "Singleton" means something; it'd be grand if the usage of it didn't get diluted due to people "lacking a sense of nuance" to get easy things right. And then to double-down on it is just intellectually-stunted, and not something I think we should be revelling in. I do also feel rather like Cnut vs the surf in this regard though.

Ah well.


Anyhoo, can I come up with a singleton implementation in CFML?

The first step of this didn't go well:

// Highlander.cfc
component {

    private Highlander function init() {
        throw "should not be runnable"
    }
}
// test.cfm    
connor = new Highlander()

writeDump(connor)

I'd expect an exception here, but both ColdFusion and Lucee just ignore the init method, and I get an object. Sigh.

This is easily worked-around, but already my code is gonna need to be less idiomatic than I'd like it to be:

public Highlander function init() {
    throw "Cannot be instantiated directly. Use getInstance"
}

Now I get the exception.

Next I start working on the getInstance method:

public static Highlander function getInstance() {
    return createObject("Highlander")
}
// test.cfm
connor = Highlander::getInstance()

writeDump(connor)

This still returns a new instance of the object every time, but it's simply a first step. To easily show whether instances are the same or different, I'm gonna given them an ID:

// Highlander.cfc
component {

    variables.id = createUuid()

    public Highlander function init() {
        throw "Cannot be instantiated directly. Use getInstance"
    }

    public static Highlander function getInstance() {
        return createObject("Highlander")
    }

    public string function getId() {
        return variables.id
    }
}
// test.cfm
connor = Highlander::getInstance()

writeDump(connor)

goner = Highlander::getInstance()

writeDump([
    connor = connor.getId(),
    goner = goner.getId()
])

 

See how the IDs are different: they're different objects.

We solve this by making getInstance only create the object instance once for the life of the class (not the object: the class).

public static Highlander function getInstance() {
    static.instance = isNull(static.instance)
        ? createObject("Highlander")
        : static.instance

        return static.instance
}

It checks if there's already an instance of itself that it's created before. If so: return it. If not, create and store the instance, and then return it.

Now we get better results from the test code:

 

Now it's the same ID. Note that this is not isolated to that request: it sticks for every request for the life of the class (which is usually the life of the JVM, or until the class needs to be recompiled). I'm altering my writeDump call slightly:

writeDump(
    label = "Executed @ #now().timeFormat('HH:mm:ss')#",
    var = [
        connor = connor.getId(),
        goner = goner.getId()
    ]
)

 

 

The ID sticks across requests. It's not until I restarts my ColdFusion container that the static class object is recreated, and I get a new ID:

 

One flaw in this implementation is that there's nothing to stop the calling code using createObject rather than using new to try to create an instance of the object. EG: this "works":

// test2.cfm
connor = createObject("Highlander")
goner = createObject("Highlander")

writeDump(
    label = "Executed @ #now().timeFormat('HH:mm:ss')#",
    var = [
        connor = connor.getId(),
        goner = goner.getId()
    ]
)

 

When I say this "works" I am setting the bar very low, in that "it doesn't error": it's not how the Highlander class is intended to be used though.

Oh: in case it's not clear why there's no exception here: it's cos when one uses createObject, the init method is not automatically called.

Can I guard against this?

Sigh.

OK, on Lucee I can do this with minimal fuss:

// Highlander.cfc
component {

    if (static?.getInstanceUsed !== true) {
        throw "Cannot be instantiated directly. Use getInstance"
    }

    variables.id = createUuid()

    public Highlander function init() {
        throw "Cannot be instantiated directly. Use getInstance"
    }

    public static Highlander function getInstance() {
        static.getInstanceUsed = true

        static.instance = isNull(static.instance)
            ? createObject("Highlander")
            : static.instance

        static.getInstanceUsed = false

        return static.instance
    }

    public string function getId() {
        return variables.id
    }
}

What's going on here? The conceit is that the class's pseudo-constructor code is only executed during object creation, and when we are creating an object via getInstance we disable the "safety" in the pseudo-constructor, but then re-enable it once we're done creating the instance. if we don't use getInstance, then the safety has either never been set - exception - or it's been set to false by an erstwhile call to getInstance - also exception.

Looking at that code, I can see that there's a race-condition potential with getInstance's setting/unsetting of the safety, so in the real world that code should be locked.

As I alluded to above: this code does not work in ColdFusion, because ColdFusion has a bug in that the pseudo-constructor is run even when running static methods, so the call to getInstance incorrectly calls the pseudo-constructor code, and triggers the safety check. Sigh. I can't be arsed coming up with a different way to work around this just for ColdFusion. I will raise a bug with them though (TBC). ColdFusion also has another bug in that static?.getInstanceUsed !== true errors if static.getInstance is null, as the === doesn't like it. I guess I'll raise that too (also TBC).

So. There we go. A singleton implementation.


PHP's OOP is more mature than CFML's, so a PHP implementation of this is a bit more succinct/elegant:

class Highlander {
    
    private ?string $id;
    
    private static self $instance;
    
    private function __construct() {
        $this->id = uniqid();
    }
    
    public function getId() : string {
        return $this->id;
    }
    
    public static function getInstance() : static {
        self::$instance = self::$instance ?? new static();
        return static::$instance;
    }
}

$connor = Highlander::getInstance();
$goner = Highlander::getInstance();

var_dump([
    'connor' => $connor->getId(),
    'goner' => $goner->getId()
]);

$transient = new Highlander();
array(2) {
  ["connor"]=>
  string(13) "66d457f9d4cd1"
  ["goner"]=>
  string(13) "66d457f9d4cd1"
}

Fatal error: Uncaught Error: Call to private Highlander::__construct() from global scope in /home/user/scripts/code.php:30
Stack trace:
#0 {main}
  thrown in /home/user/scripts/code.php on line 30

And that's that.

Righto.

--
Adam

Sunday, 17 March 2024

CFML: solving a CFML problem with Java. Kinda.

G'day:

The other day on the CMFL Slack channel, Nick Petrie asked:

Anyone know of a native or plugin-based solution to pretty-formatting XML for display on the page? I'd like to output the XML nested this:
I'll use a simplified example> we wanna render this:
<aaa><bbb ccc="ddd"><eee/></bbb></aaa>
Like this:
<aaa>
    <bbb ccc="ddd">
        <eee/>
    </bbb>
</aaa

There's no native CFML way of doing this, but I figured "this is a solved problem: there'll be an easy way to do it in Java". I googled java prettier xml, and the first match was on the Baeldung website (Pretty-Print XML in Java) which is a site I trust to have good answers. But I checked that and a few others, and they all seem to be solving the problem the same way. So I decided to run with that.

The task at hand is to convert this to CFML (still using the Java libs to do the work, I mean, just runnable in CFML):

public static String prettyPrintByTransformer(String xmlString, int indent, boolean ignoreDeclaration) {

    try {
        InputSource src = new InputSource(new StringReader(xmlString));
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src);

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        transformerFactory.setAttribute("indent-number", indent);
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, ignoreDeclaration ? "yes" : "no");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        Writer out = new StringWriter();
        transformer.transform(new DOMSource(document), new StreamResult(out));
        return out.toString();
    } catch (Exception e) {
        throw new RuntimeException("Error occurs when pretty-printing xml:\n" + xmlString, e);
    }
}

To be very clear, this is not my code, it's from Pretty-Print XML in Java.

And also to be clear: I'm not gonna be doing anything unique or difficult or insightful or anything like that in this article. All I'm gonna do is to show how easy it is to convert native Java code to native CFML code, and this is a topical use case. It's gonna be a function with some object creation and some variable assigments. I'm gonna go line-by-line through that function above, and CFMLerise it. I just made that word up.

I'm gonna be using trycf.com to write and run this code, and the aim is to have a version that runs in both CF and Lucee.

First, let's run with the same function signature:

unformattedXml = '<aaa><bbb ccc="ddd"><eee/></bbb></aaa>' 

public string function prettyPrintByTransformer(required string xmlString, numeric indent=4, boolean ignoreDeclaration=true) {

}

prettyPrintByTransformer(unformattedXml)

In each step I'll be using that same unformattedXml input value, and just running the function to ensure it's got no compilation errors and each new statement I add "works" (in that it doesn't have runtime errors). The function won't do anything useful until it's done.

In this first step note that I've given the latter two parameters sensible defaults. This is a change from the Java version.

First things first: I don't like how they've put that largely pointless try/catch in the Java code. To me that sort of error-handling should be in the calling code if needed, not embedded in the implementation. If the implementation errors-out: let it. The actual exception will be more useful than swallowing the real exception and throwing a contrived one.

I'll include each statement I'm converting as a comment above the CFML version. This is only so I can draw your focus to it. I'd never include comments of this nature in my actual code.

public string function prettyPrintByTransformer(required string xmlString, numeric indent=4, boolean ignoreDeclaration=true) {
    // InputSource src = new InputSource(new StringReader(xmlString));
    var xmlAsStringReader = createObject("java", "java.io.StringReader").init(xmlString) // new StringReader(xmlString)
    var src = createObject("java", "org.xml.sax.InputSource").init(xmlAsStringReader)
}

I could have done this without the intermediary variable, but CFML is quite cumbersome making Java object proxies, so I think the code is clearer spread over two statements.

    // Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src);
    var document = createObject("java", "javax.xml.parsers.DocumentBuilderFactory").newInstance().newDocumentBuilder().parse(src)
    // TransformerFactory transformerFactory = TransformerFactory.newInstance();
    var transformerFactory = createObject("java", "javax.xml.transform.TransformerFactory").newInstance()

Now: this statement did not work for me:

transformerFactory.setAttribute("indent-number", indent);

The CFML equivalent is exactly the same as it happens, but I was just getting "Not supported: indent-number" (CF) or "TransformerFactory does not recognise attribute 'indent-number'." (Lucee). I presume it's a library version difference, but I was fairly limited in my troubleshooting as I was running this on trycf.com. Although I did verify I was getting the same thing on my local CF2023 container too. I googled a bit and found a work around, but I'll come back to this a bit further down.

    // Transformer transformer = transformerFactory.newTransformer();
    var transformer = transformerFactory.newTransformer()
    /*
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, ignoreDeclaration ? "yes" : "no");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    */
    var outputKeys = createObject("java", "javax.xml.transform.OutputKeys")
    transformer.setOutputProperty(outputKeys.ENCODING, "UTF-8")
    transformer.setOutputProperty(outputKeys.OMIT_XML_DECLARATION, ignoreDeclaration ? "yes" : "no")
    transformer.setOutputProperty(outputKeys.INDENT, "yes")
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", indent)

Once again, I need an intermediary variable here for the outputKeys class proxy. Just to save repetition in this case.

Also note the last line there is extra: it's the solution for the bit that wasn't working earlier. Easy.

    // Writer out = new StringWriter();
    var out = createObject("java", "java.io.StringWriter").init()
    // transformer.transform(new DOMSource(document), new StreamResult(out));
    var documentAsDomSource = createObject("java", "javax.xml.transform.dom.DOMSource").init(document)
    var outAsStreamResult = createObject("java", "javax.xml.transform.stream.StreamResult").init(out)
    transformer.transform(documentAsDomSource, outAsStreamResult)

More intermediary variables here.

    // return out.toString();
    return out.toString()

And that's it. I've not got much commentary in all that lot, because it's all so straight forward [shrug].

The end result in one piece is thus:

public string function prettyPrintByTransformer(required string xmlString, numeric indent=4, boolean ignoreDeclaration=true) {
    var xmlAsStringReader = createObject("java", "java.io.StringReader").init(xmlString)
    var src = createObject("java", "org.xml.sax.InputSource").init(xmlAsStringReader)
    
    var document = createObject("java", "javax.xml.parsers.DocumentBuilderFactory").newInstance().newDocumentBuilder().parse(src)
    
    var transformerFactory = createObject("java", "javax.xml.transform.TransformerFactory").newInstance()
    var transformer = transformerFactory.newTransformer()
    
    var outputKeys = createObject("java", "javax.xml.transform.OutputKeys")
    transformer.setOutputProperty(outputKeys.ENCODING, "UTF-8")
    transformer.setOutputProperty(outputKeys.OMIT_XML_DECLARATION, ignoreDeclaration ? "yes" : "no");
    transformer.setOutputProperty(outputKeys.INDENT, "yes")
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", indent)
    
    var out = createObject("java", "java.io.StringWriter").init()
    
    var documentAsDomSource = createObject("java", "javax.xml.transform.dom.DOMSource").init(document)
    var outAsStreamResult = createObject("java", "javax.xml.transform.stream.StreamResult").init(out)
    transformer.transform(documentAsDomSource, outAsStreamResult)

    return out.toString()
}

And a coupla test runs:

formattedXml = prettyPrintByTransformer(unformattedXml)
writeOutput("<pre>#encodeForHtml(formattedXml)#</pre>")
<aaa>
    <bbb ccc="ddd">
        <eee/>
    </bbb>
</aaa>
formattedXml = prettyPrintByTransformer(unformattedXml, 8, false)
writeOutput("<pre>#encodeForHtml(formattedXml)#</pre>")
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<aaa>
        <bbb ccc="ddd">
                <eee/>
        </bbb>
</aaa>

The output above is from Lucee. On CF we get this:

<?xml version="1.0" encoding="UTF-8"?><aaa>
        <bbb ccc="ddd">
                <eee/>
        </bbb>
</aaa>

Note this is nothing to do with the CFML code, it'll be some Java library variation on the Lucee and CF set-ups on trycf.com.

Right, so all this lot shows is that there's no reason to shy away from implementing a CFML version of some Java code you might find that solves a problem. So in turn, if you have some "algorithmic" issue that you wanna solve in CFML, don't shy away from googling for Java solutions, and checking how easy/hard it is to convert.

A runnable version of this code is @ trycf.com.

Righto.

--
Adam

Monday, 31 October 2022

CFML: AND and OR operators not doing what one might expect

G'day:

A question came up on the CFML Slack forums today about some "unexpected behaviour" with ColdFusion's and operator. Here's an example (CF2021):

cf-cli>5 && 7
7

cf-cli>5 || 7
5

Compared to Lucee:

CFSCRIPT-REPL: 5  && 7
true
CFSCRIPT-REPL: 5 || 7
true

The questioner was puzzled by ColdFusion's behaviour, expecting to see something more like Lucee's result.

The docs for these (Expressions-Developing guide › Boolean operators) says this:

AND or &&
Return True if both arguments are True; return False otherwise. For example, True AND True is True, but True AND False is False.

On the face of it Lucee is getting it right, and ColdFusion is being weird, however this is a case of Lucee following the docs, and the docs being wrong.

CFML's behaviour with the AND and OR operators is not quite as the docs say, they behave the way described clearly in the JavaScript docs (JavaScript › Expressions and operators › Logical AND (&&)):

More generally, the operator returns the value of the first falsy operand encountered when evaluating from left to right, or the value of the last operand if they are all truthy.

I only cite the JS docs cos they're clear and well-worded, not because of any connection to CFML.

I guess it works like this in languages with truthy/falsy values as opposed to strict true/false because the operand-values can have meaning beyond just being boolean.

I checked some other languages I had to hand:

adam@DESKTOP-QV1A45U:~$ node -i
> 5 && 7
7
> 5 || 7
5

(And JS run in the browser is the same)

Ruby:

adam@DESKTOP-QV1A45U:~$ irb
irb(main):001:0> 5 && 7
=> 7
irb(main):002:0> 5 || 7
=> 5
irb(main):003:0>

Groovy:

groovy:000> 5 && 7
===> true
groovy:000> 5 || 7
===> true
groovy:000>

PHP:

php > echo 5 && 7;
1
php > echo 5 || 7;
1

Clojure:

user=> (and 5 7)
7
user=> (or 5 7)
5

There's a strong precedent there, but obviously it could have gone either way. CFML just went the way shown. So it's a bug in Lucee that it doesn't work the same as ColdFusion.

BTW: ColdFusion has worked that way since at least CF5. I just checked.

Why am I writing this? Well cos it's not correctly documented anywhere, so I figured I'd jot something down in the hope Google picks it up.

Righto.

--
Adam

Saturday, 29 October 2022

CFML: addressing confusion around arrays returned from Java methods and using them with CFML code

G'day:

This has come up often enough that it's probably worth having something clear and googleable around for people to find when this crops up for them.

Context

Sometimes it's useful to call a Java method on a CFML object to benefit from functionality that's in Java that's not in CFML. Being able to call split to split a CFML string on a regular expression pattern is a good example:

string = "abcde"
arrayOfChars = string.split("")
writeDump(arrayOfChars)

Lovely.

Problem

So what's the problem? Often there isn't one. However for people who prefer using CFML's object.method() syntax rather than its function(object) syntax, they might get a surprise:

nextChars = arrayOfChars.map((char) => chr(asc(char) + 1))

This results in:

The map method was not found.
Either there are no methods with the specified method name and argument types
or the map method is overloaded with argument types that ColdFusion cannot decipher reliably.
ColdFusion found 0 methods that match the provided arguments.
If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity.

Or Lucee (less info, slightly more helpful as to what the issue is):

No matching Method/Function for [Ljava.lang.String;.map(lucee.runtime.type.Lambda) found

What's the problem? Look at the Lucee error. It's pointing out that arrayOfChars is a [Ljava.lang.String (a Java String[]). So that code is trying to call CFML's map method on a Java String[]. Java String[] doesn't have a map method. one has to recall that when one is using object.method() syntax then the type of the object is what dictates what methods can be called. It's not the same with function(object) when function's implementation can take a "close enough" type and cast it to the type it actually needs.

"Workaround"

It's important to note that this would work no worries:

nextChars = arrayMap(arrayOfChars, (char) => chr(asc(char) + 1))

Solution

However sometimes one might want an actual CFML array (so the object passes type-checks, etc), and this is easy enough to achieve:

arrayOfChars = arrayNew(1).append(arrayOfChars, true)

Here we are creating a CFML array, and then using its append method - which will take a Java array and cast it to a CFML array internally - before appending it to the array it's being called on. Job done. Oh the true argument there just means to append the arrays together, rather than appending the first argument into the last element of the first array. Try it yerself to see the difference: pass it false instead.

Summary / proof

string = "abcde"
arrayOfCharsFromSplit = string.split("")

arrayOfCharsAfterAppend = arrayNew(1).append(arrayOfCharsFromSplit, true)

nextChars = arrayOfCharsAfterAppend.map((char) => chr(asc(char) + 1))

writeDump([
    values = [
        arrayOfCharsFromSplit = arrayOfCharsFromSplit,
        arrayOfCharsAfterAppend = arrayOfCharsAfterAppend,
        nextChars = nextChars
    ], types = [
        arrayOfCharsFromSplit = arrayOfCharsFromSplit.getClass().getName(),
        arrayOfCharsAfterAppend = arrayOfCharsAfterAppend.getClass().getName(),
        nextChars = nextChars.getClass().getName()
    ]
])

And the code is in a trycf.com gist today.

Righto.

--
Adam

Wednesday, 14 September 2022

CFML: working MySQL datasource in Application.cfc (this is just a note-to-self/Google)

G'day:

No content in this one, I just want something I can find when I search for how to config a MySQL datasource in Application.cfc in ColdFusion. I always forget/lose the code, and I can never find anything on Google. It'd be marvellous of Adobe would document this stuff, but it's a bit much to expect of them, I guess.

component {

    setsettings()
    loadDatasources()

    private void function setSettings() {
        this.name = "app1"
    }

    private void function loadDataSources() {
        this.datasources["dsn1"] = {
            driver = "mysql",
            class = "com.mysql.jdbc.Driver",
            url = "jdbc:mysql://database.backend:3306/"
                & "#server.system.environment.MARIADB_DATABASE#"
                & "?useUnicode=true&characterEncoding=UTF-8",
            username = server.system.environment.MARIADB_USER,
            password = server.system.environment.MARIADB_PASSWORD
        }
        this.datasource = "dsn1"
    }
}

The bit I always forget is the class bit, so I end up with an error along these lines:

No driver or URL found.

java.sql.SQLException: No driver or URL found.
 at coldfusion.server.j2ee.sql.pool.JDBCPool.createPhysicalConnection(JDBCPool.java:586)
 at coldfusion.server.j2ee.sql.pool.ConnectionRunner$RunnableConnection.run(ConnectionRunner.java:67)
 at java.base/java.lang.Thread.run(Thread.java:834)

This is also on GitHub @ Application.cfc.

Righto.

--
Adam

Thursday, 8 September 2022

CFML: speaking of application scope proxies

G'day:

OK so you probably weren't talking about application scope proxies, but I was in my previous article: CFML: looking at how CFWheels messes up a loop. In that article I look at some very uncontrolled (and buggy: hence the article) application-scope access. And I made the observation that one should never access the application scope in one's application code, other than via a proxy.

This got me thinking. How hard is it actually to write one of these proxies that handles the locking for you, keeps yer code testable, and minimises the chance of having shared scope errors? All whilst minimising locking boilerplace and stuff.

Turns out it's pretty simple to get a proof of concept together (code on Github @ ApplicationScopeProxy.cfc / ApplicationScopeProxyTest.cfc):

component {

    public function init(struct applicationScope) {
        variables.applicationScope = arguments.applicationScope
    }

    public function set(required string key, required any value) {
        lock scope="application" type="exclusive" timeout=5 throwontimeout=true {
            "variables.applicationScope.#key#" = value
        }
    }

    public any function get(required string key) {
        lock scope="application" type="readonly" timeout=5 throwontimeout=true {
            if (isDefined("variables.applicationScope.#key#")) {
                return structGet("variables.applicationScope.#key#")
            }
            throw(type="ApplicationScopeProxy.KeyNotFoundException", message="Key [#key#] not found in application scope");
        }
    }

    public void function withReadOnlyLock(required function task) {
        lock scope="application" type="readonly" timeout=5 throwontimeout=true {
            task()
        }
    }

    public void function withExclusiveLock(required function task) {
        lock scope="application" type="exclusive" timeout=5 throwontimeout=true {
            task()
        }
    }
}

And it's got superficial first-pass red/green tests:

import testbox.system.BaseSpec
import cfml.cfmlLanguage.scopes.ApplicationScopeProxy

component extends=BaseSpec {

    function run() {
        describe("Tests of ApplicationScopeProxy", () => {
            describe("Tests for the set method", () => {
                it("sets a variable", () => {
                    testApp = {}
                    proxy = new ApplicationScopeProxy(testApp)
                    proxy.set("testKey", "TEST_VALUE")

                    expect(testApp).toHaveKey("testKey")
                    expect(testApp.testKey).toBe("TEST_VALUE")
                })

                it("sets a variable with a deep path", () => {
                    testApp = {}
                    proxy = new ApplicationScopeProxy(testApp)
                    proxy.set("one.two.three", "TEST_VALUE")

                    expect(testApp).toHaveKey("one")
                    expect(testApp.one.two.three).toBe("TEST_VALUE")
                })
            })

            describe("Tests for the get method", () => {
                it("gets a variable", () => {
                    testApp = {testKey = "TEST_VALUE"}
                    proxy = new ApplicationScopeProxy(testApp)
                    result = proxy.get("testKey")

                    expect(result).toBe("TEST_VALUE")
                })

                it("gets a variable with a deep path", () => {
                    testApp = {one={two={three = "TEST_VALUE"}}}
                    proxy = new ApplicationScopeProxy(testApp)
                    result = proxy.get("one.two.three", "TEST_VALUE")

                    expect(result).toBe("TEST_VALUE")
                })

                it("throws an exeption if the key is not found", () => {
                    testApp = {}
                    proxy = new ApplicationScopeProxy(testApp)

                    expect(() => {
                        proxy.get("one.two.three", "TEST_VALUE")
                    }).toThrow(
                        type = "ApplicationScopeProxy.KeyNotFoundException",
                        regex = "Key \[one\.two\.three\] not found in application scope"
                    )
                })
            })

            describe("Tests for the withReadOnlyLock method", () => {
                it("waits until it can get a lock before continuing", () => {
                    proxy = new ApplicationScopeProxy({})

                    telemetry = ["test start"]
                    t1 = runAsync(() => {
                        telemetry.append("t1 started")
                        lock scope="application" type="exclusive" timeout=5 throwontimeout=true {
                            sleep(1000)
                        }
                        telemetry.append("t1 ended")
                    })
                    t2 = runAsync(() => {
                        telemetry.append("t2 started")

                        proxy.withReadOnlyLock(() => {
                            telemetry.append("withReadOnlyLock ran")
                        })

                        telemetry.append("t2 ended")
                    })
                    t1.get()
                    t2.get()

                    lockTelemetry = telemetry.find("withReadOnlyLock ran")
                    t1EndTelemetry = telemetry.find("t1 ended")
                    expect(lockTelemetry).toBeGt(t1EndTelemetry, serializeJson(telemetry))
                })
            })

            describe("Tests for the withExclusiveLock method", () => {
                it("waits until it can get a lock before continuing", () => {
                    proxy = new ApplicationScopeProxy({})

                    telemetry = ["test start"]
                    t1 = runAsync(() => {
                        telemetry.append("t1 started")
                        lock scope="application" type="readlonly" timeout=5 throwontimeout=true {
                            sleep(1000)
                        }
                        telemetry.append("t1 ended")
                    })
                    t2 = runAsync(() => {
                        telemetry.append("t2 started")

                        proxy.withExclusiveLock(() => {
                            telemetry.append("withExclusiveLock ran")
                        })

                        telemetry.append("t2 ended")
                    })
                    t1.get()
                    t2.get()

                    lockTelemetry = telemetry.find("withExclusiveLock ran")
                    t1EndTelemetry = telemetry.find("t1 ended")
                    expect(lockTelemetry).toBeGt(t1EndTelemetry, serializeJson(telemetry))
                })
            })
        })
    }
}

The tests kinda summarise its usage, but there's four methods (five including constructor):

  • Constructor: pass in the scope from the application-boot code (from the DI config or just in onApplicationStart etc). This way even the proxy is uncoupled from the scope. It also aids testing, which you'll've noticed in the tests.
  • set - sets a value in the scope at the location of the specified key. The key can be a dotted-path, and that will be unpacked into substructs. Is run in an exclusive lock.
  • get - as above, but gets it back out again. Is run in a read-only lock.
  • withReadOnlyLock - to avoid race conditions like in the CFWheels code, this takes a block of code and runs it all in a read-only lock. Good for multiple accesses to the application scope that rely on the state not being changed between each access.
  • withExclusiveLock - same as above, except with an exclusive lock. This would handly situations where code is reading and writing to the scope, and needs to be run as an atom before other accesses try to act on the data themselves.

This is just a proof of concept I knocked together in about 1hr without giving too much thought to it (the best way to write code to present to the public as if I know what I'm doing ;-)). There is some room for improvement, obvs:

  • It's not for the methods themselves to set the timeout / throwontimeout values on the locks. I just set those for the sake of expediency. A real-world implementation would likely pass those with each call, and probably optionally fall back to values set by init, or just the default for lock (if there is one? I think maybe not, for timeout).
  • If the returned value is a complex object, then it'll still be prone to race conditions as it'll just be a reference to the underlying shared value. So one would either need to advise to use duplicate when fetching the value, or make it an option on the get, or have a getCopy method too.
  • Having methods like exists, delete, etc would be handy too.
  • Maybe some mechanism to pass in a callback that receives variables.applicationScope as an argument, to do more complex stuff, and the implementation of that just ensures the lock is made?

There'd be plenty of options. The good thing with TDD and incremental delivery, is that one can just deliver what's needed now, and worry about the rest later.

What CFWheels needs now is that withExclusiveLock method, I think…

Righto.

--
Adam

Sunday, 4 September 2022

CFML: invokeImplicitAccessor on CFCs

G'day:

I noticed this yesterday when I was writing my "Kotlin: the next morning learning Kotlin stuff" article. I was looking into how Kotlin handles accessor methods on properties, and remembered CFML had a similar bit of functionality, and quickly revisited it by way of comparison. Whilst looking into it, I noticed that pretty much no-one has mentioned it too much, and it's not even mentioned in the appropriate place in Adobe's on CFML reference for cfcomponent. They only mention it on CFC Implicit notation, somewhat amusingly using code they have clearly lifted from this blog (their example code mentions my son by name). The article they lifted it from is about the original variant of the functionality: "invokeImplicitAccessor is quite cool. Although has some odd quirks". CFDocs mentions it in passing in their cfcomponent docs, but don't really explain it. Lucee doesn't yet support this functionality (see LDEV-171).

Back in ColdFusion 10, a feature was added to Application.cfc that one could specify this.invokeImplicitAccessor=true. This is poorly documented - no mention of it on the Application.cfc page in the docs for example - but I run through its functionalityin that "invokeImplicitAccessor is quite cool. Although has some odd quirks" I mentioned earlier. I'll not repeat it here. Setting this at application-level was always a poor implementation, as how code behaves should be handled in the code itself, not in some application settings. This implementation renders the functionality pretty much unusable in third-party modules (eg: libs one might install and use from ForgeBox), as it relies on application settings that might not exist. I guess the docs for the lib could say "you need to switch this on in Application.cfc", but even if that works, it's a shit way of going about things. This was raised with Adobe at the time the feature was going into the language, but the leadership of the ColdFusion Team in those days had a habit of not listening to their developer community, because they thought they knew better. Sigh.

Anyhow, they kinda fixed the situation in CF2016; they shifted the setting to be CFC-specific. So in a given CFC one can specify this in the component's properties. Here's a quick example:

//Person.cfc
component invokeImplicitAccessor=true {

    property string firstName;
    property string middleName;
    property string lastName;

    public void function setFirstName(required string value) {
        firstName = value
    }

    public void function setMiddleName(required string value) {
        middleName = value
    }

    public void function setLastName(required string value) {
        lastName = value
    }

    public string function getFullName() {
        return "#firstName# #middleName# #lastName#"
    }
}
// test.cfm
person = new Person()
person.firstName = "Annie"
person.middleName = "Jean"
person.lastName = "Easley"

writeOutput(person.fullName)

Result:

Annie Jean Easley

What's going on? Well it's a syntactical sugar / boilerplate reduction thing. It's just a way of exposing private properties publically without having to pepper getThing / setOtherThing etc throughout one's code. It's like a more thoughtful approach to the fairly wooden synthesised accessors that were already in CFML with the accessors CFC setting, and getter and setter options on the property definition. Those are a bit of an anti-pattern.

Just to demonstrate there's no cheating going on, we can try to access one of the write-only properties directly:

try {
    writeOutput(person.firstName)
} catch(any e) {
    writeOutput(e.message)
}

We have not defined a getter on firstName, so it's not accessible:

Element firstName is undefined in PERSON.

The implementation Adobe have given us here is still far from ideal. If they'd thought things through more, they'd've paid attention to how other languages handle properties' accessor methods, which would be more like this in a CFML-idiomatic sort of way:

property string variables.firstName {
    function get() {
        return firstName
    }
    function set(required string value){
        firstName = value
    }
}

There's no reason they could not have done that. Even if they needed to use the long-form / tag-oriented version of property:

property name="firstName" access="private" type="string" {
    function get() {
        return firstName
    }
    function set(required string value){
        firstName = value
    }
}

I mean that syntactical construct - a statement with a body block - is already supported in CFML. Just not the implementation of this specific functionality.

Anyway. We are where we are, and I figured someone ought to document this functionality. Done.

Righto.

--
Adam

Wednesday, 31 August 2022

CFML: outputting text from within inline Java in CFML

G'day:

I saw an odd question today. Me mate Ray was messing around with the Java integration in ColdFusion (that allows one to write Java code directly in one's CFML code), and asked how to write out content to the screen from within the Java code. IE: the equiv of emiting some text in one's CFML response… it ends up on the client browser "screen" (this is not my wording).

My initial reaction was "isn't the answer System.out.println("G'day Ray!")?". But it's not. System.out writes to stdout. Not the response buffer. In my Docker container if I do that println, I get it in my Docker log, eg:

OK so it's not a dumb question.

I knew the answer would be "stick it in the PageContext's BodyContent", but I had no idea of how to get a reference to that via Java. Especially basically a Java snippet running inside a ColdFusion request. I googled a lot and did not find anything I understood (my Java is shit).

So I cheated. ColdFusion knows how to get the current PageContext: via getPageContext(). I'll just pass that in to my Java code:

Warning

This code runs fine in the version of CF that is installed from the adobecoldfusion/coldfusion2021:latest Docker image, but does not run on a local install of CF (even the "exact same version"). It will give you a The setPageContext method was not found. error. I'm not sure why, but I managed to grab the ear of one of the Adobe bods, and they are actively looking into it. I'll report back when we get an answer on this.

messager = java {

    import javax.servlet.jsp.PageContext;

    public class Outputer {

        PageContext pageContext;

        public void output(String text) throws java.io.IOException {
            this.pageContext.getOut().print(text);
        }

        public void setPageContext(PageContext pageContext) {
            this.pageContext = pageContext;
        }
    }
}

messager.setPageContext(getPageContext())
writeOutput("Before stuff added from Java<br>")
messager.output("G'day Ray!<br>")
writeOutput("After stuff added from Java<br>")

And this works:

Before stuff added from Java
G'day Ray!
After stuff added from Java

I dunno if it's a good way of doing it, but it's "a" way.

I'd still prefer knowing how to get the PageContext directly from the Java code, if anyone knows…?

Righto.

--
Adam

Thursday, 11 August 2022

Lucee: Creating a log file programmatically

G'day:

This will be super short as there's not much to say, I just want to note the code down for posterity, and to cross-reference elsewhere.

We need to create some log files in our application. I knew in ColdFusion one can just create a file ad-hoc, thus:

writeLog(file="anythingYouLike", text="some log message")

However the Lucee docs for writeLog were claiming that the file option was not implemented, and was also deprecated (this has since been rectified in the docs). It actually is implemented, but I was not so sure about the deprecation status. I asked and got no straight answer really. Anyway, the docs now say it's all good, so that's something.

Whilst this works, the log file is not managed by Lucee: it doesn't show up in Lucee Admin, so one cannot set things like log level / file size / file retention etc. This is fine for some situation, but was not fine for us. We need to be able to control the log level.

I had a sniff around, and found org.lucee.cfml.Administrator, and looking some more, found Configure Lucee within your application. From that, I concocted this proof of concept:

local.admin = new Administrator("web", server.system.environment.ADMINPASSWORD)
admin.updateLogSettings(
    name = "AdamTest",
    level = "ERROR",
    appenderClass = "lucee.commons.io.log.log4j2.appender.ResourceAppender",
    layoutClass = "lucee.commons.io.log.log4j2.layout.ClassicLayout",
    appenderArgs = {
        maxfiles = 20,
        maxfilesize = 1073741824
    }
)
writeLog(log="AdamTest", type="ERROR", text="ERROR SHOULD BE LOGGED")
writeLog(log="AdamTest", type="INFO", text="INFO SHOULD NOT BE LOGGED")

And - hurrah - this does exactly what it looks like it does: all the settings are set correctly and respected. Most importantly for us is that the log level works: in this case logging an ERROR works, but any logs at level INFO are not logged. We have some debugging stuff we want to leave log entries in the codebase, but in general set the log to be ERROR so they don't log until we need to do some debugging.

I had to guess at the appenderArgs key values, but on a whim I assumed they'd be the same as the form fields in Lucee admin, and they kinda were:

<input name="custom_3_appender_maxfiles" type="text" class="large" value="0">
<!-- … -->
<input name="custom_3_appender_maxfilesize" type="text" class="large" value="16">

It also shows up in Admin correctly:

Perfect.

And that's all I have to say on this.

Righto.

--
Adam

Monday, 30 May 2022

CFML: Implementing an ObservableScopeAdapter using the Adapter Pattern, Decorator Pattern and Observer Pattern

G'day:

In my last article (A super-quick Observer Pattern implementation in CFML, and I skip TDD. Or do I?), I did what it suggests: I created a very simple observer pattern implementation in CFML.

Why? Well: to copy and paste from that article:

A few days back I was chatting to someone about application-scope-usage, and how to trigger an event when any changes to the application scope took place. I mentioned that the application scope should never be accessed directly in one's application code, and it should be wrapped up in an adapter. And it's easy for one's adapter to trigger events if needs must. An implementation of the Observer Pattern would do the trick here.

I wanted to keep the article brief-ish and on-point, so just focused on one part of it. In this article I'll put that observer service to use in an application scope adapter.

Firstly I need a scope adapter. The interface to this will be the same for any scope, so I'm gonna keep the name generic.

Well: firstly I need some tests to clearly define how the thing should work:

describe("Tests for set method", () => {
    it("sets a key's value in the scope", () => {
        var scopeAdapter = new ScopeAdapter(local)

        scopeAdapter.set("testVariable", "test value")

        expect(local).toHaveKey("testVariable")
        expect(local.testVariable).toBe("test value")
    })
})

describe("Tests for get method", () => {
    it("gets a value from the scope by its key", () => {
        var scopeAdapter = new ScopeAdapter(local)
        var testVariable = "test value"

        var result = scopeAdapter.get("testVariable")

        expect(result).toBe("test value")
    })
})

And a very simple implementation:

component {

    public function init(required struct scope) {
        variables.scope = arguments.scope
    }

    public void function set(required string key, required any value) {
        variables.scope[key] = value
    }

    public any function get(required string key) {
        return variables.scope[key]
    }

}

And that works. In real life before this was production-ready we'd probably want some error-handling around trying to get values that aren't set, and poss something to handle overwriting values etc. But for my purposes here: this will do.

When I first planned the code for this article in my head, the next step was gonna be to initialise the ScopeAdapter with an ObserverService object, and have it fire off some events on set. Then it occurred to me that this is breaking the Single Responsibility Principle a bit. It's not for the general ScopeAdapter to be doing anything other than being an adapter to a scope. So what I'm going to do is to use the Decorator Pattern: I'm going to create a decorator for a ScopeAdapter that is an ObservableScopeAdapter.

I need to back up slightly and do a slight refactor. If I'm using a decorator, then I need to be able to code to an interface, not to an implementation. I can't be having code that takes specifically a cfml.forBlog.applicationScopeAdapter.ScopeAdapter object; I need it to take an implementation of a cfml.forBlog.applicationScopeAdapter.ScopeAdapter interface. This is because when using a decorator, obviously it's no longer the same concrete implementation class. EG: if I have SomeService class, and it takes as one of its constructor parameters a ScopeAdapter object, I can't then initialise it with an ObservableScopeAdapter. One might think this is solved by making ObservableScopeAdapter simply extend ScopeAdapter. This would work, but it's a bit of an anti-pattern, and I discuss this in "Decorator Pattern vs simple inheritance". Instead we provide an interface for both ObservableScopeAdapter and the current ScopeAdapter (which will need to be renamed) to implement, and we make our SomeService take one of those as its argument.

I'm going to rename ScopeAdapter to be GeneralScopeAdapter, and create an interface that it implements called ScopeAdapter. Yay for tests, as I am refactoring here, not just "changing shit". The only new code here is the interface:

interface {
    public void function set(required string key, required any value);
    public any function get(required string key);
}

Now we want our decorator. It will do two things:

  • Hand off any calls to methods in the ScopeAdapter interface to a full implementation of a ScopeAdapter it has been configured with
  • When set is called, it triggers an event that other code can have subscribed to.

"Hang on", you might say "isn't it doing two things, which is still a violation of the SRP?". Not really. It's only implementing the event trigger part. It's delegating the scope-adapting to its dependent GeneralScopeAdapter. Indeed it's not even implementing the event-triggering side of things. It's delegating that to the ObserverService. So the one thing the decorator is implementing is "making the scope access observable". Make sense?

Anyway here are some tests to set our expectations:

import cfml.forBlog.applicationScopeAdapter.GeneralScopeAdapter
import cfml.forBlog.applicationScopeAdapter.ObservableScopeAdapter
import cfml.forBlog.observerService.SimpleObserverService

component extends=Testbox.system.BaseSpec {
    function run() {
        describe("Tests for ObservableScopeAdapter", () => {
            describe("Tests for set method", () => {
                it("functions as a ScopeAdapter when setting a key's value in the scope", () => {
                    // same implementation as above
                })

                it("triggers an event when set is called, which receives the key/value of the set call", () => {
                    var eventLog = []
                    var localScopeAdapter = new GeneralScopeAdapter(local)
                    var observerService = new SimpleObserverService()
                    var observableScopeAdapter = new ObservableScopeAdapter(localScopeAdapter, observerService)

                    observerService.on("scope.set", (event) => {
                        eventLog.append(event)
                    })

                    observableScopeAdapter.set("testVariable", "test value")

                    expect(eventLog).toHaveLength(1)
                    expect(eventLog[1]).toBe({
                        name = "scope.set",
                        data = javaCast("null", ""),
                        detail = {
                            key = "testVariable",
                            value = "test value"
                        }
                    })
                })
            })

            describe("Tests for get method", () => {
                it("functions as a ScopeAdapter when getting a value from the scope by its key", () => {
                    // same implementation as above
                })
            })
        })
    }
}

Notes:

  • I've changed the name of the ObserverService to be SimpleObserverService because I've extracted an interface for that too, and now that is called ObserverService. I didn't need to do this, but I figured it was more balanced, and "one should program to an interface", etc
  • Where I say same implementation as above, it's literally the same implementation as the earlier tests, because I want to test that the decorated implementation still behaves the same.
  • data is data passed along with the on call, which I am not using here.
  • I had actually neglected to add the feature of trigger to accept details (ie: data) at trigger-time in my initial implementation of BasicObserverService! So I had to add that functionality in. See below.

There's not much to the implementation:

import cfml.forBlog.applicationScopeAdapter.GeneralScopeAdapter
import cfml.forBlog.observerService.ObserverService


component implements=ScopeAdapter {

    public function init(required GeneralScopeAdapter scopeAdapter, ObserverService observerService) {
        variables.scopeAdapter = arguments.scopeAdapter
        variables.observerService = arguments.observerService
    }

    public void function set(required string key, required any value) {
        variables.observerService.trigger("scope.set", arguments)
        variables.scopeAdapter.set(argumentCollection=arguments)
    }

    public any function get(required string key) {
        return variables.scopeAdapter.get(argumentCollection=arguments)
    }

}
  • It implements the same interface as GeneralScopeAdapter, so it can be used anywhere that one of those is required.
  • It's initialised with an underlying GeneralScopeAdapter that does all the scope-adapting, as well as an ObserverService which does all the event handling.
  • get simply proxies to the underlying GeneralScopeAdapter
  • set does that too, but not before triggering an event that it's been called.

As I touched on above, I needed to add a feature to SimpleObservrService so that it passed detail data when triggering an event. Here's the test:

it("passes any extra details as part of the event", () => {
    var observerService = new SimpleObserverService()

    var eventResults = []

    observerService.on("testEvent", (event) => {
        eventResults.append([
            message = "test event handler 1",
            event = event
        ])
    }, {key="value set in handler"})

    observerService.trigger("testEvent", {
        key = "value set by trigger"
    })
    expect(eventResults).toBe([[
        message = "test event handler 1",
        event = {
            name = "testEvent",
            data = {key = "value set in handler"},
            detail = {key = "value set by trigger"}
        }
    ]])
})

That's self-explanatory I think.

And implementation:

public boolean function trigger(required string event, struct detail={}) {
    registerEvent(event)
    return variables.eventRegistry[event].some((eventData) => eventData.handler({
        name = event,
        data = eventData?.data,
        detail = detail
    }) == false)
}

This broke one other test, but in an expected way because it was testing what came back in the event struct, focusing on testing the data part, but it did not expect the new (empty) detail part of the data. All the other tests continued to pass, so I know this change has no unexpected side-effects.


That's it, all working. But how does one wire this into one's app? Well. Any place one currently accesses the application scope (remember the initial requirement was to trigger events when there were changes to the application scope) directly in the app needs to be swapped out for an instance of ObservableScopeAdapter that is adapting the application scope. This can be done easily enough with a DI framework: just inject it into whichever objects formerly accessed the application scope directly, and then use it. Secondly: wherever it is relevant to react to the "scope.set" event: pass in an instance of ObserverService, and attach an event handler to the "scope.set" event that does whatever one needs to. Obviously (?) it needs to be the same instance of ObserverService that the ObservableScopeAdapter is using. Again: super easy with your DI framework.

I realise this section is a bit like this:

Possibly © 9gag.com - I am using it without permission. If you are the copyright owner and wish me to remove this, please let me know.

But it's not really in scope here to explain how dependency injection works. You should already know.


The original question is begging slightly though. It presupposed the situation where there's application-scope access all over the place in an application. In a well-designed app, there simply shouldn't be. Everything ought to be managed by a DI container, and no actual objects ought to know what scope they are in, or care what scope their dependency objects are in. And data values that need to persist for the life of the application should just be in objects that persist for the life of the application, so this should not directly involve the application scope at all.

The same applies for any scope in your CFML code. The only scopes that should generally be relevant to your code are:

  • The variables scope of the CFC the code is in; reflecting the state of the object, or its dependencies, etc
  • The arguments scope of any argument values being passed into your function.
  • The local scope within your function.
  • One is possibly tied to some thread-specific scopes if using cfthread

That's it. If yer accessing any scopes other than those, you should be questioning the design of your application, I think.

The code for this lot is in Github @ /forBlog/applicationScopeAdapter (and its tests) and /forBlog/observerService (and its tests).

Righto.

--
Adam

Tuesday, 17 May 2022

If yer a CFML dev, you should consider financially supporting trycf.com

G'day:

I'll keep this on-point today.

If you are a CFML developer, you will be aware and likely use trycf.com. Whenever I have an issue with some CFML that needs to be demonstrated to someone else; eg: I'm asking for help on Slack or Stack Overflow, or demonstrating an answer to someone else's question: I create a portable / repeatable repro case on trycf.com. I use it to demonstrate bugs and behavioural differences to Adobe or Lucee when both vendors don't give the same result from the same code. I use it every day.

I believe trycf.com is the handiest resource available to CFML developers.

Until now Abram has done all the work to create and maintain trycf.com at his own expense, both financially and with his own time. I reached out to him a coupla days ago wanting to see if I could help-out financially, and he has set up a Patreon page for this very reason. His blurb is:

And the Patreon link is: https://www.patreon.com/trycf/posts.

If you don't want to do a monthly payment, there's also an option to flick him some dosh on a one off basis: https://buy.stripe.com/28o8y6dzHh1bezucMM

Both those links are on the trycf.com homepage.

Go give him some money. If you're the purse-string holder at a company: consider giving him some money on behalf of your company too, to make yourselves better CFML Community participants.

Cheers.


Whilst I'm talking about money, don't forget you perhaps also ought to be supporting Lucee. I wrote about this before: If your company (or yourself) makes money using Lucee… you should throw them a bone. Consider supporting them as well if you haven't already.

Righto.

--
Adam

Friday, 13 May 2022

CFML: adding aroundEach to TinyTestFramework was way easier than I expected

G'day:

I'm still pottering around with my TinyTestFramework. Last night I added beforeEach and afterEach handlers, but then thought about how the hell I could easily implement aroundEach support, and I could only see about 50% of it, so I decided to sleep on it.

After a night's sleep I spent about 30min before work doing a quick spike (read: no tests, just "will this even work?"), and surprisingly it did work. First time. Well except for a coupla typos, but I nailed the logic first time. I'm sorta halfway chuffed by this, sorta halfway worried that even though what I decided would probably work - and it did - I haven't quite got my head around how it works, or even quite what it's doing. So let's blog about that.

This evening after work I ignored my spike, and wrote some tests. This is another TDD lesson: it's OK to do a spike in yer code without tests and stuff to just prove a proof of concept, but then once yer good with that, put it to once side and go back to writing tests. My spike code is in a completely different environment from the environment I'm writing the tests in.

For the tests, I consider a lot of the testing of the hierarchical mechanics of how a describe / it testing framework works has already been tested extensively by the beforeEach tests (see "CFML: Adding beforeEach handlers to my TinyTestFramework. Another exercise in TDD"), and I don't need to restest that. This is the same reason I tested the afterEach stuff quite lightly too ("CFML: for the sake of completeness, here's the afterEach treatment"). In those afterEach tests I just tested the "afterness" of the way it works, which is the only difference in the implementation of afterEach compared to beforeEach. The chief difference being that when there is a hierarchy of afterEach handlers, they are called in the reverse order from equivalent beforeEach handlers.

For aroundEach what I am testing is the "aroundness" of how it works.

Here are the tests:

describe("Tests of aroundEach", () => {
    describe("Tests hierarchical sequencing", () => {
        result = []
        aroundEach((test) => {
            result.append("aroundEach top level before test")
            test()
            result.append("aroundEach top level after test")
        })
        describe("Tests hierarchical sequencing (second level: no aroundEach in this one)", () => {
            describe("Tests hierarchical sequencing (third level)", () => {
                aroundEach((test) => {
                    result.append("aroundEach third level before test")
                    test()
                    result.append("aroundEach third level after test")
                })
                describe("Tests hierarchical sequencing (inner level)", () => {
                    aroundEach((test) => {
                        result.append("aroundEach inner before test")
                        test()
                        result.append("aroundEach inner after test")
                    })
                    
                    it("is the baseline test", ()=> {
                        expect(true).toBeTrue()
                    })

                    it("tests the aroundEach handlers are called in the correct order", ()=> {
                        result.append("tests the aroundEach handlers are called in the correct order")

                        expect(result).toBe([
                            "aroundEach top level before test",
                            "aroundEach third level before test",
                            "aroundEach inner before test",
                            "aroundEach inner after test",
                            "aroundEach third level after test",
                            "aroundEach top level after test",
                            "aroundEach top level before test",
                            "aroundEach third level before test",
                            "aroundEach inner before test",
                            "tests the aroundEach handlers are called in the correct order"
                        ])
                    })
                })
            })
        })
    })

    describe("Tests with beforeEach and afterEach", () => {
        result = []

        afterEach(() => {
            result.append("set by afterEach")
        })

        beforeEach(() => {
            result.append("set by beforeEach")
        })

        aroundEach((test) => {
            result.append("set by aroundEach before test")
            test()
            result.append("set by aroundEach after test")
        })

        it("is the baseline test", ()=> {
            expect(true).toBeTrue()
        })

        it("tests the aroundEach handlers are called in the correct order", ()=> {
            result.append("tests the aroundEach handlers are called in the correct order")

            expect(result).toBe([
                "set by beforeEach",
                "set by aroundEach before test",
                "set by aroundEach after test",
                "set by afterEach",
                "set by beforeEach",
                "set by aroundEach before test",
                "tests the aroundEach handlers are called in the correct order"
            ])
        })
    })
})

So we have:

  • Test how a collection of hierarchical aroundEach handlers work. It looks like a lot of code, but it's just three nested describe blocks each with an aroundEach handler, before finally a test to observe, and a test that does the observation and tests some expectations on same.
  • Test that aroundEach plays nice with beforeEach and afterEach. Again a few lines of code, but pretty simple stuff, and the expectations are pretty clear and straight forward.

Um. OK. So.

The implementation bit is going to be a bit of an anti-climax after that lot. Firstly there's some scaffolding stuff that's obviously needed:

beforeEach = (callback) => {
    tinyTest.contexts.last().beforeEachHandler = callback
},
afterEach = (callback) => {
    tinyTest.contexts.last().afterEachHandler = callback
},
aroundEach = (callback) => {
    tinyTest.contexts.last().aroundEachHandler = callback
},
// ...
beforeEach = tinyTest.beforeEach
afterEach = tinyTest.afterEach
aroundEach = tinyTest.aroundEach

And the implementation is changing this:

it = (string label, function implementation) => {
    tinyTest.inDiv(() => {
        try {
            writeOutput("It #label#: ")

            tinyTest.contexts.each((context) => {
                context.keyExists("beforeEachHandler") ? context.beforeEachHandler() : false
            })

            implementation()

            tinyTest.contexts.reduce((reversedContexts, context) => reversedContexts.prepend(context), []).each((context) => {
                context.keyExists("afterEachHandler") ? context.afterEachHandler() : false
            })

            tinyTest.handlePass()
        } catch (TinyTest e) {
            tinyTest.handleFail()
        } catch (any e) {
            tinyTest.handleError(e)
        }
    })
},

To be this:

decoratedImplementation = tinyTest.contexts
    .filter((context) => context.keyExists("aroundEachHandler"))
    .reduce((reversed, context) => reversed.prepend(context), [])
    .reduce((decorated, context) => () => context.aroundEachHandler(decorated), implementation)

decoratedImplementation()

That's not much code, but it's a wee bit dense, even with putting each step on a separate line. What are we doing:

  • Taking the context collection.
  • Filtering out all the context objects with no aroundEachHandler element. We don't care about those.
  • Flipping the remainder around, as we need to apply them from the one nearest the test to the furthest.
  • Then, starting with the test implementation callback, sequentially pass it to the preceding callback in the hierarchy, if that makes sense. It's kind of a partial-application of each aroundEach callback I guess.
  • Once we've done that, call the returned function.

In effect what I think I am doing is calling a function that calls each aroundEachHandler callback from outermost to innermost, passing it the next handler down as its argument, all the way until the test implementation gets called at the end.

Once that was working, I realised that the filtering step could be applied to the beforeEach and afterEach calls too:

tinyTest.contexts.each((context) => {
    context.keyExists("beforeEachHandler") ? context.beforeEachHandler() : false
})

Becomes:

tinyTest.contexts
    .filter((context) => context.keyExists("beforeEachHandler"))
    .each((context) => context.beforeEachHandler())

And equivalently with afterEach:

tinyTest.contexts.reduce((reversedContexts, context) => reversedContexts.prepend(context), []).each((context) => {
    context.keyExists("afterEachHandler") ? context.afterEachHandler() : false
})

Becomes:

tinyTest.contexts
    .filter((context) => context.keyExists("afterEachHandler"))
    .reduce((reversedContexts, context) => reversedContexts.prepend(context), [])
    .each((context) => context.afterEachHandler())

The usage of ?: didn't sit well with me before, and I like this approach. Of course YMMV.

To copy and paste from last night's article: that's it. I'll add these to the source code:

You know what? I'm quite pleased that the aroundEach handling was so easily solved with just four chained method calls. It seems amazing to me for some reason. I don't mean my code is amazing: I mean the technique is.

Righto.

--
Adam