<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6778893253459142202</id><updated>2012-02-03T10:42:21.511Z</updated><category term='design'/><category term='grails'/><category term='css'/><category term='javascript'/><category term='agile'/><category term='gorm'/><category term='mysql'/><category term='java'/><category term='groovy'/><category term='html'/><category term='security'/><category term='internet'/><title type='text'>Duncan Sommerville - Developer Thoughts</title><subtitle type='html'>Thoughts on the practical aspects of modern software development - focuses on what actually works in the real world.

Considers new, old and emerging technologies as well as languages, tools, techniques and methologies.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>21</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-3958138530752220959</id><published>2010-05-01T21:42:00.023+01:00</published><updated>2010-05-02T09:26:11.194+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails From Basics - Using an IDE</title><content type='html'>&lt;span style="font-family:arial;"&gt;&lt;br /&gt;My last post got us up and running with a trivial application using the command-line - although not particularly exciting it is important to understand how to interact with Grails from the command-line, especially when debugging.&lt;br /&gt;&lt;br /&gt;However, we can make life &lt;em&gt;much&lt;/em&gt; easier for ourselves by leveraging an Integrated Development Environment (IDE).&lt;br /&gt;&lt;br /&gt;There are many to chose from, but the predominant Java-orientated ones are:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.jetbrains.com/idea/"&gt;IntellijIDEA&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.eclipse.org/"&gt;Eclipse&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://netbeans.org/"&gt;Netbeans&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I have limited experience with IntellijIDEA, but have heard good reports (although I'm also told that it's quite resource-hungry). I did use Eclipse a few years ago and liked it, but the integration with Grails was quite painful at the time.&lt;br /&gt;&lt;br /&gt;This led me to Netbeans, which I rather like - I have seen it steadily improve over the last couple of years and, while it appears to be regarded as a bit of an underdog, I think its integration with Grails is first-rate.&lt;br /&gt;&lt;br /&gt;The main thing missing from Netbeans in terms of Grails integration (this applies to all the other IDE's as well) is the inability to run an application in 'debug' mode directly, although I believe it's on the Netbeans roadmap.&lt;br /&gt;&lt;br /&gt;If you need to step through the code in 'debug' mode you need to run the application from the command-line using 'grails-debug run-app' (rather than 'grails run-app'). This will run the application, but with a listening TCP/IP socket on port 5005 to which Java debuggers can attach.&lt;br /&gt;&lt;br /&gt;Consequently you can 'attach' Netbeans to the running process and set a breakpoint - this works, but is somewhat tedious and makes debugging things like startup issues very difficult. However, we're getting ahead of ourselves - more on debugging applications later...&lt;br /&gt;&lt;br /&gt;Netbeans gets my vote - as such I will use it as the IDE for the remainder of these Grails' posts, but I'll be very interested to receive comments about other IDE's.&lt;br /&gt;&lt;br /&gt;So, lets re-create our 'library' application using Netbeans instead of doing everything via the command-line.&lt;br /&gt;&lt;br /&gt;Delete the contents of the 'GrailsDemo\library' directory if you wish (although Netbeans will create our new project in a separate directory). However, if you want to completely re-create the 'library' project you &lt;em&gt;will&lt;/em&gt; need to delete the contents of the following directory:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;%HOMEPATH%\.grails\1.2.2\library&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Alternatively you can simply create the Netbeans project with a slightly different name, although I do think that it's important to know how Grails works under the bonnet and know where all its files reside.&lt;br /&gt;&lt;br /&gt;If you haven't done so already download and install &lt;a href="http://netbeans.org/downloads/index.html"&gt;Netbeans 6.8&lt;/a&gt; or later with Groovy support - this is included in the 'Java' and 'All' downloads (I tend to simply download the 'All' distribution, then disable those elements that I'm not interested in during the installation).&lt;br /&gt;&lt;br /&gt;Once installed start Netbeans and allow it to check for and install any updates (force it via Help menu and Check for Updates option, if necessary), then create a new project:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;FileNew Project...&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Select a Cateory of 'Groovy' which has only one type of Project - a Grails Application:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9yiKZSLRLI/AAAAAAAAADg/Hesbpqqu3H8/s1600/nb-new-grails.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 221px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5466422347190453426" border="0" alt="" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9yiKZSLRLI/AAAAAAAAADg/Hesbpqqu3H8/s320/nb-new-grails.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Click the 'Next' button and set the Project Name to 'library' and click the 'Finish' button:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9ynhGwNcZI/AAAAAAAAADo/Hdw8umDqkdo/s1600/nb-project-name.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 220px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5466428234911281554" border="0" alt="" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9ynhGwNcZI/AAAAAAAAADo/Hdw8umDqkdo/s320/nb-project-name.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;This will invoke 'grails create-app' on the command-line after which Netbeans will provide you with a Output pane in the bottom-right corner of the console output and a breakdown the project hierarchy on the top-left:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9yoCvUs14I/AAAAAAAAADw/YJxbzZbwe1s/s1600/nb-created.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 200px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5466428812737435522" border="0" alt="" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9yoCvUs14I/AAAAAAAAADw/YJxbzZbwe1s/s320/nb-created.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;I tend to turn off the Navigator pane (bottom-left) as it's not particularly informative with the Groovy language due to its dynamic nature - this gives you more space to navigate the various artifacts.&lt;br /&gt;&lt;br /&gt;Expand the folders in the Project pane - you will see files exist under the following:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Configuration&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Message Bundles&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Views and Layouts&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Web Application&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;This arrangement is very similar to the Grails directory structure and is easy to navigate:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90Vk7pb3EI/AAAAAAAAAD4/4GyreG41Gxc/s1600/nb-grails-projects.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90Vk7pb3EI/AAAAAAAAAD4/4GyreG41Gxc/s320/nb-grails-projects.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466549246928608322" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;However, while it is intuitive the layout isn't &lt;em&gt;exactly&lt;/em&gt; the same which can thrown you off - it also fails to give you access to everything in the Grails hierarchy (in particular, the 'templates' directory if you install this plugin, which we'll come on to later).&lt;br /&gt;&lt;br /&gt;If you wish to view the files and directories click on the Files tab which &lt;em&gt;does&lt;/em&gt; give direct access to the exact directory structure and files therein:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S90VyngtwnI/AAAAAAAAAEA/jhvPMTPwlBg/s1600/nb-grails-files.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S90VyngtwnI/AAAAAAAAAEA/jhvPMTPwlBg/s320/nb-grails-files.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466549482041492082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;It's also worth checking out the Services tab - this is where the servlet containers (Jetty, Tomcat, Glassfish, etc) are listed and can be controlled, as well as integration with other external services such as MySQL and Hudson:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90WB4qnytI/AAAAAAAAAEI/DOE0Ird_T04/s1600/nb-grails-services.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90WB4qnytI/AAAAAAAAAEI/DOE0Ird_T04/s320/nb-grails-services.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466549744344484562" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Finally, it's also worth knowing where Netbeans stores configuration options relating to Groovy and Grails - go to the Tools menu and click on Options; you then need to click on the Miscellaneous button followed by the Groovy tab:&lt;br /&gt;&lt;br /&gt;Although not particularly intuitive the good news, as you will see, is that there are very few options. The main one to watch is the Grails Home path - make sure you update it if you download and unzip a newer version of Grails later on, as it overrides the GRAILS_HOME environment variable...&lt;br /&gt;&lt;br /&gt;Now that we've explored Netbeans a little and created our application let's start creating our artifacts (ie a Domain Class and associated Controller) as we did in the previous posting, but this time through the Netbeans interface.&lt;br /&gt;&lt;br /&gt;First of all we need to create a Domain Class called 'Book' that exists in the 'com.example' package - right-click on the Domain Classes folder and select New followed by Grails Domain Class...&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90ae1HbBGI/AAAAAAAAAEY/0fmlBiDpScA/s1600/nb-new-domain-class.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90ae1HbBGI/AAAAAAAAAEY/0fmlBiDpScA/s320/nb-new-domain-class.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466554639654257762" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Specify the Artifact Name of 'Book' and Package of 'com.example', then click Finish:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S90YJuy6q-I/AAAAAAAAAEQ/MvcXLBYOdy4/s1600/nb-new-domain-class-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 221px;" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S90YJuy6q-I/AAAAAAAAAEQ/MvcXLBYOdy4/s320/nb-new-domain-class-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466552078157130722" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;As before, our Domain Class has now been created with skeleton code - note that Netbeans has provided the output from the command-line in the Output pane on the bottom-left:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90a7HyuSbI/AAAAAAAAAEo/YXGVmDlp13Q/s1600/nb-new-domain-class-3.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90a7HyuSbI/AAAAAAAAAEo/YXGVmDlp13Q/s320/nb-new-domain-class-3.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466555125704051122" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;We can now edit our class as before:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90c6IQqlWI/AAAAAAAAAEw/1U2M635LGG4/s1600/nb-new-domain-class-4.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S90c6IQqlWI/AAAAAAAAAEw/1U2M635LGG4/s320/nb-new-domain-class-4.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466557307673023842" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Notice the syntax-highlighting provided by Netbeans - keywords are in blue, variables are in green and the &lt;em&gt;toString()&lt;/em&gt; method has been highlighted in bold.&lt;br /&gt;&lt;br /&gt;So far so good - we now need to create a Controller so that our users can interact with the Domain Class. As you might expect, right-click on the Controllers folder, select New then Grails Controller....&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S90fRvHvqMI/AAAAAAAAAE4/lR1mun9Tfzo/s1600/nb-new-controller.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S90fRvHvqMI/AAAAAAAAAE4/lR1mun9Tfzo/s320/nb-new-controller.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466559912264837314" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Enter the Artifact Name (which is &lt;em&gt;still&lt;/em&gt; Book, rather than BookController; Grails will append the 'Controller' bit to the class and filename for us) and use the same Package name of 'com.example':&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S90fbJx37BI/AAAAAAAAAFA/dHu15bNWeeQ/s1600/nb-new-controller-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S90fbJx37BI/AAAAAAAAAFA/dHu15bNWeeQ/s320/nb-new-controller-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466560074039684114" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Netbeans will invoke Grails on the command-line (and again, you can view the output as it happens in the Output pane on the bottom-left) to create a new Controller called BookController with skeleton code:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S90fkzOdJ4I/AAAAAAAAAFI/uzZ0vwUpoKs/s1600/nb-new-controller-3.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S90fkzOdJ4I/AAAAAAAAAFI/uzZ0vwUpoKs/s320/nb-new-controller-3.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466560239784241026" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;As before, we need to replace the Controller code with a single statement that tells Grails to 'scaffold' everything for us:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S90f12WE1DI/AAAAAAAAAFQ/jcj6AlA6qqY/s1600/nb-new-controller-4.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S90f12WE1DI/AAAAAAAAAFQ/jcj6AlA6qqY/s320/nb-new-controller-4.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5466560532679283762" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;That's everything in place - run the application by clicking on the green arrow icon on the top toolbar. Alternatively you can press &amp;lt;F6&amp;gt; or go to the Run menu and choose the Run Main Project option.&lt;br /&gt;&lt;br /&gt;Netbeans will effectively issue a 'grails run-app' command, with the output appearing in the bottom-right Output pane - once the server has been started Netbeans should launch your default browser and open a new Tab or Window at the appropriate URL for your running application.&lt;br /&gt;&lt;br /&gt;To stop the application switch back to Netbeans and click the square red button on the left of the Output pane. You can also view all running services by clicking on the Services tab (top-left) and expanding the Servers, then Jetty icons and right-clicking on the 'library' service.&lt;br /&gt;&lt;br /&gt;So, we now know how to create a trivial Grails application using an IDE - future postings will start to delve into some other practicalities such as unit-testing, altering the scaffolding, persisting data (so we don't lose it all every time we stop the application) and stepping through code with the debugger.&lt;br /&gt;&lt;br /&gt;Keep watching this space...&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-3958138530752220959?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/3958138530752220959/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2010/05/grails-from-basics-using-ide.html#comment-form' title='27 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/3958138530752220959'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/3958138530752220959'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2010/05/grails-from-basics-using-ide.html' title='&lt;span style=&quot;font-family:arial;&quot;&gt;Grails From Basics - Using an IDE&lt;/span&gt;'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_OFgXaBh5BPs/S9yiKZSLRLI/AAAAAAAAADg/Hesbpqqu3H8/s72-c/nb-new-grails.JPG' height='72' width='72'/><thr:total>27</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-4147005089262439272</id><published>2010-04-27T21:48:00.019+01:00</published><updated>2010-05-02T06:44:56.787+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails From Basics - First Application</title><content type='html'>&lt;span style="font-family:arial;"&gt;&lt;br /&gt;Yesterday I covered the basics of getting your environment set up and ready to run your first Grails application.&lt;br /&gt;&lt;br /&gt;We'll now step through creating an application using the command-line, which is quite straightforward.&lt;br /&gt;&lt;br /&gt;First of all we need to create a 'workspace' directory (I've created one called 'GrailsDemo' under 'MyDocuments') then change into that directory and type the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;grails create-app library&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dQysQ4u0I/AAAAAAAAABo/2aqzuUaoRxE/s1600/cl-create-app-1.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dQysQ4u0I/AAAAAAAAABo/2aqzuUaoRxE/s320/cl-create-app-1.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464925504643054402" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dRAS8t_RI/AAAAAAAAABw/An8d4GwomsE/s1600/cl-create-app-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dRAS8t_RI/AAAAAAAAABw/An8d4GwomsE/s320/cl-create-app-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464925738365746450" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;What Grails actually does is create a directory heirarchy that is largely empty but with directories that will hold our classes, controllers, services, tag-libraries, etc. Some of these directories (most notably the 'grails-app/conf' directory) will be populated with default configuration files:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dRI61an9I/AAAAAAAAAB4/vOIKxDQKEfw/s1600/cl-create-app-3.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 262px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dRI61an9I/AAAAAAAAAB4/vOIKxDQKEfw/s320/cl-create-app-3.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464925886511488978" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;As the name implies, our sample Grails application is going to be an online library system that allows us to build up a database of books together with associated authors, publishers, and so on - this also ties in quite closely with the examples provided in the main Grails documentation.&lt;br /&gt;&lt;br /&gt;We could run this right now, but the application wouldn't actually do anything as we've not yet defined any classes...&lt;br /&gt;&lt;br /&gt;What we need to do is define our first Domain Class; a Domain Class is simply a regular Groovy class (Plain Old Groovy Object or POGO) that resides in the 'grails-app/domain' directory. Any class that is found in this directory (or subdirectories) will be persisted by Grails to some sort of underlying database.&lt;br /&gt;&lt;br /&gt;For our example application we will start by simply creating a Book domain class:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;grails create-domain-class com.example.Book&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note the use of 'com.example' before the class name - this is simply a fully-qualified class name that prevents the name of our Book class from clashing with Book classes defined anywhere else in the system - you would normally use your own domain-name (in revese) here:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dTOtYk9II/AAAAAAAAACA/qgggaexG64s/s1600/cl-create-domain-class-1.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dTOtYk9II/AAAAAAAAACA/qgggaexG64s/s320/cl-create-domain-class-1.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464928185003340930" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;This command will create the 'com/example' directory structure under 'grails-app/domain' for us and create a file called 'Book.groovy' with skeleton code inside:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S9dUCeLLvNI/AAAAAAAAACI/lt0FBa4jL4I/s1600/cl-create-domain-class-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 256px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S9dUCeLLvNI/AAAAAAAAACI/lt0FBa4jL4I/s320/cl-create-domain-class-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464929074273828050" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;We need to edit the skeleton code to something meaningful - for now we'll simply add a single String property called 'title' and place a 'constraint' on it that says that it cannot be blank. We'll also create a &lt;em&gt;toString()&lt;/em&gt; method (that simply returns the 'title' string) which Grails' scaffolding can make use of - more on all this later:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dU7ra7rmI/AAAAAAAAACQ/SUVnFvupeF8/s1600/cl-create-domain-class-3.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 292px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dU7ra7rmI/AAAAAAAAACQ/SUVnFvupeF8/s320/cl-create-domain-class-3.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464930057082089058" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;So far we have defined our Domain Class (Book) but we also need to create a Controller that will interact with our users. The Controller will handle user requests and manage instances of our Domain Class for us - in other words the Controller will allow us to create, read, update and delete (CRUD) instances of our Domain Class called Book:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;grails create-controller com.example.Book&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Without a Controller our users can do nothing:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dXOt1d5UI/AAAAAAAAACY/FPo3wLAF1v0/s1600/cl-create-controller-1.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dXOt1d5UI/AAAAAAAAACY/FPo3wLAF1v0/s320/cl-create-controller-1.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464932583171024194" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;As with the Domain Class, this command will create the 'com/example' directory structure under 'grails-app/controller' for us and create a file called 'BookController.groovy':&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dX12r036I/AAAAAAAAACg/wT7tKy2-33I/s1600/cl-create-controller-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 256px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dX12r036I/AAAAAAAAACg/wT7tKy2-33I/s320/cl-create-controller-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464933255561404322" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Again, the file will be created with skelton code:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dYIqNJ_HI/AAAAAAAAACo/RzQgShNmtek/s1600/cl-create-controller-3.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 292px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dYIqNJ_HI/AAAAAAAAACo/RzQgShNmtek/s320/cl-create-controller-3.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464933578629053554" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Grails offers something called 'scaffolding' to get you up and running as quickly as possible - this means that although we do need to create a Controller, we can tell it to 'scaffold' the usual create, read, update and delete operations for us via a single line of code:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dYTT_UIFI/AAAAAAAAACw/lMS5w4Evtks/s1600/cl-create-controller-4.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 292px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dYTT_UIFI/AAAAAAAAACw/lMS5w4Evtks/s320/cl-create-controller-4.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464933761643978834" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Grails uses 'convention-over-configuration' to assocate the BookController controller class with the 'Book' domain class, as they both have the same prefix - you can change this if desired, but it's likely to make things confusing...&lt;br /&gt;&lt;br /&gt;We now have everything in place to try our first run of the library application - give it a go:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;grails run-app&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dYtg813AI/AAAAAAAAAC4/eKDtNDOoqmo/s1600/grails-run-app-1.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dYtg813AI/AAAAAAAAAC4/eKDtNDOoqmo/s320/grails-run-app-1.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464934211799866370" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S9dY3BzMRyI/AAAAAAAAADA/PNWuI5gKwuA/s1600/grails-run-app-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S9dY3BzMRyI/AAAAAAAAADA/PNWuI5gKwuA/s320/grails-run-app-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464934375236585250" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Note the URL in the last line; open your favorite browser and enter that URL in the address-bar - you should see something like this:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dZd3JEZAI/AAAAAAAAADI/a1SCpz51a60/s1600/library-run-1-1.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 222px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9dZd3JEZAI/AAAAAAAAADI/a1SCpz51a60/s320/library-run-1-1.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464935042390451202" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Click on the link below 'Available Controllers' and have a play - you should find that Grails has created a set of Views that will interact with our BookController, both based on 'scaffolding':&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dd1KKx2bI/AAAAAAAAADQ/uEh9KotDnt0/s1600/library-run-1-2.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 222px;" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9dd1KKx2bI/AAAAAAAAADQ/uEh9KotDnt0/s320/library-run-1-2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464939840681400754" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;The only two hints we had to provide were:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;The &lt;em&gt;toString()&lt;/em&gt; method in the Book class&lt;br /&gt; &lt;li&gt;The &lt;em&gt;def scaffold = Book&lt;/em&gt; line in the BookController class&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Grails does the rest for us!&lt;br /&gt;&lt;br /&gt;To terminate the application simply press &amp;lt;CTRL&amp;gt;+&amp;lt;C&amp;gt; in the command window:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9deElC9ZKI/AAAAAAAAADY/xH1b2oZQFmg/s1600/grails-run-app-3.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 162px;" src="http://1.bp.blogspot.com/_OFgXaBh5BPs/S9deElC9ZKI/AAAAAAAAADY/xH1b2oZQFmg/s320/grails-run-app-3.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5464940105594397858" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;The covers the absolute basics of creating a Grails application. Next time we'll create the same application using an IDE, rather than doing everything from the command-line - this should help to illustrate just how much easier using an IDE makes things.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-4147005089262439272?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/4147005089262439272/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2010/04/grails-from-basics-first-application.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/4147005089262439272'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/4147005089262439272'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2010/04/grails-from-basics-first-application.html' title='&lt;span style=&quot;font-family:arial;&quot;&gt;Grails From Basics - First Application&lt;/span&gt;'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_OFgXaBh5BPs/S9dQysQ4u0I/AAAAAAAAABo/2aqzuUaoRxE/s72-c/cl-create-app-1.JPG' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-5791480150554967351</id><published>2010-04-26T21:47:00.011+01:00</published><updated>2010-04-26T23:20:21.169+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails From Basics - Environment</title><content type='html'>&lt;span style="font-family:arial;"&gt;&lt;br /&gt;I've been meaning to do this for a while, but I wanted to put together a series of posts that help people get started with Grails, even when they have limited experience of J2EE and surrounding technologies (such as Spring and Hibernate).&lt;br /&gt;&lt;br /&gt;There is now an increasing amount of documentation surrounding Grails, much of it excellent (I'm a particularly big fan of &lt;a href="http://www.manning.com/gsmith/"&gt;Grails in Action&lt;/a&gt;). However, lots of the artciles assume a base level of knowledge and experience with Java, Groovy, Spring, Hibernate, etc.&lt;br /&gt;&lt;br /&gt;When I first started using Grails I had very little experience with many of these, apart from standard JDK - as a result I struggled with the learning curve to get up to speed with so many different technoligies all at once.&lt;br /&gt;&lt;br /&gt;Grails does a fantastic job of hiding the complexities of many of these underlying technoligies through its adoption of 'convention-over-configuration', but inevitably there comes a time in any non-trivial application when you need to pull back the covers and change the default way Grails does things - this is where I started to find things quite challenging!&lt;br /&gt;&lt;br /&gt;Consequently these articles will start by assuming you know relatively little about Java, J2EE, Groovy, Grails, Spring and so on, although I will assume the following:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;That you have some programming experience.&lt;/li&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Ideally Java, Ruby or similar, but any modern object-orientated language should do.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;It would also be an advantage if you were familiar with the Model-View-Controller design pattern.&lt;/li&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;This is the architecture on which Grails is based.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;li&gt;That you can download, install and configure applications on your computer.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;We will create a simple project using Grails in these posts - my environment will be based on a completely clean Windows XP Pro (SP3) desktop. I realise that this is a bit boring and somewhat dated, but it's still very common and provides a well-supported platform on which to build our project - it'll also minimise environmental distractions. Feel free to use a different operating-system, but you're on your own in terms of configuring it.&lt;br /&gt;&lt;br /&gt;For this first post we'll simply get things set up - to start with we need to download a number of applications and packages:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://java.sun.com/javase/downloads/widget/jdk6.jsp"&gt;Java SDK&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://java.sun.com/javaee/downloads/index.jsp"&gt;J2EE SDK&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.grails.org/Download"&gt;Grails&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://netbeans.org/downloads/index.html"&gt;Netbeans&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://dev.mysql.com/downloads/mysql/"&gt;MySQL Server&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.mysql.com/products/connector/"&gt;MySQL Connector/J&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://tomcat.apache.org/download-60.cgi"&gt;Tomcat&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://sourceforge.net/projects/console/"&gt;Console2&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Please don't be put off - you don't need to download and install &lt;em&gt;all&lt;/em&gt; of these right away and don't worry too much about getting the exact versions either:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9YDIa6aicI/AAAAAAAAABA/a0MVw7Uy4F0/s1600/downloads.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 205px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5464558641058843074" border="0" alt="" src="http://4.bp.blogspot.com/_OFgXaBh5BPs/S9YDIa6aicI/AAAAAAAAABA/a0MVw7Uy4F0/s320/downloads.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;The first thing you'll need to install is the Java JDK, followed by J2EE. Once these are in place you will need to unzip the Grails download to a suitable place - I usually place it in a directory called 'grails' from the root of the drive:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9YM9Xn1a1I/AAAAAAAAABI/mtZzSn3En3U/s1600/explorer.JPG"&gt;&lt;img style="WIDTH: 248px; HEIGHT: 320px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5464569446313323346" border="0" alt="" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9YM9Xn1a1I/AAAAAAAAABI/mtZzSn3En3U/s320/explorer.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Once unzipped you need to set a couple of environment variables:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;JAVA_HOME&lt;/li&gt;&lt;br /&gt;&lt;li&gt;GRAILS_HOME&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Set JAVA_HOME to the directory in which you installed the JDK and GRAILS_HOME to the directory into which you installed Grails:&lt;br /&gt;&lt;br /&gt;That should be it - open a command-print (Start -&gt; Run -&gt; cmd) and type the following:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;java -version&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;If Java is setup correctly you should get some sensible output telling you about the version of Java that you're using:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9YO8iScpFI/AAAAAAAAABQ/qkTwZRqcGm8/s1600/check-java.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 191px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5464571631019795538" border="0" alt="" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9YO8iScpFI/AAAAAAAAABQ/qkTwZRqcGm8/s320/check-java.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Following this type the following to check Grails:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;grails -version&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Grails doesn't actually recognise the '-version' switch, but if everything is setup correctly you should see something sensible output, including information about the version of Grails that is installed:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_OFgXaBh5BPs/S9YPBzjm1-I/AAAAAAAAABY/kkHwVNfaFFc/s1600/check-grails-2.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 191px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5464571721554515938" border="0" alt="" src="http://2.bp.blogspot.com/_OFgXaBh5BPs/S9YPBzjm1-I/AAAAAAAAABY/kkHwVNfaFFc/s320/check-grails-2.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;If you try to run Grails without setting the environment variables you will see something like this:&lt;br /&gt;&lt;div align="center"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9YPF8bTVCI/AAAAAAAAABg/AW_W99W_TPY/s1600/check-grails-1.JPG"&gt;&lt;img style="WIDTH: 320px; HEIGHT: 191px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5464571792655078434" border="0" alt="" src="http://3.bp.blogspot.com/_OFgXaBh5BPs/S9YPF8bTVCI/AAAAAAAAABg/AW_W99W_TPY/s320/check-grails-1.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;That's all you actually need to get started - in the next posting we will step through creating an application via the command-line, after which we'll change to using an Integrated Development Environment (IDE) which makes life &lt;em&gt;much&lt;/em&gt; easier - this is there Netbeans comes in.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-5791480150554967351?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/5791480150554967351/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2010/04/grails-from-basics-environment.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/5791480150554967351'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/5791480150554967351'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2010/04/grails-from-basics-environment.html' title='&lt;span style=&quot;font-family:arial;&quot;&gt;Grails From Basics - Environment&lt;/span&gt;'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_OFgXaBh5BPs/S9YDIa6aicI/AAAAAAAAABA/a0MVw7Uy4F0/s72-c/downloads.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-1578794710400270015</id><published>2010-01-08T11:58:00.002Z</published><updated>2010-01-08T12:22:17.549Z</updated><title type='text'>Upgrading to Grails 1.2</title><content type='html'>We have recently upgraded two of our main projects from Grails 1.1.2 to Grails 1.2.0, which for the most part went very smoothly.&lt;br /&gt;&lt;br /&gt;However, we did notice a couple of oddities with one of the projects:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The main index.html page contained code similar to the following:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; &amp;lt;g:isNotLoggedIn&amp;gt;&lt;br /&gt;  ${response.sendRedirect(createLink(controller: 'login', action: 'auth')}&lt;br /&gt; &amp;lt;/g:isNotLoggedIn&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This was throwing an exception for some strange reason, which we traced to the &lt;code&gt;createLink()&lt;/code&gt; tag library - we solved the problem by replacing the &lt;code&gt;createLink()&lt;/code&gt; with a string:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; &amp;lt;g:isnotloggedin&amp;gt;&lt;br /&gt;  ${response.sendRedirect('login/auth')}&lt;br /&gt; &amp;lt;/g:isnotloggedin&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Although not particularly elegant, it solved the problem - perhaps someone can suggest why &lt;code&gt;createLink()&lt;/code&gt; should have stopped working?&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The other problem we encountered was when trying to untar a TAR'd file, which resulting in a &lt;code&gt;ClassNotFoundException&lt;/code&gt;. It turns out that this library was provided through &lt;code&gt;ant-1.7.0.jar&lt;/code&gt; which was part of the Grails 1.1.2 distribution, but this no longer appears to be the case. Finding and adding &lt;code&gt;ant-1.7.0.jar&lt;/code&gt; to the lib folder of our upgraded project solved the problem. Again, I'm not sure why this JAR has been dropped - perhaps someone can comment?&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-1578794710400270015?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/1578794710400270015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2010/01/upgrading-to-grails-12.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1578794710400270015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1578794710400270015'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2010/01/upgrading-to-grails-12.html' title='Upgrading to Grails 1.2'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-9069739248038745482</id><published>2010-01-07T09:54:00.002Z</published><updated>2010-01-07T10:19:28.862Z</updated><title type='text'>GroovyMag</title><content type='html'>I am astonished and humbled to have been listed as a UK blogger in the excellent &lt;a target="_blank" href="http://www.groovymag.com/"&gt;GroovyMag&lt;/a&gt; (Volume 2, Issue 3, January 2010, Page 4).&lt;br /&gt;&lt;br /&gt;For those who are not aware, GroovyMag is a monthly magazine that contains various articles about Groovy, Grails and related technologies (Grails Plugins, Griffon, Scala, etc). The magazine is published and downloaded as a ZIP file that contains the main magazine in PDF format, together with source-code to accompany relevant articles.&lt;br /&gt;&lt;br /&gt;Recent articles that I have found particularly useful and/or interesting include:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Building a Grails portal (Volume 2 - Issues 8, 10, 12)&lt;/li&gt;&lt;li&gt;Goldilocks and Grails Logging (Volume 2 - Issues 7, 8)&lt;/li&gt;&lt;li&gt;Enterprise Development with Groovy &amp;amp; Grails (Volume 2, Issue 11)&lt;/li&gt;&lt;/ul&gt; Clearly, there are lots of other excellent articles - those above are simply some that stood out for me.&lt;br /&gt;&lt;br /&gt;Both my team and I use Groovy and Grails on a daily basis and have had many successes to date. However, working with emerging technologies is not without its frustrations, with Grails in particular giving us many headaches.&lt;br /&gt;&lt;br /&gt;One of the biggest challenges when working with any new technology is finding accurate and up-to-date documentation - this is steadily improving and I thoroughly recommend both &lt;a target="_blank" href="http://www.manning.com/koenig/"&gt;Groovy in Action&lt;/a&gt; and &lt;a target="_blank" href="http://www.manning.com/gsmith/"&gt;Grails in Action&lt;/a&gt;, which have effectively become our bibles. However, this is where blogs and online forums can prove invaluable and is why I have tried to contribute.&lt;br /&gt;&lt;br /&gt;I must confessed to not having had the time to post many articles recently, but will certainly start doing so shortly - watch this space!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-9069739248038745482?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.groovymag.com/' title='GroovyMag'/><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/9069739248038745482/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2010/01/groovymag.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/9069739248038745482'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/9069739248038745482'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2010/01/groovymag.html' title='GroovyMag'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-1909745582400725422</id><published>2009-10-15T09:34:00.005+01:00</published><updated>2009-10-15T22:39:31.423+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails 'metadata' from within HttpSessionListener</title><content type='html'>I needed to extract the application-name during execution - under normal circumstances this can be achieved as follows:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; String appName = grailsApplication.metadata['app.name']&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However, I recently needed to extract the application-name from within an implementation of the &lt;code&gt;&lt;a target="_blank" href="http://bit.ly/2zfBD3"&gt;HttpSessionListener&lt;/a&gt;&lt;/code&gt; interface (which does not support inject of the &lt;code&gt;&lt;a target="_blank" href="http://bit.ly/271VM1"&gt;grailsApplication&lt;/a&gt;&lt;/code&gt; object). This was part of resource releasing once following a session timeout.&lt;br /&gt;&lt;br /&gt;Access to the same object can be achieved from within a &lt;code&gt;SessionListener&lt;/code&gt;  via the &lt;code&gt;&lt;a target="_blank" href="http://bit.ly/3RP6hQ"&gt;ApplicationHolder&lt;/a&gt;&lt;/code&gt; class:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; import org.codehaus.groovy.grails.commons.ApplicationHolder&lt;br /&gt; &lt;br /&gt; ...&lt;br /&gt; &lt;br /&gt; String appName = ApplicationHolder.getApplication().metadata['app.name']&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-1909745582400725422?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/1909745582400725422/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/10/secondly-i-needed-to-determine.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1909745582400725422'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1909745582400725422'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/10/secondly-i-needed-to-determine.html' title='Grails &apos;metadata&apos; from within HttpSessionListener'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-7550953125596715738</id><published>2009-10-15T08:48:00.009+01:00</published><updated>2009-10-15T22:38:26.164+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Determine Base-Directory from Running Grails Application</title><content type='html'>It's been a while since I've had chance to post, so here are a couple of short articles to get back into the swing of it.&lt;br /&gt;&lt;br /&gt;First off, here's how to determine the base directory of a running Grails application:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; String dir = grailsAttributes.getApplicationContext().getResource("/").getFile()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;code&gt;&lt;a target="_blank" href="http://bit.ly/16oIWD"&gt;grailsAttributes&lt;/a&gt;&lt;/code&gt; object &lt;i&gt;is&lt;/i&gt; available in all Controllers, but &lt;i&gt;is not&lt;/i&gt; directly available in Services (nor can you inject it).&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Should you need to  to determine the base directory from within a Service (or access &lt;code&gt;grailsAttributes&lt;/code&gt; etc) you need implement the &lt;code&gt;&lt;a target="_blank" href="http://bit.ly/2gzEp2"&gt;ApplicationContextAware&lt;/a&gt;&lt;/code&gt; interface:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; import org.springframework.context.*&lt;br /&gt; &lt;br /&gt; class MyService implements ApplicationContextAware {&lt;br /&gt;   ApplicationContext applicationContext&lt;br /&gt; &lt;br /&gt;   ...&lt;br /&gt; &lt;br /&gt;   String dir = applicationContent.getResource("/").getFile()&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-7550953125596715738?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/7550953125596715738/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/10/its-been-while-since-ive-had-chance-to.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/7550953125596715738'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/7550953125596715738'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/10/its-been-while-since-ive-had-chance-to.html' title='Determine Base-Directory from Running Grails Application'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-2200758344893201758</id><published>2009-10-01T21:33:00.008+01:00</published><updated>2009-10-01T23:02:40.053+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails &amp; MySQL Tables</title><content type='html'>Discovered a 'gotcha' today with Grails when using it to create tables in a MySQL database - we had the following entry in our DataSources.groovy:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; development {&lt;br /&gt;  dataSource {&lt;br /&gt;   dbCreate = "create-drop"&lt;br /&gt;   url = "jdbc:mysql://localhost/mydb"&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Nothing obviously wrong with that - except that MySQL will use its default 'engine' to create the tables.&lt;br /&gt;&lt;br /&gt;This is typically the &lt;i&gt;MyISAM&lt;/i&gt; engine which offers fast reads, but no transaction protection.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The reason MySQL defaults to this engine is that it is often used to hold large amounts of relatively static and read-only data (a product list for a website for instance), often as part of a &lt;a target="_blank" href="http://en.wikipedia.org/wiki/LAMP_(software_bundle)"&gt;&lt;i&gt;LAMP&lt;/i&gt;&lt;/a&gt; deployment. Such read-only deployments do not require transactions.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can check which engine is associated with each table in MySQL via the following statement:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; SHOW TABLE STATUS;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If MySQL is creating your database tables with the &lt;i&gt;MyISAM&lt;/i&gt; engine, then the following statement in your Grails Services will have no effect:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; static transactional = true&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As such, Grails methods such as &lt;i&gt;user.discard()&lt;/i&gt; appear not to work - the update has already been written to the database and cannot be rolled back.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This can also adversely affect Integration Tests if they try to roll-back database updates as part of a test-case scenario (in readiness for subsequent tests). You expect the test-case to have discarded whatever test data was written to the database, only to discover that it still exists!&lt;br /&gt;&lt;br /&gt;The solution is quite easy - you need to add a &lt;i&gt;dialect&lt;/i&gt; statement to the &lt;i&gt;dataSource&lt;/i&gt; to tell MySQL to use a different engine; one that offers transaction protection, such as &lt;i&gt;&lt;a target="_blank" href="http://dev.mysql.com/doc/refman/5.0/en/innodb.html"&gt;InnoDB&lt;/a&gt;&lt;/i&gt;:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; development {&lt;br /&gt;  dataSource {&lt;br /&gt;   dbCreate = "create-drop"&lt;br /&gt;   dialect= "org.hibernate.dialect.MySQLInnoDBDialect"&lt;br /&gt;   url = "jdbc:mysql://localhost/mydb"&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;The &lt;i&gt;InnoDB&lt;/i&gt; engine also has the advantage of providing &lt;a target="_blank" href="http://dev.mysql.com/doc/refman/5.0/en/internal-locking.html"&gt;row-level locking&lt;/a&gt; (whereas &lt;i&gt;MyISAM&lt;/i&gt; locks the whole table for each insert or update) and it also enforces &lt;a target="_blank" href="http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html"&gt;foreign-keys constraints&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Consequently, web-applications with medium-to-large volumes of data are likely to perform faster if a transactional engine such as &lt;i&gt;InnoDB&lt;/i&gt; is specified when using MySQL.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Note that you could also change the default storage engine used my MySQL via the &lt;i&gt;my.cnf&lt;/i&gt; file:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; default-storage-engine=innodb&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-2200758344893201758?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/2200758344893201758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/10/grails-mysql-tables.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/2200758344893201758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/2200758344893201758'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/10/grails-mysql-tables.html' title='Grails &amp; MySQL Tables'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-8743007200593406232</id><published>2009-09-23T20:41:00.002+01:00</published><updated>2009-09-23T21:27:28.213+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Custom Validation Messages in Grails</title><content type='html'>Grails stores its validation messages in a set of external files that can be found in the &lt;span style="font-style: italic;"&gt;grails-app/i18n&lt;/span&gt; directory - there are several files, with each file targeting a different language.&lt;br /&gt;&lt;br /&gt;This means you can internationalise your application with relative ease, by adding messages to these files in the appropriate language and having Grails select the appropriate language at runtime (or per session).&lt;br /&gt;&lt;br /&gt;You can also add bespoke validation messages that override the 'default' messages provided. For example, the &lt;span style="font-style: italic;"&gt;messages.properties&lt;/span&gt; file contains lines such as:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; default.blank.message=Property [{0}] of class [{1}] cannot be blank&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This means that if one of your classes fails validation because a field is empty, then a message saying something like the following is added to its errors property (and typically displayed through a flash message):&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; Property [password] of class [user] cannot be blank&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is OK during development, but is not going to be particularly friendly for your average user - thankfully you can override these default messages on a class-by-class basis, so you user sees messages such as:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; Password required&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However, it can be very difficult to find documentation on what the 'identifiers' of these messages should be (the bit on the left-hand side in &lt;span style="font-style: italic;"&gt;message.properties&lt;/span&gt;).&lt;br /&gt;&lt;br /&gt;The format of each identifier is:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; ${class}.${property}.${error}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Unfortunately the &lt;span style="font-style: italic;"&gt;${error}&lt;/span&gt; section does not directly match the existing contents of the &lt;span style="font-style: italic;"&gt;messages.properties&lt;/span&gt; file or the labels used within the &lt;span style="font-style: italic;"&gt;contraints&lt;/span&gt; section of your domain class.&lt;br /&gt;&lt;br /&gt;The list below shows the available &lt;span style="font-style: italic;"&gt;${error}&lt;/span&gt; properties to use when defining your own validation messages:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;blank&lt;/li&gt;&lt;li&gt;email.invalid&lt;/li&gt;&lt;li&gt;not.inList&lt;/li&gt;&lt;li&gt;matches.invalid&lt;/li&gt;&lt;li&gt;maxSize.exceeded&lt;/li&gt;&lt;li&gt;minSize.notmet&lt;/li&gt;&lt;li&gt;nullable&lt;/li&gt;&lt;li&gt;size.toobig&lt;/li&gt;&lt;li&gt;size.toosmall&lt;/li&gt;&lt;li&gt;unique&lt;/li&gt;&lt;li&gt;url.invalid&lt;/li&gt;&lt;li&gt;validator.error&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Consider the following domain-class as an example:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; package com.example.myapp&lt;br /&gt;&lt;br /&gt;  class User {&lt;br /&gt;    String username&lt;br /&gt;    String password&lt;br /&gt;    String emailAddress&lt;br /&gt; &lt;br /&gt;    static constraints = {&lt;br /&gt;      username(blank:false, unique:true)&lt;br /&gt;      password(blank:false, minSize:6)&lt;br /&gt;      emailAddress(blank:false, email:true)&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This means that we can define bespoke messages in &lt;span style="font-style: italic;"&gt;message.properties&lt;/span&gt; as follows:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt; user.username.blank=Username required&lt;br /&gt; user.username.unique=Username must be unique&lt;br /&gt; user.password.blank=Password required&lt;br /&gt; user.password.minSize.notmet=Password is too short&lt;br /&gt; user.emailAddress.blank=Email required&lt;br /&gt; user.emailAddress.email.invalid=Email not recognised&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;One final note: If your class is declared within a package, you do &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; need to specify the full class name.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-8743007200593406232?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/8743007200593406232/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/custom-validation-messages-in-grails.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/8743007200593406232'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/8743007200593406232'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/custom-validation-messages-in-grails.html' title='Custom Validation Messages in Grails'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-1728658025205013164</id><published>2009-09-22T20:52:00.006+01:00</published><updated>2009-09-23T10:06:13.628+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='security'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Using the Grails SpringSecurity Plugin</title><content type='html'>Many web-applications require users to authenticate at some point - this can be for many reasons, but typically includes access to a privileged area, retrieval and management of that user's profile and to track behaviour and preferences for marketing purposes.&lt;br /&gt;&lt;br /&gt;Creating a simple login form and forcing users through it is relatively straightforward in Grails by creating a &lt;span style="font-style: italic;"&gt;SecurityFilters.groovy&lt;/span&gt; under the &lt;span style="font-style: italic;"&gt;grails-app/conf&lt;/span&gt; directory, as described by &lt;a target="_blank" href="http://www.strattonenglish.co.uk/login_tutorial.pdf"&gt;this tutorial&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;However, there are plugins available that provide additional capabilities such as registration forms, 'captcha'  controls, support for OpenID and LDAP integration, to name but a few.&lt;br /&gt;&lt;br /&gt;There are two Grails security plugins that seem to dominate in popularity at the moment:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a style="font-style: italic;" target="_blank" href="http://grails.org/plugin/acegi"&gt;Spring Scurity&lt;/a&gt; (aka &lt;span style="font-style: italic;"&gt;ACEGI&lt;/span&gt;)&lt;/li&gt;&lt;li style="font-style: italic;"&gt;&lt;a target="_blank" href="http://grails.org/plugin/jsecurity"&gt;JSecurity&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;Of these I have only used &lt;span style="font-style: italic;"&gt;Spring Security&lt;/span&gt; in anger, so can't really comment on &lt;span style="font-style: italic;"&gt;JSecurity&lt;/span&gt;, but I have seen good reviews - my impression is that it offers slightly less features, but is easier to implement and maintain as a result.&lt;br /&gt;&lt;br /&gt;Having said that, I have found the &lt;span style="font-style: italic;"&gt;Spring Security&lt;/span&gt; plugin to be more than adequet for my own needs and relatively straigtforward to implement - here's a quick overview and a brief introduction on how you might configure it:&lt;br /&gt;&lt;br /&gt;First off, you need to install the plugin:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  grails install-plugin acegi&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Your next job is to create what are called the 'authentication domains', of which there are three:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Authority&lt;/li&gt;&lt;li&gt;Person&lt;/li&gt;&lt;li&gt;Requestmap&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;These are domain classes in the usual sense - they exist in the &lt;span style="font-style: italic;"&gt;grails-app/domain&lt;/span&gt; directory and get referenced by the underlying &lt;span style="font-style: italic;"&gt;authenticateService&lt;/span&gt; that is installed as part of the plugin.&lt;br /&gt;&lt;br /&gt;You can create these new domain classes, based on the class-names above, as follows:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  grails create-auth-domains&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However, you can pass parameters to this command if you wish to create classes with alternative named - perhaps names that align more closely with your business-model and/or design - for example:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  grails create-auth-domains User Role UrlMap&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above will create the same three classes, but with different names:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;User -&gt; Person&lt;/li&gt;&lt;li&gt;Role -&gt; Authority&lt;/li&gt;&lt;li&gt;UrlMap -&gt; Requestmap&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; Behind the scenes the plugin also creates a file called &lt;span style="font-style: italic;"&gt;SecurityConfig.groovy&lt;/span&gt; under the &lt;span style="font-style: italic;"&gt;grails-app/conf&lt;/span&gt; directory which defines how our custom class-names above (&lt;span style="font-style: italic;"&gt;User&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;Role&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;UrlMap&lt;/span&gt;) related to the classes expected by the plugin.&lt;br /&gt;&lt;br /&gt;You need to be careful here - the temptation might be to start editing these classes so they contain properties that exactly match your design, especially within the &lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;span style="font-style: italic;"&gt;User&lt;/span&gt; (&lt;span style="font-style: italic;"&gt;Person&lt;/span&gt;) class.&lt;br /&gt;&lt;br /&gt;While there is nothing stopping you from adding &lt;span style="font-style: italic;"&gt;new&lt;/span&gt; properties, be cautious about renaming &lt;span style="font-style: italic;"&gt;existing &lt;/span&gt;properties, as the underlying &lt;span style="font-style: italic;"&gt;authenticateService&lt;/span&gt; specifically expects some of them to exist.&lt;br /&gt;&lt;br /&gt;In particular, do not change the following properties:&lt;br /&gt;&lt;ul style="line-height: 50%;"&gt;&lt;br /&gt;&lt;li&gt;User (Person)&lt;/li&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;username&lt;/li&gt;&lt;br /&gt;&lt;li&gt;passwd&lt;/li&gt;&lt;br /&gt;&lt;li&gt;enabled&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;li&gt;Role (Authority)&lt;/li&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;authority&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;li&gt;UrlMap (Requestmap)&lt;/li&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;url&lt;/li&gt;&lt;br /&gt;&lt;li&gt;configAttribute&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/ul&gt;Also note that the 'relationship' properties (those named via the &lt;span style="font-style: italic;"&gt;hasMany&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;belongsTo &lt;/span&gt;statements) cannot be renamed - this can produce classes that look slightly odd:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  class User {&lt;br /&gt;    static hasMany = [authorities: Role]&lt;br /&gt;    ...&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  class Role {&lt;br /&gt;    static hasMany = [people: User]&lt;br /&gt;    ...&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can image this also leads to a database schema that is not particularly obvious - in particular you end up with a join table called &lt;span style="font-style: italic;"&gt;role_people&lt;/span&gt; that actually relates to the &lt;span style="font-style: italic;"&gt;user&lt;/span&gt; table.&lt;br /&gt;&lt;br /&gt;The next step is optional, but I recommend it to start with to get you up and running quickly:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  grails generate-manager&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This creates both the controllers and the views for each of the domain classes created by the &lt;span style="font-style: italic;"&gt;create-auth-domains&lt;/span&gt; command.&lt;br /&gt;&lt;br /&gt;You can replace the &lt;span style="font-style: italic;"&gt;Role&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;UrlMap&lt;/span&gt; controllers with basic scaffolded versions if you prefer, but leave the &lt;span style="font-style: italic;"&gt;User&lt;/span&gt; controller alone. Likewise, you can remove the generated &lt;span style="font-style: italic;"&gt;Role&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;UrlMap&lt;/span&gt; views and rely on Spring scaffolding to create them on demand, but once again, leave the &lt;span style="font-style: italic;"&gt;User&lt;/span&gt; views alone.&lt;br /&gt;&lt;br /&gt;You may also notice that some new views are available under a new directory of &lt;span style="font-style: italic;"&gt;grails-app/views/login&lt;/span&gt; - these are the default login forms provided by the plugin.&lt;br /&gt;&lt;br /&gt;Once the plugin has been installed and the appropriate class, controllers and view generated (as above) you can configure it as follows.&lt;br /&gt;&lt;br /&gt;First you need to create a &lt;span style="font-style: italic;"&gt;Role&lt;/span&gt; (&lt;span style="font-style: italic;"&gt;Authority&lt;/span&gt;):&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  http://localhost:8080/myapp/role/create&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Set the &lt;span style="font-style: italic;"&gt;authority&lt;/span&gt; property to ROLE_USER or similar (ie ROLE_ADMIN, ROLE_SUPPORT, etc). Note that the 'ROLE' prefix is important and is expected by the controller.&lt;br /&gt;&lt;br /&gt;Next you need to create an &lt;span style="font-style: italic;"&gt;UrlMap&lt;/span&gt; (&lt;span style="font-style: italic;"&gt;Requestmap&lt;/span&gt;) entry:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  http://localhost:8080/myapp/urlmap/create&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Set the &lt;span style="font-style: italic;"&gt;url&lt;/span&gt; property to the URI that you wish to protect access to - for example, if we had a class called &lt;span style="font-style: italic;"&gt;Book&lt;/span&gt; that we wanted to protect, we might set the &lt;span style="font-style: italic;"&gt;url&lt;/span&gt; property to:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  /book/**&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This matches everything under the &lt;span style="font-style: italic;"&gt;book&lt;/span&gt; URI, which covers the &lt;span style="font-style: italic;"&gt;list&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;create&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;edit&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;delete &lt;/span&gt;actions.&lt;br /&gt;&lt;br /&gt;Then set the &lt;span style="font-style: italic;"&gt;configAttribute&lt;/span&gt; property to a comma-delimited string of &lt;span style="font-style: italic;"&gt;authority&lt;/span&gt; values defined earlier in the &lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;span style="font-style: italic;"&gt;Role&lt;/span&gt; (or &lt;span style="font-style: italic;"&gt;Authority&lt;/span&gt;) class - in our case this is ROLE_USER.&lt;br /&gt;&lt;br /&gt;The plugin will now ensure that anyone wishing to access the &lt;span style="font-style: italic;"&gt;/book&lt;/span&gt; URI (and anything below it) must have the ROLE_USER role.&lt;br /&gt;&lt;br /&gt;Finally, we need to create a user account:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  http://localhost:8080/myapp/user/create&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The Spring Security plugin will reference the &lt;span style="font-style: italic;"&gt;username&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;password&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;enabled&lt;/span&gt; fields when authenticating users.&lt;br /&gt;&lt;br /&gt;The base of the form also displays a list of available &lt;span style="font-style: italic;"&gt;Roles&lt;/span&gt; with checkboxes next to each one - this allows you associate a &lt;span style="font-style: italic;"&gt;User&lt;/span&gt; with one (or more) &lt;span style="font-style: italic;"&gt;Roles&lt;/span&gt;. Once the user is created, try and access the Book URI:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  http://localhost:8080/myapp/book/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You should find yourself redirected to the login form provided by the plugin where you need to enter the &lt;span style="font-style: italic;"&gt;username&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;password&lt;/span&gt; entered when you created the user.&lt;br /&gt;&lt;br /&gt;Assuming valid details are provided the ROLE(s) associated with that account are checked - if one of these roles matches the &lt;span style="font-style: italic;"&gt;UrlMap&lt;/span&gt; entry for the &lt;span style="font-style: italic;"&gt;/book&lt;/span&gt; URI the permission will be granted.&lt;br /&gt;&lt;br /&gt;This covered the basics of getting up and running with the Spring Security plugin for Grails - I have only really skimmed the surface and the above illustrates just one possible way of configuring the plugin&lt;br /&gt;&lt;br /&gt;Please reference the &lt;a target="_blank" href="http://www.grails.org/AcegiSecurity+Plugin+-+Tutorials"&gt;tutorials&lt;/a&gt; for further information.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-1728658025205013164?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/1728658025205013164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/using-grails-springsecurity-plugin.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1728658025205013164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1728658025205013164'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/using-grails-springsecurity-plugin.html' title='Using the Grails SpringSecurity Plugin'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-4861740660351849066</id><published>2009-09-21T20:44:00.004+01:00</published><updated>2009-10-01T23:03:15.299+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='gorm'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Mapping Natural Keys using GORM</title><content type='html'>I needed to use GORM within Grails to map a legacy database today.&lt;br /&gt;&lt;br /&gt;This in itself isn't particularly difficult, however there's quite a neat trick available when you need to map a 'natural key' onto the internal &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; field required by GORM.&lt;br /&gt;&lt;br /&gt;The table in question was called &lt;span style="font-style: italic;"&gt;users&lt;/span&gt; and took the following form:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;login     varchar(20) not null primary key,&lt;br /&gt;password  varchar(20),&lt;br /&gt;full_name varchar(20)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I know - a good example or how &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; to implement a database schema. Nevertheless, this is what I was presented with...&lt;br /&gt;&lt;br /&gt;Forgetting the natural 'natural-key' field (&lt;span style="font-style: italic;"&gt;login&lt;/span&gt;) for a  moment, the Grails domain-class took the following form:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;class User {&lt;br /&gt;  String password&lt;br /&gt;  String fullName&lt;br /&gt;&lt;br /&gt;  static constraints = {&lt;br /&gt;    password(nullable:true,maxSize:20)&lt;br /&gt;    fullName(nullable:true,maxSize:20)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  static mapping = {&lt;br /&gt;    table 'users'&lt;br /&gt;    version false&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So far so good - both the &lt;span style="font-style: italic;"&gt;password&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;full_name&lt;/span&gt; columns are &lt;span style="font-style: italic;"&gt;varchar&lt;/span&gt; types, which map to Strings via GORM. They can both be nullable (yes, really) and I have added a max-size constraint for input validation.&lt;br /&gt;&lt;br /&gt;Note that I do not need to include these properties in the &lt;span style="font-style: italic;"&gt;mapping&lt;/span&gt; section, as &lt;span style="font-style: italic;"&gt;password&lt;/span&gt; matches its column name and GORM will automatically translate the field called &lt;span style="font-style: italic;"&gt;fullName&lt;/span&gt; to a column called &lt;span style="font-style: italic;"&gt;full_name&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I then used a &lt;span style="font-style: italic;"&gt;mapping&lt;/span&gt; closure to map to the &lt;span style="font-style: italic;"&gt;users&lt;/span&gt; table (plural - otherwise GORM would look for a table called &lt;span style="font-style: italic;"&gt;user&lt;/span&gt;). We also need to indicate that the &lt;span style="font-style: italic;"&gt;version&lt;/span&gt; column does not exist in our legacy database.&lt;br /&gt;&lt;br /&gt;Now the clever bit - we map the &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; field within GORM to a transient field, then use a custom getter and setter to control it.&lt;br /&gt;&lt;br /&gt;This gives us the following:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;class User {&lt;br /&gt;  String id&lt;br /&gt;  String password&lt;br /&gt;  String fullName&lt;br /&gt;&lt;br /&gt;  static transients = ['login']&lt;br /&gt;&lt;br /&gt;  static constraints = {&lt;br /&gt;    id(unique:true,blank:false)&lt;br /&gt;    password(nullable:true,maxSize:20)&lt;br /&gt;    fullName(nullable:true,maxSize:20)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  static mapping = {&lt;br /&gt;    table 'users'&lt;br /&gt;    id column: 'login'&lt;br /&gt;    version false&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  void setLogin(String login) {&lt;br /&gt;    id = login&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  String getLogin() {&lt;br /&gt;    id&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  String toString() {&lt;br /&gt;    id&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We have created a &lt;span style="font-style: italic;"&gt;transient&lt;/span&gt; field called &lt;span style="font-style: italic;"&gt;login&lt;/span&gt; - this simply means that it will exist within our domain object, but will never be persisted to the underlying database. The domain class now effectively has a virtual property called &lt;span style="font-style: italic;"&gt;login&lt;/span&gt; that is set via &lt;span style="font-style: italic;"&gt;setLogin()&lt;/span&gt; and returned via &lt;span style="font-style: italic;"&gt;getLogin()&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;So when we create a new User object, complete with a &lt;span style="font-style: italic;"&gt;login&lt;/span&gt; property, the setter will automatically update the [persistent] &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; property instead. GORM will then use the &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; property and map it to the underlying &lt;span style="font-style: italic;"&gt;login&lt;/span&gt; column within the database table.&lt;br /&gt;&lt;br /&gt;This really can be life-saver when using Grails to communicate with legacy databases&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-4861740660351849066?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/4861740660351849066/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/mapping-natural-keys-using-gorm.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/4861740660351849066'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/4861740660351849066'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/mapping-natural-keys-using-gorm.html' title='Mapping Natural Keys using GORM'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-5083992221779406626</id><published>2009-09-18T16:32:00.010+01:00</published><updated>2009-09-18T17:42:34.404+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Initial thoughts on jQuery</title><content type='html'>I am very new to &lt;a target="_blank" href="http://jquery.com/"&gt;jQuery&lt;/a&gt; but have already had a 'eureka' moment while reading about what it is and why you might want to use it.&lt;br /&gt;&lt;br /&gt;Briefly, jQuery is a free and open-source JavaScript library designed to make life easier when implementing client-side controls using JavaScript. However, this simple explanation belies a subtle but immensely important principle - separation of concerns.&lt;br /&gt;&lt;br /&gt;Just as HTML and CSS each have separate responsibilities when rendering a web-page (HTML dictates the &lt;span style="font-style: italic;"&gt;structure&lt;/span&gt; of the page, while CSS controls its &lt;span style="font-style: italic;"&gt;presentation&lt;/span&gt;), so too is the case with jQuery - its responsibility is to control the &lt;span style="font-style: italic;"&gt;behaviour&lt;/span&gt; of the page, removing the need to embed JavaScript events within HTML markup.&lt;br /&gt;&lt;br /&gt;For example, consider the following traditional HTML snippet:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  &amp;lt;a href="" onmouseover="alert('Pop!');"&amp;gt;Surprise&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is rather spurious, but you get the idea - we have a link which pops up a message box when the mouse goes over it.&lt;br /&gt;&lt;br /&gt;The problem is that we have JavaScript embedded in our HTML markup - a bad idea.&lt;br /&gt;&lt;br /&gt;Sure, we could move &lt;span style="font-style: italic;"&gt;alert('Pop!');&lt;/span&gt; to an external JavaScript file and reference it via a &lt;span style="font-style: italic;"&gt;&amp;lt;script&amp;gt;&lt;/span&gt; tag in the header - this is an improvement, but we still need the &lt;span style="font-style: italic;"&gt;onmouseover&lt;/span&gt; attribute to exist as part of our HTML in order to link the JavaScript to our field.&lt;br /&gt;&lt;br /&gt;jQuery provides a way of avoiding this - our HTML no longer requires events to be associated with specific elements:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  &amp;lt;a id="surprise" href=""&amp;gt;Surprise&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that we have added an &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; attribute which can be referenced both from CSS and jQuery - we now need to add our JavaScript to the &lt;span style="font-style: italic;"&gt;&amp;lt;head&amp;gt;&lt;/span&gt; of the page as follows:&lt;br /&gt;&lt;pre size="90%" style="line-height: 100%;"&gt;&lt;br /&gt;  &amp;lt;script type="text/javascript" src="scripts/jquery-1.3.2.min.js"/&amp;gt;&lt;br /&gt;  &amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;  jQuery(function() {&lt;br /&gt;    $("#surprise").mouseover(function() {&lt;br /&gt;      alert('Pop!');&lt;br /&gt;    });&lt;br /&gt;  });&lt;br /&gt;  &amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The first line includes the jQuery library, while the rest implements our event.&lt;br /&gt;&lt;br /&gt;Don't worry about the syntax for now - what we're doing here is attaching a function to the &lt;span style="font-style: italic;"&gt;mouseover&lt;/span&gt; event on any DOM object that has &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; of 'surprise' (since it's an &lt;span style="font-style: italic;"&gt;id&lt;/span&gt; reference, there can only be one).&lt;br /&gt;&lt;br /&gt;As expected, our function once again displays our popup alert, but without any dependency on the HTML markup - good separation of concerns!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-5083992221779406626?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/5083992221779406626/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/initial-thoughts-on-jquery.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/5083992221779406626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/5083992221779406626'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/initial-thoughts-on-jquery.html' title='Initial thoughts on jQuery'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-2751951280955760375</id><published>2009-09-18T16:01:00.016+01:00</published><updated>2009-09-23T06:51:36.647+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'>Hanging Indents with CSS</title><content type='html'>We had a need to create hanging indents for paragraphs recently, which turned out to be relatively straightforward through a combination of using &lt;span style="font-style: italic;"&gt;text-indent&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;padding-left&lt;/span&gt; properties:&lt;br /&gt;&lt;pre style="font-size: 90%; line-height: 100%;"&gt;&lt;br /&gt;  .hang {&lt;br /&gt;    padding-left: 2em;&lt;br /&gt;    text-indent: -2em;&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Paragraphs can then have the &lt;span style="font-style: italic;"&gt;hang&lt;/span&gt; class applied in order to be presented with hanging indents:&lt;br /&gt;&lt;pre style="font-size: 90%; line-height: 100%;"&gt;&lt;br /&gt;  &amp;lt;p class="hang"&amp;gt;&lt;br /&gt;    Lorem ipsum dolor sit amet...&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-2751951280955760375?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/2751951280955760375/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/hanging-indents-with-css.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/2751951280955760375'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/2751951280955760375'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/hanging-indents-with-css.html' title='Hanging Indents with CSS'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-853084136024167715</id><published>2009-09-17T11:57:00.004+01:00</published><updated>2009-09-23T06:51:23.232+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='internet'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Rember This Milk</title><content type='html'>A recent posting from &lt;a target="_blank" href="http://www.smashingmagazine.com/"&gt;Smashing Magazine&lt;/a&gt; pointed me towards &lt;a target="_blank" href="http://www.rememberthemilk.com/"&gt;Remember The Milk&lt;/a&gt;, which is an online task and time-management application.&lt;br /&gt;&lt;br /&gt;I have to say I'm impressed - and on several levels.&lt;br /&gt;&lt;br /&gt;First of all it's free - a paid-for 'pro' upgrade is available (US$25 per year) which gives you some extra features such as iPhone and iPod support as well as synchronisation to Blackberry devices etc, but the 'free' version is certainly not lacking.&lt;br /&gt;&lt;br /&gt;Secondly it works very well - you can create tasks easily and categorise them under groups like 'personal', 'work', etc and set appropriate attributes for each task such as due-date and time, repetition, notes and location. You can also assign arbitrary 'tags' to a task, which allow you to group and explore tasks in an abstract manner.&lt;br /&gt;&lt;br /&gt;All of this is very nice, but not particularly new - the likes of Google and Yahoo have offered online calendars and task-lists for quite some time now.&lt;br /&gt;&lt;br /&gt;However, one of the things that sets Remember The Milk apart is the easy integration with other applications and devices. In my case I needed to integrate with the &lt;a target="_blank" href="http://www.mozilla.org/projects/calendar/lightning/"&gt;Lightning&lt;/a&gt; add-on to &lt;a target="_blank" href="http://www.mozillamessaging.com/en-US/thunderbird/"&gt;Thunderbird&lt;/a&gt; which proved almost trivial.&lt;br /&gt;&lt;br /&gt;Task lists within Remember The Milk are &lt;span style="font-style: italic;"&gt;each&lt;/span&gt; supported by an &lt;span style="font-style: italic;"&gt;&lt;a target="_blank" href="http://en.wikipedia.org/wiki/ICal"&gt;iCal&lt;/a&gt;&lt;/span&gt; interface - you can either reference a single interface that contains all your tasks (regardless of category) or you can reference just a single category.&lt;br /&gt;&lt;br /&gt;This means that I can link my email client at work to &lt;span style="font-style: italic;"&gt;just&lt;/span&gt; my 'work' category and my home email-client to &lt;span style="font-style: italic;"&gt;just&lt;/span&gt; my 'personal' category.&lt;br /&gt;&lt;br /&gt;Another thing that sets it apart is the ability to 'share' a task-list with known contacts so more than one person can see &lt;span style="font-style: italic;"&gt;and change&lt;/span&gt; your tasks (consider a PA, for example). You can also 'publish' a list, which gives read-only access to either a set of contacts or the general public.&lt;br /&gt;&lt;br /&gt;The final thing that impressed me about Remember The Milk is the design of its user-interface, which is about as forgiving as possible and a great example of how a web-based user-interface should be implemented.&lt;br /&gt;&lt;br /&gt;For example, when registering for the first time fields get validated as you type, rather than upon final submission of the form. Another example is that dates can be entered in as free-form text and are interpreted sensibly  - this allows dates such as 'tomorrow', 'end of month', '5pm' and so on. All of this follows the &lt;a target="_blank" href="http://designinginterfaces.com/Forgiving_Format"&gt;Forgiving UI Design Pattern&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;A similar principle exists when entering new tasks - you can use a shorthand to specify the date and tag(s) of a task all in one go - for example:&lt;br /&gt;&lt;pre style="font-size: 90%; line-height: 100%;"&gt;&lt;br /&gt;Meet Sally for Lunch ^12pm #personal&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above creates a new task called 'Meet Sally for Lunch' that is scheduled for 12pm today (the '^' prefix) with a tag of 'personal' (the '#' prefix).&lt;br /&gt;&lt;br /&gt;I've only highlighted some of the features here - there's lots more the service can offer (offline support, for instance), but take the time to have a look, if only to see a good example of user-interface design.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-853084136024167715?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/853084136024167715/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/rember-this-milk.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/853084136024167715'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/853084136024167715'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/rember-this-milk.html' title='Rember This Milk'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-1670301846245203724</id><published>2009-09-16T13:48:00.025+01:00</published><updated>2009-09-23T06:51:10.021+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Terminating a Servlet</title><content type='html'>I needed to terminate a Java Servlet recently if a startup condition failed - my initial approach was:&lt;br /&gt;&lt;pre style="font-size:90%;line-height:100%;"&gt;&lt;br /&gt;    System.exit(-1)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However, this terminates the entire servlet-container (Jetty, Tomcat, Glassfish, etc) together with any other servlets that may be running within. What I wanted was to just terminate the servlet in question...&lt;br /&gt;&lt;br /&gt;As usual, the answer is relatively straightforward - you need to throw a servlet exception - at first glace this can be achieved using &lt;a style="font-style: italic;" target="_blank" href="http://java.sun.com/products/servlet/2.3/javadoc/javax/servlet/ServletException.html"&gt;javax.servlet.ServletException&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The servlet-container will catch this exception and gracefully terminate the servlet without affecting any other running servlets.&lt;br /&gt;&lt;br /&gt;However, there is also an &lt;a style="font-style: italic;" target="_blank" href="http://java.sun.com/products/servlet/2.3/javadoc/javax/servlet/UnavailableException.html"&gt;UnavailableException&lt;/a&gt; (a subclass of &lt;span style="font-style: italic;"&gt;ServletException&lt;/span&gt;) that is more appropriate for indicating mis-configuration, etc.  Interestingly, this class can be used to indicate both 'permanent' and 'temporary' unavailability, with permanent unavailability meaning that the servlet cannot recover.&lt;br /&gt;&lt;br /&gt;If an permanent &lt;span style="font-style: italic;"&gt;UnavailableException&lt;/span&gt; is thrown during the &lt;span style="font-style: italic;"&gt;init()&lt;/span&gt; method, the servlet will never enter into service. Alternatively, if the exception is thrown during normal processing, the servlet-container will remove that servlet instance and create a new one in its place.&lt;br /&gt;&lt;br /&gt;If a temporary &lt;span style="font-style: italic;"&gt;UnavailableException&lt;/span&gt; is thrown the servlet-container will generate a &lt;a target="_blank" href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_Server_Error"&gt;HTTP 503 &lt;/a&gt;message, with the &lt;span style="font-style: italic;"&gt;Retry-After&lt;/span&gt; header set to indicate to the client how long the service is likely to be unavailable for. Note that thowing a 'temporary' UnavailableException during the init() method prevents the servlet from entering into service - as with a 'premanent' exception above.&lt;br /&gt;&lt;br /&gt;In my case I wanted to indicate 'permanent' unavailability, which is based on the following snippet:&lt;br /&gt;&lt;pre style="font-size:90%;line-height:100%;"&gt;&lt;br /&gt;  import javax.servlet.*&lt;br /&gt;&lt;br /&gt;  if (true) {&lt;br /&gt;    throw new UnavailableException("Startup condition failed")&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-1670301846245203724?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/1670301846245203724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/terminating-servlet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1670301846245203724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1670301846245203724'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/terminating-servlet.html' title='Terminating a Servlet'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-3778904169908837765</id><published>2009-09-16T13:32:00.007+01:00</published><updated>2009-09-23T06:50:54.046+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Groovy Array of IP Addresses</title><content type='html'>I had a requirement to return all the IP-addresses allocated to a host recently, which proved relatively straightforward once I'd figured out how to cast a &lt;span style="font-style: italic;"&gt;List&lt;/span&gt; object to a &lt;span style="font-style: italic;"&gt;String[]&lt;/span&gt;:&lt;br /&gt;&lt;pre style="font-size:90%;line-height:100%;"&gt;&lt;br /&gt;import import java.net.InetAddress&lt;br /&gt;&lt;br /&gt;String[] ipAddresses(String hostname) {&lt;br /&gt;  List list = []&lt;br /&gt;&lt;br /&gt;  InetAddress.getLocalHost().getAllByName(hostname).each { addr -&gt;&lt;br /&gt;    list &lt;&lt; addr.getHostAddress()&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  list.toArray(new String[list.size()])&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Trying to return a &lt;span style="font-style: italic;"&gt;String[]&lt;/span&gt; gave me a bit of a headache - the problem is that Groovy does not let you dynamically add entries, as you can with a &lt;span style="font-style: italic;"&gt;List&lt;/span&gt; or &lt;span style="font-style: italic;"&gt;Map&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;The solution (as shown above) was to dynamically populate a local &lt;span style="font-style: italic;"&gt;List&lt;/span&gt;, then create a &lt;span style="font-style: italic;"&gt;String[]&lt;/span&gt; of the required size and populate it via &lt;span style="font-style: italic;"&gt;toArray()&lt;/span&gt; to convert [and return] it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-3778904169908837765?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/3778904169908837765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/groovy-array-of-ip-addresses.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/3778904169908837765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/3778904169908837765'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/groovy-array-of-ip-addresses.html' title='Groovy Array of IP Addresses'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-1980972333455628028</id><published>2009-09-15T15:48:00.018+01:00</published><updated>2009-09-23T06:50:39.718+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Creating MD5 Digest in Groovy (or Java)</title><content type='html'>There is a quick-and-dirty way to generate MD5 digests in Groovy (or Java):&lt;br /&gt;&lt;pre style="font-size:90%;line-height:100%;"&gt;&lt;br /&gt;import java.security.MessageDigest&lt;br /&gt;&lt;br /&gt;MessageDigest digest = MessageDigest.getInstance("MD5")&lt;br /&gt;&lt;br /&gt;digest.update("encodeMe".bytes)&lt;br /&gt;&lt;br /&gt;BigInteger big = new BigInteger(1,digest.digest())&lt;br /&gt;String     md5 = big.toString(16).padLeft(32,"0")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above encodes the candidate string "encodeMe" to an MD5 digest.&lt;br /&gt;&lt;br /&gt;If there are any Grails users reading, please note that you can simply use one of the built-in codecs:&lt;br /&gt;&lt;pre style="font-size:90%;line-height:100%;"&gt;&lt;br /&gt; String md5 = "encodeMe".encodeAsMD5()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully this will be if use to someone.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-1980972333455628028?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/1980972333455628028/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/creating-md5-digest-in-groovy.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1980972333455628028'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/1980972333455628028'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/creating-md5-digest-in-groovy.html' title='Creating MD5 Digest in Groovy (or Java)'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-8365164258975793083</id><published>2009-09-15T08:39:00.005+01:00</published><updated>2009-09-23T06:50:20.469+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='internet'/><title type='text'>Visual Browsing</title><content type='html'>No sooner had I &lt;a target="_blank" href="http://dsommerville.blogspot.com/2009/09/browser-related-statistics.html"&gt;published my previous post&lt;/a&gt; on browser trends than I see the &lt;a target="_blank" href="http://news.bbc.co.uk/1/hi/technology/8256054.stm"&gt;BBC reporting&lt;/a&gt; on both &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;Google's&lt;/span&gt; new &lt;a target="_blank" href="http://fastflip.googlelabs.com/"&gt;Fast Flips&lt;/a&gt; approach to browsing the Internet, as well as &lt;a target="_blank" href="http://news.bbc.co.uk/1/hi/technology/8256046.stm"&gt;an article&lt;/a&gt; on the &lt;a target="_blank" href="http://www.bing.com/"&gt;Bing&lt;/a&gt; search-engines 'visual' search capabilities.&lt;br /&gt;&lt;br /&gt;Both of these approaches provide a visual representation of articles, with Fast Flips also categorising the content for easy navigation - this is not dissimilar to the innovative approach seen with the &lt;a target="_blank" href="http://www.cuil.com/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;Cuil&lt;/span&gt;&lt;/a&gt; search-engine which aims to provide a 'magazine-style' presentation of results, complete with thumbnail images. Indeed the whole 'magazine-layout' approach to presenting content appears to be becoming increasingly popular.&lt;br /&gt;&lt;br /&gt;An important quotes from one of the BBC articles is &lt;q style="font-style: italic;"&gt;...consumers can process results with images 20% faster than text only results&lt;/q&gt;. This could explain one of the underlying factors driving browsing habits toward increasingly visual representations - it also ties in with the whole &lt;a target="_blank" href="http://en.wikipedia.org/wiki/Web_2.0"&gt;Web 2.0&lt;/a&gt; concepts of both data-aggregation and rich user-interfaces.&lt;br /&gt;&lt;br /&gt;At the time of writing the &lt;a target="_blank" href="http://www.bing.com/visualsearch"&gt;Bing visual interface&lt;/a&gt; appears to be unavailable, so I cannot comment on specifics, other than to say that the approach sounds both interesting and exciting.&lt;br /&gt;&lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;Google's&lt;/span&gt; Fast Flick service was operating, but currently appears to offer no obvious customisation options that allow registered users to tailor the content and/or layout (please post a comment if you are aware how to do this).&lt;br /&gt;&lt;br /&gt;Consequently the content appears to be biased towards US interests at present. However, when you &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;login&lt;/span&gt; a message appears stating that &lt;q&gt;&lt;em&gt;Fast Flip may use your email address to personalise your experience on their website&lt;/em&gt;&lt;/q&gt;, so presumably you need to start using the service in order for it to start making recommendations etc.&lt;br /&gt;&lt;br /&gt;Having said that, this is still classed as a laboratory project so I'm sure these features will improve over time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-8365164258975793083?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/8365164258975793083/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/visual-browsing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/8365164258975793083'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/8365164258975793083'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/visual-browsing.html' title='Visual Browsing'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-6285618892016158410</id><published>2009-09-14T21:03:00.009+01:00</published><updated>2009-09-23T06:50:05.440+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='internet'/><title type='text'>Browser-Related Statistics</title><content type='html'>A colleague pointed me to the &lt;a target="_blank" href="http://www.w3schools.com/browsers/default.asp"&gt;W3Schools Browser Statistics&lt;/a&gt; page recently, which I was completely unaware of.&lt;br /&gt;&lt;br /&gt;A quick check shows some fascinating insights into past, present and future adoption of browsers and the operating-systems used to manage them. For instance we can see that Internet Explorer is rapidly losing market-share, with just 39.4% as at July this year, compared with 51.7% in 2008 and 57% in 2007.&lt;br /&gt;&lt;br /&gt;Meanwhile both &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;Firefox&lt;/span&gt; and &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;Google's&lt;/span&gt; Chrome browser appear to making steady inroads, increasing their market share by between 5-7% - indeed, it would appear that Chrome is starting to gain real acceptance.&lt;br /&gt;&lt;br /&gt;What did surprise me though, was the operating-system statistics (for those not in the know, browsers typically provide a &lt;a style="font-style: italic;" target="_blank" href="http://en.wikipedia.org/wiki/User_agent"&gt;User-Agent&lt;/a&gt; string as part of each HTTP header when connecting to a web-server - this allows the server to respond in the best possible manner to the client).&lt;br /&gt;&lt;br /&gt;The operating-system statistics appear to show that Linux still has a very small market-share of only 4.3% (July) and this appears to have increased only slightly over recent years. Conversely, it would appear that Mac operating-systems are making good progress and apparently out-pacing Linux.&lt;br /&gt;&lt;br /&gt;Be careful what you read though - I can think of many reasons for this apparent surprise; first it could be spot-on - perhaps Linux &lt;span style="font-style: italic;"&gt;isn't&lt;/span&gt; making as much progress as its advocates would like to think...&lt;br /&gt;&lt;br /&gt;However, it could also be that Linux is still regarded and used primarily as a server operating-system, often providing just a command-prompt for Systems Administrators (consider &lt;a target="_blank" href="http://www.ubuntu.com/products/whatIsubuntu/serveredition"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;Ubunut&lt;/span&gt; Server Edition&lt;/a&gt; for example); as such users will never browse the Internet from a server - a bad practice in any event. &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_3"&gt;Conversely&lt;/span&gt;, both Windows and Mac computers predominately host desktop operating-systems and are &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;pre&lt;/span&gt;-shipped with browsers.&lt;br /&gt;&lt;br /&gt;Another possibility is that Linux browser-implementations set the &lt;span style="font-style: italic;"&gt;User-Agent&lt;/span&gt; string to some other operating-system in order to minimise compatibility issues. This is easily done and many browsers allow advanced users to change this setting so that servers will think the client is using some other browser.&lt;br /&gt;&lt;br /&gt;I will be keeping on eye on these statistics in future - especially the trend towards Chrome. Like many others I tried it, liked it, then went back to the comfort zone (&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;Firefox&lt;/span&gt; in my case).&lt;br /&gt;&lt;br /&gt;There have been some very innovative approaches to browsers and browsing presented over the last year and it'll be interesting to see what dominates. The &lt;a target="_blank" href="http://www.flock.com/"&gt;Flock&lt;/a&gt; browser is also worth a look and both the &lt;a href="http://www.cuil.com/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;Cuil&lt;/span&gt;&lt;/a&gt; and &lt;a target="_blank" href="http://www.bing.com/"&gt;Bing&lt;/a&gt; search-engines are gaining in popularity.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-6285618892016158410?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/6285618892016158410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/browser-related-statistics.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/6285618892016158410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/6285618892016158410'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/browser-related-statistics.html' title='Browser-Related Statistics'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-5840089226998375807</id><published>2009-09-14T13:23:00.006+01:00</published><updated>2009-09-23T06:49:44.171+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><title type='text'>Agile Development - Efficiency Metric</title><content type='html'>I read &lt;a target="_blank" href="http://grantjoung.blogspot.com/2009/07/efficiency-must-have-agile-metric-as.html"&gt;this article&lt;/a&gt; by Grant Joung recently and would suggest that the principles appear sensible and are worth trying.&lt;br /&gt;&lt;br /&gt;We have been using &lt;a target="_blank" href="http://www.amazon.co.uk/dp/0321205685/"&gt;User Stories Applied by Mike Cohn&lt;/a&gt; as a general guide to adopting an Agile approach to software development recently (a great book and starting reference that is easily digestible, by the way).&lt;br /&gt;&lt;br /&gt;At present I am managing a small Agile project that is based on 2-week iterations with weekly progress meetings - so far things have been going well and I have created both Velocity and Burndown charts.&lt;br /&gt;&lt;br /&gt;However, we recently had a public bank-holiday and some members of staff had taken a couple of days off, resulting in an slightly erratic Velocity charts and making it awkward for me to get a good handle on how well the team was performing.&lt;br /&gt;&lt;br /&gt;Grant's suggestion is to create a new metric called Efficiency and then graph it:&lt;br /&gt;&lt;pre&gt; E = V/R&lt;br /&gt;&lt;br /&gt; E = Efficiency for the iteration&lt;br /&gt; V = Velocity for the iteration&lt;br /&gt; R = Total number of person days worked on the team for the Iteration&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;By tracking the number of man-days actually worked in a given iteration against the velocity achieved we were able to produce an Efficiency graph that was noticeably different to the Velocity graph - in our case it actually showed the team's efficiency &lt;span style="font-style: italic;"&gt;decreasing&lt;/span&gt; slightly over time, while the velocity appeared to be gradually &lt;span style="font-style: italic;"&gt;increasing&lt;/span&gt;!&lt;br /&gt;&lt;br /&gt;This is actually an accurate refection of what is happening on our project - we are evaluating a new technology and the developers are getting slightly bogged down by some of the technical issues while at the initial stage of learning.  I would expect our Efficiency chart to stabilise shortly and then start to gradually increase as our developer gain increased expertise with this new technology.&lt;br /&gt;&lt;br /&gt;Also note Grant's suggestion about creating a 'benchmark velocity' to help predict future progress - it is too early for us to say if this works for us just yet, but the principle appears sound.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-5840089226998375807?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://grantjoung.blogspot.com/2009/07/efficiency-must-have-agile-metric-as.html' title='Agile Development - Efficiency Metric'/><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/5840089226998375807/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/agile-development-efficiency-metric.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/5840089226998375807'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/5840089226998375807'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/agile-development-efficiency-metric.html' title='Agile Development - Efficiency Metric'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6778893253459142202.post-7162941022678196427</id><published>2009-09-13T22:54:00.010+01:00</published><updated>2009-09-23T06:49:21.479+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='html'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'>Cascading StyleSheets - Separation of Concerns</title><content type='html'>I have been spending the last few days getting back up to speed on the use of Cascading Style Sheets (CSS).&lt;br /&gt;&lt;br /&gt;This technology is closely related to HTML and essentially controls how a web page is displayed within a browser. Unfortunately CSS has traditionally been the poor relation to HTML, with many developers under-utilising it and mistakenly using HTML to control some (or all) of the rendering of a web page, such as using tables to manage layout, etc.&lt;br /&gt;&lt;br /&gt;HTML and CSS demonstrate one of the most important design considerations of any software development project - "separation of concerns". Each technology has a distinct responsibility; HTML should only be used to structure the content on a web-page, while CSS should only be used to control the rendering of that page, formatting it into something that is visually acceptable and pleasing to the user.&lt;br /&gt;&lt;br /&gt;This ensures that the content/structure of a web-page is independent from its presentation; by using cascading stylesheets the content can be formatted appropriately depending on the device - for example the way a page is displayed on a regular screen and browser (Firefox on a 1024x800 display, for example) can be completely different from the way the page is displayed on small PDA or iPhone screen by a less popular browser.&lt;br /&gt;&lt;br /&gt;By relying entirely on CSS to control the rendering of a web-page, developers can also make their sites 'skinable'. In other words they can completely change the look/feel of a site simply by changing the styleshet referenced by the web-page - the page itself doesn't have a change at all. This is invaluable when faced with demanding clients who may not know what they want until the see it and it offers significant advantages for the maintenance of a site.&lt;br /&gt;&lt;br /&gt;Websites should be designed first and foremost with structure in mind - consideration should be given to the order in which articles on a given page are presented, together with legal and accessibility considerations. Making this content look nice is a "separate concern" whose responsibility lies with cascading style-sheets.&lt;br /&gt;&lt;br /&gt;I will discuss CSS in more depth in future posts, but for now the take-away message is "separation of concerns" - avoid using HTML to control the rendering of a web-page. This means that your HTML pages will look very basic and unappealing when viewed without an attached stylesheet, but this is a good thing - it means your page is accessible, skinnable and maintainable.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6778893253459142202-7162941022678196427?l=dsommerville.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dsommerville.blogspot.com/feeds/7162941022678196427/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dsommerville.blogspot.com/2009/09/cascading-stylesheets-separation-of.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/7162941022678196427'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6778893253459142202/posts/default/7162941022678196427'/><link rel='alternate' type='text/html' href='http://dsommerville.blogspot.com/2009/09/cascading-stylesheets-separation-of.html' title='Cascading StyleSheets - Separation of Concerns'/><author><name>Duncan</name><uri>http://www.blogger.com/profile/01985534974852224635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://1.bp.blogspot.com/_OFgXaBh5BPs/Sq1h4d0MRBI/AAAAAAAAAAU/Qs1sAorCnOw/S220/linkedin.jpg'/></author><thr:total>0</thr:total></entry></feed>
