View Javadoc

1   /*
2    * JCaptcha, the open source java framework for captcha definition and integration
3    * Copyright (c)  2007 jcaptcha.net. All Rights Reserved.
4    * See the LICENSE.txt file distributed with this package.
5    */
6   
7   /*
8    * jcaptcha, the open source java framework for captcha definition and integration
9    * copyright (c)  2007 jcaptcha.net. All Rights Reserved.
10   * See the LICENSE.txt file distributed with this package.
11   */
12  
13  /*
14   * jcaptcha, the open source java framework for captcha definition and integration
15   * copyright (c)  2007 jcaptcha.net. All Rights Reserved.
16   * See the LICENSE.txt file distributed with this package.
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          //define hook when JVM is shutdown
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             //get from engine directly
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             //stats
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                 //try to resolve by language
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         //construct the map of swap size by locales;
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             //get the reminding size corresponding to the ratio
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         //get them from persistent buffer
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             //stats
219             persistentMemoryHits += temp.size();
220         }
221 
222         if (log.isDebugEnabled()) {
223             log.debug("new volatil Buffer size : " + volatileBuffer.size());
224         }
225         // stats
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         //evaluate the total feed size
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         //feed the buffer for each locale
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             //batch build and store captchas
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                 //build captchas, batch sized
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                 //persist
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 }