| /** |
| * |
| * 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.apache.zest.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.apache.zest.functional.Specification; |
| import org.apache.zest.functional.Specifications; |
| |
| import static org.apache.zest.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 ); |
| } |
| } |