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.

Sunday, November 28, 2010

Hello, CFWarning!

Since this is my first post I should probably introduce myself. As the title of my blog suggests, my name is Luke. I am primarily a ColdFusion developer, but I play with a lot of other languages and technologies as well (hopefully there will be more posts to support this statement!). That's enough for now, I want to talk about something more interesting.

The motivation for starting this blog was to share a project that I have been working on for a while now. The project is called CFWarning (you can probably already tell that I am pretty creative when it comes to naming things), and is an Eclipse plugin. It is hosted on github. The project began as a personal goal to learn how to create an Eclipse plugin, and to create something I found useful.

I chose to create a view that would inspect my code and show me warnings as I wrote the code. To achieve this I had to write a ColdFusion parser of sorts. Now, I am not an expert on parsers and I am sure that there are many things that I could have done better. However, my main objective was to create a useful Eclipse plugin, so I didn't want to focus on how to create the perfect parser. So, I wrote my own parser for tag components and began to create a view to show warnings.

Scoping seemed like the logical place to start. So, I updated the view to display all the functions in the currently open component that have unscoped variables. The view updates the warnings when the file gains focus or is saved. I would like mention that there is already a project out there that scans components for scoping issues called varScoper. If you want to scan a large number of components at once, or don't have an Eclipse based IDE, I would really recommend this tool. It can also be used as a ColdFusion Builder extension.

The second thing that I was able to do easily was to warn about possible debug code. I like to use cfdump and cfabort a lot for debugging, and may be guilty of occasionally committing before I remove the debugging code. So, I added warnings to my view that display whenever cfdump or cfabort are used in a component.

Below is what the view looks like. It's nothing fancy but gets the job done right now. The first line indicates how many functions were found in the component and how many warnings were found. After that it begins to show the warnings. Each line starts with the line number. Warnings are grouped by the function that they are found in, and each line gives a short description of the warning. The warning lines will take you to the line in the file where the warning exists when double clicked.



To install the plugin you will need to grab the jar file download from github and place it in the 'dropins' directory of your Eclipse install. Then restart or open Eclipse and choose the CFWarning view. Right now the plugin only works on tag components for the functionality that I discussed above, and was designed for ColdFusion 9. It isn't perfect and already has some known issues, but I felt it was at a point that it could be shared. Download it, give it a try and let me know what you think. I plan to continue updating the plugin for other features (see the README on github for some of the features I want to add), so if you have any ideas for future warnings let me know.