7 min read

File domain object

The first step, as usual, is to create a domain object to represent a file. We want to store the following information:

  • Name
  • The data of the file
  • A description of the file
  • The file size
  • Who uploaded the file
  • The date the file was created and last modified

Create the File domain class as follows:

package app
class File {
 private static final int TEN_MEG_IN_BYTES = 1024*1024*10
 byte[] data
 String name
 String description
 int size
 String extension
 User user
 Date dateCreated
 Date lastUpdated
 static constraints = {
 data( nullable: false, minSize: 1, maxSize: TEN_MEG_IN_BYTES )
 name( nullable: false, blank: false )
 description( nullable: false, blank: false )
 size( nullable: false )
 extension( nullable: false )
 user( nullable: false )
 }
}

There should be nothing unfamiliar here. You have created a new domain class to represent a file. The file data will be stored in the data property. The other properties of the file are all metadata. Defining the user property creates the association to a user object. The constraints are then defined to make sure that all of the information that is needed for a file has been supplied.

There is one important side effect of setting the maxSize constraint on the data property. GORM will use this value as a hint when generating the database schema for the domain objects. For example, if this value is not specified, the underlying database may end up choosing a data type to store the binary file data that is too small for the size of files that you wish to persist.

FileController

Now, we will need a controller. Let’s name it FileController. Our controller will allow users to perform the following actions:

  • Go to a page that allows users to select a file
  • Submit a file to the server
  • Download the file

Create the FileController groovy class, alongside our existing MessageController, by following the actions shown below:

package app
class FileController {
 def create = {
 return [ file: new File() ]
 }
 def save = {
 }
 def download = {
 }
}

In the create action, we are simply constructing a new file instance that can be used as the backing object when rendering the file-upload form. We will fill in the implementation details of the save and download actions as and when we will need them.

File Upload GSP

The next step is to create a GSP to render the form that allows users to upload a file to the application. Create the file grails-app/views/file/create.gsp and enter the following markup:

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
 <meta http-equiv="Content-Type" content=
 "text/html; charset=UTF-8"/>
 <meta name="layout" content="main"/>
 <title>Post File</title>
</head>
<body>
<g:hasErrors bean="${file}">
 <div class="validationerror">
 <g:renderErrors bean="${file}" as="list"/>
 </div>
</g:hasErrors>
<g:form action="save" method="post" enctype="multipart/form-data" 
class="inputform"> <fieldset> <dl> <dt>Title <span class="requiredfield">required</span></dt> <dd><g:textField name="name" value="${file.name}" size="35" class="largeinput"/></dd> <dt>File <span class="requiredfield">required</span></dt> <dd><input type="file" name="data"/></dd> <dt>File description <span class="requiredfield">required</span></dt> <dd><g:textArea name="description" value="${file .description}" cols="40" rows="10"/></dd> </dl> </fieldset> <g:submitButton name="Save" value="Save"/> | <g:link controller="home">Cancel</g:link> </g:form> </body> </html>

This GSP looks very similar to the create.gsp file for messages. Obviously, it has different fields that correspond to fields on the File domain class. The important difference is that this form tells the browser it will be submitting the file data:

<g:form action="save" method="post" enctype="multipart/form-data">

 

Run the application, go to http://localhost:8080/teamwork/file/create and sign in with the username flancelot and the password password. You should see the window as shown in the following screenshot:

Grails 1.1 Web Application Development

Saving the file

Now that our users can select files to upload, we need to implement the save action so that these files can be persisted and can be viewed by other users.

Grails file upload

Grails provides two methods of handling file upload. We are going to use both of them. The two approaches are:

  • Using data binding
  • Using the Spring MultipartFile interface

Data binding makes receiving the data of the file very simple, but is quite limited if used on its own. There is no way of binding anything other than the data of the file, such as the filename or the size of the file, to our domain object.

By also providing access to the Spring MultipartFile interface, Grails allows us to programmatically access any other information we might want from the file.

The save action

Update the FileController class and implement the save action as follows:

package app
import org.springframework.web.multipart.MultipartFile
class FileController {
 def userService
 def create = {
 return [ file: new File() ]
 }
 def save = {
 def file = new File( params )
 file.user = userService.getAuthenticatedUser()
 MultipartFile f = request.getFile( 'data' )
 file.size = f.getSize() / 1024
 file.extension = extractExtension( f )
 if(file.save()) {
 flash.userMessage = "File [${file.name}] has been uploaded."
 redirect(controller: 'home')
 } else {
 render(view: 'create', model: [file: file])
 }
 }
 def extractExtension( MultipartFile file ) {
 String filename = file.getOriginalFilename()
 return filename.substring(filename.lastIndexOf( "." ) + 1 )
 }
 def download = {
 }
 }

Apart from the implementation of the save action, we have had to import Spring MultipartFile and also inject the userService.

The first highlighted line within the save action performs the binding of request parameters to the File domain object. The usual binding will take place, that is, the name and the description properties of the File object will be populated from the request. In addition, since we have a property on our domain object that is an array of bytes, the contents of the file object in the request will also be bound into our File object.

A quick review of our code shows that we have the following property on theFile class:

byte[] data

Also the create.gsp defines the file input field with the same name:

<dd><input type="file" name="data" /></dd>

Grails is also capable of binding the contents of a file to a String property. In this case, we could just declare the data property in our File class as a String, and Grails would bind the file contents as a String.

The next line of interest occurs when we fetch the MultipartFile off the request by using the getFile method. We simply specify the request parameter that contains the file data and Grails does the rest. With an instance of MultipartFile we can access the file size and the original file name to extract the file extension.

Once we have finished populating our File object, we can call the save method and GORM will manage the persistence of the file object and the file data to the database.

Validation messages

The last thing we need to remember to add is the validation messages that will be displayed if the users don’t enter all the data that is needed to save a file. Add the following to grails-app/i18n/messages.properties:

file.name.blank=You must give the file a name
file.description.blank=The file must have a description
file.data.minSize.notmet=No file has been uploaded
file.data.maxSize.exceeded=The file is too large. The maximum file size is 10MB

LEAVE A REPLY

Please enter your comment!
Please enter your name here