Tuesday, April 28, 2009

Coldfusion - Determine File Mime Type

Wasting time on something that *should* be a very simple task is one of my worst pet-peeves. It just burns me up.

This one, for instance, in Coldfusion when you do a CFFile Upload a file you get a boat load of matadata about the file in the CFFile and File structures. There should be a way to get that metadata again using the CFFile tag, but NOOOOOOOOO you would have to dig around in java to do that. I'm cool with that. Googling, googling... still googling. This should be more readily available. How come nobody is talking about this in coldfusion? It must be simple. An hour and a half later, one (1) site has the answer, and two answers at that! http://www.coldfusionmuse.com/index.cfm/2006/8/2/mime.types I'll blog about if no one else will. Get this around!

Here it is:

<cfset variables.contenttype = getPageContext().getServletContext().getMimeType(variables.filename)>

Or there is:

<cfset obj = createObject("java","coldfusion.util.MimeTypeUtils")>
<cfset variables.contenttype = obj.guessMimeType(variables.filename)>

Another wrap!

Friday, April 24, 2009

Amazon S3 Sub-Directories SOLVED!

Today was 'S3 DAY'. A number of ups and downs for success and frustration on this project.

I was frustrated with the inability to have sub-directories or sub-folders in S3.

However, thanks to these posts: http://developer.amazonwebservices.com/connect/message.jspa?messageID=36250, it's a snap!

All you have to do is put slashes in the file name and S3 assumes the rest. No fuss like there is with Buckets.
For example: [BucketName]/projectid_00000001/fileid_0000000001/ActualFileName.txt
where you tell S3 that the file name is "projectid_00000001/fileid_0000000001/ActualFileName.txt".

Progress on the RiaForge S3.cfc
I have made quite a number of custom modifications on the RiaForge s3.cfc project. I have stripped it down to remove all bucket support, fixed the issues raised from differences using Railo on Linux, and re-programmed a number of variables to work from the Application scope.

I was also able to design it to maintain a project sub-directory schema using the project ID numbers. S3 seemed to get fussy with subfolders as numbers, so I put some characters in front of the ID number (like 'projectid_00000001').

I was previously using a sub-sub-directory schema to maintain file versions. The file names are stored in the database and the record Primary Key ID padded with zeros to ten characters in length enabled multiple files in the same directory with the same file name.

The S3 schema ends up like this:

ActualFileName.txt    (This is actually a newer version of fileid_0000000001 by storing the 'parentid' DB field if '1', code handles the rest)
WhateverFileName.txt     (Again, here is the original file, newer versions follow)
WhateverFileName.txt     (This is a newer version of fileid_0000000004, handled by code, parentid=4, sorted by createdate for all children)
WhateverFileName.txt     (This is the newest version of fileid_0000000004, handled by code, parentid=4, sorted by createdate for all children)

Hmm, the parent file ID schema is a little bit different and more complicated than the above example, but you get the idea. There is a directory for each file. The directories are each unique allowing the file within the directory to be identical to other files in the same project. The code is doing all the heavy lifting, all required file info for the display is stored in the database, including Category IDs, Taggings, or whatever. Authenticated links are generated for instant download form Amazon S3 so the entire bucket can be private and secure.

It's a sweet setup.

S3.cfc on Railo - my fix

RiaForge has an awesome project S3.cfc for Amazon's S3 storage. http://amazons3.riaforge.org/

I love it. But it didn't work on Railo out of the box.

I was getting Signature doesn't match errors when attempting to run the s3test.cfm file in Railo on Linux.

After much research and dumping the s3 xml response I discovered that the difference is in the 'StringToSign'.

Using cf8 the 'StringToSign' it was expecting was: "GET Fri, 24 Apr 2009 17:28:00 GMT /"

However, using Railo on Linux the 'StringToSign' it was expecting was:
"GET text/html; charset=UTF-8 Fri, 24 Apr 2009 17:59:00 GMT /"

Notice the 'text/html; charset=UTF-8'.

This was comparing tests from CF8 on windows and Railo on Linux. I'm not actually sure if this is a Linux issue or a Railo issue. I have not tested the reverse (Railo on Windows & CF8 on Linux).

The strange thing is that Amazon was expecting it to be different when Windows CF8 and Linux Railo were passing the exact same request. I reset the 'datetimestring' to drop the seconds on the time [<cfset var dateTimeString = GetHTTPTimeString(DateAdd("s",-(DatePart("s",NOW())),Now()))>] so that I could verify that the signature on the generated signatures from the two machines were identical, and they were. So this really doesn't make sense to me, but I'm glad there was an easy solution.

The fix to making this cfc work on Railo on Linux was to change the following line inside the 'getBuckets' function in s3.cfc

<!--- Create a canonical string to send --->
<cfset var cs = "GET\n\n\n#dateTimeString#\n/">

<!--- Create a canonical string to send --->
<cfset var cs = "GET\n\ntext/html; charset=UTF-8\n#dateTimeString#\n/">

The same edit is required inside 'getBucket' and 'getObject'. Insert "text/html; charset=UTF-8" after the second '\n'.

Then everything worked perfectly. (So far.)

Monday, April 20, 2009

First difference between Railo CF & Adobe CF

I've been working with Railo for a short time now. Migrating from Adobe CF on Windows IIS to Redhat Apache and Railo. It's been great with some added bonuses.

This is the first case where something didn't work with the exact code from Adobe CF. It's using CFLDAP.

From an LDAP UIDNumber stored in the local DB I to an LDAP Query,

    <!--- Get the old data --->
    <cfldap action="QUERY"

When I did a cfdump and a cfabort right here, on the Adobe CF server it returned the DN equal to the 'username' passed in. In Railo CF it returns blank, which consequently makes the update LDAP error witht he blank DN. Once I discovered it was the same at the 'username' I was passing in I was able to easily overwrite the blank value with the known value and it was working again.

    <cfset actualldap.dn = "uid=XXX,cn=users,dc=XXX,dc=XXX,dc=lan">
    <!--- Check any changed fields, write the attributes string 'variables.atbs' for the LDAP Modify --->
    <!--- update the changed LDAP fields --->
    <cfldap action="MODIFY"

It was an easy enough fix, but it's strange that the results would be different. I'm not sure if there is a standard or which is not standard. I'm just really glad it was an easy fix.... After wasting 3 hours checking everything else.

That's a wrap.

Wednesday, April 15, 2009

Prevent Nested Execution of a Custom Tag

After discovering this little gem of a function "GetBaseTagList()" I was able to find an easy solution to an issue I was facing on a recent project. (Thank you once again, Ben Nadel, http://www.bennadel.com/blog/657-Determine-The-Parent-ColdFusion-Custom-Tag-Hierarchy-Using-GetBaseTagList-.htm.)

I needed to wrap each display box with a shadow, and I wanted to be able to edit it, site-wide, easily in one place.

The issue was that if a content box was included within another content box I did not want to display the nested execution of the shadow wrapper. I tried to think if various ways to count the executions to do this manually, but they were all messy and potentially buggy. Thanks to the native function "GetBaseTagList()" that is no longer an issue. It displays the nested execution hierarchy with the current one first and the initial ancestor last. So, "ListRest(GetBaseTagList())" will display any and all parents in order. But if things were complicated and there were other nested tags involved (probably not a good idea), or we just wanted to be clear, specific, on the safe side and prepare for whatever idiot might touch the project in the future, then we might want to count only instances of the current custom tag "ListFindNoCase(ListRest(GetBaseTagList()),"CF_CONTENTWRAPPER")". This worked beautifully. See the full code example below.

<!--- contentwrapper.cfm  -  <cf_contentwrapper>content</cf_contentwrapper> --->


<cfif (ListFindNoCase(ListRest(GetBaseTagList()),"CF_CONTENTWRAPPER") IS 0)>

    <cfif thisTag.executionMode EQ "start">

    <table border="1" cellspacing="0" cellpadding="0">
            <td class="wrapshadow_tl"></td>
            <td class="wrapshadow_tc"></td>
            <td class="wrapshadow_tr"></td>
            <td class="wrapshadow_ml"></td>
            <td class="wrapshadow_mc">
                <!--- tag body contents are displayed in here --->

                <!--- tag body contents are displayed in here --->   
            <td class="wrapshadow_mr"></td>
            <td class="wrapshadow_bl"></td>
            <td class="wrapshadow_bc"></td>
            <td class="wrapshadow_br"></td>



Now, this will display as intented:

<!--- content1.cfm --->

<cf_contentwrapper> <!--- this custom tag will only execute when it's not included inside another instance --->
    <div style="width:400px; height:50px;">testing content 1</div>

<!--- content2.cfm --->

<cf_contentwrapper> <!--- this custom tag will only execute when it's not included inside another instance --->
    <div style="width:400px; height:50px;">testing content 2</div>
    <cfinclude template="content1.cfm"><!--- the nested custom tag in the included file will not process, but the content1 html will display --->

Isn't that just grand?