The Unbearable Lightness of Java

Serve with Petite

We have two goals here: to build service layer within Petite container and to merge it with the our Madvoc web application. Of course, it is vital to have Petite completely decoupled from web layer.

Plain Old Service Core

Now, let's forget we are building a web application. We want to build a business core of the application that can be started from the command line (and tests cases). Lets encapsulate the application core in AppCore class, that will be responsible for application lifecycle and configuration. During the startup, AppCore will create and configure the Petite container. One possible implementation may look like this:

public class AppCore {

	private static Logger log;

	public synchronized void start() {
		AppUtil.resolveRootDirs("app.nfo");
		AppUtil.prepareAppLogDir("log");
		initLogger();
		initPetite();
		initDb();
		...				// init everything else
		log.info("app started");
	}

	public synchronized void stop() {
		// close everything
		log.info("app stopped");
	}

	protected PetiteContainer petite;

	void initPetite() {
		log.info("petite initialization");
		petite = new PetiteContainer();
		AutomagicPetiteConfigurator pcfg = new AutomagicPetiteConfigurator();
		pcfg.setIncludedEntries(new String[] {this.getClass().getPackage().getName() + ".*"}););
		pcfg.configure(petite);
	}

	public Object getBean(String name) {
		return petite.getBean(name);
	}
	...
}

The lifecycle is simple: application can be started and stopped (we removed above the state tracking for the sake of simplicity). First various application directories are resolved, then logger is initialized, and, finally, Petite container is created (method initPetite). Immediately after creation, Petite container is auto-configured using AutomagicPetiteConfigurator. This configuration scans the class path for all classes that are annotated with PetiteBean annotation. Since classpath of web application may contain many jars, we can speed up the scanning process by narrowing the search only to certain packages. Here configurator simply scans for all packages that are bellow the package of AppCore - we assume that services are somewhere bellow.

This is just one way of doing things; what matters here is the concept and not the concrete values, structure or configuration used in example.

That is all, Petite is ready for use and upon start it will find all annotated services.

Integrate Madvoc and Petite

Now we have to integrate Madvoc and Petite application context. Jodd already provides support for this with PetiteWebApplication, a Petite-ready web application. As you remember, we already made AppWebApplication for our custom needs. So first, we need to make it Petite-aware, i.e. to extends PetiteWebApplication instead of just WebApplication.

By default PetiteWebApplication creates container instance that will be used as application context, so we need to change that and use our existing container from AppCore. Again, it is just a matter of overriding providePetiteContainer method.

Here is how our AppWebApplication might look like, integrated with the AppCore:

public class AppWebApplication extends PetiteWebApplication {

	final AppCore appCore;

	public AppWebApplication() {
		appCore = new AppCore();
		appCore.start();
	}

	@Override
	protected PetiteContainer providePetiteContainer() {
		return appCore.petite;
	}

	@Override
	protected void destroy(MadvocConfig madvocConfig) {
		appCore.stop();
		super.destroy(madvocConfig);
	}
}

And that is all: Petite-aware Madvoc web application.

Simple test

Let's add some dummy service:

@PetiteBean
public class FooService {
}

that will be injected in previously created IndexAction:

@MadvocAction
public class IndexAction {

	@PetiteInject
	FooService fooService;

	@Action
	public void view() {
		System.out.println(fooService);
	}
}

To test: start Tomcat and go to /index.html - fooService is initalized.

LocalRunner

Always when I am having decoupled business layer from the web layer, I make a simple console-mode application where I can play with the application without starting the Tomcat:

public class LocalRunner {

	public static void main(String[] args) {
		AppCore appCore = new AppCore();
		appCore.start();

		FooService fooService = (FooService) appCore.getBean("fooService");
		System.out.println(fooService);
		
		appCore.stop();
	}
}

Load parameters

Now back to the Petite container configuration. Usually, container reads some parameters from properties stored on classpath. For this example, we will put all parameters in several properties files on classpath root: app.properties, app-doc.properties and so on. Lets modify initPetite() method:

public class AppCore {
	...
	void initPetite() {
		log.info("petite initialization");
		petite = new PetiteContainer();
		
		// automatic configuration
		AutomagicPetiteConfigurator pcfg = new AutomagicPetiteConfigurator();
		pcfg.setIncludedPackages(new String[] {this.getClass().getPackage().getName() + ".*"});
		pcfg.configure(petite);
		
		// load parameters
		Properties appProperties = PropertiesUtil.createFromClasspath("/app*.properties");
		petite.defineParameters(appProperties);
		
		// add appCore to Petite (and resolve parameters)
		petite.addBean("app", this);
	}
	...
}

Line #17 shows a nice trick: AppCore instance is added to the container. The main reason for that is to configure AppCore from loaded parameters. When line #17 is executed, all properties named as "app.*" will be injected into the AppCore instance.

Enable session scope beans

Even if not yet sure if we gonna needed, we can prepare Petite to be able to use session scoped beans. The following listeners have to be registered in web.xml:

...
<!--listeners-->
<listener>
	<listener-class>jodd.servlet.RequestContextListener</listener-class>
</listener>
<listener>
	<listener-class>jodd.servlet.HttpSessionListenerBroadcaster</listener-class>
</listener>
...

Once when we add session scope bean, we will not be able to run application outside of servlet container. To fix this, we need to detect if we are running under servlet container (for example, there is a WEB-INF part in applications classpath) and to replace session scope with, e.g. prototype scope:

public class AppCore {

	protected boolean isWebApplication;

	public synchronized void start() {
		isWebApplication = AppUtil.resolveAppDirs("app.nfo");
		AppUtil.prepareAppLogDir("log");
		...
	}
	
	void initPetite() {
		log.info("petite initialization");
		petite = new PetiteContainer();
		if (isWebApplication == false) {
			petite.getManager().registerScope(SessionScope.class, new ProtoScope());
		}
		...
	}

}