/**
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.hystrix;

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

import java.util.Random;

import com.hystrix.junit.HystrixRequestContextRule;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;

import com.netflix.hystrix.HystrixCircuitBreaker.HystrixCircuitBreakerImpl;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import rx.Observable;

/**
 * These tests each use a different command key to ensure that running them in parallel doesn't allow the state
 * built up during a test to cause others to fail
 */
public class HystrixCircuitBreakerTest {

    @Rule
    public HystrixRequestContextRule ctx = new HystrixRequestContextRule();

    @Before
    public void init() {
        for (HystrixCommandMetrics metricsInstance: HystrixCommandMetrics.getInstances()) {
            metricsInstance.resetStream();
        }

        HystrixCommandMetrics.reset();
        HystrixCircuitBreaker.Factory.reset();
        Hystrix.reset();
    }

    /**
     * A simple circuit breaker intended for unit testing of the {@link HystrixCommand} object, NOT production use.
     * <p>
     * This uses simple logic to 'trip' the circuit after 3 subsequent failures and doesn't recover.
     */
    public static class TestCircuitBreaker implements HystrixCircuitBreaker {

        final HystrixCommandMetrics metrics;
        private boolean forceShortCircuit = false;

        public TestCircuitBreaker() {
            this.metrics = getMetrics(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter());
            forceShortCircuit = false;
        }

        public TestCircuitBreaker(HystrixCommandKey commandKey) {
            this.metrics = getMetrics(commandKey, HystrixCommandPropertiesTest.getUnitTestPropertiesSetter());
            forceShortCircuit = false;
        }

        public TestCircuitBreaker setForceShortCircuit(boolean value) {
            this.forceShortCircuit = value;
            return this;
        }

        @Override
        public boolean isOpen() {
            System.out.println("metrics : " + metrics.getCommandKey().name() + " : " + metrics.getHealthCounts());
            if (forceShortCircuit) {
                return true;
            } else {
                return metrics.getHealthCounts().getErrorCount() >= 3;
            }
        }

        @Override
        public void markSuccess() {
            // we don't need to do anything since we're going to permanently trip the circuit
        }

        @Override
        public void markNonSuccess() {

        }

        @Override
        public boolean attemptExecution() {
            return !isOpen();
        }

        @Override
        public boolean allowRequest() {
            return !isOpen();
        }

    }


    /**
     * Test that if all 'marks' are successes during the test window that it does NOT trip the circuit.
     * Test that if all 'marks' are failures during the test window that it trips the circuit.
     */
    @Test
    public void testTripCircuit() {
        String key = "cmd-A";
        try {
            HystrixCommand<Boolean> cmd1 = new SuccessCommand(key, 1);
            HystrixCommand<Boolean> cmd2 = new SuccessCommand(key, 1);
            HystrixCommand<Boolean> cmd3 = new SuccessCommand(key, 1);
            HystrixCommand<Boolean> cmd4 = new SuccessCommand(key, 1);

            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            cmd1.execute();
            cmd2.execute();
            cmd3.execute();
            cmd4.execute();

            // this should still allow requests as everything has been successful
            Thread.sleep(100);
            //assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            // fail
            HystrixCommand<Boolean> cmd5 = new FailureCommand(key, 1);
            HystrixCommand<Boolean> cmd6 = new FailureCommand(key, 1);
            HystrixCommand<Boolean> cmd7 = new FailureCommand(key, 1);
            HystrixCommand<Boolean> cmd8 = new FailureCommand(key, 1);
            cmd5.execute();
            cmd6.execute();
            cmd7.execute();
            cmd8.execute();

            // everything has failed in the test window so we should return false now
            Thread.sleep(100);
            assertFalse(cb.allowRequest());
            assertTrue(cb.isOpen());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Test that if the % of failures is higher than the threshold that the circuit trips.
     */
    @Test
    public void testTripCircuitOnFailuresAboveThreshold() {
        String key = "cmd-B";
        try {
            HystrixCommand<Boolean> cmd1 = new SuccessCommand(key, 60);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            // success with high latency
            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new SuccessCommand(key, 1);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new SuccessCommand(key, 1);
            cmd4.execute();
            HystrixCommand<Boolean> cmd5 = new FailureCommand(key, 1);
            cmd5.execute();
            HystrixCommand<Boolean> cmd6 = new SuccessCommand(key, 1);
            cmd6.execute();
            HystrixCommand<Boolean> cmd7 = new FailureCommand(key, 1);
            cmd7.execute();
            HystrixCommand<Boolean> cmd8 = new FailureCommand(key, 1);
            cmd8.execute();

            // this should trip the circuit as the error percentage is above the threshold
            Thread.sleep(100);
            assertFalse(cb.allowRequest());
            assertTrue(cb.isOpen());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Test that if the % of failures is higher than the threshold that the circuit trips.
     */
    @Test
    public void testCircuitDoesNotTripOnFailuresBelowThreshold() {
        String key = "cmd-C";
        try {
            HystrixCommand<Boolean> cmd1 = new SuccessCommand(key, 60);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            // success with high latency
            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new SuccessCommand(key, 1);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new SuccessCommand(key, 1);
            cmd4.execute();
            HystrixCommand<Boolean> cmd5 = new SuccessCommand(key, 1);
            cmd5.execute();
            HystrixCommand<Boolean> cmd6 = new FailureCommand(key, 1);
            cmd6.execute();
            HystrixCommand<Boolean> cmd7 = new SuccessCommand(key, 1);
            cmd7.execute();
            HystrixCommand<Boolean> cmd8 = new FailureCommand(key, 1);
            cmd8.execute();

            // this should remain closed as the failure threshold is below the percentage limit
            Thread.sleep(100);
            System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
            System.out.println("Current CircuitBreaker Status : " + cmd1.getMetrics().getHealthCounts());
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Test that if all 'marks' are timeouts that it will trip the circuit.
     */
    @Test
    public void testTripCircuitOnTimeouts() {
        String key = "cmd-D";
        try {
            HystrixCommand<Boolean> cmd1 = new TimeoutCommand(key);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            // success with high latency
            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new TimeoutCommand(key);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new TimeoutCommand(key);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new TimeoutCommand(key);
            cmd4.execute();

            // everything has been a timeout so we should not allow any requests
            Thread.sleep(100);
            assertFalse(cb.allowRequest());
            assertTrue(cb.isOpen());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Test that if the % of timeouts is higher than the threshold that the circuit trips.
     */
    @Test
    public void testTripCircuitOnTimeoutsAboveThreshold() {
        String key = "cmd-E";
        try {
            HystrixCommand<Boolean> cmd1 = new SuccessCommand(key, 60);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            // success with high latency
            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new SuccessCommand(key, 1);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new TimeoutCommand(key);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new SuccessCommand(key, 1);
            cmd4.execute();
            HystrixCommand<Boolean> cmd5 = new TimeoutCommand(key);
            cmd5.execute();
            HystrixCommand<Boolean> cmd6 = new TimeoutCommand(key);
            cmd6.execute();
            HystrixCommand<Boolean> cmd7 = new SuccessCommand(key, 1);
            cmd7.execute();
            HystrixCommand<Boolean> cmd8 = new TimeoutCommand(key);
            cmd8.execute();
            HystrixCommand<Boolean> cmd9 = new TimeoutCommand(key);
            cmd9.execute();

            // this should trip the circuit as the error percentage is above the threshold
            Thread.sleep(100);
            assertTrue(cb.isOpen());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Test that on an open circuit that a single attempt will be allowed after a window of time to see if issues are resolved.
     */
    @Test
    public void testSingleTestOnOpenCircuitAfterTimeWindow() {
        String key = "cmd-F";
        try {
            int sleepWindow = 200;
            HystrixCommand<Boolean> cmd1 = new FailureCommand(key, 60);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new FailureCommand(key, 1);
            cmd4.execute();

            // everything has failed in the test window so we should return false now
            Thread.sleep(100);
            assertFalse(cb.allowRequest());
            assertTrue(cb.isOpen());

            // wait for sleepWindow to pass
            Thread.sleep(sleepWindow + 50);

            // we should now allow 1 request
            assertTrue(cb.attemptExecution());
            // but the circuit should still be open
            assertTrue(cb.isOpen());
            // and further requests are still blocked
            assertFalse(cb.attemptExecution());

        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Test that an open circuit is closed after 1 success.  This also ensures that the rolling window (containing failures) is cleared after the sleep window
     * Otherwise, the next bucket roll would produce another signal to fail unless it is explicitly cleared (via {@link HystrixCommandMetrics#resetStream()}.
     */
    @Test
    public void testCircuitClosedAfterSuccess() {
        String key = "cmd-G";
        try {
            int sleepWindow = 100;
            HystrixCommand<Boolean> cmd1 = new FailureCommand(key, 1, sleepWindow);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1, sleepWindow);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1, sleepWindow);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new TimeoutCommand(key, sleepWindow);
            cmd4.execute();

            // everything has failed in the test window so we should return false now
            Thread.sleep(100);
            System.out.println("ReqLog : " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
            System.out.println("CircuitBreaker state 1 : " + cmd1.getMetrics().getHealthCounts());
            assertTrue(cb.isOpen());

            // wait for sleepWindow to pass
            Thread.sleep(sleepWindow + 50);

            // but the circuit should still be open
            assertTrue(cb.isOpen());

            // we should now allow 1 request, and upon success, should cause the circuit to be closed
            HystrixCommand<Boolean> cmd5 = new SuccessCommand(key, 60, sleepWindow);
            Observable<Boolean> asyncResult = cmd5.observe();

            // and further requests are still blocked while the singleTest command is in flight
            assertFalse(cb.allowRequest());

            asyncResult.toBlocking().single();

            // all requests should be open again

            Thread.sleep(100);
            System.out.println("CircuitBreaker state 2 : " + cmd1.getMetrics().getHealthCounts());
            assertTrue(cb.allowRequest());
            assertTrue(cb.allowRequest());
            assertTrue(cb.allowRequest());
            // and the circuit should be closed again
            assertFalse(cb.isOpen());

        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Over a period of several 'windows' a single attempt will be made and fail and then finally succeed and close the circuit.
     * <p>
     * Ensure the circuit is kept open through the entire testing period and that only the single attempt in each window is made.
     */
    @Test
    public void testMultipleTimeWindowRetriesBeforeClosingCircuit() {
        String key = "cmd-H";
        try {
            int sleepWindow = 200;
            HystrixCommand<Boolean> cmd1 = new FailureCommand(key, 60);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new TimeoutCommand(key);
            cmd4.execute();

            // everything has failed in the test window so we should return false now
            System.out.println("!!!! 1: 4 failures, circuit will open on recalc");
            Thread.sleep(100);

            assertTrue(cb.isOpen());

            // wait for sleepWindow to pass
            System.out.println("!!!! 2: Sleep window starting where all commands fail-fast");
            Thread.sleep(sleepWindow + 50);
            System.out.println("!!!! 3: Sleep window over, should allow singleTest()");

            // but the circuit should still be open
            assertTrue(cb.isOpen());

            // we should now allow 1 request, and upon failure, should not affect the circuit breaker, which should remain open
            HystrixCommand<Boolean> cmd5 = new FailureCommand(key, 60);
            Observable<Boolean> asyncResult5 = cmd5.observe();
            System.out.println("!!!! 4: Kicked off the single-test");

            // and further requests are still blocked while the singleTest command is in flight
            assertFalse(cb.allowRequest());
            System.out.println("!!!! 5: Confirmed that no other requests go out during single-test");

            asyncResult5.toBlocking().single();
            System.out.println("!!!! 6: SingleTest just completed");

            // all requests should still be blocked, because the singleTest failed
            assertFalse(cb.allowRequest());
            assertFalse(cb.allowRequest());
            assertFalse(cb.allowRequest());

            // wait for sleepWindow to pass
            System.out.println("!!!! 2nd sleep window START");
            Thread.sleep(sleepWindow + 50);
            System.out.println("!!!! 2nd sleep window over");

            // we should now allow 1 request, and upon failure, should not affect the circuit breaker, which should remain open
            HystrixCommand<Boolean> cmd6 = new FailureCommand(key, 60);
            Observable<Boolean> asyncResult6 = cmd6.observe();
            System.out.println("2nd singleTest just kicked off");

            //and further requests are still blocked while the singleTest command is in flight
            assertFalse(cb.allowRequest());
            System.out.println("confirmed that 2nd singletest only happened once");

            asyncResult6.toBlocking().single();
            System.out.println("2nd singleTest now over");

            // all requests should still be blocked, because the singleTest failed
            assertFalse(cb.allowRequest());
            assertFalse(cb.allowRequest());
            assertFalse(cb.allowRequest());

            // wait for sleepWindow to pass
            Thread.sleep(sleepWindow + 50);

            // but the circuit should still be open
            assertTrue(cb.isOpen());

            // we should now allow 1 request, and upon success, should cause the circuit to be closed
            HystrixCommand<Boolean> cmd7 = new SuccessCommand(key, 60);
            Observable<Boolean> asyncResult7 = cmd7.observe();

            // and further requests are still blocked while the singleTest command is in flight
            assertFalse(cb.allowRequest());

            asyncResult7.toBlocking().single();

            // all requests should be open again
            assertTrue(cb.allowRequest());
            assertTrue(cb.allowRequest());
            assertTrue(cb.allowRequest());
            // and the circuit should be closed again
            assertFalse(cb.isOpen());

            // and the circuit should be closed again
            assertFalse(cb.isOpen());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * When volume of reporting during a statistical window is lower than a defined threshold the circuit
     * will not trip regardless of whatever statistics are calculated.
     */
    @Test
    public void testLowVolumeDoesNotTripCircuit() {
        String key = "cmd-I";
        try {
            int sleepWindow = 200;
            int lowVolume = 5;

            HystrixCommand<Boolean> cmd1 = new FailureCommand(key, 60, sleepWindow, lowVolume);
            HystrixCircuitBreaker cb = cmd1.circuitBreaker;

            // this should start as allowing requests
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

            cmd1.execute();
            HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1, sleepWindow, lowVolume);
            cmd2.execute();
            HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1, sleepWindow, lowVolume);
            cmd3.execute();
            HystrixCommand<Boolean> cmd4 = new FailureCommand(key, 1, sleepWindow, lowVolume);
            cmd4.execute();

            // even though it has all failed we won't trip the circuit because the volume is low
            Thread.sleep(100);
            assertTrue(cb.allowRequest());
            assertFalse(cb.isOpen());

        } catch (Exception e) {
            e.printStackTrace();
            fail("Error occurred: " + e.getMessage());
        }
    }

    /**
     * Utility method for creating {@link HystrixCommandMetrics} for unit tests.
     */
    private static HystrixCommandMetrics getMetrics(HystrixCommandProperties.Setter properties) {
        return HystrixCommandMetrics.getInstance(CommandKeyForUnitTest.KEY_ONE, CommandOwnerForUnitTest.OWNER_ONE, ThreadPoolKeyForUnitTest.THREAD_POOL_ONE, HystrixCommandPropertiesTest.asMock(properties));
    }


    /**
     * Utility method for creating {@link HystrixCommandMetrics} for unit tests.
     */
    private static HystrixCommandMetrics getMetrics(HystrixCommandKey commandKey, HystrixCommandProperties.Setter properties) {
        return HystrixCommandMetrics.getInstance(commandKey, CommandOwnerForUnitTest.OWNER_ONE, ThreadPoolKeyForUnitTest.THREAD_POOL_ONE, HystrixCommandPropertiesTest.asMock(properties));
    }

    /**
     * Utility method for creating {@link HystrixCircuitBreaker} for unit tests.
     */
    private static HystrixCircuitBreaker getCircuitBreaker(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandMetrics metrics, HystrixCommandProperties.Setter properties) {
        return new HystrixCircuitBreakerImpl(key, commandGroup, HystrixCommandPropertiesTest.asMock(properties), metrics);
    }

    private static enum CommandOwnerForUnitTest implements HystrixCommandGroupKey {
        OWNER_ONE, OWNER_TWO
    }

    private static enum ThreadPoolKeyForUnitTest implements HystrixThreadPoolKey {
        THREAD_POOL_ONE, THREAD_POOL_TWO
    }

    private static enum CommandKeyForUnitTest implements HystrixCommandKey {
        KEY_ONE, KEY_TWO
    }

    // ignoring since this never ends ... useful for testing https://github.com/Netflix/Hystrix/issues/236
    @Ignore
    @Test
    public void testSuccessClosesCircuitWhenBusy() throws InterruptedException {
        HystrixPlugins.getInstance().registerCommandExecutionHook(new MyHystrixCommandExecutionHook());
        try {
            performLoad(200, 0, 40);
            performLoad(250, 100, 40);
            performLoad(600, 0, 40);
        } finally {
            Hystrix.reset();
        }

    }

    void performLoad(int totalNumCalls, int errPerc, int waitMillis) {

        Random rnd = new Random();

        for (int i = 0; i < totalNumCalls; i++) {
            //System.out.println(i);

            try {
                boolean err = rnd.nextFloat() * 100 < errPerc;

                TestCommand cmd = new TestCommand(err);
                cmd.execute();

            } catch (Exception e) {
                //System.err.println(e.getMessage());
            }

            try {
                Thread.sleep(waitMillis);
            } catch (InterruptedException e) {
            }
        }
    }

    public class TestCommand extends HystrixCommand<String> {

        boolean error;

        public TestCommand(final boolean error) {
            super(HystrixCommandGroupKey.Factory.asKey("group"));

            this.error = error;
        }

        @Override
        protected String run() throws Exception {

            if (error) {
                throw new Exception("forced failure");
            } else {
                return "success";
            }
        }

        @Override
        protected String getFallback() {
            if (isFailedExecution()) {
                return getFailedExecutionException().getMessage();
            } else {
                return "other fail reason";
            }
        }

    }

    private class Command extends HystrixCommand<Boolean> {

        private final boolean shouldFail;
        private final boolean shouldFailWithBadRequest;
        private final long latencyToAdd;

        public Command(String commandKey, boolean shouldFail, boolean shouldFailWithBadRequest, long latencyToAdd, int sleepWindow, int requestVolumeThreshold) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Command")).andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)).
                    andCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter().
                            withExecutionTimeoutInMilliseconds(500).
                            withCircuitBreakerRequestVolumeThreshold(requestVolumeThreshold).
                            withCircuitBreakerSleepWindowInMilliseconds(sleepWindow)));
            this.shouldFail = shouldFail;
            this.shouldFailWithBadRequest = shouldFailWithBadRequest;
            this.latencyToAdd = latencyToAdd;
        }

        public Command(String commandKey, boolean shouldFail, long latencyToAdd) {
            this(commandKey, shouldFail, false, latencyToAdd, 200, 1);
        }

        @Override
        protected Boolean run() throws Exception {
            Thread.sleep(latencyToAdd);
            if (shouldFail) {
                throw new RuntimeException("induced failure");
            }
            if (shouldFailWithBadRequest) {
                throw new HystrixBadRequestException("bad request");
            }
            return true;
        }

        @Override
        protected Boolean getFallback() {
            return false;
        }
    }

    private class SuccessCommand extends Command {

        SuccessCommand(String commandKey, long latencyToAdd) {
            super(commandKey, false, latencyToAdd);
        }

        SuccessCommand(String commandKey, long latencyToAdd, int sleepWindow) {
            super(commandKey, false, false, latencyToAdd, sleepWindow, 1);
        }
    }

    private class FailureCommand extends Command {

        FailureCommand(String commandKey, long latencyToAdd) {
            super(commandKey, true, latencyToAdd);
        }

        FailureCommand(String commandKey, long latencyToAdd, int sleepWindow) {
            super(commandKey, true, false, latencyToAdd, sleepWindow, 1);
        }

        FailureCommand(String commandKey, long latencyToAdd, int sleepWindow, int requestVolumeThreshold) {
            super(commandKey, true, false, latencyToAdd, sleepWindow, requestVolumeThreshold);
        }
    }

    private class TimeoutCommand extends Command {

        TimeoutCommand(String commandKey) {
            super(commandKey, false, 2000);
        }

        TimeoutCommand(String commandKey, int sleepWindow) {
            super(commandKey, false, false, 2000, sleepWindow, 1);
        }
    }

    private class BadRequestCommand extends Command {
        BadRequestCommand(String commandKey, long latencyToAdd) {
            super(commandKey, false, true, latencyToAdd, 200, 1);
        }

        BadRequestCommand(String commandKey, long latencyToAdd, int sleepWindow) {
            super(commandKey, false, true, latencyToAdd, sleepWindow, 1);
        }
    }

    public class MyHystrixCommandExecutionHook extends HystrixCommandExecutionHook {

        @Override
        public <T> T onComplete(final HystrixInvokable<T> command, final T response) {

            logHC(command, response);

            return super.onComplete(command, response);
        }

        private int counter = 0;

        private <T> void logHC(HystrixInvokable<T> command, T response) {

            if(command instanceof HystrixInvokableInfo) {
                HystrixInvokableInfo<T> commandInfo = (HystrixInvokableInfo<T>)command;
            HystrixCommandMetrics metrics = commandInfo.getMetrics();
            System.out.println("cb/error-count/%/total: "
                    + commandInfo.isCircuitBreakerOpen() + " "
                    + metrics.getHealthCounts().getErrorCount() + " "
                    + metrics.getHealthCounts().getErrorPercentage() + " "
                    + metrics.getHealthCounts().getTotalRequests() + "  => " + response + "  " + commandInfo.getExecutionEvents());
            }
        }
    }
}
