/*
 * 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;

import java.util.*;

import jp.ossc.nimbus.beans.*;
import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.service.aop.*;
import jp.ossc.nimbus.service.context.*;
import jp.ossc.nimbus.service.sequence.*;
import jp.ossc.nimbus.service.codemaster.*;

/**
 * スレッドコンテキスト初期化インターセプタ。<p>
 *
 * @author M.Takata
 */
public class ThreadContextInitializeInterceptorService extends ServiceBase
 implements Interceptor, ThreadContextInitializeInterceptorServiceMBean{
    
    private static final long serialVersionUID = 7085572561373638170L;
    
    protected ServiceName threadContextServiceName;
    protected Context<Object, Object> threadContext;
    protected ServiceName sequenceServiceName;
    protected ServiceName codeMasterFinderServiceName;
    protected Map<String, String> contextValueServiceNames;
    protected Map<String, ServiceName> contextValueServiceNameMap;
    protected Properties contextValueInvocationContext;
    protected Map<String, Property> contextKeyPropMapping;
    protected Map<String, Object> contextValueMapping;
    
    protected boolean isOutputThreadName = true;
    protected boolean isOutputThreadGroupName = true;
    protected boolean isInitializeRecursiveCall = true;
    protected boolean isClear = true;
    
    protected ThreadLocal<CallStack> callStack;
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setThreadContextServiceName(ServiceName name){
        threadContextServiceName = name;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public ServiceName getThreadContextServiceName(){
        return threadContextServiceName;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setCodeMasterFinderServiceName(ServiceName name){
        codeMasterFinderServiceName = name;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public ServiceName getCodeMasterFinderServiceName(){
        return codeMasterFinderServiceName;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setSequenceServiceName(ServiceName name){
        sequenceServiceName = name;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public ServiceName getSequenceServiceName(){
        return sequenceServiceName;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setContextValueServiceNames(Map<String, String> names){
        contextValueServiceNames = names;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public Map<String, String> getContextValueServiceNames(){
        return contextValueServiceNames;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setContextValueInvocationContext(Properties map){
        contextValueInvocationContext = map;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public Properties getContextValueInvocationContext(){
        return contextValueInvocationContext;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setOutputThreadName(boolean isOutput){
        isOutputThreadName = isOutput;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public boolean isOutputThreadName(){
        return isOutputThreadName;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setOutputThreadGroupName(boolean isOutput){
        isOutputThreadGroupName = isOutput;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public boolean isOutputThreadGroupName(){
        return isOutputThreadGroupName;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public boolean isInitializeRecursiveCall(){
        return isInitializeRecursiveCall;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setInitializeRecursiveCall(boolean isInitialize){
        isInitializeRecursiveCall = isInitialize;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setContextValueMapping(Map<String, Object> mapping){
        contextValueMapping = mapping;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public Map<String, Object> getContextValueMapping(){
        return contextValueMapping;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setContextValue(String key, Object value){
        if(contextValueMapping == null){
            contextValueMapping = new HashMap<String, Object>();
        }
        contextValueMapping.put(key, value);
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public Object getContextValue(String key){
        if(contextValueMapping == null){
            return null;
        }
        return contextValueMapping.get(key);
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public boolean isClear(){
        return isClear;
    }
    
    // ThreadContextInitializeInterceptorServiceMBeanのJavaDoc
    public void setClear(boolean isClear){
        this.isClear = isClear;
    }
    
    /**
     * サービスの開始処理を行う。<p>
     *
     * @exception Exception サービスの開始に失敗した場合
     */
    public void startService() throws Exception{
        if(threadContextServiceName == null){
            throw new IllegalArgumentException(
                "threadContextServiceName must be specified."
            );
        }
        threadContext = ServiceManagerFactory
            .getServiceObject(threadContextServiceName);
        if(!isInitializeRecursiveCall){
            callStack = new ThreadLocal<CallStack>(){
                protected CallStack initialValue(){
                    return new CallStack();
                }
            };
        }
        if(contextValueInvocationContext != null){
            if(contextKeyPropMapping == null){
                contextKeyPropMapping = new HashMap<String, Property>();
            }
            final Iterator<Object> keys = contextValueInvocationContext.keySet().iterator();
            while(keys.hasNext()){
                final String key = (String)keys.next();
                final String propStr
                     = contextValueInvocationContext.getProperty(key);
                final Property prop = PropertyFactory.createProperty(propStr);
                prop.setIgnoreNullProperty(true);
                contextKeyPropMapping.put(key, prop);
            }
        }
        if(contextValueServiceNames != null){
            contextValueServiceNameMap = new HashMap<String, ServiceName>();
            ServiceNameEditor editor = new ServiceNameEditor();
            editor.setServiceManagerName(getServiceManagerName());
            for(Map.Entry<String, String> entry : contextValueServiceNames.entrySet()){
                editor.setAsText(entry.getValue());
                contextValueServiceNameMap.put(entry.getKey(), (ServiceName)editor.getValue());
            }
        }
    }
    
    /**
     * サービスの停止処理を行う。<p>
     *
     * @exception Exception サービスの停止に失敗した場合
     */
    public void stopService() throws Exception{
        callStack = null;
        if(contextKeyPropMapping != null){
            contextKeyPropMapping.clear();
        }
    }
    
    /**
     * スレッドコンテキストを初期化して、次のインターセプタを呼び出す。<p>
     * サービスが開始されていない場合は、何もせずに次のインターセプタを呼び出す。<br>
     *
     * @param context 呼び出しのコンテキスト情報
     * @param chain 次のインターセプタを呼び出すためのチェーン
     * @return 呼び出し結果の戻り値
     * @exception Throwable 呼び出し先で例外が発生した場合、またはこのインターセプタで任意の例外が発生した場合。但し、本来呼び出される処理がthrowしないRuntimeException以外の例外をthrowしても、呼び出し元には伝播されない。
     */
    public Object invoke(
        InvocationContext context,
        InterceptorChain chain
    ) throws Throwable{
        if(getState() == State.STARTED
            && (callStack == null
                 || ((CallStack)callStack.get()).stackIndex == 0)){
            
            if(isClear){
                threadContext.clear();
            }
            Sequence sequence = null;
            if(sequenceServiceName != null){
                try{
                    sequence = (Sequence)ServiceManagerFactory
                        .getServiceObject(sequenceServiceName);
                }catch(ServiceNotFoundException e){
                }
            }
            
            if(sequence != null){
                final String requestId = sequence.increment();
                threadContext.put(ThreadContextKey.REQUEST_ID, requestId);
            }
            
            if(contextValueServiceNameMap != null){
                for(Map.Entry<String, ServiceName> entry : contextValueServiceNameMap.entrySet()){
                    threadContext.put(
                        entry.getKey(),
                        ServiceManagerFactory.getServiceObject(entry.getValue())
                    );
                }
            }
            if(contextKeyPropMapping != null
                 && contextKeyPropMapping.size() != 0){
                for(Map.Entry<String, Property> entry : contextKeyPropMapping.entrySet()){
                    final String key = entry.getKey();
                    final Property prop = entry.getValue();
                    threadContext.put(
                        key,
                        prop.getProperty(context)
                    );
                }
            }
            if(contextValueMapping != null
                && contextValueMapping.size() != 0){
                threadContext.putAll(contextValueMapping);
            }
            
            final Thread thread = Thread.currentThread();
            if(isOutputThreadName){
                threadContext.put(
                    ThreadContextKey.THREAD_NAME,
                    thread.getName()
                );
            }
            if(isOutputThreadGroupName){
                final ThreadGroup threadGroup = thread.getThreadGroup();
                threadContext.put(
                    ThreadContextKey.THREAD_GROUP_NAME,
                    threadGroup.getName()
                );
            }
            CodeMasterFinder codeMasterFinder = null;
            if(codeMasterFinderServiceName != null){
                try{
                    codeMasterFinder = (CodeMasterFinder)ServiceManagerFactory
                        .getServiceObject(codeMasterFinderServiceName);
                }catch(ServiceNotFoundException e){
                }
            }
            if(codeMasterFinder != null){
                final Map<String, Object> codeMasters = codeMasterFinder.getCodeMasters();
                threadContext.put(ThreadContextKey.CODEMASTER, codeMasters);
            }
        }
        try{
            if(callStack != null){
                ((CallStack)callStack.get()).stackIndex++;
            }
            return chain.invokeNext(context);
        }finally{
            if(callStack != null){
                ((CallStack)callStack.get()).stackIndex--;
                if(isClear && ((CallStack)callStack.get()).stackIndex == 0){
                    threadContext.clear();
                }
            }else{
                if(isClear){
                    threadContext.clear();
                }
            }
        }
    }
    
    protected static class CallStack{
        public int stackIndex;
    }
}
