JavaLoader
1.0 is a project I've been talking about off and on for the past 6
months or so, and I'm very happy to announce that a release version is
now complete.
There are several key new features to JavaLoader 1.0,
Dynamic Compilation
Now
if you are working with JavaLoader, you don't have to bundle your Java
code into a jar to load. You can simply specify to JavaLoader which
directories are your source directories, and JavaLoader will compile
and load them on the fly for you! JavaLoader will also check to see if
your source code has changed, and re-compile it as necessary!
For
example, if I wanted to compile and load a HelloWorld.java (that has a
simple 'Hello World' method) in my ./src directory, I could do:
javaloader = createObject("component", "javaloader.JavaLoader").init(sourceDirectories=[expandPath('./src')]);
And then create my HelloWord object as per normal:
<cfset helloWorld = javaloader.create("HelloWorld").init()>
<cfoutput>#helloWorld.hello()#</cfoutput>
No more ANT tasks, no more export to .jars, JavaLoader handles the compilation and deployment of your Java code for you.
ColdFusion Component Dynamic Proxy
For those of you who are not aware of what a
Dynamic Proxy
in Java is, it is essentially an Object that can mimic an object that
implements a given set of Interfaces. The method invocations against
that object are intercepted, and you can essentially do whatever you
like with them.
In JavaLoader 1.0, I've written a Dynamic Proxy that you are able to wrap around a CFC, and thereby make Java Objects
think
they are interacting with a native Java object, but are in fact,
talking to your CFC. When a Java Object calls a method on the Dynamic
Proxy, it is passed through and invoke directly on the CFC transparent
to your Java layer.
For example, the
java.lang.Thread Java object can take an instance of the interface
java.lang.Runnable in its constructor argument. Using JavaLoader's Dynamic Proxy, we can pass it what it
thinks is an instance of the Runnable Interface, but is in actuality a ColdFusion Component like so:
//give us access to the CFCDynamicProxy
CFCDynamicProxy = javaloaderloader.create("com.compoundtheory.coldfusion.cfc.CFCDynamicProxy");
//create my Runner CFC
myRunner = createObject("component", "MyRunner").init();
//wrap it in a Dynamic Proxy, that implements Runnable
runnerProxy = CFCDynamicProxy.createInstance(myRunner, ["java.lang.Runnable"]);
//pass it to the Thread and call run()
thread = createObject("java", "java.lang.Thread").init(runnerProxy);
thread.run();
...and suddenly we have true seamless communication from Java to ColdFusion components.
This
is actually something I wish I had written three years ago. It would
have radically altered how I would have written a lot of software.
Spring Integration
A natural progression from the Dynamic Proxy, was to implement a
Custom Schema in
Spring, so that you could instantiate and dependency inject your ColdFusion components with your Java Objects inside Spring itself.
Initialising
Spring with JavaLoader is covered in the documentation, but to give you
a quick taste, once we have Spring running, we can include a CFC in our
spring.xml with:
<coldfusion:cfc id="message"
script-source="file://home/www/model/Message.cfc"
script-interfaces="com.IMessage"
/>
Where
script-source is the absolute path to the CFC you want to instantiate, and
script-interfaces are the Java interfaces that your dynamic proxy will implement, and your CFC has mirrored inside it.
This
Component then gets treated like a normal Spring bean, so it can be
autowired, injected, aop'd and all the usual functionality that a
Spring bean has access to.
There are some downsides to implementing Components inside Spring, so be sure to check out the documentation for more details.
Read More
For more details, have a read of the
extended documentation, and have a look at the examples provided in the
download.
Since
this has become a far larger project than its original inception, I've
done away with the Riaforge Forum, and have started a new
javaloader-dev google group for support and discussion of JavaLoader and Java and ColdFusion integration.
While
I'm very confident in the code that I have written for JavaLoader 1.0,
I decided to call it an Alpha, simply because it has only really been
tested by me and my Unit Tests. Knowing from experience that users of
Open Source Software have a tendency to use and abuse libraries far
above and beyond what the author had ever originally dreamed led me to
decide to call it Alpha until the community has really had a good
change to play with it.
Please
download JavaLoader 1.0, take it for a spin, sign up for the
mailing list, and if you find any problems please do let me know.
This is something I just ran into with ColdFusion 9's ORM implementation, that thankfully Rupesh got back to me very fast on, otherwise, I probably would have been confused for quite a while.
I
needed to overwrite a get and set value in a CFC I was creating. We
will say for arguments sake I was hashing a password on a User object
(even though I was doing something completely different), like so:
component output="false" persistent="true"
{
property name="userid" fieldtype="id" ormtype="int" generator="identity" hint="primary key";
property name="name" type="string" notnull="true" length="200" hint="The user's name";
property name="password" type="string" length="200" hint="The user's password";
public void function setPassword(String password)
{
instance.password = hash(arguments.password);
}
public String function getPassword()
{
return instance.password;
}
}
This is how I would usually have written my Components, storing my data in variables.instance, so they wouldn't overlap with any of the data stored in my variables scope.
What I couldn't understand was, my password would keep getting stored in my database as NULL, even though I had explicitly set it.
Thankfully Rupesh explained that when the ColdFusion ORM retrieves and populates data in an ORM managed CFC, it directly accesses the variables scope in the CFC. So in the above instance, the ORM was finding no data for the password, and interpreting that to mean that I wanted to insert that as NULL into my database.
Therefore, to get my code to work was to store my data in the variables scope, like so:
component output="false" persistent="true"
{
property name="userid" fieldtype="id" ormtype="int" generator="identity" hint="primary key";
property name="name" type="string" notnull="true" length="200" hint="The user's name";
property name="password" type="string" length="200" hint="The user's password";
public void function setPassword(String password)
{
variables.password = hash(arguments.password);
}
public String function getPassword()
{
return variables.password;
}
}
This way the ORM can find exactly what it is looking for, and I can manipulate my data the way I want.
The fact that the ORM integration works this way is actually a very good thing, as it means that my password above doesn't get hash'd
over and over again, but it is something we all should be aware of when
you want to overwrite the implicit get and set functions that get
generated by <cfproperty> declarations.
In the previous two articles, we started the discussion of Hibernate Sessions and how they related to the ColdFusion 9 ORM. We noted the fact that a Hibernate Session
keeps track of what objects it loads, and can therefore update it when
it realises that the object has changed. The question then posses
itself - if a Hibernate Session has closed, which happens at the end of every ColdFusion request, what happens to the objects that it was managing?
Going forward, I'm going to reuse the Musician model I used in the Introducing ORM in Adobe ColdFusion 9 beta Adobe DevNet article, for which the updated code samples can be found here.
The very short version for those who don't wish to look at the code, is
a model in which we have a Musician, who has a name, and an age, and
he/she also has a many-to-many relationship to an array of Instruments,
which they can play.
As far as Hibernate is concerned, there are three different states for any Object that it interacts with.
Transient
An Object/Entity (you could use either word) is Transient, if it has just been created with a
createObject() or
new operator, but has yet to be saved with the
EntitySave() method.
Obviously, if transient objects are not persisted via EntitySave(),
then they never get stored in the database, and will eventually be
garbage collected.
For example, our musician
john below is a transient object:
<cfscript>
import com.*;
//this is transient
john = new Musician("Transient John");
</cfscript>
Since john is never saved, it will always be transient.
Persistent
A
Persistent object is one that has either come from, or has been saved in the database, within the scope of the current
Hibernate Session (which is an important point to note).
For example, we can make our musician
john below persistent, by:
<cfscript>
import com.*;
//join is transient
john = new Musician("Transient John");
john.setAge(47);
//john is now persistent
EntitySave(john);
</cfscript>
Therefore, since
john was inserted into the database, he is
persistet.
By the same token, if we retrieve an object through the ORM, it is also
persistent:
<cfscript>
//larry's ID is 2
//larry is also persistent
larry = EntityLoad("Musician", 2, true);
//As larry is persistent, we can change his age, which will be reflected in the database
larry.setAge(19);
</cfscript>
Since larry was retrieved from the database, he is also persistent.
Detached
Detached objects are ones that used to be
persistent, but the
Hibernate Session has
closed. Detached objects were one of the biggest things for me to wrap
my head around when starting with Hibernate, and can cause a few really
tricky errors to occur.
To explain
detached a little better, we can go through a
scenario that will causes them, as I think this is something that a lot
of ColdFusion developers are going to end up running in to.
Let's have a new musician, called
Tim, and time is going to have 2 instruments that he plays, the Flute and Piano.
We are going to grab Tim out of the database, and then put him in our
ColdFusion Session scope, so we can keep track of him from request to request.
<cfscript>
tim = EntityLoad("Musician", 2, true);
session.musician = tim;
</cfscript>
For this request, Tim will be
persistent, however, on the next request, Tim is now
detached, as the
Hibernate Session will have closed at the end of the previous request.
Therefore, if I do this:
<cfscript>
instruments = session.musician.getInstruments();
</cfscript>
<cfdump var="#instruments#">
You will see the following (not well written) error:
failed to lazily initialize a collection of role: Musician.instruments, no session or session was closed
If you are running ColdFusion via the console, you will also see something similar to:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: Musician.instruments, no session or session was
closed
What does this error even mean?
First of all, we should remember that the collection of Instruments our Musician can play are
lazy loaded. This means they aren't actually loaded from the database into the object until you actually request them.
The important thing to note here, is that lazy loading with Hibernate can only occur if the
Hibernate Session that the object was originally loaded from is still open. To put it another way, if the
Hibernate Session has closed, then Hibernate has no record of what the object was doing, and therefore can't do any lazy loading.
In the above example, since the previous ColdFusion request ended, the
Hibernate Session has closed, and if you attempt to access a relationship that is set to lazy load, and has yet to be loaded, this error occurs.
Its should be noted that singular properties are not lazy loaded -
therefore, if you are just grabbing a property on an object that is
detached, no error will be thrown.
What can we do to mitigate this issue? There are a few options, one of
which is, to simply retrieve the object all over again, like so:
<cfscript>
session.musician = EntityLoad("Musician", session.musician.getMusicianID(), true);
instruments = session.musician.getInstruments();
</cfscript>
<cfdump var="#instruments#">
Or, like so:
<cfscript>
session.musician = entityLoadByExample(session.musician, true);
instruments = session.musician.getInstruments();
</cfscript>
<cfdump var="#instruments#">
As then the object we are dealing with is considered
persistent, as it is attached to the currently open
Hibernate Session. This tends to be the easiest of the options when dealing with detached objects.
Another interesting way of making our
detached object
persistent is like so:
<cfscript>
EntitySave(session.musician);
instruments = session.musician.getInstruments();
</cfscript>
<cfdump var="#instruments#">
This may look strange, but what is actually happening when you call EntitySave(), is that it calls the Hibernate method
saveOrUpdate().
A quick version of what saveOrUpdate() does is -
- If the object being passed in is transient, INSERT it to the database.
- If the object is already persistent in the current Hibernate Session, do nothing.
- If there is already an object that represents the same Entity data persistent in the Session, throw an exception
- Otherwise, update the database with the information in the passed in object, and set its state to persistent.
Therefore in the example above, the data stored in
session.musician will be saved again to the database, and since
session.musician is now deemed
persistent, we can successfully do lazy loading of the instruments.
However, if a situation like this would occur:
<cfscript>
musicianTwo = EntityLoad("Musician", session.musician.getMusicianID(), true);
EntitySave(session.musician);
</cfscript>
We get the following error:
a different object with the same identifier value was already associated with the session: [Musician#2]
Since Hibernate deems the
persistent object that is currently connected to the
Hibernate Session much more important than the detached object, which makes sense, as the
persistent object has the most up to date version of the data stored in the database.
Generally speaking, if you at all can, try and avoid having
detached
objects in your application, as this is generally a Good Thing™, and
will allow you to avoid some pretty decent headaches as you develop
with CF9 ORM. Strategies such as storing IDs in the
ColdFusion Session
and having an API to return a new Object by that ID on request is often
a good way to go, rather than storing the Entity itself in the
ColdFusion Session. That being said, if your architecture means that you have to have
detached objects this series should be enough to give you a basic grounding the concepts of
object state, and how to manage it.
If you want to learn
more about managing Hibernate Object States (and there is far more to it than I have written here), you can read more about this in the
Hibernate documentation,
but this should hopefully have given you a decent enough overview to
avoid some possible pitfalls when handling persistence with ColdFusion
9's ORM integration.