/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.rpc.cluster.support;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcInvocation;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.dubbo.rpc.cluster.Directory;
import com.alibaba.dubbo.rpc.cluster.directory.StaticDirectory;
import com.alibaba.dubbo.rpc.protocol.AbstractInvoker;

/**
 * FailoverClusterInvokerTest
 * @author liuchao
 *
 */
@SuppressWarnings("unchecked")
public class FailoverClusterInvokerTest {
    List<Invoker<FailoverClusterInvokerTest>> invokers = new ArrayList<Invoker<FailoverClusterInvokerTest>>();
    int retries = 5;
    URL url = URL.valueOf("test://test:11/test?retries="+retries);
    Invoker<FailoverClusterInvokerTest> invoker1 = EasyMock.createMock(Invoker.class);
    Invoker<FailoverClusterInvokerTest> invoker2 = EasyMock.createMock(Invoker.class);
    RpcInvocation invocation = new RpcInvocation();
    Directory<FailoverClusterInvokerTest> dic ;
    Result result = new RpcResult();
    /**
     * @throws java.lang.Exception
     */
    
    @Before
    public void setUp() throws Exception {
        
        dic = EasyMock.createMock(Directory.class);
        
        EasyMock.expect(dic.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(dic.list(invocation)).andReturn(invokers).anyTimes();
        EasyMock.expect(dic.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        invocation.setMethodName("method1");
        EasyMock.replay(dic);
        
        invokers.add(invoker1);
        invokers.add(invoker2);
    }

    
    @Test
    public void testInvokeWithRuntimeException() {
        EasyMock.reset(invoker1);
        EasyMock.expect(invoker1.invoke(invocation)).andThrow(new RuntimeException()).anyTimes();
        EasyMock.expect(invoker1.isAvailable()).andReturn(true).anyTimes();
        EasyMock.expect(invoker1.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(invoker1.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        EasyMock.replay(invoker1);
        
        EasyMock.reset(invoker2);
        EasyMock.expect(invoker2.invoke(invocation)).andThrow(new RuntimeException()).anyTimes();
        EasyMock.expect(invoker2.isAvailable()).andReturn(true).anyTimes();
        EasyMock.expect(invoker2.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(invoker2.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        EasyMock.replay(invoker2);
        
        FailoverClusterInvoker<FailoverClusterInvokerTest> invoker = new FailoverClusterInvoker<FailoverClusterInvokerTest>(dic);
        try {
            invoker.invoke(invocation);
            fail();
        } catch (RpcException expected) {
            assertEquals(0,expected.getCode());
            assertFalse(expected.getCause() instanceof RpcException);
        }
    }
    
    @Test()
    public void testInvokeWithRPCException() {
        
        EasyMock.reset(invoker1);
        EasyMock.expect(invoker1.invoke(invocation)).andThrow(new RpcException()).anyTimes();
        EasyMock.expect(invoker1.isAvailable()).andReturn(true).anyTimes();
        EasyMock.expect(invoker1.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(invoker1.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        EasyMock.replay(invoker1);
        
        EasyMock.reset(invoker2);
        EasyMock.expect(invoker2.invoke(invocation)).andReturn(result).anyTimes();
        EasyMock.expect(invoker2.isAvailable()).andReturn(true).anyTimes();
        EasyMock.expect(invoker2.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(invoker2.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        EasyMock.replay(invoker2);
        
        FailoverClusterInvoker<FailoverClusterInvokerTest> invoker = new FailoverClusterInvoker<FailoverClusterInvokerTest>(dic);
        for(int i=0;i<100;i++){
            Result ret = invoker.invoke(invocation);
            assertSame(result, ret);
        }
    }
    
    @Test()
    public void testInvoke_retryTimes() {
        
        EasyMock.reset(invoker1);
        EasyMock.expect(invoker1.invoke(invocation)).andThrow(new RpcException(RpcException.TIMEOUT_EXCEPTION)).anyTimes();
        EasyMock.expect(invoker1.isAvailable()).andReturn(false).anyTimes();
        EasyMock.expect(invoker1.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(invoker1.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        EasyMock.replay(invoker1);
        
        EasyMock.reset(invoker2);
        EasyMock.expect(invoker2.invoke(invocation)).andThrow(new RpcException()).anyTimes();
        EasyMock.expect(invoker2.isAvailable()).andReturn(false).anyTimes();
        EasyMock.expect(invoker2.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(invoker2.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        EasyMock.replay(invoker2);
        
        FailoverClusterInvoker<FailoverClusterInvokerTest> invoker = new FailoverClusterInvoker<FailoverClusterInvokerTest>(dic);
        try{
            Result ret = invoker.invoke(invocation);
            assertSame(result, ret);
            fail();
        }catch (RpcException expected) {
            assertTrue(expected.isTimeout());
            assertTrue(expected.getMessage().indexOf((retries+1)+" times")>0);
        }
    }
    
    @Test()
    public void testNoInvoke() {
        dic = EasyMock.createMock(Directory.class);
        
        EasyMock.expect(dic.getUrl()).andReturn(url).anyTimes();
        EasyMock.expect(dic.list(invocation)).andReturn(null).anyTimes();
        EasyMock.expect(dic.getInterface()).andReturn(FailoverClusterInvokerTest.class).anyTimes();
        invocation.setMethodName("method1");
        EasyMock.replay(dic);
        
        invokers.add(invoker1);
        
        
        FailoverClusterInvoker<FailoverClusterInvokerTest> invoker = new FailoverClusterInvoker<FailoverClusterInvokerTest>(dic);
        try {
            invoker.invoke(invocation);
            fail();
        } catch (RpcException expected) {
            assertFalse(expected.getCause() instanceof RpcException);
        }
    }
    
    /**
     * 测试在调用重试过程中,directory列表变更,invoke重试时重新进行list选择 
     */
    @Test
    public void testInvokerDestoryAndReList(){
    	final URL url = URL.valueOf("test://localhost/"+ Demo.class.getName() + "?loadbalance=roundrobin&retries="+retries);
    	RpcException exception = new RpcException(RpcException.TIMEOUT_EXCEPTION);
    	MockInvoker<Demo> invoker1 = new MockInvoker<Demo>(Demo.class, url);
    	invoker1.setException(exception);
    	
    	MockInvoker<Demo> invoker2 = new MockInvoker<Demo>(Demo.class, url);
    	invoker2.setException(exception);
    	
        final List<Invoker<Demo>> invokers = new ArrayList<Invoker<Demo>>();
        invokers.add(invoker1);
        invokers.add(invoker2);
        
        Callable<Object> callable = new Callable<Object>() {
			public Object call() throws Exception {
				//模拟invoker全部被destroy掉
				for (Invoker<Demo> invoker:invokers){
					invoker.destroy();
				}
				invokers.clear();
				MockInvoker<Demo> invoker3  = new MockInvoker<Demo>(Demo.class, url);
				invokers.add(invoker3);
				return null;
			}
		};
		invoker1.setCallable(callable);
        invoker2.setCallable(callable);
        
        RpcInvocation inv = new RpcInvocation();
        inv.setMethodName("test");
        
        Directory<Demo> dic = new MockDirectory<Demo>(url, invokers);
        
        FailoverClusterInvoker<Demo> clusterinvoker = new FailoverClusterInvoker<Demo>(dic);
        clusterinvoker.invoke(inv);
    }
    
    public static interface Demo{}
    
    public static class MockInvoker<T> extends AbstractInvoker<T> {
    	URL url;
    	boolean available = true;
    	boolean destoryed = false;
    	Result result ;
    	RpcException exception;
    	Callable<?> callable;
    	
		public MockInvoker(Class<T> type, URL url) {
			super(type, url);
		}

		public void setResult(Result result) {
			this.result = result;
		}
		public void setException(RpcException exception) {
			this.exception = exception;
		}
		public void setCallable(Callable<?> callable) {
			this.callable = callable;
		}

		@Override
		protected Result doInvoke(Invocation invocation) throws Throwable {
			if (callable != null) {
				try {
					callable.call();
				} catch (Exception e) {
					throw new RpcException(e);
				}
			}
			if (exception != null) {
				throw exception;
			} else {
				return result;
			}
		}
    }
    
    public class MockDirectory<T> extends StaticDirectory<T> {
		public MockDirectory(URL url , List<Invoker<T>> invokers) {
			super(url, invokers);
		}
		@Override
		protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {
			return new ArrayList<Invoker<T>>(super.doList(invocation));
		}
    }
}