|
|
Want to know more about Packt's Article Network? Interested in contributing your article ideas? Please visit our FAQ for more information. See More As with messages, files are displayed on the home page in the order they are added to the system. Currently all messages and files are displayed on the home page. Over time, our home page is going to become rather large and unwieldy. We need a user's home page to show only the files and messages that they are interested in. To do this, users need to be able to tag their content. The new Grails concepts that will be introduced in this two-part article series by Jon Dickinson are:
See More |
File Sharing in Grails
File domain objectThe first step, as usual, is to create a domain object to represent a file. We want to store the following information:
Create the File domain class as follows: package app 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. FileControllerNow, we will need a controller. Let's name it FileController. Our controller will allow users to perform the following actions:
Create the FileController groovy class, alongside our existing MessageController, by following the actions shown below: package app 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 GSPThe 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" %> 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:
Saving the fileNow 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 uploadGrails provides two methods of handling file upload. We are going to use both of them. The two approaches are:
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 actionUpdate the FileController class and implement the save action as follows: package app 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 messagesThe 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 Grails 1.1 Web Application Development
Viewing filesThe next step is to show the uploaded files to the other team members using the application. We will display files on the home page next to messages. So we must:
The HomeController changes to: package app A list of files is retrieved in exactly the same way as a list of messages. We then need to add the files to the model that is to be rendered. In order to render the files, the home page GSP at grails-app/views/home/index.gsp must be updated. Add the following code after the message div: <div class="panel"> The code above should look almost the same as the message list. The main difference is that the file name is wrapped by a link, which will allow a user to download the file by calling the download action on the file controller with the id of the current file. The implementation of the download action will follow shortly. Add the following message to the message bundle (grails-app/i18n/messages.properties): file.size={0} KBBefore running the application we also need to add a link to the home page that will allow our users to create new files. We can't have them enter the URL into the address bar all the time, can we? Update the newactions div on the grails-app/home/index.gsp file to be: <div class="newactions"> Now, run the application again, add a new file, and you should see a window that looks like the following screenshot:
Modeling for efficiencyThe implementation we have for file upload works fine and is easy to understand, but it is also quite naive in terms of efficiency. The problem is that every time a user views the information about a file, all the binary data for the file will be loaded into the memory. By altering our domain model slightly, we can easily improve the performance. We will need a domain class to represent the data as a one-to-one relationship with the File object. The relationships between domain objects are lazy by default. This means that, by extracting the data out into its own object, retrieving a list of files will not load the binary data of the file, unless we request it specifically. Create a new domain class named FileData in the same package as the File domain class: package app The FileData class is defined as belonging to File because it is not possible for an instance of FileData to exist without an instance of File. By specifying that FileData belongs to File, we are saying that all persistence operations on file will cascade to FileData automatically. This means that when a file is saved the data object is saved. Similarly, when a file is deleted the data object will be deleted. The File domain class must also be updated to replace the old byte[] data property with a reference to the new FileData class: package app We now need to change the save action on FileController and the file input field on the create.gsp file. The save action must be changed in the following manner: def save = {While the file input element for create.gsp changes to: <dd><input type="file" name="fileData.data"/></dd> It is necessary to change the name of the input field to allow automatic binding of the submitted data to a new file instance. Supplying data on the request using dot notation allows Grails to bind request data into nested objects on the domain classes. For example, the File class has a property called fileData, which is a FileData instance. FileData has a property called data, which is an array of byte. By giving the file input field a name of fileData.data, we can be sure that the contents of the submitted file will bound to the data property of the object, which is assigned to the fileData property of our new File instance. All of this is handled through Grails data binding: def file = new File( params ) Downloading filesThe final piece of the puzzle is to allow users to download files that their teammates have posted on the site. We already have the link on the home page: <a href="${g.createLink(controller:'file', action:'download', id:file.id)}">The File class needs to be given a downloadName property, which can be used for the filename when downloading. This is simply derived from the name given to the file and the file extension: def getDownloadName() {The download action already exists in the FileController; we just need to implement it, which is surprisingly simple: def download = {The first line uses the get method on File. This method is dynamically injected by the GORM to retrieve the instance of the class from the database with the specified ID. The next two lines set the necessary headers on the HttpServletResponse to tell the client that we are downloading a file with a given name. Finally, we create a ByteArrayInputStream from the data of our file and copy the file data to the response using the GDKs overloaded left shift operator (<<) on OutputStream. As of Grails 1.1, it is possible to fetch instances of domain classes in read-only state by using the read method that is dynamically added to each domain class. This new operation would be appropriate for our download action as there should be no need to modify the file while downloading. If we used the read method instead of the get method, the first line of our download action would look like: def file = File.read(params.id). If we were implementing file download in Java, we would need to either programmatically copy the data from our ByteArrayInputStream to the response OutputStream or find a utility class to do this for us. Start up the application again and check that files can be downloaded now. SummaryWe have added support for file sharing to the teamwork application. This was incredibly easy due to the support that is built-in to Grails for handling file upload. We then used our knowledge of the default GORM behavior for the relationships to enable lazy loading of file binary data to reduce the memory footprint of the application. Grails 1.1 Web Application Development
About the AuthorJon Dickinson Jon Dickinson is the principal consultant and founder of Accolade Consulting Ltd. (http://www.accolade-consulting.co.uk) and can be contacted at jon@accolade-consulting.co.uk. He specializes in developing web applications on the Java platform to meet the goals of users in the simplest and least obtrusive way possible. Books from Packt
|
BROWSE
All Titles WordPress Web Services SOA BPEL Web Graphics & Video Web Development RAW Portugues, Espanol, Italiano, French PHP/MySQL Oracle Open Source Networking & Telephony Moodle Microsoft & .NET Linux Servers jQuery Joomla! JBoss Java e-Learning e-Commerce Dynamics Drupal CRM Cookbook Content Management Beginner Guides Architecture and Analysis AJAX Future Titles Recently Published Titles |
| ||||||||