< Magazine />


Portal HOWTO: Develop a module

Sunday, February 20, 2011 by Carsten Hufe   Tags:  portal   devproof   howto   module 

This article describes how to develop a module. Requirements for developing a module are:

  • Java 1.6
  • MySQL or Oracle database
  • IDE (Eclipse or IntelliJ)
  • Know-how in Spring Framework, JPA and Wicket

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.

Overview

Architecture
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)

Module structure

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)

Naming conventions

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

Configure a module

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.

 

Module

Application layers

The graphic shows the layer in a module:

Layer

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.

Useful stuff

There are two very helpful things coming with the Devproof Portal. The first is the GenericRepository and the second is the GenericDataProvider.

Generic Repository

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 Provider

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
}

How to run

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:

 

Eclipse RunConfig

 

Enter program arguments:

 

Eclipse RunConfig

 

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.

 

© 2009-2011 - www.devproof.org