Viewing Category: Java  [clear category selection]

Removing a double-slash sequence from URLs

Here's a mini, nay micro, tip on Java regex goodness. I often build up URLs from bits and pieces of strings where there is not a strict convention for whether a leading/trailing slash should exist before/after a directory. The easiest way to deal with this situation is to use the directory separator character gratuitously, and strip out doubles afterwards. Consider the following:

<cfset host = "localhost"/> <cfset filepath = "/downloads/file.txt"> <cfset earl = "http://" & host & "/context/" & filepath/>

Most web servers don't actually care if there's an extra double-slash where there should be just one, as in http://localhost/context//downloads/file.txt. However, there's a simple way to fix up the URL using the String.replaceAll() method. It takes a regular expression that supports negative lookbehind, which prevents the double-slash following the colon from being molested:

<cfset earl = earl.replaceAll("(?<!:)//", "/")/>

Boom! Done.

CFLDAP and master/slave failover

Anybody who has used tags like CFLDAP and CFHTTP much knows that the timeout attribute is frustratingly ineffectual. If the target server is down or otherwise slow to respond, it can hold up the CFML engine response. Charlie Arehart has a great post called CF911: Lies, Damned Lies, and CF Request Timeouts...What You May Not Realize in which he provides all the details surrounding the issue. I was recently writing some code to authenticate against Active Directory using LDAP. The requirement was to use the master server in normal operation, but failover to a slave Active Directory server. If CFLDAP is used to attempt a connection to the master while it is offline, it will cause the CFML engine to hang out waiting for a response for far longer than the number of milliseconds in timeout attribute.

I decided that before making the LDAP connection, I would check to see if a TCP socket could be created to the server. Using a bit of Java, it's pretty simple to test connectivity:

<cffunction name="isTcpServiceAlive" returntype="boolean" access="public" output="false">     <cfargument name="host" type="string" required="true"/>     <cfargument name="port" type="numeric" required="true"/>     <cfargument name="timeout" type="numeric" required="true" default="2000"/>       <cfset var socket = createObject("java", "java.net.Socket").init()/>     <cfset var address = createObject("java", "java.net.InetSocketAddress").init(javaCast("string", arguments.host), javaCast("int", arguments.port))/>       <cftry>         <cfset socket.connect(address, arguments.timeout)/>         <cfset socket.close()/>         <cfreturn true/>         <cfcatch>             <cfreturn false/>         </cfcatch>     </cftry> </cffunction>

If the respone from isTcpServiceAlive() is false, there's not much reason to try CFLDAP using the master:389. The timeout is in milliseconds, and I think 2 seconds is a pretty reasonable amount of time to wait for a service on the same LAN.

ColdFusion/JRun + Apache Commons Logging

I recently encountered a problem I'd never seen before when taking advantage of powerful Java libraries within CFML components. Since this is really specific to Adobe ColdFusion and Macromedia JRun, it won't be an issue with other configurations. Specifically, here are the details:

  • Microsoft Windows “Server” 2003 Standard Edition
  • Adobe ColdFusion 8.01 Enterprise as MultiServer
  • JavaLoader 1.0 beta
  • jXLS 0.9.9-SNAPSHOT
  • Apache POI 3.5-FINAL
  • Apache Commons BeanUtils 1.8.0
  • Apache Commons BeanUtils Collections 1.8.0
  • Apache Commons BeanUtils Core 1.8.0
  • Apache Commons Collections 3.2.1
  • Apache Commons Digester 1.8
  • Apache Commons JEXL 1.1
  • Apache Commons Logging 1.1.1

The project uses the jXLS XLSTransformer utility class to parse an Excel file and to push information into cells containing syntax like ${bean.prop}. It worked fine on my workstation, but when running on the staging servers, it threw an exception with the following message: User-specified log class 'jrunx.axis.Logging' cannot be found or is not useable.

After many hours of investigation, I tracked the problem down to the so-called discovery process that org.apache.commons.logging.LogFactory uses to provide logger implementations. It was my assumption that when using Mark Mandel's JavaLoader to create instances of classes from the JAR files added to its ClassLoader, they would be isolated from the rest of the JVM. That's not exactly how it works, even if configured not to use ColdFusion's ClassLoader as the parent. To force the LogFactory not to use jrunx.axis.Logging, I tried rebuilding the jXLS library with a commons-logging.properties file to specify which logger implementation to use; I tried adding the properties file to the lib directory. Neither solved the problem.

The solution is pretty simple. After configuring JavaLoader, and before having it instantiate the needed XLSTransformer, just set the desired logger programmatically. Here is the chunk of XML that ColdSpring uses to fill JavaLoader with all the JAR files required.

<bean id="jxlsClassPath" class="model.io.FileEnumerator"> <property name="pathList"> <value>/jxls/lib</value> </property> <property name="patternList"> <value>*.jar</value> </property> </bean> <bean id="jxlsFactory" class="jxls.Factory"> <property name="javaloader"> <bean class="javaloader.JavaLoader"> <constructor-arg name="loadPaths"> <bean factory-bean="jxlsClassPath" factory-method="getFileArray"/> </constructor-arg> </bean> </property> </bean>

The code inside the CFC that does the work of creating the XLSTransformer then explicitly sets the logger:

<cfscript> var javaLoader = getJavaLoader(); var logFactory = "null"; var transformer = createObject("component", "jxls.Transformer").init(); logFactory = javaLoader.create("org.apache.commons.logging.LogFactory").getFactory(); logFactory.setAttribute("org.apache.commons.logging.LogFactory", "org.apache.commons.logging.impl.LogFactoryImpl"); logFactory.setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog"); transformer.setXLSTransformer(getJavaLoader().create("net.sf.jxls.transformer.XLSTransformer")); return transformer; </cfscript>

There was much celebration when this worked, I assure you.

jXLS POI Bug

While performing XLS file creation using the jXLS library, I encountered a problem when working with Excel documents that cell validation rules. The issue occurs when jXLS calls the Apache POI methond to shift the rows down. To verify that I understood the problem correctly, and to try to find a workaround, I created a Eclipse Java project to transform a very simple document to reproduces the problem. The following image shows the expected result after transformation:

The input file that works properly looks like this:

By making a small change, to put cell validation into the worksheet as shown in the following image, the transform fails.

The complete stack trace follows:

java.lang.ClassCastException: org.apache.poi.hssf.record.aggregates.DataValidityTable cannot be cast to org.apache.poi.hssf.record.Record at org.apache.poi.hssf.model.RecordOrderer.findInsertPosForNewCondFormatTable(RecordOrderer.java:162) at org.apache.poi.hssf.model.RecordOrderer.findSheetInsertPos(RecordOrderer.java:101) at org.apache.poi.hssf.model.RecordOrderer.addNewSheetRecord(RecordOrderer.java:89) at org.apache.poi.hssf.model.Sheet.getConditionalFormattingTable(Sheet.java:483) at org.apache.poi.hssf.model.Sheet.updateFormulasAfterCellShift(Sheet.java:438) at org.apache.poi.hssf.usermodel.HSSFSheet.shiftRows(HSSFSheet.java:1275) at org.apache.poi.hssf.usermodel.HSSFSheet.shiftRows(HSSFSheet.java:1176) at net.sf.jxls.util.Util.shiftRows(Util.java:908) at net.sf.jxls.util.TagBodyHelper.shift(TagBodyHelper.java:146) at net.sf.jxls.util.TagBodyHelper.removeBorders(TagBodyHelper.java:112) at net.sf.jxls.controller.SheetTransformationControllerImpl.removeBorders(SheetTransformationControllerImpl.java:81) at net.sf.jxls.tag.ForEachTag.process(ForEachTag.java:171) at net.sf.jxls.transformer.TagRowTransformer.transform(TagRowTransformer.java:34) at net.sf.jxls.transformer.SheetTransformer.transformSheet(SheetTransformer.java:89) at net.sf.jxls.transformer.XLSTransformer.transformWorkbook(XLSTransformer.java:238) at net.sf.jxls.transformer.XLSTransformer.transformXLS(XLSTransformer.java:217) at net.sf.jxls.transformer.XLSTransformer.transformXLS(XLSTransformer.java:196) at com.ecivis.poi.Transformer.apply(Transformer.java:18) at com.ecivis.poi.TransformerTestApp.main(TransformerTestApp.java:53)

It appears that this bug has been logged as #46547 and fixed earlier this year. Unfortunately, that fix applies to the POI 3.5 release; the jXLS library only works with POI 3.2 currently. When jXLS 0.9.8 is used with the current release of POI, it crashes (apparently) when trying to call a method that no longer exists on HSSFCell.

If you'd like to download the Eclipse project archive to browse the code or the included Excel files, be my guest: jxls-poi-bug-eclipse-project.zip (~4 Mb)

Configuring a Production Open BlueDragon Server

I've just finished building up a couple production servers to host web applications. The servers are Xen guests on an AMD Quad-Core Opteron x86_64 host. The VPS template is a minimal installation of CentOS, to which I added packages as needed. The release of Sun Java 1.6u12 came out just as I was writing this, so these instructions will need to get updated slightly when JPackage has a new RPM (more on that later). Both Matt Woodward and Dave Shuck recently wrote about configuring CFML engines with Tomcat. The installation I'll describe is somewhat similar.

  • CentOS 5.2
  • Tomcat 5.5.23 (tomcat5-5.5.23-0jpp.7.el5_2.1)
  • Apache 2.2 (httpd-2.2.3-11.el5_2.centos.4)
  • Sun Java 1.6u11 (java-1.6.0-sun-1.6.0.11-1jpp)
  • Sun JavaMail 1.4.1
  • Open BlueDragon 1.0.1

The installation of packages using yum is a snap, however there was an issue with the architecture detection. There is a simple workaround, to hard-code i386 as the basearch:

sed -i -r 's/\$basearch/i386/g' /etc/yum.repos.d/CentOS-Base.repo

The procedure is to install jpackage-utils, then download and repackage the Sun Java SE Development Kit 6 (jdk 1.6) using the JPackage Project non-free nosrc RPM. I install some, but not all of the, resulting RPMs:

yum --nogpgcheck localinstall java-1.6.0-sun-1.6.0.11-1jpp.i586.rpm java-1.6.0-sun-devel-* java-1.6.0-sun-fonts-*

The CentOS Wiki has a thorough article on installing Java on CentOS. I've considered using OpenJDK, but I don't know what sort of compatibility issues that would raise.

The Tomcat server starts up just fine with GNU's version of the Java runtime (libgcj and java-1.4.2-gcj-compat). However, using the GNU version of JavaMail (classpathx-mail) instead of Sun JavaMail, the following chunk of CFML will fail with a javax.mail.NoSuchProviderException exception from within the Open BlueDragon web application:

<cfscript> server = "localhost"; port = 25; username = ""; password = ""; mailSession = createObject("java", "javax.mail.Session").getDefaultInstance(createObject("java", "java.util.Properties").init()); transport = mailSession.getTransport("smtp"); transport.connect(server, JavaCast("int", port), username, password); transport.close(); </cfscript>

Open BlueDragon does include include the correct Jar, but the JVM that Tomcat configures loads the system version first. Rather that muck about with the classpaths, I downloaded the current version of JavaMail, extracted mail.jar, and created alternatives link:

unzip -j -d /tmp javamail-1_4_1.zip javamail-1.4.1/mail.jar mv /tmp/mail.jar /usr/share/java/javamail-1.4.1.jar alternatives --install /usr/share/java/javamail.jar javamail /usr/share/java/javamail-1.4.1.jar 5000 alternatives --auto javamail file /var/lib/tomcat5/common/lib/\[javamail\].jar

Tomcat installs a set of symlinks to /usr/share/tomcat5. Configuration files are placed in /etc/tomcat5. For this installation, I use a stripped-down version of server.xml that provides web application hosting on a per-user basis.

<Server port="8005" shutdown="SHUTDOWN"> <GlobalNamingResources /> <Service name="Catalina"> <Connector port="8080" address="127.0.0.1" protocol="HTTP/1.1" /> <Connector port="8009" address="127.0.0.1" protocol="AJP/1.3" /> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" debug="0" /> <Host name="localhost-username" appBase="/home/username/webapps" unpackWARs="false" autoDeploy="false" debug="1"> <Context path="" docBase="openbd" allowLinking="true" caseSensitive="true" swallowOutput="true" /> </Host> </Engine> </Service> </Server>

The standard Tomcat configuration has a single Host within an Engine named Catalina. I've added a second Host that is specific to a system user username, which allows each user on the system to manage their own deployed web applications and choose their own root Context. Installing Open BlueDragon as the default web application simplifies the Apache HTTP configuration.

The username user has an Apache HTTP configuration file in /etc/httpd/conf.d/username.conf with mod_rewrite rules to proxy all requests for CFML files to the Tomcat HTTP Connector. I had intended to use the AJP Connector with mod_proxy_ajp, but there is a problem with the the proxy request not specifying the proper hostname. There might be a solution to that issue, but I haven't found it yet. The plain mod_proxy_http module works properly in the following configuration:

<VirtualHost *:80> DocumentRoot /home/username/websites/sitename ... RewriteCond %{SCRIPT_FILENAME} \.cfm$ RewriteRule ^/(.*)$ http://localhost-username:8080/$1 [P] </VirtualHost>

The rest of the Apache HTTP configuration handles web requests for flat files, served from ~/websites/sitename. The CFML files can be placed in ~/webapps/openbd, however an easier deployment is to place everything in ~/websites/sitename (like you would with a typical ColdFusion server). Symbolic links can be added for directories containing CFML. Consider the following:

cd ~/webapps/openbd ln -s ../../websites/sitename/MachII MachII

It would probably be a good idea to set the Open BlueDragon root mapping appropriately. There are a few issues with file ownership and permissions that I didn't address above. I've added username to the /etc/sudoers file, granting that user limited access.

Tomcat Monitoring and Startup Via Cron

Over the last week, my virtual private server needed to be restarted a couple times. Once, I was there to see the restart, and manually run the script to bring Tomcat up. However, another time I wasn't. Since I run Tomcat from a plain user account, it doesn't start up with the server itself using the SysV-style init scripts from /etc/init.d. Many years ago, I created a cronjob to check on an Eggdrop IRC bot that sometimes died or went haywire. The same solution works fine for Tomcat. The following shell script (re)starts Tomcat, if needed. It searches for all the processes with the command name java. Any found processes are output with a user-defined format that includes just two fields, and no header. The next command in the pipeline filters out lines that do not start with the appropriate username -- the one that kicked off the cronjob. Any lines making it to the second grep are matched for the Tomcat class name. Lastly, wc counts up the number of lines, which should accurately specify the number of Tomcat instances started by this user. Currently, there aren't any other user accounts that would start an instance of Tomcat, but it's best to prepare for the possibility that a different user account will run Tomcat.

#!/bin/sh export CATALINA_HOME=$HOME/server/tomcat export JRE_HOME=$HOME/java/jre/default PROCS=`/bin/ps -C java -o euser=,args= | grep -E "^$USER" | grep -o -F org.apache.catalina.startup.Bootstrap | wc -l` case $PROCS in 0) echo "Tomcat does not appear to be running. Starting Tomcat..." $CATALINA_HOME/bin/catalina.sh start exit 1 ;; 1) exit 0 ;; *) echo "More than one Tomcat appears to be running. Restarting Tomcat..." $CATALINA_HOME/bin/catalina.sh stop && $CATALINA_HOME/bin/catalina.sh start exit 2 ;; esac

The crontab for the plain user account will run the script above every 5 minutes, which seems pretty reasonable.

0-59/5 * * * * $HOME/bin/check-tomcat.sh

While working on this VPS, I decided to update the JRE to Java 6 update 10 because I've heard that some operations, such as CFC instantiation, are faster. It seems faster, but I don't have any actual performance data to prove it.

Touch a File from Java

Charlie Arehart posted a comment to my blog post about setting the last modified date on a file using some CFML and Java. If a person just wants to set the last modified date of a file to the current system time, his solution is much simpler. I'm reposting his comment here because it's easier to read when formatted as a blog post.

<cfscript> function touch(filepath) { fileInstance = createObject("java", "java.io.File").init(toString(filepath)); currentTime = createObject("java", "java.util.Date").init(); fileInstance.setLastModified(currentTime.getTime()); } </cfscript>

CFFILE and Last Modified

I did a bit of testing this morning to answer a message on the CF-Talk list. After my recent work with Java locale and time zone stuff, I thought this would be a good experiment to mix in a bit of java.io.File.

<!--- A plain file upload form ---> <form method="POST" enctype="multipart/form-data"> <input type="file" name="upload"/><br/><br/> <input type="submit" value="Upload"/> </form> <hr/> <!--- Configuration ---> <cfset tempDirectory = createObject("java", "java.lang.System").getProperty("java.io.tmpdir")/> <cfset directorySeparator = createObject("java", "java.lang.System").getProperty("file.separator")/> <cfset modDateTime = "10/28/1971 12:14 PM"/> <!--- Save incoming file ---> <cfif structKeyExists(form, "upload")> <cffile action="upload" filefield="upload" destination="#tempDirectory#" nameconflict="MakeUnique" mode="644" result="result"/> <cfset clientFileLastMod = dateFormat(result.timeLastModified, "mm/dd/yyyy") & " " & timeFormat(result.timeLastModified, "hh:mm TT")/> <cfoutput><p>File #result.clientFile# was saved as #result.serverFile# with last modified date of #clientFileLastMod#.</p></cfoutput> <!--- Set our own last modified time ---> <cfset dateFormatterClass = createObject("java", "java.text.DateFormat")/> <cfset dateTimeFormatter = dateFormatterClass.getDateTimeInstance(dateFormatterClass.SHORT, dateFormatterClass.SHORT)/> <cfset timeZone = createObject("java", "java.util.TimeZone").getTimeZone("PST")/> <cfset dateTimeFormatter.setTimeZone(timeZone)/> <cfset parsedDateTime = dateTimeFormatter.parse(modDateTime)/> <cfset uploadedFile = createObject("java", "java.io.File").init(result.serverDirectory & directorySeparator & result.serverFile)/> <cfset uploadedFile.setLastModified(parsedDateTime.getTime())/> <cfoutput><p>Setting last modified date on uploaded file to #modDateTime#.</p></cfoutput> <cfdirectory action="list" directory="#uploadedFile.getParent()#" name="listing" filter="#uploadedFile.getName()#"/> <cfdump var="#listing#"/> </cfif>