Firstly, create a servlet.
public class AudioCaptchaServlet extends HttpServlet {
/**
* Serial uid.
*/
private static final long serialVersionUID = 1L;
/**
* Logger.
*/
private static Log logger = LogFactory.getLog(AudioCaptchaServlet.class);
private static final SoundCaptchaService AUDIO_CAPTCHA_SERVICE =
AudioCaptchaService.getInstance();
/**
* Init.
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
logger.info("Initalizing AudioCaptchaServlet...");
}
/**
*
*/
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
logger.info("Generating sound captcha for session id "
+ request.getSession().getId());
AudioInputStream challenge = null;
try {
// get the session id that will identify the generated captcha.
// the same id must be used to validate the response, the session id
// is a good candidate!
String captchaId = request.getSession().getId();
// call the AudioCaptchaService getChallenge method
challenge = AUDIO_CAPTCHA_SERVICE.getSoundChallengeForID(captchaId);
} catch (IllegalArgumentException e) {
logger.error("Illegal argument exception occured.", e);
throw new RuntimeException(e);
} catch (CaptchaServiceException e) {
logger.error("Captcha service exception occured.", e);
throw new RuntimeException(e);
} catch (Exception e) {
logger.error("An exception occured.", e);
throw new RuntimeException(e);
}
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("audio/wav");
ServletOutputStream out = response.getOutputStream();
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
AudioSystem.write(challenge,
javax.sound.sampled.AudioFileFormat.Type.WAVE,
byteOutputStream);
out.write(byteOutputStream.toByteArray());
out.flush();
out.close();
logger.info("Generating sound captcha completed");
}
}
Secondly, add the servlet configuration in web.xml.
<servlet>
<servlet-name>Audio Captcha Servlet</servlet-name>
<servlet-class>myapp.servlet.AudioCaptchaServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Audio Captcha Servlet</servlet-name>
<url-pattern>/jcaptcha.wav</url-pattern>
</servlet-mapping>
Thirdly, add the sound captcha in jsp.
<a href="myapp/jcaptcha.wav"><img src="myapp/resources/images/speaker1.gif" alt="Sound Captcha" /></a>
Everything seemed to be working together nicely so far. I was able to see the sound captcha icon once the web application is deployed. However, when I clicked the icon, it did not play the sound and an error message was found in the log.
Trouble while processing utterance java.lang.IllegalStateException: Syllable relation has already been set.
I googled around and felt lucky to found this page. I downloaded FreeTTS 1.2.2 source release, updated Voice.java as below, and rebuilt the binaries using maven.
public void deallocate() {
setLoaded(false);
//TODO consider removing utteranceProcessors.clear();
utteranceProcessors.clear();
if (!externalAudioPlayer) {
if (audioPlayer != null) {
audioPlayer.close();
audioPlayer = null;
}
}
if (!externalOutputQueue) {
//TODO Consider removing comment
//outputQueue.close();
}
}
Once I replaced FreeTTS with the new binary in the web app, everything worked as expected.
In retrospect, it was still a mystery why I had to customize Voice.java to make FreeTTS work sound jCaptcha in servlet. In addition, I was not a big fan that there is no out-of-box support for image and sound captcha to use the same characters. But I was happy that I managed to get jCaptcha work to meet the requirements. This was quite a learning experience.