防止使用Struts2下载时取消出现异常,并实现支持下载完成后删除临时文件

项目开发中用到一些文件下载的功能.

使用org.apache.struts2.dispatcher.StreamResult时确有发现,在下载过程中终止下载,会引起服务端java.lang.IllegalStateException异常抛出,且无法关闭文件句柄.

在网上寻求解决办法,发现Jimmy Song在GitHub上的一篇文章https://github.com/41zone/StreamResultX,可解燃眉之急,在此感谢原创作者.

由于项目的需要,除了要解决struts2下载文件出现的问题之外,下载的文件大多数为临时文件,于是在Jimmy Song所贡献的代码之上,又做了一些改动,以支持下载完成之后(或下载过程中终止下载)自动删除临时文件.

源代码

/**
 * <p>在此感谢Jimmy Song
 * <br/>该类扩展了struts2中的result-type为stream的实体类,并重写了doExecute方法。
 * <br/>该类修正了源于ResultStream的一些错误处理,在文档下载取消时释放HttpResponse对象的引用。
 * <br/>防止java.lang.IllegalStateException异常抛出。
 * <br/>使用原有ResultStream进行文件下载时,如若点击取消,Socket并不会断开,流也没有关闭。
 * <br/>在JSP容器通过Response获取输出流之前,前面的流并没有关闭,所以会造成该异常的报出。
 *
 * @author Jimmy Song
 * @version 1.2 
 * @see https://github.com/41zone/StreamResultX
 */

/**
 * <p>借用Jimmy Song的StreamResultX,实现下载完之后删除文件
 *
 * @author ManerFan <a href="mailto:juniorfan@yeah.net"></a>
 */
public class StreamResultX extends StreamResult {

    private static final long serialVersionUID = -8275283556955657976L;

    /**
     * 文件路径
     */
    private String filePath;

    /**
     * 扩展重写StreamResult方法
     */
    protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
        resolveParamsFromStack(invocation.getStack(), invocation);

        OutputStream oOutput = null;

        /** 下载完成后需要删除的文件 */
        File delFile = null;

        try {
            String path = (String) invocation.getStack().findValue(
                    conditionalParse(filePath, invocation));
            if (null != path && !path.trim().isEmpty()) {
                delFile = new File(path);
            }

            if (inputStream == null) {
                // Find the inputstream from the invocation variable stack
                inputStream = (InputStream) invocation.getStack().findValue(
                        conditionalParse(inputName, invocation));
            }

            if (inputStream == null) {
                String msg = ("StreamResultX : Can not find a java.io.InputStream with the name ["
                        + inputName + "] in the invocation stack. " + "Check the <param name=\"inputName\"> tag specified for this action.");
                LOG.error(msg);
                throw new IllegalArgumentException(msg);
            }

            // Find the Response in context
            HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext()
                    .get(HTTP_RESPONSE);

            // Set the content type
            if (contentCharSet != null && !contentCharSet.equals("")) {
                oResponse.setContentType(conditionalParse(contentType, invocation) + ";charset="
                        + contentCharSet);
            } else {
                oResponse.setContentType(conditionalParse(contentType, invocation));
            }

            // Set the content length
            if (contentLength != null) {
                String _contentLength = conditionalParse(contentLength, invocation);
                int _contentLengthAsInt = -1;
                try {
                    _contentLengthAsInt = Integer.parseInt(_contentLength);
                    if (_contentLengthAsInt >= 0) {
                        oResponse.setContentLength(_contentLengthAsInt);
                    }
                } catch (NumberFormatException e) {
                    LOG.warn("StreamResultX warn : failed to recongnize " + _contentLength
                            + " as a number, contentLength header will not be set", e);
                }
            }

            // Set the content-disposition
            if (contentDisposition != null) {
                oResponse.addHeader("Content-Disposition",
                        conditionalParse(contentDisposition, invocation));
            }

            // Set the cache control headers if neccessary
            if (!allowCaching) {
                oResponse.addHeader("Pragma", "no-cache");
                oResponse.addHeader("Cache-Control", "no-cache");
            }
            // Get the outputstream
            oOutput = oResponse.getOutputStream();

            if (LOG.isDebugEnabled()) {
                LOG.debug("StreamResultX : Streaming result [" + inputName + "] type=["
                        + contentType + "] length=[" + contentLength + "] content-disposition=["
                        + contentDisposition + "] charset=[" + contentCharSet + "]");
            }

            // Copy input to output

            byte[] oBuff = new byte[bufferSize];
            int iSize;
            try {
                LOG.debug("StreamResultX : Streaming to output buffer +++ START +++");
                while (-1 != (iSize = inputStream.read(oBuff))) {
                    oOutput.write(oBuff, 0, iSize);
                }
                LOG.debug("StreamResultX : Streaming to output buffer +++ END +++");
                // Flush
                oOutput.flush();
            } catch (Exception e) {
                LOG.warn("StreamResultX Warn : socket write error");
                if (oOutput != null) {
                    try {
                        oOutput.close();
                    } catch (Exception e1) {
                        oOutput = null;
                    }
                }
            } finally {
                if (inputStream != null)
                    inputStream.close();
                if (oOutput != null)
                    oOutput.close();

                if (null != delFile // 可以删除
                        && delFile.exists() // 文件存在
                        && delFile.isFile()) { // 是常规文件
                    boolean delSucc = delFile.delete();
                    LOG.debug("delete file [" + filePath + " ], status [" + delSucc + "]");
                }
            }
        } finally {
            if (inputStream != null)
                inputStream.close();
            if (oOutput != null)
                oOutput.close();

            if (null != delFile // 可以删除
                    && delFile.exists() // 文件存在
                    && delFile.isFile()) { // 是常规文件
                boolean delSucc = delFile.delete();
                LOG.debug("delete file [" + filePath + " ], status [" + delSucc + "]");
            }
        }
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }
}

使用方法

首先,在struts.xml对应的package中加入
<result-types>
        <result-type name="streamx" class="manerfan.struts2.dispatcher.StreamResultX"/>
</result-types>
其次,在对应下载的action中,将原有的type="stream"类型修改为type="streamx"即可
<result name="success" type="streamx">
	<param name="contentType">application/octet-stream;charset=ISO8859-1</param>
	<param name="contentDisposition">attachment;filename="${downloadFileName}"</param>   
	<param name="bufferSize">4096</param>
	<param name="inputName">inputStream</param>
	<param name="filePath">realFilePath</param>
</result>
配置中,[ contentDisposition ]downloadFileName为浏览器下载框默认下载文件名;[ inputName ]inputStream为指向下载文件的InputStream;[ filePath ]realFilePath为所下载文件的绝对路径.
以上三个参数需要在对应action中设置getter方法
若配置filePath参数,则在下载结束后自动删除下载的文件
若不配置filePath参数,则在下载结束后不删除下载的文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值