Friday, August 31

Reusable Advice - Part II: Unobtrusive Notification

Besides encapsulating behaviors that affect multiple classes into reusable modules, AOP can also be used for unobtrusive notification tasks.
Instead of browsing application logs looking for exceptions, for example, I like to have the application itself email me the exception stack-trace and the arguments that caused the error when something goes wrong.

Here's an example advice that sends an email after an exception is thrown. The date of occurrence and the exception stack-trace are sent in the body of the email.

The Java code for the advice:



...
public class ThrowableAdvice {
private MailSender mailSender;
private SimpleMailMessage mailMessage;
public static final String NEW_LINE = System.getProperty("line.separator");

// Setters and getters omitted for clarity
...

/**
* Send email notification with exception stacktrace
*
* @param throwable
* @return boolean frue if email message is sent, false otherwise
*/
public boolean notifyByEmail(Throwable throwable) {
SimpleMailMessage msg = new SimpleMailMessage(this.mailMessage);
StringBuffer txt = new StringBuffer(msg.getText());
txt.append(NEW_LINE).append(new Date());
txt.append(NEW_LINE).append(ExceptionUtils.stackTraceToString(throwable));
msg.setText(txt.toString());
try {
this.mailSender.send(msg);
return true;
} catch (MailException me) {
logger.error("Error sending email notification ", me);
}
return false;
}
}
...


A useful method to "print" a stack-trace onto a String:



...
public static String stackTraceToString(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
...


And a snippet of the Spring XML configuration:



...
<aop:config>
<aop:aspect id="appThrowableInterceptor" ref="throwableAdvice">
<aop:after-throwing
method="notifyByEmail"
throwing="throwable"
pointcut="execution(* some.package.*.*(..))" />
</aop:aspect>
</aop:config>

<bean id="throwableAdvice" class="ThrowableAdvice">
<property name="mailSender" ref="mailSender"/>
<property name="mailMessage" ref="throwableMessage"/>
</bean>

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="localhost"/>
</bean>

<bean id="throwableMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="subject" value="ALERT! Exception thrown @ some app module"/>
<property name="text" value="ALERT! Exception thrown from some module"/>
<property name="from" value="the.notifier@application.org"/>
<property name="to">
<list>
<value>developers@application.org</value>
</list>
</property>
</bean>
...


This is as flexible and unobtrusive as it gets -the throwableAdvice advice above can be:
  • easily reused by specifying a different pointcut (above in bold);
  • removed altogether without needing to touch the advised (target) code;



(Vaguely) Related Posts:
Better Living Through (Unit) Testing and Advice
Reusable Advice - Part I


References:
AOP@Work: Dependency injection with AspectJ and Spring
AOP@Work: Check out library aspects with AspectJ 5
AOP@Work: AOP myths and realities
Improve modularity with aspect-oriented programming
Spring AOP Reference documentation

Saturday, August 18

Compulsive Attention Seeker

A few weeks ago, I approached a TfL staff member (let's call him SM) after failing to exit the gate with my Oyster card. Chat transcript below.

Me: "Hi, I can't get out and the machine keeps telling me to seek attention..."

SM (grinning and slowly printing each word in my head): "seek attention? Are you sure the machine told you to seek attention"?

Me (mildly annoyed and a tad confused): "Yes, it displayed the words seek attention in flashing red letters!"

This goes on a few more rounds until SM, visibly amused, opens the gate and let's me through.

...


Today's note to self: the gate screens actually display the words "Seek Assistance"...

Sunday, August 12

Resources for learning Chinese (language & culture)

http://chinesepod.com/
Download free .mp3 podcasts with the basic, free account. Each lesson is a short and entertaining conversation between an English native and a Chinese speaker.
There is also plenty of activity in the discussion panels, where community members help and discuss spoken and writing details about the lessons.

http://fsi-language-courses.org/
Very thorough and authoritative course material developed by the United States government. Each course module contains audio (.mp3), a student textbook (in .PDF format), and a workbook. The lessons are impeccable and demanding.

http://www.pinyin.info/
"This website is aimed at contributing to a better understanding of the Chinese languages and how romanization can be used to write languages traditionally associated with Chinese characters (such as Japanese, Korean, and especially Mandarin Chinese)."
Very academic with plenty of excellent scholarly essays, such as:




http://zhongwen.com/
Online dictionary of Chinese characters.

http://www.echineselearning.com/
Online, one-on-one tutoring with native Chinese teachers. Similar to ChinesePod but access to most content requires paid subscription.

[Update, Jan 2013]:
Mr.China

[Update, Mar 2010 via Mariam]:
Skritter
http://www.skritter.com/
Learn Chinese and Japanese characters by actually writing them from scratch. Much better than this Taiwanese school at teaching pronunciation and stroke order.
Slick and easy to use User Interface.


[Update, Apr 2008]:
Nciku
http://www.nciku.com
Dictionary, conversations (text-to-speech annotated with hànzì and pīnyīn), Q & A space, tools, an awesome handwriting recognition tool that let's you draw Chinese characters, and plenty more cool stuff!


[Update, Dec 2007]:
Free Foreign Language Courses Online
http://education-portal.com/articles/Free_Foreign_Language_Courses_Online.html

BBC Languages - Mandarin Chinese
http://www.bbc.co.uk/languages/chinese/

MIT - Foreign Languages and Literatures
http://ocw.mit.edu/OcwWeb/Foreign-Languages-and-Literatures/index.htm

Utah University - Languages, Philosophy and Speech Communication
http://ocw.usu.edu/Languages__Philosophy_and_Speech_Communication

eLanguageSchool.net - Learn to Speak Mandarin Chinese Online
http://learnchinese.elanguageschool.net/

Other interesting resources:
Language Centre in the School of Oriental and African Studies (SOAS) at the University of London.

Comparing American and Chinese Negotiation Styles (Video)

How hard is it to learn Chinese? (BBC News)

88 MOCCA - The Museum of Chinese Contemporary Art on the web

Chinese Contemporary

UK Chinese Music

Beginner's Chinese

I know I'll be 'intermediate material' the day I am able to karaoke to this song...

Friday, August 10

CRUD unit-testing checklist

I'm writing a unit-test code generator for CRUD operations to keep me from repeating myself. In preparation for this, I'm compiling a comprehensive set of CRUD unit-tests beyond the eponymous ones.

So, for a given business object Foo I may want to test:

Create


public void TestCreate_NullFoo {
try {
PersistenceServices.create(Null);
} catch(PersistenceServiceException pse){
return;
}
fail("Expected exception not thrown");
}

// possible test, depends on requirements
public void TestCreate_EmptyFoo {
Foo foo = new Foo(); // no properties set
try {
PersistenceServices.create(foo);
} catch(PersistenceServiceException pse){
return;
}
fail("Expected exception not thrown");
}

// depends on requirements; validation shouldn't happen here
public void TestCreate_InvalidFoo {
Foo foo = new Foo();
foo.set...; // populate with known invalid values
try {
PersistenceServices.create(foo);
} catch(PersistenceServiceException pse){
return;
}
fail("Expected exception not thrown");
}

// depends on requirements; validation shouldn't happen here
public void TestCreate_IncompleteFoo {
Foo foo = new Foo();
foo.set...; // miss required values
try {
PersistenceServices.create(foo);
} catch(PersistenceServiceException pse){
return;
}
fail("Expected exception not thrown");
}

public void TestCreate_IncrementsCount {
int beforeCreate = PersistenceServices.countFoos();
Foo foo = new Foo();
foo.set...; // populate with required values
PersistenceServices.create(foo);
int afterCreate = PersistenceServices.countFoos();
assertTrue(afterCreate > beforeCreate);
}

public void TestCreate_IsNotIdempotent {
Foo foo = new Foo();
foo.set...; // populate with required values
Foo aCreated = PersistenceServices.create(foo);
Foo bCreated = PersistenceServices.create(foo);
assertNotEquals(aCreated, bCreated);
}

public void TestCreate_Foo {
Foo foo = new Foo();
foo.set...; // populate with required & validated values
Foo created = PersistenceServices.create(foo);
assertEquals(foo, created);
}



Read

public void TestRead_NullFoo {
Foo read = PersistenceServices.getFoo(Null);
assertNull(read);
}

// depends on requirements
public void TestRead_EmptyFooId {
Foo read = PersistenceServices.getFoo("");
assertNull(read);
}

public void TestRead_DoesNotIncrementCount {
int beforeRead = PersistenceServices.countFoos();
PersistenceServices.getFoo(aFoo); // aFoo is known, existing Foo
int afterRead = PersistenceServices.countFoos();
assertTrue(beforeRead == afterRead);
}

public void TestRead_DuplicateFoo { // see IsIdempotent below
}

public void TestRead_IsIdempotent {
Foo aFoo = PersistenceServices.getFoo(aFoo);
Foo bFoo = PersistenceServices.getFoo(aFoo);
assertEquals(aRead, bRead);
}

// depends on requirements; could throw friendly exception
public void TestRead_DeletedFoo {
Foo deleted = PersistenceServices.delete(aFoo);
Foo read = PersistenceServices.getFoo(deleted);
assertNull(read);
}

public void TestRead_FooById {
Foo foo = new Foo();
foo.set...; // populate with required & validated values
Foo created = PersistenceServices.create(foo);
Foo read = PersistenceServices.getById(created.id);
assertEquals(foo, read);
}



Update

public void TestUpdate_NullFoo {}

public void TestUpdate_EmptyFoo {}

public void TestUpdate_InvalidFoo {}

public void TestUpdate_IncompleteFoo {}

public void TestUpdate_DuplicateFoo {}

public void TestUpdate_UnchangedFoo {}

public void TestUpdate_DoesNotIncrementCount {}

public void TestUpdate_IsIdempotent {}

public void TestUpdate_UndoFoo {}

public void TestUpdate_Foo {}



Delete

public void TestDelete_NullFoo {
Foo deleted = PersistenceServices.delete(Null);
assertNull(deleted);
}

public void TestDelete_EmptyFoo { // dependent of business requirements
Foo foo = new Foo(); // no properties set
Foo deleted = PersistenceServices.delete(foo);
assertNull(deleted);
}

public void TestDelete_InvalidFoo {
}

public void TestDelete_IncompleteFoo {
}

public void TestDelete_DuplicateFoo {
}

public void TestDelete_DecrementsCount {
Foo aFoo = PersistenceServices.getFoo(aFoo);
int beforeDelete = PersistenceServices.countFoos();
PersistenceServices.delete(aFoo);
int afterDelete = PersistenceServices.countFoos();
assertTrue(beforeDelete > afterDelete);
}

public void TestDelete_IsIdempotent {
Foo aFoo = PersistenceServices.delete(aFoo);
Foo bFoo = PersistenceServices.getFoo(aFoo);
assertNull(bFoo);
aFoo = PersistenceServices.delete(aFoo);
bFoo = PersistenceServices.getFoo(aFoo);
assertNull(bFoo);
}

public void TestDelete_ExistingFoo {
Foo aFoo = PersistenceServices.delete(aFoo);
assertNotNull(aFoo);
Foo bFoo = PersistenceServices.getFoo(aFoo);
assertNull(bFoo);
}

public void TestDelete_MissingFoo {
// aFoo does not exist
Foo deleted = PersistenceServices.delete(aFoo);
assertNull(deleted); // could expect exception
}

// depends on requirements
public void TestDelete_ReturnsDeleted {
Foo aFoo = PersistenceServices.getById(aFoo.id);
Foo deleted = PersistenceServices.delete(aFoo);
assertEquals(aFoo, deleted);
}


(Vaguely) Related Posts
Testing For Developers
Automated Unit Testing
CRUD base classes for unit testing
Unitils Guidelines