Frank Fusion

Saturday, 26 April 2008

ColdFusion Encapsulation Gotcha

Good object design dictates that we encapsulate our objects, hiding direct access to object properties from outside code. Access to the values of these properties is often granted to outside code with the use of 'getter' methods.

The following, useless, component appears to encapsulate its 'foo' property using a getter method but, as we shall see shortly, does nothing of the kind:

<cfcomponent output="false" displayname="foobar">
<cfset variables.foo = StructNew()>
<cfset variables.foo.bar = "Hello world">

<cffunction name="GetFoo">
<cfreturn variables.foo>
</cffunction>
</cfcomponent>

Why is that not encapsulated?

The reason the 'foo' property is not encapsulated here is that ColdFusion passes certain variable types by reference rather than value. So when we return the variables.foo structure we are actually returning a reference to the original data in memory rather than the data itself.

The following code demonstrates what effect this has:

<h1>Encapsulation gotcha example</h1>
<cfset oFoo = CreateObject('component', 'foobar')><!--- the component defined above --->

<h2> Before:</h2>
<cfdump var="#oFoo.GetFoo()#">

<cfset fooReference = oFoo.GetFoo()>
<cfset fooReference.foo = "Wayne's world, excellent">

<h2> After:</h2>
<cfdump var="#oFoo.GetFoo()#">

If the 'foo' property were properly encapsulated here, both dumps would show the same thing. However, because we have a reference to the original data in the variables 'fooReference', we can make changes to the data directly (breaking encapsulation).

What to do about it?

It is important to note here that passing variables by reference saves memory - it is certainly NOT a bad thing! However, if encapsulation is more important to you, you can use the Duplicate() method to return a reference to a copy of the data which has the effect of returning the data itself:

<cffunction name="GetFoo">
<cfreturn Duplicate(variables.foo)>
</cffunction>

Final note

ColdFusion passes queries, structures and external objects such as COM objects and CFC instances by reference.

Strings (including numeric values) and arrays are passed by value and do not need to be 'duplicated' in this way.

Here is a link to an Adobe livedocs page that talks about passing variables to and from functions in detail:

http://livedocs.adobe.com/coldfusion/7/htmldocs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=ColdFusion_Documentation&file=00001008.htm

Labels: ,

Sunday, 20 April 2008

Scatting with ColdSpring

I really like ColdSpring - quiet, clean and powerful - mmmm (forgive me, I'm very tired). There is one thing that I have found that bugs me a little however; the verbosity of defining aspects and advisors.

Here's a quick xml sample of creating a really simple aspect and applying its advice to a bean:


<beans>
<!-- state checker advice and advisor -->
<bean id="stateChecker" class="aopxml.stateChecker" />
<bean id="stateCheckerAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor">
<property name="advice">
<ref bean="stateChecker" />
</property>
<property name="mappedNames">
<value>*</value>
</property>
</bean>

<!-- test component with proxy -->
<bean id="testerTarget" class="aopxml.tester" singleton="false" />
<bean id="tester" class="coldspring.aop.framework.ProxyFactoryBean" singleton="false">
<property name="target">
<ref bean="testerTarget" />
</property>
<property name="interceptorNames">
<list>
<value>stateCheckerAdvisor</value>
</list>
</property>
</bean>

</beans>

This is example is from my blogpost http://fusion.dominicwatson.co.uk/2008/03/stricter-oop-using-aop.html.

Worse still, if I want to apply my aspect to any another beans, I would have to explicitly do so by defining a proxy for each bean.

So what's the solution?

The first thing I thought was to have specific AOP xml tags for the configuration that would allow you to define aspects and instruct ColdSpring to automagically create proxies for you based on the component name rules you supply it.

I posted this thought on the ColdSpring google group and the reply made me look to the Spring framework (on which ColdSpring is based) - what does the Spring framework do? Well, it does pretty much exactly as I suggested:

http://static.springframework.org/spring/docs/2.5.x/reference/aop.html#aop-schema

So, with a minor adaption for ColdFusion (though not budging from the Spring Xml schema), here is how the above Xml example could look:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop">
<!-- 'new' aop tags -->
<aop:config>
<aop:aspect id="stateCheckerAspect" ref="stateChecker">
<aop:around pointcut="*.*" method="invokeMethod"/>
</aop:aspect>
</aop:config>

<!-- 'aspect' beans -->
<bean id="stateChecker" class="temp.stateChecker" />

<!-- 'regular' beans -->
<bean id="tester" class="temp.tester"/>
</beans>

The above XML defines an Around advice that will be applied to every bean and every method in the factory. Of course, different rules could be setup and I think the benefit is quite graphical.

This got me all excited and sent me on an all night coding spree. The result was a new ColdSpring bean factory that would take the Xml above and make it work. Its just a proof of concept and a little rough around the edges but it shows what can be done with reasonable ease (thanks to the excellent codebase already in ColdSpring).

The extended factory will indeed parse most of the aop elements defined in the Spring schema. I won't go into detail as they are very well documented in the Spring documentation that I linked to. However, here is a quick example of an aop:config that the extended factory will do stuff with:

<aop:config>
<aop:pointcut id="aopxmlPackagePC" expression="aopxml.*.Add*"/>
<aop:aspect id="stateCheckerAspect" ref="stateChecker">
<aop:around pointcut-ref="aopxmlPackagePC" method="invokeMethod"/>
</aop:aspect>
<aop:aspect id="someErrorCatchingAspect" ref="someAdviceBean">
<aop:pointcut id="thisAppServices" expression="com.myCo.thisApp.services.*.*"/>
<aop:after-throwing pointcut-ref="thisAppServices" method="afterThrowing"/>
</aop:aspect>
</aop:config>

You can download the simple working example here. You may need to create a mapping to whereever you unzip it (mapping called 'aopxml').

A disclaimer: this is completely unofficial and just a proof of concept - I'm just scatting. Enjoy :)

Labels: , , , ,

Saturday, 5 April 2008

Stricter OOP without AOP! Sort of ;)

I've been playing around with the idea of forcing my components not to add or remove variables from their 'variables' scope, other than when instantiated. Its something I'd like not to be possible and I'd like a ColdFusion error if it happens. I blogged my first effort at cracking it here:

Stricter OOP using AOP!

The biggest downside to this was complexity of implementation. While I don't mind hidden complexity, using AOP meant that I would have to explicitly declare that each of my objects was wanting to use this feature/'aspect' - the effort in doing so using ColdSpring far outweighed any benefit. This was a 'global aspect' that would be best placed in a global base class if possible.

So, how to wrap all of a component's methods automatically using a base class that it extended? The concept is similar to AOP but it would lack the hassle of having to apply AOP to my model. Of course, the hassle would be figuring out how to do it!

A very long and steep-learning-curved night later and I have a working version. It borrows from the concept used in ColdSpring's AOP implementation of creating a temporary file in order to create component methods on the fly (mixins). The use is slightly different however. The following is an attempt to explain the base class constructor as concicely as possible:

  1. Store copy of self in a state variable.

  2. Create temporary cfc file with methods of the same name as those in this component. The body of these methods have a single line that calls a 'CallMethod()' method, passing the method name and any args as arguments.

  3. Instantiate temporary cfc and delete the file.

  4. Overwrite the original component methods with those in the temporary component

  5. Removed methods used in this process from the component

The result is a component that appears unchanged (from the outside) but that has each of its methods replaced with a wrapper that invokes the samed named method on a copy of the original component.

Once this component has been written, implementation couldn't be much easier. Simply extend the component and call it's constructor from within the extended component and you're done.

Rather than have all that code be used specifically for monitoring a component's variables, I first created an uber-base class which for now I have called selfproxy. This does what I have described above but does nothing useful in the 'self proxied' methods. To do something useful (such as monitor the variables scope), I just extend the component and override its method interceptor.

This 'self proxying' mullarky lacks the precision and flexibility of AOP, but if you're painting with a broad brush it might be a useful thing.

A working example (tested on ColdFusion 8 running on Windows XP).

I'm just scatting on a theme here and would be interested to hear any constructive criticism ;)

Anyways, time for bed!

Labels: , , ,

Thursday, 3 April 2008

A better autosuggesting widget

Following on from my previous post about extending the cfinput auto-suggest functionality (a tidy autosuggesting solution), I decided that my solution was too narrow. I have now written a custom tag that allows access to all the useful properties of the auto-suggest widget.

The tag can be found @ http://betterautosuggest.riaforge.org/

Here is a quick and slim example of how it can be used:

<cfimport taglib="myCustomTagsFolder" prefix="custom">

<cfform action="" method="post">
<custom:betterautosuggest
name="fruit"
autosuggest="apple,banana,lemon,lime,mango,orange,peach,pear"
delimchar=";"/>
</cfform>



Enjoy :)

Labels: , ,

A tidy autosuggesting solution

A question was asked on the CF-Talk list for which I did not know the answer. I chimed in anyhow and got investigating. In the process, I learned a whole load more javascript as well as all sorts of interesting things about CF8's ajax form fun and, in the end, I solved the problem consicely :)

The problem

"I have a form field that autosuggests an email address, however it only works with one email address at a time. How would I go about setting it up so that when a user starts typing the autosuggest will append the emailaddress with a semicolon and then allow the user to search for anotheraddress in the same field?"

The bottom line, unless I am misguided, is that it can't be done with the ColdFusion form tags. An autosuggestdelimiters attribute would be a really useful addition to the cfinput tag but for now, it doesn't exist.

Hunting for the solution

I had a look at the javascript that an autosuggest box produces and, after going crosseyed, found myself looking at the YAHOO.widget.autocomplete documentation (on which ColdFusion bases its autocomplete functionality). A quick look down the list of properties uncovered the delimChar property; 'surely not' I thought - surely. Set the delimChar property to the list delimiter you want and the auto suggest will work exactly as asked.

The solution

After some experimentation I came up with a single, tiny, javascript function to add in the delimiter to the autosuggest control:

SetDelimiter = function(elId, delim){
if(!ColdFusion.objectCache[elId])
alert("Error adding delimiter: Auto suggest item, '" + elId + "', could not be found");
else
ColdFusion.objectCache[elId].delimChar = delim;
}

This function needs to be run at some point after the page and the ajax objects have been loaded. I have it working at the moment by calling it when the input receives focus which clearly isn't the best but isn't going to hurt anyone (can someone tell me where it should go?)

Here is some CF code to demonstrate:

<script type="text/javascript" src="SetAutoSuggestDelimiter.js"></script>
<cfform action="" method="post">
<cfinput name="person" autosuggest="cfc:foo.bar({cfautosuggestvalue})" onfocus="SetDelimiter('person',';')"/>
</cfform>
Time for bed!

Labels: , ,

Sunday, 30 March 2008

Stricter OOP using AOP!

An application I wrote recently was exhibiting a strange bug. When a user added contacts to the contacts database, details from previously entered contacts were being inserted in fields that were left blank for the new contact. The client had entered over a hundred contacts before they realised that this was happening and were somewhat worried. After some investigation, I tracked down the cause of the problem: an accidentally unscoped variable in a CFC method.

In Object Oriented languages, an object's properties are explicitly declared in its definition; they are integral to the make-up of the object. To add or remove an object's properties during its life could be seen as mutating the object and an undesirable behaviour in a strict OO design (I do not believe it is possible to do this in C++ but I may be mistaken).

Due to the way we emulate OO in ColdFusion, such mutation is exactly what happened when I clumsily used an unscoped variable in my CFC; the variable became part of my object's 'integral make-up' resulting in the sneaky bug.

This made me wonder: how could I restrict my objects from ever modifiying their definitions by deleting or creating new variables in the variables or this scope during their existance?

The first, and so far only, useable answer I have arrived at is through using Aspect Oriented Programming (AOP). My implementation uses the ColdSpring framework although it could be done without it. For the AOP savvy, I simply created an around advice object that checks the target object's properties before and after any method execution - if there is an inconsistency, an appropriate error is thrown. The pseudo code looks like this:

BEGIN
SET variablesBefore = Get object's variables
EXECUTE object's method
SET variablesAfter = Get object's variables

IF variablesBefore <> variablesAfter THEN throw error
END


If anyone is interested in the Source code I can post it, just a PITA to put code up using blogger! Better solutions on a postcard.

Dominic

Labels: , , , ,

Tuesday, 25 March 2008

XmlSearch and default Namespaces

This pops up frequently enough on the cf-talk list and around the blogosphere. To get an idea of the problem you can read this blog post that usually gets quoted as the answer:

http://www.talkingtree.com/blog/index.cfm/2005/11/18/XmlSearchNoNameNamespace

If you were keen, you may have noted the comments that refer to the technique not working when there are multiple, nested default namespaces. Take the following Xml for example:

<?xml version="1.0"?>
<foo xmlns="http://dominicwatson.co.uk/foo/">
<foochild>
<bar xmlns="http://dominicwatson.co.uk/bar/">
<barchild>lamb</barchild>
</bar>
</foochild>
</foo>

Doing XmlSearch(theXml, '//:foochild')will return results but XmlSearch(theXml, '//:barchild') will not. There are ways around this using XPath but they can become very cumbersome if your XPath is anything like complicated.

The source of the problem is ColdFusion's pretty poor provision of XPath goodiness. Other languages provide XPath querying methods that take a namespace mapping object as an argument. This allows you to define custom prefixes to use with the namespace URIs in your XPath queries. Here is a .net blog post as an example:

http://weblogs.asp.net/wallen/archive/2003/04/02/4725.aspx

Enter BetterXml 1.0!

In version 1 of my BetterXml components, just released, I have added the ability to add custom prefix mappings to Xml documents in ColdFusion (much in the way other languages allow). Using a betterXml object loaded with the xml above, we can now do something like this:

<cfscript>
oXml.MapNamespace('http://dominicwatson.co.uk/foo/', 'f');
oXml.MapNamespace('http://dominicwatson.co.uk/bar/', 'b');
results = oXml.Search('//b:barchild);
</cfscript>


Problem solved! You can download the latest version of the project on riaforge:

http://betterXml.riaforge.org/

Labels: , ,

Monday, 18 February 2008

Undefined array elements

This seems to come up every now and again on the house of Fusion cf-talk list. Basically, arrays can contain undefined values which cause all sorts of problems. Here's a piece of code that demonstrates the problem:


<cfset myArray = ArrayNew(1)>
<cfset myArray[1] = 'foo'>
<cfset myArray[3] = 'bar'>
<cfloop from="1" to="#ArrayLen(myArray)#" index="i">
<cfoutput>#myArray[i]#,</cfoutput>
</cfloop>

The trouble is that myArray[2] is undefined. Thinking back to the article on the underlying java methods of the array object (see coldfusion objects are java objects), I came up with this, I think elegent, solution to the problem:


<cfscript>
function DeleteUndefinedArrayElements(arr){
var aTemp = ArrayNew(1);
aTemp[2] = '';
ArrayDeleteAt(aTemp,2);

arr.removeAll(aTemp);
return arr;
}
</cfscript>


So now we can rewrite our original example as:


<cfset myArray = ArrayNew(1)>
<cfset myArray[1] = 'foo'>
<cfset myArray[3] = 'bar'>
<cfset myArray = DeleteUndefinedArrayElements(myArray)>
<cfloop from="1" to="#ArrayLen(myArray)#" index="i">
<cfoutput>#myArray[i]#,</cfoutput>
</cfloop>


And there we have it. Usefully, the original array remains untouched due to arrays being the only ColdFusion objects not passed by reference. This means that you can get a copy of the array without undefined elements should you need the original intact.

Of course, it would be better to fix the problem at the source, i.e. stop the undefined elements being 'created' in the first place. This may not always be possible though, so a solution like this might come in handy.

Labels: ,

Wednesday, 19 December 2007

More loopy looism

Here's a little thing I was forced to realise after a far too high profile site when slightly wrong (in a far too big way).

Looping a query inside a loop of another query changes the way you can refer to the outer-loops current row. Running the following code demonstrates this:


<cfscript>
qry_foo = QueryNew('foo');
QueryAddRow(qry_foo, 3);
for(i=1; i LTE 3; i=i+1){
QuerySetCell(qry_foo,'foo',"foo #i#",i);
}
qry_bar = QueryNew("bar");
QueryAddRow(qry_bar, 10);
for(i=1; i LTE 10; i=i+1){
QuerySetCell(qry_bar,'bar',"bar #i#",i);
}
</cfscript>
<cfoutput>
<ul>
<cfloop query="qry_foo">
<li>#qry_foo.foo#
<ul>
<cfloop query="qry_bar">
<li>#qry_foo.foo#</li>
</cfloop>
</ul>
</li>
</cfloop>
</ul>
</cfoutput>


The problem is that you cannot access the current row of the qry_foo query when inside the loop of qry_bar in this way. Instead, you would have to use:

qry_foo.foo[qry_foo.currentRow]

Does anyone else find this bizarre?

Labels:

Tuesday, 4 December 2007

SQL Date Comparison Gotcha

Just a little thing to remind me to use DateDiff() instead of regular operators when comparing dates. Here's why:

Say you have a deadline date stored in a db and you need to see if today is passed the deadline. If you do this:

DECLARE @deadline smalldatetime
SET @deadline = '2007-12-04'

IF(GetDate() <= @deadline)
PRINT 'Deadline not passed'
ELSE
PRINT 'Deadline passed'

You will get 'Deadline passed' if the deadline is today which is not as you might expect. The reason for this is that GetDate() also returns the time and the operator takes this into consideration, i.e. the following is true 2007-12-04 16:19 > 2007-12-04 00:00.

So, instead, when comparing the difference in days one should always use:

IF(DateDiff(d, GetDate(), @deadline) >= 0)
PRINT 'Deadline not passed'
ELSE
PRINT 'Deadline passed'

Incidentally, the same is true for ColdFusion and the DateDiff ColdFusion function works in the same way.

Labels: ,

Sunday, 23 September 2007

BetterXML Beta Release!

http://betterxml.riaforge.org/

After some initial learning about XML and handling it in ColdFusion 6.1 & 7, I came to the conclusion that ColdFusion wasn't the best when it came to XML.

When reading and transforming XML, ColdFusion does a great job and is at its usual brilliantly quick. Where I think it gets cumbersome is in maintenance scenarios where you have to edit a given XML file. For example, you may have some XML that is being used as the sole data source and needs updating whenever a user adds item x or deletes item z, etc.

An ideal solution to this is to use XPath to quickly navigate the XML in readiness for your CRUD operations. ColdFusion's XMLSearch() method is not suitable in this case however, as it only returns data from an XPath query. To navigate the XML using XPath, we need the XPath query to return a nodeset that is a collection of pointers to the original data. Once we have this collection of pointers, we can go about performing our CRUD operations on the data.

This is where BetterXML comes in. BetterXML is a set of two ColdFusion Components; an XML reader and an XML editor, the editor extending the reader. They both use the org.apache.xpath.XPathAPI java object that lets them quickly navigate the data using XPath.

The public methods are as follows:

XML Reader:
  • Init( src ) - Returns an instance of the object, loading the XML if supplied
  • Load( src ) - loads XML from either an xml string, file or URL
  • Search( XPath, filter ) - returns an array of either strings or simplified structures based on the results of the XPath query
  • Count( XPath ) - returns the number of results returned by the XPath query
  • Names( XPath ) - returns the names of the attributes and elements returned by the XPath query
  • XML() - returns the XML as a string
XML Editor:
  • Init( src ) - Returns an instance of the object, loading the XML if supplied
  • Write( destination ) - Writes the XML to file
  • CreateElement( XPath, Name, Value ) - Creates an element in all the parents returned by the XPath query. The value of the element can be a simple value or a structure to write nested elements.
  • CreateAttribute( XPath, Name, Value ) - Creates an attribute in all the elements returned by the XPath query
  • Update( XPath, Value ) - Updates all the elements returned by the XPath query. The updated value can be a simple value or a structure to write nested elements.
  • Delete( XPath ) - Deletes all the elements returned by the XPath query
A quick example
Lets say you have a huge XML file that is full of comments that are now redundant. To delete all the comments from the file we can do:

<cfinvoke component="BetterXML_Editor" method="init" src="#myFile#" returnVariable="xmlFile"/>
<cfscript>
xmlFile.Delete('//comment()');
xmlFile.Write(myFile);
</cfscript>

The code is minimal and the load times hugely improved when compared to using native ColdFusion functions to achieve the same end :)

Dom

Labels: ,

Loopy Loo

What is the best way to loop a structure in a cfscript block? On discovering the elements() and keys() methods that are inherited from java.util.dictionary, I presumed these would be faster i.e.
<cfscript>
keys = myStruct.keys();
while( keys.hasMoreElements() ){
key = keys.nextElement();
value = myStruct[key];
// do stuff...

}
// or
values = myStruct.elements();
while( values.hasMoreElements() ){
value = values.nextElement();
// do stuff...
}
// or
keys = myStruct.keys();
values = myStruct.elements();
while( keys.hasMoreElements() ){
key = keys.nextElement();
value = values.nextElement();
// do stuff...
}
</cfscript>
Normally, I would use StructKeyArray() and one might conceivably use StructKeyList():
<cfscript>
// StructKeyArray()
keys = StructKeyArray(myStruct);
size = ArrayLen(keys);
for (i=1; i LTE size; i=i+1){
key = keys[i];
value = myStruct[key];
// do stuff...
}

// StructKeyList()
keys = StructKeyList(myStruct);
size = ListLen(keys);
for (i=1; i LTE size; i=i+1){
key = ListGetAt(keys, i);
value = myStruct[key];
// do stuff...
}
</cfscript>
So which is faster? Well, it all depends:

For small structures, say 10 keys, StructKeyArray() is the winner followed closely by the inherited Java methods and then StructKeyList(). The differences in performance aren't huge here, but StructKeyList() is still clearly slower.

As the structure gets larger however, the results change. Using StructKeyList() becomes exponentially slower and the inherited Java methods overtake StructKeyArray() somewhere in the region of a structure with 150 keys.

Conclusion: use StructKeyArray() unless you happen to be dealing with a huge structure and you need to squeeze every bit of performance necessary. I can't see why you should ever use StructKeyList() to loop a structure, perhaps someone could give an example where it is necessary?

On a similar note, I had also presumed that using myArray.size() would be faster than ArrayLen(myArray), it certainly looks prettier to me anyway! It turns out I was wrong. Some magic in ArrayLen() means it well outperforms size(). Still, size() is OO and looks nice ;)

Labels: ,

Saturday, 22 September 2007

Singular URL and FORM variable

After reading this discussion on IsDefined() vs StructKeyExists(), I got to thinking about how one might use the underlying Java methods of ColdFusion structures to combine the URL and FORM variables efficiently (it is related, really).

A quick inspection of the java docs for java.util.Hashtable turned up the putAll() method. With it, you can 'put' one struct into another, overwriting any matching keys.

So, to very quickly and elegantly combine the form and url scopes into a request variable called 'args', we can do:
<cfset request.args = StructNew()>
<cfset request.args.putAll(url)>
<cfset request.args.putAll(form)>
Et voila!

Labels: ,

ColdFusion objects are Java objects...

A good while back I read the following post about using the methods that ColdFusion objects inherit from their Java parents. It prompted me to write a little function for finding out what Java objects any ColdFusion object inherits from:

http://coldfused.blogspot.com/2007/01/extend-cf-native-objects-harnessing.html

<cfscript>
function GetClassHeirarchy(obj)
{
var thisClass = obj.GetClass();
var sReturn = thisClass.GetName();

do{
thisClass = thisClass.GetSuperClass();
sReturn = sReturn & " EXTENDS: #thisClass.GetName()#";
}while(CompareNoCase(thisClass.GetName(), 'java.lang.Object'));

return sReturn;
}
</cfscript>

<!--- so, for example: --->
<cfoutput>#GetClassHeirarchy(StructNew())#</cfoutput>

I think it fairly useless having the function just sitting around and have decided to post results, with links to java and other docs, here on the interweb! You will be able to use most, if not all, the inherited methods that you see in the JavaDocs.

ColdFusion Query
java.lang.Object -> coldfusion.sql.imq.imqTable -> coldfusion.sql.Table -> coldfusion.sql.QueryTable

ColdFusion Struct
java.lang.Object -> java.util.Dictionary -> java.util.Hashtable -> coldfusion.util.FastHashtable -> coldfusion.runtime.Struct

ColdFusion Array
java.lang.Object -> java.util.AbstractCollection -> java.util.AbstractList -> java.util.Vector -> coldfusion.runtime.Array

ColdFusion XML Document Object
java.lang.Object -> org.apache.xerces.dom.NodeImpl -> org.apache.xerces.dom.ChildNode -> org.apache.xerces.dom.ParentNode -> org.apache.xerces.dom.CoreDocumentImpl -> org.apache.xerces.dom.DocumentImpl

That's all for now, will be posting examples of use in the coming hours...

Dom

Labels: ,