Raging Goblin

5 May 2013

Spring Roo 8: Uploading images 1 (store images as blob in database)

Here is some hard gained knowledge. How to provide the possibility to upload (and view) images to a Spring Roo website. It took me quite a while to understand how to do this and I will describe 2 ways of doing it. In this post I will show a method with storing the image as a byte-array in the database, in the next one I will describe how to store the images on disk. Each method has it’s own benefits so take your pick. The reference is our logbook application introduced in previous posts. For some reason I never have been able to get this working with MySQL, it will generate a tinyblob field which is not capable of storing my images, and using @Lob on the field causes a java.lang.AbstractMethodError: org.apache.commons.dbcp.DelegatingPreparedStatement.setBinaryStream(ILjava/io/InputStream;J). I don’t know how to solve this, so we switch to PostgreSQL. This might be a better choice anyway as I am not a particular fan of dinosaurs like Larry Ellison or Steve Ballmer. Now take care that PostgreSQL is a completely different beast than MySQL. Try to setup your database properly with the proper owner and access rights on the server and the database itself before you continue. I will not tell you how to do that as there is plenty of stuff about this on the internet and I don’t want to deprive you of a fine ‘cursing and banging your head against a wall’ adventure.

Edit 17-05-2013: See comments below, update commons-dbcp to version 1.4 and you will do fine with MySQL as well.

To start with the most simple method, here are the steps needed to store the images as blobs in the database:

Step 1: Add maven dependencies
To handle uploads Spring relies on 2 dependencies: commons-fileupload.jar and commons-io.jar. The first one is already in the pom, so only the latter should be added:

<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.4</version>
</dependency>

If you use the STS, right click on your project and update the maven dependencies.

Step 2: Add properties to the ‘LogItem’

private byte[] image;
private String contentType;

Take care that the Roo shell is running, you will notice that Roo updates the views and the ApplicationConversionFactoryBean as needed.

Step 3: Update create.jspx to send a MultipartFile
The view for creating a LogItem (~/src/main/webapp/WEB-INF/views/logitems/create.jspx) should be manually adjusted to provide an upload field:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <jsp:output omit-xml-declaration="yes"/>
    <form:create id="fc_raging_goblin_roo_domain_LogItem" modelAttribute="logItem" multipart="true" path="/logitems" render="${empty dependencies}" z="user-managed">
        <field:input field="message" id="c_raging_goblin_roo_domain_LogItem_message" z="apnKidZldiFISmA4Q9pumki/o6Q="/>
        <field:datetime dateTimePattern="${logItem_creationdate_date_format}" field="creationDate" id="c_raging_goblin_roo_domain_LogItem_creationDate" z="p5GCO6w9q76CIDoEn17xbZPqlT0="/>
        <field:select field="logUser" id="c_raging_goblin_roo_domain_LogItem_logUser" itemValue="id" items="${logusers}" path="/logusers" z="RJfGPnxQnmkMqPwvKANgyfbJA6g="/>
        <field:input field="image" id="c_raging_goblin_roo_domain_LogItem_image" type="file" z="user-managed"/>
        <field:input field="contentType" id="c_raging_goblin_roo_domain_LogItem_contentType" z="user-managed" render="false"/>
    </form:create>
    <form:dependency dependencies="${dependencies}" id="d_raging_goblin_roo_domain_LogItem" render="${not empty dependencies}" z="Qbb30wyy/eoxYyR+b/J66Ec1Lxw="/>
</div>

Note several changes here: multipart=”true” on the form tag, type=”file” on the field tag for the image and render=”false” on the contentType tag. Spring Roo will adjust the z values to user-managed for us, but you can do this yourself as well. Change the update.jspx in the same way. I also removed the image and contentType from list.jspx by setting render=”false” on those columns.
Edit 17-05-2013: See later post on how to update an image.

Step 4: Update input.tagx to handle type=”file”
In the previous step we added type=”file” to the input fields of the images, but this field actually does nothing with this parameter, so add

<c:when test="${type eq 'file'}">
   <form:input type="file" id="_${sec_field}_id" path="${sec_field}" disabled="${disabled}" />
</c:when>

to this tag (~/src/main/webapp/WEB-INF/tags/form/fields/input.tagx) just above the line ‘<c:when test=”${type eq ‘password’}”>’.

Step 5: Add a method to the controller to send the actual image
In order to send the actual image to the browser we need to add an extra REST mapping to the LogItemController. Add the following method:

@RequestMapping(value = "/{id}/image", method = RequestMethod.GET)
	public String showImage(@PathVariable("id") Long id, HttpServletResponse response, Model model) {
		LogItem logItem = LogItem.findLogItem(id);
		if (logItem != null) {
			byte[] image = logItem.getImage();
			if (image != null) {
				try {
					response.setContentType(logItem.getContentType());
					OutputStream out = response.getOutputStream();
					IOUtils.copy(new ByteArrayInputStream(image), out);
					out.flush();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

This will take care of sending the image when you browse to an url like http://localhost:8080/logbook/logitems/1/image. Note that I just added /image to the standard url mapping of a LogItem.

Step 6: Change create and update methods of LogItemController to set contentType
Copy the create method from the aspect to the java file and wait to let Roo update the aspect. Than add a parameter and store the contentType:

    @RequestMapping(method = RequestMethod.POST, produces = "text/html")
    public String create(@Valid LogItem logItem, BindingResult bindingResult, Model uiModel, 
    		@RequestParam("image") MultipartFile multipartFile,
    		HttpServletRequest httpServletRequest) {
        if (bindingResult.hasErrors()) {
            populateEditForm(uiModel, logItem);
            return "logitems/create";
        }
        uiModel.asMap().clear();
        logItem.setContentType(multipartFile.getContentType());
        logItem.persist();
        return "redirect:/logitems/" + encodeUrlPathSegment(logItem.getId().toString(), httpServletRequest);
    }

You will have to edit the update method in a similar fashion.

Step 7: Add a PropertyEditor to the LogItemController
At this moment Spring will send a CommonMultipartFile to the controller, but the type of the image is a byte-array. Spring will fail with a IllegalArgumentException (Failed to convert property value of type org.springframework.web.multipart.commons.CommonsMultipartFile to required type byte[] for property image). Spring however comes very conveniently with a PropertyEditor to handle this conversion. Register this in the LogItemController:

	@InitBinder
	protected void initBinder(HttpServletRequest request,
			ServletRequestDataBinder binder) throws ServletException {
		binder.registerCustomEditor(byte[].class,
				new ByteArrayMultipartFileEditor());
	}

Step 8: Update show.jspx to display the image
To display an image add a standard HTML img tag to show.jspx:

<img src="${logitem.id}/image" height="256px"/>

Screenshot

Good luck!

References

  1. http://viralpatel.net/blogs/spring-roo-save-read-blob-object-spring-roo-tutorial/
  2. http://gadgets.coolman.ca/multipart-file-upload-spring-roo/
  3. http://whyjava.wordpress.com/tag/spring-roo/

Create a free website or blog at WordPress.com.