Sunday, February 20, 2011 by Carsten Hufe
This article describes how to develop a module. Requirements for developing a module are:
The Devproof module development comes only with few proprietary interfaces. The core technologies are the Spring Framework, Hibernate (with JPA annotations) and Apache Wicket. You mainly develop with these frameworks.

Devproof core is the module which is always required. It contains all necessary core modules like user management, tag support and other common stuff. One module represents one subproject. A module can have dependencies to other modules, e.g. the download and bookmark module require the deadlink module. It is possible to run Devproof Core standalone without any modules, so you have a blank portal framework.
You can clone it from Github and check out from SVN and take the blog module as your module template.
# Git users do git clone git://github.com/devproof/portal.git devproof # SVN users do svn checkout https://svn.github.com/devproof/portal/ devproof
After checking out you get the following folders:
module-article -> article module module-blog -> blog module module-bookmark - > bookmark module ... and more modules portal-bundle -> bundles the project (packaging with Tomcat, SQL generation etc.) portal-core -> devproof core portal-webapp -> bundles all modules to a WAR file
Build the projects with:
cd devproof # for linux and cygwin user ./gradlew build dist # for windows user gradle.bat build dist # for gradle native users ;-) gradle build dist
After building the application, all defined modules are packaged as JAR files. Each built module contains its own SQL files in the JAR file. While bundling all SQL files get extracted, concatinated and saved in this folder. You find the built and bundled stuff under:
portal-bundle/build/dist/devproof-portal-<version>-war/war/devproof-portal-<version>.war portal-bundle/build/dist/devproof-portal-<version>-war/sql/ (concatinated SQL files)
If you browse in the existing modules, you will see that a module normally follows this structure:
.\mymodule | pom.xml | \---src +---main | +---java | | +---com | | +---mycompany | | +---mymodule | | \---repository(DAOs and repositories) | | \---entity (JPA entities) | | \---page (wicket pages + HTML) | | \---panel (wicket panels + HTML) | | \---query (query objects, e.g. for the dataprovider) | | \---service (business logic) | | devproof-module.xml (configuration of the module) | | | +---resources | | \---sql (SQL files) | | +---META-INF | | \---devproof.module (contains the paths to the spring contexts) | | | \---webapp | \---WEB-INF | web.xml (just for testing and developing) +---test ... (junit tests)
The naming conventions are quite simple. There are only class naming conventions. The classes should end with these suffixes:
Suffix |
Sample |
Description |
| Page | BlogPage | Wicket page |
| Panel | SearchBoxPanel | Wicket panels |
| - | Blog | A JPA/hibernate persistence class |
| Query | BlogQuery | Query objects for the dataprovider |
| Repository | BlogRepository | Repositories/Data access object interface |
| Service | BlogService | Business logic interface |
| ServiceImpl | BlogServiceImpl | Business logic implementation |
| Constants | BlogConstants | Classes with constant |
At first you have to create a file named devproof.module and copy it to the src/main/resources/META-INF/. The file contains the the path to your spring context which should be loaded. Here is an example:
spring.context.file=classpath:/org/devproof/portal/module/blog/devproof-module.xml
On startup the portal locates all files named devproof.module and builds the spring application context with it. You can define what you like in the spring context. One example could the definition of a module:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:devproof="http://www.devproof.org/schema/devproof/portal"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.devproof.org/schema/devproof/portal
http://www.devproof.org/schema/devproof/portal-1.1.xsd">
<devproof:module-scan
base-package="org.devproof.portal.module.blog"
module-name="Blog"
author="devproof - Carsten Hufe" author-url="http://devproof.org"
module-version="1.1.0" portal-version="1.1.0"/>
<!-- More spring stuff here -->
</beans>
The element <devproof:module-scan/> does the same like spring's <context:component-scan/>. Additionally it scans the following annotations:
@GenericRepository // define a generic repository or DAO @RegisterGenericDataProvider // annotation for entities, to provide a generic Wicket dataprovider @Secured // provide security for Wicket components @NavigationBox // register a wicket panel as navigation box @ModulePage // mount a page and register as module page @Entity // JPA or hibernate annotation, get registered automatically in SessionFactory
Furthermore <devproof:module-scan/> registers your module with a name and some meta information like author and versions. The annotation @ModulePage cannot only mount pages, it registers also links. Above you can see what kinds of links can be registered with @ModulePage.

The graphic shows the layer in a module:

Data Access Objects/Repositories should only consumed by a Service or DataProvider. Normally there is no implementation of the repository, because you should use the provided GenericRepository. The service contains the whole business logic, which should heavy unit tested. A service can consume other services. A data provider provides standardized access to data for wicket listing components. Typically there is no need for an implementation, because there is a GenericDataProvider available (see "Useful stuff"). Wicket pages and components can consume services and data provider. Wicket components are children of Wicket pages. It is very easy to mock dependencies in a service and write unit tests for it. You can also write tests for Wicket pages. At least there is no implementation of the repository, so that you do not have the risk to find business logic in the repository. Finally the architecture is easy to understand for new developers, because it is common.
There are two very helpful things coming with the Devproof Portal. The first is the GenericRepository and the second is the GenericDataProvider.
As you have seen in the convention there is no repository implementation convention, because there is no repository implementation. Here is an example of the GenericRepository:
// name of spring bean
@GenericRepository("downloadRepository")
// enables caching
@CacheQuery(region = "download.query.cache")
public interface DownloadRepository extends CrudRepository<Download, Integer> {
@Query("Select d from Download d")
List<Download> findAll();
// disables caching for this query ...
@CacheQuery(enabled = false)
@Query("select d.allRights from Download d where d.modifiedAt = (select max(modifiedAt) from Download)")
List<Right> findLastSelectedRights();
@BulkUpdate("update Download d set d.hits = (d.hits + 1) where d = ?")
void incrementHits(Download download);
@BulkUpdate("update Download d set d.numberOfVotes = (d.numberOfVotes + 1), d.sumOfRating = (d.sumOfRating + ?) where d = ?")
}
The CrudRepository interface provides the default CRUD methods. If this does not suffice you can define own HQL queries. If you have parameters you can use the question mark '?' placeholder in the query. The first question mark represents the first method parameter, the second represents the second method parameter and so on. There is a little exception when limitClause is true in the Query annotation the last two method parameters are for the query limitations. The return type of the defined method must be the same what hibernate returns. With the BulkUpdate annotation you can define efficient update queries. Generic repositories get scanned with the <devproof:module-scan/> element. Generic repositories must consumed by services or data providers, because you have to open the transaction at the service border with springs @Transactional annotation. It should never consumed by wicket pages or components!
Generic data providers are useful whenever you display a list in Wicket, e.g. the DataView component. The generic dataprovider implements Wicket's ISortableDataProvider. Here is an example how you can define one on a JPA entity:
@Entity
@Table(name = "my_table")
@RegisterGenericDataProvider(value = "myDataProvider", sortProperty = "createdAt", sortAscending = false, queryClass = MyQuery.class)
public class MyEntity {
@Id
private String id;
private Date createdAt;
// more fields
// setters and getters
}
Generate Eclipse/IntelliJ project files with Gradle:
# Generate Eclipse Project files ./gradlew eclipse # Generate IntelliJ IDEA project files ./gradlew idea
You can execute the command in the specific module or root folder. After importing you can create a new Eclipse run configuration:

Enter program arguments:

Finally press Run.
In this case you will run the Bookmark module. After making changes at the module you have not to rebuild the module with Gradle. Only a restart is required if the hot code replacement fails. If you have helpful suggestions or corrections, please contact me.