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.
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.
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.
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.
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();
}
}
Now back to the
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.
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());
}
...
}
}