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

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.Set;
import java.util.HashSet;
import java.util.Date;
import java.rmi.RemoteException;
import java.rmi.server.*;

import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.service.aop.*;
import jp.ossc.nimbus.service.context.Context;
import jp.ossc.nimbus.service.repository.*;
import jp.ossc.nimbus.service.distribute.ClusterService;
import jp.ossc.nimbus.service.sequence.Sequence;
import jp.ossc.nimbus.service.sequence.StringSequenceService;
import jp.ossc.nimbus.service.sequence.TimeSequenceVariable;
import jp.ossc.nimbus.service.performance.ResourceUsage;

/**
 * 業務フロー実行サーバサービス。<p>
 *
 * @author M.Takata
 */
public class BeanFlowServerService extends ServiceBase
 implements BeanFlowServerServiceMBean{
    
    private static final long serialVersionUID = 4150733833643322667L;
    private ServiceName beanFlowFactoryServiceName;
    private ServiceName interceptorChainFactoryServiceName;
    private ServiceName jndiRepositoryServiceName;
    private ServiceName contextServiceName;
    private ServiceName resourceUsageServiceName;
    private Repository jndiRepository;
    private String jndiName = DEFAULT_JNDI_NAME;
    private int rmiPort;
    private ServiceName clusterServiceName;
    private ClusterService cluster;
    private StringSequenceService sequence;
    private String sequenceTimestampFormat = "HHmmssSSS";
    private int sequenceDigit = 3;
    private BeanFlowServerImpl server;
    private ResourceUsage<Comparable<?>> resourceUsage;
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setBeanFlowFactoryServiceName(ServiceName name){
        beanFlowFactoryServiceName = name;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public ServiceName getBeanFlowFactoryServiceName(){
        return beanFlowFactoryServiceName;
    }
    
    // BeanFlowServerServiceMBean
    public void setInterceptorChainFactoryServiceName(ServiceName name){
        interceptorChainFactoryServiceName = name;
    }
    // BeanFlowServerServiceMBean
    public ServiceName getInterceptorChainFactoryServiceName(){
        return interceptorChainFactoryServiceName;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setContextServiceName(ServiceName name){
        contextServiceName = name;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public ServiceName getContextServiceName(){
        return contextServiceName;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setResourceUsageServiceName(ServiceName name){
        resourceUsageServiceName = name;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public ServiceName getResourceUsageServiceName(){
        return resourceUsageServiceName;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setJndiName(String name){
        jndiName = name;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public String getJndiName(){
        return jndiName;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setJndiRepositoryServiceName(ServiceName name){
        jndiRepositoryServiceName = name;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public ServiceName getJndiRepositoryServiceName(){
        return jndiRepositoryServiceName;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setRMIPort(int port){
        rmiPort = port;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public int getRMIPort(){
        return rmiPort;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setClusterServiceName(ServiceName name){
        clusterServiceName = name;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public ServiceName getClusterServiceName(){
        return clusterServiceName;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setSequenceTimestampFormat(String format){
        sequenceTimestampFormat = format;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public String getSequenceTimestampFormat(){
        return sequenceTimestampFormat;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setSequenceDigit(int digit){
        sequenceDigit = digit;
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public int getSequenceDigit(){
        return sequenceDigit;
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public boolean isAcceptable(){
        return server == null ? false : server.isAcceptable();
    }
    // BeanFlowServerServiceMBeanのJavaDoc
    public void setAcceptable(boolean isAcceptable){
        if(server == null){
            return;
        }
        server.setAcceptable(isAcceptable);
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public int getCurrentFlowCount(){
        return server == null ? 0 : server.getCurrentFlowCount();
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public Comparable<?> getResourceUsage(){
        return server == null ? null : server.getResourceUsage();
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public Set<Object> getCurrentFlowIdSet(){
        return server == null ? new HashSet<Object>() : server.getIdSet();
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public Date getFlowStartTime(String id){
        try{
            return server == null ? null : (server.getFlowStartTime(id) >= 0 ? new Date(server.getFlowStartTime(id)) : null);
        }catch(NoSuchBeanFlowIdException e){
            return null;
        }
    }
    
    // BeanFlowServerServiceMBeanのJavaDoc
    public long getFlowCurrentProcessTime(String id){
        try{
            return server == null ? -1 : server.getFlowCurrentProcessTime(id);
        }catch(NoSuchBeanFlowIdException e){
            return -1;
        }
    }
    
    public void setResourceUsage(ResourceUsage<Comparable<?>> usage){
        resourceUsage = usage;
    }
    
    /**
     * サービスの開始処理を行う。<p>
     *
     * @exception Exception サービスの開始処理に失敗した場合
     */
    public void startService() throws Exception{
        
        if(beanFlowFactoryServiceName == null){
            throw new IllegalArgumentException(
                "BeanFlowFactoryServiceName must be specified."
            );
        }
        
        if(jndiRepositoryServiceName == null){
            throw new IllegalArgumentException(
                "JndiRepositoryServiceName must be specified."
            );
        }
        jndiRepository = ServiceManagerFactory
            .getServiceObject(jndiRepositoryServiceName);
        
        sequence = new StringSequenceService();
        sequence.create();
        sequence.setFormat(TimeSequenceVariable.FORMAT_KEY + "(" + sequenceTimestampFormat + "," + sequenceDigit + ")");
        sequence.start();
        
        if(resourceUsage == null && resourceUsageServiceName != null){
            resourceUsage = ServiceManagerFactory
                .getServiceObject(resourceUsageServiceName);
        }
        server = new BeanFlowServerImpl(
            beanFlowFactoryServiceName,
            interceptorChainFactoryServiceName,
            contextServiceName,
            sequence,
            resourceUsage,
            rmiPort
        );
        if(!jndiRepository.register(jndiName, server)){
            throw new Exception("Could not register in jndiRepository.");
        }
        if(clusterServiceName != null){
            cluster = (ClusterService)ServiceManagerFactory.getServiceObject(clusterServiceName);
            if(cluster.isJoin()){
                throw new IllegalArgumentException("ClusterService already join.");
            }
            cluster.setOption(
                (Serializable)RemoteObject.toStub(server)
            );
            cluster.join();
        }
    }
    
    /**
     * サービスの停止処理を行う。<p>
     *
     * @exception Exception サービスの停止処理に失敗した場合
     */
    public void stopService() throws Exception{
        if(cluster != null){
            cluster.leave();
            cluster = null;
        }
        if(sequence != null){
            sequence.stop();
            sequence.destroy();
            sequence = null;
        }
        jndiRepository.unregister(jndiName);
    }
    
    /**
     * {@link BeanFlowServer}実装クラス。<p>
     *
     * @author M.Takata
     */
    public static class BeanFlowServerImpl extends UnicastRemoteObject
     implements BeanFlowServer{
        
        private static final long serialVersionUID = -2397154705661936441L;
        private static Method executeMethod;
        private static Method executeAsynchMethod;
        static{
            try{
                executeMethod = BeanFlow.class.getMethod("execute", new Class[]{Object.class, BeanFlowMonitor.class});
            }catch(NoSuchMethodException e){
                executeMethod = null;
            }
            try{
                executeAsynchMethod = BeanFlow.class.getMethod("executeAsynch", new Class[]{Object.class, BeanFlowMonitor.class, BeanFlowAsynchCallback.class, Integer.TYPE});
            }catch(NoSuchMethodException e){
                executeAsynchMethod = null;
            }
        }
        
        private final ServiceName beanFlowFactoryServiceName;
        private final ServiceName interceptorChainFactoryServiceName;
        private final ServiceName contextServiceName;
        private final Sequence sequence;
        private final ResourceUsage<Comparable<?>> resourceUsage;
        private final Map<Object, BeanFlow> flowMap = Collections.synchronizedMap(new HashMap<Object, BeanFlow>());
        private final Map<Object, BeanFlowMonitor> monitorMap = Collections.synchronizedMap(new HashMap<Object, BeanFlowMonitor>());
        private final Map<Object, BeanFlowAsynchContext> contextMap = Collections.synchronizedMap(new HashMap<Object, BeanFlowAsynchContext>());
        private boolean isAcceptable = true;
        
        /**
         * インスタンスを生成する。<p>
         *
         * @param beanFlowFactoryServiceName BeanFlowFactoryサービス名
         * @param interceptorChainFactoryServiceName InterceptorChainFactoryサービス名
         * @param contextServiceName Contextサービス名
         * @param sequence Sequenceサービス
         * @param resourceUsage ResourceUsageサービス
         * @param port RMIポート番号
         * @exception java.rmi.RemoteException オブジェクトのエクスポートが失敗した場合
         */
        public BeanFlowServerImpl(
            ServiceName beanFlowFactoryServiceName,
            ServiceName interceptorChainFactoryServiceName,
            ServiceName contextServiceName,
            Sequence sequence,
            ResourceUsage<Comparable<?>> resourceUsage,
            int port
        ) throws java.rmi.RemoteException{
            super(port);
            this.beanFlowFactoryServiceName = beanFlowFactoryServiceName;
            this.interceptorChainFactoryServiceName = interceptorChainFactoryServiceName;
            this.contextServiceName = contextServiceName;
            this.sequence = sequence;
            this.resourceUsage = resourceUsage;
        }
        
        private BeanFlowFactory getBeanFlowFactory(){
            return (BeanFlowFactory)ServiceManagerFactory.getServiceObject(beanFlowFactoryServiceName);
        }
        
        private BeanFlow getBeanFlow(Object id) throws NoSuchBeanFlowIdException{
            BeanFlow invoker = (BeanFlow)flowMap.get(id);
            if(invoker == null){
                throw new NoSuchBeanFlowIdException(id);
            }
            return invoker;
        }
        
        private BeanFlowMonitor getBeanFlowMonitor(Object id) throws NoSuchBeanFlowIdException{
            BeanFlowMonitor monitor = (BeanFlowMonitor)monitorMap.get(id);
            if(monitor == null){
                throw new NoSuchBeanFlowIdException(id);
            }
            return monitor;
        }
        
        private BeanFlowAsynchContext getBeanFlowAsynchContext(Object id){
            BeanFlowAsynchContext context = (BeanFlowAsynchContext)contextMap.get(id);
            return context;
        }
        
        private InterceptorChain getInterceptorChain(String flowName){
            if(interceptorChainFactoryServiceName == null){
                return null;
            }
            InterceptorChainFactory factory = (InterceptorChainFactory)ServiceManagerFactory.getServiceObject(interceptorChainFactoryServiceName);
            return factory.getInterceptorChain(flowName);
        }
        
        private Context<?, ?> getContext(){
            return contextServiceName == null ? null : (Context<?, ?>)ServiceManagerFactory.getServiceObject(contextServiceName);
        }
        
        public boolean isAcceptable(){
            return isAcceptable;
        }
        public void setAcceptable(boolean isAcceptable){
            this.isAcceptable = isAcceptable;
        }
        
        public Set<String> getBeanFlowNameSet(){
            return getBeanFlowFactory().getBeanFlowKeySet();
        }
        
        public boolean containsFlow(String name){
            return getBeanFlowFactory().containsFlow(name);
        }
        
        public Object createFlow(String flowName, String caller, boolean isOverwride) throws BeanFlowException{
            String id = sequence.increment();
            BeanFlow invoker = getBeanFlowFactory().createFlow(flowName, caller, isOverwride);
            BeanFlowMonitor monitor = invoker.createMonitor();
            monitorMap.put(id, monitor);
            flowMap.put(id, invoker);
            return id;
        }
        
        public String[] getOverwrideFlowNames(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlow(id).getOverrideFlowNames();
        }
        
        public int getCurrentFlowCount(){
            return flowMap.size();
        }
        
        @SuppressWarnings("unchecked")
        public <T extends Comparable<?>> T getResourceUsage(){
            return resourceUsage == null ? (T)new Integer(getCurrentFlowCount()) : (T)resourceUsage.getUsage();
        }
        
        @SuppressWarnings("unchecked")
        public Object execute(Object id, Object obj, Map<?, ?> ctx) throws Exception, NoSuchBeanFlowIdException{
            BeanFlow invoker = getBeanFlow(id);
            BeanFlowMonitor monitor = getBeanFlowMonitor(id);
            if(ctx != null && ctx.size() != 0){
                Context<Object, Object> context = (Context<Object, Object>)getContext();
                if(context != null){
                    context.putAll(ctx);
                }
            }
            InterceptorChain chain = getInterceptorChain(invoker.getFlowName());
            try{
                if(chain == null){
                    return invoker.execute(obj, monitor);
                }else{
                    DefaultMethodInvocationContext context = new DefaultMethodInvocationContext(
                        invoker,
                        executeMethod,
                        new Object[]{obj, monitor}
                    );
                    try{
                        chain.setCurrentInterceptorIndex(-1);
                        return chain.invokeNext(context);
                    }catch(Throwable e){
                        if(e instanceof Exception){
                            throw (Exception)e;
                        }else{
                            throw (Error)e;
                        }
                    }finally{
                        chain.setCurrentInterceptorIndex(-1);
                    }
                }
            }finally{
                flowMap.remove(id);
                monitorMap.remove(id);
            }
        }
        
        @SuppressWarnings("unchecked")
        public void executeAsynch(Object id, Object input, Map<?, ?> ctx, BeanFlowAsynchCallback callback, int maxAsynchWait) throws NoSuchBeanFlowIdException, RemoteException, Exception{
            BeanFlow invoker = getBeanFlow(id);
            BeanFlowMonitor monitor = getBeanFlowMonitor(id);
            if(ctx != null && ctx.size() != 0){
                Context<Object, Object> context = (Context<Object, Object>)getContext();
                if(context != null){
                    context.putAll(ctx);
                }
            }
            BeanFlowAsynchCallbackImpl callbackWrapper = new BeanFlowAsynchCallbackImpl(id, callback);
            InterceptorChain chain = getInterceptorChain(invoker.getFlowName());
            Object beanFlowAsynchContext = null;
            if(chain == null){
                beanFlowAsynchContext = invoker.executeAsynch(input, monitor, callbackWrapper, maxAsynchWait);
            }else{
                DefaultMethodInvocationContext context = new DefaultMethodInvocationContext(
                    invoker,
                    executeAsynchMethod,
                    new Object[]{input, monitor, callbackWrapper, maxAsynchWait, new Integer(maxAsynchWait)}
                );
                try{
                    chain.setCurrentInterceptorIndex(-1);
                    beanFlowAsynchContext = chain.invokeNext(context);
                }catch(Throwable e){
                    if(e instanceof Exception){
                        throw (Exception)e;
                    }else{
                        throw (Error)e;
                    }
                }finally{
                    chain.setCurrentInterceptorIndex(-1);
                }
            }
            if(callback != null){
                contextMap.put(id, (BeanFlowAsynchContext)beanFlowAsynchContext);
            }
        }
        
        
        
        public boolean isExistsFlow(Object id){
            return flowMap.containsKey(id);
        }
        
        public void suspendFlow(Object id) throws NoSuchBeanFlowIdException{
            getBeanFlowMonitor(id).suspend();
        }
        
        public boolean isSuspendFlow(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).isSuspend();
        }
        
        public boolean isSuspendedFlow(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).isSuspended();
        }
        
        public void resumeFlow(Object id) throws NoSuchBeanFlowIdException{
            getBeanFlowMonitor(id).resume();
        }
        
        public void stopFlow(Object id){
            BeanFlowAsynchContext context = getBeanFlowAsynchContext(id);
            if(context != null){
                context.cancel();
                contextMap.remove(id);
            }
            try{
                getBeanFlowMonitor(id).stop();
            }catch(NoSuchBeanFlowIdException e){
            }
        }
        
        public boolean isStopFlow(Object id){
            try{
                return getBeanFlowMonitor(id).isStop();
            }catch(NoSuchBeanFlowIdException e){
                return true;
            }
        }
        
        public boolean isStoppedFlow(Object id){
            try{
                return getBeanFlowMonitor(id).isStopped();
            }catch(NoSuchBeanFlowIdException e){
                return true;
            }
        }
        
        public String getFlowName(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).getFlowName();
        }
        
        public String getCurrentFlowName(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).getCurrentFlowName();
        }
        
        public String getCurrentStepName(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).getCurrentStepName();
        }
        
        public long getFlowStartTime(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).getStartTime();
        }
        
        public long getFlowCurrentProcessTime(Object id) throws NoSuchBeanFlowIdException{
            return getBeanFlowMonitor(id).getCurrentProcessTime();
        }
        
        public void clearMonitor(Object id) throws NoSuchBeanFlowIdException{
            getBeanFlowMonitor(id).clear();
        }
        
        public void cancel(Object id){
            BeanFlowAsynchContext context = getBeanFlowAsynchContext(id);
            if(context != null){
                context.cancel();
                contextMap.remove(id);
            }
        }
        
        public void end(Object id){
            try{
                stopFlow(id);
            }catch(NoSuchBeanFlowIdException e){
            }
            BeanFlow flow = flowMap.remove(id);
            if(flow != null){
                flow.end();
            }
            monitorMap.remove(id);
            contextMap.remove(id);
        }
        
        public Set<Object> getIdSet(){
            return flowMap.keySet();
        }
        
        protected class BeanFlowAsynchCallbackImpl implements BeanFlowAsynchCallback{
            protected Object id;
            protected BeanFlowAsynchCallback callback;
            public BeanFlowAsynchCallbackImpl(Object id, BeanFlowAsynchCallback callback) throws RemoteException{
                this.id = id;
                this.callback = callback;
            }
            
            public void reply(Object output, Throwable th) throws RemoteException{
                end(id);
                if(callback != null){
                    callback.reply(output, th);
                }
            }
        }
    }
}