Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin
Note

See this page for more examples and advanced configuration

Wiki Markup
{snippet:id=description|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}

Parameters

Wiki Markup
{snippet:id=parameters|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}

Extending the Interceptor

Wiki Markup
{snippet:id=extending|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}

Examples

Example action mapping:

Wiki Markup
{snippet:id=example-configuration|lang=xml|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}

Notice the interceptor configuration in the preceding example.

Example JSP form tags:

Wiki Markup
{snippet:id=example-form|lang=xml|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}
Wiki Markup
{snippet:id=multipart-note|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}

Example Action class:

Wiki Markup
{snippet:id=example-action|lang=java|javadoc=true|url=org.apache.struts2.interceptor.FileUploadInterceptor}

Setting parameters example:

Code Block
xml
xml

<interceptor-ref name="fileUpload">
  <param name="allowedTypes">
     image/png,image/gif,image/jpeg
  </param>
</interceptor-ref>

This part is optional and would be done in place of the <interceptor-ref name="fileUpload"/> line in the action mapping example above.

We can create a nice reusable Interceptor which can hadle file uploads absolutely transparently and Action will not know anything about web-app and just gets its files:

  1. Before further invocation it scans multipart request for files, and if content-type and size is acceptable it puts collected java.io.File instance to invocationContext.parameters Map named according to their <input> names.
  2. In Action we can just declare properties of type File which will be set by parameters Inteceptor, in execute() we can move file with aFile.renameTo(...) to the right place, or just read the it and leave alone.
  3. After invocation it removes all uploaded files via aFile.delete(). (Action should not care for iles uploaded waste disk space)

This Interceptor may be configured to filter files with certain mime type or size, and of course to be applied to any action(s) or even included into stack.

Code Block

<interceptor name="fileUpload" class="neuro.util.xwork.FileUploadInterceptor">
    <param name="allowedTypes">image/jpeg</param>
    <param name="maximumSize">8192</param>
</interceptor>

This removes (duplicate) web-app specific code from Action and gives ua a nice reusable component that handles 90% of all typical file upload tasks. Neat. Also illustrates power of Interceptor concept of XWork/WebWork2. Try to do this in Struts. 8)

Things to improve: error handling, reporting & i18n.

Code Block

//FileUploadInterceptor.java

package neuro.util.xwork;

import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.webwork.dispatcher.multipart.MultiPartRequestWrapper;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.interceptor.Interceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;

/**
 *
 */
public class FileUploadInterceptor implements Interceptor {
    protected static final Log log = LogFactory.getLog(FileUploadInterceptor.class);

    protected String allowedTypes;
    protected String disallowedTypes;
    protected Long maximumSize;

    /**
     */
    public FileUploadInterceptor() {
        if (log.isDebugEnabled()) log.debug("new FileUploadInterceptor()");
    }

    /**
     */
    public void init() {
        if (log.isDebugEnabled()) log.debug("init()");
    }

    /**
     */
    public void destroy() {
        if (log.isDebugEnabled()) log.debug("destroy()");
    }

    /**
     * list of allowed mime-types, optional
     */
    public void setAllowedTypes(String allowedTypes) {
        this.allowedTypes = allowedTypes;
    }

    /**
     * list of diallowed mime-types, optional
     */
    public void setDisallowedTypes(String disallowedTypes) {
        this.disallowedTypes = disallowedTypes;
    }

    /**
     * maximum file Size, optional
     */
    public void setMaximumSize(Long maximumSize) {
        this.maximumSize = maximumSize;
    }

    /**
     *
     * TODO: i18n!
     */
    public String intercept(ActionInvocation invocation) throws Exception {
        if (!(ServletActionContext.getRequest() instanceof MultiPartRequestWrapper)) {
            if (log.isDebugEnabled()) log.debug("bypass " + invocation.getProxy().getNamespace() + "/" + invocation.getProxy().getActionName());
            return invocation.invoke();
        }
        MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) ServletActionContext.getRequest();
        if (multiWrapper.hasErrors()) {
            Collection errors = multiWrapper.getErrors();
            Iterator i = errors.iterator();
            while (i.hasNext()) {
                //how to get to addError() from here?
                log.error((String) i.next());
            }
        }

        Enumeration e = multiWrapper.getFileNames();

        //Bind allowed Files
        while (e.hasMoreElements()) {
            // get the value of this input tag
            String inputName = (String) e.nextElement();
            // get the content type
            String contentType = multiWrapper.getContentType(inputName);
            // get the name of the file from the input tag
            String fileName = multiWrapper.getFilesystemName(inputName);
            // Get a File object for the uploaded File
            File file = multiWrapper.getFile(inputName);

            log.info("file " + inputName + " " + contentType + " " + fileName + " " + file);

            // If it's null the upload failed
            if (file == null) {
                log.error("Error uploading: " + fileName);
            } else {
                if (acceptFile(file, contentType, inputName))
                    invocation.getInvocationContext().getParameters().put(inputName, file);
                // Do additional processing/logging...
            }
        }

        //Invoke Action
        String result = invocation.invoke();

        //Cleanup
        e = multiWrapper.getFileNames();
        while (e.hasMoreElements()) {
            String inputValue = (String) e.nextElement();
            File file = multiWrapper.getFile(inputValue);
            log.info("removing file " + inputValue + " " + file);
            if (file != null && file.isFile()) file.delete();
        }

        return result;
    }

    //overload this method to modify accept behaviour
    //TODO: addErrors?
    //TODO: i18n!
    protected boolean acceptFile(File file, String contentType, String inputName) {
        if (log.isDebugEnabled()) log.debug("checking" + inputName + " " + file.getName() + " " + file.length() + " " + contentType);
        if (maximumSize != null && maximumSize.longValue() < file.length())
            log.error("file is too long:" + inputName + " " + file.getName() + " " + file.length());
        else if (allowedTypes != null && allowedTypes.indexOf(contentType) < 0)
            log.error("Content-Type not allowed:" + inputName + " " + file.getName() + " " + contentType);
        else if (disallowedTypes != null && disallowedTypes.indexOf(contentType) >= 0)
            log.error("Content-Type disallowed:" + inputName + " " + file.getName() + " " + contentType);
        //somehow we need to set error messages here...
        else {
            if (log.isDebugEnabled()) log.debug("accepted");
            return true;
        }
        if (log.isDebugEnabled()) log.debug("not accepted");
        return false;
    }

}

Example Page code:

Code Block

<form .... enctype="multipart/form-data">
    ....
    select user icon:
    <input type="file" name="picture">
<form>

Example Action code:

Code Block

    /**
     */
    protected File picture;

    public void setPicture(File picture) {
        this.picture = picture;
    }

    /**
     */
    public String execute() {
        if (picture != null && picture.isFile()) {
            final File target = new File("...");
            if (target.exists()) {
                if (log.isDebugEnabled()) log.debug("Removed previous picture version");
                target.delete();
            }
            picture.renameTo(target);
        }
    }

If this Interceptor considered generally useful - may be it will be incorporated into WW2 codebase?