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

import java.io.Serializable;
import java.lang.reflect.Method;
import java.rmi.server.*;

import jp.ossc.nimbus.beans.MethodEditor;
import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.service.aop.*;
import jp.ossc.nimbus.service.aop.invoker.*;
import jp.ossc.nimbus.service.performance.ResourceUsage;
import jp.ossc.nimbus.service.repository.*;
import jp.ossc.nimbus.service.distribute.ClusterService;
import jp.ossc.nimbus.service.distribute.KeepAliveListener;

/**
 * リモート呼び出しサーバサービス。<p>
 * {@link ProxyServerInvoker}インタフェースを実装したオブジェクトをJNDIにバインドする。<br>
 * {@link ProxyServerInvoker}インタフェースの実装クラスは、インターセプタを挟み込む機能や、実サービスの呼び出し方法をカスタマイズする機能を持つ。<br>
 * 実サービスの呼び出しを行う{@link Invoker}のデフォルト実装クラスは、{@link MethodReflectionCallInvokerService}で、呼び出しコンテキストの{@link jp.ossc.nimbus.service.aop.InvocationContext#getTargetObject() InvocationContext.getTargetObject()}で取得したサービス名のサービスを呼び出す。<br>
 * InvocationContext.getTargetObject()でサービス名が取得できない場合は、{@link #setRemoteServiceName(ServiceName)}で設定されたサービス名のサービスを呼び出す。<br>
 *
 * @author M.Takata
 */
public class ProxyServerService extends ServiceBase
 implements ProxyServerServiceMBean{
    
    private static final long serialVersionUID = -1165545167180777753L;
    
    private ServiceName remoteServiceName;
    private ServiceName interceptorChainListServiceName;
    private ServiceName invokerServiceName;
    private MethodReflectionCallInvokerService defaultInvoker;
    private ServiceName interceptorChainFactoryServiceName;
    private InterceptorChainFactory interceptorChainFactory;
    private ServiceName jndiRepositoryServiceName;
    private Repository jndiRepository;
    private String jndiName;
    private int rmiPort;
    private ServiceName clusterServiceName;
    private ClusterService cluster;
    private ServiceName resourceUsageServiceName;
    private ResourceUsage<Comparable<?>> resourceUsage;
    private ServiceName clientSocketFactoryServiceName;
    private RMIClientSocketFactory clientSocketFactory;
    private ServiceName serverSocketFactoryServiceName;
    private RMIServerSocketFactory serverSocketFactory;
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setRemoteServiceName(ServiceName name){
        remoteServiceName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getRemoteServiceName(){
        return remoteServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setInterceptorChainListServiceName(ServiceName name){
        interceptorChainListServiceName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getInterceptorChainListServiceName(){
        return interceptorChainListServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setInvokerServiceName(ServiceName name){
        invokerServiceName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getInvokerServiceName(){
        return invokerServiceName;
    }
    
    // ProxyServerServiceMBean
    public void setInterceptorChainFactoryServiceName(ServiceName name){
        interceptorChainFactoryServiceName = name;
    }
    // ProxyServerServiceMBean
    public ServiceName getInterceptorChainFactoryServiceName(){
        return interceptorChainFactoryServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setJndiName(String name){
        jndiName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public String getJndiName(){
        return jndiName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setJndiRepositoryServiceName(ServiceName name){
        jndiRepositoryServiceName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getJndiRepositoryServiceName(){
        return jndiRepositoryServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setRMIPort(int port){
        rmiPort = port;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public int getRMIPort(){
        return rmiPort;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setClusterServiceName(ServiceName name){
        clusterServiceName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getClusterServiceName(){
        return clusterServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setResourceUsageServiceName(ServiceName name){
        resourceUsageServiceName = name;
    }
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getResourceUsageServiceName(){
        return resourceUsageServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setRMIClientSocketFactoryServiceName(ServiceName name){
        clientSocketFactoryServiceName = name;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getRMIClientSocketFactoryServiceName(){
        return clientSocketFactoryServiceName;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public void setRMIServerSocketFactoryServiceName(ServiceName name){
        serverSocketFactoryServiceName = name;
    }
    
    // ProxyServerServiceMBeanのJavaDoc
    public ServiceName getRMIServerSocketFactoryServiceName(){
        return serverSocketFactoryServiceName;
    }
    
    public void setRMIClientSocketFactory(RMIClientSocketFactory csf){
        clientSocketFactory = csf;
    }
    
    public void setRMIServerSocketFactory(RMIServerSocketFactory ssf){
        serverSocketFactory = ssf;
    }
    
    public void setResourceUsage(ResourceUsage<Comparable<?>> usage){
        resourceUsage = usage;
    }
    
    /**
     * サービスの開始処理を行う。<p>
     *
     * @exception Exception サービスの開始処理に失敗した場合
     */
    public void startService() throws Exception{
        if(resourceUsage == null && resourceUsageServiceName != null){
            resourceUsage = ServiceManagerFactory
                .getServiceObject(resourceUsageServiceName);
        }
        if(interceptorChainFactoryServiceName != null){
            interceptorChainFactory = ServiceManagerFactory
                    .getServiceObject(interceptorChainFactoryServiceName);
        }else if(getInvokerServiceName() == null){
            if(defaultInvoker == null){
                defaultInvoker = new MethodReflectionCallInvokerService();
                defaultInvoker.create();
                defaultInvoker.start();
            }else{
                defaultInvoker.start();
            }
        }else{
            @SuppressWarnings("unused")
            Invoker invoker = (Invoker)ServiceManagerFactory
                .getServiceObject(getInvokerServiceName());
            defaultInvoker = null;
        }
        
        if(jndiRepositoryServiceName == null){
            throw new IllegalArgumentException(
                "jndiRepositoryServiceName must be specified."
            );
        }
        
        if(jndiName == null && remoteServiceName == null){
            throw new IllegalArgumentException(
                "jndiName or remoteServiceName must be specified."
            );
        }
        
        if(jndiName == null){
            jndiName = remoteServiceName.getServiceManagerName()
                 + '/' + remoteServiceName.getServiceName();
        }
        jndiRepository = ServiceManagerFactory
            .getServiceObject(jndiRepositoryServiceName);
        ProxyServerInvoker serverInvoker = null;
        if(interceptorChainFactory == null){
            serverInvoker = new ProxyServerInvokerImpl(
                interceptorChainListServiceName,
                invokerServiceName,
                defaultInvoker,
                remoteServiceName,
                resourceUsage,
                rmiPort,
                clientSocketFactory != null ? clientSocketFactory
                    : (clientSocketFactoryServiceName != null ? (RMIClientSocketFactory)ServiceManagerFactory.getServiceObject(clientSocketFactoryServiceName)
                        : null),
                serverSocketFactory != null ? serverSocketFactory
                    : (serverSocketFactoryServiceName != null ? (RMIServerSocketFactory)ServiceManagerFactory.getServiceObject(serverSocketFactoryServiceName)
                        : null)
            );
        }else{
            serverInvoker = new ProxyServerInvokerImpl(
                interceptorChainFactory,
                remoteServiceName,
                resourceUsage,
                rmiPort,
                clientSocketFactory != null ? clientSocketFactory
                    : (clientSocketFactoryServiceName != null ? (RMIClientSocketFactory)ServiceManagerFactory.getServiceObject(clientSocketFactoryServiceName)
                        : null),
                serverSocketFactory != null ? serverSocketFactory
                    : (serverSocketFactoryServiceName != null ? (RMIServerSocketFactory)ServiceManagerFactory.getServiceObject(serverSocketFactoryServiceName)
                        : null)
            );
        }
        if(!jndiRepository.register(jndiName, serverInvoker)){
            throw new Exception("Could not register in jndiRepository.");
        }
        if(clusterServiceName != null){
            cluster = ServiceManagerFactory.getServiceObject(clusterServiceName);
            if(cluster.isJoin()){
                throw new IllegalArgumentException("ClusterService already join.");
            }
            cluster.setOption(
                (Serializable)new ProxyServiceClientInvoker(
                    (ProxyServerInvoker)RemoteObject.toStub(serverInvoker)
                )
            );
            cluster.join();
        }
    }
    
    /**
     * サービスの停止処理を行う。<p>
     *
     * @exception Exception サービスの停止処理に失敗した場合
     */
    public void stopService() throws Exception{
        if(defaultInvoker != null){
            defaultInvoker.stop();
        }
        if(cluster != null){
            cluster.leave();
            cluster = null;
        }
        jndiRepository.unregister(jndiName);
    }
    
    /**
     * サービスの破棄処理を行う。<p>
     *
     * @exception Exception サービスの破棄処理に失敗した場合
     */
    public void destroyService() throws Exception{
        if(defaultInvoker != null){
            defaultInvoker.destroy();
            defaultInvoker = null;
        }
    }
    
    /**
     * {@link ProxyServerInvoker}実装クラス。<p>
     * 呼び出しコンテキスト、または、{@link ProxyServerService#getRemoteServiceName()}で、呼び出し対象のサービスを特定して、ローカルの{@link ServiceManager}から呼び出し対象のサービスを取得して、呼び出す。<br>
     * また、インターセプタを挟み込む機能や、実サービスの呼び出し方法をカスタマイズする機能を持つ。<br>
     *
     * @author M.Takata
     */
    public static class ProxyServerInvokerImpl extends UnicastRemoteObject
     implements ProxyServerInvoker{
        
        private static final long serialVersionUID = -2397154705661936441L;
        
        private final ServiceName interceptorChainListServiceName;
        private final ServiceName invokerServiceName;
        private final Invoker defaultInvoker;
        private final ServiceName remoteServiceName;
        private final InterceptorChainFactory interceptorChainFactory;
        private final ResourceUsage<Comparable<?>> resourceUsage;
        
        /**
         * インスタンスを生成する。<p>
         *
         * @param interceptorChainFactory {@link InterceptorChainFactory}サービスのサービス名
         * @param remoteServiceName リモート呼び出しされるサービスのサービス名
         * @param port RMIポート番号
         * @param csf RMIClientSocketFactory
         * @param ssf RMIServerSocketFactory
         * @exception java.rmi.RemoteException オブジェクトのエクスポートが失敗した場合
         */
        public ProxyServerInvokerImpl(
            InterceptorChainFactory interceptorChainFactory,
            ServiceName remoteServiceName,
            ResourceUsage<Comparable<?>> resourceUsage,
            int port,
            RMIClientSocketFactory csf,
            RMIServerSocketFactory ssf
        ) throws java.rmi.RemoteException{
            super(port, csf, ssf);
            this.interceptorChainListServiceName = null;
            this.invokerServiceName = null;
            this.defaultInvoker = null;
            this.interceptorChainFactory = interceptorChainFactory;
            this.remoteServiceName = remoteServiceName;
            this.resourceUsage = resourceUsage;
        }
        
        /**
         * インスタンスを生成する。<p>
         *
         * @param interceptorChainListServiceName {@link InterceptorChainList}サービスのサービス名
         * @param invokerServiceName {@link Invoker}サービスのサービス名
         * @param defaultInvoker デフォルトの{@link Invoker}サービス
         * @param remoteServiceName リモート呼び出しされるサービスのサービス名
         * @param port RMIポート番号
         * @param csf RMIClientSocketFactory
         * @param ssf RMIServerSocketFactory
         * @exception java.rmi.RemoteException オブジェクトのエクスポートが失敗した場合
         */
        public ProxyServerInvokerImpl(
            ServiceName interceptorChainListServiceName,
            ServiceName invokerServiceName,
            Invoker defaultInvoker,
            ServiceName remoteServiceName,
            ResourceUsage<Comparable<?>> resourceUsage,
            int port,
            RMIClientSocketFactory csf,
            RMIServerSocketFactory ssf
        ) throws java.rmi.RemoteException{
            super(port, csf, ssf);
            this.interceptorChainListServiceName = interceptorChainListServiceName;
            this.invokerServiceName = invokerServiceName;
            this.defaultInvoker = defaultInvoker;
            this.remoteServiceName = remoteServiceName;
            interceptorChainFactory = null;
            this.resourceUsage = resourceUsage;
        }
        
        /**
         * リモート呼び出しされるサービスを呼び出す。<p>
         * 呼び出しコンテキストの{@link jp.ossc.nimbus.service.aop.InvocationContext#getTargetObject() InvocationContext.getTargetObject()}で取得したサービス名のサービスをローカルの{@link ServiceManager}から取得して、{@link jp.ossc.nimbus.service.aop.InvocationContext#setTargetObject(Object) InvocationContext.setTargetObject(Object)}で、呼び出しコンテキストに設定する。<br>
         * InvocationContext.getTargetObject()でサービス名が取得できない場合は、{@link ProxyServerService#setRemoteServiceName(ServiceName)}で設定されたサービス名のサービスを取得して、呼び出しコンテキストに設定する。<br>
         * その後、コンストラクタで指定された{@link InterceptorChainList}と{@link Invoker}を持った、{@link InterceptorChain}を生成して、呼び出す。<br>
         * 
         * @param context 呼び出しコンテキスト
         * @return サービスの呼び出し結果
         * @exception Exception リモート呼び出しされるサービスの呼び出しに失敗した場合
         */
        public Object invoke(InvocationContext context) throws Exception{
            
            InterceptorChain chain = null;
            if(interceptorChainFactory == null){
                chain = new DefaultInterceptorChain(
                    interceptorChainListServiceName,
                    invokerServiceName
                );
                if(invokerServiceName == null && defaultInvoker != null){
                    ((DefaultInterceptorChain)chain).setInvoker(defaultInvoker);
                }
            }else{
                StringBuilder key = new StringBuilder();
                Object target = context.getTargetObject();
                if(target != null){
                    key.append(target);
                }
                if(context instanceof MethodInvocationContext){
                    Method method = ((MethodInvocationContext)context).getTargetMethod();
                    if(method != null){
                        final MethodEditor editor = new MethodEditor();
                        editor.setValue(method);
                        key.append(':').append(editor.getAsText());
                    }
                }
                chain = interceptorChainFactory.getInterceptorChain(key.length() == 0 ? null : key.toString());
            }
            
            ServiceName serviceName = null;
            if(context.getTargetObject() != null
                && context.getTargetObject() instanceof ServiceName){
                serviceName = (ServiceName)context.getTargetObject();
                if(remoteServiceName != null
                     && !remoteServiceName.equals(serviceName)){
                    throw new IllegalAccessException(
                        serviceName + " don't be allowed access."
                    );
                }
            }else{
                serviceName = remoteServiceName;
            }
            if(serviceName != null){
                context.setTargetObject(
                    ServiceManagerFactory.getServiceObject(serviceName)
                );
                try{
                    chain.setCurrentInterceptorIndex(-1);
                    return chain.invokeNext(context);
                }catch(Exception e){
                    throw e;
                }catch(Throwable e){
                    e.printStackTrace();
                    return null;
                }finally{
                    chain.setCurrentInterceptorIndex(-1);
                }
            }else{
                throw new ServiceNotFoundException(null);
            }
        }
        
        public boolean isAlive(ServiceName name){
            ServiceName serviceName = remoteServiceName;
            if(name != null){
                if(remoteServiceName != null
                     && !remoteServiceName.equals(name)){
                    return false;
                }
                serviceName = name;
            }
            if(serviceName == null){
                return true;
            }else{
                try{
                    final Service service = ServiceManagerFactory.getService(serviceName);
                    return service != null && service.getState() == State.STARTED;
                }catch(ServiceNotFoundException e){
                    return false;
                }
            }
        }
        
        // KeepAliveCheckInvokerのJavaDoc
        @SuppressWarnings("unchecked")
        @Override
        public <T extends Comparable<?>> T getResourceUsage(){
            return resourceUsage == null ? null : (T)resourceUsage.getUsage();
        }
     }
    
    public static class ProxyServiceClientInvoker implements KeepAliveCheckInvoker, Serializable{
        
        private static final long serialVersionUID = 6931936167248832069L;
        
        private ProxyServerInvoker serverInvoker;
        
        public ProxyServiceClientInvoker(){
        }
        
        public ProxyServiceClientInvoker(ProxyServerInvoker server){
            serverInvoker = server;
        }
        
        // KeepAliveCheckInvokerのJavaDoc
        public Object invoke(InvocationContext context) throws Throwable{
            try{
                return serverInvoker.invoke(context);
            }catch(java.rmi.RemoteException e){
                throw new InvokeException(e);
            }
        }
        
        // KeepAliveCheckInvokerのJavaDoc
        public boolean isAlive(){
            try{
                return serverInvoker.isAlive(null);
            }catch(Exception e){
                return false;
            }
        }
        
        // KeepAliveCheckInvokerのJavaDoc
        public void addKeepAliveListener(KeepAliveListener listener){
            throw new UnsupportedOperationException();
        }
        
        // KeepAliveCheckInvokerのJavaDoc
        public void removeKeepAliveListener(KeepAliveListener listener){
            throw new UnsupportedOperationException();
        }
        
        // KeepAliveCheckInvokerのJavaDoc
        public void clearKeepAliveListener(){
            throw new UnsupportedOperationException();
        }
        public <T extends Comparable<?>> T getResourceUsage(){
            try{
                return serverInvoker.getResourceUsage();
            }catch(Exception e){
                return null;
            }
        }
    }
}