2013年1月16日 星期三

How to Obtain Result in Action -- Applying Struts2 PreResultListener

最近為了個資法上路,各家資訊廠商都做了因應,其中最常見的需求就是把系統曾經被使用者匯出去的機敏性資料留下記錄,但是因為資料是會隨時間一直變動的,最保險的方式就是將當下匯出去的資料保存下來,那麼在Struts2裡面該怎樣做到這件事呢?答案是:PreResultListener

設計概念如下:
  1. 利用interceptor判斷Action是否需要Result的內容,如果需要就註冊PreResultListener。
  2. PreResultListener執行時,判斷Result若是可以記錄的型別則替換mock response物件,並且執行Result execute method。
  3. 從mock response物件取得content和content type,並且將原始的response物件還原。
  4. 呼叫callback method將content和content type傳回Action,讓Action自己implement想要處理的動作。
程式如下:
ObtainResultInterceptor
package com.gss.gmo.cao.struts2.interceptor;

import com.gss.gmo.cao.struts2.interceptor.obtainresult.ObtainResultAware;
import com.gss.gmo.cao.struts2.interceptor.obtainresult.ObtainResultListener;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

/**
 * @author linus_chien
 *
 */
public class ObtainResultInterceptor extends AbstractInterceptor {

    private static final long serialVersionUID = 1L;

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        if (invocation.getAction() instanceof ObtainResultAware) {
            invocation.addPreResultListener(new ObtainResultListener());
        }
        return invocation.invoke();
    }

}
ObtainResultListener
package com.gss.gmo.cao.struts2.interceptor.obtainresult;

import static org.apache.commons.lang3.ArrayUtils.contains;

import javax.servlet.http.HttpServletResponse;

import lombok.SneakyThrows;

import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.StreamResult;
import org.apache.struts2.views.jasperreports.JasperReportsResult;
import org.springframework.mock.web.MockHttpServletResponse;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.DefaultActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.mock.MockActionInvocation;

/**
 * @author linus_chien
 *
 */
public class ObtainResultListener implements PreResultListener, StrutsStatics {

    private static final Class<?>[] RESULT_TYPES = { StreamResult.class, JasperReportsResult.class };

    @Override
    @SneakyThrows
    public void beforeResult(ActionInvocation invocation, String resultCode) {
        Object action = invocation.getAction();
        if (action instanceof ObtainResultAware) {
            ObtainResultAware obtainResultAction = (ObtainResultAware) action;

            Result result = getResult(invocation);

            if (isNotValidResultType(result)) {
                return;
            }

            MockHttpServletResponse mockResponse = new MockHttpServletResponse();
            HttpServletResponse realResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
            invocation.getInvocationContext().put(HTTP_RESPONSE, mockResponse);

            try {
                result.execute(invocation);
                byte[] content = mockResponse.getContentAsByteArray();
                String contentType = mockResponse.getContentType();
                obtainResultAction.handleResult(content, contentType);
            } finally {
                invocation.getInvocationContext().put(HTTP_RESPONSE, realResponse);
            }
        }
    }

    private boolean isNotValidResultType(Result result) {
        return result == null || !contains(RESULT_TYPES, result.getClass());
    }

    @SneakyThrows
    private Result getResult(ActionInvocation invocation) {
        Result result;
        if (!isMock(invocation)) {
            result = ((DefaultActionInvocation) invocation).createResult();
        } else {
            result = invocation.getResult();
        }
        return result;
    }

    private boolean isMock(ActionInvocation invocation) {
        return invocation instanceof MockActionInvocation;
    }

}
ObtainResultAware
package com.gss.gmo.cao.struts2.interceptor.obtainresult;

/**
 * @author linus_chien
 *
 */
public interface ObtainResultAware {

    void handleResult(byte[] content, String contentType);

}