Portal HOWTO: Develop a module

Sunday, May 03, 2009 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 database
  • IDE
  • Know-how in Spring Framework, JPA, Wicket and Maven 2

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

The module which is always required is the Devproof Core. It contains all necessary core modules like user management, tag support and other common stuff. One module represents one Maven artifact. 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.

Currently there is no Maven archetype, but you can check out the project from the SVN repository and take the blog module as your module template. Check out the projects with the following command:

svn checkout http://devproof.googlecode.com/svn/trunk/ devproof


After checking out you will get the folders:

module-article -> article module
module-blog -> blog module
module-bookmark - > bookmark module
... and more modules

portal-build -> builds all the modules
portal-core -> devproof core
portal-webapp -> bundles all modules to a WAR file


Build the projects with:

cd portal-build
mvn clean install -Pall


You can also build with the profiles blog, download and so on (see Getting started). After building the application all defined modules are packaged as JAR files. The portal-webapp/target/ folder contains the whole packaged WAR file. The
portal-webapp/target/sql/ contains the concatinated SQL files. Each built module contains its own SQL files in the JAR file. While bundling all SQL files (of the selected modules) will be extracted, concatinated and saved in this folder.

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
| | \---dao (DAOs, only interfaces!)
| | \---entity (JPA entities)
| | \---page (wicket pages)
| | \---panel (wicket panels)
| | \---query (query objects, e.g. for the dataprovider)
| | \---service (business logic)
| | devproof-module.xml (configuration of the module)
| |
| +---resources
| | \---sql (SQL files)
| | \---com
| | \---mycompany
| | \---mymodule
| | \---page (HTML, CSS and Properties files)
| | \---panel (HTML, CSS and Properties files)
| |
| \---webapp
| \---WEB-INF
| web.xml (only 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
Entity BlogEntity A JPA/hibernate persistence class
Query BlogQuery Query objects for the dataprovider
Dao BlogDao Data access object interface
Service BlogService Business logic interface
ServiceImpl BlogServiceImpl Business logic implementation
Constants BlogConstants Classes with constants


Configuration
 

The whole configuration is located in the devproof-module.xml. This is only a normal spring context. Here is an example:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <!-- Module configuration -->
    <bean class="org.devproof.portal.core.config.ModuleConfiguration">
    	<property name="name" value="Bookmark"/>
    	<property name="moduleVersion" value="${devproof.version}"/>
    	<property name="portalVersion" value="${devproof.version}"/>
    	<property name="author" value="devproof - Carsten Hufe"/>
    	<property name="url" value="http://devproof.org"/>	 		
    	
    	<!-- Hibernate/JPA entities -->
    	<property name="entities">
    		<list>
			    <value>org.devproof.portal.module.bookmark.entity.BookmarkEntity</value>
			    <value>org.devproof.portal.module.bookmark.entity.BookmarkTagEntity</value>
			</list>
    	</property>
    	<property name="pages">
    		<list>
    			<bean class="org.devproof.portal.core.config.PageConfiguration">
    				<property name="mountPath" value="/bookmarks"/>
    				<property name="pageClass" value="org.devproof.portal.module.bookmark.page.BookmarkPage"/>
    				<property name="registerMainNavigationLink" value="true"/>
    			</bean>
    			<bean class="org.devproof.portal.core.config.PageConfiguration">
    				<property name="mountPath" value="/bookmark"/>
    				<property name="pageClass" value="org.devproof.portal.module.bookmark.page.BookmarkRedirectPage"/>
    				<property name="indexMountedPath" value="true"/>
    			</bean>
    		</list>
    	</property>
    	<!--  Register boxes -->
    	<property name="boxes">
    	   	<list>
    			<bean class="org.devproof.portal.core.config.BoxConfiguration">
    				<property name="boxClass" value="org.devproof.portal.module.bookmark.panel.BookmarkBoxPanel"/>
    				<property name="name" value="Latest Bookmark Box"/>
    			</bean>
    		</list>
    	</property>
    </bean>

	<!--  Dao  -->
	<!--  Services  -->
	<!--  DataProvider  -->
</beans>


The module locator of the Devproof Portal searches for devproof-module.xml files and loads it automatically into the spring context. Then it is looking for all ModuleConfiguration classes. This class must be defined in every module. I think the most bean properties are self-explanatory. Here is a screenshot which describes some properties of the PageConfiguration class:

Module

Application layers


The graphic shows the layer in a module:

Layer

Data Access Objects should only consumed by a Service or DataProvider. Normally there is no implementation of the DAO because you should use the provided Generic DAO. The service contains the whole business logic, which should heavy unit tested. A service can consume other services. A data provider provides standardised access to data for wicket listing components. Typically there is no need for an implementation because there is a Generic Data Provider available (see Useful stuff). Wicket pages and components can consume services and data provider. Wicket components are children of Wicket pages. This is a very simple, but powerful layer architecture. It is very easy to mock dependencies in a service and write unit tests for it. You can also write tests for Wicket pages. Furthermore there is no implementation of the DAO, so that you do not have the risk of business logic in the DAO. Finally the architecture is easy to understand for new developers.

Useful stuff
 

There are two very cool things coming with the Devproof Portal. The first is the Generic DAO and the second is the Generic Data Provider.

Generic DAO

As you have seen in the convention there is no DAO implementation convention, because there is no DAO implementation. Here is an example of a Generic DAO:

public interface DownloadDao extends GenericDao<DownloadEntity, Integer> {
	@Query(value = "select distinct(d) from DownloadEntity d join d.allRights ar"
			+ " where ar in (select rt from RoleEntity r join r.rights rt where r = ? and rt.right like 'download.view%') order by d.modifiedAt desc", limitClause = true)
	public List<DownloadEntity> findAllDownloadsForRoleOrderedByDateDesc(RoleEntity role, Integer firstResult, Integer maxResult);
	@BulkUpdate("update DownloadEntity d set d.hits = (d.hits + 1) where d = ?")
	public void incrementHits(DownloadEntity download);
	@BulkUpdate("update DownloadEntity d set d.numberOfVotes = (d.numberOfVotes + 1), d.sumOfRating = (d.sumOfRating + ?) where d = ?")
	public void rateDownload(Integer rating, DownloadEntity download);
	@BulkUpdate("update DownloadEntity d set d.broken = true where d = ?")
	public void markBrokenDownload(DownloadEntity download);
	@BulkUpdate("update DownloadEntity d set d.broken = false where d = ?")
	public void markValidDownload(DownloadEntity download);
}


The GenericDao 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.

Defining the Generic DAO in the spring context:

<bean id="downloadDao" parent="baseGenericDao">
	<property name="daoInterface" value="org.devproof.portal.module.download.dao.DownloadDao" />
	<property name="entityClass" value="org.devproof.portal.module.download.entity.DownloadEntity" />
</bean>	


DAOs should only consumed by services or data providers. It should never consumed by wicket pages or components!

Generic Data Provider

Generic Data Providers are useful whenever you display a wicket list component, e.g. the Wicket DataView component.

<bean id="bookmarkDataProvider" parent="persistenceDataProvider" scope="prototype">
	<property name="entityClass" value="org.devproof.portal.module.bookmark.entity.BookmarkEntity" />
	<property name="sort">
		<bean class="org.apache.wicket.extensions.markup.html.repeater.util.SortParam">
			<constructor-arg value="title"/>
			<constructor-arg value="true"/>
		</bean>		
	</property>
</bean>


This is a simple definition for the spring context and it provides paging and sorting for this BookmarkEntity. Additionally it optimizes the queries against the database.

How to run


Generate Eclipse meta files with Maven:

mvn eclipse:eclipse


You can execute the command in the specific module or portal-build 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 Maven. Only a restart is required if the hot code replacement fails.

If you have helpful suggestions or corrections, please contact me.