1.1 restoration
[mir.git] / source / mircoders / localizer / basic / filters / ThrottleFilter.java
1 /*\r
2  * Copyright (C) 2001, 2002 The Mir-coders group\r
3  *\r
4  * This file is part of Mir.\r
5  *\r
6  * Mir is free software; you can redistribute it and/or modify\r
7  * it under the terms of the GNU General Public License as published by\r
8  * the Free Software Foundation; either version 2 of the License, or\r
9  * (at your option) any later version.\r
10  *\r
11  * Mir is distributed in the hope that it will be useful,\r
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14  * GNU General Public License for more details.\r
15  *\r
16  * You should have received a copy of the GNU General Public License\r
17  * along with Mir; if not, write to the Free Software\r
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
19  *\r
20  * In addition, as a special exception, The Mir-coders gives permission to link\r
21  * the code of this program with  any library licensed under the Apache Software License,\r
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library\r
23  * (or with modified versions of the above that use the same license as the above),\r
24  * and distribute linked combinations including the two.  You must obey the\r
25  * GNU General Public License in all respects for all of the code used other than\r
26  * the above mentioned libraries.  If you modify this file, you may extend this\r
27  * exception to your version of the file, but you are not obligated to do so.\r
28  * If you do not wish to do so, delete this exception statement from your version.\r
29  */\r
30 package mircoders.localizer.basic.filters;\r
31 \r
32 import mircoders.localizer.basic.MirBasicAntiAbuseFilterTypes;\r
33 import mircoders.entity.EntityComment;\r
34 \r
35 import java.util.*;\r
36 \r
37 import mir.util.StringRoutines;\r
38 import mir.entity.Entity;\r
39 import mir.session.Request;\r
40 \r
41 /**\r
42  * A ip-based throttling filter.\r
43  *\r
44  * <p>\r
45  * Expressions have the form <time in minutes>:<posting limit>\r
46  */\r
47 public class ThrottleFilter extends MirBasicAntiAbuseFilterTypes.BasicFilterType {\r
48   private long overallHorizon;\r
49 \r
50   private ThrottleManager throttleManager;\r
51 \r
52   public ThrottleFilter(String aName, long anOverallHorizon) {\r
53     super(aName);\r
54 \r
55     overallHorizon = anOverallHorizon;\r
56     throttleManager = new ThrottleManager(overallHorizon);\r
57   }\r
58 \r
59   /** * {@inheritDoc} */\r
60   public boolean validate(String anExpression) {\r
61     List parts = StringRoutines.splitString(anExpression.trim(), ":");\r
62 \r
63     try {\r
64       if (parts.size()==2) {\r
65         Integer.parseInt((String) parts.get(0));\r
66         Integer.parseInt((String) parts.get(1));\r
67 \r
68         return true;\r
69       }\r
70     }\r
71     catch (Throwable t) {\r
72     }\r
73 \r
74     return false;\r
75   }\r
76 \r
77   /**\r
78    *\r
79    */\r
80   public boolean test(String anExpression, Entity anEntity, Request aRequest) {\r
81     String ip = aRequest.getHeader("ip");\r
82     int limit;\r
83     long period;\r
84 \r
85     List parts = StringRoutines.splitString(anExpression, ":");\r
86 \r
87     try {\r
88       period = Integer.parseInt((String) parts.get(0))*1000*60;\r
89       limit = Integer.parseInt((String) parts.get(1));\r
90     }\r
91     catch (Throwable t) {\r
92       return false;\r
93     }\r
94 \r
95     return throttleManager.addMessage(ip, anEntity, limit, period);\r
96   };\r
97 \r
98   private class ThrottleManager {\r
99     private Map throttles;\r
100     private long overallHorizon;\r
101     private Thread cleanUpThread;\r
102 \r
103     public ThrottleManager(long anOverallHorizon) {\r
104       throttles = new HashMap();\r
105       overallHorizon = anOverallHorizon;\r
106 \r
107       cleanUpThread = new Thread() {\r
108         public void run() {\r
109           while (true) {\r
110             synchronized(throttles) {\r
111               List toDelete = new ArrayList();\r
112               Iterator i = throttles.entrySet().iterator();\r
113 \r
114               while (i.hasNext()) {\r
115                 Map.Entry entry = (Map.Entry) i.next();\r
116                 try {\r
117                   if (((Throttle) entry.getValue()).flush()) {\r
118                     toDelete.add(entry.getKey());\r
119                   }\r
120                 }\r
121                 catch (Throwable t) {\r
122                   toDelete.add(entry.getKey());\r
123                 }\r
124               }\r
125 \r
126               i = toDelete.iterator();\r
127               while (i.hasNext()) {\r
128                 throttles.remove(i.next());\r
129               }\r
130             }\r
131             try {\r
132               Thread.sleep(60*10*1000);\r
133             }\r
134             catch (InterruptedException e) {\r
135               break;\r
136             }\r
137           }\r
138         }\r
139       };\r
140 \r
141       cleanUpThread.setDaemon(true);\r
142       cleanUpThread.start();\r
143     }\r
144 \r
145     public boolean addMessage(String anIP, Entity anEntity, int aLimit, long aPeriod) {\r
146       synchronized (throttles) {\r
147         Throttle throttle = (Throttle) throttles.get(anIP);\r
148 \r
149         if (throttle==null) {\r
150           throttle = new Throttle(overallHorizon);\r
151           throttles.put(anIP, throttle);\r
152         }\r
153         return throttle.addMessage(anEntity, aLimit, aPeriod);\r
154       }\r
155     }\r
156 \r
157     private class Throttle {\r
158       private List messages;\r
159       private long horizon;\r
160 \r
161       public Throttle(long aHorizon) {\r
162         messages = new ArrayList();\r
163         horizon = aHorizon;\r
164       }\r
165 \r
166       public boolean flush() {\r
167         long limit = System.currentTimeMillis() - horizon;\r
168 \r
169         while (messages.size()>0 && ((Message) messages.get(0)).getTime()<=limit) {\r
170           messages.remove(0);\r
171         }\r
172 \r
173         return messages.size()==0;\r
174       }\r
175 \r
176       public boolean addMessage(Entity anEntity, int aLimit, long aPeriod) {\r
177         Message lastMessage=null;\r
178         if (messages.size()>0) {\r
179           lastMessage = (Message) messages.get(messages.size()-1);\r
180         }\r
181 \r
182         Message newMessage = new Message(anEntity.getId(),\r
183                 anEntity instanceof EntityComment, System.currentTimeMillis());\r
184 \r
185         if (!newMessage.equals(lastMessage))\r
186           messages.add(newMessage);\r
187 \r
188         if (messages.size()>=aLimit) {\r
189           Message message = (Message) messages.get(messages.size()-aLimit);\r
190           return (System.currentTimeMillis()-message.getTime())<aPeriod;\r
191         }\r
192 \r
193         return false;\r
194       }\r
195 \r
196       private class Message {\r
197         private String id;\r
198         private boolean isComment;\r
199         private long time;\r
200 \r
201         public Message(String anId, boolean anIsComment, long aTime) {\r
202           id = anId;\r
203           isComment = anIsComment;\r
204           time = aTime;\r
205         }\r
206 \r
207         public String getId() {\r
208           return id;\r
209         }\r
210 \r
211         public boolean getIsComment() {\r
212           return isComment;\r
213         }\r
214 \r
215         public long getTime() {\r
216           return time;\r
217         }\r
218 \r
219         public int hashCode() {\r
220           return getId().hashCode();\r
221         }\r
222 \r
223         public boolean equals(Object anObject) {\r
224           if (anObject instanceof Message) {\r
225             Message that = (Message) anObject;\r
226 \r
227             if (that.getId().equals(getId()) && that.getIsComment() == getIsComment()) {\r
228               return true;\r
229             }\r
230           }\r
231 \r
232           return false;\r
233         }\r
234       }\r
235     }\r
236   }\r
237 }\r