Saturday, October 29, 2011

Script Components in CFWarning

I have updated my CFWarning plugin to now parse script components. I have been working with script components now for a while and it was time to have the ability to check the scoping before I committed. Now the plugin will check for scoping in both script and tag components. I have the updated code and plugin jar file on github. There are still some cases of false positives for script components, but it does a good job of finding variables that are not scoped.

After working on this for a while, and then going back and checking all of the files in my project I think that I am going to make some small changes to the setup of this project. My plan is to extract the parsing components and have at least two parts to the project. One would be compiled as the plugin, the other would be compiled as an executable that could be run from the command line or as an ant task. The second one then would be able to check the components in a set of directories. The ant task seems like a good option, as it could be setup as part of a CI process.

I'll have another post when I have any more updates on the project.

Tuesday, May 31, 2011

ORM Entities and isStruct

I was writing a function today and ran into something that through me off. I have a function that can take either an object (ORM) or a struct. Inside the function I needed to check of the value was a struct or an object to determine how to access the properties / keys. This seem like a simple thing to do so I wrote the following:
public void function foo(required any bar) {
  if(isStruct(arguments.bar)) {
    ...do something
  } else {
    ...do something similar
  }
}
And this worked just fine when the argument is a struct. However, when I tested it with the ORM entity it failed inside the struct block of the if statement. This didn't seem right, so I went and checked the documentation for the isStruct method and here is the explanation I found:

Returns True, if is a ColdFusion structure or is a Java object that implements the java.lang.Map interface.

So if the function will only return true if it is a ColdFusion structure or object that implements java.lang.Map, why would it return true for my ORM entity? I posted the finding on the cf-orm-dev google group, and it turns out that all ColdFusion components will return true for isStruct. Thanks to Brian Kotek for pointing out that it wasn't just my ORM entities that behaved this way. So either ColdFusion components are ColdFusion structs or it at some point implements the java.lang.Map interface.

I still find this to be a little strange, so I did some tests to see if components really were structs.
obj = createObject('component');
obj.key1 = 'value1';
obj['key2'] = 'value2';
StructInsert(obj,'key3','value3');
As it turns out, all three of these forms worked. So, I guess ColdFusion components are structs or can at least be manipulated in some ways the same.

Luckily, my original problem is easily solved. The isObject method returns false for structs and true for components, so all I had to do was reverse my cases and use the isObject method.

Still I'd be interested if anyone had a deeper understanding of why components behave like structs.

Monday, March 21, 2011

ColdFusion ORM is Case Sensitive

Obviously, right? Everyone that is using Hibernate to do ORM in any of their projects already knows this. Normally, this isn't a big deal. However, some of my coworkers came across an error this last week that had us scratching our head for a few minutes. I have reproduced the error using the Bookclub derby sample database that ships with ColdFusion. Maybe you'll spot the problem:

application.cfc
component {

 this.name = "ORMTEST";
 
 this.ormEnabled = true;
 this.ormSettings.datasource="bookclub";
 this.ormSettings.flushAtRequestEnd = false;
 
 public boolean function onRequestStart() {
  ormReload();
  return true;
 }

}
Book.cfc
component persistent="true" table="BOOKS" {
 property name="id" column="BOOKID" generator="increment";
 property name="title" column="TITLE";
 property name="description" column="BOOKDESCRIPTION";
 property name="genre" column="GENRE";
 property name="spotlight" column="ISSPOTLIGHT";
 property name="author" fieldtype="many-to-one" cfc="ORMTest.Author" fkcolumn="AUTHORID";
}
Author.cfc
component persistent="true" table="AUTHORS" {
 property name="id" column="AUTHORID" generator="increment";
 property name="firstName" column="FIRSTNAME";
 property name="lastName" column="LASTNAME";
 property name="spotlight" column="ISSPOTLIGHT";
 property name="biography" column="BIO";
 property name="books" fieldtype="one-to-many" cfc="ORMTest.BOOK" fkcolumn="AUTHORID";
}
Did you spot it? It took us a while, and it only occurs with HQL queries. With the above code, if you run ORMExecuteQuery("select book from Book as book") you should get the error. Running the EntityLoad() function will not cause the error. We actually turned on the saveMapping setting to see the hibernate files that were being created. At that point it was pretty clear what the issue was. The mapping that was created had the Book entity created with an entity name of BOOK. Typically, the entity name, if not explicitly stated, will be the name of the component (and in the same case). However, in this case the entity name is being created based of the property in Author.cfc. Luckily the fix was pretty easy, change the property on the Author entity to be the correct case. We actually had to delete our class files and clear the template cache to get the change to take effect.

I'm not sure why ColdFusion chooses to use the property off the Author.cfc to determine the entity name, but it was pretty confusing. I think that I would have preferred that a hard error were thrown in this situation instead of getting the runtime error.

Friday, March 4, 2011

ColdFusion, Dates and Timezones

Recently I have been working on a project where I would like to display date/time values to the user in their preferred timezone. This seemed like a pretty straight forward task; take a date and convert it to the user's timezone based on the offset.

The first thing I did was to see what ColdFusion offered for built in functionality. Sadly, I found that ColdFusion didn't have much for working with date/times in multiple timezones. I was optimistic when I found the dateConvert() method, but found that it only converted from the server's local timezone to UTC and back.

After some research I found that the date object itself does not have a timezone value. All dates are represented with a long integer value as the number milliseconds from the epoch (which is January 1, 1970) in UTC. This fact is important when thinking about what a conversion from one timezone to another really is.

Let's say I have a date/time of '1/1/2011 08:00:00.000' that I want to display to users in many timezones. Assume my ColdFusion server is running on US/Central time and I want to display the date/time to users in the UTC timezone. Ignoring daylight time, the US/Central timezone is 6 hours behind UTC. Using the dateAdd() method I might do something like this:
localDate = parseDateTime("1/1/2011 08:00:00.000");
UtcDate = dateAdd("h",6,localDate);
When I output the result of both of these values I get the following:

localDate = {ts '2011-01-01 08:00:00'}
UtcDate = {ts '2011-01-01 14:00:00'}

And this seems to make sense. The first date/time is exactly what should be expected, and the dateAdd() method added six hours to the date to get the UTC date/time. Now, I'll output the values again, except this time I will run the date object's getTime() method:

localDate.getTime() = 1293890400000
UtcDate.getTime() = 1293912000000

Notice here that the values are different. As I explained, dates are stored as an offset, in milliseconds, from the epoch in UTC. Therefore, these two date objects are now representing different date/time values. This may not be a big issue in most circumstances, but it didn't sit right with me that the object returned was actually a different value.

When I want to show the same date/time in multiple timezones, I really just want a different format of the date/time. Unfortunately, in ColdFusion there is no option for a timezone in any of the date or time formatting functions. The parseDateTime() method recognizes a timezone, and there are Locale Specific functions for the date and time formatting functions, but there is nothing for displaying date/time values with a timezone.

The easiest way that I found to format dates for multiple timezones was by dropping down into Java. Two classes are all that are needed: java.text.SimpleDateFormat and java.util.TimeZone. These two classes are part of the Standard Java classes, so there is no need for JavaLoader or adding the classes to your classpath. Here is how the classes can be used to make a really simple formatting function to display date/times in different timezones.
<cfcomponent name="TZDateFormat">

 <cfset variables.TimeZone = createObject("java","java.util.TimeZone")>

  <cffunction name="format" access="public" returntype="string" output="false">
  
    <cfargument name="datetime" required="true">
    <cfargument name="mask" required="true">
    <cfargument name="timezone" required="false">
  
    <cfset var tzObject = ''>
    <cfset var dateFormatter = ''>
  
    <cfif not structKeyExists(arguments,"timezone")>
      <cfset tzObject = variables.TimeZone.getDefault()>
    <cfelse>
      <cfset tzObject = variables.TimeZone.getTimeZone(arguments.timezone)>
    </cfif>
  
    <cfset dateFormatter = createObject("java","java.text.SimpleDateFormat").init(arguments.mask)>
  
    <cfset dateFormatter.setTimeZone(tzObject)>
  
    <cfreturn dateFormatter.format(parseDateTime(arguments.datetime)) >  
  
 </cffunction>
 
</cfcomponent>
The function takes three arguments. First, is the date/time value. Second, is the mask or format that I want the date/time to be output in. Last, is the optional timezone that I want the date/time to be displayed in. If the last argument is not supplied it will default to the default timezone on the server. To see all the available timezone IDs that are supported call the getAvailableIDs() method on the java.util.TimeZone object. This will return an array of the IDs.

So, I'll go back to my original example. This time, though, I will use my format method to display the date/time in UTC.
localDate = TZDateFormat.format("1/1/2011 08:00:00","MM/dd/yyyy HH:mm:ss")
UtcDate = TZDateFormat.format("1/1/2011 08:00:00","MM/dd/yyyy HH:mm:ss","UTC")
When I output these values I get the following:

localDate = 01/01/2011 08:00:00
UtcDate = 01/01/2011 14:00:00

As you can see, these values look very similar to the earlier outputs that I did. The major thing to notice here is that the return from the format function is a string. They look like dates when I output them, but they are just strings. A word of warning: ColdFusion will implicitly treat these strings as date objects if you use any of the built in date functions, and will interpret these as dates in the server's default timezone. This probably wouldn't lead to desirable behavior.

I think there are three main benefits to using SimpleDateFormat object to display dates in other timezones:
  1. The dateAdd() or dateConvert() methods actually change the date. The SimpleDateFormat format method returns a string formatted as desired. It also has the ability to output the timezone with the 'z' mask (which ColdFusion cannot do).
  2. The SimpleDateFormat object has access to the JREs timezone database. This means all the timezone conversion, including daylight time, is taken care of.
  3. The format in SimpleDateFormat can do date and time formatting together. In ColdFusion I have to use dateFormat() and timeFormat() methods separately.
That's all there really is to showing the dates in different timezones. I ended up setting my ColdFusion instance to run in UTC timezone, and I save all dates in my database in UTC. I did this to erase any chance of ambiguity of dates coming from the database (think daylight time switches in the fall). I also created a replacement function for the datePart() method using java.util.GregorianCalendar. I did this so that I could get the datepart for different timezones. Be aware that that GregorianCalendar starts the months at zero, and ColdFusion starts the months at one. The final caveat to the whole thing are the masks that SimpleDateFormat uses. The masks between Java and ColdFusion are not the same, so check the Java APIs for the correct mask characters.

I was disappointed that ColdFusion didn't support this out-of-the-box. However, it is great that ColdFusion can drop down into Java libraries so easily to leverage this kind of functionality.