Java tutorial
/* * JCaptcha, the open source java framework for captcha definition and integration * Copyright (c) 2007 jcaptcha.net. All Rights Reserved. * See the LICENSE.txt file distributed with this package. */ package com.octo.captcha.service; import com.octo.captcha.Captcha; import com.octo.captcha.engine.CaptchaEngine; import com.octo.captcha.service.captchastore.CaptchaStore; import org.apache.commons.collections.FastHashMap; import java.util.*; /** * This class provides default implementation for the management interface. It uses an HashMap to store the timestamps * for garbage collection. * * @author <a href="mailto:mag@jcaptcha.net">Marc-Antoine Garrigue</a> * @version 1.0 */ public abstract class AbstractManageableCaptchaService extends AbstractCaptchaService implements AbstractManageableCaptchaServiceMBean, CaptchaService { private int minGuarantedStorageDelayInSeconds; private int captchaStoreMaxSize; private int captchaStoreSizeBeforeGarbageCollection; private int numberOfGeneratedCaptchas = 0; private int numberOfCorrectResponse = 0; private int numberOfUncorrectResponse = 0; private int numberOfGarbageCollectedCaptcha = 0; private FastHashMap times; private long oldestCaptcha = 0;//OPTIMIZATION STUFF! protected AbstractManageableCaptchaService(CaptchaStore captchaStore, com.octo.captcha.engine.CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds, int maxCaptchaStoreSize) { super(captchaStore, captchaEngine); this.setCaptchaStoreMaxSize(maxCaptchaStoreSize); this.setMinGuarantedStorageDelayInSeconds(minGuarantedStorageDelayInSeconds); this.setCaptchaStoreSizeBeforeGarbageCollection((int) Math.round(0.8 * maxCaptchaStoreSize)); times = new FastHashMap(); } protected AbstractManageableCaptchaService(CaptchaStore captchaStore, com.octo.captcha.engine.CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds, int maxCaptchaStoreSize, int captchaStoreLoadBeforeGarbageCollection) { this(captchaStore, captchaEngine, minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize); if (maxCaptchaStoreSize < captchaStoreLoadBeforeGarbageCollection) throw new IllegalArgumentException( "the max store size can't be less than garbage collection size. if you want to disable garbage" + " collection (this is not recommended) you may set them equals (max=garbage)"); this.setCaptchaStoreSizeBeforeGarbageCollection(captchaStoreLoadBeforeGarbageCollection); } /** * Get the fully qualified class name of the concrete CaptchaEngine used by the service. * * @return the fully qualified class name of the concrete CaptchaEngine used by the service. */ public String getCaptchaEngineClass() { return this.engine.getClass().getName(); } /** * Set the fully qualified class name of the concrete CaptchaEngine used by the service * * @param theClassName the fully qualified class name of the CaptchaEngine used by the service * * @throws IllegalArgumentException if className can't be used as the service CaptchaEngine, either because it can't * be instanciated by the service or it is not a ImageCaptchaEngine concrete * class. */ public void setCaptchaEngineClass(String theClassName) throws IllegalArgumentException { try { Object engine = Class.forName(theClassName).newInstance(); if (engine instanceof com.octo.captcha.engine.CaptchaEngine) { this.engine = (com.octo.captcha.engine.CaptchaEngine) engine; } else { throw new IllegalArgumentException("Class is not instance of CaptchaEngine! " + theClassName); } } catch (InstantiationException e) { throw new IllegalArgumentException(e.getMessage()); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e.getMessage()); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e.getMessage()); } catch (RuntimeException e) { throw new IllegalArgumentException(e.getMessage()); } } /** * @return the engine served by this service */ public CaptchaEngine getEngine() { return this.engine; } /** * Updates the engine served by this service */ public void setCaptchaEngine(CaptchaEngine engine) { this.engine = engine; } /** * Get the minimum delay (in seconds) a client can be assured that a captcha generated by the service can be * retrieved and a response to its challenge tested * * @return the maximum delay in seconds */ public int getMinGuarantedStorageDelayInSeconds() { return minGuarantedStorageDelayInSeconds; } /** * set the minimum delay (in seconds)a client can be assured that a captcha generated by the service can be * retrieved and a response to its challenge tested * * @param theMinGuarantedStorageDelayInSeconds * the minimum guaranted delay */ public void setMinGuarantedStorageDelayInSeconds(int theMinGuarantedStorageDelayInSeconds) { this.minGuarantedStorageDelayInSeconds = theMinGuarantedStorageDelayInSeconds; } /** * Get the number of captcha generated since the service is up WARNING : this value won't be significant if the real * number is > Long.MAX_VALUE * * @return the number of captcha generated since the service is up */ public long getNumberOfGeneratedCaptchas() { return numberOfGeneratedCaptchas; } /** * Get the number of correct responses to captcha challenges since the service is up. WARNING : this value won't be * significant if the real number is > Long.MAX_VALUE * * @return the number of correct responses since the service is up */ public long getNumberOfCorrectResponses() { return numberOfCorrectResponse; } /** * Get the number of uncorrect responses to captcha challenges since the service is up. WARNING : this value won't * be significant if the real number is > Long.MAX_VALUE * * @return the number of uncorrect responses since the service is up */ public long getNumberOfUncorrectResponses() { return numberOfUncorrectResponse; } /** * Get the curent size of the captcha store * * @return the size of the captcha store */ public int getCaptchaStoreSize() { return this.store.getSize(); } /** * Get the number of captchas that can be garbage collected in the captcha store * * @return the number of captchas that can be garbage collected in the captcha store */ public int getNumberOfGarbageCollectableCaptchas() { return getGarbageCollectableCaptchaIds(System.currentTimeMillis()).size(); } /** * Get the number of captcha garbage collected since the service is up WARNING : this value won't be significant if * the real number is > Long.MAX_VALUE * * @return the number of captcha garbage collected since the service is up */ public long getNumberOfGarbageCollectedCaptcha() { return numberOfGarbageCollectedCaptcha; } /** * @return the max captchaStore load before garbage collection of the store */ public int getCaptchaStoreSizeBeforeGarbageCollection() { return captchaStoreSizeBeforeGarbageCollection; } /** * max captchaStore size before garbage collection of the store */ public void setCaptchaStoreSizeBeforeGarbageCollection(int captchaStoreSizeBeforeGarbageCollection) { if (this.captchaStoreMaxSize < captchaStoreSizeBeforeGarbageCollection) throw new IllegalArgumentException("the max store size can't be less than garbage collection " + "size. if you want to disable garbage" + " collection (this is not recommended) you may " + "set them equals (max=garbage)"); this.captchaStoreSizeBeforeGarbageCollection = captchaStoreSizeBeforeGarbageCollection; } /** * This max size is used by the service : it will throw a CaptchaServiceException if the store is full when a client * ask for a captcha. */ public void setCaptchaStoreMaxSize(int size) { if (size < this.captchaStoreSizeBeforeGarbageCollection) throw new IllegalArgumentException("the max store size can't " + "be less than garbage collection size. if you want " + "to disable garbage" + " collection (this is not recommended) you may " + "set them equals (max=garbage)"); this.captchaStoreMaxSize = size; } /** * @return the desired max size of the captcha store */ public int getCaptchaStoreMaxSize() { return this.captchaStoreMaxSize; } /** * Garbage collect the captcha store, means all old captcha (captcha in the store wich has been stored more than the * MinGuarantedStorageDelayInSecond */ protected void garbageCollectCaptchaStore(Iterator garbageCollectableCaptchaIds) { // this may cause a captcha disparition if a new captcha is asked between // this call and the effective removing from the store! long now = System.currentTimeMillis(); long limit = now - 1000 * minGuarantedStorageDelayInSeconds; while (garbageCollectableCaptchaIds.hasNext()) { String id = garbageCollectableCaptchaIds.next().toString(); if (((Long) times.get(id)).longValue() < limit) { //remove from times times.remove(id); //remove from ids store.removeCaptcha(id); //update stats this.numberOfGarbageCollectedCaptcha++; } } } public void garbageCollectCaptchaStore() { long now = System.currentTimeMillis(); Collection garbageCollectableCaptchaIds = getGarbageCollectableCaptchaIds(now); this.garbageCollectCaptchaStore(garbageCollectableCaptchaIds.iterator()); } /** * Empty the Store */ public void emptyCaptchaStore() { //empty the store this.store.empty(); //And the timestamps this.times = new FastHashMap(); } private Collection getGarbageCollectableCaptchaIds(long now) { //construct a new collection in order to avoid iterations synchronization pbs : // this may cause a captcha disparition if a new captcha is asked between // this call and the effective removing from the store! HashSet garbageCollectableCaptchas = new HashSet(); //the time limit under which captchas are collectable long limit = now - 1000 * getMinGuarantedStorageDelayInSeconds(); if (limit > oldestCaptcha) { // iterate to find out if the captcha is perimed Iterator ids = new HashSet(times.keySet()).iterator(); while (ids.hasNext()) { String id = (String) ids.next(); long captchaDate = ((Long) times.get(id)).longValue(); oldestCaptcha = Math.min(captchaDate, oldestCaptcha == 0 ? captchaDate : oldestCaptcha); if (captchaDate < limit) { garbageCollectableCaptchas.add(id); } } } return garbageCollectableCaptchas; } //******* ///Overriding business methods to add some stats and store management hooks ///**** protected Captcha generateAndStoreCaptcha(Locale locale, String ID) { //if the store is full try to garbage collect if (isCaptchaStoreFull()) { //see if possible long now = System.currentTimeMillis(); Collection garbageCollectableCaptchaIds = getGarbageCollectableCaptchaIds(now); if (garbageCollectableCaptchaIds.size() > 0) { //possible collect an rerun garbageCollectCaptchaStore(garbageCollectableCaptchaIds.iterator()); return this.generateAndStoreCaptcha(locale, ID); } else { //impossible ! has to wait throw new CaptchaServiceException("Store is full, try to increase CaptchaStore Size or" + "to decrease time out, or to decrease CaptchaStoreSizeBeforeGrbageCollection"); } } if (isCaptchaStoreQuotaReached()) { //then garbage collect garbageCollectCaptchaStore(); } return generateCountTimeStampAndStoreCaptcha(ID, locale); } private Captcha generateCountTimeStampAndStoreCaptcha(String ID, Locale locale) { //update stats numberOfGeneratedCaptchas++; //mark as now Long now = new Long(System.currentTimeMillis()); //store in my timestampeds ids this.times.put(ID, now); //retrieve and store cpatcha Captcha captcha = super.generateAndStoreCaptcha(locale, ID); return captcha; } protected boolean isCaptchaStoreFull() { return getCaptchaStoreMaxSize() == 0 ? false : getCaptchaStoreSize() >= getCaptchaStoreMaxSize(); } protected boolean isCaptchaStoreQuotaReached() { return getCaptchaStoreSize() >= getCaptchaStoreSizeBeforeGarbageCollection(); } /** * Method to validate a response to the challenge corresponding to the given ticket and remove the coresponding * captcha from the store. * * @param ID the ticket provided by the buildCaptchaAndGetID method * * @return true if the response is correct, false otherwise. * * @throws CaptchaServiceException if the ticket is invalid */ public Boolean validateResponseForID(String ID, Object response) throws CaptchaServiceException { Boolean valid = super.validateResponseForID(ID, response); //remove from local after because validate may throw an exception if id is not found this.times.remove(ID); //update stats if (valid.booleanValue()) { numberOfCorrectResponse++; } else { numberOfUncorrectResponse++; } return valid; } }