Skip to content

Pattern for Application Services creation and integration

Matteo E. Minnai edited this page Mar 21, 2013 · 8 revisions

Pattern for application services creation and integration

How to add application services to the Entando platform

Table of Contents

Scope of the guide

The aim of this guide is to give a detailed description of the architectural model of Entando 3.2 and the steps to follow to create a new application service.

Intended audience

This guide is for developers aiming to build a new application Service for the Entando platform.

Prerequisites

In order to take the maximum advantage of the present guide, it is necessary to have a basic knowledge about: the Java platform, the Apache Tomcat servlet container.

You are also required to fully understand what Struts² and Tiles framework are because they are extensively used within the system: in this respect we won't analyze the code in detail; this makes the present guide more of a step-by-step recipe to cook new services.

top

Introduction

We define the Entando Manager as a part of the Entando Core which implements a basic system funtionality. A Manger is also the main handler of that particular functionality.

Services are divided in two groups, basic services and CMS services; please note that CMS services are served by the jACMS plugin which is provided in bundle with Entando itself.

The basic services comprehend:

  • AuthenticationProviderManager: authenticator service..
  • BaseConfigManager: configuration service. Load the configuration parameters from system database, making it available to the invoker.
  • CacheManager: cache handler service.
  • CategoryManager: category handler service.
  • ControllerManager: this service supervises the execution of a request coming from the client.
  • GroupManager: group handler.
  • I18nManager: this service returns the localized strings upon request
  • KeyGeneratorManager: this service superintends the generation of primary keys
  • LangManager: this service handles the various languages of the system
  • NotifyManager: event notification dispatcher service
  • PageManager: page handler
  • PageModelManager: this service handles the various page models
  • RoleManager: role manager
  • ShowletTypeManager: this service manages the Widgets (ShowletTypes) types defined in the system
  • UrlManager: this manager creates a URL to page of the Portal from essential information
  • UserManager: account manager
  • ShortcutManager: this manager handles the shortcuts of the main page of the administration view
  • APICatalogManager: Catalogue of the resources that can be served through REST API.
  • OAuthConsumerManager: handles system API consumers
  • ContentManager: contents manager
  • ContentPageMapperManager: this service manages the map of the contents published in the pages of the portal
  • LinkResolverManager: this manager resolves the symbolic links
  • ResourceManager: resources (audio, video, images etc.) handler
  • SearcheEngineManager: this service creates the indexes of all the objects which will be later parsed by the search engine.

The services defined in the system are initialized during system start-up through the Factory provided by the Spring Framework.

It's worth noting that each service has one and only one instance. The invocation of a service can be obtained in either two ways: through the "Dependency Injection" technique favored by Spring or using the appropriate elements of the system like ApsWebApplicationUtils.

Every Entando manager is described through a specific interface and every object class accesses a service always using the appropriate interface and never invoking the class directly.

The manager is the only linking point between the system data -whatever their origin is- and the functionality which uses them.
An example of service is the PageManager which manages the tree of the portal pages. Every operation involving the pages, such as addition, removal, displaying and so on is handled by the PageManager.

Architectural model

Before moving on is important to describe the tree layers that compose architectural model of Entando:

  • Data Access Layer: It is composed by all the elements which superintend the Persistence Layer. The main component are the DAO classes (Data Access Object) which are the only linking element between the platform and the data sources (Database, Filesystem, LDAP service directory etc.)

  • Business Layer: This is the core of the system. Here the concept of Entando service as manager of every macro functionality, takes place. This layer is built upon the Spring Framework, whose listener, during the system start-up, initializes all the services and injects them in the web application context as beans. The Business Layer utilizes the Data Access layer to get the data needed, gives to the higher layer (the Presentation layer) the elements to display and supports it in the execution of actions.

  • Presentation Layer: the aim of this layer is to build the graphic interfaces which represent the mean through which the users interact with the system. This layer gives a pure View layer (that is, a JSP without any business logic) and a "slim" controller (which checks the consistency of the data submitted and serves the data produced); both of them provide support to the layer below, the Business Layer. In the Entando platform this layer is divided in two parts:

  1. the Portal View, referred to as Front-end
  2. the Administration View referred to as Back-end
    These views, which differs by functionality and architecture, are completely independent from each other.

Every application service must be developed in the total respect of the architectural schema above, placing every part in the right layer. The presence of the elements of the new service in all of the three layers depends on characteristics of the service itself. The typical service which needs the usual addition, removal, editing and searching operations will have elements in each layer - take as reference the "Personal Card" management service explained further in this guide and found implemented in the Portal Example demo.
Moreover the "Personal Card" service has customized elements in the "View" layer, both in the Portal and the Administration area. The LDAP plugin, on the contrary, has elements in the Business layer only.

Portal View

This is the part of the presentation layer where the results of the queries to system services are displayed mainly through the Widgets. Widgets are the preferred method to use to make the system services interact with users. The tasks of the Portal View are to provide services based on the current user permissions (every element of the Portal Layer incorporates the rules which govern the access to services) and to serve content as fast as possible (using content caching mechanism).
The portal view is handled by a specialized servlet (ControllerServlet) whose primary target is to invoke a precise succession of services (coherency of the URL, user privilege checks etc) which will finally result in the rendering of the requested page.

Administration View

This is the area reserved for administration of the various elements of the Portal (Pages, Contents, Resources etc.) whose access is reserved to a restricted pool of users.
The view of the Adminstration Area (comprehensive of the controller logic) has been completely redesigned: the reference framework, firmly tied to the Spring framework, is now Struts².
The View has been modified to met the (Italian) Public Administration requisites of accessibility - taking as a firm point the respect of all the W3C standards.

Distribution of the components of the system layers

The source files and certain supporting elements (eg. the static resources and the templates directories etc.), are enclosed in two packages:

  • com.agiletec.aps : here can be found all the elements of the Data Access Layer, Business and presentation Layer (the last limited to the Portal View only)
  • com.agiletec.apsadmin : this package contains all the elements need to manage the presentation layer of the Administration View

A similar division exists in the directory WEB-INF of the web application: here are contained, including the supporting folders, the usual aps (which contains all the JSP and the TLD files of the Portal View layer) and apsadmin (containing all the JSP files belonging to the Administration Area).

top

How to create an Entando service

The following paragraphs explains in detail how to create a new service in the Entando platform.

The main objective of the present guide is to allow the Entando-Developers a fast development of new services to integrate with existing ones, without modifying the Base Core sources (java classes, JSP files, configurations, etc).

For the purpose of this guide we use the Personal Card Service (or simply Card) as reference: as found in the Portal Example project in our GitHub page.
Readers are strongly advised to consult the implemented code as you proceed reading on, to have a look to the code in its entirety.

Business Layer and Data Layer

During the process of the creation of a new service, the following procedure starts from the implementation of the Business and Data Layer of the new functionality.

Active elements: the classes involved are <NAME_OF_THE_HANDLED_OBJECT>Manager (the name of the service) which must extend the AbstractService class. In a similar manner, if the DAO classes are needed, the <NAME_OF_THE_HANDLED_OBJECT>DAO must extend the AbstractDao class.

Implementation

Create the package, external to the core classes, respecting the same schema used by the core.

For instance, if the new service is called Card, the resulting name will be org.entando.entando.<PROJECT_NAME>.aps.system.services.card.CardManager.

Create an interface, namely known as service signature, which respects the following syntax I<NAME_OF_THE_HANDLED_OBJECT>Manager.

A proper name for the manager interface name is ICardManager

This interface includes all the public methods (and the constants, if present) of the service which will be accessible from the outside. Every use of the implemented methods must happen through the invocation of this interface.

Create the class of the service <NAME_OF_THE_HANDLED_OBJECT>Manager which extends in turn the AbstractService and implements the methods declared in the interface declared above.

An example of Card's manager class is the following:

   public class CardManager extends AbstractService implements ICardManager {
      ....
   }

Take care to implement the init method of the abstract service (which allows the correct initialization of the service), and the methods declared in the interface properly.

Example of a possible init method

  /**
   * Service initialization
   */
   public void init() throws Exception {
        .....
   }

Add, in the class (or interface) <PROJECT_NAME>SystemConstants contained in a sub-package aps.system of the project, the constant <NAME_OF_THE_HANDLED_OBJECT>_MANAGER which uniquely identifies the name of the service within the project

Example of the name definition of the manager through a constant

  public interface MyProjectSystemConstants {
  
      public static final String CARD_MANAGER = "CardManager";
      .......
  }

Now we have to add the service in a new configuration file, which will be later parsed by Spring.
The configuration files must be inserted in a directory under /main/resources/spring/<PROJECT_NAME>/aps/managers/ following the same pattern used for the configuration files of the core.

The new manager must be inserted in the Spring context using a syntax similar to the one shown below:

  <bean id="CardManager"
    class="org.entando.entando.<PROJECT_NAME>.aps.system.services.card.CardManager"
    parent="abstractService" >
  </bean>

Please note the bean id “CardManager”: this is the constant value defined previously.

Important! Care must be taken in the definition of the bean since it must notmatch any other existing ID in the system, unless we intend to extend an existing service on purpose.

At this point we have to make the system aware of the new service by instructing Spring to load every XML file in the configuration directories of your service.
This is typically done editing the file /main/webapp/WEB-INF/web.xml and adding to the attribute param-value of the parameter contextConfigLocation the following file pattern string
classpath*:spring/<PROJECT_NAME>/aps/**/**.xml
This definition must be added in the last position.

The same pattern must be inserted in method getSpringConfigFilePaths of the class in /test/java/org/entando/entando/<PROJECT_NAME>/<PROJECT_NAME>ConfigUtils.
This class is used to setup the proper environment for the test suites; again, the definition must be placed in the last position.

If the new service uses a DAO (Data Access Object) so that it adds new elements in the Data Layer, the first thing to do is to crate an interface, called DAO Signature, using this pattern I<NAME_OF_THE_HANDLED_OBJECT>DAO.

Add, in the class which implements that interface, an instance variable of the same type of the newly created one. This variable must have both getter and setter, both rigorously public like the following example

  public void setCardDao(ICardDAO cardDao);
  protected ICardDAO getCardDao();

Create the DAO class <NAME_OF_THE_HANDLED_OBJECT>DAO which implements the interface just created. If we are willing to use a database, the DAO just created must extend the AbstractDAO class.

Example of a possible DAO Class

  public class CardDAO extends AbstractDAO implements ICardDAO {
    ....
  }

Inject the new DAO in the bean of the service previously described.

Example of a complete bean declaration

  <bean id="CardManager" class="org.entando.entando.projectname.aps.system.services.card.CardManager"  parent="abstractService" >
    <property name="cardDAO" >
      <bean  class="org.entando.entando.projectname.aps.system.services.card.cardDAO">
        <property name="dataSource" ref="servDataSource" />
      </bean>
    </property>
  </bean>

NOTE: inject the datasource having care to choose the proper reference between the default portDataSource or servDataSource (which always exist in a Entando installation) and the new data sources eventually created for the new service.

jUnit tests for the Data Layer and Business Layer

Every service in the DAO must be tested in its public methods. In other words it's necessary to:

  • create a java class named <PROJECT_NAME>ConfigUtils (in the package org.entando.entando.<PROJECT_NAME>) which extends the class ConfigUtils.
    The methods getSpringConfigFilePaths must be overridden as well; you might also want to override the method closeDataSources too.
    The former provides the path for the configuration files of the new service needed by Spring, the latter handles the database connection closure of the new datasources.

  • create a java class <PROJECT_NAME>BaseTestCase (in the package org.entando.entando.<PROJECT_NAME>.aps) which extends the class com.agiletec.aps.BaseTestCase.
    Override the method getConfigUtils so that it returns an instance of <PROJECT_NAME>ConfigUtils (that is, the class previously created).

  • Create the test classes Test<NAME_OF_THE_HANDLED_OBJECT>Manager and, if needed, the Test<NAME_OF_THE_HANDLED_OBJECT>DAO in the package org.entando.entando.<PROJECT_NAME>.aps.system.services.<NAME_OF_THE_HANDLED_OBJECT>.
    Such classes must extend the classes previously created; remember to test all the public methods of the new service. Under normal circumstances you don't have to make specific tests for the DAO methods: DAO problems will eventually emerge testing against the manager itself.

To check a service we have to invoke it in every test class; this is done with a code similar to the following:

    ImyServiceManager myServiceManager =  (ImyServiceManager)  this.getService(MyProjectSystemConstants.MY_SERVICE_MANAGER);

In order to test a DAO you have to explicitly create it in the same way Spring does, declaring the datasource as well as the beans required as dependencies.
That is, you have to code something similar to the following code in your test class:

    DataSource dataSource = (DataSource)  this.getApplicationContext().getBean("dataSourceName");
    ...
    MyServiceDAO myServiceDao = new MyServiceDAO();
    myServiceDao.setDataSource(dataSource);

Two databases, namely <PROJECT_NAME>testPort and <PROJECT_NAME>testServ, are provided for testing purposes. They reflect their "production" counterparts, the <PROJECT_NAME>Port and <PROJECT_NAME>Serv.
If the new service requires additional databases they all must have a test and a production version as well.

    public void testNameOfMethodToTest() {
      ........
    }

When creating test methods it's important to plan the restore of the data in the state they were prior the execution of the tests, whatever the result is.
This assures the coherence and the correctness of the following tests: you don't want a failed test to cause a succession of failures in different classes whose tests previously where successful!

How to extend existing Entando Managers

If the newly born service alters existing managers (by either integrating or modifying functionalities) you are strongly advised to avoid modifying the core!
Create inside the package it.<PROJECT_NAME>.aps.system.services of your project, a new manager which extends the existing one. In the Spring configuration file of your service the ID of the service must perfectly match the name of the core service that we are going to extend.

Example of a possible extension of the core UserManager

  <bean id="UserManager" class="it.projectname.aps.system.services.user.UserManager" 
      parent="abstractService" >
    <property name="userDAO" >
    	<bean class="it.projectname.aps.system.services.user.UserDAO">
  			<property name="dataSource" ref="servDataSource" />
  			<property name="encrypter">
  				<bean class="com.agiletec.aps.util.DefaultApsEncrypter"></bean>
  			</property>
  		</bean>
  	</property>
  	<property name="configManager" ref="BaseConfigManager"/>
  </bean>

In example above, the new UserManager bean substitutes, having the same name, the one of the Entando core. Remember to insert all the properties found in the declaration of the core bean in the new one.

Presentation Layer - Administration Area

The most of the Application services will need an administration interface.
Within the Entando core, the class which superintends the interface mechanism are all grouped inside the package com.agiletec.aspadmin.
This package in turn contains other sub packages organized (and separated) by functionality; each serves a well determined function whose controls are displayed in the Administration Area.
The new service must present the sources to manage the back-end interfaces developed following the same structure used in the core.

Implementation

Create the package -outside the Core path!- respecting the schema used in Entando, as stated earlier.

Suppose to have the need to create Actions to handle the Cards (defined in the homonym class): the resulting name of the package will be: it.<PROJECT_NAME>.apsadmin.card.

Crate the java interface, also called Action Signature, which respects this pattern:

I<NAME_OF_THE_HANDLED_OBJECT>Action.

An example of possible name for the Action class would be IcardAction.

This interface presents all the public methods and eventually the constants, which will be implemented in the service class. Every method presented in the interface is an action which can be executed.
If our service provides some search function of the object handled by the service we have to create an additional java interface, namely I<NAME_OF_THE_HANDLED_OBJECT>FinderAction.
This is the gate to the finder action class.

Create the action class named <NAME_OF_THE_HANDLED_OBJECT>Action which extends the BaseAction and implements the interface above. If needed, create the finder action class which manages the search operations.

Any action class must have a corresponding Spring configuration file; the syntax to use is close to the one shown in the example below:

  <bean id="cardAction" scope=”prototype”  
    class="org.entando.entando.projectname.apsadmin.card.CardAction"  parent="abstractBaseAction" >
  </bean>

Important: the scope of the bean of the action classes must be "prototype" and care must be taken when defining the bean: it must not match any other bean id of the core, unless we are extending an existing service, as we have already seen.

Insert the configuration in a XML file located in main/resources/spring/<PROJECT_NAME>
The standard name for this file is usually apsadmin.conf.

Once again, make Spring aware of the new action by adding the following string classpath*:spring/<PROJECT_NAME>/apsadmin/**/**.xml in the attribute param-value of the parameter contextConfigLocation located in the file /WEB-INF/web.xml.
This definition must be placed in the last position.

Create, at the same level of the interfaces and classes, another XML file which contains the definitions of the actions previously implemented. These definitions follow the Struts² rules: there is one definition for every action which can be triggered by users from the user interface.

<struts>
  <package name="projectname_do/Card" namespace="/do/Card"
		extends="entando-default">
    ....
  	<action name="list" class="cardFinderAction">
			<result type="tiles">admin.Card.list</result>
			<interceptor-ref name="entandoDefaultStack">
				<param name="requestAuth.requiredPermission">superuser</param>
			</interceptor-ref>
		</action>

		<action name="search" class="cardFinderAction">
			<result type="tiles">admin.Card.list</result>
			<interceptor-ref name="entandoDefaultStack">
				<param name="requestAuth.requiredPermission">superuser</param>
			</interceptor-ref>
		</action>

		<action name="new" class="cardAction" method="newCard">
			<result type="tiles">admin.Card.entry</result>
			<interceptor-ref name="entandoDefaultStack">
				<param name="requestAuth.requiredPermission">superuser</param>
			</interceptor-ref>
		</action>
		....
	</package>
</struts>
```
  
Note: the name of the Struts² package must present as prefix the name of
the project.  
Furthermore, we can see how Entando enforces privilege checks on backend
interface using the various stacks:

  
-   `entandoDefaultStack`: this is the default for the actions of the Administration view which need specific permissions to be executed (eg. check for the user permission when accessing the Administration area). This stack does not enforce validation or range check of the submitted parameters. This stack requires the explicit declaration of the permission needed to execute the action in the requiredPermission tag.

-   `entandoValidationStack`: extension of the entandoDefaultStack with the addition of validation checks.

-   `entandoFreeStack`: this stack is to be used for actions both internal and external to the Administration area, which require neither permission nor validation checks.

-   `entandoFreeValidationStack`: extension of the entandoFreeValidationStack stack with validation check enabled.

  

Create, at the same level of the java interface and action classes, the
appropriate XML files to define the kind and the number of validation
checks to perform. These validation files follow the Struts2 syntax for
the validation.

Create a new `<PROJECT_NAME>-struts.xml` in the directory
`main/resource`. This file must contain all the references to the
configuration files of the new actions of the project and is similar to
the following example:

```xml
    <struts> <include file="org/entando/entando/projectname/apsadmin/card/card.xml"/></struts>
```


The xml file containing the definitions of the various actions of the project must be declared in the parameter `Struts2Config` within the `main/webapp/WEB-INF/web.xml` file.  
As always the definition must be inserted in the last position.

### <a id="local"></a>  Internationalization and localization

The property files reside in the same directory of the newly created Action classes and provide support for the Internationalization (i18n).  
The Properties file must follow strictly the rules as specified in documentation released in the Struts2 framework website.

In the properties files must be inserted not only the static labels of the JSP files of the user interfaces, but all the labels correlated to the validation support. These labels must be provided for all the languages supported: e.g. for English and Italian languages you must provide the files `package_it.properties` and `package_en.properties`.

As for the ID of the service beans, the keys of the labels must not match any of the common resources keys contained in the files `global-messages_en.properties` and `global-messages_it.properties`.

To avoid problems, you are encouraged to subdivide the labels in the following groups:

-   on menu basis

-   per titles (h1 e h2)

-   static strings of the JSP files

-   string used by the validation files

### <a id="test"></a> Testing Actions with jUnit


To create the proper environment and the classes to test new newly
created actions you have to:  
  

-   create a java class named `<PROJECT_NAME>ConfigUtils` (or use the class used to test the manager methods) which extends the class `ConfigUtils`, in `.../src/test/java/org/entando/entando/<``PROJECT_NAME>`.  
The methods `getSpringConfigFilePaths` and, optionally, `closeDataSources` must be extended as well.  
The former provide the path for the configuration files of the new service needed by Spring, the latter handles the database connection closure of the new datasources.

-   create a java class `<PROJECT_NAME>ApsAdminTestCase` in `.../src/test/java/org/entando/entando/<PROJECT_NAME>/apsadmin` which extends the class `com.agiletec.apsadmin.ApsAdminBaseTestCase`, then override the method `getConfigUtils` (so that it returns an instance of the class previously created) and the `setInitParameters` (so to load the definitions stored in the various Struts XML files needed to test your actions.)

-   create the Action classes named `Test<NAME_OF_THE_HANDLED_OBJECT>Action` which extends the class of the previous step.  
Here you will place and organize your unit test having care to always restore the database(if any) at the end of each test.

### <a id="jsp"></a> Creation of the JSP for the Administration area

All the JSP files composing the user interfaces are located in the
directory  `main/webapp/WEB-INF/<PROJECT_NAME>/apsadmin/jsp/`.

#### <a id="ex_card"></a> Example of the Card service JSP

-   `cardFinder.jsp`: this is the interface for the search card service; the search itself is handled by the Action class `CardFinderAction`.

-   `entryCard.jsp` generates the Card add/remove interface, handled by the Action class `CardAction`.

The (JSP) interfaces must be declared inside the template called
`main.layout` in the file `main/webapp/WEB-INF/apsadmin/tiles.xml` that
specifies the configuration of the pages being invoked as a result of
the action. Such configuration must obey the rules of Tiles2, a Struts2
plugin.

Define a new Tiles configuration file for the pages,
`<PROJECT_NAME>-tiles.xml` inside the folder
`main/webapp/WEB-INF/<PROJECT_NAME>/apsadmin/portalexample-tiles.xml`.  
The pages must extend the `main.layout` and the single ID represent the
result (in the form of tiles type) of every action.  
The tiles configuration must be declared within the parameter
`org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG` of the
descriptor file `web.xml` of the web application.  
Once again, it must be placed in the last position.


### <a id="new"></a> Creation of a new voice in the Administration Area


To add a new element in the Plugin menu create in the directory
`main/webapp/WEB-INF/<PROJECT_NAME>/apsadmin/jsp/common/template/` a
file named `subMenu.jsp` which contains the new menu item referring to
the new application service (a plugin, in this case) Then create a new
bean (a Spring Object) with id `<SERVICE_NAME>SubMenu` that refers to
the class `PluginSubMenuContainer`; this class has a property called
`submenuFilePath` whose value is the path of the `subMenu.jsp` just
created.

A possible declaration of the menu is the following:

```xml
  <bean id="cardPluginSubMenu"
		class="com.agiletec.apsadmin.system.plugin.PluginSubMenuContainer">
		<property name="subMenuFilePath">
			<value>/WEB-INF/projectname/apsadmin/jsp/common/template/subMenu.jsp
			</value>
		</property>
	</bean>
```
  
Following carefully these steps the new menu item will be included in
the `Plugin` menu in the administration area.

### <a id="modify"></a> Modify the existing administration area interfaces

If the Application service is going to modify exinsting interfaces for
any reason (eg. integration of new modules, link or whatever) you are
advised to *avoid any modification of the Core interfaces*. Create
instead in the tiles configuration file `<PROJECT_NAME>-tiles.xml` a new
definition with the same name of the core interface to override. So the
element to modify is simply rewritten from scratch.

An example of a possible redefinition of the "Pages Tree" interface
would be to copy the following definition in the files
`<PROJECT_NAME>-tiles.xml`:

```xml
<definition extends="main.layout" name="admin.Page.viewTree">
  <put-attribute name="title" value="title.pageManagement" />
	<put-attribute name="extraResources"
		value="/WEB-INF/apsadmin/jsp/common/template/extraresources/pageTree.jsp" />
	<put-attribute name="body"
		value="/WEB-INF/projectname/apsadmin/jsp/page/pageTree.jsp" />
</definition>
```


where the `admin.Page.viewTree` is the ID of the interface of the page
tree handler.

The path of the JSP must be the same of the JSP files of the interface
to extend *with the solely exclusion* of the root directory of your
project.


Whenever it's possible please follow these guidelines: if the new
service adds some new functionality extending the existing ones, a good
practice is to use the submenu technique showed for the plugins so to
create the entry point for the new service.

[top](#top)

<!--
>RIMOSSO: riferimenti alla versione di Entando
-->
Clone this wiki locally