bugfix
[mir.git] / source / mircoders / global / Abuse.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 \r
31 package mircoders.global;\r
32 \r
33 import java.io.File;\r
34 import java.io.FileNotFoundException;\r
35 import java.io.FileOutputStream;\r
36 import java.util.Arrays;\r
37 import java.util.Date;\r
38 import java.util.GregorianCalendar;\r
39 import java.util.HashMap;\r
40 import java.util.Iterator;\r
41 import java.util.List;\r
42 import java.util.Map;\r
43 import java.util.Random;\r
44 import java.util.Vector;\r
45 import javax.servlet.http.Cookie;\r
46 import javax.servlet.http.HttpServletResponse;\r
47 \r
48 import org.apache.commons.collections.ExtendedProperties;\r
49 import mir.config.MirPropertiesConfiguration;\r
50 import mir.entity.Entity;\r
51 import mir.log.LoggerWrapper;\r
52 import mir.session.Request;\r
53 import mir.util.GeneratorFormatAdapters;\r
54 import mir.util.StringRoutines;\r
55 import mircoders.entity.EntityComment;\r
56 import mircoders.entity.EntityContent;\r
57 import mircoders.entity.EntityUsers;\r
58 import mircoders.localizer.MirAdminInterfaceLocalizer;\r
59 import mircoders.localizer.MirAntiAbuseFilterType;\r
60 \r
61 \r
62 public class Abuse {\r
63   private List filterRules;\r
64   private Map filterTypes;\r
65   private List filterTypeIds;\r
66   private int maxIdentifier;\r
67   private LoggerWrapper logger;\r
68   private LoggerWrapper adminUsageLogger;\r
69   private int logSize;\r
70   private boolean logEnabled;\r
71   private boolean openPostingDisabled;\r
72   private boolean openPostingPassword;\r
73   private boolean cookieOnBlock;\r
74   private String articleBlockAction;\r
75   private String commentBlockAction;\r
76   private List log;\r
77   private String configFile = MirGlobal.config().getStringWithHome("Abuse.Config");\r
78 \r
79   private MirPropertiesConfiguration configuration;\r
80 \r
81   private static String cookieName=MirGlobal.config().getString("Abuse.CookieName");\r
82   private static int cookieMaxAge = 60*60*MirGlobal.config().getInt("Abuse.CookieMaxAge");\r
83 \r
84   public Abuse() {\r
85     logger = new LoggerWrapper("Global.Abuse");\r
86     adminUsageLogger = new LoggerWrapper("AdminUsage");\r
87     filterRules = new Vector();\r
88     maxIdentifier = 0;\r
89     log = new Vector();\r
90 \r
91     try {\r
92       configuration = MirPropertiesConfiguration.instance();\r
93     }\r
94     catch (Throwable e) {\r
95       throw new RuntimeException("Can't get configuration: " + e.getMessage());\r
96     }\r
97 \r
98     logSize = 100;\r
99     logEnabled = false;\r
100     articleBlockAction = "";\r
101     commentBlockAction = "";\r
102     openPostingPassword = false;\r
103     openPostingDisabled = false;\r
104     cookieOnBlock = false;\r
105 \r
106     try {\r
107       filterTypes = new HashMap();\r
108       filterTypeIds = new Vector();\r
109 \r
110       Iterator i = MirGlobal.localizer().openPostings().getAntiAbuseFilterTypes().iterator();\r
111 \r
112       while (i.hasNext()) {\r
113         MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) i.next();\r
114         filterTypes.put(filterType.getName(), filterType);\r
115         filterTypeIds.add(filterType.getName());\r
116       }\r
117     }\r
118     catch (Throwable t) {\r
119       throw new RuntimeException("Can't get filter types: " + t.getMessage());\r
120     }\r
121 \r
122     load();\r
123   }\r
124 \r
125   private void setCookie(HttpServletResponse aResponse) {\r
126     Random random = new Random();\r
127 \r
128     Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));\r
129     cookie.setMaxAge(cookieMaxAge);\r
130     cookie.setPath("/");\r
131 \r
132     if (aResponse!=null)\r
133       aResponse.addCookie(cookie);\r
134   }\r
135 \r
136   private boolean checkCookie(List aCookies) {\r
137     if (getCookieOnBlock()) {\r
138       Iterator i = aCookies.iterator();\r
139 \r
140       while (i.hasNext()) {\r
141         Cookie cookie = (Cookie) i.next();\r
142 \r
143         if (cookie.getName().equals(cookieName)) {\r
144           logger.debug("cookie match");\r
145           return true;\r
146         }\r
147       }\r
148     }\r
149 \r
150     return false;\r
151   }\r
152   FilterRule findMatchingFilter(Entity anEntity, Request aRequest) {\r
153     Iterator iterator = filterRules.iterator();\r
154 \r
155     while (iterator.hasNext()) {\r
156       FilterRule rule = (FilterRule) iterator.next();\r
157 \r
158       if (rule.test(anEntity, aRequest))\r
159         return rule;\r
160     }\r
161 \r
162     return null;\r
163   }\r
164 \r
165   public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {\r
166     try {\r
167       long time = System.currentTimeMillis();\r
168 \r
169       FilterRule filterRule = findMatchingFilter(aComment, aRequest);\r
170 \r
171       if (filterRule!=null) {\r
172         logger.debug("Match for " + filterRule.getType()+" rule '"+ filterRule.getExpression()+"'");\r
173         filterRule.setLastHit(new GregorianCalendar().getTime());\r
174         MirGlobal.performCommentOperation(null, aComment, filterRule.getCommentAction());\r
175         setCookie(aResponse);\r
176       }\r
177 \r
178       logger.debug("checkComment: " + (System.currentTimeMillis()-time) + "ms");\r
179     }\r
180     catch (Throwable t) {\r
181       t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));\r
182       logger.error("Abuse.checkComment: " + t.toString());\r
183     }\r
184   }\r
185 \r
186   public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {\r
187     try {\r
188       long time = System.currentTimeMillis();\r
189 \r
190       FilterRule filterRule = findMatchingFilter(anArticle, aRequest);\r
191 \r
192       if (filterRule!=null) {\r
193         logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression()+"'");\r
194         filterRule.setLastHit(new GregorianCalendar().getTime());\r
195         MirGlobal.performArticleOperation(null, anArticle, filterRule.getArticleAction());\r
196         setCookie(aResponse);\r
197       }\r
198 \r
199       logger.info("checkArticle: " + (System.currentTimeMillis()-time) + "ms");\r
200     }\r
201     catch (Throwable t) {\r
202       t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));\r
203       logger.error("Abuse.checkArticle: " + t.toString());\r
204     }\r
205   }\r
206 \r
207   public boolean getLogEnabled() {\r
208     return logEnabled;\r
209   }\r
210 \r
211   public void setLogEnabled(boolean anEnabled) {\r
212     if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))\r
213       logEnabled = anEnabled;\r
214     truncateLog();\r
215   }\r
216 \r
217   public int getLogSize() {\r
218     return logSize;\r
219   }\r
220 \r
221   public void setLogSize(int aSize) {\r
222     logSize = aSize;\r
223     truncateLog();\r
224   }\r
225 \r
226   public boolean getOpenPostingDisabled() {\r
227     return openPostingDisabled;\r
228   }\r
229 \r
230   public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {\r
231     openPostingDisabled = anOpenPostingDisabled;\r
232   }\r
233 \r
234   public boolean getOpenPostingPassword() {\r
235     return openPostingPassword;\r
236   }\r
237 \r
238   public void setOpenPostingPassword(boolean anOpenPostingPassword) {\r
239     openPostingPassword = anOpenPostingPassword;\r
240   }\r
241 \r
242   public boolean getCookieOnBlock() {\r
243     return cookieOnBlock;\r
244   }\r
245 \r
246   public void setCookieOnBlock(boolean aCookieOnBlock) {\r
247     cookieOnBlock = aCookieOnBlock;\r
248   }\r
249 \r
250   public String getArticleBlockAction() {\r
251     return articleBlockAction;\r
252   }\r
253 \r
254   public void setArticleBlockAction(String anAction) {\r
255     articleBlockAction = anAction;\r
256   }\r
257 \r
258   public String getCommentBlockAction() {\r
259     return commentBlockAction;\r
260   }\r
261 \r
262   public void setCommentBlockAction(String anAction) {\r
263     commentBlockAction = anAction;\r
264   }\r
265 \r
266   public List getLog() {\r
267     synchronized(log) {\r
268       try {\r
269         List result = new Vector();\r
270 \r
271         Iterator i = log.iterator();\r
272         while (i.hasNext()) {\r
273           LogEntry logEntry = (LogEntry) i.next();\r
274           Map entry = new HashMap();\r
275 \r
276           entry.put("ip", logEntry.getIpNumber());\r
277           entry.put("id", logEntry.getId());\r
278           entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));\r
279           if (logEntry.getIsArticle())\r
280             entry.put("type", "content");\r
281           else\r
282             entry.put("type", "comment");\r
283           entry.put("browser", logEntry.getBrowserString());\r
284 \r
285           result.add(entry);\r
286         }\r
287 \r
288         return result;\r
289       }\r
290       catch (Throwable t) {\r
291         throw new RuntimeException(t.toString());\r
292       }\r
293     }\r
294   }\r
295 \r
296   public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser) {\r
297     appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false));\r
298   }\r
299 \r
300   public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser) {\r
301     appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true));\r
302   }\r
303 \r
304   public void load() {\r
305     try {\r
306       ExtendedProperties configuration = new ExtendedProperties();\r
307 \r
308       try {\r
309         configuration = new ExtendedProperties(configFile);\r
310       }\r
311       catch (FileNotFoundException e) {\r
312       }\r
313 \r
314       getFilterConfig(filterRules, "abuse.filter", configuration);\r
315 \r
316       setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));\r
317       setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));\r
318       setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));\r
319       setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));\r
320       setLogSize(configuration.getInt("abuse.logSize", 10));\r
321       setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));\r
322       setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));\r
323     }\r
324     catch (Throwable t) {\r
325       throw new RuntimeException(t.toString());\r
326     }\r
327   }\r
328   public void save() {\r
329     try {\r
330       ExtendedProperties configuration = new ExtendedProperties();\r
331 \r
332       setFilterConfig(filterRules, "abuse.filter", configuration);\r
333 \r
334       configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled()?"1":"0");\r
335       configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword()?"1":"0");\r
336       configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock()?"1":"0");\r
337       configuration.addProperty("abuse.logEnabled", getLogEnabled()?"1":"0");\r
338       configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));\r
339       configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());\r
340       configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());\r
341 \r
342       configuration.save(new FileOutputStream(new File(configFile)), "Anti abuse configuration");\r
343     }\r
344     catch (Throwable t) {\r
345       throw new RuntimeException(t.toString());\r
346     }\r
347   }\r
348 \r
349   public List getFilterTypes() {\r
350     try {\r
351       List result = new Vector();\r
352 \r
353       Iterator i = filterTypeIds.iterator();\r
354       while (i.hasNext()) {\r
355         String id = (String) i.next();\r
356 \r
357         Map action = new HashMap();\r
358         action.put("resource", id);\r
359         action.put("identifier", id);\r
360 \r
361         result.add(action);\r
362       }\r
363 \r
364       return result;\r
365     }\r
366     catch (Throwable t) {\r
367       throw new RuntimeException("can't get article actions");\r
368     }\r
369   }\r
370 \r
371   public List getArticleActions() {\r
372     try {\r
373       List result = new Vector();\r
374 \r
375       Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();\r
376       while (i.hasNext()) {\r
377         MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =\r
378             (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();\r
379 \r
380         Map action = new HashMap();\r
381         action.put("resource", operation.getName());\r
382         action.put("identifier", operation.getName());\r
383 \r
384         result.add(action);\r
385       }\r
386 \r
387       return result;\r
388     }\r
389     catch (Throwable t) {\r
390       throw new RuntimeException("can't get article actions");\r
391     }\r
392   }\r
393 \r
394   public List getCommentActions() {\r
395     try {\r
396       List result = new Vector();\r
397 \r
398       Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();\r
399       while (i.hasNext()) {\r
400         MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =\r
401             (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();\r
402 \r
403         Map action = new HashMap();\r
404         action.put("resource", operation.getName());\r
405         action.put("identifier", operation.getName());\r
406 \r
407         result.add(action);\r
408       }\r
409 \r
410       return result;\r
411     }\r
412     catch (Throwable t) {\r
413       throw new RuntimeException("can't get comment actions");\r
414     }\r
415   }\r
416 \r
417   public List getFilters() {\r
418     List result = new Vector();\r
419 \r
420     synchronized(filterRules) {\r
421       Iterator i = filterRules.iterator();\r
422       while (i.hasNext()) {\r
423         FilterRule filter = (FilterRule) i.next();\r
424         result.add(filter.clone());\r
425       }\r
426       return result;\r
427     }\r
428   }\r
429 \r
430   public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
431     return addFilter(filterRules, aType, anExpression, aComments, aCommentAction, anArticleAction);\r
432   }\r
433 \r
434   public FilterRule getFilter(String anId) {\r
435     synchronized (filterRules) {\r
436       FilterRule result = (FilterRule) findFilter(filterRules, anId);\r
437       if (result==null)\r
438         return result;\r
439       else\r
440         return (FilterRule) result.clone();\r
441     }\r
442   }\r
443 \r
444   public String setFilter(String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
445     return setFilter(filterRules, anIdentifier, aType, anExpression, aComments, aCommentAction, anArticleAction);\r
446   }\r
447 \r
448   public void deleteFilter(String anIdentifier) {\r
449     deleteFilter(filterRules, anIdentifier);\r
450   }\r
451 \r
452   private String addFilter(List aFilters, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
453     MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);\r
454 \r
455     if (type==null)\r
456       return "invalidtype";\r
457 \r
458     if (!type.validate(anExpression)) {\r
459       return "invalidexpression";\r
460     }\r
461 \r
462     FilterRule filter = new FilterRule();\r
463 \r
464     filter.setId(generateId());\r
465     filter.setExpression(anExpression);\r
466     filter.setType(aType);\r
467     filter.setComments(aComments);\r
468     filter.setArticleAction(anArticleAction);\r
469     filter.setCommentAction(aCommentAction);\r
470 \r
471     synchronized (aFilters) {\r
472       aFilters.add(filter);\r
473     }\r
474 \r
475     return null;\r
476   }\r
477 \r
478   private String setFilter(List aFilters, String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
479     MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);\r
480 \r
481     if (type==null)\r
482       return "invalidtype";\r
483 \r
484     if (!type.validate(anExpression)) {\r
485       return "invalidexpression";\r
486     }\r
487 \r
488     synchronized (aFilters) {\r
489       FilterRule filter = findFilter(aFilters, anIdentifier);\r
490 \r
491       if (filter!=null) {\r
492         filter.setExpression(anExpression);\r
493         filter.setType(aType);\r
494         filter.setCommentAction(aCommentAction);\r
495         filter.setArticleAction(anArticleAction);\r
496         filter.setComments(aComments);\r
497       }\r
498 \r
499       return null;\r
500     }\r
501   }\r
502 \r
503   private FilterRule findFilter(List aFilters, String anIdentifier) {\r
504     synchronized (aFilters) {\r
505       Iterator i = aFilters.iterator();\r
506       while (i.hasNext()) {\r
507         FilterRule filter = (FilterRule) i.next();\r
508 \r
509         if (filter.getId().equals(anIdentifier)) {\r
510           return filter;\r
511         }\r
512       }\r
513     }\r
514 \r
515     return null;\r
516   }\r
517 \r
518   private void deleteFilter(List aFilters, String anIdentifier) {\r
519     synchronized (aFilters) {\r
520       FilterRule filter = findFilter(aFilters, anIdentifier);\r
521 \r
522       if (filter!=null) {\r
523         aFilters.remove(filter);\r
524       }\r
525     }\r
526   }\r
527 \r
528   private String generateId() {\r
529     synchronized(this) {\r
530       maxIdentifier = maxIdentifier+1;\r
531 \r
532       return Integer.toString(maxIdentifier);\r
533     }\r
534   }\r
535 \r
536   public class FilterRule {\r
537     private String identifier;\r
538     private String expression;\r
539     private String type;\r
540     private String comments;\r
541     private String articleAction;\r
542     private String commentAction;\r
543     private Date lastHit;\r
544 \r
545     public FilterRule() {\r
546       expression = "";\r
547       type = "";\r
548       identifier = "";\r
549       comments = "";\r
550       articleAction = articleBlockAction;\r
551       commentAction = commentBlockAction;\r
552       lastHit = null;\r
553     }\r
554 \r
555     public Date getLastHit() {\r
556       return lastHit;\r
557     }\r
558 \r
559     public void setLastHit(Date aDate) {\r
560       lastHit = aDate;\r
561     }\r
562 \r
563     public String getId() {\r
564       return identifier;\r
565     }\r
566 \r
567     public void setId(String anId) {\r
568       identifier = anId;\r
569     }\r
570 \r
571     public String getExpression() {\r
572       return expression;\r
573     }\r
574 \r
575     public void setExpression(String anExpression) {\r
576       expression = anExpression;\r
577     }\r
578 \r
579     public String getType() {\r
580       return type;\r
581     }\r
582 \r
583     public void setType(String aType) {\r
584       type = aType;\r
585     }\r
586 \r
587     public void setComments(String aComments) {\r
588       comments = aComments;\r
589     }\r
590 \r
591     public String getComments() {\r
592       return comments;\r
593     }\r
594 \r
595     public String getArticleAction() {\r
596       return articleAction;\r
597     }\r
598 \r
599     public void setArticleAction(String anArticleAction) {\r
600       articleAction = anArticleAction;\r
601     }\r
602 \r
603     public String getCommentAction() {\r
604       return commentAction;\r
605     }\r
606 \r
607     public void setCommentAction(String aCommentAction) {\r
608       commentAction = aCommentAction;\r
609     }\r
610 \r
611     public boolean test(Entity anEntity, Request aRequest) {\r
612       MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) filterTypes.get(type);\r
613       try {\r
614         if (filterType != null)\r
615           return filterType.test(expression, anEntity, aRequest);\r
616       }\r
617       catch (Throwable t) {\r
618         logger.error("error while testing "+type+"-filter '"+expression+"'");\r
619       }\r
620 \r
621       return false;\r
622     };\r
623 \r
624     public Object clone() {\r
625       FilterRule result = new FilterRule();\r
626       result.setComments(getComments());\r
627       result.setExpression(getExpression());\r
628       result.setId(getId());\r
629       result.setType(getType());\r
630       result.setArticleAction(getArticleAction());\r
631       result.setCommentAction(getCommentAction());\r
632       result.setLastHit(getLastHit());\r
633 \r
634       return result;\r
635     }\r
636   }\r
637 \r
638   private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {\r
639     synchronized(aFilters) {\r
640       Iterator i = aFilters.iterator();\r
641 \r
642       while (i.hasNext()) {\r
643         FilterRule filter = (FilterRule) i.next();\r
644 \r
645         String comments = StringRoutines.replaceStringCharacters(filter.getComments(), new char[] { '\\', ':'}, new String[] { "\\\\", "\\:"} );\r
646 \r
647         aConfiguration.addProperty(aConfigKey, filter.getType()+":"+filter.getExpression()+":"+filter.getArticleAction() + ":" + filter.getCommentAction() + ":" +\r
648            comments);\r
649       }\r
650     }\r
651   }\r
652 \r
653   private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {\r
654     synchronized(aFilters) {\r
655       aFilters.clear();\r
656 \r
657       if (aConfiguration.getStringArray(aConfigKey)!=null) {\r
658 \r
659         Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).\r
660             iterator();\r
661 \r
662         while (i.hasNext()) {\r
663           String filter = (String) i.next();\r
664           List parts = StringRoutines.splitStringWithEscape(filter, ':', '\\');\r
665           if (parts.size() == 2) {\r
666             parts.add(articleBlockAction);\r
667             parts.add(commentBlockAction);\r
668             parts.add("");\r
669           }\r
670 \r
671           if (parts.size() == 5) {\r
672             addFilter( (String) parts.get(0), (String) parts.get(1), (String) parts.get(4), (String) parts.get(3), (String) parts.get(2));\r
673           }\r
674         }\r
675       }\r
676     }\r
677   }\r
678 \r
679   private static class LogEntry {\r
680     private String ipNumber;\r
681     private String browserString;\r
682     private String id;\r
683     private Date timeStamp;\r
684     private boolean isArticle;\r
685 \r
686     public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {\r
687       ipNumber = anIpNumber;\r
688       browserString = aBrowserString;\r
689       id = anId;\r
690       isArticle = anIsArticle;\r
691       timeStamp=aTimeStamp;\r
692     }\r
693 \r
694     public String getIpNumber() {\r
695       return ipNumber;\r
696     }\r
697 \r
698     public String getBrowserString() {\r
699       return browserString;\r
700     }\r
701 \r
702     public String getId() {\r
703       return id;\r
704     }\r
705 \r
706     public Date getTimeStamp() {\r
707       return timeStamp;\r
708     }\r
709 \r
710     public boolean getIsArticle() {\r
711       return isArticle;\r
712     }\r
713   }\r
714 \r
715   private void truncateLog() {\r
716     synchronized(log) {\r
717       if (!logEnabled)\r
718         log.clear();\r
719       else {\r
720         while (log.size()>0 && log.size()>logSize) {\r
721           log.remove(0);\r
722         }\r
723       }\r
724     }\r
725   };\r
726 \r
727   private void appendLog(LogEntry anEntry) {\r
728     synchronized (log) {\r
729       if (logEnabled) {\r
730         log.add(anEntry);\r
731         truncateLog();\r
732       }\r
733     }\r
734   }\r
735 \r
736   public void logAdminUsage(EntityUsers aUser, String aDescription) {\r
737     try {\r
738       String user = "unknown (" + aUser.toString() +")";\r
739       if (user!=null)\r
740         user = aUser.getValue("login");\r
741       adminUsageLogger.info(user + ": " + aDescription);\r
742     }\r
743     catch (Throwable t) {\r
744       logger.error("Error while logging admin usage ("+aUser.toString()+", "+aDescription+"): " +t.toString());\r
745     }\r
746   }\r
747 }