This blog is mainly about Java...

Friday, March 26, 2010

Advanced Seam series part 2 of 3: Seam Filters

Seam Filters.

In this blog post I will be showing two filters. One that hacks in UTF-8 as encoding, and the other, a download filter for downloading files efficiently.

Seam filters can potentially be called outside of the Seam context, thus they are not truly a Seam component, but they act as one. However, they are able to reuse the functionality for component scanning, installation and configuration for filters.

There are two ways to define filters.

1. Through web.xml
    <filter-name>UTF8 Filter</filter-name>

      <filter-name>UTF8 Filter</filter-name>

And the Filter it self:
This filter is a fix for a bug in Seam encoding filter. More information here.
Remember you must implement Filter.
 * This Filter is a fix for bug in seam encoding filter, see here for more details:
 * It might be removed if the version will be upgraded to at least 2.2.1.CR1
public class UTF8Filter implements Filter {

    public void destroy() {}

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)   throws IOException, ServletException {
        // set encoding to UTF-8
        chain.doFilter(req, res);

    public void init(FilterConfig arg0) throws ServletException {}
However, the servlet specification doesn't provide a well defined order if you specify your filters in a web.xml, however Seam provides a way to do that by using the @Filter annotation.

In this next example I will show an efficient and pretty safe download filter which you can use to serve files to the user. First I want to present the way you shouldn't do it.

Say you have a File entity class.

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;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;

import org.hibernate.validator.NotNull;

public class File {

    private Long id;

    @Column(nullable = false, length = 256)
    private String name;

    private String hash;

    // BLOB = L + 2 bytes (max size is 2^16 - 1 or 65,535 bytes, 65KB)
    // MEDIUMBLOB = L + 3 bytes (max size is 2^24 - 1 or 16,777,215 bytes, 16MB)
    // LONGBLOB = L + 4 bytes (max size is 2^32 - 1 or 4,294,967,295 bytes, 4GB)
    @Basic(fetch = FetchType.LAZY)
    // @Basic is used in conjunction with @Lob
    // Set to MAX 100MB LONGBLOB in MySQL
    @Column(length = 104857600, nullable = false)
    private byte[] data;

    public void setHash() {
        try {
            hash = PasswordSupport.generateFileHash(data);
        } catch (GeneralSecurityException e) {
            hash = null;

    public InputStream getInputStream() {
        return new ByteArrayInputStream(data);
    //getters and setters
For the PasswordSupport class (which basically hashes the input), have a look at my previous post. We always want to generate a new hash if a new file appears, thus the triggers @PreUpdate and @PrePersist

But to save you some time, here is the method:
     * Hash file
     * @throws GeneralSecurityException 
    public static final String generateFileHash(byte[] data) throws GeneralSecurityException {
        byte[] salt = new byte[1024];
        String theHash;
        try {
            AnnotatedBeanProperty<UserPassword> userPasswordProperty = new AnnotatedBeanProperty<UserPassword>(ProcessUser.class, UserPassword.class);
            // Will get the hash value from annotation UserPassword in User.class
            theHash = PasswordHash.instance().createPasswordKey(String.valueOf(Arrays.hashCode(data)).toCharArray(), salt, userPasswordProperty.getAnnotation().iterations());
            return theHash;
        } finally {
            salt = null;
            theHash = null;
Typically, you would do the following to serve the user a file:


import javax.annotation.PostConstruct;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletResponse;

import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;

public class FileServiceImpl implements FileService {

    private EntityManager entityManager;

    private FileNameMap fileNameMap;
    public void contruct() {
        fileNameMap = URLConnection.getFileNameMap();
    public void download(File file) throws IOException {
        byte[] data = file.getData();
        HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
        //Take mime type from filename, I don't know if the null check is necessary, the API doesn't say. 
        String mime = fileNameMap.getContentTypeFor(file.getName());
        if (mime == null || "".equals(mime.trim())) {
            mime = "application/octet-stream";
        response.addHeader("Content-Disposition", "attachment;filename=" + file.getName());
        OutputStream writer = response.getOutputStream();
        //Skip the rest of JSF phases
        //rest of ejb not shown
This method of doing it is bad because it will load the file in to memory and serve it to the user. Not to mention all the Seam interceptors along the way.

A much better way is to use a filter.
All you need to do is create a link that has the downloadFileId as parameter and the filter will activate, like this


import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mydomain.model.File;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.Filter;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.web.AbstractFilter;

 * Implemented from suggestion on 
 * {@link}
 * @author Shervin Asgari
@Filter(around = { "org.jboss.seam.web.ajax4jsfFilter" })
public class DownloadFilter extends AbstractFilter {

    public void doFilter(ServletRequest request, ServletResponse resp, FilterChain arg2) throws IOException, ServletException {
        if (HttpServletRequest.class.isAssignableFrom(request.getClass())) {
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse response = (HttpServletResponse) resp;
            String downloadFileId = req.getParameter("downloadFileId");
            //The param is seperated with the id of the file and the hash eg http://localhost:8080/myapp/somePage.xhtml/?downloadFileId=1-8B94B45466C738FE90EF9CA8D47281EAE821F4E0 
            //The two ifs could be one, but for debugging and breakpoints, its easier this way
            if (downloadFileId != null && downloadFileId.matches("^\\d+-.*$")) {
                boolean started = false;
                try {
                    String[] ids = downloadFileId.split("-",0);
                    if(ids.length >= 1) {
                        String fileId = ids[0];
                        String hash = ids.length >= 2 ? ids[1] : null;
                        started = true;
                        File file = null;
                        EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");

                        if(hash != null) {
                            file = (File) entityManager.createQuery("SELECT f FROM " + File.class.getName() + " f " +
                                    "WHERE and f.hash=:hash").setMaxResults(1).setParameter("id", Long.valueOf(fileId)).setParameter("hash", hash).getSingleResult();
                        } else {
                            //This is here for backward compatibility
                            file = (File) entityManager.createQuery("SELECT f FROM " + File.class.getName() + " f " +
                            "WHERE and f IS NULL").setMaxResults(1).setParameter("id", Long.valueOf(fileId)).getSingleResult();
                        if(file != null) {
                            FacesContext facesContext = FacesContext.getCurrentInstance();
                            InputStream stream = file.getInputStream();
                            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
                            OutputStream out = response.getOutputStream();
                            try {
                                byte[] buf = new byte[response.getBufferSize()];
                                int len;
                                while ((len = > 0) {
                                    out.write(buf, 0, len);
                            } finally {
                            if(facesContext != null)
                } catch (Exception e) {
                    resp.getOutputStream().write(String.valueOf("Error downloading file" + e.getMessage()).getBytes());

                finally {
                        arg2.doFilter(req, response);
            } else {
                arg2.doFilter(req, response);
This way you will not load the file into memory, and avoid all the interceptors of Seam, thus it will perform much faster and better. Note that we have included a backward compatibility in case the file entity does not have a hash yet.

No comments: