-
Notifications
You must be signed in to change notification settings - Fork 3
Pattern for Application Services creation and integration
How to add application services to the Entando platform
- Scope of the guide
- Introduction
-
How to create an Entando service
- Business Layer and Data Layer
- Implementation
- jUnit tests for the Data Layer and Business Layer
- How to extend existing Entando Managers
- Presentation Layer - Administration Area
- Implementation
- Internationalization and localization
- Testing Actions with jUnit
- Creation of the JSP for the Administration area
- Creation of a new voice in the Administration Area
- Modifying the existing administration area interfaces
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.
This guide is for developers aiming to build a new application Service for the Entando platform.
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.
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.
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:
- the Portal View, referred to as Front-end
- 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.
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.
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.
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).
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.
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.
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.
/**
* 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
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.
public class CardDAO extends AbstractDAO implements ICardDAO {
....
}
Inject the new DAO in the bean of the service previously described.
<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.
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 packageorg.entando.entando.<PROJECT_NAME>
) which extends the classConfigUtils
.
The methodsgetSpringConfigFilePaths
must be overridden as well; you might also want to override the methodcloseDataSources
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 packageorg.entando.entando.<PROJECT_NAME>.aps
) which extends the classcom.agiletec.aps.BaseTestCase
.
Override the methodgetConfigUtils
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, theTest<NAME_OF_THE_HANDLED_OBJECT>DAO
in the packageorg.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!
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.
<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.
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.
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
-->
All the material here contained is published under the GNU Free Documentation License v1.3
The Entando trademark and logo are registered trademarks of Entando, srl. All
Rights Reserved.
All other trademarks are the property of their respective owners.