1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.octo.captcha.engine.bufferedengine;
19
20 import com.octo.captcha.Captcha;
21 import com.octo.captcha.CaptchaException;
22 import com.octo.captcha.CaptchaFactory;
23 import com.octo.captcha.engine.CaptchaEngine;
24 import com.octo.captcha.engine.CaptchaEngineException;
25 import com.octo.captcha.engine.bufferedengine.buffer.CaptchaBuffer;
26 import org.apache.commons.collections.MapIterator;
27 import org.apache.commons.collections.map.HashedMap;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 import java.util.*;
32
33 /***
34 * Abstact class that encapsulate a CaptchaEngine to allow buffering. A BufferedEngineContainer has mainly one function
35 * : to provide cached captchas to increase performances. This is done through two embedded buffers : a disk buffer and
36 * a memory buffer. When captchas are requested, the bufferedEngine take them either from the memory buffer if not empty
37 * or directly from the engine. Some good periods are defined with a scheduler to feed the disk buffer with captchas and
38 * some others to swap captchas from the disk buffer to the memory buffer.
39 *
40 * @author Benoit Doumas
41 */
42 public abstract class BufferedEngineContainer implements CaptchaEngine {
43
44 private static final Log log = LogFactory.getLog(BufferedEngineContainer.class.getName());
45
46 protected CaptchaBuffer persistentBuffer = null;
47
48 protected CaptchaBuffer volatileBuffer = null;
49
50 protected CaptchaEngine engine = null;
51
52 protected ContainerConfiguration config = null;
53
54 protected int volatileMemoryHits = 0;
55
56 protected int persistentMemoryHits = 0;
57
58 protected int persistentToVolatileSwaps = 0;
59
60 protected int persistentFeedings = 0;
61
62 private boolean shutdownCalled = false;
63
64
65 /***
66 * Construct an BufferedEngineContainer with and Captcha engine, a memory buffer, a diskBuffer and a
67 * ContainerConfiguration.
68 *
69 * @param engine engine to generate captcha for buffers
70 * @param volatileBuffer the memory buffer, which store captcha and provide a fast access to them
71 * @param persistentBuffer the disk buffer which store captchas not in a volatil and memory consuming way
72 * @param containerConfiguration the container configuration
73 */
74 public BufferedEngineContainer(CaptchaEngine engine, CaptchaBuffer volatileBuffer,
75 CaptchaBuffer persistentBuffer, ContainerConfiguration containerConfiguration) {
76 this.engine = engine;
77 if (engine == null) {
78 throw new CaptchaEngineException("impossible to build a BufferedEngineContainer with a null engine");
79 }
80 this.volatileBuffer = volatileBuffer;
81 if (persistentBuffer == null) {
82 throw new CaptchaEngineException("impossible to build a BufferedEngineContainer with a null volatileBuffer");
83 }
84 this.persistentBuffer = persistentBuffer;
85 if (persistentBuffer == null) {
86 throw new CaptchaEngineException("impossible to build a BufferedEngineContainer with a null persistentBuffer");
87 }
88 this.config = containerConfiguration;
89 if (config == null) {
90 throw new CaptchaEngineException("impossible to build a BufferedEngineContainer with a null configuration");
91 }
92
93 Shutdown sh = new Shutdown();
94 Runtime.getRuntime().addShutdownHook(sh);
95 }
96
97
98 /***
99 * @see com.octo.captcha.engine.CaptchaEngine#getNextCaptcha()
100 */
101 public Captcha getNextCaptcha() {
102 log.debug("entering getNextCaptcha()");
103 return getNextCaptcha(config.getDefaultLocale());
104 }
105
106 /***
107 * @see com.octo.captcha.engine.CaptchaEngine#getNextCaptcha(java.util.Locale)
108 */
109 public Captcha getNextCaptcha(Locale locale) {
110 log.debug("entering getNextCaptcha(Locale locale)");
111 Captcha captcha = null;
112 locale = resolveLocale(locale);
113 try {
114 captcha = volatileBuffer.removeCaptcha(locale);
115 } catch (NoSuchElementException e) {
116 log.debug("no captcha under this locale", e);
117 }
118
119
120 if (captcha == null) {
121
122 captcha = engine.getNextCaptcha(locale);
123 log.debug("get captcha from engine");
124
125 if (config.isServeOnlyConfiguredLocales()) {
126 log.warn("captcha is directly built from engine, try to increase the swap frequency or the persistant buffer size");
127 }
128 } else {
129 log.debug("get captcha from memory");
130
131 volatileMemoryHits++;
132 }
133 return captcha;
134 }
135
136
137 /***
138 * @return captcha factories used by this engine
139 */
140 public CaptchaFactory[] getFactories() {
141 return this.engine.getFactories();
142 }
143
144 /***
145 * @param factories new captcha factories for this engine
146 */
147 public void setFactories(CaptchaFactory[] factories) {
148 this.engine.setFactories(factories);
149 }
150
151
152 /***
153 * Helper for locale
154 */
155 private Locale resolveLocale(Locale locale) {
156 if (!config.isServeOnlyConfiguredLocales()) {
157
158 return locale;
159 } else {
160 if (this.config.getLocaleRatio().containsKey(locale)) {
161 return locale;
162
163 } else if (this.config.getLocaleRatio().containsKey(locale.getLanguage())) {
164 return new Locale(locale.getLanguage());
165 } else {
166 return config.getDefaultLocale();
167 }
168 }
169
170 }
171
172
173 /***
174 * Method launch by a scheduler to swap captcha from disk buffer to the memory buffer. The ratio of swaping for each
175 * locale is defined in the configuration component.
176 */
177 public void swapCaptchasFromPersistentToVolatileMemory() {
178
179 log.debug("entering swapCaptchasFromDiskBufferToMemoryBuffer()");
180
181 MapIterator it = config.getLocaleRatio().mapIterator();
182
183
184 HashedMap captchasRatios = new HashedMap();
185 while (it.hasNext()) {
186
187 Locale locale = (Locale) it.next();
188 double ratio = ((Double) it.getValue()).doubleValue();
189 int ratioCount = (int) Math.round(config.getSwapSize().intValue() * ratio);
190
191
192 int diff = (int) Math.round((config.getMaxVolatileMemorySize().intValue() - this.volatileBuffer
193 .size()) * ratio);
194
195 diff = diff < 0 ? 0 : diff;
196 int toSwap = (diff < ratioCount) ? diff : ratioCount;
197
198 captchasRatios.put(locale, new Integer(toSwap));
199 }
200
201 Iterator captchasRatiosit = captchasRatios.mapIterator();
202
203 while (captchasRatiosit.hasNext() && !shutdownCalled) {
204 Locale locale = (Locale) captchasRatiosit.next();
205 int swap = ((Integer) captchasRatios.get(locale)).intValue();
206 if (log.isDebugEnabled()) {
207 log.debug("try to swap " + swap + " Captchas from persistent to volatile memory with locale : "
208 + locale.toString());
209 }
210
211 Collection temp = this.persistentBuffer.removeCaptcha(swap, locale);
212
213 this.volatileBuffer.putAllCaptcha(temp, locale);
214 if (log.isDebugEnabled()) {
215 log.debug("swaped " + temp.size() + " Captchas from persistent to volatile memory with locale : "
216 + locale.toString());
217 }
218
219 persistentMemoryHits += temp.size();
220 }
221
222 if (log.isDebugEnabled()) {
223 log.debug("new volatil Buffer size : " + volatileBuffer.size());
224 }
225
226 persistentToVolatileSwaps++;
227 }
228
229
230 /***
231 * Method launch by a scheduler to feed the disk buffer with captcha. The ratio of feeding for each locale is
232 * defined in the configuration component.
233 */
234 public void feedPersistentBuffer() {
235
236 log.debug("entering feedPersistentBuffer()");
237
238 int freePersistentBufferSize = config.getMaxPersistentMemorySize().intValue() - persistentBuffer.size();
239 freePersistentBufferSize = freePersistentBufferSize > 0 ? freePersistentBufferSize : 0;
240 int totalFeedsize = freePersistentBufferSize > config.getFeedSize().intValue() ? config.getFeedSize().intValue() : freePersistentBufferSize;
241
242 log.info("Starting feed. Total feed size = " + totalFeedsize);
243
244
245 MapIterator it = config.getLocaleRatio().mapIterator();
246 while (it.hasNext() && !shutdownCalled) {
247 Locale locale = (Locale) it.next();
248 double ratio = ((Double) it.getValue()).doubleValue();
249 int ratioCount = (int) Math.round(totalFeedsize * ratio);
250
251
252 if (log.isDebugEnabled()) {
253 log.debug("construct " + ratioCount + " captchas for locale " + locale.toString());
254 }
255
256 int toBuild = ratioCount;
257 while (toBuild > 0 && !shutdownCalled) {
258 int batch = toBuild > config.getFeedBatchSize().intValue() ? config.getFeedBatchSize().intValue() : toBuild;
259 ArrayList captchas = new ArrayList(batch);
260
261 int builded = 0;
262 for (int i = 0; i < batch; i++) {
263 try {
264 captchas.add(engine.getNextCaptcha(locale));
265 builded++;
266 } catch (CaptchaException e) {
267 log.warn("Error during captcha construction, skip this one : ", e);
268 }
269 }
270
271 persistentBuffer.putAllCaptcha(captchas, locale);
272 if (log.isInfoEnabled()) {
273 log.info("feeded persistent buffer with " + builded + " captchas for locale " + locale);
274 }
275 toBuild -= builded;
276 }
277
278 }
279 log.info("Stopping feed : feeded persitentBuffer with : " + totalFeedsize + " captchas");
280 log.info("Stopping feed : resulting persitentBuffer size : " + persistentBuffer.size());
281 persistentFeedings++;
282 }
283
284 public ContainerConfiguration getConfig() {
285 return config;
286 }
287
288 public CaptchaBuffer getPersistentBuffer() {
289 return persistentBuffer;
290 }
291
292 public Integer getPersistentFeedings() {
293 return new Integer(persistentFeedings);
294 }
295
296 public Integer getPersistentMemoryHits() {
297 return new Integer(persistentMemoryHits);
298 }
299
300 public Integer getPersistentToVolatileSwaps() {
301 return new Integer(persistentToVolatileSwaps);
302 }
303
304 public CaptchaBuffer getVolatileBuffer() {
305 return volatileBuffer;
306 }
307
308 public Integer getVolatileMemoryHits() {
309 return new Integer(volatileMemoryHits);
310 }
311
312 class Shutdown extends Thread {
313 public Shutdown() {
314 super();
315 }
316
317 public void run() {
318 log.info("Buffered engine shutdown thread started");
319 shutdownCalled = true;
320 try {
321 closeBuffers();
322 } catch (Exception ee) {
323 ee.printStackTrace();
324 }
325 }
326 }
327
328 public void closeBuffers() {
329 this.persistentBuffer.dispose();
330 this.volatileBuffer.dispose();
331 log.info("Buffers disposed");
332 }
333 }