Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

The following article describes and shows code for the basics of uploading attachments with Seam, and downloading those attachments.

To start with go ahead and 'seam create-entity' or with JBoss Tools right click on the project and "New -> Seam Entity" and name the Entity "Attachment". We'll only use part of the generated code, mainly only to create the files in the correct locations so you don't need to worry about it.

Next, lets modify the Attachment.java Entity and change it to:

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;

@Entity
public class Attachment implements java.io.Serializable {

	@Id
	@GeneratedValue
	private Long id;
	public Long getId() { return this.id; }
	public void setId(Long id) { this.id = id; }
	
	private String name;
	public String getName() { return this.name;	}
	public void setName(String name) { this.name = name; }
	
	private long size;
	public long getSize() { return this.size; }
	public void setSize(long size) { this.size = size; }
	
	private String contentType;
	public String getContentType() { return this.contentType; }
	public void setContentType(String contentType) { this.contentType = contentType; }
	
	@Lob
	@Column(length = 2147483647)
	@Basic(fetch = FetchType.LAZY)
	private byte[] data;
	public byte[] getData() { return this.data; }
	public void setData(byte[] data) { this.data = data; }
	
}

Now it will contain all of the basic fields to correspond with s:fileUpload.

Open attachment.xhtml and paste in:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:s="http://jboss.com/products/seam/taglib"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:rich="http://richfaces.org/rich" template="layout/template.xhtml">

	<ui:define name="body">

		<h:messages globalOnly="true" styleClass="message" />

		<h:form enctype="multipart/form-data">

			<rich:panel>
				<f:facet name="header">Upload Attachment</f:facet>

				<s:decorate id="fileUploadDecoration" template="layout/edit.xhtml">
					<ui:define name="label">Attachment</ui:define>
					<s:fileUpload id="file" 
						      data="#{attachmentHome.instance.data}"
						      contentType="#{attachmentHome.instance.contentType}"
						      fileName="#{attachmentHome.instance.name}"
						      fileSize="#{attachmentHome.instance.size}" />
				</s:decorate>
				
				<s:decorate id="nameDecoration" template="layout/display.xhtml">
					<ui:define name="label">Name</ui:define>
					<h:outputText value="#{attachmentHome.instance.name}"/>
				</s:decorate>
				
				<s:decorate id="contentTypeDecoration" template="layout/display.xhtml">
					<ui:define name="label">Content Type</ui:define>
					<h:outputText value="#{attachmentHome.instance.contentType}"/>
				</s:decorate>
				
				<s:decorate id="sizeDecoration" template="layout/display.xhtml">
					<ui:define name="label">Size</ui:define>
					<h:outputText value="#{attachmentHome.instance.size}"/>
				</s:decorate>
				
				<div style="clear: both" />
			</rich:panel>
			
			<div class="actionButtons">
				<h:commandButton value="Upload" 
						 action="#{attachmentHome.persist}"  
						 rendered="#{!attachmentHome.managed}"/>
				<h:commandButton value="Delete" 
						 action="#{attachmentHome.remove}"
						 immediate="true"  
						 rendered="#{attachmentHome.managed}">
					<s:conversationPropagation type="end" />
				</h:commandButton>
				<s:button propagation="end" 
                          id="done" 
                          value="Done"
                          view="/attachmentList.xhtml"/>
			</div>

		</h:form>

	</ui:define>

</ui:composition>

The main difference between the above code and what you would expect with a standard CRUD page is that the fields are outputText instead of inputText because the values are automatically determined by the s:fileUpload component. No code I have here is set in stone, feel free to modify as heavily as you need to, this is just an outline really.

Now, let's edit attachmentList.xhtml to display the properties of the uploaded file and allow for downloading.

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                template="layout/template.xhtml">
                       
	<ui:define name="body">
	    
	    <h:messages globalOnly="true" styleClass="message"/>
	    
	    <rich:panel>
	        <f:facet name="header">Files Uploaded</f:facet>
	        
	        <div class="results">
	        
	            <h:outputText value="No files exists" rendered="#{empty attachmentList.resultList}"/>
	                   
	            <rich:dataTable id="attachmentList" 
	            			 var="attachment"
	                      	 value="#{attachmentList.resultList}" 
	                   		 rendered="#{not empty attachmentList.resultList}">
	                <rich:column>
	                    <f:facet name="header">File Name</f:facet>
	                    <s:link value="#{attachment.name}" view="/attachment.xhtml">
	                        <f:param name="attachmentId" value="#{attachment.id}" />
	                    </s:link>
	                </rich:column>
	                <rich:column>
	                    <f:facet name="header">Content Type</f:facet>
	                    #{attachment.contentType}
	                </rich:column>
	                <rich:column>
	                    <f:facet name="header">Size(bytes)</f:facet>
	                    #{attachment.size}
	                </rich:column>
	                <rich:column>
	                    <f:facet name="header">Action</f:facet>
	                    <s:link value="Download" action="#{downloadAttachment.download}">
		                    <f:param name="attachmentId" value="${attachment.id}" />
		                </s:link>
	                </rich:column>
	            </rich:dataTable>
	            
	        </div>
	        
	    </rich:panel>
	    
	    <div class="actionButtons">
	        <s:button id="done" 
	               value="Create attachment"
	                view="/attachment.xhtml"/>			  
	    </div>
	    
	</ui:define>

</ui:composition>

As you can see there is a Component called 'downloadAttachment' referenced that we haven't created yet so lets do that. You can make this an EJB3 or a Seam pojo, the latter is easier so we'll do that. The DownloadAttachment component is the real meat and potatoes of this whole article, so pay attention:

@Name("downloadAttachment")
public class DownloadAttachment {
	
	@Logger
	private Log log;
	
	@In
	private EntityManager entityManager;
	
	@In(value="#{facesContext.externalContext}")
	private ExternalContext extCtx;
	
	@In(value="#{facesContext}")
	FacesContext facesContext;
	
	@RequestParameter
	private Long attachmentId;
	
	public String download() {
		Attachment attachment = entityManager.find(Attachment.class, attachmentId);
		HttpServletResponse response = (HttpServletResponse)extCtx.getResponse();
		response.setContentType(attachment.getContentType());
                response.addHeader("Content-disposition", "attachment; filename=\"" + attachment.getName() +"\"");
		try {
			ServletOutputStream os = response.getOutputStream();
			os.write(attachment.getData());
			os.flush();
			os.close();
			facesContext.responseComplete();
		} catch(Exception e) {
			log.error("\nFailure : " + e.toString() + "\n");
		}

		return null;
	}
}

And for the sake of completeness, here is the AttachmentHome.java and AttachmentList.java which required no changes:

import org.jboss.seam.annotations.Name;
import org.jboss.seam.framework.EntityQuery;

@Name("attachmentList")
public class AttachmentList extends EntityQuery
{
    @Override
    public String getEjbql() 
    { 
        return "select attachment from Attachment attachment";
    }
}
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.web.RequestParameter;
import org.jboss.seam.framework.EntityHome;

import org.domain.knowledgebase.entity.Attachment;

@Name("attachmentHome")
public class AttachmentHome extends EntityHome<Attachment> {

    @RequestParameter 
    Long attachmentId;
    
    @Override
    public Object getId() { 
        if (attachmentId==null) {
            return super.getId();
        } else {
            return attachmentId;
        }
    }
    
    @Override @Begin
    public void create() {
        super.create();
    }
 	
}

Now the only thing left to do is add exception handling for FileUploadExceptions and define the multipart filter in components.xml.

Add the following to pages.xml:

<exception class="org.jboss.seam.web.FileUploadException">
  <redirect view-id="/error.xhtml">
    <message>#{org.jboss.seam.handledException.message}</message>
  </redirect>
</exception>

And to components.xml:

xmlns:web="http://jboss.com/products/seam/web"
...
http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.0.xsd
...
<web:multipart-filter create-temp-files="true"
                      max-request-size="1000000" 
                      url-pattern="*.seam" />

Incorporating this into your own Webapp

If you want to make this seamless with your your own app you can rip out the form in the attachment.xhtml page and the dataTable in the attachmentList.xhtml page and embed that xhtml in any page you need, it should 'just work'.

31 comments:
 
24. Aug 2008, 14:19 America/New_York | Link

Very useful tutorial, thanks a lot.

 
26. Aug 2008, 02:52 America/New_York | Link

Yes, very helpful.

 
27. Aug 2008, 09:50 America/New_York | Link
PipoXP (TH) | pipoxp123.AT.gmail.com

Very useful, thanks so much.

 
03. Sep 2008, 04:00 America/New_York | Link

This example only explains how to save files into a database. Is there a way to save files to a location on the filesystem? Is there any reason why saving files inside the database vs the filesystem? Wouldn't that put added load on the DB?

 
05. Sep 2008, 21:53 America/New_York | Link

Excuse me, Thank you for your view, but could help?

What we need is upload a text file A Basa data, and if possible, open it in a front XHTML ...

Serer IS possible to do this?

I tried TU example, but by giving them upload, ME sack of implementation, and does not meet the expected response .....

 
09. Sep 2008, 16:17 America/New_York | Link

Hello all,

In my case, I need to download a file (shows pop-up to user choose betwen download or open) AND redirect to a new page, could be using Seam page navigation, such as: return success. and .page.xml rules.

Does anyone have any suggestion? Because when I add: facesContext.responseComplete();, it can not redirect any more.

 
09. Sep 2008, 19:46 America/New_York | Link
DOWNLOAD

IF YOU NEED TO DOWNLOAD ONE FILE IN THE PRESENTATION, LOOK THIS PAGE:

http://docs.jboss.com/seam/1.2.1.GA/reference/en/html/itext.html

 
03. Oct 2008, 13:21 America/New_York | Link

Sorry, anyone knows how to DISPLAY the image in my app??

 
14. Nov 2008, 08:51 America/New_York | Link
quangdaocct

Hi all. This is exam about save file in Database. But i want to ask about how i can save file at the folder in the web server. Thanks very much.

 
17. Dec 2008, 02:13 America/New_York | Link
I have same redirect issue, Does anyone have the answer?
 
17. Mar 2009, 08:25 America/New_York | Link

Hello,

The download does not get the right file from the database. Looks like it saves the current xhtml to the file when we try to download.

Anyone with the same Problem?

Thanks

 
24. Mar 2009, 19:27 America/New_York | Link

For some reason I don't know, I'm getting null values for data, filename, contentType ....

Any tip?

I have JBoss 4.2.2, Seam 2.1, IceFaces 1.7.2 SP1.

I can't use IceFaces fileInput, because it doesn't work well with FireFox 3 and Chrome.

Thanks.

 
20. May 2009, 17:07 America/New_York | Link

Hello

I also have the error, that im only getting null values from the s:fileupload

I use Jboss 5.0.1 and jboss 2.1.1

if somebody have a tip please contact me!!!

thx kapi

 
25. May 2009, 00:55 America/New_York | Link
hcgpragt

AFAIK s:upload calls twice. One time with null values and one time with the actual file / upload data. If you test for the null and just return then, it should work.

 
15. Jun 2009, 15:49 America/New_York | Link
Hikesh wrote on Mar 17, 2009 08:25:
Hello, The download does not get the right file from the database. Looks like it saves the current xhtml to the file when we try to download. Anyone with the same Problem? Thanks

Do you solve this problem? I have the same

 
26. Jun 2009, 15:53 America/New_York | Link
Hikesh

Hello Rafael,

Yes, the problem was solved. 1st issue, the attribute names used in this example are invalid, for example for a oracle database, so, use ur own attribute names. That is basic. And you will have to update in all files, in the example.

2nd, Check if you have a warning while declaring the entity manager in the DownloadAttachment component, this causes the issue you are facing. So get it solved.

3rd, Make sure you are giving the right parameter to the download link on attachmentList.xhtml

 <s:link value="Download" action="#{downloadAttachment.download}">
	  <f:param name="AttachmentIdFile" value="#{_attachment.idFile}" />
  </s:link>

If you have any trouble, just contact... Hope it helps

 
20. Jul 2009, 10:10 America/New_York | Link

I had a problem when i try to download.

Exception during request processing:

Caused by org.jboss.seam.InstantiationException with message: "Could not instantiate Seam component: downloadAttachment" 

...........

Caused by java.lang.IllegalArgumentException with message: "could not set field value: downloadAttachment.log" 

...........

Caused by java.lang.IllegalArgumentException with message: "Could not set field value by reflection: DownloadAttachment.log on: upload.session.DownloadAttachment with value: class org.jboss.seam.log.LogImpl"

...........

Caused by java.lang.IllegalArgumentException with message: "" 

...........

Please help me, how to solve the problem. thanks.

 
28. Jul 2009, 12:01 America/New_York | Link

This works for me up until I want to edit other parts of the record, without changing the image.

How do I avoid uploading blank data to the Datbase (postgres / orcale) when I want to update everything but the image?

Many thanks Andrew

 
10. Sep 2009, 18:07 America/New_York | Link

@kapi

I was getting this same problem (null values on upload), see my forum post for my solution:

SeamFilter called twice, breaks s:fileUpload

 
28. Sep 2009, 14:42 America/New_York | Link

Is it possible to redirect to different pages. I tried using xml for navigation. But it doesnt work out.

Does anyone have a solution?

Thanks in advance.

 
16. Oct 2009, 05:38 America/New_York | Link

Work like a charm. A big thank you!

27. Nov 2009, 02:01 America/New_York | Link
I am studying Jbosss AS 5.1.0 GA and Seam 2.2.0 GA with Window Vista laptop.
and made some code to download a file from special folder.
Here are my codes:

1. Download interface class
2. Download action class
3. Download xhtml file

Here aie the action class:

public String download() {
                String fileName = downloadFilename;
        log.info("downloade(),uploadFile.getName()="+fileName);
        if (fileName == null ) {
                fileName = "test.txt";
        }
                String contentType = getContentType(fileName);
        log.info("downloade(),contentType="+contentType);
                try {
                                                 HttpServletResponse response = (HttpServletResponse)extCtx.getResponse();
                log.info("downloade(),1 response="+response);
                log.info("downloade(),before etResponse.");
                if ( facesContext == null ) {
                        facesContext = new FacesContext();
                        }
                response = (HttpServletResponse)facesContext.getContext().getExternalContext().getResponse();
                log.info("downloade(),2 response="+response);
                response.reset();
                response.setContentType(contentType);
                response.addHeader("Content-disposition", "attachment; filename=\"" + fileName +"\"");
                        //ServletOutputStream os = response.getOutputStream();
                OutputStream os = response.getOutputStream();
                        File file = new File( fileLoc, fileName);
                log.info("downloade(),file="+file);
                        byte[] fileBytes = FileUtil.readBytes( file );

                        os.write(fileBytes);
                        os.flush();
                        os.close();
                        //facesContext.responseComplete();
                } catch(Exception ex) {
                        log.error("downloade(),Failure : " + ex.toString());
                }

                return null;
        }


I got this error messages:
java.lang.IllegalStateException: Servlet response already use stream, Writer not possible
        at org.ajax4jsf.webapp.FilterServletResponseWrapper.getWriter(FilterServletResponseWrapper.java:207)
        at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:112)
..
..

Do you know what is my prblem?

Thanks
Chang Shin

 
26. Jan 2010, 04:11 America/New_York | Link
Paulo Martins

Great article! It worked in the first try, thanks guys!

 
26. Apr 2010, 17:43 America/New_York | Link
java.lang.IllegalStateException: Servlet response already use stream, Writer not possible

I got the above error when I mistakenly omitted the facesContext.responseComplete() call. The error (for me) was actually on the originating view (that contains the link to download the file) upon redisplay. I didn't discover this snafu until I added some logging and noticed that my file streaming was actually completing successfully.

 
03. Jul 2010, 16:04 America/New_York | Link

Double check your h:form if you really did set enctype=multipart/form-data.

That's a common mistake why you might be missing the data.

 
04. Sep 2010, 20:45 America/New_York | Link
Great stuff !!!, sure worked first time !!!, had almost given up on file uploads with Seam !!!!!,

Otherwise tips for others, don't forget to add the required imports to file DownloadAttachment as below,

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.web.RequestParameter;
import org.jboss.seam.log.Log;


Also don't forget to create a table called attachment to store the attachments, the structure of the table i used is


  id int(4) NOT NULL AUTO_INCREMENT - Primary Key
  name varchar(50),
  size int(4),
  contentType varchar(50),
  data blob,


Also on uploading you might get an error to do with file size being exceeded, try uploading a smaller file.
I think for larger files use the LongBlob datatype

Hope it helps
 
25. Dec 2010, 21:32 America/New_York | Link

Hi,

Thanks for this wonderful article. By forcing facesContext.responseComplete(), we get a browser download dialog. however, any re-rendering that must be done after download is complete can't be done here.

Do you have any suggestion on how re-rendering of certain component can be triggered after download?

Thanks, Shriharsh

 
29. Dec 2010, 00:28 America/New_York | Link

useful tutorial

 
15. Feb 2011, 15:31 America/New_York | Link
Good article...

I am also getting the same issue i.e. the download does not get the right file from the database but it saves the current xhtml to the file when we try to download.

Here is what I am tried...
xxxxList.xhtml
<rich:column>
    <f:facet name="header">Action</f:facet>
        <s:link value="Download" action="#{downloadAttachment.download}">
         <f:param name="metTestAttachmentHjid" value="${metTestAttachment.hjid}" />
     </s:link>
</rich:column>


@Name("downloadAttachment")
public class DownloadAttachment {
   @In
 private EntityManager entityManager;
   @In(value="#{facesContext.externalContext}")
 private ExternalContext extCtx;
   @In(value="#{facesContext}")
 FacesContext facesContext;
   @RequestParameter
 private Long metTestAttachmentHjid;


 public String download() {
     MetTestAttachment attachment = entityManager.find(MetTestAttachment.class, metTestAttachmentHjid);
  HttpServletResponse response = (HttpServletResponse)extCtx.getResponse();

  //THIS RETURNS CORRECT ID OF THE FILE
  log.info("DownloadAttachment.download, attachment.getContenttype() check id :"+metTestAttachmentHjid);

  response.setContentType(attachment.getContenttype());
                response.addHeader("Content-disposition", "attachment; filename=\"" + attachment.getName() +"\"");
  try {
   ServletOutputStream os = response.getOutputStream();
   os.write(attachment.getData());
   os.flush();
   os.close();
   facesContext.responseComplete();
  } catch(Exception e) {
   log.error("\nFailure : " + e.toString() + "\n");
  }
 }

}


Entity:
public class MetTestAttachment implements java.io.Serializable {
 private Long hjid;
 private String name;
 private String contenttype;
 private byte[] data;
 private int filesize;
 ....
}

Home object
@Name("metTestAttachmentHome")
public class MetTestAttachmentHome extends EntityHome<MetTestAttachment> {
 @RequestParameter
    Long metTestAttachmentHjid;
   @RequestParameter
    Long id;

 public void setMetTestAttachmentHjid(Long id) {
  setId(id);
 }

 public Long getMetTestAttachmentHjid() {
  return (Long) getId();
 }
}

Any suggestion for best approach to upload multiple files..? I will try rich:fileUpload ... to check
 
08. Jun 2011, 15:17 America/New_York | Link

Just an FYI in case anyone is struggling with this, the code as is was working for me, then when we threw in a special kind of attachment, it began throwing the Servlet response already use stream, Writer not possible exception for me, I re-arranged it so that the code generating the byte array is called before the servlet response is acquired, and the problem did not recur.

So instead of using:

HttpServletResponse response = (HttpServletResponse) extCtx.getResponse();
//...
ServletOutputStream os = response.getOutputStream();
os.write(attachment.getData());

Because attachment.getData() in my case was taking a long time, I did something like this instead:

byte[] data = attachment.getData();  // This takes some time
HttpServletResponse response = (HttpServletResponse) extCtx.getResponse();
response.setContentType(format);
response.addHeader("Content-disposition", "attachment; filename=\"" + attachment.getName() +"\"");
ServletOutputStream os = response.getOutputStream();
os.write(data);
os.flush();
os.close();
facesContext.responseComplete();

I am not certain why this is, or if it is a race condition that I am now simply beating. An approach to avoid storing it in a byte and use streams array might be to kick off the long running part of the attachment, and wait to acquire the output stream until the byte stream is ready.

 
18. Jul 2011, 15:15 America/New_York | Link

There are some very great sources here and thank you for being so kind to post them here. So we can read them and give our opinion on pariuri live subject.