001: /*
002: * Copyright 2004 Sun Microsystems, Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: *
016: */
017:
018: package org.apache.roller.util;
019:
020: import java.util.HashMap;
021: import java.util.Map;
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.apache.roller.util.cache.Cache;
025: import org.apache.roller.util.cache.CacheManager;
026: import org.apache.roller.util.cache.ExpiringCacheEntry;
027:
028: /**
029: * A tool used to provide throttling support.
030: *
031: * The basic idea is that if the # of hits from a client within a certain
032: * interval of time is greater than the threshold value then the client is
033: * considered to be abusive.
034: */
035: public class GenericThrottle {
036:
037: private static Log log = LogFactory.getLog(GenericThrottle.class);
038:
039: // threshold and interval to determine who is abusive
040: private int threshold = 1;
041: private int interval = 0;
042:
043: // a cache to maintain the data
044: Cache clientHistoryCache = null;
045:
046: public GenericThrottle(int thresh, int inter, int maxEntries) {
047:
048: // threshold can't be negative, that would mean everyone is abusive
049: if (thresh > -1) {
050: this .threshold = thresh;
051: }
052:
053: // interval must be a positive value
054: if (inter > 0) {
055: this .interval = inter;
056: }
057:
058: // max entries must be a positive value
059: if (maxEntries < 0) {
060: maxEntries = 1;
061: }
062:
063: // cache props
064: Map cacheProps = new HashMap();
065: cacheProps.put("id", "throttle");
066: cacheProps.put("size", "" + maxEntries);
067: cacheProps.put("timeout", "" + this .interval);
068:
069: // get cache instance. handler is null cuz we don't want to register it
070: this .clientHistoryCache = CacheManager.constructCache(null,
071: cacheProps);
072: }
073:
074: /**
075: * Process a new hit from the client.
076: *
077: * Each call to this method increments the hit count for the client and
078: * then returns a boolean value indicating if the hit has pushed the client
079: * over the threshold.
080: *
081: * @retuns true if client is abusive, false otherwise
082: */
083: public boolean processHit(String clientId) {
084:
085: if (clientId == null) {
086: return false;
087: }
088:
089: // see if we have any info about this client yet
090: ClientInfo client = null;
091: ExpiringCacheEntry cacheEntry = (ExpiringCacheEntry) this .clientHistoryCache
092: .get(clientId);
093: if (cacheEntry != null) {
094: log.debug("HIT " + clientId);
095: client = (ClientInfo) cacheEntry.getValue();
096:
097: // this means entry had expired
098: if (client == null) {
099: log.debug("EXPIRED " + clientId);
100: this .clientHistoryCache.remove(clientId);
101: }
102: }
103:
104: // if we already know this client then update their hit count and
105: // see if they have surpassed the threshold
106: if (client != null) {
107: client.hits++;
108:
109: log.debug("STATUS " + clientId + " - " + client.hits
110: + " hits since " + client.start);
111:
112: // abusive client
113: if (client.hits > this .threshold) {
114: return true;
115: }
116:
117: } else {
118: log.debug("NEW " + clientId);
119:
120: // first timer
121: ClientInfo newClient = new ClientInfo();
122: newClient.hits = 1;
123: newClient.id = clientId;
124:
125: ExpiringCacheEntry newEntry = new ExpiringCacheEntry(
126: newClient, this .interval);
127: this .clientHistoryCache.put(clientId, newEntry);
128: }
129:
130: return false;
131: }
132:
133: /**
134: * Check the current status of a client.
135: *
136: * A client is considered abusive if the number of hits from the client
137: * within the configured interval is greater than the set threshold.
138: *
139: * @returns true if client is abusive, false otherwise.
140: */
141: public boolean isAbusive(String clientId) {
142:
143: if (clientId == null) {
144: return false;
145: }
146:
147: // see if we have any info about this client
148: ClientInfo client = null;
149: ExpiringCacheEntry cacheEntry = (ExpiringCacheEntry) this .clientHistoryCache
150: .get(clientId);
151: if (cacheEntry != null) {
152: log.debug("HIT " + clientId);
153: client = (ClientInfo) cacheEntry.getValue();
154:
155: // this means entry had expired
156: if (client == null) {
157: log.debug("EXPIRED " + clientId);
158: this .clientHistoryCache.remove(clientId);
159: }
160: }
161:
162: if (client != null) {
163: return (client.hits > this .threshold);
164: } else {
165: return false;
166: }
167: }
168:
169: // just something to keep a few properties in
170: private class ClientInfo {
171:
172: public String id = null;
173: public int hits = 0;
174: public java.util.Date start = new java.util.Date();
175:
176: }
177:
178: }
|