Monday, December 22, 2008

Hibernate/JPA objects in an HTTP session

Last week I ran into an unexpected problem.  

My application has a User object, which contains various fields.  User also has a JPA-managed One-To-Many relationship with something called Store.  This relationship had been working fine (and in fact through this story never stopped working).

For the first time, last week, I added a JPA-managed relationship between User and an entity class called QuickListEntry.  QuickListEntry in turn has a relationship with Product, which in turn has plenty of other relationships with other entities.

We're using Struts 2, which is only marginally relevant here.

In Action A, we load up the User object from the SecurityContext, do some stuff with him, and then stuff him into the Session for access later.  In Action B, we pull him out of the session and read his data, including his QuickListEntries.  Well what do you know -- I'm getting a Hibernate LazyInitializationException.  This has never happened before.  It never happed with the User's Stores.  What's going on?

Well here's what's going on.  When the User is stuffed into the Session, he is disconnected from the JPA EntityManager.  When he is pulled out of the Session, his extended relationships (User to QuickListEntry to Product) can't be followed, because he is disconnected from the EntityManager.  

So the most correct solution in this case is not to stuff the User object into the Session at all.  Either stuff the userid in there (which is probably harmless) and then use the userid to re-load the User from the DB in every Action's prepare() method; or pull the User from the SecurityContext on every page load (again, I'd put this in the prepare() method of every Action class).

Wednesday, December 17, 2008

Eclipse/Tomcat note to self

Tomcat stops reading config data.  Error with docBase, directory does not exist or is not readable.  Here's what seemed to help:

Remove Tomcat as a server.  Close and restart Eclipse.  Add Tomcat as a server, with the correct project.  Re-add /Catalina/localhost/my.xml.  Make sure the entry is in my.xml and not in server.xml.

Friday, September 26, 2008

Dear Internet,

I swear to god I'm going to buy http://www.localhost.com/ just so I can put a bigass message up on port 8080 saying REREAD YOUR URL, DUMBASS.

Sincerely yours,
Dumbass

Tuesday, May 20, 2008

I'll forget this if I don't save it

If you use a framework like Struts, you will find that your internal file paths don't match your external ones. So you may have /foo/bar/myThingy.action (externally) which is mapped to /WEB-INF/secure/jsps/myThingy.jsp internally.

If you use SiteMesh, the decorator patterns you put in decorators.xml should be mappings to your internal directory structure, not your external structure.

Friday, March 21, 2008

Hibernate Session vs. EntityManager

The answer to the question below is depressingly easy. I was overthinking the problem. Session is Hibernate's custom implementation; EntityManager is the standard JPA creature. If you want to stick to the standards (in this case, I do, although the odds of our shifting to a different JPA provider are very slender), stick to EntityManager. The big thing I'm missing out on so far is access to Hibernate's various CascadeTypes; the JPA default CascadeTypes are rather limited.

The way I'm working lately

This is actually almost fun.

Figure out some functionality that needs to exist. Within a unit test, write the top-level code for it regardless of whether the supporting methods exist or not.

List people = populationDao.getPeople();

...even if I haven't written populationDao.getPeople() yet.

Eclipse will complain that I have written references to methods that don't exist. Hit control-1 and choose the answer that is "create method 'getPeople' in interface 'populationDao'." Then, of course, PopulationDaoImpl will have errors, because I have a method in populationDao that I haven't implemented. Open up PopulationDaoImpl, control-1, 'add unimplemented methods,' and write the implementation for getPeople().

This way I know that getPeople() has at least some test coverage right from the start.

Monday, March 17, 2008

Open Hibernate Question

Open question: I see in the Hibernate documentation that Hibernate's EntityManager is "similar to" Hibernate's Session interface. But it isn't clear when I should be using one versus the other. I'm using the EntityManager now and everything's going fine. But I want to make sure I'm doing this The Right Way so that I don't have to rip it apart and rewrite it in three months when I better understand the business problems I'm fighting. Any readers have any insight?

Friday, March 7, 2008

Acegi: It Makes Sense Now

I was trying to write a massive security infrastructure beast. I don't need to do that.

The first thing I've done since the last post is to write my own 'SecurityService' class that implements the UserDetailsService interface. UserDetailsService has just one method -- UserDetails loadUserByUsername(String). So it takes a username and loads that whole user object.

Well this confused me. Where the hell, I thought, does the password get dealt with here?

The answer is that the password is handled by the Authentication Provider (in this example, an org.acegisecurity.providers.dao.DaoAuthenticationProvider). So this particular authentication provider accepts a username and password, uses the username to pull the UserDetails data out of the UserDetailsService, and then (in memory) (presumably) compares the submitted password to the password returned from the UserDetails object's getPassword() method.

I'm not thrilled by this implementation, because it assumes that passwords will be stored in plaintext! They shouldn't be.

Anyway, I verified all of this by building out a deliberately stupid implementation of the UserDetailsService and wiring it into my application -- specifically I wrote one that always returns the same UserDetails object, with a known and bogus username and password. I tested this and it worked, so it all makes more sense to me now. Next step is to rewrite my bogus UserDetailsService to talk to my actual application code and return real user objects.

Down the road a bit I'm probably going to have to look at the other authentication providers, so that I can handle encrypted passwords. But in the interim this is good progress.

Stupid side note: for some reason, whenever I log in, users are getting redirected to an image file the first time.

Back with Acegi

After a ColdFusion-induced layoff I'm back hacking at Acegi Security.

At this point I have a simple (very simple) Struts 2 application in place and I'm applying Acegi Security to it.

What I've done so far is taken the Acegi Petclinic Tutorial and applied it to my application. I used just about the simplest possible security implementation there -- I changed some names in their default users.properties file and implemented properties-file-driven security.

So far it does work, though -- the login page is exposed to all comers, while the more secure pages deeper in the app are hidden. Routing to the "landing" page upon login works correctly as well.

Next I want to change authentication so that it looks at my users table instead of looking at the properties file. I'll continue to assume just two levels of authorization (anonymous and logged-in user). The Acegi Security reference documentation suggests that I'm going to want to implement the UserDetailsService interface... but of course UserDetailsService doesn't accept a password argument. So I'm going to have to figure out what to do to really authenticate the user, as opposed to just looking them up.

Wednesday, February 27, 2008

Acegi and Struts 2

The easy part is done. Acegi's jar files are in place and they reliably block access to the "protected" part of the application, and redirect users to the login page. Unfortunately I haven't yet figured out how to connect my login action to Acegi authentication.

I believe I will have to have my login action point at my own custom implementation of UserDetailsService, which will in turn have a link to my already-existing UserDao. It will have to put the UserDetails in question into the security context... I think I got it. Now just to, y'know, write the code.
First real step was to tell web.xml that I was going to have multiple applicationContext files (applicationContext.xml and securityContext.xml) so that I don't poop all over my application context with security stuff. Yeesh, there's a lot of configuration there.

Acegi Security

Today I am checking out Acegi Security for our app. I'm slapping it against the application now, early on, rather than trying to shoehorn it in later, after everything works in an insecure fashion.

I would like to know how the hell it's supposed to be pronounced.

It certainly looks cool. Pretty pluggable. Should fit reasonably well within our current application design; I'm just going to write a UserDetailsService implementation that uses our existing Hibernate configuration.

More as I actually do it.

Friday, February 22, 2008

Incredibly Basic Design Problem

I'm working on a web application. All noteworthy data in here has been scrubbed appropriately.

This application has users. There are both external and internal users. Each user may have access to a certain subset of the application's functionality, although generally speaking internal users' functionality access is different from external users' functionality access. For instance, we might have an external user who is a customer, and another external user who is a supplier. We might have an internal user who is a sales rep, and another who provides support to the logistics team.

The question I immediately ask myself is this: Am I looking at an inheritance hierarchy, or am I looking at an opportunity for composition?

In other words, do I have:

public abstract class User {...}
public abstract class InternalUser extends User {...}
public class LogisticsUser extends InternalUser {...}

?

Or should I rely instead on composition?

public class User {
Set availableRoles = new HashSet();
}
User fred = new User();
fred.addRole(LOGISTICS);

I might consider answering this question with a look at the database tables that I'm relying on, but (1) I'm more concerned with the conceptual problem here than the implementation details and (2) in this particular case the tables are no help at all anyway.

So here is the core question: Can a user ever fill multiple roles? Java doesn't support multiple concrete inheritance, so a given user either IS a LogisticsUser or he IS a CafeteriaUser or what-have-you. If users can fill multiple roles, then I should be assigning them roles rather than slotting them in a particular concrete class.

And in this particular case, yes, some users do need to fill multiple roles (for instance, we might have a logistics support person who also has administrative authority over some of the website). So I'll be making a simple User hierarchy with a more complex collection of Roles available.

Wednesday, February 20, 2008

Today I Am A Real Boy

Today a client coworker gave me some Java code of his to work on and finish up, because we're going to need it for forward development.

So I looked at what stuff could be refactored, wrote a couple of unit tests, refactored some bits out, ran the tests, and...

Well the code logged a few major exceptions and was unusable. But the test passed.

We were conversing about this code in general terms a few minutes later, and I pointed at the screen and hollered "I want that test to fail! Help me make it fail!"

Saturday, February 16, 2008

Dippy

To go with "ATM machine" we now have "plain POJO," per the Hibernate docs.

Thursday, February 14, 2008

Glad to see this works

I wasn't sure this would work. One quick unit test verifies that it does. Very good to know:

Query query = getEntityManager().createQuery("from User order by ?");
query.setParameter(1, "lastName");


I don't know why it wouldn't work, really, but I had only used replaceable parameters in the WHERE clause of queries before.

Thought I was GONE didn't you

I've played a bit over the last few days with a simple Struts 2 CRUD app, mostly following the examples given here.

Right now I have a data table that lists the users of the app and provides an exciting 'edit' link for each one. The edit link takes you to a pretty standard 'edit' type page for the given user.

Here's what I'd like to do next (several things, all fairly independent of the others).
  • Turn the data table into a submit-driven paging sorting data grid.
  • Turn the data table into an AJAX-driven paging sorting data grid.
  • Implement a smart(ish) filter for the data grid.
  • Change the two-page layout (table, data entry grid) into a one-page AJAX-driven layout (click on a user to open the edit grid for that user next to the table).
I think I'm going to start with the first one; I'm going to have to do it eventually anyway.

Thursday, February 7, 2008

Another thing that drives me berserk about ColdFusion and CFEclipse

It is damned near impossible without using the "find" feature and extraordinarily tedious tag-counting (and tag-collapsing) to figure out whether or not a given variable is in scope at a particular part of the page. I can't control-click to go to the spot where the variable is declared; I get no indication as to whether the variable exists at a given spot; I don't even get a warning telling me that the variable may not have been declared conditionally at a given spot.

And then I get exceptions thrown when my predecessor's code references a variable that was conditionally declared earlier and the declaration got skipped this time. Madness.

Wednesday, February 6, 2008

Wrong Ways, first in a series

After a few days spent doing ColdFusion support I'm doing some more Struts/Hibernate development today.

An observation: There's a right way to set up developers' environments, and there are dozens of wrong ways. I feel like I have a pretty good grasp of some of the wrong ways, and now I'd like to share them with you. Critically important note: These observations do not pertain to my current client or my employer.

Wrong Way #1: Stint on hardware. A consultant costs, let's say, $100/hr. A fully-loaded full-time employee might be $70/hr. According to newegg, 4 GB of RAM from a no-name vendor costs $100. If you want to go with a really reliable, trusted vendor, you might spend $200. If you are furnishing a consultant with hardware, a really great Wrong Way to do things would be to save $200 on memory. Give him a machine with 1 GB or less. Sure, it takes 15 minutes for his machine to boot every day, and lockups, crashes, and slow response time cost at least an hour a day. But you saved the cost of that memory!

(but seriously) This is especially and offensively common when different cost centers or different managers are paying for developers' time versus developers' hardware. The MIS guys saved $150 off their budget but cost the company an additional $6000 in developer productivity over three months. Woo! Way to contribute! Go team!

Wrong Way #2: Lock down that Internet. Developers can't be trusted with free reign to go to any website they want. Network administrators know what kind of tools, open-source libraries, and reference materials a developer could want. Let the network guys and their management make that determination. Blogs? Forget it! Those things are just time sinks, they're like reading the newspaper at work. And there's no reason to differentiate between a customer service rep and a software developer in terms of Internet access. If you can't trust a CSR with access to blogspot.com, there's no way a developer needs it. Lock 'em all down.

Wrong Way #3: Shared environments. Software licensing is expensive. Rather than spring for a development license for each developer, why not set up a shared dev server, with a shared database and everything? Don't give developers local development environments; just make their desktops into glorified terminals. Also, this way your developers won't have to worry about solving problems when they're away from an Internet connection or when the VPN is down. That will encourage them to spend more time in the office.

Oh, I bet there are plenty more of these I could do...

Monday, February 4, 2008

Excellent series on developers' database management and putting your db in version control over at http://odetocode.com/:

Part 1
Part 2
Part 3
Part 4
Part 5

Friday, February 1, 2008

Well that was funny

I only had to change the URLs in hibernate.cfg.xml and in applicationContext.xml. That's good and easy.

Things didn't work for a sec, because in the 'real' database that I'm now using, Users aren't stored in a 'user' table, they are stored in, let's say, a table called 'bogus_user'. With Hibernate 2 I would have known to go into User.hbm.xml and modify the table name reference, but the current quickstart project uses Hibernate 3 annotations. I checked the docs quickly, suspecting I need to change the @Entity annotation on the User class. The docs pointed me at the free Chapter 2 sample of the Manning Java Persistence with Hibernate book. It showed that I needed to add @Table(name="bogus_user") to the User class declaration. I did that, and redeployed.

This time I ran into a hilarious error. The bogus_user table has thousands of entries, and my simpleminded script was trying to display all of them at once. So -- next step -- limit the number of entries returned. That will be after lunch, as my wife and I are going to meet for lunch for the first time in ages.

I'll probably be buying that Manning book this weekend.

Changing a data source

It's very quiet here this morning. Snow and ice last night, freezing rain this morning. A lot of people are working from home today.

I'm going to try something wacky this morning. Having successfully finished the 'quickstart' demo provided by the fine Struts 2 folks, I've already changed the names of my packages and classes to match the activities I actually plan to undertake (quickstart.service.PersonServiceImpl changes to com.clientname.project.UserDaoImpl, for instance).

Now I'm going to do something more entertaining. I'm going to try to dump a copy of our dev MySQL database and build it locally, and then point this toy app at it. I may not have the right privs for that. So if that doesn't work, I'm going to point my app at our dev database and see what happens. I'm pretty sure I know what will have to change.

Thursday, January 31, 2008

Onward

The rest of the quickstart demo went pretty smoothly. Not surprising, considering that it was largely a matter of typing (or copy and pasting) sample code into real files.

But now the fun begins: problems. The truth is that you don't understand how a technology or combination of technologies works until you've had to fix them when they're supposed to work.

So, first problem: Exception stack trace in my Tomcat log. I am not going to drop the entire stack trace into the blog at this point, but let me describe how I'm handling this. First, I skimmed the stack trace looking for references to code that I know to be mine. In this case there are no "quickstart." files anywhere in the stack trace, so I know it's not my Java code. Now, here are the first few lines of the stack trace:

SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/applicationContext.xml]; nested exception is java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.ParserContext.registerBeanComponent(Lorg/springframework/beans/factory/parsing/BeanComponentDefinition;)V
Caused by: java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.ParserContext.registerBeanComponent(Lorg/springframework/beans/factory/parsing/BeanComponentDefinition;)V
at org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser$AopAutoProxyConfigurer.configureAutoProxyCreator(AnnotationDrivenBeanDefinitionParser.java:130)
at org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.parse(AnnotationDrivenBeanDefinitionParser.java:79)

The most obvious problem appears to be an error parsing applicationContext.xml. Probably I have a typo in there somewhere. However, I can't find one after glancing over the file for five minutes and that is almost certainly enough time for now. So I'm going to do something annoying. I typed this one in by hand and probably fat-fingered something. So I'm going to copy-and-paste the file contents from the demo and see if that makes the problem go away. If it doesn't then I'll have to use my brain and nobody wants that.

That doesn't make the problem go away! Crap. Time to do some Google searches on the exception messages.

And Matt Raible comes through again. Dude is rapidly becoming my hero. A thread on the AppFuse mailing list suggests that there might be collisions between various Spring jars. I'm reorganizing my build path, let's see how this works.

...nope. OK, time to review my build path, make sure I have everything I need. Yes, this would have been a good time to have used Maven, I know.

Hey, it turns out that I ignored a bunch of Hibernate jars earlier -- specifically the Hibernate Annotations and Hibernate Entity Manager jars. I'll grab them now. And that doesn't solve the problem either. Same error.

Clean, refresh, delete the deployment, rebuild, redeploy... and it's a whole new suite of errors! But these are more comprehensible. They can go in another post.

Continuing the Struts 2 / Spring / Hibernate Toy

If you are just joining us, I am working my way through this.

I'm working on the provided PersonServiceImpl. Eclipse can't find the @Transactional annotation, which is apparently org.springframework.transaction.annotation.Transactional. Thought I had all the Spring jars -- guess not. http://www.docjar.com/ -- which should be in every developer's bookmark list -- suggests that it is found in spring-dao.jar, which I don't have. It looks like I don't have it because it doesn't exist.

I have a hunch here and it turned out to be right. Rather than relying on Eclipse's Spring enablement guck, I went right to the site to download the latest Spring distribution, and grabbed it. Unpacked it, grabbed spring.jar, dropped it into my project. There, right there, is the org.springframework.transaction.annotation package. I had assumed -- wrongly -- that Eclipse's Spring capability enablement would include spring.jar. Sigh.

Adding jars to the project

It just occurred to me that if you have this feed copied to your livejournal friends list you're going to hate me, because there's no way that I know of to hide crap behind a cut. So, er, sorry. But this is mostly for my future reference, and so being spammy is good from my perspective. And maybe if somebody else needs a hand in the future they might find these posts and get something good out of them.

So, I made a 'quickstart' project in MyEclipse, and added it to the deployments listed under my Tomcat 6 server in the 'servers' tab.

Now it's time to download and add 24 jar files to my project. Although MyEclipse 6 doesn't support Struts 2 yet, I'm going to try and handle the Hibernate jars with MyEclipse's "add Hibernate capabilities" feature, let's see how this goes. I need the Hibernate Core, Hibernate Annotations, and Hibernate Entity Manager features, 16 jars in all.

This guide says I need antlr.jar, asm.jar, asm-attrs.jar, cglib.jar, dom4j.jar, jdbc2_0-stdext.jar, ehcache.jar, hibernate3.jar, xml-apis.jar, and commons-collections.jar from the Hibernate Core. MyEclipse provides all of these in the Hibernate 3.1 Core Libraries module; it also adds commons-logging, jaas, jaxen beta (I have no idea what jaxen is), jta, log4j, and xerces.

The quickstart guide says that Hibernate Annotations should provide ejb3-persistence.jar, jta.jar, and hibernate-annotations.jar. MyEclipse's Hibernate Core gives jta.jar. The other two aren't provided by MyEclipse, so I will need to grab them.

The quickstart guide says that "Hibernate Entity Manager" should provide
hibernate-entitymanager.jar, javassist.jar, and jboss-archive-browsing.jar. MyEclipse's library plugin does not provide any of these, so I'll have to go grab them as well. No problem.

For now I'll add the Hibernate 3.1 Core library to my project, and go out and get the other Hibernate libraries that I need. I'll take the defaults for the Hibernate config file (putting it in src/hibernate.cfg.xml) because I don't have strong opinions about where it belongs.

Hm. After another page of the wizard, the wizard is now asking me about creating Hibernate SessionFactory classes and stuff. I'd better check the quickstart to see if it's got suggestions as to how I should set that junk up.

After reviewing the quickstart, I'm going to hold off on having a wizard-created SessionFactory. I can always create one later if I need it.

OK, that's done. Now I notice the quickstart has references to spring.jar. I could probably just enable the Spring capabilities within MyEclipse. MyEclipse's Spring 2.0 Core libraries includes a lot of stuff that I may not need. If I were working with a team I would probably hold off for the time being, but since it's just me and I'm confident that I know what's in all these jars, I'll add them all wizardishly too.

By default MyEclipse wants Spring's applicationContext.xml in "src". I know I don't want it there; the quickstart wants it in WebRoot/WEB-INF/ and I am inclined to agree, so off it goes. Also, I'm not going to create a Spring LocalSessionFactory, because the quickstart appears to be doing things differently.
Using the instructions mentioned in the previous post I'm going through and creating myself a toy configuration for this app.

I had thought previously that I could just create a new project in MyEclipse and use MyEclipse's "add capabilities" features to get the stuff I want. But MyEclipse doesn't, as of yet, support Struts 2. So I'm going to stick with doing things the old fashioned way.

First step -- grab Tomcat and install it -- as expected, no problems there. Configure MyEclipse to recognize this Tomcat installation by going to Preferences -- MyEclipse -- Servers -- Tomcat -- Tomcat 6, click "Enable," click the "Browse" button next to "Tomcat home directory," and find the root of my Tomcat installation. The other arguments are automagically populated from there, hit Apply, hit OK. Go down to the 'Servers' tab in MyEclipse, select this Tomcat 6 server, click the exciting green start arrow, Tomcat starts up just fine.

Now start up MySQL -- hm, it won't start -- check properties for the MySQL service, find that it points to a directory that doesn't exist.

I should point out that this is an inherited machine that wasn't wiped before it was handed over. Sigh.

So, grab MySQL, install it -- now everything seems OK, except that I have an old, nonfunctional MySQL service in my service list in addition to the new functional one. I'll live.

Make a note in my lab notebook as to the usernames and passwords for Tomcat and MySQL. Yes, it might be slightly insecure but my memory is even less secure.

These quickstart instructions next provide a handy little SQL script to create a 'Person' table. Open up the MySQL Query Browser, open a new script tab, copy, paste, execute -- no dice. Syntax error. Huh. The syntax looks right to me:

CREATE TABLE 'quickstart'.'Person' (
'id' INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
'firstName' VARCHAR(45) NOT NULL,
'lastName' VARCHAR(45) NOT NULL,
PRIMARY KEY('id')
)
ENGINE = InnoDB;

But the MySQL query browser is complaining that I've got a syntax error near the second line. OK, let's check out the online refs, what is the correct syntax for CREATE TABLE in MySQL 5.0? I should mention that MySQL's online reference is just great, at least for this basic stuff. Detailed syntax, lots of examples.

Poke around for ten minutes to look for answers to this, can't find any. Ten minutes seems like a good enough time-boxing, so I'm just going to use the MySQL query browser's GUI to replicate the same code that is here.

Just for reference, here is the autogenerated SQL that the query browser came up with to create this table after I did the happy clicky:

DROP TABLE IF EXISTS `quickstart`.`person`;
CREATE TABLE `quickstart`.`person` (
`id` int(10) unsigned NOT NULL auto_increment,
`firstName` varchar(45) NOT NULL,
`lastName` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Grand.

Wednesday, January 30, 2008

Good intro -- Struts 2 + Hibernate

Another good link, something to look at from work: Struts + Spring + JPA (Hibernate).

Monday, January 28, 2008

Struts 2 Links

Here is a free online PDF intro to Struts 2. No idea how good it is... yet.

A decent description of the post/redirect/get pattern for web app request management.

No, Seriously

I swear. Struts 2. It's just that today was spent fighting fires and -- God help me -- documenting the 'as-is' state of the app.

Tomorrow: Documenting what I'm doing with Struts 2 and Hibernate.

Saturday, January 26, 2008

Exciting News

As you might have guessed, my productivity isn't quite as high with ColdFusion as it is in a Java web app environment. It took me a couple of days this last week to develop an embarrassingly small number of components and unit tests in CF.

I haven't been whining out loud about ColdFusion quite as much as I've been complaining on this blog, but several related activities, combined with some whining, and my lack of productivity, have led us to general agreement that we're going to be remediating this application's problems using Struts 2, Spring, and probably Hibernate. I'm very happy about this, as you might imagine.

I haven't used Struts 2 on a real app yet, although I have used Struts 1. I'll probably be using this blog in part as a Struts 2 linkdump, and partly as a description / walkthrough as I learn. I'm really looking forward to it.

My first link to dump, courtesy of client coworker: how to set up a Struts 2 project in MyEclipse 6.

Friday, January 25, 2008

Continued CF Whining

At this point I'm simply going to have to assume that all CF data is string data, and deliberately coerce it into behaving like numeric data as needed:

I have a query, and the field "storeId" is defined in the DB as being an integer. I have a method, setStoreId(), which has an argument, defined as being an integer. If I do obj.setStoreId(query.storeId), an expression exception is thrown, because query.storeId is not an integer.

Really?

And if I bludgeon that into submission, then later when I do getStoreId(), which is defined as returning an integer, I get another expression exception, because the value it's returning isn't an integer. Even though its value is the number 110.

Thursday, January 24, 2008

ColdFusion needs unit tests freakin' everywhere

By trade I am a Java developer -- I've done plenty of Java EE work as well as pure Java SE stuff. I'm a consultant in Pittsburgh. I won't mention my employer by name, or name any clients that aren't dead, but I've worked at a fair number of clients in the greater Pittsburgh area.

Back in 2000, at a long-dead website, I did about six months of really painful ColdFusion development. In the fall of this year I was offered the opportunity to do more CF consulting, at a local company, and I took the posting.

So, I've been doing CF again for a few months now. I miss so much stuff from Java development that I won't even list it all, although my previous CF post hits some of them.

Here's a new one: Today I lost half a day to the difference between <cfloop query="foo"> and <cfloop query="#foo#">. The error message I received informed me that I was trying to use a complex object where I needed a simple value. I didn't get a stack trace. I didn't get a line number.

And of course I didn't have a debugger. The only way to find out what's going on in ColdFusion is to layer your code with <cfdump> tags and other output-to-the-page wickedness.

So, anyway, what I'm finding is that the more CF development I do, the more I have to write unit tests for even ordinary model objects -- I need to make sure that freakin' getters and setters are behaving intelligently when faced with even slightly unusual circumstances.

Case in point: I have a method, setShippingCharge(), on a Customer CFC. When I designed Customer I knew that shippingCharge was numeric, and so I wrote it like this:

<cffunction name="setShipping" output="false" returntype="void">
 <cfargument name="shippingCharge" type="Numeric">
 <cfset shippingcharge = "arguments.shippingCharge"/>
 <cfreturn/>
</cffunction>

I didn't write any unit tests for this, because, y'know, it's a setter, and I'm a Java guy.

I ran some code using this method today, populating a Customer object from a database query. Well the
shipping_charge field in the database was nullable, and in this case, it was actually null. ColdFusion complained about this, because of a type mismatch. Null -- or, thanks to the retardation of ColdFusion, the empty string "" -- wasn't numeric!

So instead I had to rewrite the function like this:

<cffunction name="setShipping" output="false" returntype="void">
 <cfargument name="shippingCharge">
 <cfif>
  <cfset shippingcharge = "0"/>
  <cfelseif>
   <cfset shippingcharge = arguments.shippingCharge/>
  <cfelse>
   <cfthrow type="foo.IllegalArgumentException" message="this is supposed to be numeric">
 </cfif>
 <cfreturn/>
</cffunction>



I'm not sure I understand, at this point, why ColdFusion functions even allow you to declare their expected data type. If you've got to go through gyrations like the above for every method, what good is the 'type' attribute on <cfargument>?

I was going to turn this into a rant on dynamic languages but I guess I'll save that, except to say that gosh, a compiler could sure prevent a lot of this pain.

Wednesday, January 23, 2008

JVM web app frameworks

Found myself today in a position to discuss criteria for accepting or rejecting web app frameworks to a client with specific needs. This presentation by Matt Raible is incredibly helpful in providing at least a base to talk from.

I cleverly note this so that I have a link to it next time I need it.

Tuesday, January 22, 2008

ColdFusion Pain

A brief note regarding ColdFusion, which I'm currently doing development on (augh). CF is pretty loosely typed. Unit test (using CFUnit, god help me) certainly need to explicitly do testing on the types that methods return, as well as methods' abilities to handle arguments of varying types.

As a side note, I use CFEclipse, which is the ColdFusion plugin for Eclipse. It really isn't very good. Its ability to do tag completion is woeful; ctrl-space code completion isn't very complete -- and often the "timed" code-completion popup occurs at precisely the wrong moment (namely, when I'm pressing 'return' for some other reason).

Worst, in my opinion, is CFEclipse's inability to find things. When doing Java development in Eclipse, if I highlight a variable name, I expect to see every instance of that variable highlighted in the current file. CFEclipse doesn't do that with ColdFusion files.

I'm also addicted, in Java, to using the right-click menu to find declarations of variables or to find references to methods and/or files. Not available in CFEclipse at all.

I accept that some of this stuff is hard to do for a loosely typed "language" like ColdFusion. But this is playing hell with my productivity. I also accept that CFEclipse is an open-source project and that I could contribute to it if I really wanted to. I'm considering it.

Sunday, January 20, 2008

Hi

I'm trying to capture thoughts about technology and software development as they happen.

Lately I'm playing with the Google Web Toolkit so I want to discuss my progress with that.