Open EMMCode Design Guide
Open EMMCode Design Guide
0_20170502
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
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
1 Introduction
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.
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.
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.
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
prefixes: 3dbars, crm, report, ibm, siemens, ... Please consult with us first to check if your
prefix is already taken or reserved.
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
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.
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.
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.
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.
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
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;
}
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
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"));
<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:
<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.
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
Please see appendix A for a diagram of the Tiles structure in EMM/OpenEMM (legacy layout).
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.
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.
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.
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.
<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.
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>
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>
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:
/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).
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.
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:
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).
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
- org.agnitas.util.SFtpHelper
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
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;
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.
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.
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.
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
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.
String expireSuccess = configService.getValue(ConfigValue.ExpireSuccessMax,
companyID);
int expireSuccess = configService.getIntegerValue(ConfigValue.ExpireSuccessMax,
companyID);
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).
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.
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.
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.
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
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:
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
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:
mysqldump aCceQx hexblob routines triggers nodata u root p r
emm_<prefix>schema.sql [emm|openemm]
and
mysqldump aCceQx hexblob nocreateinfo u root p r emm_<prefix>
content.sql [emm|openemm]
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.
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.
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.
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
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
static methods
constructor(s) for new method (if needed)
getter and setter (as pairs, but only if a getter or setter is really needed)
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
The declared type of a new object should describe its purpose and not its implementation:
Map<String, String> translationTable = new HashMap<String, String>();
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.
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.
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.
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>
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".
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
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.
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.
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;
}
public void setMembers(List<Person> members) {
this.members = members;
}
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.
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.
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:
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.
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).
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”
We recommend
But we know that these metrics are not applicable for every case (like very long case
statements).
7 Usability
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.
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.
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> (<columndefinitions>) COMMENT '<text>';
ALTER TABLE ADD <tablename> ADD <columndefinition>;
MySQL/MariaDB colum-definition: <columnanme> <allcolumnproperties> COMMENT
'<text>';
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';
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.
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();
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();
}
}
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("SQLError" + 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.
Before:
for( int i = 0; i < 1000000; i++) {
logger.debug( "i = " + i);
}
After:
for( int i = 0; i < 1000000; i++) {
if(logger.isDebugEnabled()) {
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.
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
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;
}