# CircuitBreaker.java

    /**
     *
     * Copyright 2009-2010 Rickard Öberg AB
     *
     * 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 org.qi4j.library.circuitbreaker;

    import java.beans.PropertyChangeListener;
    import java.beans.PropertyChangeSupport;
    import java.beans.PropertyVetoException;
    import java.beans.VetoableChangeListener;
    import java.beans.VetoableChangeSupport;
    import java.util.Date;
    import org.qi4j.functional.Specification;
    import org.qi4j.functional.Specifications;

    import static org.qi4j.functional.Specifications.not;

    /**
     * Implementation of CircuitBreaker pattern
     */
    public class CircuitBreaker
    {
        public enum Status
        {
            off,
            on
        }

        private int threshold;
        private long timeout;
        private Specification<Throwable> allowedThrowables;

        private int countDown;
        private long trippedOn = -1;
        private long enableOn = -1;

        private Status status = Status.on;

        private Throwable lastThrowable;

        PropertyChangeSupport pcs = new PropertyChangeSupport( this );
        VetoableChangeSupport vcs = new VetoableChangeSupport( this );

        public CircuitBreaker( int threshold, long timeout, Specification<Throwable> allowedThrowables )
        {
            this.threshold = threshold;
            this.countDown = threshold;
            this.timeout = timeout;
            this.allowedThrowables = allowedThrowables;
        }

        public CircuitBreaker( int threshold, long timeout )
        {
            this( threshold, timeout, not( Specifications.<Throwable>TRUE() ) ); // Trip on all exceptions as default
        }

        public CircuitBreaker()
        {
            this( 1, 1000 * 60 * 5 ); // 5 minute timeout as default
        }

        public synchronized void trip()
        {
            if( status == Status.on )
            {
                if( countDown != 0 )
                {
                    // If this was invoked manually, then set countDown to zero automatically
                    int oldCountDown = countDown;
                    countDown = 0;
                    pcs.firePropertyChange( "serviceLevel", ( oldCountDown ) / ( (double) threshold ), countDown / ( (double) threshold ) );
                }

                status = Status.off;
                pcs.firePropertyChange( "status", Status.on, Status.off );

                trippedOn = System.currentTimeMillis();
                enableOn = trippedOn + timeout;
            }
        }

        public synchronized void turnOn()
            throws PropertyVetoException
        {
            if( status == Status.off )
            {
                try
                {
                    vcs.fireVetoableChange( "status", Status.off, Status.on );
                    status = Status.on;
                    countDown = threshold;
                    trippedOn = -1;
                    enableOn = -1;
                    lastThrowable = null;

                    pcs.firePropertyChange( "status", Status.off, Status.on );
                }
                catch( PropertyVetoException e )
                {
                    // Reset timeout
                    enableOn = System.currentTimeMillis() + timeout;

                    if( e.getCause() != null )
                    {
                        lastThrowable = e.getCause();
                    }
                    else
                    {
                        lastThrowable = e;
                    }
                    throw e;
                }
            }
        }

        public int threshold()
        {
            return threshold;
        }

        public synchronized Throwable lastThrowable()
        {
            return lastThrowable;
        }

        public synchronized double serviceLevel()
        {
            return countDown / ( (double) threshold );
        }

        public synchronized Status status()
        {
            if( status == Status.off )
            {
                if( System.currentTimeMillis() > enableOn )
                {
                    try
                    {
                        turnOn();
                    }
                    catch( PropertyVetoException e )
                    {
                        if( e.getCause() != null )
                        {
                            lastThrowable = e.getCause();
                        }
                        else
                        {
                            lastThrowable = e;
                        }
                    }
                }
            }

            return status;
        }

        public Date trippedOn()
        {
            return trippedOn == -1 ? null : new Date( trippedOn );
        }

        public Date enabledOn()
        {
            return enableOn == -1 ? null : new Date( enableOn );
        }

        public boolean isOn()
        {
            return status().equals( Status.on );
        }

        public synchronized void throwable( Throwable throwable )
        {
            if( status == Status.on )
            {
                if( allowedThrowables.satisfiedBy( throwable ) )
                {
                    // Allowed throwable, so counts as success
                    success();
                }
                else
                {
                    countDown--;

                    lastThrowable = throwable;

                    pcs.firePropertyChange( "serviceLevel", ( countDown + 1 ) / ( (double) threshold ), countDown / ( (double) threshold ) );

                    if( countDown == 0 )
                    {
                        trip();
                    }
                }
            }
        }

        public synchronized void success()
        {
            if( status == Status.on && countDown < threshold )
            {
                countDown++;

                pcs.firePropertyChange( "serviceLevel", ( countDown - 1 ) / ( (double) threshold ), countDown / ( (double) threshold ) );
            }
        }

        public void addVetoableChangeListener( VetoableChangeListener vcl )
        {
            vcs.addVetoableChangeListener( vcl );
        }

        public void removeVetoableChangeListener( VetoableChangeListener vcl )
        {
            vcs.removeVetoableChangeListener( vcl );
        }

        public void addPropertyChangeListener( PropertyChangeListener pcl )
        {
            pcs.addPropertyChangeListener( pcl );
        }

        public void removePropertyChangeListener( PropertyChangeListener pcl )
        {
            pcs.removePropertyChangeListener( pcl );
        }
    }