/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.aop.interceptor.servlet;

import javax.servlet.*;
import javax.servlet.http.*;

import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.service.aop.*;
import jp.ossc.nimbus.service.journal.Journal;
import jp.ossc.nimbus.service.journal.EditorFinder;
import jp.ossc.nimbus.service.journal.editor.JournalServletResponseWrapper;
import jp.ossc.nimbus.service.journal.editor.JournalHttpServletResponseWrapper;
import jp.ossc.nimbus.service.context.Context;
import jp.ossc.nimbus.service.sequence.Sequence;

/**
 * アクセスジャーナルインターセプタ。<p>
 *
 * @author M.Takata
 */
public class AccessJournalInterceptorService
 extends ServletFilterInterceptorService
 implements AccessJournalInterceptorServiceMBean{
    
    private static final long serialVersionUID = 2545303821021717162L;
    
    protected ServiceName journalServiceName;
    protected Journal journal;
    protected ServiceName accessEditorFinderServiceName;
    protected EditorFinder accessEditorFinder;
    protected ServiceName requestEditorFinderServiceName;
    protected EditorFinder requestEditorFinder;
    protected ServiceName responseEditorFinderServiceName;
    protected EditorFinder responseEditorFinder;
    protected ServiceName sequenceServiceName;
    protected Sequence sequence;
    protected ServiceName contextServiceName;
    protected Context<Object, Object> context;
    protected boolean isResponseWrap;
    protected boolean isResponseBufferedOutput;
    protected boolean isBushingRequestBlock;
    protected String requestIdKey = DEFAULT_REQUEST_ID_KEY;
    
    protected String accessJournalKey = DEFAULT_ACCESS_JOURNAL_KEY;
    protected String requestJournalKey = DEFAULT_REQUEST_JOURNAL_KEY;
    protected String responseJournalKey = DEFAULT_RESPONSE_JOURNAL_KEY;
    protected String servletRequestJournalKey
         = DEFAULT_SERVLET_REQUEST_JOURNAL_KEY;
    protected String servletResponseJournalKey
         = DEFAULT_SERVLET_RESPONSE_JOURNAL_KEY;
    protected String httpSessionJournalKey
         = DEFAULT_HTTP_SESSION_JOURNAL_KEY;
    
    protected boolean isOutputRequestSession = false;
    protected boolean isOutputResponseSession = false;
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setAccessJournalKey(String key){
        accessJournalKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getAccessJournalKey(){
        return accessJournalKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setRequestJournalKey(String key){
        requestJournalKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getRequestJournalKey(){
        return requestJournalKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setResponseJournalKey(String key){
        responseJournalKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getResponseJournalKey(){
        return responseJournalKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setServletRequestJournalKey(String key){
        servletRequestJournalKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getServletRequestJournalKey(){
        return servletRequestJournalKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setServletResponseJournalKey(String key){
        servletResponseJournalKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getServletResponseJournalKey(){
        return servletResponseJournalKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setHttpSessionJournalKey(String key){
        httpSessionJournalKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getHttpSessionJournalKey(){
        return httpSessionJournalKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setJournalServiceName(ServiceName name){
        journalServiceName = name;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public ServiceName getJournalServiceName(){
        return journalServiceName;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setAccessEditorFinderServiceName(ServiceName name){
        accessEditorFinderServiceName = name;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public ServiceName getAccessEditorFinderServiceName(){
        return accessEditorFinderServiceName;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setRequestEditorFinderServiceName(ServiceName name){
        requestEditorFinderServiceName = name;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public ServiceName getRequestEditorFinderServiceName(){
        return requestEditorFinderServiceName;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setResponseEditorFinderServiceName(ServiceName name){
        responseEditorFinderServiceName = name;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public ServiceName getResponseEditorFinderServiceName(){
        return responseEditorFinderServiceName;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setSequenceServiceName(ServiceName name){
        sequenceServiceName = name;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public ServiceName getSequenceServiceName(){
        return sequenceServiceName;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setContextServiceName(ServiceName name){
        contextServiceName = name;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public ServiceName getContextServiceName(){
        return contextServiceName;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setResponseWrap(boolean isWrap){
        isResponseWrap = isWrap;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public boolean isResponseWrap(){
        return isResponseWrap;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setResponseBufferedOutput(boolean isBuffered){
        isResponseBufferedOutput = isBuffered;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public boolean isResponseBufferedOutput(){
        return isResponseBufferedOutput;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setBushingRequestBlock(boolean isBlock){
        isBushingRequestBlock = isBlock;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public boolean isBushingRequestBlock(){
        return isBushingRequestBlock;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setRequestIDKey(String key){
        requestIdKey = key;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public String getRequestIDKey(){
        return requestIdKey;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setOutputRequestSession(boolean isOutput){
        isOutputRequestSession = isOutput;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public boolean isOutputRequestSession(){
        return isOutputRequestSession;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public void setOutputResponseSession(boolean isOutput){
        isOutputResponseSession = isOutput;
    }
    
    // AccessJournalInterceptorServiceMBeanのJavaDoc
    public boolean isOutputResponseSession(){
        return isOutputResponseSession;
    }
    
    /**
     * サービスの開始処理を行う。<p>
     *
     * @exception Exception サービスの開始に失敗した場合
     */
    public void startService() throws Exception{
        if(journalServiceName == null){
            throw new IllegalArgumentException(
                "journalServiceName must be specified."
            );
        }
        journal = ServiceManagerFactory.getServiceObject(
            journalServiceName
        );
        
        if(accessEditorFinderServiceName != null){
            accessEditorFinder = ServiceManagerFactory
                .getServiceObject(
                    accessEditorFinderServiceName
                );
        }
        
        if(requestEditorFinderServiceName != null){
            requestEditorFinder = ServiceManagerFactory
                .getServiceObject(
                    requestEditorFinderServiceName
                );
        }
        
        if(responseEditorFinderServiceName != null){
            responseEditorFinder = ServiceManagerFactory
                .getServiceObject(
                    responseEditorFinderServiceName
                );
        }
        
        if(sequenceServiceName != null){
            sequence = ServiceManagerFactory
                .getServiceObject(
                    sequenceServiceName
                );
        }
        
        if(contextServiceName != null){
            context = ServiceManagerFactory
                .getServiceObject(
                    contextServiceName
                );
        }
    }
    
    /**
     * リクエストのジャーナルを出力して、次のインターセプタを呼び出し、レスポンスのジャーナルを出力する。<p>
     * サービスが開始されていない場合は、何もせずに次のインターセプタを呼び出す。<br>
     *
     * @param context 呼び出しのコンテキスト情報
     * @param chain 次のインターセプタを呼び出すためのチェーン
     * @return 呼び出し結果の戻り値
     * @exception Throwable 呼び出し先で例外が発生した場合、またはこのインターセプタで任意の例外が発生した場合。但し、本来呼び出される処理がthrowしないRuntimeException以外の例外をthrowしても、呼び出し元には伝播されない。
     */
    public Object invokeFilter(
        ServletFilterInvocationContext context,
        InterceptorChain chain
    ) throws Throwable{
        if(getState() != State.STARTED){
            return chain.invokeNext(context);
        }
        final ServletRequest request = context.getServletRequest();
        final ServletResponse response = context.getServletResponse();
        
        if(isBushingRequestBlock){
            final String recorded
                 = (String)request.getAttribute(ACCESS_JOURNAL_RECORDED);
            if(recorded != null){
                // ジャーナル記録済みなのでそのまま後続処理実行
                return chain.invokeNext(context);
            }
            
            request.setAttribute(ACCESS_JOURNAL_RECORDED, "recorded");
        }
        try{
            journal.startJournal(accessJournalKey, accessEditorFinder);
            if(sequence != null){
                String sequenceId = sequence.increment();
                if(context != null){
                    this.context.put(requestIdKey, sequenceId);
                }
                journal.setRequestId(sequenceId);
            }else if(context != null){
                journal.setRequestId(
                    (String)this.context.get(requestIdKey)
                );
            }
            
            ServletResponse res = null;
            if(isResponseWrap){
                if(response instanceof HttpServletResponse){
                    final HttpServletResponse httpResponse
                         = (HttpServletResponse)response;
                    final JournalHttpServletResponseWrapper responsew
                         = new JournalHttpServletResponseWrapper(httpResponse);
                    responsew.setBufferedOutput(isResponseBufferedOutput);
                    res = responsew;
                }else{
                    final JournalServletResponseWrapper responsew
                         = new JournalServletResponseWrapper(response);
                    responsew.setBufferedOutput(isResponseBufferedOutput);
                    res = responsew;
                }
                context.setServletResponse(res);
            }else{
                res = response;
            }
            try{
                journal.startJournal(
                    requestJournalKey,
                    requestEditorFinder
                );
                journal.addInfo(servletRequestJournalKey, request);
                if(isOutputRequestSession
                     && (request instanceof HttpServletRequest)){
                    journal.addInfo(
                        httpSessionJournalKey,
                        ((HttpServletRequest)request).getSession(false)
                    );
                }
                return chain.invokeNext(context);
            }finally{
                journal.endJournal();
                try{
                    journal.startJournal(
                        responseJournalKey,
                        responseEditorFinder
                    );
                    journal.addInfo(servletResponseJournalKey, res);
                    if(isOutputResponseSession
                         && (request instanceof HttpServletRequest)){
                        journal.addInfo(
                            httpSessionJournalKey,
                            ((HttpServletRequest)request).getSession(false)
                        );
                    }
                    if(isResponseWrap && isResponseBufferedOutput){
                        if(res instanceof JournalHttpServletResponseWrapper){
                            final JournalHttpServletResponseWrapper responsew
                                 = (JournalHttpServletResponseWrapper)res;
                            responsew.flush();
                        }else{
                            final JournalServletResponseWrapper responsew
                                 = (JournalServletResponseWrapper)res;
                            responsew.flush();
                        }
                    }
                }finally{
                    journal.endJournal();
                }
            }
        }finally{
            journal.endJournal();
        }
    }
}