0% found this document useful (0 votes)
97 views28 pages

Open EMMCode Design Guide

Uploaded by

JA
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
97 views28 pages

Open EMMCode Design Guide

Uploaded by

JA
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 28

EMM/OpenEMM Code Design Guide 2.16.

0_20170502

AGNITAS EMM/OpenEMM Code Design Guide

Table of Contents
1 Introduction....................................................................................................... 3
1.1 EMM and OpenEMM........................................................................................................ 3
1.2 The Purpose of this Guide............................................................................................... 3
1.3 Follow this Document, not the Existing (Legacy) Code...................................................3
1.4 Define Extension Prefix................................................................................................... 3
1.5 OpenEMM Contributor Agreement..................................................................................4
2 General Requirements........................................................................................4
2.1 Environment Pre-Settings............................................................................................... 4
2.2 Software Technology....................................................................................................... 4
2.3 SOLID Principles.............................................................................................................. 5
2.4 Code Modifications......................................................................................................... 5
2.5 Database Access............................................................................................................. 5
2.6 Code Layers and Frameworks.........................................................................................6
2.6.1 Spring.................................................................................................................. 6
2.6.2 Struts................................................................................................................... 7
2.6.3 Tiles...................................................................................................................... 8
2.7 GUI Messages................................................................................................................. 8
2.7.1 Messages Objects................................................................................................ 8
2.7.2 Messages in JSPs.................................................................................................. 9
2.7.3 messages.jsp and messages-transitional.jsp.....................................................10
3 Directory Structure...........................................................................................10
4 Code Components............................................................................................. 11
4.1 Java Packages and Class Files.......................................................................................11
4.2 Utility and Helper Classes............................................................................................. 11
4.3 JSP Files......................................................................................................................... 12
4.4 Javascript Files.............................................................................................................. 13
4.5 Configuration and Property Files...................................................................................13
4.5.1 Configuration Data in Database.........................................................................14
4.5.2 Configuration Values in class ConfigValue.........................................................14
4.5.3 Messages Properties.......................................................................................... 15
4.6 TLD Files........................................................................................................................ 15
4.7 Third-Party Libraries...................................................................................................... 15
4.8 Database Tables and Fields........................................................................................... 15
4.9 Navigation, Stylesheets, Icons and Images..................................................................17
4.10 Quartz Jobs................................................................................................................. 17
4.11 Other Files................................................................................................................... 17
5 Coding Rules.................................................................................................... 17
5.1 Recommended Suffixes for Class Names......................................................................17
5.2 Default Order of Content in Classes..............................................................................18
5.3 Type of Class................................................................................................................. 18
5.4 Visibility of Methods...................................................................................................... 19
5.5 Database Access in JSPs............................................................................................... 19
5.6 Dropping Database Records......................................................................................... 19
5.7 Comment Dubious Code............................................................................................... 19
5.8 Return Values for Methods............................................................................................ 20
5.9 Immutable Class Design............................................................................................... 20
5.10 No new Names for Methods and Properties................................................................21
6 Coding Style..................................................................................................... 21
6.1 Code Layout and Format............................................................................................... 21
6.2 Names/Identifiers.......................................................................................................... 22
6.3 Constants and Magic Numbers.....................................................................................22
6.4 Naming Conventions for Messages Properties..............................................................22
6.5 Code Metrics................................................................................................................. 23

© 2016 AGNITAS AG, public document 1 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

7 Usability........................................................................................................... 23
7.1 Sorting & Searching...................................................................................................... 23
8 Documentation.................................................................................................23
8.1 Javadoc......................................................................................................................... 23
8.2 Database Schema......................................................................................................... 23
8.3 Javascript Documentation............................................................................................. 24
8.4 Doc Files........................................................................................................................ 25
9 Other Requirements..........................................................................................25
9.1 Persistence................................................................................................................... 25
9.2 Logging......................................................................................................................... 26
9.3 Exception Handling....................................................................................................... 27
9.4 Security......................................................................................................................... 27
9.5 Testing.......................................................................................................................... 27
9.6 Threads......................................................................................................................... 28

© 2016 AGNITAS AG, public document 2 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

1 Introduction

1.1 EMM and OpenEMM

EMM and OpenEMM are web-based feature-rich enterprise applications for e-mail marketing,
marketing automation, email newsletter and service mails (transaction mails, event and time
triggered mails).

While OpenEMM is a single-server version of this software and published as open source under
the OSI-certified open source license CPAL, EMM is the commercial enhancement of OpenEMM,
offers additional features and runs on a server farm.

The mainline of EMM and OpenEMM is maintained by software developer AGNITAS AG in


Munich/Germany. However, companies and freelancers from all continents have already
contributed to the code base.

To gain a better understanding of the software we recommend to read the EMM/OpenEMM


Install & Admin Guide first. The latest Guide for OpenEMM can be downloaded here:
http://sourceforge.net/projects/openemm/files/OpenEMM%20documentation/Documentation
%20%28latest%20versions%29/

1.2 The Purpose of this Guide

This document is targeted at all contributors of OpenEMM and/or EMM. To make it easier for
contributors to develop new features and extensions for EMM/OpenEMM, we have listed some
guidelines for best practice to show you how you should design and structure your code and
data. If you want to write EMM/OpenEMM plugins please also read the extension architecture
documentation, available at https://sourceforge.net/projects/openemm/files/OpenEMM
%20development/.

Whether you want to have your open source code included into the mainline of OpenEMM or
you develop an extension for EMM, you should follow this guide. While it pobably means some
more effort on your side, please keep in mind that you only have to write your code once. But
we as the maintainer of EMM/OpenEMM have to make sure that your code will work for every
update in the future. If you follow the guidelines of this document it will make our job a lot
easier!

This guide focuses on code written in Java and Javascript. However, where applicable the rules
of this guide are also valid for other programming languages as well.

1.3 Follow this Document, not the Existing (Legacy) Code

Please do not use the existing EMM/OpenEMM code as a guide! It contains to some extent
legacy code from the year 2000 and 2001 (when JSP was brand new and Java frameworks non-
existent!). We are in a continous process to refactor the application. This document does not
describe how the code looks like right now but how it should look like.

1.4 Define Extension Prefix

If you plan to contribute not only a patch or a single feature, but a whole featue set or a
customer-specific extension, please choose a short but meaningful prefix for your extension,
because you will need it as unique ID and for naming conventions. Examples for suitable

© 2016 AGNITAS AG, public document 3 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

prefixes: 3dbars, crm, report, ibm, siemens, ... Please consult with us first to check if your
prefix is already taken or reserved.

The default prefix for core classes of EMM/OpenEMM is “core”.

1.5 OpenEMM Contributor Agreement

Due to legal reasons we kindly ask you to read and to sign the OpenEMM Contributor
Agreement before contributing code to OpenEMM. You can find this document at
http://www.openemm.org/fileadmin/docs/OpenEMMContributorAgreement.pdf.

To create this document we simply took the excellent Contributor Agreement of Oracle (former
SUN) (http://oss.oracle.com/oca.pdf) which is used - among other things - for OpenJDK (Java)
contributions, and renamed it. What we like most about this agreement, is, that it is fair and
short - and especially short of legalese. We hope you agree!

If you have questions regarding the OpenEMM Contributor Agreement, please visit
SUN/Oracle's extensive FAQ at http://oss.oracle.com/oca-faq.pdf where SUN/Oracle answers a
lot of potential questions. Just replace "Oracle" with "AGNITAS" (the original developer of
OpenEMM) in your mind.

2 General Requirements

2.1 Environment Pre-Settings

Your software has to run on Intel Servers with RedHat operating system.

If you want to edit OpenEMM's source code in Eclipse follow these steps:
 download and install Eclipse IDE for Java EE Developers
 create a new dynamic web project with source folders src/java + src/conf and content
directory src/jsp
 unpack the OpenEMM source tarball
 import the unpacked files (and overwrite dummy files in dynamic web project)
 add all JARs from directory lib to the project's build path

You can safely ignore any errors reported by the JSP validator of Eclipse, because these are
false positives and the application will just work fine!

If you want to preview a mailing in the GUI of EMM/OpenEMM, you need backend tool xmlback
which must be located in the path defined by property mailgun.ini.xmlback of the
configuration file emm.properties. While this file is included in the distribution of OpenEMM for
both Linux and Windows, a Windows version for EMM does not exist. If you need the Linux
version of xmlback for EMM please ask us for the latest release.

If you want to compile the backend code of OpenEMM, use script openemm_build.xml. The
comments in the header of this script list the preparations necessary for a successful
compilation.

2.2 Software Technology

© 2016 AGNITAS AG, public document 4 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

EMM uses Oracle DBMS 11, MySQL 5.5/5.6 or MariaDB 10.1 and OpenEMM uses MySQL as
database. Both products use the web container Tomcat 6 and Sendmail 8.13 or higher as SMTP
server.

2.3 SOLID Principles

Please follow the SOLID principles, also known as:

 Single responsibility (for classes)


 Open closed principle (open for extensions, closed for changes)
 Liskov substitution principle (code which works with super class, has to work with its
sub class too)
 Interface segregation (high cohesion of methods of same interface)
 Dependency inversion (high level classes should not depend on low level classes)

2.4 Code Modifications

To simplify the integration and maintainance of a general or customer-specific extension do


not modify the existing code but extend it - if possible in separate files. For example:
 instead of modifying existing classes, it is better to inherit and create new classes with
modified/added code
 instead of including code directly into existing JSPs it is better to include separate files
via the include statement

If you implement a new feature or you enhance an existing feature, you can hide the new
code in the GUI by encapsulating it with a new permission. This is done in the JSP by using the
agn tag ShowByPermission:

<agn:ShowByPermission token=”feature.subfeature.status” >
    <!­­ encapsulated code ­­>
</agn:ShowByPermission>

Replace token value feature.subfeature.status with the real name of the permission. To get an
idea for the name of the permisson have a look at the existing ones in table
admin_group_permission_tbl:

SELECT security_token FROM admin_group_permission_tbl;

You have to save your new permission name in this table with admin_group_id 4 (default
group) in order to activate the corresponding code and to make sure that it can be accessed
by EMM/OpenEMM users.

For the management of permissions, use class com.agnitas.emm.core.Permission.

Security advice: In order to encapsulate functionality not only in JSPs but also in Java classes
with permissions, use configuration file applicationContext-permissions.xml to define a
permission for a certain sub action in an Action class. Before the code of a sub action is
executed, class DelegatingActionProxySecured (see struts-config.xml) will evaluate, if the user
has proper permission for this sub action. If you would forget to define the permisson for the
Java code, a malicious user could circumvent the JSPs by calling the actions via a forged URL
directly.

2.5 Database Access

© 2016 AGNITAS AG, public document 5 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

In general, the performance of the database is the bottle neck in EMM/OpenEMM. Therefore,
please reduce access to the database as much as possible. If an attribute is already available
in the application context, in the session scope or in the request data, take it from there. If you
need to retrieve the attribute from the database, do not load complete objects or whole
records, but select only the properties or attributes you really need. To keep the volume of the
data transfer to a minimum

2.6 Code Layers and Frameworks

The software layers EMM/OpenEMM uses are Presentation (MVC pattern), Service (business
logic) and DAO (to access the DBMS). The MVC pattern is implemented by framework Struts
and the DI wiring of the layers by meta framework Spring.

Presentation layer must not and Struts Actions should not access DAO classes or the DBMS
directly. JSPs may access request data (see section 4.2 for details) and Struts Actions may
access service layer classes. In general, Struts Actions should be lean, all business logic
belongs to the service layer providing the business logic. Service layer classes have to be
stateless and should be singletons.

Please avoid any cyclic dependencies between code layers because it breaches the
architecture model of EMM/OpenEMM, makes the code more difficult to understand and to
extend and it complicates testing!

Spring 2.5 and Struts 1.3 are key frameworks of EMM/OpenEMM. While we use Hibernate,
please do not use Hibernate for any new code because we want to phase it out due to lack of
optimization options. For webservices EMM/OpenEMM uses Axis 1 right now. However, we are
about to introduce a new webservice API based on Spring WS early in 2011. If you want to
introduce a new framework or additional libraries, use them as long as the license is Apache,
Eclipse, LGPL or comparable.

2.6.1 Spring

You may use Spring as DI container to wire classes. Use Spring for the wiring of bean, DAO and
service classes. In general: When in doubt, use Spring because it makes re-wiring easier for
future enhancements.

Define classes to be configured by Spring as so-called Spring beans and define the classes
used by these beans as properties in Spring's XML configuration file (do not use annotations
like @Autowired or @Resource):

<bean id="MailingDao" class="org.agnitas.emm.mailing.dao.impl.MailingDaoImpl">
    <property name="dataSource" ref="dataSource" />
</bean>

Spring will inject the property class into class MailingDaoImpl via setter:

private DataSource dataSource; 

public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

Now you can use the datasource property in this class:

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

© 2016 AGNITAS AG, public document 6 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

Do not use a dependency lookup (because this creates a new dependency and runs contrary
to the DI concept):

JdbcTemplate jdbc =
     new JdbcTemplate((DataSource) ApplicationContext().getBean("dataSource"));

If the property class to be injected via setter is not a singleton (singleton=”false” in


applicationContext.xml) but you need to inject a new instance each time, do not inject the
class directly but use a factory which provides a method to create a new instance. Define
Spring bean and property classes to be injected:

<bean name="/profiledb" class="org.agnitas.web.ProfileFieldAction" >
    <property name="profileFieldFactory" ref="ProfileFieldFactory" />
</bean>

Inject the property in class ProfileFieldAction via a setter and use the the method of the factory
when needed:

private ProfileFieldFactory profileFieldFactory;

public void setProfileFieldFactory( ProfileFieldFactory factory) {
    this.profileFieldFactory = factory;
}

[...]

field = this.profileFieldFactory.newProfileField();

2.6.2 Struts

Please use Struts' form beans where appropriate and do not abuse HTTP requests to set and
get parameters. As a rule of thumb, all values for fields of a web form (including hidden fields)
should be stored in a Struts form bean.

You should not use Spring's ApplicationContext in Struts Actions directly via

getWebApplicationContext.getBean("Name")

because it creates a new dependency and runs contrary to the DI concept. Instead, inject
Beans into Struts Actions via configuration of Spring's class DelegatingActionProxy (proxy
class for the actual Struts Action) in struts-config.xml and define the associated Beans
including its properties in applicationContext.xml like

struts-config.xml:

<action path="/my_bean" 
type="org.springframework.web.struts.DelegatingActionProxy" parameter="method" 
name="myForm" scope="session" [...]>
<forward name="success" path="my_success" />
<forward name="error" path="my_error" />
</action>

and

applicationContext.xml:

© 2016 AGNITAS AG, public document 7 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

<bean name="/my_bean" class="org.agnitas.emm.{..}.web.MyAction">
<property name="..." ref="..." />
</bean>

Use the value of attribute path from struts-config.xml for attribute name in
applicationContext.xml. Do not use the id attribute, because in this case XML parser would not
accept the slash.

If applicable we recommend to use the DispatchAction class (to be exact: BaseDispatchAction)


as base class for Actions. DispatchAction combines the job of multiple actions for one JSP and
avoids long switch-case blocks in the execute method. Instead of overwriting the single
execute method you may create your own methods like create(), read(), update() and delete().
You configure the mappings for those methods via forward attributes with the name of the
methods in struts-config.xml.

2.6.3 Tiles

The presentation layer of EMM/OpenEMM uses Tiles to remove infrastructure code from the
core JSPs and to avoid duplicate code like navigation, etc. The infrastructure code for each JSP
is located in file template.jsp and file tiles-defs.xml defines how to assemble the final JSPs
from the JSP fragments. Each core JSP consists of two Tiles (because some parameters are
needed in advance to create the navigation bar and tabs):
 a {feature}-setup.jsp fragment which sets up parameters needed early on in the
process of rendering the view (like menus) and
 a core {feature}.jsp fragment rendering the functionality of the web page

({feature} represents the function of the JSP like mailing-list).

Please see appendix A for a diagram of the Tiles structure in EMM/OpenEMM (legacy layout).

2.7 GUI Messages

EMM/OpenEMM handles 4 flavours of GUI messages: errors (red), warnings (yellow), success
(green) and info messages (blue). “Global” messages are displayed at a central place (like the
page header or the upper right corner) and “local” messages are shown inline next to the
related form element.

Global messages are used to give the user feedback that a general error occurred or some
action happened like saving a mailing or target group, deleting a subscriber or updating an
EMM action. Local messages are used to show an information or error next to the related form
element like a missing but required value or a wrong data input format.

2.7.1 Messages Objects

The Action classes of Struts 1 provide two methods to show messages in the presentation
layer:
 Action.saveMessages(HttpServletRequest, ActionMessages)
 Action.saveErrors(HttpServletRequest, ActionMessages)

These methods define the severity of the messages (error or no error), but not, if a message is
global or local.

© 2016 AGNITAS AG, public document 8 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

Please note, that class ActionMessages is used for any message. Class ActionErrors, that was
used for error messages in the past, just exists for backward compatibility.

Any message equals to an instance of ActionMessage and is stored in an ActionMessages


object by using method ActionMessages.add(String property, ActionMessage message) . The
argument message is the message itself, and property argument is one parameter in
combination with some others (explained later), that defines, where a message is shown.

To add a global message, argument property of method ActionMessages.add() is set to


ActionMessages.GLOBAL_MESSAGE.

To add a local message, method ActionMessages.add() is called, too, but argument property is
not set to ActionMessages.GLOBAL_MESSAGE, but to an arbitrary property name. It is strongly
recommended to choose a meaningful name, that is related to the form element, where the
message should be displayed. For example, if an error message tells the user that the chosen
password does not match the security policy, the message should be shown next to the input
field of the password and argument property could be simply named password.

2.7.2 Messages in JSPs

A message is displayed in a JSP by the JSP tag <html:messages>. Example of a global


message:

<html:messages id="msg" property="org.apache.struts.action.GLOBAL_MESSAGE" 
message="true" >
$msg
</html:messages>

Tag <messages> iterates over all messages stored in the specified property. In each iteration,
the message text of the current message is stored in page scope attribute msg (whose name
is defined by attribute id). Because it is possible to add error messages and non-error
messages to the same property, attribute message can switch between both types: Value
false selects error messages, and value true selects non-error messages.

All global messages are displayed because property


org.apache.struts.action.GLOBAL_MESSAGE is set. This is the value of the class member
ActioMessages.GLOBAL_MESSAGE.

To display a local message, the property name used in your Java code has to be used. To
display the message from the password example above, attribute property has to be set to
value password:

<html:messages id="msg" property="password" message="true" >
$msg
</html:messages> 

Please note: If attribute property is not set in the JSP, all messages are shown independently
of their property name used in the Java class.

In some cases, it is useful to show additional JSP code, when there is at least one message to
view or to hide the code, if no messages are to be shown. For that reason Struts 1 provides tag
<messagesPresent>. Here is a generic code fragment:

<logic:messagesPresent property=”messageProperty” message=”true”> 
This is the message to show
</logic:messagesPresent> 

© 2016 AGNITAS AG, public document 9 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

As for tag <messages>, attribute message switches between error messages and non-error
messages.

For a complete code example imagine a form element to enter the user's birthday with three
dropdown boxes to choose day, month and year from. In this case, different types of errors
could occur:
 The user did not enter his/her birthday
 One or more of the entered values are out-of-range (month > 12, day < 1, etc.)
 The user's computed age is outside of an acceptable range

To show the error message(s) for the different error situations, this code could be used:

<logic:messagesPresent property=”birthday” messages=”false”>
<ul> 
<html:messages property=”birthday” messages=”false” id=”msg”>
<li>$msg</li>
</html:messages>
</ul>
</logic:messagesPresent>

2.7.3 messages.jsp and messages-transitional.jsp

JSPs messages.jsp and messages-transitional.jsp both display messages, but there is a


significant difference: messages-transitional.jsp displays any message (global and local) at
the top of the page while messages.jsp displays global messages only.

JSP messages-transitional.jsp is used for a transitional phase and will be removed in


future. Since it is deprecated, it should not be used for any new JSP file! For new JSP files,
define messages.jsp to be used (in tiles-defs.xml) and add global messages by using the
correct property ActionMessages.GLOBAL_MESSAGE.

If a local message should be displayed, JSP code containing tag <html:messages> has to be
added to the correct location within the JSP. If the message is not shown or shown at a wrong
place, please check your Java code and JSP page if the correct property name is used.

3 Directory Structure

EMM/OpenEMM uses the classic directory structure of a Linux and a Java web application:

/bin executable files of backend (shell and python scripts, etc.)


/conf global configuration files
/lib Java libraries of web container (Tomcat)
/var data files of backend (logs, spool files, etc.)
/webservices helper files for implementation of webservices

/webapps/core root of Java frontend with JSP files and JSP directories
/webapps/core/WEB-INF configuration files and directories for frontend
/webapps/core/WEB-INF/lib Java libraries required by EMM/OpenEMM
/webapps/core/WEB-INF/classes executables Java classes of frontend

Please adhere to the existing structure and create new directories and sub-directories where
applicable (see next section).

© 2016 AGNITAS AG, public document 10 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

4 Code Components

An extension consists of various types of files. All file names have to be self-explaining and in
English language. This section explains how to structure the files of the code by file type.

4.1 Java Packages and Class Files

If your extension requires not only to modify existing classes but also to write new ones please
create your own packages. Packages have to start with domain “com” or “org”, company
“agnitas”, application “emm” and your prefix. Please use these categories for your classes:

 [com|org].agnitas.emm.{prefix}.{section}.beans: Beans interfaces


 [com|org].agnitas.emm.{prefix}.{section}.beans.impl: Beans implementation
 [com|org].agnitas.emm.{prefix}.{section}.dao: Data access object interfaces
 [com|org].agnitas.emm.{prefix}.{section}.dao.impl: Data access object implementation
 [com|org].agnitas.emm.{prefix}.{section}.service: Interfaces for business logic
 [com|org].agnitas.emm.{prefix}.{section}.service.impl: Implementation of business logic
 [com|org].agnitas.emm.{prefix}.{section}.web: Struts action classes
 [com|org].agnitas.emm.{prefix}.{section}.web.forms: Struts form beans
 [com|org].agnitas.emm.{prefix}.{section}.util section-wide Utility&Helper classes

If you develop an extension, set {prefix} to your extension prefix, otherwise use core.
{section} is the placeholder for a functional area of EMM/OpenEMM like pid, reporting or
webservice.

Over time we will refactor all existing packages of EMM/OpenEMM to comply with this package
model.

If you write a new class, check first if EMM/OpenEMM offers a base class which you can user to
inherit useful code from (like BaseDispatchAction or BaseDaoImpl).

Do not use wildcards in import statements but list all required classes individually unless you
use almost all classes of a certain package.

If you use methods of a Helper or Utility class, add the class name to each method call for
clarity (but do not omit the import statement).

4.2 Utility and Helper Classes


In order to not re-invent the wheel again and again, below is a list of feature-independent
utility and helper classes (cue: cross cutting concerns).

CSV:
org.agnitas.util.CsvReader
org.agnitas.util.CsvWriter

JSON:
com.agnitas.json.JsonReader
com.agnitas.json.Json5Reader (allows a non strict mode when reading JSON data)
com.agnitas.json.JsonWriter

Accessing (S)FTP:
- org.agnitas.util.FtpHelper

© 2016 AGNITAS AG, public document 11 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

- org.agnitas.util.SFtpHelper

Reading IO streams to byte arrays


com.agnitas.emm.core.commons.StreamHelper

HTTP request/response
org.agnitas.util.HttpUtils

HTML documents/fragments:
org.agnitas.util.HtmlUtils

CSS styles:
com.agnitas.emm.grid.grid.util.CssUtils

Images:
com.agnitas.util.ImageUtils

Cookies
com.agnitas.emm.util.http.CookieUtil

Decoding/Endcoding:
org.agnitas.util.DataEncryptor

Encoder classes, that encapsulate specific MessageDigest instances:


com.agnitas.emm.core.commons.encoder.*

Checking password policy:


com.agnitas.emm.core.commons.password.ComPasswordCheck(Impl)

4.3 JSP Files

Create your own directory for your JSPs and name it like your prefix. If you have to modify
existing JSPs please create a subdirectory with the name of your prefix and include your code
via the include statement in the original JSP. If this is not possible you have to provide an
alternative JSP with your prefix which can be integrated via tiles-defs.xml.

Please do not use scriptlets but the JSP Expression Language (EL) and/or the corresponding
tags of the Java Standard Tag Library JSTL, especially use

<c:set ... /> (not  <% pageContext.setAttribute("..."); %> )
<c:if ... (not  <% if ... )

If both, the use of EL and JSTL is possible, please choose EL. If both, the use of JSTL and Struts
tags is possible, use JSTL tags. If your JSPs need Javascript-Code with a length of more than a
few lines, please provide this code in a separate file in subdirectory {prefix} of existing
directory js.

Never use scriptlets to access the DB directly. Since we use an Action based framework it
should work like this:
1. Action calls DAO - even better a service class calls DAO - and query result is written to
request
2. JSP displays data via EL expression or via a JSTL tag (2 nd best option)

Example:
1. Action:
private TargetDao targetDao;

© 2016 AGNITAS AG, public document 12 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

List<Targets> targetList = targetDao.get(...)
request.setAttribute("targets", targetList);
[...]
public void setTargetDao(TargetDao targetDao) {
    this.targetDao = targetDao;
}

2. JSP:
<c:forEach var="target" items="${targets}">
    <html:option value="${target.id}">
        ${target.shortname}
    </html:option>
</c:forEach>

If lists are displayed in a JSP (like field names in a drop down selector), these lists should be
sorted alphabetically by default, except if otherwise requested.

4.4 Javascript Files

More and more functionality of EMM/OpenEMM is handled by Javascript (JS). To make sure that
JSP files are not cluttered with JS code, separate the code from HTML, JSTL tags and EL.

Do not write JS code longer than one statement into HTML tags but put it at the end of the JSP
and bind it via an ID of the HTML tag.

If Javascript code is longer than a few lines, take it out of the JSP and put it in a separate JS file.

JS files for more than one area of EMM/OpenEMM should be saved in directory /js (same level
as /WEB-INF). A JS file for a dedicated JSP like - let's say recipient-view.jsp - should be saved in
directory /recipient/recipient-view/ and the name of the file should describe its purpose like
search-filter-beautifier.js. In any case the name of a JS file should primarily not describe where
it belongs to but its purpose. Only if you would end up with several files of the same name
(like the old list.jsp's and view.jsp's) you should prefix the file name to indicate its affiliation.

JS libs like jQuery have to be located in /js/libs. Do not locate any JS files in directory
/assets/core because this directory structure is for client specific files only (like individual
layouts).

If a certain JS code is rather complex it should better use a class structure instead of using just
a set of functions.

4.5 Configuration and Property Files

EMM/OpenEMM has several configuration files, which usually have to be modified by an


extension:

 web.xml: Basic application settings


 applicationContext*.xml: Spring configuration files for classes and permissions
 struts-config.xml: Struts configuration
 tiles-defs.xml: Tiles configuration
 emm.properties: Individual settings for the Java code of EMM/OpenEMM
 cms.properties Individual settings for the CMS module
 message*.properties: GUI text translations

© 2016 AGNITAS AG, public document 13 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

For the first 4 configuration files of this list please provide snippet files containing only the
new and/or modified XML content. We will extend our Ant build script to merge the basic files
with your snippet files through the xmltask tag of the Ant plugin with the same name.

For bigger extensions create your own {prefix}Context.xml file for your Beans and DAOs and
include it via the context-param tag in web.xml. Also, create your own struts-config-
{prefix}.xml file for your Actions and Forms and include it via the init parameter of the action
servlet declaration in web.xml. Place both files in WEB-ROOT/WEB-INF/{prefix}.

If you plan to introduce new tables and use Hibernate to access them (but we discourage you
to use Hibernate!), you have to create a *.hbm.xml file which is loaded by dataContext.xml of
Spring.

If you need to add more than a few lines to emm.properties please create your own
configuration file {prefix}.properties. Your properties have to be preceeded by your prefix.

4.5.1 Configuration Data in Database

The EMM/OpenEMM database holds 3 different tables to store configuration data of EMM:
 config_tbl: used to store global parameters which are valid for the whole
EMM/OpenEMM instance
 company_info_tbl: used to hold default values (company_id = 0) for parameters and to
store company-specific values which are needed by only a few tenants (not available
for OpenEMM)
 company_tbl: used to store (different) values of parameters which are used by (almost)
all tenants

4.5.2 Configuration Values in class ConfigValue

Always add new configuration parameters to class


org.agnitas.emm.core.commons.util.ConfigValue to have them in one place and because
values of this class will be cached by ConfigService (for 5 minutes by default) to reduce
interaction with the DB. While the database tables mentioned in section 4.4.1 above hold the
configuration values, parameters listed in ConfigValue may provide a fallback value (used in
case the DB based value is missing).

The mapping of parameter names in the DB tables to parameter names used in ConfigValue
works like this:

 names from parameters of table config_tbl are formed by concatenating field class and
field name, separated by a dot
 names from parameters of table company_info_tbl are formed by using field cname

Parameters stored in table company_tbl are not accessible by ConfigService and, therefore,
are of no use in class ConfigValue.

A value of type ConfigValue may be used like this:

String expireSuccess = configService.getValue(ConfigValue.ExpireSuccessMax, 
companyID);

or for numeric values

int expireSuccess = configService.getIntegerValue(ConfigValue.ExpireSuccessMax,
companyID);

© 2016 AGNITAS AG, public document 14 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

or for boolean values (like 'true', 'false', '0', '1', 'yes', 'no')

boolean expireSuccess = 
configService.getBooleanValue(ConfigValue.ExpireSuccessMax, companyID);

The second parameter companyID is optional, but should be used when needed to enable a
account-specific configuration. For global configuration values, leave this parameter out.

Any exception from using ConfigValue should have a valid reason and has to be documented
(like it is done for the check methods of class org.agnitas.service.JobQueueService).

4.5.3 Messages Properties

Hard coded text messages in JSPs are not acceptable because EMM/OpenEMM are multi-
language applications. Please use message properties and add them to file
new_messages.properties (EMM) or create your own section in file message.properties
(OpenEMM).

If you are able to provide translations of your GUI phrases please feel free to extend the
language specific message property files as well. The default language is English.

If a message consists of a complete sentence (including verb), end it with a period or (in case
of error or warning messages) with an exclamation point.

For the naming of messages properties please read section “Naming Conventions for
Messages Properties” in chapter “Coding Style” below.

4.6 TLD Files

EMM/OpenEMM provides its own tags in JSP tag library agnitas-taglib.tld. If you want to create
more than just a few tags, please create a separate {prefix}-taglib.tld file and put the
corresponding classes into package org.agnitas.{prefix}.taglib.

4.7 Third-Party Libraries

If you plan to incude third party code like a new library or JAR file, please make sure that the
new code component uses a license like Apache, BSD, Eclipse, LGPL or comparable.
Unfortunately, we can not accept GPL licensed libraries!

All JAR files required by your new code must be located in WEB-ROOT/WEB-INF/lib. All JS
libraries have to be located in WEB-ROOT/js/lib. Do not simply include JS libraries with a link in
the GUI code! Please provide only libraries which are really needed by your code.

Do not remove any copyright notice from the source code of third party libraries, and if you
have to change the code of a third party library leave a note in a doc file *.txt so that this
change is not lost when the library is updated to a new version at a later time.

4.8 Database Tables and Fields

All project-specific data should be stored in project-specific database tables, if possible. If you
need to create new tables, preceed the table names with your prefix + underscore and end it

© 2016 AGNITAS AG, public document 15 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

with underscore + "tbl", like crm_status_tbl. If your code creates temporary tables add a
“_tmp_” to the name of these tables to indicate their transient nature.

SQL code:

In SQL Statements use UPPER CASE characters for


 DDL and DML expressions like SELECT, INSERT, UPDATE, WHERE, ALTER TABLE, etc.
 data types like VARCHAR, INT(1), TIMESTAMP, etc.
 system functions (like SYSDATE, CURRENT_TIMESTAMP, TO_CHAR(), etc.)
Use lower case for names of tables, columns, indexes, constraints, triggers, tablespaces, views
and procedures. This ensures maximum compatibility with Oracle and MySQL/MariaDB (while
MySQL/MariaDB is case-sensitive, Oracle is not). Do not wrap table and column names with
quotes!

To make sure that new tables for MySQL/MariaDB use the InnoDB storage engine and
character set UTF-8, add

ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

at the end of each table definition.

If you use an id field in a table (and you should), please do not simply call it id (even if it is
preceeded with your prefix) but name it according to your table name like crm_status_id for
table crm_status_tbl.

Please make sure that your update of the EMM/OpenEMM database schema is backward
compatible to older versions of EMM/OpenEMM, i.e. you may add new tables and fields, but
you should never delete them. Also, if you define a new column for a database table being
NOT NULL, you must define a DEFAULT value for this column to maintain downward
compatibility with older EMM/OpenEMM code.

If you want to modify an existing database field you must be careful to change it only in such
a way that older code versions will still be able to use it without problems. The best practise
approach works like this:
1. Create a new database field with the required changed attributes
2. Change the code so that it reads the data from the original database field and writes to
both, the original field and the new field (with the new format)
3. (Manually) migrate all data from the old database field to the new field
4. Change the code so that it reads the data from the new database field and writes to
this new field only
5. Remove the old database field

Table check:

If you want to check, if a non-mandatory database table really exists, before operating on it,
use method checkTableExists of class org.agnitas.util.DbUtilities (EMM only).

SQL files:

To allow the update of existing databases you have to create


 a SQL file emm_{prefix}-schema.sql or openemm_{prefix}-schema.sql containing your
new tables, columns, keys, indices, views and triggers and
 a separate dump file emm_{prefix}-content.sql or openemm_{prefix}-content.sql to fill
in the database content (if applicable).

© 2016 AGNITAS AG, public document 16 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

For MySQL/MariaDB we recommend the mysqldump tool. Use

mysqldump ­aCceQx ­­hex­blob ­­routines ­­triggers ­­no­data ­u root ­p ­r 
emm_<prefix>­schema.sql [emm|openemm]

and

mysqldump ­aCceQx ­­hex­blob ­­no­create­info ­u root ­p ­r emm_<prefix>­
content.sql [emm|openemm]

to create the schema file and the content file.

If you are an EMM/OpenEMM committer, please ignore the file name patterns mentioned in
this section and have a look at the Development Process Guide instead.

4.9 Navigation, Stylesheets, Icons and Images

If you want to introduce a new menu item in the sidebar you have to extend file
sidemenu.properties. If you need your own stylesheet, please name it {prefix}-style.css and
place it in directory WEB-ROOT/styles.

Use your prefix for the names of all your icons and images and place them for the menu level
in directory WEB-ROOT/assets/{prefix}/images and use directory WEB-ROOT/assets/
{prefix}/images/sub_icons for the sub menu level.

In general, your GUI design should seamlessly integrate with the existing EMM/OpenEMM
design. The user interface should be as close as possible to the current GUI. The GUI must at
least support Internet Explorer 9 as well as Firefox 10 on Windows.

4.10 Quartz Jobs

Please create and manage timed jobs not directly or via cronjobs but only via Spring's Quartz
classes. Quartz uses a default scheduler which can be extended. Define all job details and
triggers in cronContext.xml. If you have several Quartz jobs to configure please create your
own {prefix}CronContext.xml.

4.11 Other Files

If your code needs other files (like key files, txt files, etc.), please place them in directory WEB-
ROOT/WEB-INF/classes , but note their path as a property in file emm.properties so that the
location of the file(s) can be changed easily.

5 Coding Rules

5.1 Recommended Suffixes for Class Names

 Action Struts action class


 Base (abstract) super class from which several other classes inherit
 Builder construction class with methods which take parameters and create new
components
 Dao DAO layer class with CRUD methods to access database

© 2016 AGNITAS AG, public document 17 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

 Exception customized Exceptions


 Factory implementation of factory pattern to provide instances of (undefined) classes
 Form Struts form bean for Struts action class
 Helper support class with helpful methods for one or more service classes
 Impl implementation class of interface
 Job Class to invoke a task (usually triggered by cronjob-like Quartz framework)
 Lib collection of related general methods needed in different service classes
 Manager management class of service layer
 Service service layer class with business logic, called by Action class
 Tag implementation class for JSP tags
 Utils collection of static support methods needed in various corners of the code, must
not depend on other classes of code layers
 Worker Thread (implementing Runnable or Callable), used if execution of a method takes
very long, worker classes usually wrap method calls to service classes

5.2 Default Order of Content in Classes

To maintain an uniform order for the content of classes, please follow this sequence:

Class Header:
 license header (OpenEMM only)
 package name
 static import declarations with fully qualified class names
 import declarations with fully qualified class names, NO wildcards!
 class order: 1. java.* -> 2. javax.* -> 3. all other classes, sorted alphabetically
(default settings of Eclipse)
 JavaDoc with purpose of class, if not obvious anyway
 NO author, NO creation date, NO change date (this is handled by the VCS)

Class Body:
 class signature

 declaration of constants (UPPER CASE)


 declaration of global class variables (static)
 declaration of normal instance variables
 declaration of non-anonymous inner classes

 static methods
 constructor(s) for new method (if needed)
 getter and setter (as pairs, but only if a getter or setter is really needed)

 public methods with declaration of local variables


 protected methods with declaration of local variables
 private methods with declaration of local variables

Hints:
 whenever possible, define elements as private and final
 declare/define variables as local as possible and within methods as late as possible
(better visibility, less errors)
 do not use infrastructure getters and setters (like for Spring) in interfaces

5.3 Type of Class

The declared type of a new object should describe its purpose and not its implementation:

Map<String, String> translationTable = new HashMap<String, String>();

© 2016 AGNITAS AG, public document 18 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

A Map is better than a HashMap and a Collection (if reasonable) would be even better than a
Map to achieve maximum flexibility. If a class whose type you want to use offers an interface,
use the type of the interface to avoid committing to a certain implementation.

5.4 Visibility of Methods

In general, methods should be as private as possible, i.e. the default visibility of methods
should be private and a public visibility should only be chosen if access from other packages is
really necessary. The more limited the visibility of methods, the easier it is to implement
changes to the code.

When in doubt, you should start with private visibility and increase the scope of visibility on
demand only.

5.5 Database Access in JSPs

JSPs must not access the DAO layer or database directly. If the code in the Action class is lean
it may access a DAO class (otherwise please put code into a separate service class) and write
the result into the request. The JSP can display the data from the request via an EL expression
or JSTL tag.

Example for Action class:

private TargetDao targetDao;
[...]
List<Targets> targetList = targetDao.get(...);
request.setAttribute("targets", targetList);
[...]
public void setTargetDao(TargetDao targetDao) {
    this.targetDao = targetDao;
}

Example for JSP code:
<c:forEach var="target" items="${targets}">
    <html:option value="${target.id}">
        ${target.shortname}
    </html:option>
</c:forEach>

5.6 Dropping Database Records

If a EMM/OpenEMM user deletes important objects like mailings, mailing lists or target groups,
these objects should never be dropped right away. Instead, the table design for these objects
should provide a column deleted which indicates whether the corresponding record is deleted
or not. If the deleted flag is set to value "1" it indicates that the record was deleted by the
user. The code which checks for undeleted objects should always use comparision "deleted
== 0".

5.7 Comment Dubious Code

© 2016 AGNITAS AG, public document 19 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

Sometimes it may be necessary to write code which looks wrong at first but was intended. In
this case please leave a comment so that the reader knows your intention and does not
suspect a mistake. A few examples for code which should be commented:

 if you compare objects with “==” or “!=” (i.e. you compare if two references point to
the same object)
 if you have built a switch-case instruction which deliberately uses an implicit “fall
through” path, i.e. not every case option is terminated with a break or a return
statement
 if you have built a try-catch-finally instruction which deliberately uses an empty catch
block

5.8 Return Values for Methods

If an expected error occurs within a method (like a missing element), the method should return
-1 for numerical and NULL for all other return types.

If an unexpected error occurs in a method (like a missing database), the method should not
return NULL but throw an exception which should be evaluated by the calling code.

5.9 Immutable Class Design

Sometimes your code may show weird side effects because someone's else code manipulates
your objects without explicit notification.

If you want to make sure that instances of a certain class can not be altered by other code,
you have to write an immutable class, because this class design prevents third parties from
modifying already created instances. The result is a class that can be safely referenced
multiple times without special synchronizing effort, which makes this class thread-safe.

An immutable class design has to make sure, that the class can not be modified by a sub-class
and that its instances and attributes must not be changed after creation. For implementation
you have to declare the class as final and all attributes of the class as private final, and you
should declare all input parameters of its public methods as final.

If the class offers methods which accept mutable objects as input (like setter methods), you
have to create new instances of these objects (“defensive copy”) instead of referencing the
original (see new Date and handling of collection members below). This cloning of objects
leads to thread-safety, because every thread works not with the original object, but with a new
copy.

Example of a mutable class:

public class MutableDemo

    private Date datum;
    private List<Person> members = new ArrayList<Person>();

    public dateMethod(Date datum) {
        this.datum = datum;
        // more code
    }
    public Date getDatum() {
        return datum;
    }

© 2016 AGNITAS AG, public document 20 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

    public void setMembers(List<Person> members) {
        this.members = members;
    }

Example of an immutable class:

public final class ImmutableDemo

    private final Date datum;
    private final List<Person> members = new ArrayList<Person>();

    public dateMethod(final Date datum) {
        this.datum = new Date(datum.getTime());
        // more code
    }
    public Date getDatum() {
        return new Date(datum.getTime());
    }
    public void setMembers(final List<Person> members) {
        this.members.clear();
        this.members.addAll(members);
    }

Immutable classes should be used when it makes sense, like code areas where you need
thread-safety. But keep in mind that this design pattern slows down execution a bit and needs
more memory than a mutable class due to all the object copies, of course.

5.10 No new Names for Methods and Properties

When you do a code refactoring (which we appreciate in general) please do not change names
of existing methods or properties, because experience shows that you will never catch all
dependencies. A better way is to introduce a new method or property with the desired name
and let the old code call this method or use this name.

For instance, the method with the obsolete name could be marked as deprecated by an
annotation and call the new method via a friendly “DeprecatedException”. This will lead to a
warning at compile time and to a log entry at run time which helps to track down all
dependencies, so that the old code can be removed at a later time.

6 Coding Style

While code is written only once, it has to be read many times – especially in big projects! That
means that you should try not to write “clever” code in order to save yourself some typing
work, but you should write readable code to save those developers research time who will
come after you and who will have to maintain the code originally written by you.

6.1 Code Layout and Format

Please follow the well-known best practice rules for the layout and formatting of your Java
code like those mentioned in the official Java Coding Style Guide, available at
http://www.sourceformat.com/pdf/java-coding-standard-sun.pdf

We consider the five most important rules for your coding style to be:

© 2016 AGNITAS AG, public document 21 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

 use descriptive (English) names for packages, classes, methods, attributes and
properties - and avoid unclear or misleading acronyms like “dbg” or “lib”
 use 4 spaces for an indentation (instead of a tab)
 use whitespace around operators, before opening brackets and after a comma in a list
 use curly brackets { and } in if statements even when not mandatory
 do not put too many return statements in a long method
 your comments should not state how it is done but what is done and why

6.2 Names/Identifiers

All names/identifiers have to be descriptive and in English language for easier maintenance.
To find short and meaningful names sometime can be a challenge, but it is worth the effort
because your code will be much more often read than written. And if you can not sum up the
purpose of a class, method or property with a simple name, maybe then your class, method or
property serves too many purposes and should be split?

Class names start with capital letters and properties as well as methods start with small
letters. Use “CamelCase” notation for better readability. For constants use only capital letters
with underscores (not hyphens) for separation of name segments.

If there are more than one implementation for a certain class or method, the name of the class
or method should include a hint refering to the type of implementation.

6.3 Constants and Magic Numbers

Prevent the use of magic numbers and define constants with meaningful names instead.

If a class using a certain constant implements an interface, the constant should be defined in
the interface, so that other implementations may use this constant as well (unless the
constant is implementation-specific, of course).

6.4 Naming Conventions for Messages Properties

Before you create a new message property please check first if the message already exists.

To avoid duplicates and to better find existing messages, follow these rules:

1. separate parts of the full name like prefix, name and suffix with a dot
(prefix.name.suffix)
2. do NOT use underscores but use CamelCase for longer Names (recipient.NewRecipient)
3. use prefix “default.” for generic expressions like “Yes” , “No”, “Size”, “Time”, “Type”,
etc. which are not dedicated to a certain feature
4. use prefix “button.” for button names
5. use prefix “error.” for error messages and prefix “warning.” for warning messages
6. if a message property belongs to a certain feature group of EMM/OpenEMM start it with
the name of that feature group (like “action.”, “export.”, “import.”, “mailing.”,
“recipient.”, “settings.”, “statistic.” or “target.”)
7. use verb suffixes like “list”, “show”, “create”, “change” and “delete” for the
appropriate functionality
8. use prefix “UserRight.” for permissions, append it with the category where it should be
sorted in (like “Mailing.” or “Recipient.”), if necessary append it with a token for the
subfeature, and end with the permission itsself like “mailing.show” or “recipient.delete”

© 2016 AGNITAS AG, public document 22 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

6.5 Code Metrics

We recommend

 max. 30 classes per package (reduce through package splits)


 max. 30 methods and 700 lines per class (reduce through class splits)
 max. 5 parameters per method (reduce through use of object parameters)
 max. 30 statements per method (reduce through use of private methods)
 max. 3 levels of nested statements like if, case, for, while, do, etc. in a method (reduce
through use of private methods)
 cyclomatic complexity: max. 20 different execution paths within a method (reduce
trhough use of private methods)

But we know that these metrics are not applicable for every case (like very long case
statements).

7 Usability

7.1 Sorting & Searching

Dropdowns, lists and tables should be sorted alphabetically by default if not specified
otherwise.

Search functions should work case-insensitively by default because a lot of users do not use
capital letters when searching.

8 Documentation

We can not accept contributions without documentation because we have not the time to find
out how your code works and how it should be built, integrated and deployed. To be able to
understand your code and integrate it into EMM/OpenEMM's mainline we need a minimum of
the following documentation.

8.1 Javadoc

All interfaces (especially for DAOs) and Struts Action classes need documentation . To do this,
document the purpose of all public properties, public methods and constants of your
interfaces in javadoc format. Documentation of methods has to include the meaning of all
input parameters and the return type.

8.2 Database Schema

Oracle and MySQL/MariaDB both offer the possibility to add comments to tables and columns.
For every database update file which adds a table or a column, add a statement to provide a
brief comment for every new object. If the comment is added using a separate statement, it
should follow immediately after the creation statement.

© 2016 AGNITAS AG, public document 23 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

Syntax to add a comment to a table:


Oracle: COMMENT ON TABLE <tablename> IS '<text>';
MySQL/MariaDB: ALTER TABLE <tablename> COMMENT '<text>';

Syntax to add a comment to a column:


Oracle: COMMENT ON COLUMN <tablename>.<columnname> IS '<text>';
MySQL/MariaDB: ALTER TABLE <tablename> MODIFY <columnname> <all­column­
properties> COMMENT "<text>';

For MySQL/MariaDB you have to duplicate the whole list of column properties, such as data
type, default values, etc. For clarification please consult the MySQL/MariaDB documentation.

For MySQL/MariaDB there is a easier way to add comments on tables and columns during
creation. This is the preferred way whenever possible:
CREATE TABLE <tablename> (<column­definitions>) COMMENT '<text>';
ALTER TABLE ADD <tablename> ADD <column­definition>;
MySQL/MariaDB colum-definition: <columnanme> <all­column­properties> COMMENT 
'<text>';

Full example for Oracle:


CREATE TABLE admin_group_tbl (
    admin_group_id NUMBER,
    company_id NUMBER,
    shortname VARCHAR2(100),
    description VARCHAR2(1000)
) TABLESPACE data_accounting;

COMMENT ON TABLE admin_group_tbl IS 'stores groups for users to ease handling
    of permissions';
COMMENT ON COLUMN admin_group_tbl.admin_group_id IS 'unique group ID';
COMMENT ON COLUMN admin_group_tbl.company_id IS 'tenant ­ use company_ID 1 for
    generic groups';
COMMENT ON COLUMN admin_group_tbl.shortname IS 'short descriptive name for this
    group';
COMMENT ON COLUMN admin_group_tbl.description IS 'description for this group';

Full example for MySQL/MariaDB:


CREATE TABLE admin_group_tblx (
    admin_group_id INT(11) NOT NULL AUTO_INCREMENT COMMENT 'unique group ID',
    company_id INT(11) DEFAULT '0' COMMENT 'tenant ­ use company_ID 1 for
        generic groups',
    shortname VARCHAR(100) DEFAULT '' COMMENT 'short descriptive name for this
        group',
    description VARCHAR(1000) DEFAULT '' COMMENT 'description for this group',
    PRIMARY KEY (admin_group_id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
    COMMENT 'stores groups for users to ease handling of permissions';

To indicate whether the DB column of a production database should be hashed or truncated


when creating a test database out of it for development purposes, add suffix [private_data] to
the comment of a column that should be hashed (like email addresses), and add suffix
[secret_data] to the comment of a column that should be truncated (like passwords).

8.3 Javascript Documentation

© 2016 AGNITAS AG, public document 24 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

Due to the nature of the language, undocumented Javascript code is more difficult to
understand than Java code. Therefore, we have three rules to document Javascript code:

1. Make a comment before each function/method (unless it is trivial) with the same info
Javadoc provides (i.e. purpose of the function/method, its parameters and the return
type)
2. If the function/method is longer than about 30 lines, insert one-liners to explain its
different parts
3. If the code does something non-trivial (i.e. a simple glance at the code does not help to
understand its purpose) leave a comment explaining what is going on

Of cource, you may use these rules for your Java code as well.

8.4 Doc Files

1. deployment documentation to get a working system:


 description of build process + new build target for OpenEMM's Ant build script
openemm_build.xml
 description of configuration process (with meaning of all parameters/properties
which have to be set or modified)
2. list of new files with one-line explanation of purpose for each file (inluding new JAR
files)
3. list of modified files with result of unified diff for each file
4. list of new tables, new columns and modified columns with one-line description for
each element

9 Other Requirements

9.1 Persistence

EMM uses an Oracle, MySQL or MariaDB database and OpenEMM uses a MySQL database -
both via JDBC and Hibernate. However, you should use DBMS independent SQL code to access
the database. We prefer Spring's JDBC templates. Please do not use Hibernate any longer,
since we will phase it out.

If you have to use SQL statements which are specific to Oracle or MySQL/MariaDB, please
encapsulate the database specific SQL code like this:

if (AgnUtils.isOracleDB()) {
    // Oracle SQL statement
} else {
    // MySQL/MariaDB SQL statement
}

Please make sure to close all open database resources like connections, statements, prepared
statements, result sets, input and output streams. Best practise:

Connection connection = null; 
Statement statement = null; 
ResultSet resultSet = null; 
try { 
    connection = getConnection(); 

© 2016 AGNITAS AG, public document 25 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

    String sql = "Some SQL Statement"; 

    statement = connection.createStatement(); 
    resultSet = statement.executeQuery(sql); 
    // read ResultSet and process it 
} finally { 
    if (resultSet != null) { 
        resultSet.close(); 
    } 
    if (statement != null) { 
        statement.close(); 
    } 
    if (connection != null) { 
        connection.close(); 
    } 
}

For your convenience we offer several methods closeQuietly() in class DBUtilities.

9.2 Logging

Do not forget logging and use at least log levels ERROR, WARN, INFO and DEBUG! For logging
purposes we use the log4j logger. Minimum logging: Exceptions have to be caught and logged
with log level ERROR. Please choose your log messages carefully.

A good example:
import org.apache.log4j.Logger;

// replace {ClassName} with the name of your class
private static final transient Logger logger = 
Logger.getLogger({ClassName}.class);

logger.error("Error while trying to get tracking point definitions :" + 
template, e);

Bad: AgnUtils.logger().error("SQL­Error" + template, e)';
Very bad: System.err.println(“...”);
Even worse: System.out.println(“...”);

Optional: Log level INFO with time of entering and leaving of time critical methods, and log
level DEBUG for complex methods with input and output values.

Logging code usually consists of time-consuming String concatenations. If a log statement is


used in a piece of code which is executed quite often you should wrap it in an if clause
checking the log level first to save execution time. A simplified example:

Before:

for( int i = 0; i < 1000000; i++) {
    logger.debug( "i = " + i);
}

After:

for( int i = 0; i < 1000000; i++) {
    if(logger.isDebugEnabled()) {

© 2016 AGNITAS AG, public document 26 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

        logger.debug( "i = " + i);
    }
}

In the second case the debug method is only processed if the logger runs on DEBUG level (if
you use logger.info(): INFO level). This can save a serious amount of CPU time for all other log
levels!

For auditing reasons EMM/OpenEMM provides a log of all user activities which is accessible
directly via the GUI (user activity log). So, if you add new functionality for a user to create,
update or delete the properties of an important EMM object like a mailing, a mailing list or a
target group, make sure that any change by the user is written to the user activity log.

9.3 Exception Handling

Use exceptions only to handle errors (like missing resources) and really exceptional cases (like
too many requests to handle). Do not use exceptions to control the flow of execution.

Only catch exceptions that have been expected, do not use a global

catch (Exception e) {...} 

Throw exceptions as early as possible, but as late as needed. Examples: Check parameters
before executing a method, check parameters in the constructor before an invalid object is
generated.

If you can not handle an exception right now, log it, close open resources in the finally block
and propagate the exception to a higher level.

If a suitable kind of exception exists, use it. If not, create your own Exception class (hierarchy)
like

AgnTagException extends RuntimeException {...} 

Use checked exceptions for problems from the outside (like missing resources), and use
unchecked exceptions (runtime exceptions) for problems from within the software, i.e. errors
that should not have happened in the first place.

9.4 Security

Security is essential for web based applications. Please follow these few simple rules to
improve the security of EMM/OpenEMM:

 Never rely on any client-side validation and never trust input from a client. Always
screen input on the server side based on an internal whitelist, i.e. input which is not
explicitly defined as acceptable, will be rejected
 Always use prepared statements for database access to prevent SQL injections
 Parse input from a web frontend to filter out unwanted HTML code and to prevent XSS
injections (as an example have a look at method checkForHtmlTags() and its sub-
method getUnsafeHtmlTagNames() of class StrutsFormBase)
 Do not use direct object references, but use a reference ID instead to avoid references
to internal data like files or directories

9.5 Testing

© 2016 AGNITAS AG, public document 27 / 28


EMM/OpenEMM Code Design Guide 2.16.0_20170502

JUnit tests are mandatory for all methods of DAO layer classes and for all actions of all Struts
Action classes. Each test should check not only success cases but also error cases (for Struts
Actions especially those which report back a specific error message to the GUI).

9.6 Threads

If an action initiated by the user takes longer than a second, wrap the job to perform this
action in a separate worker thread. Use the Executor framework (EFW) of package
java.util.concurrent to implement this worker thread.

For example, to generate the list of recipients can take several seconds. Therefore, Action
class RecipientAction uses the Executor framework for ACTION_LIST. Here is the (simplified)
code used by RecipientAction to create and manage worker threads for ACTION_LIST:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
// name of action covered by worker thread of EFW
public static final String FUTURE_TASK = "GET_RECIPIENT_LIST";

// map to hold Futures (future wraps result of a worker
// thread and offers methods to manage this thread)
protected AbstractMap<String, Future> futureHolder;
// setter for Spring (DI via applicationContext.xml)
public void setFutureHolder(AbstractMap<String, Future> futureHolder) {
this.futureHolder = futureHolder;
}
// service class of EFW (executes worker threads imple­
// menting Callable interface, creates Future object)
protected ExecutorService executorService;
// setter for Spring (DI via applicationContext.xml)
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
public ActionForward execute(<list of parameters>) { 
// generates UID for each future
String key =  FUTURE_TASK+"@"+ req.getSession(false).getId();
// calls method to initiate new worker thread
Future recipientListFuture = getRecipientListFuture(<list of parameters>);
// writes Future object into futureHolder map
futureHolder.put(key, recipientListFuture);
// is worker thread finished?
if (futureHolder.containsKey(key) && futureHolder.get(key).isDone()) {
// reads Future of worker thread and writes it as
// request attribute (for use by JSP)
req.setAttribute("recipientList", futureHolder.get(key).get());
// remove finished Future from futureHolder map
futureHolder.remove(key);
}
}
Future getRecipientListFuture(<list of parameters>) {
// starts instance of class RecipientQueryWorker
// as new worker thread
Future future = service.submit(new RecipientQueryWorker(<list of parameters>));
// returns Future of worker thread (containing
// status and result if thread is finished)
return future;
}

© 2016 AGNITAS AG, public document 28 / 28

You might also like