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.

Tuesday, November 16, 2010

Java applet logging

The chances are that you are already using Apache commons logging and log4j in your application. A new challenge is how you do logging for Applet. By that I mean, dumping the output to applet Java console.

Good news is that you don't need to change your code at all. In addition, I think it is overkill to include log4j.properties and log4j.jar as part of client code for Java applet. Without them, commons logging will simply pick default Java logging and it works like a charm.
Log log = LogFactory.getLog(getClass());

The catch is to make sure to use the correct logging level. On the client side, Java logging is defined here. The default logging level is info.
C:\Program Files\Java\jdk1.6.0_20\jre\lib\logging.properties

.level= INFO
So you want to use at least log.info(), instead of log.debug(), to see anything in the Java applet console. Otherwise, the setting has to be changed in logging.properties. It is just not realistic to ask every end user to do it.

Compile options in Ant javac task

Recently I was tasked to migrate a code base from JDK 1.4 to 1.6. As part of this work, I need to take care of deprecated methods and unchecked types in collections. To verify, I turned on the options in ANT javac task as below. Note the use of deprecation="on" and <compilerarg value="-Xlint:unchecked"/>.

        <javac destdir="${build.dir}" source="1.6" target="1.6" debug="true"
deprecation="on" failonerror="true">
<src path="${src.dir}"/>
<classpath refid="master-classpath"/>
<compilerarg value="-Xlint:unchecked"/>
</javac>

Monday, November 15, 2010

Create a connection pool to MySQL in OC4J

It is quite easy to create a connection pool/datasource to an Oracle database in OAS EM (Enterprise Manager). You simply do that in OC4J > Administration > JDBC Resources. However, it is not that straight forward to do that with a third party database, in my case, MySQL. Basically, you need to provide mySQL jdbc driver and make sure to use the correct parameters.

This is the obscure part. The creation of connection pool/datasource in EM is via the ascontrol application. So we need to let it know how to load mySQL jdbc driver. There are two steps. Firstly, copy mysql-connector-java-5.1.13-bin.jar to
C:\product\10.1.3.1\OracleAS_1\j2ee\home\applications\ascontrol\ascontrol\WEB-INF\lib. Secondly, edit C:\product\10.1.3.1\OracleAS_1\j2ee\home\applications\ascontrol\META-INF\orion-application.xml and comment out the "remove-inherited" section like below.

   <imported-shared-libraries>
<!--
<remove-inherited name="global.libraries"/>
-->
<import-shared-library name="oracle.xml.security"/>
</imported-shared-libraries>



Everything is pretty straight forward after this. To create a connection pool, remember to use "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource" for "Connection Factory Class" and similar to "jdbc:mysql://localhost:3306/test" for "JDBC URL". You can create a datasource the same way after a connection pool is created.

Reference:
http://forums.oracle.com/forums/thread.jspa?threadID=680574

Tuesday, September 7, 2010

MySQL connection pool with DBCP

I have an application using Apache Commons DBCP for connection pooling to MySQL database. It was deployed on Tomcat. The users had problem login the application (authenticated through the MySQL database) after a long period of idle time. The subsequent login always worked. The log showed the following exception.

com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception:
** BEGIN NESTED EXCEPTION **
java.io.EOFException
STACKTRACE:
java.io.EOFException
...


My guess was that the connection pool went dead due to the long idle period. After some Googling, I specified two more DBCP configurations in BasicDataSource Spring configuration.

<property name="testWhileIdle">
<value>true</value>
</property>
<property name="validationQuery">
<value>select 1</value>
</property>


I was able to login successfully after 14 hours of idle time. So this looks promising, but still needs more testing.

Wednesday, August 25, 2010

FileUpload Streaming

I use Apache Commons FileUpload to upload files. I was doing it in the "traditional" way, using DiskFileItemFactory. One requirement I have is that I need to convert the stream to DataHandler, because it will then be passed to call web services with MTOM. This works perfectly fine with DiskFileItermFactory. However, with DiskFileItemFactory, you still need to setup a temp folder to save the file and also FileCleaningTracker to clean up. So I decided to give the streaming API a try.

I had to create FileItemStreamDataSource to wrap FileItemSteam for the purpose of creating a DataHandler.
package com.myapp.fileupload;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.activation.DataSource;

import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class FileItemStreamDataSource implements DataSource {
/**
* log.
*/
private Log log = LogFactory.getLog(getClass());

private FileItemStream stream;

public FileItemStreamDataSource(final FileItemStream stream) {
this.stream = stream;
}

@Override
public String getContentType() {
return stream.getContentType();
}

@Override
public InputStream getInputStream() throws IOException {
log.info("******** open stream **********");
return stream.openStream();
}

@Override
public String getName() {
return stream.getName();
}

@Override
public OutputStream getOutputStream() throws IOException {
throw new IOException();
}

}



There was no error in tests. However, when trying to open the file on the other end of web service, I found the content was either corrupted or empty (only 1KB). I wonder if this is because the stream was not read in full somehow. Maybe it is the same as the discussion here? Could it be that the stream is not kept open in web service calls? Anyway, I did not have enough time to get it work now and will need to revisit it later.

Friday, August 13, 2010

Enable Oracle SSO for an application

Assuming that SSO is enabled for OAS. (This requires a conf file generated from the SSO provided and installed in C:\product\10.1.3.1\OracleAS_1\Apache\Apache\conf\osso on your OAS)

After deploying your application (myapp), add the following to your "mod_osso.conf" file C:\product\10.1.3.1\OracleAS_1\Apache\Apache\conf\mod_osso.conf.

    <Location /myapp> 
require valid-user
AuthType Basic
</Location>

Wednesday, August 11, 2010

Spring AOP Interception

According to this post, most important - proxy-based AOP only works from "outside". Internal method calls are never intercepted.

This means that only the methods of your Spring beans can be intercepted. If this is not a problem, then here is an example.

Java
package com.my.aop;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.ThrowsAdvice;

/**
* A Spring AOP advice to log service exception.
*/
@Aspect
public final class LoggingAdvice implements ThrowsAdvice {

/**
* Log.
*/
private Log log = LogFactory.getLog(this.getClass());

/**
* Log AxisFault detail after it is thrown.
*
* @param method calling method
* @param args method arguments
* @param target target
* @param ex AxisFault
* @throws Throwable throwable
*/
@AfterThrowing(pointcut="execution(* com.my.service.MyService.*(..))", throwing="ex")
public void afterThrowing(final Throwable ex) {
log.error(System.getProperty("line.separator") + "*****" + ex.getMessage());
}
}


Spring applicationContext.xml
<aop:aspectj-autoproxy/>
<bean id="loggingAdvice" class="my.com.aop.LoggingAdvice" />

Thursday, July 29, 2010

On enterprise architect

Quote from The Role of the Enterprise Architect.

one of the fundamental requirements for being a successful EA is the ability to lead without actually owning anything: If you don't have good leadership skills, the rest of it fundamentally doesn’t matter... If you do not lead and do not take the risk to lead, the transformation won’t occur. One of the barriers for the profession today is that many architects are not prepared to take the risk of leadership.

The authority of an architect ultimately comes from their ability to articulate a compelling value proposition for architecture in general, for specific architect in a specific situation... This is probably one of the biggest problems that architects coming from a technical background have. They'll tell you about features and functions but never get around talking about benefits.

Wednesday, May 5, 2010

Missing Maven dependencies when deployed in Eclipse

I created a dynamic web project in Eclipse with Maven structure. I have Maven Eclipse Plugin so I enabled it. Everything seemed fine but the app wouldn't run in Tomcat within Eclipse. It turned out that the maven dependencies did not get copied to WEB-INF/lib when I browsed to "wtpwebapps" folder where the app was deployed.

To enable it, go to Project > properties > Java EE Module Dependencies, and check "Maven Dependencies" for this project. And it worked like a charm.

Friday, April 30, 2010

Quote from Skype architect

Technical skills are a hygiene factor for architects. You need to have them to be accepted for the job. But emotional intelligence and ability to understand organizations are the skills that define how good you really are.

Here
is the complete original post.

Saturday, April 24, 2010

Create a template project with gwt-maven-plugin archetype

I wanted to use maven for my GWT project. In the beginning, I ran archetype to create a project template with gwt-maven-plugin.

mvn archetype:generate -DarchetypeRepository=http://repository.codehaus.org -DarchetypeGroupId=org.codehaus.mojo -DarchetypeArtifactId=gwt-maven-plugin -DarchetypeVersion=1.2 -DgroupId=myGroupId -DartifactId=myArtifactId

Friday, March 26, 2010

Download artifact from remote repository

Sometimes you may want to download an artifact from a remote repository without adding the dependency in pom.xml. You can do this with dependency:get.

mvn org.apache.maven.plugins:maven-dependency-plugin:2.1:get \
-DrepoUrl=http://download.java.net/maven/2/ \
-Dartifact=robo-guice:robo-guice:0.4-SNAPSHOT


Here is a Reference.