/*
* Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.synapse.commons.throttle.core;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.Serializable;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
/**
* Contains all runtime data for a particular remote caller.
* provides the default rate based access controller algorithm implementation.
* This is not thread-safe
*/
public abstract class CallerContext implements Serializable, Cloneable {
private static final long serialVersionUID = 1652165180220263492L;
private static Log log = LogFactory.getLog(CallerContext.class.getName());
/* next access time - the end of prohibition */
private long nextAccessTime = 0;
/* first access time - when caller came across the on first time */
private long firstAccessTime = 0;
/* The nextTimeWindow - beginning of next unit time period- end of current unit time period */
private long nextTimeWindow = 0;
/* The globalCount to keep track number of request */
private AtomicLong globalCount = new AtomicLong(0);
private String roleId;
private long unitTime;
/**
* Count to keep track of local (specific to this node) number of requests
*/
private AtomicLong localCount = new AtomicLong(0);
/**
* Used for debugging purposes. *
*/
private UUID uuid = UUID.randomUUID();
/* The Id of caller */
private String id;
public CallerContext clone() throws CloneNotSupportedException {
super.clone();
CallerContext clone = new CallerContext(this.id) {
@Override
public int getType() {
return CallerContext.this.getType();
}
};
clone.nextAccessTime = this.nextAccessTime;
clone.firstAccessTime = this.firstAccessTime;
clone.nextTimeWindow = this.nextTimeWindow;
clone.globalCount = new AtomicLong(this.globalCount.longValue());
clone.localCount = new AtomicLong(this.localCount.longValue());
clone.roleId = this.roleId;
localCount.set(0);
return clone;
}
public CallerContext(String ID) {
if (ID == null || "".equals(ID)) {
throw new InstantiationError("Couldn't create a CallContext for an empty " +
"remote caller ID");
}
this.id = ID.trim();
}
public UUID getUuid() {
return uuid;
}
/**
* @return Returns Id of caller
*/
public String getId() {
return this.id;
}
/**
* Init the access for a particular caller , caller will registered with context
*
* @param configuration -The Configuration for this caller
* @param throttleContext -The Throttle Context
* @param currentTime -The system current time in milliseconds
*/
private void initAccess(CallerConfiguration configuration, ThrottleContext throttleContext, long currentTime) {
this.unitTime = configuration.getUnitTime();
this.firstAccessTime = currentTime;
this.nextTimeWindow = this.firstAccessTime + this.unitTime;
this.roleId = configuration.getID();
//Also we need to pick counter value associated with time window.
throttleContext.addCallerContext(this, this.id);
throttleContext.replicateTimeWindow(this.id);
}
/**
* To verify access if the unit time has already not over
*
* @param configuration - The Configuration for this caller
* @param throttleContext -The Throttle Context
* @param currentTime -The system current time
* @return boolean -The boolean value which say access will allow or not
*/
private boolean canAccessIfUnitTimeNotOver(CallerConfiguration configuration,
ThrottleContext throttleContext, long currentTime) {
boolean canAccess = false;
int maxRequest = configuration.getMaximumRequestPerUnitTime();
if (maxRequest != 0) {
if ((this.globalCount.get() + this.localCount.get()) < maxRequest) { //If the globalCount is less than max request
if (log.isDebugEnabled()) {
log.debug("CallerContext Checking access if unit time is not over and less than max count>> Access "
+ "allowed=" + maxRequest + " available="+ (maxRequest - (this.globalCount.get() + this.localCount.get()))
+" key=" + this.getId() + " currentGlobalCount=" + globalCount + " currentTime="
+ currentTime + " " + "nextTimeWindow=" + this.nextTimeWindow + " currentLocalCount=" + localCount + " Tier="
+ configuration.getID() + " nextAccessTime=" + this.nextAccessTime);
}
canAccess = true; // can continue access
this.localCount.incrementAndGet();
// Send the current state to others (clustered env)
throttleContext.flushCallerContext(this, id);
// can complete access
} else {
//else , if caller has not already prohibit
if (this.nextAccessTime == 0) {
//and if there is no prohibit time period in configuration
long prohibitTime = configuration.getProhibitTimePeriod();
if (prohibitTime == 0) {
//prohibit access until unit time period is over
this.nextAccessTime = this.firstAccessTime + configuration.getUnitTime();
} else {
//if there is a prohibit time period in configuration ,then
//set it as prohibit period
this.nextAccessTime = currentTime + prohibitTime;
}
if (log.isDebugEnabled()) {
String type = ThrottleConstants.IP_BASE == configuration.getType() ?
"IP address" : "domain";
log.debug("Maximum Number of requests are reached for caller with "
+ type + " - " + this.id);
}
// Send the current state to others (clustered env)
throttleContext.flushCallerContext(this, id);
} else {
// else , if the caller has already prohibit and prohibit
// time period has already over
if (this.nextAccessTime <= currentTime) {
if (log.isDebugEnabled()) {
log.debug("CallerContext Checking access if unit time is not over before time window exceed >> "
+ "Access allowed=" + maxRequest + " available="
+ (maxRequest - (this.globalCount.get() + this.localCount.get()))
+ " key=" + this.getId() + " currentGlobalCount=" + globalCount
+ " currentTime=" + currentTime + " " + "nextTimeWindow=" + this.nextTimeWindow
+ " currentLocalCount=" + localCount + " " + "Tier=" + configuration.getID()
+ " nextAccessTime=" + this.nextAccessTime);
}
// remove previous caller context
if (this.nextTimeWindow != 0) {
throttleContext.removeCallerContext(id);
}
// reset the states so that, this is the first access
this.nextAccessTime = 0;
canAccess = true;
this.globalCount.set(0);// can access the system and this is same as first access
this.localCount.set(1);
this.firstAccessTime = currentTime;
this.nextTimeWindow = currentTime + configuration.getUnitTime();
throttleContext.replicateTimeWindow(this.id);
throttleContext.addAndFlushCallerContext(this, this.id);
if(log.isDebugEnabled()) {
log.debug("Caller=" + this.getId() + " has reset counters and added for replication when unit "
+ "time is not over");
}
} else {
if (log.isDebugEnabled()) {
String type = ThrottleConstants.IP_BASE == configuration.getType() ?
"IP address" : "domain";
log.debug("Prohibit period is not yet over for caller with "
+ type + " - " + this.id);
}
}
}
}
}
return canAccess;
}
/**
* To verify access if unit time has already over
*
* @param configuration -The Configuration for this caller
* @param throttleContext -The Throttle that caller having pass
* @param currentTime -The system current time
* @return boolean -The boolean value which say access will allow or not
*/
private boolean canAccessIfUnitTimeOver(CallerConfiguration configuration, ThrottleContext throttleContext, long currentTime) {
boolean canAccess = false;
// if number of access for a unit time is less than MAX and
// if the unit time period (session time) has just over
int maxRequest = configuration.getMaximumRequestPerUnitTime();
if (maxRequest != 0) {
if ((this.globalCount.get() + this.localCount.get()) < maxRequest) {
if (this.nextTimeWindow != 0) {
// Removes and sends the current state to others (clustered env)
//remove previous callercontext instance
throttleContext.removeCallerContext(id);
this.globalCount.set(0);// can access the system and this is same as first access
this.localCount.set(1);
this.firstAccessTime = currentTime;
this.nextTimeWindow = currentTime + configuration.getUnitTime();
throttleContext.replicateTimeWindow(this.id);
// registers caller and send the current state to others (clustered env)
throttleContext.addAndFlushCallerContext(this, id);
}
if (log.isDebugEnabled()) {
log.debug("CallerContext Checking access if unit time over next time window>> Access allowed="
+ maxRequest + " available=" + (maxRequest - (this.globalCount.get() + this.localCount.get()))
+ " key=" + this.getId()+ " currentGlobalCount=" + globalCount + " currentTime=" + currentTime
+ " nextTimeWindow=" + this.nextTimeWindow +" currentLocalCount=" + localCount + " Tier="
+ configuration.getID() + " nextAccessTime="+ this.nextAccessTime);
}
canAccess = true; // this is bonus access
//next time callers can access as a new one
} else {
// if number of access for a unit time has just been greater than MAX now same as a new session
// OR if caller in prohibit session and prohibit period has just over
if ((this.nextAccessTime == 0) || (this.nextAccessTime <= currentTime)) {
if (log.isDebugEnabled()) {
log.debug("CallerContext Checking access if unit time over>> Access allowed=" + maxRequest
+ " available=" + (maxRequest - (this.globalCount.get() + this.localCount.get())) + " key=" + this.getId()
+ " currentGlobalCount=" + globalCount + " currentTime=" + currentTime + " nextTimeWindow=" + this.nextTimeWindow
+ " currentLocalCount=" + localCount + " Tier=" + configuration.getID() + " nextAccessTime="
+ this.nextAccessTime);
}
//remove previous callercontext instance
if (this.nextTimeWindow != 0) {
throttleContext.removeCallerContext(id);
}
// reset the states so that, this is the first access
this.nextAccessTime = 0;
canAccess = true;
this.globalCount.set(0);// can access the system and this is same as first access
this.localCount.set(1);
this.firstAccessTime = currentTime;
this.nextTimeWindow = currentTime + configuration.getUnitTime();
// registers caller and send the current state to others (clustered env)
throttleContext.replicateTimeWindow(this.id);
throttleContext.addAndFlushCallerContext(this, id);
if(log.isDebugEnabled()) {
log.debug("Caller=" + this.getId() + " has reset counters and added for replication when unit "
+ "time is over");
}
} else {
// if caller in prohibit session and prohibit period has not over
if (log.isDebugEnabled()) {
String type = ThrottleConstants.IP_BASE == configuration.getType() ?
"IP address" : "domain";
log.debug("Even unit time has over , CallerContext in prohibit state :"
+ type + " - " + this.id);
}
}
}
}
return canAccess;
}
/**
* Clean up the callers - remove all callers that have expired their time window
*
* @param configuration -The Configuration for this caller
* @param throttleContext -The Throttle that caller having pass
* @param currentTime -The system current time
*/
public void cleanUpCallers(CallerConfiguration configuration,
ThrottleContext throttleContext, long currentTime) {
if (log.isDebugEnabled()) {
log.debug("Cleaning up the inactive caller's states ... ");
}
if (configuration == null) {
if (log.isDebugEnabled()) {
log.debug("Couldn't find the configuration .");
}
return;
}
// if number of access for a unit time is less than MAX and
// if the unit time period (session time) has over
int maxRequest = configuration.getMaximumRequestPerUnitTime();
if (!(maxRequest == 0)) {
if ((this.globalCount.get() + this.localCount.get()) <= (maxRequest - 1)) {
if (this.nextTimeWindow != 0 && this.nextTimeWindow < (currentTime - this.unitTime)) {
if (log.isDebugEnabled()) {
log.debug("Removing caller with id " + this.id);
}
//Removes the previous callercontext and Sends the current state to
// others (clustered env)
throttleContext.removeAndDestroyShareParamsOfCaller(id);
}
} else {
// if number of access for a unit time has just been greater than MAX
// now same as a new session
// OR
// if caller in prohibit session and prohibit period has just over and only
if ((this.nextAccessTime == 0) || this.nextAccessTime < (currentTime - this.unitTime)) {
if (this.nextTimeWindow != 0 && this.nextTimeWindow < (currentTime - this.unitTime)) {
if (log.isDebugEnabled()) {
log.debug("Removing caller with id " + this.id);
}
//Removes the previous callercontext and Sends
// the current state to others (clustered env)
throttleContext.removeAndDestroyShareParamsOfCaller(id);
}
}
}
}
}
/**
* Check whether that caller can access or not ,based on current state and pre-defined policy
*
* @param throttleContext -The Context for this caller - runtime state
* @param configuration -The Configuration for this caller - data from policy
* @param currentTime -The current system time
* @return boolean -The boolean value which say access will allow or not
* @throws ThrottleException throws for invalid throttle configuration
*/
public boolean canAccess(ThrottleContext throttleContext, CallerConfiguration configuration,
long currentTime) throws ThrottleException {
boolean canAccess;
if (configuration == null) {
if (log.isDebugEnabled()) {
log.debug("Couldn't find the configuration .");
}
return true;
}
if (configuration.getMaximumRequestPerUnitTime() < 0
|| configuration.getUnitTime() <= 0
|| configuration.getProhibitTimePeriod() < 0) {
throw new ThrottleException("Invalid Throttle Configuration");
}
// if caller access first time in his new session
if (this.firstAccessTime == 0) {
initAccess(configuration, throttleContext, currentTime);
}
// if unit time period (session time) is not over
if (this.nextTimeWindow > currentTime) {
canAccess = canAccessIfUnitTimeNotOver(configuration, throttleContext, currentTime);
} else {
canAccess = canAccessIfUnitTimeOver(configuration, throttleContext, currentTime);
}
return canAccess;
}
/**
* Returns the next time window
*
* @return long value of next time window
*/
public long getNextTimeWindow() {
return this.nextTimeWindow;
}
public void incrementGlobalCounter(int incrementBy) {
globalCount.addAndGet(incrementBy);
}
public void incrementLocalCounter() {
localCount.incrementAndGet();
}
public long getGlobalCounter() {
return globalCount.get();
}
public void setGlobalCounter(long counter) {
globalCount.set(counter);
}
public void setLocalCounter(long counter) {
localCount.set(counter);
}
public long getLocalCounter() {
return localCount.get();
}
public void resetLocalCounter() {
localCount.set(0);
}
public void resetGlobalCounter() {
globalCount.set(0);
}
/**
* Gets type of throttle that this caller belong ex : ip/domain
*
* @return Returns the type of the throttle
*/
public abstract int getType();
public long getFirstAccessTime() {
return firstAccessTime;
}
public void setFirstAccessTime(long firstAccessTime) {
this.firstAccessTime = firstAccessTime;
}
public void setNextTimeWindow(long nextTimeWindow) {
this.nextTimeWindow = nextTimeWindow;
}
public long getUnitTime() {
return unitTime;
}
public void setUnitTime(long unitTime) {
this.unitTime = unitTime;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
}