Showing posts with label tips. Show all posts
Showing posts with label tips. Show all posts

Friday, July 31, 2009

ColdFusion ORM : Troubleshooting - 'Lazy' does not work

Few days back Manju logged a bug in CF-ORM saying 'lazy' does not work for many-to-one relation and that too on non-Windows machine. At first, I simply rejected the bug because a) ORM can not have anything to do with OS and therefore, if it works on Windows, it works on all the plaform and b) I know it works :-). But he did not agree and I had to take a look at that machine. And apparently he was right - lazy was not working ! The related entity was in-fact getting loaded immediately. (Question for you - how will you know that lazy is working or not?)

Even after seeing this, I did not believe it and asked him to replicate this on another system and he successfully showed that to me on one another system. And he agreed that it works fine on most of the configurations. The problem exists only on a few of the systems.

This got me thinking - Why would a relation get loaded immediately even after it is marked lazy? The only answer would be - if some one is accessing that lazy field and calling some method on it. I checked his code which was loading the entities to see if there could be any case, where the field would get loaded and unfortunately there was none.

And then suddenly it hit me - what if "memory tracking" is swithched on? That would access each of the field of each object recursively to compute the size of an object and that can definitely cause this. I immediately checked the server monitor and the "memory tracking" was right there staring at me in "red"! It was indeed enabled. I asked Manju to check the other system as well (where lazy was not working) and the memory tracking was enabled there as well.

So the lesson - If the 'memory tracking' is enabled on the server, the relationship will no longer remain lazy. And btw, you should enable "Memory tracking" on the server only if you need to track the memory for some troubleshooting. Memory tracking is really really expensive in terms of performance.

Another reason why it might not work for you could be - if you are sending the object to flex. Currently, during serialization, the related objects also get sent irrespective of the lazy attribute set on the relationship. We are still working on it and hopefully by the time we release, this will be fixed.

Monday, July 27, 2009

ColdFusion ORM : How to log SQL

When you use ORM for developing an application, SQLs are generated and executed by the underlying ORM engine (i.e Hibernate for ColdFusion ORM). However, for both troubleshooting and performance optimization, it is crucial to monitor what queries are getting generated. It can help you find out if there is any error in mapping that you have provided as well as it can help you decide various tuning strategies.

ColdFusion can log the SQLs generated by ORM either onto the console or a file. At the same time it leaves enough hook for you to log it anywhere you want.

ColdFusion ORM provides two ways to log the SQLs.

  1. Using application setting to log to console : This is a quick and simple way to log the sql to console. This is enabled by setting "logsql" in ormsettings.
       <cfset this.ormsettings.logsql="true">
    This setting is self sufficient and it will log all the sqls executed by hibernate to the console (or a file where the server output goes). However this is not a very flexible option. The sqls are always written to the console and it will be combined with any other output that goes to console. Also this option will not show the DDL queries used for creating or updating tables. It only logs the SQL for the entity operations.
  2. Using log4J.properties: Hibernate uses log4j for its logging and you can completely control its logging (including SQL) by modifying the log4j.properties. log4j.properties is present under <cf_home>/lib directory. Please note that you don't need to do any application specific setting for this.

I will go in details about using log4j.properties for SQL logging. Here is a snippet from log4j.properties file that is shipped with ColdFusion.

###--------------- Hibernate Log Settings ------ 
### Set Hibernate log
log4j.logger.org.hibernate=ERROR, HIBERNATECONSOLE

### log just the SQL
#log4j.logger.org.hibernate.SQL=DEBUG, HIBERNATECONSOLE
#log4j.additivity.org.hibernate.SQL=false
### Also log the parameter binding to the prepared statements.
#log4j.logger.org.hibernate.type=DEBUG
### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=DEBUG, HIBERNATECONSOLE
### log cache activity ###
log4j.logger.org.hibernate.cache=ERROR, HIBERNATECONSOLE

# HibernateConsole is set to be a ColsoleAppender for Hibernate message using a PatternLayout.
log4j.appender.HIBERNATECONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.HIBERNATECONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.HIBERNATECONSOLE.layout.ConversionPattern=%d{MM/dd HH:mm:ss} [%t] HIBERNATE %-5p - %m%n%n
#---------------------------------------------

First we will see the relevant settings for SQL logging:

  • log4j.logger.org.hibernate.SQL : Defines whether the SQL executed for entity operations will be logged and where it will be logged. The second value for this i.e 'HIBERNATECONSOLE' is a appender that controls where the SQLs will be logged. In the above example HIBERNATECONSOLE is a 'console' appender which means it will log the sql to console.
  • log4j.logger.org.hibernate.type : Defines whether parameter values used for parametrized query will be logged.
  • log4j.logger.org.hibernate.tool.hbm2ddl : Defines whether DDL sql statements will be logged.

To enable the SQL logging for console, we just need to uncomment the settings mentioned above. Here is how the hibernate log settings in log4j.properties file would look like

###--------------- Hibernate Log Settings ------ 
### Set Hibernate log
log4j.logger.org.hibernate=ERROR, HIBERNATECONSOLE

### log just the SQL
log4j.logger.org.hibernate.SQL=DEBUG, HIBERNATECONSOLE
log4j.additivity.org.hibernate.SQL=false
### Also log the parameter binding to the prepared statements.
#log4j.logger.org.hibernate.type=DEBUG
### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=DEBUG, HIBERNATECONSOLE
### log cache activity ###
log4j.logger.org.hibernate.cache=ERROR, HIBERNATECONSOLE

# HibernateConsole is set to be a ColsoleAppender for Hibernate message using a PatternLayout.
log4j.appender.HIBERNATECONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.HIBERNATECONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.HIBERNATECONSOLE.layout.ConversionPattern=%d{MM/dd HH:mm:ss} [%t] HIBERNATE %-5p - %m%n%n
#---------------------------------------------
Here is the complete log4j.properties for logging SQL for console. Ofcourse after changing this you need to restart the server. If you need to log the parameter values used for queries, you need to uncomment '#log4j.logger.org.hibernate.type=DEBUG' as well.

What if you want to log the SQL to a file and not to console? That is pretty easy. You just need to change the 'Appender' used here (HIBERNATECONSOLE) to point to a 'FileAppender' instead of a ConsoleAppender. Here is how the configuration for HIBERNATECONSOLE should look like after you point it to a File Appender.

log4j.appender.HIBERNATECONSOLE=org.apache.log4j.FileAppender
log4j.appender.HIBERNATECONSOLE.File=../hibernatesql.log
log4j.appender.HIBERNATECONSOLE.Append=true
log4j.appender.HIBERNATECONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.HIBERNATECONSOLE.layout.ConversionPattern=%d{MM/dd HH:mm:ss} [%t] HIBERNATE %-5p - %m%n%n

For standalone ColdFusion installation, the file 'hibernatesql.log' will be created in the /logs directory.You can also specify a full path of the file for property 'log4j.appender.HIBERNATECONSOLE.File' and the log will be written to that.

That was easy. Isn't it? What if you want a rolling log file where you dont want the log file size to grow infinitely. That is fairly easy too. All you need to do is to use an appropriate appender. The appender definition for that will look like

log4j.appender.HIBERNATECONSOLE=org.apache.log4j.RollingFileAppender
log4j.appender.HIBERNATECONSOLE.File=../hibernatesql.log
log4j.appender.HIBERNATECONSOLE.Append=true
log4j.appender.HIBERNATECONSOLE.MaxFileSize=500KB
log4j.appender.HIBERNATECONSOLE.MaxBackupIndex=3
log4j.appender.HIBERNATECONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.HIBERNATECONSOLE.layout.ConversionPattern=%d{MM/dd HH:mm:ss} [%t] HIBERNATE %-5p - %m%n%n

Here is the complete log4j.properties for logging SQL to a file that will be rolled automatically once it reaches 500KB.

Now that you have seen how easy it is to change one 'Appender' to another, you can pretty much log it anywhich way you want. Here are some of the interesting 'Appender's that come with log4j which you can easily use.

DailyRollingFileAppender, ExternallyRolledFileAppender, JMSAppender, SMTPAppender, SocketAppender, SyslogAppender

See log4J for more details on log4j settings.

Saturday, July 18, 2009

ColdFusion 9 : Implicit/Generated CFC Methods

How many times did you write the accessor methods (better known as getters and setters) for the fields of your CFC? And how many times did you feel that it was really a mundane job and wished there was a better way to do this? With ColdFusion 9, we have done just that! You no longer need to write those accessors. ColdFusion will automatically generate the accessor methods for you in the object. All you need to do is to define the properties using cfproperty.

Consider this CFC Person.cfc

Component Person{
property firstname;
property lastname;
property age;
property city;
property state;
}

When an object is created for this CFC, you can directly call the setters and getters for the properties as


person = new Person();
person.setFirstName("Brad");
person.setLastName("Pitt");
person.setAge(46);

writeOutput("Name : #person.getFirstName()# #person.getLastName()#");
writeOutput("Age : #person.getAge()#");


The implicit setter/getter uses the 'variable' scope, which is a private scope for a object, for storing the data. i.e the setter method puts the data in the variable scope and the getter method gets the data from the variable scope.

What if you want to override the setter/getter method? Ofcourse you can do that. You just need to define those methods in your CFC and ColdFusion will call your method instead of calling the implicit one.

Hmm.. What if you dont want ColdFusion to generate getter or setter or both methods for a particular property? You can disable that by adding attribute getter="false" and setter="false" on the property.

So if you define a property

property name="city" getter="false" setter="false";

ColdFusion will not generate getCity() and setCity() methods for this CFC.

The implicit methods can also do some nice validation if you have given the 'type' for properties. Consider the same Person.cfc with type specified where we have made age as a numeric type.


Component Person{
property string firstname;
property string lastname;
property numeric age;
property string city;
property string state;
}

ColdFusion will do the datatype validation whenever the setter method is called for any property. So if you call


person = new Person();
person.setFirstName("Brad");
person.setLastName("Pitt");
person.setAge("LA");

It will throw a nice error saying "The value cannot be converted to a numeric".

Whats more interesting is that you can do a whole lot of validation using cfproperty. We have added two more attributes on cfproperty named 'validate' and 'validateparams'. These attributes allow you do even more advanced validations of the property data when the setter method is called for a property. (This is similar to cfparam or cfform validation).

The possible values for validate attributes are

  • string
  • boolean
  • integer
  • numeric
  • date
  • time
  • creditcard: A 13-16 digit number conforming to the mod10 algorithm.
  • email: A valid e-mail address.
  • eurodate: A date-time value. Any date part must be in the format dd/mm/yy. The format can use /, -, or . characters as delimiters.
  • regex: Matches input against pattern specified in validateparams.
  • ssn: A U.S. social security number.
  • telephone: A standard U.S. telephone number.
  • UUID: A Home Universally Unique Identifier, formatted 'XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXX', where 'X' is a hexadecimal number.
  • guid: A Universally Unique Identifier of the form "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" where 'X' is a hexadecimal number
  • zipcode: U.S., 5- or 9-digit format ZIP codes

The 'validateparams' attribute available with <cfproperty> takes the parameters required by the validator specified in the 'validate' attribute. This needs to be specified in the implicit struct notation.
  • min: Minimum value if 'validate' is integer/numeric
  • max: Maximum value if the 'validate' is integer/numeric
  • minLength: Minimum length of the string if the 'validate' is string
  • maxLength: Maximum length of the string if the 'validate' is string
  • pattern: regex expression if the validator specified in 'validate' attribute is regex
Consider the Person again where we will add few more properties with some validation

Component Person{
property string firstname;
property string lastname;
property numeric age;
property string city;
/**
* @validate string
* @validateparams {minLength=2, maxLength=2}
*/
property string state;
/**
* @validate zipcode
*/
property numeric zip;
/**
* @validate telephone
*/
proeprty phone;
}

Now when you create an object of Person and call setState(state), before setting the data for state in variable scope, ColdFusion will validate that state value provided is of type 'string' and its length is 2. Similarly setZip() will validate that the input data is a valid zip code and setPhone() will validate that the input data is a valid telephone number.

For String type properties, you can also do regular expression validation allows you to have all sort of validation on a property of string type.

And to top it all, all of these nice goodies are available to you at a much lesser cost than regular UDFs. Yes, you read it right. Implicit methods perform much better than regular UDFs that you write in CFC. To confirm that, lets use this CFC where we will use implicit methods for 'firstName' and write our own accessors for 'lastName'.


component 
{
property String firstName;
lastName = "";

public void function setLastName(string name)
{
lastName = name;
}

public string function getLastName()
{
return lastName;
}
}

Both implicit methods and UDFs are doing exactly the same thing i.e settings and getting the data in/from variable scope. Now lets run this comparison test



person = new Person();
t = getTickCount();
for(i = 0; i < 100000; i++)
{
person.setfirstName("Brad");
fname = person.getFirstName();
}
writeOutput("Total Time in implicit methods " & (getTickCount() - t));
t = getTickCount();
for(i = 0; i < 100000; i++)
{
person.setLastName("Pitt");
lname = person.getLastName();
}
writeOutput("Total Time in UDF methods " & (getTickCount() - t));

Here is the output
Total Time in implicit methods 63
Total Time in UDF methods 485

Thats almost eight times faster than regular UDF. Isn't that sweet? You get those auto-generated methods, whole lot of auto-magical validations and to top that, incredible performance. I am sure writing accessors for your CFC fields will become a thing of past now !

Monday, June 30, 2008

New 'EvalAtPrint' attribute in CFDocumentItem

A new evalAtPrint attribute has been added to cfdocumentitem tag in ColdFusion 801 which is applicable when the item type is header or footer. The default value of this attribute is false.

Note: The cfdocumentitem behavior with the default evalAtPrint value is identical to the behavior in ColdFusion MX 7, but differs from the behavior of the ColdFusion 8 release.

When the evalAtPrint attribute value is false, the default value, ColdFusion evaluates the cfdocumentitem tag body at the same time when it is processing the cfdocument tag. This is the behavior identical to CFMX7. In this case you can use CFDocument variables (TotalPageCount, CurrentPageNumber, TotalSectionpageCount and CurrentSectionpageNumber) only inside cfoutput and not in any expression because the page numbers are not known unless the cfdocument body content is evaluated and layed out.

In order to use CFDocument variables in expression inside CFDocumentItem tagbody, you should set evalAtPrint to true as shown in the following example.

<cfdocument format="pdf">
<cfdocumentitem type="header" evalAtPrint="true">
<cfif (CFDocument.currentPageNumber % 2 eq 0)>
<cfoutput> Page is even</cfoutput>
<cfelse>
<cfoutput> Page is odd</cfoutput>
</cfif>
</cfdocumentitem >
Document body…
</cfdocument>

In this case, ColdFusion evaluates the cfdocumentitem tag body when it prints the header or footer, not when it evaluates the cfdocument tag body. This ensures that the cfdocument tag body is already evaluated and layed out and page number information is available when cfdocumentitem tag is being evaluated

A side effect of this is, if a variable is used inside both cfdocument and cfdocumentitem, cfdocumentitem tag will only see the final value of that variable. In the following example, cfdocumentitem will always get 11 as value of ‘I’ and hence all the headers will print ‘Chapter 11’.


<cfdocument format="pdf" >
<cfloop from=1 to=10 index="i">
<cfdocumentsection>
<cfdocumentitem type="header" >
<cfoutput>Chapter #i#</cfoutput>
</cfdocumentitem >
DocumentSection body
</cfdocumentsection>
</cfloop>
</cfdocument>

To handle this, you can now pass the variable into the cfdocumentitem tag as a custom attribute, as shown in the following example:

<cfdocument format="pdf" >
<cfloop from=1 to=10 index="i">
<cfdocumentsection>
<cfdocumentitem type="header" evalAtPrint="true" no = #i#>
<cfoutput>Chapter #attributes.no#</cfoutput>

<cfif (CFDocument.currentPageNumber % 2 eq 0)>
<cfoutput> Page is even</cfoutput>
<cfelse>
<cfoutput> Page is odd</cfoutput>
</cfif>
</cfdocumentitem >
Document Body.
</cfdocumentsection>
</cfloop>
</cfdocument>

Monday, February 18, 2008

Reading MP3 meta-data from ColdFusion

On friday I posted about how to read the meta-data for Swf (flash) file from ColdFusion and on the same note, I think it will be cool to read the meta-data for MP3 file as well. Here is the specification for MP3 header and it is fairly straight forward to read the header using the spec.

ColdFusion 8 knows how to read MP3 file and it can provide a set of meta-data for it. Though this functionality is not exposed via any tag or function, you can still use it. The meta-data it provides are

  • Bit Rate : in kbps
  • Frequency : in Hz
  • Play duration : in second
  • Version : integer
      • 0 - MPEG version 2.5
      • 1 - MPEG version 2
      • 3 - MPEG version 1
  • Copy Right : true/false
  • Channel mode : integer
      • 0 - Stereo
      • 1 - Joint Stereo
      • 2 - Dual Channel
      • 3 - Mono or Single Channel

Here is an example code.

<cfset mp3File = createObject("java", "coldfusion.util.MP3File").init("C:\music.mp3")>
<cfoutput>mp3File.getBitRate() : #mp3File.getBitRate()# kbps</cfoutput><br>
<cfoutput>mp3File.getFrequency() : #mp3File.getFrequency()# Hz</cfoutput><br>
<cfoutput>mp3File.getVersion() : #mp3File.getVersion()#</cfoutput><br>
<cfoutput>mp3File.getDuration() : #mp3File.getDuration()# Sec</cfoutput><br>
<cfoutput>mp3File.isCopyRighted() : #mp3File.isCopyRighted()#</cfoutput><br>
<cfoutput>mp3File.getChannelMode() : #mp3File.getChannelMode()#</cfoutput><br>

Though you can use the above mentioned class (coldfusion.util.MP3File) without any problem, you must keep it in mind that it is unsupported and *might* change in future. Just a disclaimer !!! :-)

This class does not get you the ID3V1 or ID3V2 meta-data, but it should be easy for you to do it. For ID3V1, all you need to do is to read the last 128 bytes of the mp3 file and you have all the information required. Reading ID3V2 tag is little more involved and there are a number of open source java libraries available (common one being jidlib and jaudiotagger) which can be used.

Friday, February 15, 2008

Reading Flash (Swf) metadata from ColdFusion

Wouldn't it be cool if you could read the meta-data of Swf file in ColdFusion? The most common use case I can think of is to find the dimension of swf movie so that you can set the dimension for the same in object tag while you are embedding the flash movie in your page. And the good news is that you can do that currently in ColdFusion with just 2-3 lines of code!! All you need to do is to create a TagDecoder and decoder the header with it. Here is the complete code.

<cfscript>
fis = createObject("java", "java.io.FileInputStream").init("C:\test.swf"); // Create the FileInputStream
decoder = createObject("java", "macromedia.swf.TagDecoder").init(fis); // create TagDecoder
header = decoder.decodeHeader(); // Decode the header.
fis.close();
rect = header.size;
WriteOutput("
Is Compressed : #header.compressed#<br>
Frame Count : #header.framecount#<br>
Frame rate : #header.rate#<br>
Version : #header.version#<br>
Height : #rect.getHeight()#<br>
Width : #rect.getWidth()#");
</cfscript>

The height and width value that you see here would be in twips which is defined as 1/20 of a point or 1/1440 inch. This means, for a 72 dpi screeen, you will have to divide it by 20 to get the height and width in pixels.

Thursday, January 10, 2008

DNS lookup Caching in ColdFusion/Java

Two days back, I got an interesting question from our support team. The Customer in this case was using CFLdap which connected to a particular LDAP server and things were working fine. The problem came when they replaced this LDAP server with another LDAP server and assigned the same dns name to the new ldap server. Ideally any connection made henceforth should have worked with the new ldap server but actually that did not happen. ColdFusion started throwing error and it did not work until the ColdFusion server is restarted. Ever seen something similar?

It is obvious that it happend because the IP address was being cached for the host name as a result of which ColdFusion was still trying to connect to the old IP address even though the host name now pointed to a different IP address. This caching also applies for all other network protocol tags such as CFHTTP, CFFTP, CFFEED etc and is not limited to CFLDAP. It is actually the JVM that does this caching. When JVM is requested to resolve a host name, it does the host name to IP address resolution and caches the result. This result will be used for all subsequent lookups. It also caches the negative results. By that I mean, if the dns reolution fails, it caches the failed result for a certain period so that any lookup for that hostname in that period will not result into another resolution on network and will immediately return the failed result.

For more detail on this caching, check out the Javadoc for InetAddress class.

As per this doc, there are two parameters that control this caching

networkaddress.cache.ttl (default: -1)
Indicates the caching policy for successful name lookups from the name service. The value is specified as as integer to indicate the number of seconds to cache the successful lookup.

A value of -1 indicates "cache forever".

networkaddress.cache.negative.ttl (default: 10)
Indicates the caching policy for un-successful name lookups from the name service. The value is specified as as integer to indicate the number of seconds to cache the failure for un-successful lookups.

A value of 0 indicates "never cache". A value of -1 indicates "cache forever".


Now where do you specify this settying? You can specify this setting in <Java_home>/jre/lib/security/java.security file. For standalone ColdFusion server it will be in <ColdFusion _dir>/runtime/jre/lib/security/java.security file.

As you see, by default networkaddress.cache.ttl caches the result for ever and hence it is configured for best performance. Any change to this mean drop in performance. If you don't want to cache the resolved IP address for ever, as is the case here, you would need to change networkaddress.cache.ttl value to 60 seconds or 300 seconds or any value you feel suitable. You would not want to set it to 0 as that would mean "never cache" the result which might affect the performance significantly.

In which case you would want to change the value for networkaddress.cache.negative.ttl? That would be mostly in case when you want to cache the negative result for a longer time and in turn improving the performance. For example, if you are trying to connect to a hostname which can not be resolved to any ip address, and that happens very frequently, each of the call (as long as they are not in the same 10 sec window) would become very slow. Increasing this value would increase the performance but again you would not want to cache the negative result for ever.

After you change this setting, you will have to restart the ColdFusion server for this change to take effect.

Friday, December 14, 2007

Images and CFDocument performance

Sometime back one of our customer reported that using png images in cfdocument makes it very very slow. I could not replicate it with any png image but it did happen with his png image.

Today, Andy reported a similar issue but this time it was for jpeg images. In both the cases, performance hit is huge and it does not happen with all the images.

I spent a significant amount of time debugging it today and it turns out that the reason for both the issues are same - there is something special about these images and that is colorspace. All these images actually had a different colorspace than what java imaging uses. Because of this, when we need the pixel values of image to print it on pdf (by calling BufferedImage.getRGB()), it tries to convert this colorspace to RGB colorspace and that is very very costly. That is where the entire time goes. So how do you fix it? I opened all the images in an image editor and saved it again. This time it got saved in standard RGB colorspace and the time taken to create the pdf got reduced from 110 sec to 1.5 seconds. That is huge!!! Isn't it? But can you control all the images over the web? NO.. right? Read on.. there is more to this story.

A little bit of looking up on web pointed me to this Sun bug which is the exact same bug which we were hitting. Thankfully it got fixed in mustang i.e JDK1.6 which ColdFusion8 uses by default. But hey wait a second.. Didn't Andy say that he is seeing it on ColdFusion 8? why do we still see this happening when it is fixed in JDK1.6? It appears that this bug was fixed only in core JDK api but not in JAI (Java advanced imaging) codecLib that ColdFusion 8 uses. So what do we do now? You can do either of these two

  1. Remove clibwrapper_jiio.jar from "lib" folder.
  2. Or, set this system property to the JVM. -Dcom.sun.media.imageio.disableCodecLib=true . You can set this in [cf-install_dir]/runtime/bin/jvm.config if you are using standalone coldfusion server.

You should keep in mind that codecLib libraries are native libraries which are meant to increase the java imaging performance. So disabling it might degrade the performance of CFImage somewhere. Also keep in mind that removing this jar or disabling codecLib will not result into any loss of functionality - it just means that all image operations will be pure java.

There is another related Sun's bug which I thought might be useful to you. Image loading might get very slow if the server is running in debug mode. Your server is running in debug mode if you see something like this in your jvm.config or VM startup option.

-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
This bug got introduced in JDK1.6 and does not exist on 1.5. So make sure that you are not running the server in debug mode.

To summarise, you can do following to increase the performance of image loading in cfdocument.
  1. If you are on JDK1.5, there is not much you can do. The only option is to change the colorspace of images. I will try to see if we can address this in ColdFusion code.
  2. If you are on JDK1.6,
    • Disable codecLib as mentioned above.
    • Disable debug mode by removing the complete debug string mentioned above
  3. In addition to this, you might want to use 'localurl' attribute for cfdocument tag. See this for more details.

Wednesday, December 12, 2007

Missing text in pdf created by CFDocument

I had heard about people not getting images in the generated pdf but this one was something new and spooky. Yesterday Andy Matthews posted it on cf-talk where he says that for his content, though a pdf containing 15 pages gets created but only the first page has some content and rest of the pages are blank. I ran his code on my machine and sure enough it was happening. All the content including text and image on all the pages except the first page was gone. So what was wrong? I simplified his code and here is a simple example which you can try.

<cfdocument format="pdf">
Text Outside div
<div style="width:300px;overflow:auto;">
This is text inside the div. Will it show up?
</div><br>
</cfdocument>
In the generated pdf you will see only "Text Outside div". Spooky.. isn't it? The reason it happened was because of css style 'overflow:auto'. The rendering engine used by CFDocument underneath does not handle overflow:auto and it simply ignores the content in the div. The weird part is - it considers that content for all rendering calculations including page number and page break calculation.

Something you should watch out for if you use css styles in cfdocument. A workaround for the time being is not to use overflow:auto style. So modifying the above code like this will make it work.

<cfdocument format="pdf">
Text Outside div
<div style="width:300px;">
This is text inside the div. Will it show up?
</div><br>
</cfdocument>

Thursday, August 16, 2007

ColdFusion 8 : Changes with File Upload

Till ColdFusion 7, it was not possible to upload a large file. The server could not handle uploading a file of probably anything more than 300-400 MB. And on top of that, if there are multiple people trying to upload large files simultaneously to the server, only thing that you can do is to pray :-)

You might wonder and I agree with you, that its a huge limitation. So why was it this way ? Well.. that was because the server had to keep the entire uploaded file in the memory. Whoa.. Why the heck does the server need to keep the entire uploaded file in memory? - For a single reason of serving the function GetHttpRequestData(). As you know, this function returns a 'struct' containing the metadata for the Http request that comes to the server. Since this struct also includes request body, and the uploaded file is part of the request, the file has to be retained in the memory in order to honor this function.

With ColdFusion 8, we have removed the limitation on file upload size. So now you can upload files of any size without causing any issue with the server. I have successfully tried with 5 GB file because that was the biggest I could get hold of but you can absolutely upload files of any size. ColdFusion nicely handles it, reads it the way it should without requiring much memory.

But hey, hold on a sec.. What about the function GetHttpRequestData()? Wasn't that the reason for this limitation in the first place? Well, the function GetHttpRequestData() works perfectly fine except in this one case - In the multipart request, i.e; request in which a file is uploaded - "content" in the returned struct will be empty. If you need to get the uploaded file content in memory, you can always read the uploaded file and keep it in memory. So that should not be much we are asking for. Are we?

If in case, you absolutely want the content from GetHttpRequestData() and file upload size is of no concern to you, we allow you to revert back to ColdFusion 7 behaviour. All you need to do for this is to set the Java system property "coldfusion.markResetForMultiPart" to "true" and you are back to the old behavior.

Wednesday, August 08, 2007

ColdFusion and WebService : "class file has wrong version"

I can't even count the number of times I have seen this error while QA folks were running webservices regressions for ColdFusion 8. Everytime, someone was running webservices tests on a J2EE configuration they used to get an error :

coldfusion.jsp.CompilationFailedException: Errors reported by Java compiler:
....
class file has wrong version 49.0, should be 48.0

I saw the same error posted in the public forum where the user had deployed ColdFusion 8 on JRun with JDK1.5 and was trying to invoke webservice. And hence this blog entry :-)

This error comes because of incompatibility between the tools.jar and the JVM version. ColdFusion 8 ships Java 1.6's tools.jar (you will find it in cfusion/lib) which is of course incompatible with any other JDK/JRE version. You will see the same behavior with ColdFusion 7 as well, if you run it on a JVM other than 1.4. So the rule of thumb is - if ColdFusion is running on a JVM version other than the shipped one, you must ensure that the tools.jar is in synch. In short, you will need to copy JDK's tools.jar to cfusion/lib.

Friday, August 03, 2007

Using CFFeed with URL sending compressed content

Today someone posted on ColdFusion forum regarding this problem where cffeed was not able to handle a particular URL and it was throwing an error. The URL which he tried was http://movies.msn.com/rss/topcelebs and it failed with an error

"Unable to read the source URL.
unknown compression method
"

The reason it happens is - The URL returns the response in gzip compressed format only. So when ColdFusion sent a request to this URL and asked for uncompressed data, it could not get anything and hence it was unable to read it. A simpe workaround for this is to use cfhttp to fetch the content, write to a temporary file and then use the cffeed tag to read this file. Important thing to keep in mind here is to set an additional header in the cfhttp tag using cfhttpparam to indicate that it can accept compressed data as well.

Here is the modified code where it first tries cffeed with the URL. If that fails, then it tries to use cfhttp to fetch the content and writes to a temporary file and then uses it in cffeed.

<cfset tempDir=GetTempDirectory()>
<cfset tempFile = GetTempFile(tempDir, "myfeed")>
<cfset tempFileName = GetFileInfo(tempFile).name>

<cftry>
<cffeed action="read" source="http://movies.msn.com/rss/topcelebs" name="feedInStruct" >
<cfcatch any>
<cfhttp url="http://movies.msn.com/rss/topcelebs" path="#tempDir#" file="#tempFileName#">
<cfhttpparam type="header" name="Accept-Encoding" value="compress,gzip,deflate">
</cfhttp>
<cffeed action="read" source="#tempFile#" name="feedInStruct" >
</cfcatch>
</cftry>
<cfif FileExists(tempFile)>
<cfset FileDelete(tempFile)>
</cfif>

<cfdump var="#feedInStruct#">

Thursday, August 02, 2007

ColdFusion 8 : IsInstanceOf

If you use lot of CFC inside your ColdFusion application, I am sure you would have come across a situation where you would need to know whether the object is an instance of a particular CFC. This is specially needed when you have components extending other component or you are passing the objects around. ColdFusion 8 introduces a new function IsInstanceOf to do exactly the same. It becomes even more useful after we have interfaces in ColdFusion. And the icing on the cake is that it works even with java objects which means that you can use this function to find out if a particular object is of a particular java class type.

Here is how the function looks.

IsInstanceOf(object, typeName)

where typeName is name of the component/Interface or fully qualified java class name.

It returns 'true' if

  • The object passed is an instance of a component which is same as specified type or inherits it or implements the specified interface. Just to be clear, a component 'A' inherits a component 'B' if A or any of its super component extends 'B'. Similarly a Component 'A' implements an interface 'B' if A or any of its super component, implements interface 'B' or any of the interface that 'A' or its parents implement, extends from the specified interface.
  • The object passed is an instance of a java class (created using cfobject or createObject for java class) which is same as specified class name or inherits the specified class name or implements the specified interface.

Here is an example

Intf.cfc
<cfinterface>
<cffunction name = "foo">
</cffunction>
</cfinterface>

Comp.cfc

<cfcomponent implements="Intf">
<cffunction name = "foo">
<cfoutput>In method foo</cfoutput>
</cffunction>
</cfcomponent>

test.cfm

<cfset obj = CreateObject("Component", "Comp")>
<!--- Create a Java object --->
<cfset javaObj = CreateObject("java", "java.lang.StringBuffer")>

<cfoutput>object is of type Comp : #IsInstanceOf(obj, "Comp")#</cfoutput><br>
<cfoutput>object is of type Intf : #IsInstanceOf(obj, "Intf")#</cfoutput><br>

<cfoutput>java object is of type String : #IsInstanceOf(javaobj, "java.lang.String")#</cfoutput><br>
<cfoutput>java object is of type StringBuffer : #IsInstanceOf(javaobj, "java.lang.StringBuffer")#</cfoutput><br>

Thursday, July 19, 2007

ListToArray in ColdFusion 8

There has been lots and lots of discussion in CF blogs and forums about ListToArray not supporting empty elements. Seeing which I had blogged about a simple way which could do the same. Thanks to Charlie Griefer for pointing out the problem in it and then thanks to Ben Nadel and Andrew Clark for pointing me in the right direction.

Though it got pretty late, I was able to sneak-in this change in ColdFusion 8. ListToArray() now takes an additional optional argument "includeEmptyElements", which if 'true' will include the empty elements of list into the array. Default is of course 'false'. It also takes care of empty elements at the end of list and multiple delimiters. Here is how the function looks

ListToArray(list, delimiter, includeEmptyElements) returns Array

Lets take a look at couple of examples to see it working

<cfset list = "a,b,,c, ,d,,">
<cfset arr = ListToArray(list, ',', true)>
<cfdump var="#arr#">
Here is how the output looks.








Here is another example.

<cfset list = "one,/$/,six">
<cfset arr = listToArray(list, ",$/",true)>
<cfdump var="#arr#">

The output for which looks like this







Though we wanted to, there was just not enough time to make similar change in all the list functions for CF 8. Something for CF 9 :-)

Enhancements to CFDocument in ColdFusion 8

As you all know, CFDocument tag is used to easily create pdf or flashpaper documents from HTML/CFML content. ColdFusion 8 has added lot of enhancements to it and in this post we will talk about those enhancements.

1. Bookmark : You can create bookmarks for each section of the pdf using "bookmark" attribute. The bookmark created is of only one level and its name is set to the documentsection's name. Here is a sample code for creating pdf with bookmarks.

<cfdocument format="PDF" bookmark="yes">
<cfdocumentsection name="Introduction">
<h3>Introduction</h3>
<p>The introduction goes here.</p>
</cfdocumentsection>
<cfdocumentsection name="Chapter 1">
<h3>Chapter 1: Getting Started</h3>
<p>Chapter 1 goes here.</p>
</cfdocumentsection>
<cfdocumentsection name="Chapter 2">
<h3>Chapter 2: Building Applications</h3>
<p>Chapter 2 goes here.</p>
</cfdocumentsection>
<cfdocumentsection name="Conclusion">
<h3>Conclusion</h3>
<p>The conclusion goes here.</p>
</cfdocumentsection>
</cfdocument>

2. Proxy Support : With ColdFusion 8, you can now provide proxy configuration to cfdocument for retrieving external content like images. This will be useful in situation where the machine hosting ColdFusion is connected to the external world via a proxy. The new attributes added for this (which are self explanatory) are listed below

  • proxyHost
  • proxyPort
  • proxyUser
  • proxyPort

3. Content from URL : Though this was added in 7.0.2, I think it makes sense to add here because it was not there in 7. :-) Have you ever created or wanted to create a pdf from a web page? If yes, then the new attribute "src" in cfdocument and cfdocumentsection tag makes it very easy to do this. Here is an example to do this.

<cfdocument format="pdf" src="http://www.google.com" />

4. Basic Authentication : If the CFDocument body contains a resource (e.g; image or URL) that is protected with basic authentication, ColdFusion 7 can not retrieve it and it was one of the reason for getting "red-x" for images. (See my old post on this). ColdFusion 8 addresses this by adding these two attributes to cfdocument and cfdocumentsection tag.

  • authuser - user name to be used for basic authentication.
  • authpassword - password to be used for basic authentication.

5. User Agent : There are some cases where the web server is configured to allow requests only from a certain set of browsers (User agents to be precise) to prevent spiders and bots from overloading the server. In ColdFusion 7, when CFDocument creates a URLConnection for an image, it sends a "User-Agent" header, that looks like "User-Agent:Java/1.4.2_07", in the HTTP request. If the web server does not recognize "Java" user-agent, it returns a status code of 404 (resource not found) and hence the images can not be displayed. ColdFusion 8 adds a new attribute "user-agent" to address this.

  • user-agent : User agent to be used for making http connection. The default value will now be "ColdFusion". If this also does not work, you can use the same string that browsers like IE or firefox use. This attribute has been added to <cfdocument> and <cfdocumentsection> tag.

6. PDF name : When a pdf generated by cfdocument is sent to the browser and you try to save it, the browser will prompt with the cfm name for the pdf which is generally not desirable. With ColdFusion 8, you can provide the appropriate name in "saveAsName" attribute of cfdocument.

  • saveAsName - Name that appears in the 'saveAs' dialog when you try to save the pdf from the browser (Generally File -> saveAs). This name will not work if you use "save" dialog of the pdf plugin.

Here is a sample code for the same.

<cfdocument format="pdf" saveAsName="mypdf">
<p>This is a PDF document.</p>
</cfdocument>

7. Local URLs : When CFDocument body contains a relative URL, ColdFusion will resolve the relative URL to an absolute URL and will send an HTTP request for this url. A side effect of this is - Server ends up sending HTTP request even for local URL or images that are lying on the local file system which obviously hurts the performance. In ColdFusion 8, we have added a new attribute "localURL" to cfdocument tag which if enabled, will try to resolve the relative URLs as file on the local machine.

  • localURL : "true" | "false" - It should be enabled if the images used in cfdocument body are on the local machine. This would make the cfdocument engine retrieve the images directly from the file system rather then asking the server for it over http.

This attribute helps reducing the load from the server so that the same web server thread can now serve user request instead of serving local images to CFDocument. This also addresses some of the "missing image" problems which I mentioned here. Here is a sample code using this attribute.

<cfdocument format="PDF" localUrl="true">
<table>
<tr>
<td>bird</td>
<td><image src="images/bird.jpg"></td>
</tr>
<tr>
<td>Rose</td>
<td><image src="images/rose.jpg"></td>
</tr>
</table>
</cfdocument>

8. Section Page Counts : CFDOCUMENT scope contains two new variables which give you the page counts for document section.

  • TOTALSECTIONPAGECOUNT - total no of pages in the current section
  • CURRENTSECTIONPAGENUMBER - Current page number in the current section.

9. Dynamic header and footer : CFDOCUMENT scope variables can now be used in expressions inside <cfdocumentitem> which makes it possible to have dynamic header and footers. You can now build logic for header/footer content based on the page number. Here is a sample code which prints section title if the page is even and prints the page no otherwise. Below is a code snippet which creates a dynamic header.

    <cfdocumentitem type="header">
<cfif (cfdocument.currentpagenumber mod 2) is 0>
<cfoutput>#sectionTitle#</cfoutput>
<cfelse>
<cfoutput>#cfdocument.currentpagenumber# of #cfdocument.TOTALPAGECOUNT#</cfoutput>
</cfif>
</cfdocumentitem>

10. We have also fixed most of the CFDocument related bugs e.g text chopping, image cropping or red-x, image scaling, "Document has no pages" etc.

Tuesday, July 10, 2007

New File I/O in ColdFusion 8

Till now we have been using <cffile> for all kind of file operations and it does a very good job. If you want to read a file, give the file to this tag and this tag gives you the read content. If you want to write content to a file, you give the content and file name to this tag and it will do that. You want to copy/delete/move your files, this tag will do all of that. All very simple and short. But there are two particular issues which <cffile> does not address.

1. Reading/writing big files - Since <cffile> is a tag, it can only perform one-shot operations. So, to read, it has to read everything in one shot and to write, you have to provide the entire content and that means that <cffile> will have to keep the entire content in memory. It is not of much concern if the file size is just few KBs but as the size increases beyond 100 kb or when it reaches few megs, it can really hurt. It would create a memory crunch on the server and if the load is high and there are many read/write happening simultaneously with large files, it can even lead to OutOfMemory error in server. Apart from creating memory crunch, it will also slow down the server because VM would need to allocate/deallocate larger chunk of memory which would lead to larger and frequent Garbage Collection cycle. At this point, you might ask, why would I ever read or write such a big file? Well I can think of few

  • You need to process the data that comes in a flat file
  • csv parsing
  • Finding the mime type of a file like mp3, image, video etc
  • you want to create a log viewer
  • ... many more
You get the idea.. right?

2. Again since <cffile> is a tag, it is not very easy to use inside cfscript. Either you have to move out of cfscript to use this tag or you wrap this tag in a function and call that function. Though thats true with all the tags but cffile is so commonly used that this looks like a limitation.

New File I/O introduced in ColdFusion 8 addresses both these problems. New File I/O is all based on functions and hence that automatically takes care of problem 2. That means you no longer need to use cffile if you are inside cfscript. I will give more details on handling problem 2 in my next post. In this post I will mainly focus on reading/writing files in chunk using new IO .

The new I/O is based on the same philosophy that is used in other languages i.e;

  1. You first open a file
  2. Perform read/write operations on it
  3. and close the file.
Lets see each of the steps in little detail.

Step 1 : Open a file : Here is the function to open a file

FileOpen(filepath [,mode] [,charset]) -> fileobject
Both mode and charset here and optional. Mode can be "read", "readBinary", "write" or "append"

"read" mode, which is default, is used to read a text file and hence any read operation will give you text data from it. When the file is a text file, you can also optionally specify the charset of the file. So if the file contains UTF-8 or UTF-16 characters (or characters from any other charset), you need to specify it while opening the file.

"readBinary" is used to read a binary file and hence any read operation will give you the binary data i.e byte array.

"write" mode will open the file in write mode which means that if the file already exists, it will be overwritten.

"append" mode, as the name suggests, will open the file in append mode which means that any write operation on that file object will write it at the end of file.

FileOpen function returns you a handle to the native file and you need to use this handle for all further read/write operation. Of course you should keep in mind that you can not perform "read" operation on a file handle that was opened in "write" mode and vice versa.

Step 2
: Do Read/Write operations : Once you get the handle to file object, you can perform multiple read/write operations using this handle. There are several functions to do that.

2A. Read Operation :

i) FileRead(fileobj, no of character/bytes to read) : This provides you a way to read a chunk of data (say 1 kb at a time) from the file at a time. Since you only read a chink of data at a time, it does not create memory crunch on the server. Since this is read operation, file must have been opened in "read" or "readBinary" mode. Depending on which mode the file was opened, this function will return the text or binary data read. One thing to note here - If the data remaining is less than the requested size, this method will return you only the remainign data. i.e if 100 character are remaining in the file being read, and you request for 1000 characters, it will return you 100 characters only.

ii) FileReadLine(fileobject) - This reads one line from the text file. To call this method, the file must have been opened in "read" mode.

Both these read operations can be called multiple times until you reach end of the file. One the end of file has reached, any further read call will result into an "EndOfFile" error. So in order to avoid this error, you should always check whether you have reached the end of file. And the function to do that is

FileIsEOF(fileobj) : Just to be more clear, EOF here stands for "End of File". This function will return true if the end of file has been reached otherwise will return false.

Here are few examples of reading content from file
Read 1 kb binary data at a time.

<cfscript>
myfile = FileOpen("c:\temp\song.mp3", "readbinary");
while (! FileIsEOF(myfile)) { // continue the loop if the end of file has not reached
x = FileRead(myfile, 1024); // read 1 kb binary data
...// process this binary data..
}
</cfscript>
Process a text file line by line
<cfscript>
myfile = FileOpen("c:\temp\myfile.txt", "read");
while (! FileIsEOF(myfile)) { // continue the loop if the end of file has not reached
x = FileReadLine(myfile); // read a line
...// process this line..
}
</cfscript>
2B. Write operation

i)FileWrite(fileobject, content) - This will add the text or binary content to the file. The file must have been opened in "write" or "append" mode.

ii) FileWriteLine(fileobject, text) - This will add the text followed by a new line character to the file. Here again, the file must have been opened in "write" or "append" mode.

You might wonder that if both the write operations add the content to the file, whats the difference between "write" and "append" mode? The difference is only at the time of opening the file. As I said earlier, opening the file in "write" mode will overwrite the file if already existsed and put the file pointer at the the beginning of file. Whereas opening file in "append" mode will simply put the file pointer at the end of file.

Any subsequent "write" calls, irrespective of "write" or "append" mode, will append the content to the file.

Here is an example of writing content to a file. This reads one line from an input file, does some processing on it, and writes the resultant data to another file.

<cfscript>
infile = FileOpen("c:\temp\input.txt", "read");
outfile = FileOpen("C:\temp\result.txt", "write");
while (! FileIsEOF(infile)) { // continue the loop if the end of file has not reached
x = FileReadLine(infile); // read a line
data = processLine(x);
FileWriteLine(outfile, data);
}
</cfscript>

Step 3 : Close the file : Once you are done with read/write operations, you *must* close the handle to file. And the way to do that is using function
FileClose(fileobj)
What if you don't close the file object? Well, that file will remain locked by the server as long as the file is open, and no other process can modify/rename or delete that file.
You might also ask, why does not ColdFusion automatically take care of closing the file? Why should the developer be bothered about it? Well.. ColdFusion does take care of it when the file object goes out of scope and if it is not kept in any accessible scopes but you can never be certain when exactly this will happen. This might happen immediately or this might happen hours later :-).
Bottomline, you should make it a practice to call FileClose() once you are done with the file object.
Just to show its usage, I will complete the example I used in write.
<cfscript>
infile = FileOpen("c:\temp\input.txt", "read");
outfile = FileOpen("C:\temp\result.txt", "write");
while (! FileIsEOF(infile)) { // continue the loop if the end of file has not reached
x = FileReadLine(infile); // read a line
data = processLine(x);
FileWriteLine(outfile, data);
}
FileClose(infile);
FileClose(outfile);
</cfscript>

These set of functions would greatly help if you need to work with a file of more than 10 kb size.

Apart from these set of functions, ColdFusion 8 also adds a new language struct to read text files. With ColdFusion 8, you can use <cfloop> to iterate over "lines" or "characters" in a text file. This makes it very easy and convenient to do any kind of text file parsing or processing in your application. Lets take a look at the new syntax of cfloop for reading file (and I really love this syntax :-)).

New attributes in cfloop for reading file :

"file" - path of the file to read
"characters" - no of characters to read in one iteration.

  1. Reading Lines : Below is the simplest syntax to read one line at a time from the file in a loop. This would read the entire file and the loop would end when the file has been completely read. The read content will be available in the index variable specified.

    <cfloop file="c:\temp\myfile.txt" index="line">
    <cfoutput>#line#</cfoutput> <!--- or do whatever with the line --->
    </cfloop>

    With cfloop, you can also iterate over a part of the file by specifying "from" and "to" values.
    Here is an example to loop over lines between 10 and 20.

    <cfloop file="c:\temp\myfile.txt" index="line" from=10 to=20>
    <cfoutput>#line#</cfoutput> <!--- or do whatever with the line --->
    </cfloop>

    "from" and "to" both are optional attributes where "from" defaults to '1' i.e start of file and "to" defaults to the last line of the file.

    So to read first 10 lines from the file, you can use

    <cfloop file="c:\temp\myfile.txt" index="line" to="10">
    <cfoutput>#line#</cfoutput> <!--- or do whatever with the line --->
    </cfloop>

    One word of caution here - If you use "to" attribute here, its value must be less than the number of lines in the file otherwise you would get an "EndOfFile" error. For example, if I had only 5 lines in my file and value of to is 7, this would throw an error because line 6 and 7 do not exist.

  2. Reading characters : For reading characters instead of line, you need to provide the value for "characters" attribute and as many characters will be read in one iteration. The loop will automatically end when the end of file has reached. The read content will be available in the index variable specified.

    An example for that is

    <cfloop file="c:\temp\myfile.txt" index="chars" characters="1000">
    <cfset x=chars>
    <!--- do whatever with the characters --->
    </cfloop>
    One important thing to note here. In the last iteration, when the end of file has reached, index variable will only have the remaining characters. For example if I have 130 characters in the file and I run the loop to read a chunk of 20 characters, in the last iteration, index variable's value will only have last 10 characters.

This completes the first part of new File IO which mainly addresses the problem of working with larger files. However this does not mean that you can not or should not use these for smaller files. You can very much use these for all kind of files. These are very simple to use and perform really well. Go ahead and play around with it !

Friday, June 08, 2007

Finding Image Type for a file

This is a follow up on my previous post on URLConnection APIs
We will use this API to find out the image type of a given file. Here is the udf.


<cffunction name="getMimeType">
<cfargument name="filepath">

<cfset var urlConn = createObject("java", "java.net.URLConnection")>
<cfset var fileobj = fileopen(filepath, "readbinary")>
<!--- just read the first 20 characters of the file as thats sufficient --->

<cfset var bytes = fileread(fileobj, 20)>
<cfset var istream = createObject("java", "java.io.ByteArrayInputStream").init(bytes)>
<cfset fileobj.close()>
<cfreturn urlConn.guessContentTypeFromStream(istream)>
</cffunction>

<cffunction name="GetImageType">
<cfargument name="filepath">
<cfset var mimetype = getMimeType(filepath)>
<cfset var imagetype="">
<cfif not isDefined("mimetype")>
<cfthrow message="Not an Image file">
</cfif>
<cfswitch expression="#mimetype#">
<cfcase Value="image/gif">
<cfset imagetype="gif">
</cfcase>
<cfcase Value="image/x-bitmap">
<cfset imagetype="bmp">
</cfcase>
<cfcase Value="image/png">
<cfset imagetype="png">
</cfcase>
<cfcase Value="image/jpeg">
<cfset imagetype="jpeg">
</cfcase>
<cfcase Value="image/jpg">
<cfset imagetype="jpeg">
</cfcase>
<cfdefaultcase>
<cfthrow message="Not an Image file">
</cfdefaultcase>
</cfswitch>
<cfreturn imagetype>
</cffunction>

If I run the code below

<cfset filepath = "C:\temp\test.jpg">
<cfoutput>#GetImageType(filepath)#</cfoutput>

It nicely prints out "jpeg".



You should note that I used the new File IO function added in ColdFusion 8 using which I can read as many no of bytes from the file as I want. No more reading the entire file into memory.

More extensive post on File IO functions coming next !

Finding mime type of any file

Hemant recently told me about this cool API in java.net.URLConnection which can tell you the mime type for common file types.


guessContentTypeFromName(String name) - that simply checks the extension of the file name specified and gets the mime type from a static map it maintains. This can be very useful in many scenarios. The map it uses is quite extensive and contains almost all the mime types.

But this has a problem. What if some one has renamed a gif to jpg? This method will say that the file is a jpg image whereas it was a gif.

Thankfully there is another method in the same class which can address this problem.


guessContentTypeFromStream(InputStream stream) - which reads the stream, takes a look at the initial bits of the file and uses that to determine the file type.
This method does a check for the following mime types :-


- application/java-vm
- application/x-java-serialized-object
- text/html
- application/xml
- image/gif
- image/x-bitmap
- image/x-pixmap
- image/png
- image/jpeg
- image/jpg
- image/vnd.fpx
- audio/basic
- audio/x-wav


Though this method leaves out many of other common types such as mp3, it is still very useful. This makes it so easy to find out the type of an image.

Saturday, June 02, 2007

ListToArray with empty elements - Part II

Yesterday I had posted about how to convert List to Array that would include empty elements. Charlie rightly pointed out a problem with that which is - it does not consider the empty elements at the end of the list.
So, a list "a,b,,c,d,," when converted to array will have only 5 elements instead of 7.

Incidently Charlie had also talked about the same some time back here and he mentions another problem with this approach. The array that is returned after split method is read only and you can not modify this array.
In order to address both these, I had to write this UDF which I guess many people would have written anyways..





<cfscript>
function list2Array(list)
{
var endCommaCount = 0;
var i = 0; var c = "";
var stringForSplit = ""; var retList=""; var arr = "";

for(i=len(list); i > 0; i--)
{
c = list.charAt(i-1);
if(c == ',')
endCommaCount++;
if( c != ',' && c != ' ')
break;
}
retlist = ArrayNew(1);

if(i != 0)
{
stringForSplit = left(list, i);
arr = stringForSplit.split("\s*,\s*");

for(i = 1; i <= ArrayLen(arr); i++)
ArrayAppend(retList, arr[i]);
}

for(i = 0; i < endCommaCount; i++)
ArrayAppend(retList, "");

return retList;
}
</cfscript>



UPDATE : Thanks to Andrew Clark for the split tip which makes it more slim and elegant. The code above changes like



<cfscript>
function list2Array(list)
{
var i = 0;
var retlist = ArrayNew(1);
var arr = list.split(",", -1);
for(i = 1; i <= ArrayLen(arr); i++)
ArrayAppend(retList, arr[i]);
return retList;
}
</cfscript>

Lets test this udf and see if it works..


<cfset cflist = "a,b,,c,,d, ,">
<cfset cfarr1 = ListToArray(cflist)>
<cfset ArrayAppend(cfarr1, "END")>
<cfset cfarr2 = List2Array(cflist)>
<cfset ArrayAppend(cfarr2, "END")>

With CF Function : <cfdump var="#cfarr1#" format="text">
With UDF : <cfdump var="#cfarr2#" format="text">


And this produces this result.
With CF Function :
array - Top 6 of 6 rows

1) a
2) b
3) c
4) d
5)
6) END
With UDF :
array - Top 9 of 9 rows

1) a
2) b
3) [empty string]
4) c
5) [empty string]
6) d
7) [empty string]
8) [empty string]
9) END


One thing to note here is that I am using new cfscript operators added in ColdFusion 8 which is so cool!!!

Friday, June 01, 2007

ListToArray with empty elements

Recently there was a request on our forum to support empty elements in ListToArray function for Scorpio i.e ColdFusion 8. it might not be done in Scorpio since it is too late to add a new function. However it can be very easily achieved in cfm itself. Here is the code to do it.

<cfset cflist="a,b,,c,,,d">
<cfset cfarr1 = ListToArray(cflist)>

<cfset cfarr2 = cflist.split(",")>
<cfset cfarr2 = cflist.split(",", -1)>

<cfdump var="#cfarr1#">
<cfdump var="#cfarr2#">

<cfscript>
for(i=1; i <= ArrayLen(cfarr2); i++)
{
writeoutput("Element #i# : #cfarr2[i]# <br>");
}
</cfscript>



As you can see, 3rd, 5th and 6th element in this cfarr2 are empty.