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

import java.util.*;
import java.util.regex.*;

import jp.ossc.nimbus.beans.*;
import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.service.aop.invoker.*;
import jp.ossc.nimbus.service.cache.CacheMap;

/**
 * デフォルトインターセプタチェーンファクトリ。<p>
 * キー文字列に合致する{@link InterceptorChain}を取得するファクトリ。<br>
 *
 * @author M.Takata
 */
public class DefaultInterceptorChainFactoryService extends ServiceBase
 implements InterceptorChainFactory, DefaultInterceptorChainFactoryServiceMBean{
    
    private static final long serialVersionUID = 8047982449933936760L;
    
    private Map<String, String> interceptorChainListMapping;
    private Map<Object, InterceptorChainList> keyAndChainListMap;
    private Map<String, String> interceptorMapping;
    private Map<Object, ServiceName> keyAndInterceptorMap;
    private Map<String, String> invokerMapping;
    private Map<Object, ServiceName> keyAndInvokerMap;
    private ServiceName defaultInterceptorChainListServiceName;
    private InterceptorChainList defaultInterceptorChainList;
    private ServiceName defaultInvokerServiceName;
    private Invoker defaultInvoker;
    private boolean isRegexEnabled;
    private int regexMatchFlag;
    private ServiceName interceptorChainCacheMapServiceName;
    private CacheMap<String, InterceptorChain> chainCache;
    private boolean isUseThreadLocalInterceptorChain = true;
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setInterceptorChainListMapping(Map<String, String> mapping){
        interceptorChainListMapping = mapping;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public Map<String, String> getInterceptorChainListMapping(){
        return interceptorChainListMapping;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setInterceptorMapping(Map<String, String> mapping){
        interceptorMapping = mapping;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public Map<String, String> getInterceptorMapping(){
        return interceptorMapping;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setDefaultInterceptorChainListServiceName(ServiceName name){
        defaultInterceptorChainListServiceName = name;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public ServiceName getDefaultInterceptorChainListServiceName(){
        return defaultInterceptorChainListServiceName;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setInvokerMapping(Map<String, String> mapping){
        invokerMapping = mapping;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public Map<String, String> getInvokerMapping(){
        return invokerMapping;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setDefaultInvokerServiceName(ServiceName name){
        defaultInvokerServiceName = name;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public ServiceName getDefaultInvokerServiceName(){
        return defaultInvokerServiceName;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setRegexEnabled(boolean isEnable){
        isRegexEnabled = isEnable;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public boolean isRegexEnabled(){
        return isRegexEnabled;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setRegexMatchFlag(int flag){
        regexMatchFlag = flag;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public int getRegexMatchFlag(){
        return regexMatchFlag;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setInterceptorChainCacheMapServiceName(ServiceName name){
        interceptorChainCacheMapServiceName = name;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public ServiceName getInterceptorChainCacheMapServiceName(){
        return interceptorChainCacheMapServiceName;
    }
    
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public void setUseThreadLocalInterceptorChain(boolean isUse){
        isUseThreadLocalInterceptorChain = isUse;
    }
    // DefaultInterceptorChainFactoryServiceMBeanのJavaDoc
    @Override
    public boolean isUseThreadLocalInterceptorChain(){
        return isUseThreadLocalInterceptorChain;
    }
    
    @Override
    public void createService() throws Exception{
        keyAndChainListMap = new LinkedHashMap<Object, InterceptorChainList>();
        keyAndInterceptorMap = new LinkedHashMap<Object, ServiceName>();
        keyAndInvokerMap = new LinkedHashMap<Object, ServiceName>();
    }
    
    @Override
    public void startService() throws Exception{
        final ServiceNameEditor editor = new ServiceNameEditor();
        editor.setServiceManagerName(getServiceManagerName());
        if(interceptorChainListMapping != null){
            for(Map.Entry<String, String> entry : interceptorChainListMapping.entrySet()){
                final String keyStr = entry.getKey();
                Object key = keyStr;
                if(isRegexEnabled){
                    key = Pattern.compile(keyStr, regexMatchFlag);
                }
                final String nameStr = entry.getValue();
                editor.setAsText(nameStr);
                final ServiceName name = (ServiceName)editor.getValue();
                final InterceptorChainList chainList
                     = (InterceptorChainList)ServiceManagerFactory
                        .getServiceObject(name);
                keyAndChainListMap.put(key, chainList);
            }
        }
        if(interceptorMapping != null){
            for(Map.Entry<String, String> entry : interceptorMapping.entrySet()){
                final String keyStr = entry.getKey();
                Object key = keyStr;
                if(isRegexEnabled){
                    key = Pattern.compile(keyStr, regexMatchFlag);
                }
                final String nameStr = (String)entry.getValue();
                editor.setAsText(nameStr);
                final ServiceName name = (ServiceName)editor.getValue();
                keyAndInterceptorMap.put(key, name);
            }
        }
        if(invokerMapping != null){
            for(Map.Entry<String, String> entry : invokerMapping.entrySet()){
                final String keyStr = entry.getKey();
                Object key = keyStr;
                if(isRegexEnabled){
                    key = Pattern.compile(keyStr, regexMatchFlag);
                }
                final String nameStr = (String)entry.getValue();
                editor.setAsText(nameStr);
                final ServiceName name = (ServiceName)editor.getValue();
                keyAndInvokerMap.put(key, name);
            }
        }
        if(defaultInterceptorChainListServiceName != null){
            defaultInterceptorChainList
                 = (InterceptorChainList)ServiceManagerFactory
                    .getServiceObject(defaultInterceptorChainListServiceName);
        }
        if(defaultInvokerServiceName == null){
            final MethodReflectionCallInvokerService invoker
                 = new MethodReflectionCallInvokerService();
            invoker.create();
            invoker.start();
            defaultInvoker = invoker;
        }else{
            defaultInvoker = ServiceManagerFactory
                .getServiceObject(defaultInvokerServiceName);
        }
        if(interceptorChainCacheMapServiceName != null){
            chainCache = ServiceManagerFactory
                    .getServiceObject(interceptorChainCacheMapServiceName);
        }
    }
    
    @Override
    public void stopService() throws Exception{
        keyAndChainListMap.clear();
        keyAndInterceptorMap.clear();
        keyAndInvokerMap.clear();
        if(chainCache != null){
            chainCache.clear();
        }
    }
    
    @Override
    public void destroyService() throws Exception{
        keyAndChainListMap = null;
        keyAndInterceptorMap = null;
        keyAndInvokerMap = null;
    }
    
    /**
     * キー文字列に合致する{@link InterceptorChain}を取得する。<p>
     * {@link #isUseThreadLocalInterceptorChain()}がtrueの時は、{@link DefaultThreadLocalInterceptorChain}を返すので、スレッド単位での再利用が可能である。<br>
     * また、{@link #setInterceptorChainCacheMapServiceName(ServiceName)}を指定している場合は、生成したInterceptorChainをキャッシュして返す。<br>
     *
     * @param key キー文字列
     * @return キー文字列に合致するInterceptorChain
     */
    @Override
    public InterceptorChain getInterceptorChain(Object key){
        final String keyStr = key == null ? null : key.toString();
        if(chainCache != null){
            synchronized(chainCache){
                if(chainCache.containsKey(keyStr)){
                    return (InterceptorChain)chainCache.get(keyStr);
                }
            }
        }
        InterceptorChainList chainList = null;
        Invoker invoker = null;
        if(keyStr == null){
            chainList = defaultInterceptorChainList;
            invoker = defaultInvoker;
        }else if(isRegexEnabled){
            if(keyAndChainListMap.size() != 0){
                for(Map.Entry<Object, InterceptorChainList> entry : keyAndChainListMap.entrySet()){
                    final Pattern pattern = (Pattern)entry.getKey();
                    if(pattern.matcher(keyStr).matches()){
                        chainList = entry.getValue();
                        break;
                    }
                }
            }
            if(chainList == null && keyAndInterceptorMap.size() != 0){
                for(Map.Entry<Object, ServiceName> entry : keyAndInterceptorMap.entrySet()){
                    final Pattern pattern = (Pattern)entry.getKey();
                    if(pattern.matcher(keyStr).matches()){
                        if(chainList == null){
                            chainList = new DefaultInterceptorChainList();
                        }
                        final ServiceName name = entry.getValue();
                        final Interceptor interceptor
                             = ServiceManagerFactory.getServiceObject(name);
                        ((DefaultInterceptorChainList)chainList).addInterceptor(
                            interceptor
                        );
                        break;
                    }
                }
            }
            if(keyAndInvokerMap.size() != 0){
                for(Map.Entry<Object, ServiceName> entry : keyAndInvokerMap.entrySet()){
                    final Pattern pattern = (Pattern)entry.getKey();
                    if(pattern.matcher(keyStr).matches()){
                        final ServiceName name = entry.getValue();
                        invoker = ServiceManagerFactory.getServiceObject(name);
                        break;
                    }
                }
            }
        }else{
            if(keyAndChainListMap.size() != 0){
                chainList = (InterceptorChainList)keyAndChainListMap
                    .get(keyStr);
            }
            if(keyAndInvokerMap.size() != 0){
                invoker = (Invoker)keyAndInvokerMap.get(keyStr);
            }
        }
        if(chainList == null){
            chainList = defaultInterceptorChainList;
        }
        if(invoker == null){
            invoker = defaultInvoker;
        }
        InterceptorChain chain = isUseThreadLocalInterceptorChain
            ? new DefaultThreadLocalInterceptorChain(
                chainList,
                invoker
              )
            : new DefaultInterceptorChain(
                chainList,
                invoker
              );
        if(chainCache != null){
            synchronized(chainCache){
                if(chainCache.containsKey(keyStr)){
                    return (InterceptorChain)chainCache.get(keyStr);
                }else{
                    chainCache.put(keyStr, chain);
                }
            }
        }
        return chain;
    }
}
