Wednesday, December 29, 2010

Work with sound jCaptcha in servlet, Part 2 - Customize Voice class in FreeTTS

I created a sound jCaptcha with FreeTTS in part 1. Now I need to add the sound captcha in a web application. There were three pieces to accomplish that.

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.

Monday, December 20, 2010

Work with sound jCaptcha in servlet, Part 1 - Setup FreeTTS

I have been working on jCaptcha recently. In addition to image captcha, I need to implement sound/audio captcha for 508 compliance. jCaptcha has an example here. However, I ran into two problems with the example. Firstly, there was an error when I used one instance (then changed captcha engine) for image and sound captcha. I ended up creating separate instances for image and sound captcha. Secondly, image and sound captcha don't share the same text sources so they are in fact two independent captchas. This is confusing since what the users see is not what they hear. But it seems to be a known problem and I have to live that. Otherwise, I would need to write my own implementation of WordGenerator to make sure to use the same text before passing it to captcha engine.

Note that jCaptcha depends on FreeTTS for sound. Here are the steps to setup FreeTTS for jCaptcha.
  1. Download and unzip jCaptcha. I used 1.2.2.
  2. Run jcaptcha.exe in /lib
  3. Copy all jars to your jCaptcha project classpath
  4. Set system property directly. This saves the trouble to mess with FreeTTS configuration.
            String voiceClasses = System.getProperty("freetts.voices"); // another one "freetts.voicesfile"
if (voiceClasses == null) {
System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory");
voiceClasses = System.getProperty("freetts.voices");
}
log.info("freetts.voices:" + voiceClasses);

String bundle = "toddlist";
DictionaryReader reader = new FileDictionary(bundle);
WordGenerator generator = new DictionaryWordGenerator(reader);

SoundConfigurator soundConfig = new FreeTTSSoundConfigurator("kevin16", VOICE_PACKAGE, 1.0f, 100, 100);

WordToSound word2sound = new FreeTTSWordToSound(soundConfig, 3, 6);

CaptchaFactory[] factories = new CaptchaFactory[]{new GimpySoundFactory(generator, word2sound)};

CaptchaEngine captchaEngine = new GenericCaptchaEngine(factories);

instance = new GenericManageableCaptchaService(captchaEngine, 180,
180000, 75000);



With these limitations, my code compiled and passed the unit tests. Now this is the part one of the story. Stayed tuned for part 2.

Read a file from classpath

I need to read a file from classpath. It is well documented to do this by
getClass().getClassLoader().getResourceAsStream()

But this returns an InputStream. What if I need a File? It turns out that I can use getResource() instead of getResourceAsStream(), to create a URL and then get a URI to create a File.
URL fileUrl = getClass().getClassLoader().getResource("test.txt");
File file = new File(new URI(fileUrl.toString()));

Friday, December 10, 2010

Where does System.out.print() output go in OAS?

Just to be clear, you are not supposed to use SOP. Use logging instead. Commons logging, java.util.logging, log4j, slf4j, you name it. However, what if third-party code has SOP and you wonder where the output goes when deployed in OAS (Oracle Application Server).

OAS actually dumps the output to opmn log under OAS. In my local installation, it is C:\product\10.1.3.1\OracleAS_1\opmn\logs\default_group~home~default_group~1.log. Just be aware it saves almost all output so the file could be huge.

Wednesday, December 1, 2010

Start OC4J with -userThreads

I have an application deployed on OC4J. It uses quartz to schedule a job. The job connects to the database through JNDI. The JNDI is defined through Enterprise Manager. When started, the log showed the following error.

Javax.naming.NamingException: Not in an application scope - start Orion with the -userThreads switch if using user-created threads

It turns out that I need to add "-userThreads" to OC4J options to overcome this. I did this through EM > Administration > Server properties, and then add "-userThreads" to OC4J options. The error was gone after a restart.