Tropical Software Observations

Showing posts with label grails. Show all posts
Showing posts with label grails. Show all posts
19 April 2011

Posted by Anonymous

at 2:10 PM

1 comments

Labels:

Managing multiple Grails versions in development

In Grails development, it's not uncommon to maintain several projects with different Grails versions.

It's a PITA to switch between Grails versions during development because it requires the GRAILS_HOME environment variable to be updated, to point to the correct Grails directory.

Rescue My Ass
I added 2 new bash commands to make my life easy:

  • grls - list all available installed versions.
  • gr - set GRAILS_HOME to the specified version.

How to Use It
Beech-Forkers-MacBook:~ huiming$ grls
1.1.2
1.3.2
1.3.4
1.3.6

Beech-Forkers-MacBook:~ huiming$ gr 1.3.2
Beech-Forkers-MacBook:~ huiming$ grails
Welcome to Grails 1.3.2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /Users/huiming/work/tools/grails


How I Implemented It
First, I keep all Grails installations under a same directory:

Beech-Forkers-MacBook:~ huiming$ ls -d1 work/tools/grails-*
work/tools/grails-1.1.2
work/tools/grails-1.3.2
work/tools/grails-1.3.4
work/tools/grails-1.3.6

Then, add the commands into my ~/.profile file:

TOOLS=~/work/tools

function gr {
rm -f $TOOLS/grails && \
ln -s $TOOLS/grails-$1 $TOOLS/grails
}

alias grls="ls -d $TOOLS/grails-* | xargs basename | sed 's/grails-//g'"

export GRAILS_HOME=$TOOLS/grails
PATH=$GRAILS_HOME/bin:$PATH

Thanks to Jeff, as the idea is largely based on his solution.

30 August 2010

Posted by Unknown

at 4:20 PM

0 comments

Labels: , ,

RabbitMQ: PubSub Messaging with Groovy

RabbitMQ typically send "direct" message between the queue and the listeners -- if you have 5 messages put on the queue with 3 listeners, then each of the listener will get one of these messages. However if you need to have all the listeners get the same message, you need to set the exchange to "fanout".

The explanation of using rabbitmq's fanout feature may be well described in the FAQ, but it's not clear in code. So I hope someone will find the below groovy code helpful to see how to use rabbitmq fanout for pubsub topology messaging.

The below code is a basic message publisher to send message to rabbitmq queue. Note the exchange declared as "fanout" exchange. The queue binding may not be useful in fanout use case as all the messages will be send out to all listening consumers.



import com.rabbitmq.client.*


def factory = new ConnectionFactory();
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost("/");
factory.setHost(rabbitmqhost)
factory.setPort(rabbitmqport)
Connection conn = factory.newConnection();
def channel = conn.createChannel()


String exchangeName = "foExchange"
String routingKey = "foo"
String queueName = "fooQueue"


channel.exchangeDeclare(exchangeName, "fanout", true)
channel.queueDeclare(queueName, true, false, false, null);


(1..3).each { n ->
    channel.basicPublish(exchangeName, routingKey,  MessageProperties.PERSISTENT_TEXT_PLAIN, 
                        " (${n}) test testing test [${new Date()}]".getBytes() );
}


channel.close()
conn.close()
System.exit(0)
println " Fin "




Now lets look at the consumer side to consume messages in fanout use case.In a fanout setup, the consumer doesn't have to do anything special. If you don't specify any particular queue name, it will get all messages from the exchange. You can however continue to received message from any particular queue if you bind to it.





String exchangeName = "fooExchange"
String routingKey = "foo"
//String queueName = "fooQueue"


String queueName = channel.queueDeclare().getQueue()
channel.queueBind(queueName, exchangeName, "#");
println " Queue: ${queueName} "


boolean noAck = false;
def consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, noAck, consumer);
boolean running = true


while(running) {


    QueueingConsumer.Delivery delivery;
    try {
        delivery = consumer.nextDelivery();
        println new String(delivery.body)
    } catch (InterruptedException ie) {
        continue;
    }
    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    
}


27 July 2010

Posted by Unknown

at 2:05 PM

2 comments

Labels: , ,

Spatial Search with Grails and Solr Plugin

Grails has a good Solr plugin (http://www.grails.org/plugin/solr) that works well and also provides spatial search that works out of the box. However, the documentation to use this feature is a bit skimpy so here are the basic steps to enable spatial search with Solr for your Grails app.

Install the solr plugin for Grails using 'grails install-plugin solr' and enable Solr indexing for your domain class by:

    static enableSolrSearch = true
    static solrAutoIndex = true

Next, we need to add a Solr index field annotation to the domain members.


    //import org.grails.solr.Solr
    @Solr(field="latitude_rad_d")
    Double latitude

    @Solr(field="longitude_rad_d")
    Double longitude


Once we have those in place, we can use SolrService to query:

    getSpatialQuery(query, lat, lng, range)

08 April 2010

Posted by Unknown

at 3:13 PM

2 comments

Labels: ,

Using Google FriendConnect for Authentication

The idea of this article is how to let user sign in to your site by using Google Friend Connect. This is a summarize steps to integrate the JS API to a Grails app and based on instruction for "Give users an option of logging in with Friend Connect".




1. Register your site with Google Friend Connect . You must register your site with Google Friend Connect to get your site ID. We will need the site ID to use with the Friend Connect API.


2. Include Friend Connect library
Refer to this page for the full outlines of the Javascript API.
  google.load('friendconnect', '0.8');

4. Render Login Button.
google.friendconnect.renderSignInButton({ 'id': 'target-id', 'text' : 'Click here to join ', 'style': 'standard' });

5. Using OpenSocialApi to detect if user already login.

google.friendconnect.container.loadOpenSocialApi({
  site: 'XXXXXXXXXXXXXXXXXXXX',
  onload: function() {
    if (!window.timesloaded) {
      window.timesloaded = 1;
    } else {
      window.timesloaded++;
    }
    if (window.timesloaded > 1) {
      // no need to handle this for now
      //window.top.location.href = "/authenticate.html";
    }
  }
});
6. Get user ID.
To obtain the login user, first initiate a data request for "VIEWER".
   var req = opensocial.newDataRequest();
    req.add(req.newFetchPersonRequest("VIEWER"), "viewer_data");
    req.send(onData);
The request will run the callback "onData" when it receive data back from the server.
Next we need to handle the callback from the request for the "VIEWER" information.
 
   function onData(data) {
      if (!data.get("viewer_data").hadError()) {
         var owner_data = data.get("viewer_data").getData();
         var name = owner_data.getDisplayName();
         var id = owner_data.getId();
         window.location.href = "/auth/checkfriendconnect?id=" + id;
       }
    }
The owner ID is unique to the person and will be used for us to tied in to the user in our system.

The next line, "window.location.href = "/auth/checkfriendconnect?id=" + id;" we are actually redirecting the browser to another page for the Friend Connect ID check by using the ID as a params.


7. Back in the server, query the user for Friend Connect ID with the ID specified and perform the usual login processing once the ID is matched to a user.

19 January 2010

Posted by Unknown

at 3:03 PM

0 comments

Labels: , , ,

GORM query by association (query properties of child object)

Let's say we have a Book object and it has an associated Author object. To query Books based on certain properties of the Author we have 2 common ways to it.

1. Using HQL query

def results = Book.findAll(" from Book as b where :title = b.author.name) ", [title:"Mr Author"])

2. Using criteria with createAlias()

def results = Book.withCriteria {
createAlias("author", "a")
eq("a.name", "Mr Author")
}

I hope this helps some of you out there. For some reason "createAlias" is not well documented in the Grails documentation, unless you cross reference the Hibernate docs.

28 April 2009

Posted by Bacchus D

at 4:57 PM

0 comments

Labels: , ,

Grails and PartyTime Training in China


We've just returned from a trip to Guangzhou where we spent two days working with our partners, Well United, on Groovy, Grails, and PartyTime. It turned out to be a larger group than expected but the more the merrier!

It was a lot to cover in two days but even though the time allotted for the training session was rather short, it went very well and the Well United team picked everything up quickly.  The C#, PHP, and Java developers seemed enthusiastic about Grails and we hope to be working together on projects sometime soon.

Many thanks to our gracious hosts in Guangzhou and Hong Kong. We enjoyed the Macanese and Cantonese food and hope to be back soon!


20 March 2009

Posted by Unknown

at 9:40 AM

0 comments

Labels: ,

Grails and Rails: Domain Classes and Web Services

Let's take a brief look at the differences between Grails and Rails in term of persisting domain classes and creating web services. Both Grails and Rails are MVC frameworks and are very similar in terms of code syntax.

1. Domain Class Persistence

In Grails:



class Person {
String firstName
String lastName
String email
Date birthDate
Address address

static constraint = {
firstName(blank:false)
lastName(blank:false)
email(blank:false, email:true) // check if it's really e-mail
}
}

class Address {
String street
String city
String country
Person person

static belongsTo = Person
}



In Rails, attributes can be inferred from the database table's structure:

class Person <>
# Expects database schema to have a table "person"
# with columns first_name, last_name, email, birth_date

validates_presence_of :first_name, :last_name, :email
end

class Address <> has_one :address
belongs_to :person
end


2. Making a service class available as a web service

In Grails, inside a controller closure:


def listAllPeople = {
def pplList = Person.list() // or by query Person.findByLastName("Hewitts")
render pplList as XML
// render pplList as JSON
}



In Rails, inside a controller block:


def listAllPeople
@pplList = Person.all # or by query Person.find_by_last_name("Hewitts")
render :xml => @pplList
# render :json => pplList
end


Note:
1. Grails will auto-generate the database schema and update the schema when more domain fields are added to the class. It's also possible to go in the reverse direction if you prefer starting with an existing schema.
2. Rails expects the database schema to be pre-created and to conform to camel case naming conventions.
3. Grails has the notion of service classes which are commonly used for business logic and can be use in a controller as a web service end point.

30 January 2009

Posted by Unknown

at 3:41 PM

1 comments

Labels: ,

Injecting Grails Service class via Spring

A quick note on how to inject Service objects -- or for that matter any object -- via Spring.

Grails has always been able to support the injection of objects via Spring whether using XML or Spring DSL. A good scenario of using Spring DSL is to swap implementations easily.

In our project, Party Time, we use a lot of services. Most of these services implement Java interfaces and may be changed in the future. To be flexible in the changing of these service implementation, we auto-wire the services via Spring DSL.

For example, we have a Service class, LocalCDNService that implements the ICDNProvider interface. We configure the implementation by specifying in grails-app/conf/spring/resources.groovy:


beans = {
cdnProvider(LocalCDNService)
}


And from then onwards we can refer to the service object within our Grails controllers, taglibs, or services by simply declaring:


class ExampleController {
def cdnProvider
...
}


Contrast this to the common way of autowiring services by declaring:


class ExampleController {
def localCdnService
}


If we were to change CDN provider from LocalCDNService to AWSCDNService, we would probably need to change many classes. However if you were to use the Spring DSL way, you just need to change grails-app/conf/spring/resources.groovy:


beans = {
cdnProvider(AWSCDNService)
}


Hopefully this will shed more light to those new to using Spring within Grails.

09 January 2009

Posted by Unknown

at 5:31 PM

5 comments

Labels: ,

Testing Grails Service class

Testing Grails service classes is easy if the service does not call any domain persistence methods. Meaning if your service class does cause some domain object to persist to database, it's going to be painful to try to mock the calls and check the result. And I'm not even sure if it's worth it to try.

The problem is that grails-testing newbies like me will want to try to do unit testing for services. In short, don't do that unless your service classes are simple and easy. (Keep this in mind when you start your project -- you won't see this unless you've experienced the pain before).

The best way that I found so far is to make service tests into integration tests. This mean putting service tests into the "integration" folder under the test directory. For more explanation on the difference between Unit Testing and Integration Testing in Grails, go here.

Testing Service classes is easy once you understand that it is:

  • it is an integration test -- will talk to the database
  • it is slow -- probably because of the data connection overhead
  • will persist data to database -- make sure your test environment database settings don't point to a production database
A typical service test looks like this:
class FooServiceTests extends GroovyTestCase {

def fooService

void setUp() {
fooService = new FooService // *1
fooService.otherService = new OtherService() // *2
}


void testServiceCall() {
def user = new User(name:"Test", email:"test@name.com")
user.save(flush:true) // *3
fooService.doSomething(user) // *4
assertEquals user.result, "expected result"
}

}
A couple of helpful points as indicated by the numbers above:
  1. You have to initialize the service class yourself, there is no DI.
  2. If the service has other service classes inside it, initialize and assign it manually
  3. Remember to always flush to persist to the database immediately -- else you might get weird things like no "id" for the supposedly saved domain object.
  4. In integration mode, fooService will act like a "headless" Grails app

I hope this is helpful to you -- I may not be right, so feedback and corrections are welcomed.

18 December 2008

Posted by Unknown

at 10:56 AM

3 comments

Labels:

Grails 1.0.4 "no Session" Error

In Grails 1.0.4, there are a few significant changes to how GORM returns Model objects from queries or model traversal.

If you're getting "Could not initialize proxy - no Session" exceptions when trying to access a domain object's collection of child objects, check that you aren't storing the domain object in a HTTP session, as when model objects are stored in the session, they're detached. Prior to version 1.0.3, the detached object had enough information as it was "eagerly fetched". With 1.0.4, this is no longer the case as it's just a proxied object.

To prevent this problem, before you access the object's child collection do the following:

def user = session.user
user.refresh() // Either refresh the object to attach it back to the session
user = User.get( user.id ) // Or retrieve back the object with its id
user.profiles[0] // Then doing stuff like this won't throw the error anymore

For more discussion and context, read this message thread.

02 December 2008

Posted by Irregular Zero

at 11:10 AM

0 comments

Labels:

Clickpass plugin for Grails, Part 2

Go to Clickpass and create an account. Once in the account, go to the "My Details" tab and check the "Show software developer tools" under "Additional Preferences". There should now be a "developer" tab present, select it and push the "Create new site" button.

1. Register site

  • Type in the site name.
  • URL: http://localhost:8080/makedo. This is for running a grails app on your own machine named makedo.
  • Description and logo are optional.

2. Log users in
  • OpenID trust root: http://localhost:8080.
  • begin_openid_login: http://localhost:8080/makedo/openid/login?success_controller=
    clickpass&success_action=welcome&error_controller=&error_action=
    . This link is where Clickpass will access, providing the OpenID details, after one has passed the bar that should be in the app.
  • Leave the OpenID parameter label alone.
  • Submission method: POST.
  • SSL is optional.
  • Once you have saved the changes, the page will change and display a choice of clickpass bars and the html code for it down below. Copy the html code into _clickpass.gsp.

3. Merge accounts
  • begin_add_openid_to_user URL: http://localhost:8080/makedo/clickpass/merge. This is after the authentication and the check whether the OpenID belongs to any user on the database, bringing him to the registration form. If the user selects to login a pre-existing account, the username and password he provides will be sent to this link. If successful the OpenID will be associated with that account.
  • user_id label: Username. This is for the login page Clickpass will provide before accessing the URL above. Merely cosmetic.
  • Adding the connect bar is optional.

4. Register new signups
  • process_openid_registration_url: http://localhost:8080/makedo/clickpass/register. This is after the authentication and the check whether the OpenID belongs to any user on the database, bringing him to the registration form. The user provides information to Clickpass which will be passed along to the link. If successful a new user account with the associated OpenID will be created and saved in your app.
  • new_openid_completion_url: http://localhost:8080/makedo/clickpass/welcome. This is for when authentication is successful and all other things have been taken cared of, e.g. registration or merging.
  • The others are optional except for the fields. Changes in the fields may require changes in the register method of the Clickpass controller.
  • The URL for the registration form should be left for last as it changes due to the fields. Once you're satified and have saved the changes, you can copy and paste the string into urlString in the welcome method of the Clickpass controller.
Aftermath
Once the settings are done, you can use Clickpass.

Clickpass plugin for Grails, Part 1

Clickpass works by handling the OpenID authentication and sending it back to the site. This means that any Grails app using this plugin must also install the OpenID plugin. Also the site will need to be registered with Clickpass and some settings need to be set online. When the authenticated details are sent back, the user is logged in and sent to the welcome page, sent to a form for details to complete registration or asked to login to attach the OpenID to an existing user account.

  • grails create-plugin clickpass.
  • Add ClickpassController.groovy to grails-app/controller.
  • Add User.groovy to grails-app/domain. This is a dummy file required for the plugin to be packaged.
  • Add ClickpassTagLib.groovy to grails-app/taglib.
  • Add _clickpass.gsp to grails-app/views.
  • Add welcome.gsp to grails-app/views/clickpass.

ClickpassController.groovy
class ClickpassController {
def openidService

def index = { response.sendError(404) }

def welcome = {
def user = null

if (openidService.isLoggedIn(session)) {
user = User.findByOpenid(openidService.getIdentifier(session))
if (user == null) {
def urlString = "http://www.clickpass.com/process_new_openid?
site_key=Xr2zelexGn&process_openid_registration_url=
http%3A%2F%2Flocalhost%3A8080%2Fsocialr%2Fclickpass%2F
register&new_openid_completion_url=http%3A%2F%2F
localhost%3A8080%2Fsocialr%2Fclickpass%2Fwelcome&
requested_fields=nickname%2Cemail&required_fields=&
nickname_label=Nickname&email_label=Email"
redirect(url:urlString)
}
}
}

def merge = {
def user = User.findByUsernameAndPassword(params.user_id, params.password)
def urlredirect =
java.net.URLDecoder.decode(params.clickpass_merge_callback_url) +
"&userid_authenticated=false"

if (user == null) {
redirect(url:urlredirect)
}

user.openid = openidService.getIdentifier(session)

if (user.save()) {
redirect(controller:'clickpass',action:'welcome')
}
else {
redirect(url:urlredirect)
}
}

def register = {
def user = new User()
user.username = params.nickname
user.email = params.email
user.openid = openidService.getIdentifier(session)
user.password = ''

if (user.save()) {
flash.message = "Thank you for signing up."
redirect(controller:'clickpass', action:'welcome')
}
else {
flash.message = "There is a problem signing up."
redirect(controller:'clickpass', action:'index')
}
}
}
  • index: Nothing for the index to do so set up a 404.
  • welcome: When a user is authenticated by OpenID, a search is made to see whether that OpenID is in the database, if not then redirect to the registration page. The urlString is obtained from Clickpass so this needs to be changed for each site
  • merge: A user, authenticated by OpenID, who wants to attach the ID to his account.
  • register: Create a new user account with the OpenID and save it.

User.groovy
Nothing in it except for the class declaration.

ClickpassTagLib.groovy
class ClickpassTagLib {
static namespace = "clickpass"

def bar = { attrs, body ->
try {
out << render(template:"/clickpass", contextPath:"${pluginContextPath}")
}
catch(Exception e) {
log.error(e)
}
}
}
  • The namespace allows one to use it, i.e <clickpass:foobar/>, in the code instead of the generic <g:foobar/>
  • For usage in an app, all that is required is <clickpass:bar/> to be inserted into the html.
  • The / in the template name is necessary to revert the path to the project base, otherwise it would go looking in the main app views folder.
  • The contextPath:"${pluginContextPath}" is not mentioned in the documention but after setting the path to the base, this sets it to the correct plugin views folder.

_clickpass.gsp
The html for this is generated by Clickpass, after registering your site, you can just copy and paste. This creates the Clickpass login bar.

welcome.gsp
<html>
<head>
<link rel="openid.server" href="http://www.clickpass.com/openid_server" />
<link rel="openid.delegate" href="http://clickpass.com/public/iseec" />
<title>Welcome</title>
</head>
<body>
<h1>Welcome</h1>
<openid:identifier />
<openid:ifLoggedIn>
<openid:logoutLink>Logout</openid:logoutLink>
</openid:ifLoggedIn>
</body>
</html>
  • This is a simple welcome page after the user successfully logs in and his account is verified.
  • One can replace it with their own or redirect from this page.
  • The OpenID is displayed as well as a logout link.

Aftermath
  • grails package-plugin to get it all into a zip file.
  • grails install-plugin /path/to/plugin/grails-example-0.1.zip to install it into an app.

28 May 2008

Posted by Irregular Zero

at 12:41 PM

1 comments

Labels: ,

Flex File Upload using Grails Backend

This file upload application is based off the one by Nocturnal at Coding Cowboys, a Flex client with a PHP backend. The client has been stripped down to its core functions and PHP replaced with a Grails backend.

The Grails backend is a file controller with an action defined:

def upload = {
if(request.method == 'POST') {
Iterator itr = request.getFileNames();

while(itr.hasNext()) {
MultipartFile file = request.getFile(itr.next());
File destination = new File(file.getOriginalFilename())

if (!file.isEmpty()) {
file.transferTo(destination)
// success
}
else
{
// failure
}
}

// Trigger an Event.COMPLETE event, notifying the Flex client
response.sendError(200,'Done');
}
}

This is capable of uploading multiple files at once and can be tested using a view gsp:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Test Upload</title>
</head>
<body>
<div class="body">
<h1>Upload File</h1>
<g:form method="post" action="upload" enctype="multipart/form-data">
<input type="file" name="file_1"/><br/>
<input type="file" name="file_2"/><br/>
<input type="file" name="file_3"/><br/>
<input type="file" name="file_4"/><br/>
<input type="file" name="file_5"/><br/>
<input type="submit"/>
</g:form>
</div>
</body>
</html>

The Flex client has been pared down to 3 main functions, adding files to the upload list, removing them from the list and finally uploading the files to the server.

First the imports, global variables and initializer:
import mx.controls.*;
import mx.managers.*;
import mx.events.*;
import flash.events.*;
import flash.net.*;

// Convention: Underscore to differentiate global and local variables
private var _refAddFiles:FileReferenceList;
private var _refUploadFile:FileReference;
private var _arrUploadFiles:Array;
private var _numCurrentUpload:Number;

private function initApp():void {
_refAddFiles = new FileReferenceList();
_refAddFiles.addEventListener(Event.SELECT, onSelectFile);
_arrUploadFiles = new Array();
_numCurrentUpload = 0;
}

The add-files function opens the browse window and enable the selection of one or more files to add to the upload list:
// Called to add file(s) for upload
// The fileList property is populated anew each time browse() is called on that FileReferenceList object.
private function addFiles():void {
_refAddFiles.browse();
}

// Called when file(s) are selected
private function onSelectFile(event:Event):void {
// Add files to _arrUploadFiles
if (_refAddFiles.fileList.length >= 1) {
for (var k:Number = 0; k < _refAddFiles.fileList.length; k++) {
_arrUploadFiles.push({
name:_refAddFiles.fileList[k].name,
size:formatFileSize(_refAddFiles.fileList[k].size),
file:_refAddFiles.fileList[k]});
}
listFiles.dataProvider = _arrUploadFiles;
listFiles.selectedIndex = _arrUploadFiles.length - 1;
}
}

The remove-files function:
private function removeFiles():void {
var arrSelected:Array = listFiles.selectedIndices;
if (arrSelected.length >= 1) {
// Null selected files
for (var i:Number = 0; i < arrSelected.length; i++) {
_arrUploadFiles[Number(arrSelected[i])] = null;
}

// Remove the null entries
for (var j:Number = 0; j < _arrUploadFiles.length; j++) {
if (_arrUploadFiles[j] == null) {
_arrUploadFiles.splice(j, 1);
j--;
}
}
listFiles.dataProvider = _arrUploadFiles;
listFiles.selectedIndex = 0;
}
}
The upload function is where the point of integration with Grails is, in the request calling up the controller. Although the Grails backend can handle multiple files, Flex can only initiate the upload one file at a time. The event listener and the return response in the Grails controller allow the upload function to loop:
private function uploadFiles():void {
if (_arrUploadFiles.length > 0) {
listFiles.selectedIndex = _numCurrentUpload;

var request:URLRequest = new URLRequest();
request.data = sendVars;
request.url = "http://localhost:8080/App/file/upload";
request.method = URLRequestMethod.POST;
_refUploadFile = new FileReference();
_refUploadFile = listFiles.selectedItem.file;
_refUploadFile.addEventListener(Event.COMPLETE, onUploadComplete);
_refUploadFile.upload(request, "file", false);
}
}

private function onUploadComplete(event:Event):void {
_numCurrentUpload++;
if (_numCurrentUpload < _arrUploadFiles.length) {
uploadFiles();
} else {
_numCurrentUpload = 0;
_arrUploadFiles = new Array();
listFiles.dataProvider = _arrUploadFiles;
listFiles.selectedIndex = 0;
Alert.show("Files have been uploaded", "Upload Complete");
}
}
And lastly the UI:
<mx:Panel title="Files Upload" width="100%" height="100%">
<mx:DataGrid id="listFiles" allowMultipleSelection="true" verticalScrollPolicy="on"
draggableColumns="false" resizableColumns="false" sortableColumns="false" width="100%" height="100%">
<mx:columns>
<mx:DataGridColumn headerText="File" dataField="name" wordWrap="true"/>
</mx:columns>
</mx:DataGrid>

<mx:ControlBar>
<mx:Button label="Add" click="addFiles()"/>
<mx:Button label="Remove" click="removeFiles()"/>
<mx:Button label="Upload" click="uploadFiles()"/>
</mx:ControlBar>
</mx:Panel>
The Flex mxml/swf files must be moved to the web-app folder of the Grails project and the embedded Jetty activated by 'grails run-app' in order to run this application.

The files will be uploaded to the project directory and thus are not accessible by the webapp. The line to amend is in the Grails controller:
File destination = new File("web-app/" + file.getOriginalFilename())
Note this only works while the project is in development mode. For war files, the base directory would depend on where the server is started, usually in the server home directory. So for a standalone Jetty server, the path to add would be "webapps/App/".

Using the war file in a Tomcat server has been problematic. Only one file can be successfully uploaded to the server, there is no looping.

Links:
Coding Cowboys: Flex upload component

Flex Client:
http://livedocs.adobe.com/flex/3/langref/flash/net/URLRequest.html
http://livedocs.adobe.com/flex/3/langref/flash/net/FileReference.html
http://livedocs.adobe.com/flex/3/langref/flash/net/FileReferenceList.html
http://livedocs.adobe.com/flex/3/html/help.html?content=17_Networking_and_communications_7.html

Grails Backend:
http://www.grails.org/Controllers+-+File+Uploads
http://docs.codehaus.org/display/GRAILS/File+Upload