AudioInspector
Project Facts:
- Client: MediaServices GmbH
- Duration: July 2007 - October 2007
- Tasks: Implementation of webshop, remote interface, backoffice and hosting
- Technology: Java, Servlet/JSP, Tomcat, Stripes web framework, MySQL, iBATIS, Eclipse
- URL: www.audioinspector.com
AudioInspector is a tool for digitization of analogue audio streams. It is used for transferring audio content from original recording media to digital mass storage systems. AudioInspector is a desktop program that runs on Microsoft Windows XP and Vista (Screenshot of AudioInspector).
AudioInspector can be only purchased online, at www.audioinspector.com. It is available in two license models, usage-based and unlimited. With the usage-based model you pay a small initial license fee, and then you just pay when using the software. Therefore the software makes remote calls to audioinspector.com for authentication, license installation, and charging.
My part of the project was to create
- Web page design and layout
- Web shop including checkout and credit card payment
- Remote Interface for authentication, license handling and charging
- Backoffice for user management, license handling and order management
- Hosting, maintenance and monitoring
Implementation Details
Web Application
For the web application I decided to use the Stripes Web Framework. In the past I already used Struts and WebWork in big international projects, but I was really excited about Stripes. Almost no configuration, easy to understand and use. Perfect. And if someone argues that the functionality Stripes provides is not sufficient for his complex web project, then he (or she) should re-think if the project - or the approach - is too complex in general.
Service Layer
During the last years in my previous job, I was extensively working in the design and implementation of services for enterprise applications. Therefore I know the importance this part for a big application, especially when 3 different parts (web shop, back office, remote interface) share the same service layer. I already invested a lot of time in this area.
5 years ago, I started to work with Apache Avalon, which is a brilliant concept and API, but lacked in the implementation with Fortress and Merlin. Of course I got in touch with EJB’s and the common way of implementing services as facades. Later I also used Spring and Google Guice, but more as interest and for smaller applications.
3 years ago I started together with a previous co-worker and friend (Kristinn Danielsson), to implement our own service framework. Goal: it should be simple to use and understand, and should only require minimum configuration. The 3rd version which was already excellent, we used for the Vodafone Music platform. There we proved that the concept works, and that our implementation was of production quality in huge environments. But I still found areas that were not perfect. In May 2007 I left the company, but still had the ideas and improvements in my head. I started to create it from scratch. This time smaller and simpler.
Areas the service framework covers:
- Service lifecycle
- Configuration
- Logging
- Exception handling concept
- Testing and mocks
- Aspects
- …
For each of topics I thought a lot, tried out several prototypes, and could write and discuss very, very long. Years of experience is in my framework.
Currently I’m investigating, how and when I will launch it. I will probably bring it open source together with my second product that is built on top of it.
So that you get a feeling, how the service framework works, I outline here one simple re-usable service, that is implemented using my concept.
It will be an EmailService that can be used to send emails from an application. The implementation uses Velocity templating engine, so that you can use customized emails, or for instance HTML emails using Velocity files. The EmailService implementation will be named VelocityEmailService. Of course someone can implement his own version, for instance a FreemarkerEmailService, and configure this one his application.
The major part of the concept is a strict separation of a service into an API and implementation. And if one service uses another service, it may only depend on the other services API, never the implementation.
EmailService.java (API)
package at.mext.library;
import ...
public interface EmailService {
/**
* Sends an email to the given email address.
*
* @param to email address of the receiver
* @param subject subject of the email
* @param template name of the template used to
* generate the email
* @param context Map with context variables passed
* to the templating engine
* @param attachments array of the files to attach.
*/
public void send(String to, String subject, String template,
Map<String, Object> context, String[] attachments);
}
This EmailService consists of only one method - this is just for simplicity and demonstration purpose.
VelocityEmailService.java (Implementation)
package at.mext.library;
import ...
@Singleton
public class VelocityEmailService implements EmailService, Startable {
@Configure String hostName = "smtp.mailserver.com";
@Configure String hostUser = "user";
@Configure String hostPassword = "secret";
@Configure String from = "support@mext.at";
private VelocityEngine velocity;
public void start() {
velocity = new VelocityEngine();
velocity.init();
}
@Asynchronous
public void send(String to, String subject, String template,
Map<String, Object> context, String[] attachments) {
try {
String message = getVelocityMessage(template, context);
MultiPartEmail email = new MultiPartEmail();
email.setAuthentication(hostUser, hostPassword);
email.setHostName(hostName);
email.setFrom(mailFrom);
email.addTo(to);
email.setSubject(subject);
email.setMsg(message);
if (attachments != null) {
for (String attachment : attachments) {
email.attach(getEmailAttachment(attachment));
}
}
email.send();
} catch (Throwable t) {
t.printStackTrace();
}
}
private EmailAttachment getEmailAttachment(String attachment) {
EmailAttachment emailAttachment = new EmailAttachment();
emailAttachment.setPath(attachment);
String name = attachment;
if (attachment.indexOf('/') >= 0) {
name = attachment.substring(attachment.lastIndexOf('/') + 1);
}
emailAttachment.setName(name);
emailAttachment.setDescription(name);
return emailAttachment;
}
private String getVelocityMessage(String template,
Map<String, Object> context) throws ResourceNotFoundException,
ParseErrorException, Exception {
VelocityContext velocityContext = new VelocityContext(context);
Template velocityTemplate = velocity.getTemplate(template);
StringWriter writer = new StringWriter();
velocityTemplate.merge(velocityContext, writer);
return writer.toString();
}
public void stop() {
velocity = null;
}
}
As you can see the VelocityEmailService simply implements EmailService, and Startable which is a lifecycle interface of my service framework. Startable services must implement two methods start() and stop(). Services usually implement Startable if they require some initialization work and clearance.
@Singleton is an annotation of the service framework and marks this service as singleton service. This means that only one instance of the service exist in the container, and the service methods must be thread safe.
@Configure is the way you can configure variables of the service implementation. This is quite cool, because when you implement the service, you can define that a variable is configurable and can be modified later via property file or JMX. But you don’t need to care about this right now, just give the variable a default value and continue with the implementation.
The implementation of the send method is annotated with the @Asynchronous aspect. This means that the execution of the service call is asynchronous. The default implementation of the Asynchronous aspect simply executes the method in a separate thread. But it is very easy to create an own implementation that uses JMS of the underlying container.
The implementation uses the Apache commons email library.
The default implementation of the service framework uses the ServiceLoader mechanism introduced with Java 6. Therefore you have to add a file named at.mext.library.EmailService to META-INF/services. The file just contains the fully qualified name of the implementation, which is at.mext.library.VelocityEmailService.
Usage of the EmailService
Suppose you have a RegistrationService that sends a welcome email after successul registration, then the RegistrationService implementation (e.g. DefaultRegistrationService) can use the EmailService by using the API.
DefaultRegistrationService.java (Implementation that uses the EmailService)
package at.mext.library;
import ...
@Singleton
public class DefaultRegistrationService implements RegistrationService {
@InjectService Service service;
...
public void register(User user) {
// add the user to the DB
...
Map<String, Object> context = new HashMap<String, Object>();
context.put("user", user);
service.get(EmailService.class).send(user.getEmail(),
"Welcome", "emails/welcome.vm", context, null);
}
}
Velocity Welcome Email Template (emails/welcome.vm)
Welcome $user.firstName $user.lastName, thank you for registering at ...
@InjectService injects the service manager to the service. The service manager provides access to other services via the get method and the API class of the using service. E.g. service.get(EmailService.class) returns the VelocityEmailService instance in the previous example. If you have different implementations you can configure the wanted implementation either on class level or on global level. This gives you full controll over the used implementations. But usually, if you start with one implementation of your service you don’t have to configure anything.
You can imagine, that with this concept you are able to create a library with many services, that can be re-used in several projects.
I hope this simple example gives you an impression of the power and simplicity of the service framework. If you are interested in it, just send me an email or leave a comment. I will open source it with full documentation, examples and tutorials very soon. The implementation of version 1.0 is ready, and already used in this project (www.audioinspector.com).

Subscribe to this Weblog