Thursday, August 7, 2008
Getting Fully Qualified (attributes, that is)
Just use the Node.attribute() method, and pass it the QName of the attribute. You'll just need to define a namespace, like:
def atom = new Namespace("http://www.w3.org/2005/Atom")
def rdf = new Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf")
And then you're ready to go. For example, you have a document with a title node with an RDF "about" attribute. title[@about] won't get it, because the attribute is behind the namespace. But this will:
def parser = new XmlParser(false, true) def feed = parser.parse(feedDoc)
def about = feed.title.attribute(rdf.about)
The nice thing about this is it works the same way everywhere, e.g.,
def ed = root[foaf.Person].find {
it.attribute(rdf.about) == "$edRef"
}[foaf.name].text()
And so on. Have fun!
Thursday, May 1, 2008
Password validation in Grails on user domain class
class User {
String userId
String password
String password2
String email
static constraints = {
password(blank: false, nullable: false, size:5..20, validator: {password, obj ->
def password2 = obj.properties['password2']
if(password2 == null) return true // skip matching password validation (only important when setting/resetting pass)
password2 == password ? true : ['invalid.matchingpasswords']
})
}
static transients = ['password2']
}
Notice password2 property is a 'transient' property. This means it will not be included in the database model or object relational mapping of GORM. The other trick here is the custom validator closure in the constraints for the password field. The way this validator works is if password2 property is not null compare it with password, if they do not equal return the error code 'invalid.matchingpasswords'.
In this case you could add the error code user.password.invalid.matchingpasswords in the messages.properties file in 'grails-app/i18n' folder of your project. Do this like normal Java properties:
user.password.invalid.matchingpasswords=uh oh the passwords you entered do not match
Now in cases where password2 is not being used it will be null. This will prevent the validator from doing the test of password vs. password2. This is important if you have forms which update the other fields on your user class while not updating the password field. With out this check for null you would have many troubles saving user instances due to this validation. Note this trick it could be useful to you many times over!
To finish this little exercise off check out this little bit of controller code:
class UsersController {
def create = {
// maybe pass some models into template?
}
def save = {
def user = new User(params) // password, password2 set from form?
if(user.save())
{
flash.message = "user '${user.userId}' added"
redirect(action:create)
}
else
{
flash.message = "error(s) creating user"
render(view:"create",model:[user:user])
}
}
}
When creating the new User object we pass params to the constructor. This will cause the form parameters to bind to the User object's properties. See the data binding section of the Grails reference documentation. If password2 was a form field then it is auto magically set on the new User object. When we call save a few lines later this value will be compared with the 'password' property, voila!
Wednesday, October 31, 2007
DOMCatagory
If you do any XML processing then I suggest you try DOMCategory in Groovy. A 'category' itself is feature of the Groovy language, borrowed from the objective C language. The short of it is you can use 'category' classes to wrap a given piece of your code. The objects in the wrapped code then gain new abilities! So what about DOMCatetgory (DOMcat)? This category allows you to work with DOM objects using a simple syntax. So an example would help. Let me first show a simple XML document then I will give a short script that can parse some values from the document.
Here is a real piece of XML I have dealt with. You can see its complexity is more than basic. It contains the namespace 'soap' which maps to http://www.w3.org/2003/05/soap-envelope, and also there are namespaces with values of urn:zimbraAdmin and urn:zimbra. For sake of ease lets say this file is named "zimbra.xml" and is found at the root ('/') of the file system.
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<context xmlns="urn:zimbra">
<sessionId id="467" type="admin">467</sessionId>
<change token="8"/>
</context>
</soap:Header>
<soap:Body>
<GetAllDistributionListsResponse xmlns="urn:zimbraAdmin">
<dl id="b5d362c5-ef95-4fce-a5bc-1f7b06ece7f6" name="testing2@west.ora.com">
<a n="uid">testing2</a>
<a n="mail">testing2@west.ora.com</a>
<a n="zimbraMailStatus">enabled</a>
<a n="cn">testing2</a>
<a n="description">Testing DL for SOAP</a>
<a n="zimbraId">b5d362c5-ef95-4fce-a5bc-1f7b06ece7f6</a>
<a n="objectClass">zimbraDistributionList</a>
<a n="objectClass">zimbraMailRecipient</a>
<a n="displayName">testing2</a>
<a n="zimbraMailAlias">testing2@west.ora.com</a>
</dl>
<dl id="45c9cd14-811f-426b-888f-42ec05f3d7cc" name="testing3@west.ora.com">
<a n="uid">testing3</a>
<a n="mail">testing3@west.ora.com</a>
<a n="zimbraMailStatus">enabled</a>
<a n="cn">testing3</a>
<a n="description">for testing Zimbra SOAP services</a>
<a n="zimbraId">45c9cd14-811f-426b-888f-42ec05f3d7cc</a>
<a n="objectClass">zimbraDistributionList</a>
<a n="objectClass">zimbraMailRecipient</a>
<a n="displayName">testing3</a>
<a n="zimbraMailAlias">testing3@west.ora.com</a>
</dl>
<dl id="ddcec243-29fd-4aea-b443-912c69b30544" name="testing@west.ora.com">
<a n="uid">testing</a>
<a n="mail">testing@west.ora.com</a>
<a n="zimbraMailStatus">enabled</a>
<a n="cn">Testing</a>
<a n="description">Testing DL via SOAP</a>
<a n="zimbraId">ddcec243-29fd-4aea-b443-912c69b30544</a>
<a n="objectClass">zimbraDistributionList</a>
<a n="objectClass">zimbraMailRecipient</a>
<a n="displayName">Testing</a>
<a n="zimbraMailAlias">testing@west.ora.com</a>
</dl>
</GetAllDistributionListsResponse>
</soap:Body>
</soap:Envelope>
Now lets examine parsing this document from disk and examining some of its data. Here is a code snippit to create a DOM object, which we will use later with a DOMCategory.
def reader = new FileReader(new File("/zimbra.xml"))
def doc = DOMBuilder.parse(reader)
def zimbradom = doc.documentElement
Now that we have a dom we can bust out the DOMCategory so that parsing is a snap!
use(groovy.xml.dom.DOMCategory)
{
soapdom.'soap:Body'[0].'GetAllDistributionListsResponse'[0].'dl'.each { node ->
println "I am ${node.'@name'}"
}
}
A simple example but there is a lot to explain here. First you can see that you use indexes to select a given node. In this case our XML document has only one 'soap:Body' node, and only one 'GetAllDistributionListsResponse' node. So we could acutally omit the [0] indexes:
use(groovy.xml.dom.DOMCategory)
{
soapdom.'soap:Body'.'GetAllDistributionListsResponse'.'dl'.each { node ->
println "I am ${node.'@name'}"
}
}
Notice how we select an attribute value on a given element. Simply prefix a '@' symbol before the name of the attribute. See also that the full qualified names are being used to select some elements. Since the document names the element 'soap:Body' it can be selected with that exact name. While the elements that fall in the 'urn:zimbra' and 'urn:zimbraAdmin' namespaces are not prefixed and therefore we do not use a prefix to select said elements (as seen in selecting 'GetAllDistributionListsResponse' and 'dl'). Keep this idea in mind when selecting qualified attributes too!
Tuesday, July 24, 2007
Samba + Groovy
smbAuth = new NtlmPasswordAuthentication("bob.com", "joe", "smoe")
fullPath = "smb://bob.com/media$/blah.txt"
smbFile = new SmbFile(fullPath, smbAuth)
if(smbFile.exists()) {
smbDate = new Date(smbFile.getLastModified())
println "Date on SMB file: ${smbDate}"
}
The above example connects to the samba machine, gets the file we want, and then gets and prints the modified date on that file. You can use this library to write quick scripts to sync files from a non samba machine to another machine (Windows?).
Wednesday, June 27, 2007
Go Check Out this Loop Tutorial
I'm gonna write about Groovy!
I'll make sure that I add new things here often. I encourage readers to ask questions, hoping that I can answer them. This could lead me to understand better how groovy could fit into others personal worlds.