Viewing Category: CFML
[
clear category selection]
(displaying entries
1 - 20)
We have an internal application (Tomcat 7.0.x, Railo 3.3.x) that is configured with very long session timeout — 10 hours, in fact. This enables a user to login first thing in the morning and not have to worry about losing any session data, even after long periods of inactivity. However, we also have monitoring systems that check the application health every few minutes. Similarly, we have scheduled tasks that run throughout the day. In both of these cases, the HTTP user agents make a single connection, receive a response, and do not save any state between requests, i.e. cookies. This presents a bit of a problem; our application server will allocate a new session for each of these stateless connections and leave it in memory for 10 hours. Consider that one health check occurs every 3 minutes. The minimum number of sessions created before they start expiring is 200 (10 × 60 ÷ 3). That's not an insignificant amount of memory wasted. It would be ideal if we could expire those abandoned sessions sooner, or better yet, recycle a single session for use with these stateless user agents.
Tomcat has a solution. The Crawler Session Manager Valve will effectively gather requests made by the same user agent from the same IP address into a single session. Enabling the valve is as simple as adding the following into the conf/server.xml file:
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.CrawlerSessionManagerValve" crawlerUserAgents=".*ServerNanny.*|.*CFSCHEDULE.*" sessionInactiveInterval="600"/>
</Host>
To verify that the valve is working as expected, make a few requests of the server:
for I in `jot - 1 100`; do curl -s -j -o /dev/null -A 'ServerNanny' 'http://tomcat:8080/app/'; done
Then open the Tomcat Manager and view your application's sessions. Even though there were 100 stateless requests thrown at the server, they should have been forced into a single session. One of the attributes within the session will be org.apache.catalina.valves.CrawlerSessionManagerValve. For example, here's what I see when view the session details:
In contrast, opening the same URL with Firefox, configured to ignore cookies, will result in a new session being created for each request. You can see the CFML application attribute created, and the serialized display of the session scope, which might be useful if you want to snoop on the data in this shared session.
Some things to keep mind
- If access to your application server made only through a broker proxy, you'll want to use the Remote IP Valve to pass accurate client IP addresses to the Crawler Session Manager Valve. I'll discuss this in another post.
- If the client does not send a User-Agent HTTP header, as is the case with Server Nanny, you'll need to insert a custom header into the request so it can be identified by the regex pattern in the valve configuration. I'll describe how I satisfied this requirement in another post.
- You will probably want to have your application set username information in the session so they stand out as authenticated sessions in the Tomcat Manager list of sessions. You could even detect the crawlers and insert a fake username to make the shared session obvious.
CFML,
Tomcat
|
Posted
7/12/12
@ 3:18 AM
by Joseph Lamoree
I'm presenting today at OpenCF Summit on automating CFML application build, test, and deploy.
I'm actively working on my slides for two upcoming presentations. I've created a Github repository for the slides and the sample application: Automating CFML App Build, test, and Deploy
CFML
|
Posted
2/26/12
@ 12:25 AM
by Joseph Lamoree
The lack of a ternary operation often makes for extra verbosity in CFML. When I'm pining for this feature, it's often when doing something like this:
<cfif structKeyExists(names, "name")>
<cfset doSomethingInteresting(names.name)>
<cfelse>
<cfset doSomethingInteresting("default")>
</cfif>
If CFML had a built-in function like the following, that would go a long way to appeasing me:
<cffunction name="structValue" returntype="any" access="public" output="false">
<cfargument name="struct" type="struct" required="true"/>
<cfargument name="key" type="string" required="true"/>
<cfargument name="default" type="any" required="false"/>
<cfif structKeyExists(arguments.struct, arguments.key)>
<cfreturn arguments.struct[arguments.key]/>
<cfelseif structKeyExists(arguments, "default")>
<cfreturn arguments.default/>
<cfelse>
<cfreturn/>
</cfif>
</cffunction>
There is already a structFind(struct, key). If it was expanded to accept a default value, that would be bitchin'. Returning to the example above creating a structValue(struct, key, default) function makes for simpler way to call doSomethingInteresting(value):
<cfset doSomethingInteresting(structValue(names, name, "default"))/>
That's my $0.02.
CFML
|
Posted
10/12/11
@ 11:09 AM
by Joseph Lamoree
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.
CFML,
Java
|
Posted
9/13/11
@ 4:57 PM
by Joseph Lamoree
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.
CFML,
Java
|
Posted
8/26/11
@ 8:26 AM
by Joseph Lamoree
A little bit ago, I added VarScoper to my ANT build script. That was something I had wanted to do for a long time, and it was silly easy. I also wanted to add QueryParam Scanner, but it was a bit trickier. Today I spent some time with it and whipped a pretty decent solution, if I do say so myself. I dropped the application into the root of a ColdFusion instance. Then I interacted with it with my browser and watched which parameters are posted when the scan request is made. I gathered up all the options and made an ANT build script that posts to the QueryParam Scanner application, and parsed the XML for the result.
I took my working script and removed everything but the stuff for QueryParam Scanner: qpscanner-build.xml.txt. At the top of the file are configuration options. At the bottom is a sample ANT task that does the work. I added an option for whether the build should report a failure if any alerts are returned. Simple, right?
I should mention that this build requires the ant-contrib library for the post task. Other than that, it's stock ANT from Apache. Also, the author of QueryParam Scanner, Peter Boughton, has the source in a Git repository on Github.
CFML
|
Posted
6/19/11
@ 5:51 PM
by Joseph Lamoree
While firing up a clean installation of a web application, I got an error (as expected) that the Transfer definitions path did not exist. To make fresh deployment of a web application easier, I modified my Transfer configuration, which is done inside ColdSpring. I created a component that wraps the actual Transfer configuration, and will create a missing path if needed. The same result could have been achieved using AOP and inserting a before advice pointcut, but that too requires writing out a piece of generated code -- the problem I'm trying to solve. Here's the wrapper:
<!--- /model/transfer/TransferEnvironment.cfc --->
<cfcomponent extends="transfer.com.config.Configuration" output="false">
<cffunction name="init" returntype="TransferEnvironment" access="public" output="false">
<cfreturn super.init(argumentCollection=arguments)/>
</cffunction>
<cffunction name="setDefinitionPath" returntype="void" access="public" output="false">
<cfargument name="definitionPath" type="string" required="true"/>
<cfif not directoryExists(expandPath(arguments.definitionPath))>
<cftry>
<cfdirectory action="create" directory="#expandPath(arguments.definitionPath)#" mode="775"/>
<cfcatch>
<cfthrow type="TransferConfigurationException" message="The Transfer definition path (#arguments.definitionPath#) does not exist, and could not be created."/>
</cfcatch>
</cftry>
</cfif>
<cfset super.setDefinitionPath(arguments.definitionPath)/>
</cffunction>
</cfcomponent>
The ColdSpring XML using the wrapper is practically unchanged from what it was when using the actual Transfer configuration component:
<bean id="transferFactory" class="transfer.TransferFactory" lazy-init="false">
<constructor-arg name="configuration">
<bean class="model.transfer.TransferEnvironment">
<property name="configPath"><value>${transferConfigFile}</value></property>
<property name="datasourceName"><value>${transferDatasourceName}</value></property>
<property name="definitionPath"><value>${transferCodeGenPath}</value></property>
</bean>
</constructor-arg>
</bean>
<bean id="transfer" class="transfer.com.Transfer" factory-bean="transferFactory" factory-method="getTransfer"/>
Both Sean Corfield and Jamie Krug have written about configuring Railo for multiple web sites/contexts long ago. However, having just done this setup myself, I thought I'd take notes and share them. I created a Google Document called (surprisingly enough)
Railo Multi-web on Tomcat. I'm going to add to it as I have gather more information.
I recently wrote a modest shell script to make updating an installation of Open BlueDragon less cumbersome. I reported an issue with a nightly build, and I wanted to be able to switch between build versions reliably and quickly. The script, available as openbluedragon-nightly-updater.sh, will download and archive the nightly build and then deploy to web application server configured. My development environment is Apache Tomcat on Mac OS X 10.5/10.6. I create a directory in $HOME called Servers into which I download tarball distributions of Tomcat. I then create a symbolic link to the current version as ~/Servers/Tomcat. I use the ROOT context for simplicity. I keep old nightly builds in my ~/Downloads directory. Therefore, the configuration in the update script looks like this:
NIGHTLY="OpenBlueDragon-Nightly-`date +%Y-%m-%d`.zip"
SERVER="$HOME/Servers/Tomcat"
DEPLOY="$SERVER/webapps"
CONTEXT="ROOT"
URL="http://www.openbluedragon.org/download/nightly/openbd.war"
ARCHIVE="$HOME/Downloads/$NIGHTLY"
The script has two options: (-m) minimal or full update, and (-f) local file or remote download. With the minimal update, it just replaces the $DEPLOY/$CONTEXT/WEB-INF/lib/OpenBlueDragon.jar file. The full update replaces all of the Open BlueDragon JAR files, as well as the administration application and manual. It does not, however delete the existing Open BlueDragon configuration in $DEPLOY/$CONTEXT/WEB-INF/bluedragon. This is important to me because I don't want to lose the current settings for datasources, mail, debugging, and whatnot. I also want to keep all the symbolic links to my CFML applications in $DEPLOY/$CONTEXT in tact.
While working on a problem today, I wanted to verify that the issue wasn't introduced with last night's build. I just ran the following commands to revert to last week's build:
~/Servers/Tomcat/bin/catalina.sh stop
~/Workspace/admin/coldfusion/openbluedragon-nightly-updater.sh -m -f \
~/Downloads/OpenBlueDragon-Nightly-2010-06-12.zip
~/Servers/Tomcat/bin/catalina.sh start
In less time than it takes to get another Diet Coke, the installation of Open BlueDragon was running an arbitrary version. It turned out that the issue I was troubleshooting had nothing to do with Open BlueDragon -- it was an error in my MXUnit test case. To get running again on the latest version, I ran the script without any arguments.
Many people, right or wrong, deploy CFML applications to the web server by performing a checkout a source code control system, such as Subversion or Git. This has the effect of placing repository information in directories with the rest of the files; ${APPROOT}/**/.svn and ${APPROOT}/.git, for example. It's possible that this repository information (containing the code in plain-text and configuration files) will be exposed by the web server. That would be bad.
Whether the repository data is visible to an HTTP client depends on several factors: the OS, the web server and configuration, the directory and file permissions and OS- and filesystem-specific attributes. Probably the two most common environments are Windows with IIS and Linux with Apache. In the first case, IIS by default is configured to hide files and directories with the NTFS hidden attribute. Since both Subversion and Git create their repository directories with this flag enabled, the default scenario on Windows/IIS is safe. However, the same is not true for Linux/Apache (or Apache on Windows, for that matter).
Apache has always shipped, to the best of my knowledge, with a server-wide directive to prevent disclosing .htaccess and .htpasswd files:
<FilesMatch "^\.ht">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
It's not enough, I'm afraid, to remove the "ht" from the regex. To properly secure the SCCS artifacts, I like to use the trusty mod_rewrite module:
RewriteRule /(\.svn|\.git)/.* - [L,F]
And while I'm on the topic of using mod_rewrite to secure an application, here are some rules I use to prevent any similar shenanigans:
RewriteRule ^/app/(config|filters|listeners|plugins|properties|views)/.* - [L,F]
RewriteRule ^/(MachII|MachIIDashboard|coldspring|transfer|cfpayment)/.* - [L,F]
RewriteRule ^/(db|gen|model|taglib)/.* - [L,F]
Please feel free to comment. Oh wait, I'm lame and haven't enable comments on this blog. I suppose you could send them to @jlamoree instead.
I've often needed to programmatically determine a component's relative (possibly mapped) path, as well as the absolute path (with symbolic links resolved) to the CFC file. Getting the absolute path is a cinch using the path element returned by the getMetadata() function. However, the relative path must be gleaned from the name by replacing the dot-notation with directory separators. No, it's not rocket surgery.
<cffunction name="getPaths" returntype="struct" access="private" output="false">
<cfset var md = getMetaData(this)/>
<cfset var paths = structNew()/>
<cfset var name = listLast(md.name, ".")/>
<cfset paths.relative = "/" & listChangeDelims(reReplaceNoCase(md.name, name, ""), "/", ".")/>
<cfif not right(paths.relative, 1) eq "/">
<cfset paths.relative = paths.relative & "/"/>
</cfif>
<cfset paths.absolute = getDirectoryFromPath(md.path)/>
<cfset paths.temp = getTempDirectory()/>
<cfreturn paths/>
</cffunction>
Dumping out the structure returned by getPaths() yields the following:
The temp directory of the CFML engine is also returned, should you need that too.
CFML
|
Posted
6/13/10
@ 4:35 PM
by Joseph Lamoree
A moment ago I asked both of my followers on Twitter about character sets that eliminate visually similar symbols. Neither of them replied yet, but I thought I'd expand on the question. I was working on a token maker that generates random sets of characters for use as unique identifiers or serial numbers. In the following code block, see the UNIQUE character set -- it doesn't contain characters that could be recognized incorrectly when printed or in an e-mail (1,l,0,O,2,Z 5,S...).
<cfset variables.characters = structNew()/>
<cfset variables.characters.ALPHA_LOWER = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"/>
<cfset variables.characters.ALPHA_UPPER = ucase(variables.characters.ALPHA_LOWER)/>
<cfset variables.characters.ALPHA = variables.characters.ALPHA_LOWER & "," & variables.characters.ALPHA_UPPER/>
<cfset variables.characters.NUMERIC = "0,1,2,3,4,5,6,7,8,9"/>
<cfset variables.characters.ALPHANUMERIC = variables.characters.NUMERIC & "," & variables.characters.ALPHA/>
<cfset variables.characters.ALPHANUMERIC_LOWER = variables.characters.NUMERIC & "," & variables.characters.ALPHA_LOWER/>
<cfset variables.characters.ALPHANUMERIC_UPPER = variables.characters.NUMERIC & "," & variables.characters.ALPHA_UPPER/>
<cfset variables.characters.UNIQUE = "3,4,5,6,7,8,9,a,A,b,B,c,C,d,D,e,E,F,G,h,H,j,J,k,K,L,m,M,n,N,p,P,Q,r,R,t,T,u,U,v,V,w,W,x,X,y,Y"/>
<cfset variables.characters.UNIQUE_LOWER = _listDeleteEmptyItems(reReplace(variables.characters.UNIQUE, "[A-Z]", "", "all"))/>
<cfset variables.characters.UNIQUE_UPPER = _listDeleteEmptyItems(reReplace(variables.characters.UNIQUE, "[a-z]", "", "all"))/>
The resulting variable contains a variety of character sets that can be used to create any length of random string.
So, if you do know what such a unique character set is called, do let me know.
Oh, one other thing I should mention. I created the function _listDeleteEmptyItems() with a leading underscore in case one of the CFML engines implements a function of the same name. With my luck, it would happen next week.
CFML
|
Posted
6/5/10
@ 5:05 PM
by Joseph Lamoree
There is a blog post and comment thread on Ben Nadel's site about File Downloads Without Using CFContent that Sami Hoda alerted me to. Specifically, he pointed to comments about mod_xsendfile, an Apache module that serves files by scanning the output for a special HTTP header. This is really awesome in a CFML/JEE environment because the application server is freed up from waiting for a file to finish transfering.
I did some experimentation with mod_xsendfile v0.11 on Windows XP (yes, you read that correctly, on Windows) using Apache 2.2. It works beautifully. Here's an example of the web server configuration:
LoadModule xsendfile_module modules/mod_xsendfile-0.11.so
<VirtualHost *:80>
ServerName downloads
DocumentRoot "C:/workspace/Download"
XSendFile on
XSendFileIgnoreEtag on
XSendFileIgnoreLastModified on
XSendFilePath "C:/Documents and Settings/jlamoree/My Documents/Downloads"
<Directory "C:/workspace/Download">
AllowOverride all
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
The experimental CFML reads a directory of files and displays a list of links, one for a download using mod_xsendfile, and another to download a file using cfcontent. The meat of the code is pasted below, but you can download the entire experiment as mod_xsendfile-experiment.zip.
<cfheader name="Content-Disposition" value="attachment; filename=""#url.filename#""" />
<cfif url.method eq "mod_xsendfile">
<cfheader name="Content-Type" value="application/octet-stream" />
<cfheader name="X-Sendfile" value="#local.filename#" />
<cfelseif url.method eq "cfcontent">
<cfcontent file="#local.filename#" reset="yes" deletefile="no" type="application/octet-stream" />
</cfif>
<cfabort />
I created a quick and dirty JMeter test to compare both methods of sending a 8 Mb file. The first request using mod_xsendfile took 327 ms. The second request, only 99 ms. Using cfcontent the request times were 205 ms and 183 ms. So, take that with a grain of salt. In fact, use a whole salt shaker.
Apache,
CFML
|
Posted
9/30/09
@ 7:17 PM
by Joseph Lamoree
While working on a Customer and Address object relationship that is many-to-many, I wanted a way to send a single bean to the form that included all the fields of the customer, shipping address, and billing address. I modified BeanUtils to support merging two beans, while adding a prefix to property names. The reverse operation, pulling properties with a specified prefix out of a single bean, I've called an extraction.
Why go to all this trouble? Certainly this solves a very specific problem, however it works very well to mask the underlying aggregation or composition. Since the client will return all the properties in a single batch, it makes sense. Obviously, it's possible to split the client-server interaction up to separate the fields. Consider a blog form that allows dynamic creation and association with categories or tags using background AJAX.
At any rate, here's an example of preparing a bean to hand to the view layer that populates an HTML form:
var bu = getBeanUtils();
var customer = getCustomerService().getCustomer(customerId);
var shippingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
var billingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
bu.transfer(customer, customerBean);
if (customer.hasShippingAddress()) {
shippingAddress = customer.getShippingAddress();
bu.transfer(shippingAddress, shippingAddressBean);
bu.merge(customerBean, shippingAddressBean, "shippingAddress");
}
if (customer.hasBillingAddress()) {
billingAddress = customer.getBillingAddress();
bu.transfer(billingAddress, billingAddressBean);
bu.merge(customerBean, billingAddressBean, "billingAddress");
}
return customerBean;
When processing the user input, BeanUtils pulls the addresses out:
var bu = getBeanUtils();
var customerBean = _event.getArg("formBean");
var shippingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
var billingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
bu.extract(customerBean, shippingAddressBean, "shippingAddress");
bu.extract(customerBean, billingAddressBean, "billingAddress");
Following the property extraction, the beans can all be passed to the appropriate validator. Assuming all is well, the properties can be pushed back to the object instances that are persisted:
var bu = getBeanUtils();
var cs = getCustomerService();
var customer = cs.getCustomer(customerId);
var shippingAddress = "null";
var billingAddress = "null";
bu.compose(customer, customerBean, "name,email,phone");
shippingAddress = customer.getShippingAddress();
bu.compose(shippingAddress, shippingAddressBean);
shippingAddress.save();
billingAddress = customer.getBillingAddress();
bu.compose(billingAddress, billingAddressBean);
billingAddress.save();
customer.save();
Hopefully that makes sense. I pulled out a lot of superfluous code, and it's not clear where each chunk is executing. My objective was to show how the bean utility assists in the data round trip. The current version of the bean utility is posted so you can see the implementation of the transfer, compose, merge, and extract methods: BeanUtils.cfc
CFML
|
Posted
9/1/09
@ 5:18 AM
by Joseph Lamoree
I use a lot of beans in my CFML applications. When I use the term beans, I'm referring to a particular naming convention on components (CFCs), much like the JavaBean concept. My beans have getters/setters that all return/receive string values. This makes it possible for the MVC controller to populate a bean the user input, without any exceptions thrown due to incorrect data type or format. Validation of the bean properties happens later, and the results of the validation are stored inside the bean. When the bean is then handed off to a MVC view, the validation messages can be displayed alongside the field label. Consider the following model:
In most situations, the system instantiates a beans.Bean and calls the appropriate getter and setter methods. However, this particular component doesn't actually have any getters or setters -- it has an onMissingMethod() handler that mimics their behavior. This generic bean can stand in place of any real bean. It creates its own validation.ValidationMessages instance and uses a datatype.Collection to keep track of its properties. Because it always knows what properties it contains, other components can easily move values in and out of the bean. A beans.BeanUtils component exists to simplify the common task of loading and unloading beans:
None of the information I've covered so far is particularly new or innovative. However, I recently encountered a special situation using the beans.Bean that prompted me to do some experimenting. While working on a search interface (think: Google advanced search options), I wanted to put all the search information into a bean to pass off to the search service. However, I also wanted a method (unrelated to getters and setters) that returns a properly URL-encoded string for use in the view. It seemed trivial to extend the bean and add the getSearchArgs() method. Doing so, unfortunately, breaks the onMissingMethod() mechanism for supporting arbitrary getters and setters. The solution is to implement the getters and setters using the extended component's managed structure.
<cfcomponent extends="beans.Bean">
<cffunction name="init" returntype="SearchBean">
<cfset super.init("query,...") />
<cfreturn this />
</cffunction>
<cffunction name="getSearchArgs" returntype="string">
<!-- Return URL-encoded arguments -->
...
</cffunction>
<cffunction name="getQuery" returntype="string">
<cfreturn getProperty("query") />
</cffunction>
<cffunction name="setQuery" returntype="void">
<cfargument name="query" type="string" required="true" />
<cfset setProperty("query", arguments.query) />
</cffunction>
...
</cfcomponent>
The getProperty() and setProperty() methods come from the extended component (super class), and the init method (constructor) sets up the appropriate property names. The onMissingMethod() mechanism for supporting dynamic property getter/setters is not used, or needed. Simple, right?
CFML
|
Posted
8/22/09
@ 9:00 PM
by Joseph Lamoree
As I've been integrating cfpayment with my commerce application, I've found it handy to have a diagram showing the important bits of the system. This diagram (click the thumbnail for a legible version) shows the bean-like methods in brown, and the methods that would be used by an application using the software in red. The methods in plain black are interesting, but should not have the focus. I should note that the diagram is not a faithful UML Class Diagram of the software, obviously. Some private properties and methods are left out for (an attempt at) clarity. There is also no such abstract class as cfpayment.model.Account; the credit card, EFT, and token only inherit from it by convention. At any rate, I hope it's helpful.
CFML,
PayPal
|
Posted
6/18/09
@ 4:32 PM
by Joseph Lamoree
While working on the PayPal Website Payments Pro gateway for the cfpayment project, I had to fiddle with the typical unit testing procedure. The PayPal Express Checkout process involves a user interacting with the PayPal website between the start and completion of a payment transaction. The documentation at PayPal has a pretty good overview of the flow, from the two supported entry points.
If you look at the source from Subversion, you'll see that the NVPGatewayTest.testBeginExpressCheckout() method first sends the SetExpressCheckout command to PayPal, then redirects the user to the PayPal site. Once the user authenticates at PayPal and confirms some profile information, they are returned to the unit test at NVPGatewayTest.testCompleteExpressCheckout(), bring along two URL parameters: token and payerId. The test then performs two requests of PayPal: GetExpressCheckoutDetails and DoExpressCheckoutPayment.
This seems to be working pretty well. I recorded a screencast of the process showing the test from start to finish: PayPal Gateway Express Checkout Testing.
CFML,
PayPal
|
Posted
6/16/09
@ 9:35 AM
by Joseph Lamoree
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.
This is a quick post about a project I've been working on, an application to manage scheduled jobs on the server. The system maintains a queue of running jobs, preventing multiple resource intensive jobs from overloading the server. I created a screencast demo and uploaded it to Vimeo. Unfortunately, there is a problem with one of the edit points that causes it to stop playing halfway through. I'll have to fix that when I get back from a short visit to San Francisco. The entire movie can be seen if you download the original file, linked to the bottom of the video details page.
One of the cool artifacts of the work on the job scheduler was a generic bean that uses the onMissingMethod() facility in ColdFusion. The component pretends to have all the getter and setting methods one would expect, given a known set of properties. This reduces the number is silly beans with a whole mess of getters and setters that don't do anything useful.
I've also been working quite a bit with jQuery and jQuery UI -- especially the dialog widget. It really takes the effort out of doing some complicated bits of user interface work.
CFML,
jQuery
|
Posted
12/29/08
@ 2:36 AM
by Joseph Lamoree
Today I worked on an application that I wrote several years ago to take a PDF with blank form fields, and merge in values from a database. I understand that ColdFusion 8 now supports several PDF functions now, but I wanted to rewrite the existing code from using native Java wrapped in a CFX, into plain CFML including Java classes from the iText library. I thought this would simplify hosting because the iText JAR comes with a stock installation of CF8. So, I went about writing a CFC to encapsulate the form field merge. Unfortunately, I later discovered that the version of iText included with CF8 is somewhat old. By using the current iText API documentation, I avoided using deprecated methods. I could have used the JavaLoader to add the newer version to the classpath. But instead, I used the deprecated syntax. At some point in the future, however, this code will work out of the box! :)
fields = [
["CertificateTitle", "Super Awesome Guy"],
["FirstName", "Joseph"],
["LastName", "Lamoree"],
["DateIssued", dateFormat(now(), "dddd, dd mmm yyyy")]
];
pdfWriter = createObject("java", "com.lowagie.text.pdf.PdfWriter");
perms = bitOr(0, pdfWriter.ALLOW_PRINTING);
encType = pdfWriter.STANDARD_ENCRYPTION_128;
pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init(pdfIn);
fos = createObject("java", "java.io.FileOutputStream").init(pdfOut);
pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init(pdfReader, fos);
pdfStamper.setEncryption(userPasswd.getBytes("UTF8"), ownerPasswd.getBytes("UTF8"), encType, perms);
pdfStamper.setFormFlattening(true);
pdfFields = pdfStamper.getAcroFields();
for (i = 1; i lte arrayLen(fields); i = i + 1) {
pdfFields.setField(fields[i][1], fields[i][2]);
}
pdfStamper.close();
I haven't yet tried this with Open BlueDragon, but I believe it will work if the correct version of iText is added to the classpath.
CFML
|
Posted
11/21/08
@ 2:29 AM
by Joseph Lamoree