Struct addressing in Railo

November 17, 2008 · By Gert Franz · 8 Comments

There is a major difference in how Railo addresses structs in comparison to the CFML standard. The result is that implicit addressing, (as I call it) works in the CFML standard but not in Railo. This is an issue we are long aware of AND we will NOT fix it.The following code will demonstrate the problem: <cfset a.b["c.d.e.f"] = 1>
<cfoutput>#a.b.c.d.e.f#</cfoutput>
Railo will throw the error:
key [C] doesn't exist in struct (keys:c.d.e.f)
So Railo will display what keys are in that struct and you see it listed. The only key inside b is c.d.e.f. Here's why:
CF8 (for instance) will look for the following (in Pseudocode=:

IF variables scope contains key "a.b.c.d.e.f"
   display the variables["a.b.c.d.e.f"]
ELSEIF variables scope contains key "a"
   If variables["a"] contains key "b"
      IF variables["a"]["b"] contains key "c"
         IF variables["a"]["b"]["c"] contains key "d"
            IF variables["a"]["b"]["c"]["d"] contains key "e"
               IF variables["a"]["b"]["c"]["d"]["e"] contains key "f"
                  Display variables["a"]["b"]["c"]["d"]["e"]["f"]
               ELSE
                  THROW ERROR
               END
            ELSEIF variables["a"]["b"]["c"]["d"] contains key "e.f"
               Display variables["a"]["b"]["c"]["d"]["e.f"]
            ELSE
               THROW ERROR
            END
...
            ELSEIF variables["a"]["b"] contains key "c.d.e.f"
               Display variables["a"]["b"]["c.d.e.f"]
            ELSE
               THROW ERROR
            END
...
END

So only the red if case get's involved. You see, there is a lot to do in CF8. This is of course much more flexible, but on the other hand it is slow. In addition the term a.b.c.d.e.f is misleading as well. It suggerates that a contains a key b that contains a key c etc. Which is not the case. A contains a key b and b contains a key c.d.e.f. Respecting the above we did not implement this implicit searching for struct keys since it is very slow and would only affect less than .1% of all the structs and in addition is misleading.
OK. This said, here's another thing why it is misleading. Just try to dump the following in CF8: <cfdump var="#a.b.c.d#">
Normally you would expect to see a struct containing something. But correctly CF throws an error.
The problem occured for example in cffm by Rick Root. There some resource keys are defined in the resource file cffm.properties like this: msg.t1, msg.t2 etc. Then JavaRB is used in order to place them in a struct called cffm.resourceKit. Since JavaRB reads the message names as keys an addressing inside cffm like this: cffm.resourceKit.errorMsg.t11 will throw an error in Railo. What you would have to do in cffm is to call the struct like this: cffm.resourceKit["errorMsg.t11"]. This would work in CF8 and in Railo as well. If you would dump the struct: cffm.resourceKit.errorMsg both Railo and CF8 would throw an error.
Now, what I did in order to solve the problem for CFFM is, that I added the following small function to cffm: <cffunction name="dotNotation2Struct" output="No" returntype="struct">
<cfargument name="resourceKit" type="struct" required="Yes">
<cfset var key = "">
<cfset var dotKey = "">
<cfset var st = arguments.resourceKit>
<cfset var sOldKey = "">
<cfloop collection="#arguments.resourceKit#" item="dotKey">
<cfset st = arguments.resourceKit>
<cfset sOldKey = "">
<cfloop list="#dotKey#" index="key" delimiters=".">
<cfif sOldKey neq "">
<cfset st = st[sOldKey]>
</cfif>
<cfif not structKeyExists(st, key)>
<cfset st[key] = structNew()>
</cfif>
<cfset sOldKey = key>
</cfloop>
<cfset st[sOldKey] = arguments.resourceKit[dotKey]>
<cfset structDelete(arguments.resourceKit, dotKey)>
</cfloop>
<cfreturn arguments.resourceKit>
</cffunction>

The above code converts all key that contain a "." in their keyname into a corresponding struct. This for instance is done in CFFM in the file cffm.cfm like follows: variables.resourceKit = dotNotation2Struct(variables.rbm.getResourceBundle("#variables.rbFile#","#variables.defaultJavaLocale#"));
After this line of code CFFM ran flawlessly and now even understands Railo resources like FTP or Amazon S3. So I was able to use CFFM in order to browse through my FTP accounts. I just added a mapping that points to my ftp account like: /ftp -> ftp://username:password@ftpserver/ and added it to the folders that can be browsed with cffm. Somewhere there is a line inside cffm.cfm that looks like this: <cfinvokeargument name="includeDir" value="c:\..."> You can now replace it with a mapping you created in the Railo administrator. ...
<cfinvokeargument name="includeDir" value="/ftp">
OR
<cfinvokeargument name="includeDir" value="/s3">
OR
<cfinvokeargument name="includeDir" value="/ram">
...
Then I can use CFFM to work with these external file systems even though cffm knows nothing about ftp or s3.

Tags: CFML · Comparison · HowTo · Performance · Railo 3.0 · Standards · Testing

8 responses so far ↓

  • 1 ike // Nov 17, 2008 at 9:31 PM

    hmm... well Railo handles it the way I would expect it to be handled... I didn't realize that CF8 would allow structs to be addressed that way... blech... not good. But then again I'm still scratching my head as to why they allowed isDefined(&quot;arguments.x&quot;) to return something *different* than structKeyExists(arguments,&quot;x&quot;). The null value from an optional argument with no default is unusable because attempting to use it throws an error -- it should return false from *both* function calls because it shouldn't exist in the argument struct.
  • 2 Michael Streit // Nov 18, 2008 at 10:15 AM

    Here is how Railo handle this cases
    isDefined('a.b.c.d'):false
    isDefined('variables.a.b.c.d'):false
    isDefined('variables[&quot;a.b.c.d&quot;]'):true

    structKeyExists(variables,'a'):false
    structKeyExists(variables,'a.b'):false
    structKeyExists(variables,'a.b.c.d'):true

    and now how CFMX does
    isDefined('a.b.c.d'):true
    isDefined('variables.a.b.c.d'):true
    isDefined('variables[&quot;a.b.c.d&quot;]:throw-&gt;must be a syntactically valid variable name

    structKeyExists(variables,'a'):false
    structKeyExists(variables,'a.b'):false
    structKeyExists(variables,'a.b.c.d'):true

    like you can see in cfmx the structKeyExists function work the same way as in Railo,
    because there is no key with name &quot;a&quot; or &quot;a.b&quot;, the only key that exists is &quot;a.b.c.d&quot;

    isDefined works not the same way, because isDefined does not look for keys, it checks for variables.
    isdefined is more like the following
    function _isDefined(str){
       try{
       evaluate(str);
    return true;
    }
    catch(e){}
    return false;
    }
    and for cfmx the variable &quot;a.b.c.d&quot; exists.
  • 3 Michael Streit // Nov 20, 2008 at 1:18 PM

    we have add a new function for next patch release, this function allows to translate the keys to a more convienent form.

    example:
    &lt;cfset variables['a.b.c']=1&gt;
    &lt;pre&gt;
    &lt;cfoutput&gt;before:   #variables.toString()#&lt;/cfoutput&gt;
    &lt;cfset structKeyTranslate(variables)&gt;
    &lt;cfoutput&gt;after:   #variables.toString()#&lt;/cfoutput&gt;
    &lt;/pre&gt;

    output:
    before:   {a.b.c={1}}
    after:   {a={{b={{c={1}}}}}}

    we have also add 2 addional attributes
    - boolean deepTranslation do also translate the children of the struct
    - boolean leaveOriginalKey leave the original key in the struct

    example:
    &lt;cfset variables['a.b.c']=1&gt;
    &lt;pre&gt;
    &lt;cfoutput&gt;before:   #variables.toString()#&lt;/cfoutput&gt;
    &lt;cfset structKeyTranslate(variables,false,true)&gt;
    &lt;cfoutput&gt;after:   #variables.toString()#&lt;/cfoutput&gt;
    &lt;/pre&gt;

    output:
    before:   {a.b.c={1}}
    after:   {a={{b={{c={1}}}}}, a.b.c={1}}
  • 4 Snake // Nov 20, 2008 at 4:16 PM

    Perhaps someone should have a little word with Rick Root and let him know that using dots in variable names is not good practice :-)
  • 5 Gerald Guido // Nov 24, 2008 at 9:30 PM

    Is it the 28th yet? Or is it the 27th?
  • 6 Gert Franz // Nov 25, 2008 at 3:50 PM

    Gerald,

    don't understand your comment, could you explain it a little?
  • 7 Gerald Guido // Nov 25, 2008 at 4:04 PM

    http://groups.google.com/group/transfer-dev/msg/e02d4854bc342964

    Plus

    http://www.coldfusioncamp.com/en/index.cfm

    Equals an early Christmas? No?
  • 8 Gert Franz // Nov 25, 2008 at 4:13 PM

    Well, I can already say that we will NOT announce the Open Source version on the CFCamp. But we will release an Open Source Version still this year. It will not be the final one but maybe an early alpha. I will anounce it on my blog asap.

    Since we cannot make a first impression twice we would rather make it a good one... That's why we are taking our time.

    Gert

Leave a Comment

Leave this field empty: