Programming guide

Contents

General

The Cyclos code is internally developed as 3 base projects, cyclos3 for the service layer (service and entity definitions), cyclos3_impl for service implementation (dao, security, and service implementations) and cyclos3_web for the web layer (Struts actions and forms, JSPs, JavaScripts). Some additional projects are used internally, like cyclos3_standalone (that configures Jetty as a servlet container on the standalone packages) and cyclos3_build (contains the Ant build file that generates the downloadable packages).

Cyclos code conventions

All Java code should follow these code conventions:

Eclipse (from 3.3 Europa) now supports set a formatter and an option to cleanup the code every time a class is saved, which helps enforcing those conventions. For setting up see Environment setup.


Coding conventions for help pages

The help files are located in the ''cyclos3_web'' project, under pages > general > translation_files > helps, and are ordered by locale.

The helps are divided in modules or categories like advertisments.jsp or account management.jsp. Please do not add any new modules to this. The helps are used to build (automatically) manuals (admin and member) and they are used for the help pop-ups. The help pop-ups jump just to an anchor inside one of the help files. The head description is used for the general description of the function and is obligatory. The sub description is optional and only used for very large help files (like account management) that have sections that can be considered as categories themselves (e.g Currencies in Account management)

Setup of a help file:

text "where to find it?" is a header in itialics. Must be repeated for every subheader section, if more than one is available. Otherwise it will go right below the head description.

done in order to get the functionality working. Applies mostly only for admin, though in some cases it may also be relevant for members. The header is in italics. Must be repeated for every subheader section, if more than one is available. Otherwise it will go right below the head description.

Conventions for helps via the template

helptemplates.xml. You can import them via window - preferences - web and xml - jsp files - templates. Call the templates via code assist (ctrl-space, and the first few letters of their titles).

name description example
"Quotes" putting quotes around a term, usually to refer to a literal on the screen. Note that the "" are part of the name. "description"
HAdmin A text which should only be visible for admins <span class="admin">Text for admin only</span>
HBroker A text which should only be visible for brokers <span class="broker">Text for broker only</span>
HDividingHeader For headers making a main division in the page, but which are not linked to or classified via p-description tags. the "member activity results" header in the statistics help file
HExternal link a link to another file and subject in the help system. see <a href="${pagePrefix}${cursor}#anchorName"><u>payment filter</u></a>
HGH Glossary header The header for the start of the glossary, to be used at most once in a help file
HGlossary item help the start of an item in the glossary of terms in a help file <a name="median"></a><b>Median</b><p> The median bla bla blah ... more explanation <hr class='help'>
HHeader for help module the main header of the file, to be used at its start
HInternal link a link to another anchor INSIDE the same help file see <a href="#bla"><u>bla</u></a>
HMember A text which should only be visible for member <span class="member">Text for member only</span>
HOperator A text which should only be visible for operators <span class="operator">Text for operator only</span>
HPicture To display an icon. There are several icons available, like edit.gif, delete.gif, view.gif, etc. See the directory cyclos3_web\WEB_INF\images\system on your local cyclos install for an overview of all available icons. <img border="0" src="${images}/edit.gif" width="16" height="16">
HSubmodule Header help header for submodule in the new help system; the text is also to be processed into the project site descriptions
HUnknown link to be used during the development of the new help system, to link to other files in the help system which have not yet been written. Later on, after all files are written, you should perform a search for "unknownAnchor" and replace them all with real anchors. see <a href="${pagePrefix}payments#unknownAnchor"><u>payment filters</u></a >
HWindow Help Help window section. This is the content of one single help window. If two windows link to one and the same help window, you may copy/paste the anchor line (with the <a href.... so that it appears twice (with of course an adapted anchor) <a name="bla"></a> <h3>Bla's</h3> This window explains about bla's... <hr class="help">



Some more help file Conventions

"Menu: Account > Member payment"

Note that there are never parenthesis.

The "Submit" button

classes together, like this:

<span class="admin broker">Text for admin and broker only</span>

<span class="admin">
<li>bla bla
</span>

will go wrong because the span tag will be ignored, resulting in a list item still visible for members and brokers. In stead of this, use:


<li class="admin">bla bla


Links in help pages
Try to use links whenever this is helpful. You can use internal links (within the same help page) as follows:

<a href="#ads_results"><u>See ads results</u></a> (example of a link in the advertisements help page to an anchor ads_results)

<a href="${pagePrefix}groups#group_filters"><u>group filter</u></a> (example of a link from any help page to the anchor group_filers in the groups help page)

<a href="${pagePrefix}groups"><u>Groups</u></a> (example of a link from any help page to groups help page)

<img border="0" src="${images}/delete.gif" width="16" height="16"> (A link to an image)

Tips on the helps...

[^/][^u][^>]</a>

or

<a href=.*">[^<][^u]
<u>[^</]*</[^u]

Best practices

Use of Java Enumerations

Enumerations are widely used on Cyclos. Always when there are multiple possible values for a given attribute, and those values are static, an enumeration is used. Examples are: invoice direction (incoming or outgoing), advertisement status (active, permanent, scheduled or expired) are examples of enumerations. In order to reduce duplication, whenever enumerations are used on JSP pages (like options on a select input), the enum items are not hard-coded on the JSP file, but the enum items are stored by the Action on the request, and that collection is iterated to generate the content.

Exemple code for the Action class:

//Store all enum items, following the item declaration order, on the statusList request attribute
RequestHelper.storeEnum(request, Ad.Status.class, "statusList");
//Store only specific items, on a different order
request.setAttribute("statusList", Arrays.asList(Ad.Status.ACTIVE, Ad.Status.SCHEDULED));

Then, in order to use them on the JSP page, the default is to have a translation key with a common prefix and ending with the enum item. On the given example, ad.status.<ITEM>:

<html:select property="query(status)" styleClass="InputBoxEnabled">
    <html:option value="">
        <bean:message key="global.search.all"/>
    </html:option>
    <c:forEach var="status" items="${statusList}">
        <html:option value="${status}">
            <bean:message key="ad.status.${status}"/>
        </html:option>
    </c:forEach>
</html:select>

In order to persist enum items, the enum class must implement StringValuedEnum or IntValuedEnum. They both declare a method called getValue, which returns a String or int respectively. That value will be stored on the database. Also, a custom hibernate type will have to be declared on the hibernate mapping.

Enum class:

public static enum Status implements StringValuedEnum {
    PENDING("N"), CHECKED("C"), PROCESSED("P");

    private final String value;

    private Status(final String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

Hibernate mapping:

<property name="status" column="status" length="1" not-null="true">
    <type name="nl.strohalm.cyclos.utils.hibernate.StringValuedEnumType">
        <param name="enumClassName">nl.strohalm.cyclos.entities.accounts.external.ExternalTransfer$Status</param>
    </type>
</property>

Enumerations are also used to declare all relationships an entity has. Those enum classes must implement the Relationship interface, which has a getName method. Here's an example:

public static enum Relationships implements Relationship {
    CUSTOM_VALUES("customValues"), LOANS("loans"), MEMBERS("members");
    private final String name;

    private Relationships(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Lazy loading relationships

In older cyclos versions, none of the relationships between child - parent were lazy. For example: if loading a transfer, you also were loading the complete from and to account, their owners, the type, ... a lot of entities even if all we need is a single transfer field. So, in cyclos3, all relationships are lazy, which means: if we load a transfer, none of it's relationships will be loaded. If one tries to access the relationship, an exception will occur, unless you have explicitly fetched the property, which can be done using the FetchService.fetch method or by loading or searching entities passing which relationships will be fetched. Each entity has an enumeration with it's relationships, so only those really needed are read from the db.

It is also possible to fetch relationships recursively, for example, I want to load a transfer and show the username of the member who performed the payment. The transfer has a from account, which has a member, which has an user. This is done with the RelationshipHelper.nested method.

Here are some examples:

//Fetching a pre-existent instance
Member member = ...;
member = fetchService.fetch(member, 
    Element.Relationships.GROUP, 
    RelationshipHelper.nested(Member.Relationships.BROKER, Element.Relationships.USER));

//Loading an instance with fetched relationships
Ad ad = adService.load(10L, Ad.Relationships.OWNER, Ad.Relationships.CURRENCY);

//Searching entities with a fetch
MemberQuery query = new MemberQuery();
query.fetch(Element.Relationships.GROUP, Member.Relationships.BROKER);
query.setName("john");
List<Member> members = (List<Member>) elementService.search(query);

It is also possible to fetch collections on the same way, but it's not possible to recursively fetch relationships for elements in collections. Example:

//To retrieve a member's advertisements with their categories, I cannot use the nested, because ads is a collection
Member member = (Member) elementService.load(id, Member.Relationships.ADS);
//To fetch ad categories, the ads collection itself must be passed to the fetchService.fetch method
Collection<Ad> ads = fetchService.fetch(member.getAds(), Ad.Relationships.CATEGORY);

JavaScript

Including a JavaScript file

Cyclos provides it's own tag, <cyclos:script src="/path/to/script" /> which appends the Cyclos application version to the script path as a request variable, forcing browsers to not using cached files among different versions, which could lead in script errors.

Third party JavaScript libraries

The following external libraries are used on Cyclos:

JavaScript code conventions for Cyclos

<script src="<c:url value="/pages/reports/statistics/forms/statisticsForms.js"/>"></script>
// For each element with the "trAdmin" class, invoke Element.show or Element.hide, depending on the variable shouldShow
$$(".trAdmin").each(Element[shouldShow ? "show" : "hide"]);

// For each input "delete" class, set a pointer cursor and add another CSS class
$$("input.delete").each(function(input) {
    setPointer(input);
    input.addClassName("highlight");
});
<script>
    var removeConfirmation = "<cyclos:escapeJS><bean:message key="group.removeConfirmation"/></cyclos:escapeJS>";
    var groupName = "<cyclos:escapeJS>${group.name}</cyclos:escapeJS>";
</script>

Predefined class names

The head.js and layout.js files declares several CSS classes that may be used by controls and other elements to add behaviour automatically. They are:

Predefined JavaScript variables

Several variables are made available to every JavaScript files. Here are them:

Use JSP data in JavaScript files

The only JavaScript code used on JSP pages is data passed to the scripts. The <cyclos:escapeJS> tag is used to ensure the text written will be sanitized for JavaScript string, avoiding breaking the syntax. Examples:

//A resource bundle message
var confirmationMessage = "<cyclos:escapeJS><bean:message key="ad.remove.confirmation"/></cyclos:escapeJS>"

//An id (a number cannot break the syntax - no escapeJS tag)
var currentMemberId = "${member.id}";

//A description could contain line breaks, quotes or other data that would break the syntax - use the escapeJS tag
var groupDescription = "<cyclos:escapeJS>${group.description}</cyclos:escapeJS>";

//A collection of data to JavaScript
var transferTypes = [];
<c:forEach var="tt" items="${transferTypes}">
    transferTypes.push({id:${tt.id}, name:"<cyclos:escapeJS>${tt.name}</cyclos:escapeJS"});
</c:forEach>

Actions for result lists

The result lists often have actions, like remove, edit, view, check/uncheck all and so on. Normally, the Behavior library is used on the JavaScript file using CSS selectors and a function related function to apply behaviour to page elements, like these:

Behaviour.register({

//View details
'img.details': function(img) {
    setPointer(img);
    img.onclick = function() {
        self.location = pathPrefix + "/editExternalAccount?externalAccountId=" + img.getAttribute("externalAccountId");
    }
},
//Remove a record
'img.remove': function(img) {
    setPointer(img);
    img.onclick = function() {
    if (confirm(removeConfirmation)) {
        self.location = pathPrefix + "/removeExternalAccount?externalAccountId=" + img.getAttribute("externalAccountId");
    }
},
//Add a new record
'#newButton': function(button) {
    button.onclick = function() {
        self.location = pathPrefix + "/editExternalTransferType?account="+externalAccountId;
    }
},
//Check all rows
'#selectAllButton': function(button) {
    button.onclick = checkAll.bind(self, "adInterestsIds", true);
},
//Uncheck all rows
'#selectNoneButton': function(button) {
    button.onclick = checkAll.bind(self, "adInterestsIds", false);
}
});

The elements class name is used by Behaviour. Remember that an element can have several class names, separated by spaces. Also, many times an argument must be passed to the Behaviour function, like the record identifier. Here is an example for the above 'img.details' selector:

<img class="edit details" externalAccountId="${externalAccount.id}" src="<c:url value="/pages/images/edit.gif"/>" />

Hide & Show elements in pages

Prototype extensions are used to show or hide elements. Here are a few examples:

//Show all rows with a myClass class
$$('tr.myClass').each(Element.show);

//Show or hide rows with a myClass class, according to a flag
$$('tr.myClass').each(Element[willShow ? 'show' : 'hide']);

Pseudo inheritance in javascript

Though not needed much, sometimes we use a form of pseudo-inheritance in javascript. We do this in the statistics part. There is one common javascript file, which defines the functions for all the forms. Then, for each form there is an additional javascript file, which defines extra functions or variations on these common functions. If you define a function in the specific javascript file which has the same name as a function in the common javascript file, the common function will be overridden and is lost. You can still use the common function by using the following construct:

var superNameOfFunctionInCommonScriptFile = NameOfFunctionInCommonScriptFile;
NameOfFunctionInCommonScriptFile = function () {
   superNameOfFunctionInCommonScriptFile();
   additionalFunctionCalls();     
}

JSP

Read form elements

Default struts lets you read form elements whenever there is a property with getter (and maybe a setter). But cyclos forms don't have properties, except for query: The use this:

getQuery(String key)

and

setQuery(String key, Object value)

The way to read form elements into the jsp is by using this inside the jsp:

query(keyName)

Predefined variables

Several variables are made accessible through session or application scopes, so they can be easily accessed through JSP expression language, like ${varName}. Those variables are:

Menu items and permissions

The <cyclos:menu> tag generates a menu item and may be nested within another <cyclos:menu> tag to create sub-menus. It have an url attribute that is relative to the context root and a resource bundle key to retrieve the item label. Optionally, both 'module' and 'operation' attributes may be specified to ensure that only when the logged user has that permission granted, the menu item will be displayed. When a menu item has no sub-items (i.e., no permissions), the root menu item is not shown.

Here's an example:

<cyclos:menu key="menu.admin.bookkeeping">
    <cyclos:menu 
        url="/do/admin/overviewExternalAccounts" 
        key="menu.admin.bookkeeping.overview" 
        module="systemExternalAccounts" 
        operation="details" />  
</cyclos:menu>

Multi drop down

The Multi drop down is a Cyclos custom widget that combines a select box with check boxes, which is a convenient way to select multiple options occupying a small space on the page. There is a custom tag, <cyclos:multiDropDown that generates the control. It has a name (the checkboxes name) and several other options, as described on the tag library documentation.

Below is an example:

<cyclos:multiDropDown name="query(groupFilters)" emptyLabelKey="member.search.allGroupFilters">
    <c:forEach var="groupFilter" items="${groupFilters}">
        <cyclos:option value="${groupFilter.id}" text="${groupFilter.name}" />
    </c:forEach>
</cyclos:multiDropDown>

Result pagination

Normally, search pages, whose Action extends BaseQueryAction will store the search results on the request under a specific name. Assuming that name as members, the following code generates page navigation links:

<cyclos:pagination items="${members}"/>

It is very important that the action's databinder has a registered property for page parameters, which can be registered using a helper, like this:

binder.registerBinder("pageParameters", DataBinderHelper.pageBinder());

When that property is not registered on the data binder, navigating to other pages will have no effect, and the first page will be shown again.

Checking permissions

On many situations it is important to check whether a given permission is granted to the logged user, in order to determine whether a button will be displayed, for example. To do that, there is a function on the Cyclos tag library called cyclos:granted, which takes 2 parameter: module name and operation name. When the operation name is null, return true when there is at least 1 operation under the given module. An usage example would be:

<c:if test="${cyclos:granted('memberReports', 'showAccountInformation')}">
... do stuff
</c:if>

Toggle row color in list

Every table that shows results use 'zebra' rows, alternating row colors. To do that, Cyclos use the toggle tag library, and rows are created using:

<tr class="<t:toggle>ClassColor1|ClassColor2</t:toggle>">
...
</tr>


Controls

Data binding

General info about DataBinders

The general idea of data binders is: a form is submitted, containing a bunch of strings and some basic types, but we need to convert internationalized data (dates/numbers), entities, nested entities, collections... This is done by the DataBinders.

There are several DataBinder implementations:

The PropertyBinder uses a Converter to convert from/to objects and strings. There are several converters out-of-the-box, like:

Reading/writing JavaBeans from/to the form

In order to read or write an entire object from or to a form, a BeanBinder is used. Tipically, the Action class will have an instance variable called that will be initialized upon the first use, on a getDataBinder method. To create the BeanBinder, a factory method can be used, and then properties should be declared using the registerProperty method.

For a matter of consistency, there is a BaseBindingForm class that declares a protected Map<String, Object> called values. In order to use nice names on the jsp, and not values, child forms declares 4 methods, according to the object it is editing. Assuming a form to edit a person, those methods would be: getPerson() - returning the whole Map, getPerson(key) - returning the value associated with that key, setPerson(Map) - setting the whole map and setPerson(key, value) - setting a specific key's value. The 4 methods are necessary because on the page, the Struts tags will interpret properties as mapped properties, on this example, person(key), and we will need to read the Map as a whole in order to do the data binding. Examples ahead.

Given a JavaBean called Person, with an id, name, birthDate, income and a boolean flag called active, this would be a data binder example on the action class:

private DataBinder<Person> dataBinder = null;

private DataBinder<Person> getDataBinder() {
    if (dataBinder == null) {
        LocalSettings settings = SettingsHelper.getLocalSettings(getServlet().getServletContext());

        BeanBinder<Person> binder = BeanBinder.instance(Person.class);
        binder.registerBinder("id", PropertyBinder.instance(Long.class, "id", IdConverter.instance()));
        binder.registerBinder("name", PropertyBinder.instance(String.class, "name"));
        binder.registerBinder("birthDate", PropertyBinder.instance(Calendar.class, "birthDate", settings.getDateConverter()));
        binder.registerBinder("price", PropertyBinder.instance(BigDecimal.class, "price", settings.getNumberConverter()));
        binder.registerBinder("active", PropertyBinder.instance(Boolean.TYPE, "active"));

        dataBinder = binder;
    }
}

The form object would be something like this:

public class PersonForm extends BaseBindingForm {
    public Map<String, Object> getPerson() { 
        return values;
    }

    public Object getPerson(String key) {
        return values.get(key);
    }

    public void setPerson(Map<String, Object> map) {
        values = map;
    }

    public void setPerson(String key, Object value) {
        values.put(key, value);
    }
}

The JSP file, the form (without layout or field labels) would be something like:

<ssl:form method="POST" action="${formAction}">
    <html:hidden property="person(id)"/>
    <html:text property="person(name)" styleClass="large"/>
    <html:text property="person(birthDate)" styleClass="small date"/>
    <html:text property="person(income)" styleClass="small float"/>
    <html:checkbox property="person(active)" styleClass="large"/>
</ssl:form>

Then, to read the person object, the code on action is:

PersonForm form = context.getForm();
Person person = getDataBinder().readFromString(form.getPerson());

Before editing, the person could be set on the form like this

PersonForm form = context.getForm();
Person person = ...;
getDataBinder().writeAsString(form.getPerson(), person);
DataBinders and nested JavaBeans

Now lets assume that the previous Person has an Address, which is not an empty, but a nested JavaBean (Hibernate calls it component). It is possible to register a BeanBinder inside another BeanBinder instance. An example would be:

BeanBinder<Address> addressBinder = BeanBinder.instance(Address.class);
addressBinder.registerBinder("street", PropertyBinder.instance(String.class, "street"));
addressBinder.registerBinder("number", PropertyBinder.instance(Integer.class, "number"));
addressBinder.registerBinder("city", PropertyBinder.instance(String.class, "city"));

BeanBinder<Person> personBinder = ...
personBinder.registerBinder("address", addressBinder);

Then, for the binding process, a nested object must exist on the form, so Struts can set the nested value. A nested MapBean must be created on the form constructor, or an error will occur on form submit. The MapBean is a class that Struts sees as a dynamic bean, with properties declared dynamically. The MapBean constructor receives the property names it must respond to. Here is an example:

public PersonForm() {
    setPerson("address", new MapBean("street", "number", "city"));
}

Then, at the jsp page, the fields can be used by separating the component from it's properties with a period:

<html:text property="person(address).street" styleClass="large" />
<html:text property="person(address).number" styleClass="small number" />
<html:text property="person(address).city" styleClass="large" />
DataBinders and collections of values

The SimpleCollectionBinder transforms string collections (or arrays) in object collections or object collections into string collections. In order to use it, a hack must be done on the Form, or Struts will set the first element only. In order to Struts handle the property as a collection, an empty collection must be on the form.

Following the previous Person example, let's assume that the person class now have a collection of Interests that the user selects using a multi drop down component.

The following code should be set on the form constructor. This is VERY important!!!:

public PersonForm() {
    setPerson("interests", Collections.emptyList());
}

The data binder should have the property registered. Remember that when no converter is specified, a coercion converter is used. For entity types, the entity is converted from / to it's identifier as string, making it easy to read several entity references from an array of strings, like:

BeanBinder<Person> binder = BeanBinder.instance(Person.class);
...
binder.registerBinder("interests", SimpleCollectionBinder.instance(Interest.class, "interests"));

Then, on the JSP, the collection is built by creating several fields with the same name. In case of the multi drop down, several checkboxes with the same name are generated. Here is an example, assuming the action sets a collection as a request attribute called 'interestList'. It is also assumed that the 'person' instance is set under that name on the request. Also note the cyclos:contains tag that tests whether a item is contained within a collection:

<cyclos:multiDropDown name="person(interests)" emptyLabelKey="person.pleaseSelectInterests">
    <c:forEach var="interest" items="${interestList}">
        <cyclos:option value="${interest.id}" text="${interest.name}" selected="${cyclos:contains(person.interests, interest)}"/>
    </c:forEach>
</cyclos:multiDropDown>
DataBinders and collections of beans

The BeanCollectionBinder mixes the ideas of both SimpleCollectionBinder and BeanBinder to create collections of JavaBeans. It reads the form data by reading a string array for each property and then, positionally, assemble a JavaBean with all properties and adding it to a returned collection.

Still following the previous example, now we are going to add a collection of Preferences for the person, assuming the preference have an id, a name and a value.

First, the form must respond to the nested object, and again we will use the MapBean. But now, as the properties are collections and no longer atomic values, the first constructor argument of the MapBean should be set to true, indicating that it's a holding collections. Here is the code that should be placed on the form constructor:

public PersonForm() {
    setPerson("preferences", new MapBean(true, "id", "name", "value"));
}

Then, the DataBinder code on the action would have this added to it:

BeanBinder<Person> binder = BeanBinder.instance(Person.class);
...
BeanBinder<Preference> preferenceBinder = BeanBinder.instance(Preference.class);
preferenceBinder.registerBinder("id", PropertyBinder.instance(Long.class, "id", IdConverter.instance()));
preferenceBinder.registerBinder("name", PropertyBinder.instance(String.class, "name"));
preferenceBinder.registerBinder("value", PropertyBinder.instance(String.class, "value"));
binder.registerBinder("preferences", new BeanCollectionBinder(preferenceBinder, "preferences"));


Initialize empty Collections

Struts can only set nested beans if they exist on the form. so, take a look on the constructor of forms that use nested beans normally a Map stores the properties (so we don't need to declare each one) that map is on the BaseBindingForm it's overriden with a proper name, like, BaseQueryForm defines a getQuery, setQuery... but, the constructor should set an empty nested bean there's a class called MapBean, that receives a list of accepted properties if you don't do that, struts will throw an exception saying the nested bean doesn't exist collections have a true before the properties on the MapBean constructor you may check the EditLocalSettingsForm

Validating forms

Form validation is not done in javascript, but coordinated via the action.

In the action...

To validate a form, you have to override BaseQueryAction.validateForm or BaseFormAction.validateForm. The body of the method would typically look like this:

protected void validateForm(final ActionContext context) {
   //get the form
   final StatisticsFinancesForm form = context.getForm();    
   //get the object to validate
   final Object paymentFilterIds = form.getQuery("paymentFilters");
   //do the validation
   statisticalService.validate(paymentFilterIds);
}

As you can see, it is the Service which is responsible for the validation itself.

In the Service...

In the Service implementation, a validate method would typically look like this:

private Validator validator = null;

public void validate(final LoanGroup loanGroup) {
    getValidator().validate(loanGroup);
}

private Validator getValidator() {
    if (validator == null) {
        validator = new Validator("loanGroup");
        validator.property("name").required().maxLength(100);
        validator.property("description").required().maxLength(1000);
        validator.property("fictional").key("global.fictional").required();
        validator.property("fictional2").displayName("Fictional II").required();
    }
    return validator;
}

The semantics for the validator are:

The following property validations are included out of the box:

Other validations may be included as well, using the add method. The validation should implement PropertyValidation and return a subclass of ValidationError when an error exist or null when the property is valid.

It is also property to have general validations (not tied to an specific property). The Validator.general method register a general validation that should implement GeneralValidation.

Calling validation from javascript

You call the validation of the form from the javascript file belonging to the form, for example via an onclick of the submit button, or in a construction like this:

form.onsubmit = function() {
    return requestValidation(form);
}

Http filters

Transaction Filter

The transaction filter applies the open session in view pattern, allowing the whole request to use a single database transaction.

Encoding Filter

The servlet specification defines that the HttpServletRequest.setEncoding() method must be called before parsing the request parameters. This filter sets it according to the character encoding defined on the Cyclos configuration.

Logged User Filter

Filter used to verify if the user is logged and to disconnect the user after the time out time configured in the system (settings - access settings)

Web Services White List

Applies the 'whitelist' for web services access, allowing specific hosts to access the services

Action context & helpers

Resource bundle messages from control

The action classes may access a message translation using the ActionContext.message method, optionally passing arbitrary arguments, for example:

String memberName = context.message("member.name");
//If my.message is 'I am {0}, hello {1}', the output will be 'I am John, hello Joe'
String anotherMessage = context.message("my.message", "John", "Joe");
Redirect pages (forward with arguments)

Since Cyclos use the redirect after post pattern, sending a redirect after any form submission (to avoid an action to be re-executed when the member reloads the browser), and Struts forwards does not support parameters, Cyclos use a custom api to send a redirect passing request parameters. The ActionHelper class declare the redirectWithParam and redirectWithParams methods. The former receives a parameter name and value, and the latter a Map<String, Object> with the parameters. Here is an example:

// A single parameter named 'memberId'
ActionHelper.redirectWithParam(request, forward, "memberId", 10);

// Several parameters
Map<String, Object> params = new HashMap<String, Object>();
params.put("memberId", 6);
params.put("groupId", 10);
params.put("return", true);
ActionHelper.redirectWithParams(request, forward, params);
Error handling

The BaseAction class handles some common exceptions, like ValidationException and PermissionDeniedException. Other exceptions are caught to generate an application error log.

Specific business exceptions should be handled by the actions, like:

Transfer transfer;
try {
    transfer = doPayment(context, payment);
} catch (final NotEnoughCreditsException e) {
    return context.sendError("payment.error.enoughCredits");
} catch (final MaxAmountPerDayExceededException e) {
    return context.sendError("payment.error.maxAmountOnDayExceeded");
} catch (final UnexpectedEntityException e) {
    return context.sendError("payment.error.invalidTransferType");
} catch (final UpperCreditLimitReachedException e) {
    return context.sendError("payment.error.upperCreditLimit");
}
Retrieve settings

Use the SettingsHelper static methods getLocalSettings, getAccessSettings and so on, passing the request or servlet context.

Send messages to JSP

When an action is performed, normally a JavaScript alert is shown to the user to confirm the action. To do this, an action must send a message to the next page, using:

context.sendMessage("member.profile.changed");

Service Layer

Creating new permissions

In order to create a new permission, you should add a new enum item to the corresponding class:

Also, check out the subclasses of GroupPermissionsDTO for more details on how to apply the permissions themselves.

Security layer

There are 2 implementations for each service interface: one for enforcing security and another one for the actual business implementation. The security layer contains a reference to the local service interface (which is only implemented by the actual service implementation). Each implemented method should check the applicable permissions and finally return the value returned by the service implementation.

The security implementations are located on the same packages as their corresponding service implementations, and use extensively the PermissionService.permission(...) method.

Data access layer

BaseDAO methods

The BaseDAOImpl class, from which all other DAO implementation classes extend, provide several methods for it's implementations, like:

HQL query helper

The HibernateHelper class offers several method to help building an HQL query, based on a StringBuilder to build the query string and a Map containing the named parameters.

Here are some:

Example:

//Assuming an entity called Person, here is the PersonDAOImpl
public List<Person> search(PersonQuery query) {
    Map<String, Object> namedParameters = new HashMap<String, Object>();
    StringBuilder hql = HibernateHelper.getInitialQuery(Person.class, "p", query.getFetch());
    HibernateHelper.addLikeParameterToQuery(hql, namedParameters, "p.name", query.getName());
    HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "p.income", ">=", query.getMinimumIncome());
    HibernateHelper.addParameterToQuery(hql, namedParameters, "p.type", query.getType());
    HibernateHelper.addInParameterToQuery(hql, namedParameters, "p.group", query.getGroups());
    HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "p.creationDate", query.getCreationPeriod());
    HibernateHelper.appendOrder(hql, "p.name");
    return list(query, hql.toString(), namedParameters);
}

Steps to create a functionality

There are several steps to add functionality, because it involves changing and configuring all application layers and configuration files.

Here there is a bottom-up listing of steps:

Entities

Normally new functionality will require new persistent entities or, at least, changes to existing entities. The package they're created is a subpackage of nl.strohalm.cyclos.entities. On either case, the ['http://www.hibernate.org hibernate] mapping should be created / modified, and new mapping files should be declared on the persistence.xml file.

Entities always extend the Entity class, which declares the identifier (always a generated Long) and other methods, like equals and hashCode (both use the identifier).

There also should be an enumeration representing the relationships, as explained here.

Here is an example:

public class ExternalAccount extends Entity {

    public static enum Relationships implements Relationship {
        MEMBER_ACCOUNT_TYPE("memberAccountType"),  SYSTEM_ACCOUNT_TYPE("systemAccountType"), 
        FILE_MAPPING("fileMapping"), TYPES("types"), IMPORTS("imports");
        
        private final String name;
        private Relationships(final String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }

    private static final long                  serialVersionUID = -3694123388080600042L;
    private String                             name;
    private MemberAccountType                  memberAccountType;
    private Collection<ExternalTransferType>   types;
    
    @Override
    public String toString() {
        return getId() + " - " + getName();
    }

    // Omitting getters and setters
}

The entity should also be mapped on a Hibernate xml file. Here is the example for the previous ExternalAccount class, which is called ExternalAccount.hbm.xml:

<hibernate-mapping>
    <class name="nl.strohalm.cyclos.entities.accounts.external.ExternalAccount" table="external_accounts">
    <cache usage="read-write"/>
    <id name="id" type="long">
        <column name="id" sql-type="integer"/>
        <generator class="native"/>
    </id>
    <property name="name" column="name" type="string" length="50" not-null="true" />
    <property name="description" column="description" type="text" />
    <many-to-one name="memberAccountType" class="nl.strohalm.cyclos.entities.accounts.MemberAccountType">
        <column name="member_account_id" index="fk_member_account" not-null="true" sql-type="integer"/>
    </many-to-one>
</hibernate-mapping>

The entity mapping also should be declared on the persistence.xml file, like this:

<bean id="sessionFactory" class="nl.strohalm.cyclos.spring.CustomSessionFactoryBean">
    <property name="mappingResources">
        <list>
            ...
            <value>/nl/strohalm/cyclos/entities/accounts/external/ExternalAccount.hbm.xml</value>
            ...
        </list>
    </property>
</bean>

Entity changes almost always lead to database changes. These have to be applied also. Don't forget to enter any changes in the changelog.xml file as follows:

<version label="3.6_dev2">
    <statements database="mysql">
        <item>alter table account_status add column d_rate decimal(15,6), [continued sql]</item>
        <item>[more valid sql statements]</item>
    </statements>
</version>

A good tip for entering or changing entities in *.hbm.xml file and in the database is the following recipe:

Before entering them into the changelog.xml, you may want to check your sql statements on syntax errors first against a test database in the mysql console.

DAOs

The database access layer is implemented on Cyclos using DAO's. There is always the separation between the interface and the implementation, but both are on nl.strohalm.cyclos.dao.** package. Other classes that will use the DAO should always reference the interface. The implementation will only be referenced on the dao.xml configuration file.

All DAO interfaces should extend BaseDAO<EntityType> and since not all entities allow all CRUD operations (for example, a remark can only be inserted, others may not be modified, others can't be removed), individual operations are declared on separate interfaces. Here is an example:

public interface ExternalAccountDAO extends BaseDAO<ExternalAccount>, InsertableDAO<ExternalAccount>, 
UpdatableDAO<ExternalAccount>, DeletableDAO<ExternalAccount> {

    /*Lists all external accounts, ordering results by name*/
    List<ExternalAccount> listAll();

    /*External Account Details, ordering results by name*/
    List<ExternalAccountDetailsVO> listExternalAccountOverview();
}

The DAO implementations should extend BaseDAOImpl<EntityType> and implement the specific DAO interface. Another requirement is that the constructor should invoke the super constructor with the specific entity class. Here is an implementation example:

public class ExternalAccountDAOImpl extends BaseDAOImpl<ExternalAccount> implements ExternalAccountDAO {

    public ExternalAccountDAOImpl() {
        super(ExternalAccount.class);
    }

    public List<ExternalAccount> listAll() {
        return list("from ExternalAccount ea order by ea.name", null);
    }

    public List<ExternalAccountDetailsVO> listExternalAccountOverview() {
        final StringBuilder hql = new StringBuilder();
        hql.append(" select new " + ExternalAccountDetailsVO.class.getName() + "(ea.id, ea.name, sum(t.amount))");
        hql.append(" from ExternalAccount ea left join ea.types et left join et.transfers t ");
        hql.append(" group by ea.id, ea.name");
        hql.append(" order by ea.name");
        final List<ExternalAccountDetailsVO> result = list(hql.toString(), null);
        return result;
    }
}

Finally, the DAO implementation should be declared on the dao.xml file as a bean. For historical reasons, the bean is suffixed with Dao and not DAO, so, services that will reference the DAO should have a setter that ends with Dao. Here is the bean definition for the externalAccountDao bean:

<bean id="externalAccountDao" class="nl.strohalm.cyclos.dao.accounts.external.ExternalAccountDAOImpl" />

Services

The service layer is the responsible for executing the business logic and security checks. There are 2 interfaces: the "remote", which contains methods used by the web layer and the "local" interface, used by other services. Also, there are 2 implementations: the security layer and the service implementation. The security layer has a reference to the local interface (service implementation), and is responsible to enforce the permission for the logged user to invoke the current method, then delegates the method execution to the actual service implementation.

The web layer will always (indirectly) reference the security layer. The direct implementations will be hidden from it. So, after correctly enforcing the security, a real service implementation is invoked. Then, subsequent flow will no longer check permissions, as services must reference other services using the local interface, which no longer checks for security. This is important, because, for example, a regular member is not allowed to view transfer types. However, he needs a list of transfer types in order to do a payment. So, the payment service should have a method to list transfer types, checking for the payment permission.

Each class of the security layer must extend BaseServiceSecurity. However, there is no base class for the business implementation.

Permissions

As said before, permissions are declared on the Permissions class. New modules / operations should be added there.

Also, the translation keys for the new permissions should be created on the translation file. For modules, they are called permission.<module name>, and for operations, permission.<module name>.<operation name>.

Struts actions

The actions are, together with the forms, the MVC controls used in Cyclos. They are used to prepare data to be displayed and to pass data from the JSP pages to the service layer.

A normal CRUD will be composed of 3 actions:

There is a struts-config-*.xml file for each functionality group. These files are declared on the web.xml file. Actions are mapped on different paths according to the user type. Administrator actions are mapped on <root>/do/admin/<action>, member actions on <root>/do/member/<action> and operator actions on <root>/do/operator/<action>.

Also, since Struts actions are not within the Spring context (which would require all actions to be also mapped on a beans.xml file), a mechanism is necessary to retrieve beans, such as services. In order to do this, the actions should have a setter for a property with the same name as the Spring bean, and it should be annotated with the @Inject annotation. Since Cyclos extends the default Struts request processor, it sets all dependencies on the action creation.

Actions often use data binders to read data from the user input.

Here is an example for listing external accounts:

public class ListExternalTransferTypesAction extends BaseAction {

    private ExternalTransferTypeService externalTransferTypeService;

    @Inject
    public void setExternalTransferTypeService(final ExternalTransferTypeService externalTransferTypeService) {
        this.externalTransferTypeService = externalTransferTypeService;
    }

    @Override
    protected ActionForward executeAction(final ActionContext context) throws Exception {
        final HttpServletRequest request = context.getRequest();
        final ExternalAccount externalAccount = (ExternalAccount) request.getAttribute("externalAccount");
        final List<ExternalTransferType> externalTransferTypes = externalTransferTypeService.listByAccount(externalAccount);
        request.setAttribute("externalTransferTypes", externalTransferTypes);
        request.setAttribute("editable", getPermissionService().checkPermission("systemExternalAccounts", "manage"));
        return context.getInputForward();
    }
}

The struts-config_externalAccounts.xml should also be mapped on the web.xml file, like this:

<!-- Standard Action Servlet Configuration -->
<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>
            /WEB-INF/struts-configs/struts-config.xml,
            ...
            /WEB-INF/struts-configs/struts-config_externalAccounts.xml,
            ...
        </param-value>
    </init-param>
    ...
</servlet>

Here is the mapping for the given action:

<action-mappings type="org.apache.struts.config.SecureActionConfig">
    <action 
	path="/admin/listExternalAccounts" 
	type="nl.strohalm.cyclos.controls.accounts.external.ListExternalAccountsAction"
        input="admin/_listExternalAccounts">
	<set-property property="secure" value="true" />
    </action>
    ...
</action-mappings>

As a general case, actions with a form should have a scope="request", unless some search actions, whose form may keep the value between calls, where the scope should be session (the Struts default). Also note the input attribute, which points to a path with an underscore. It is a Tiles definition. In the Cyclos standards, all tiles definitions have an underscore before the name.

Forms

You should create a form for actions that require an user input, or even parameters that are passed as URL parameters. The form is declared on the struts-config_*.xml file and it's class is placed on the same package as the action.

JSPs

Cyclos JSP's use several tag libraries: the Java Standard Tag Library (JSTL), the Struts tags, the toggle taglib and a custom Cyclos taglib.

As a normal directive, the page will have as less javascript code as possible, and the JSTL tags are used to control the page generation.

Here is an example for a JSP:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags/struts-html" prefix="html" %>
<%@ taglib uri="http://devel.cyclos.org/tlibs/cyclos-core" prefix="cyclos" %>
<%@ taglib uri="http://www.servletsuite.com/servlets/toggletag" prefix="t" %> 
<%@ taglib uri="http://sslext.sf.net/tags/sslext" prefix="ssl" %>

<script src="<c:url value="/pages/groups/editGroup.js"/>"></script>
<c:set var="titleKey" value="group.title.new"/>
<c:set var="helpPage" value="${isMember ? 'insert_operator_group' : 'insert_group'}"/>

<ssl:form action="${formAction}" method="post">
<html:hidden property="groupId" />
<c:if test="${isOperatorGroup}">
	<html:hidden property="group(nature)" value="OPERATOR"/>
</c:if>
<table class="defaultTableContent" cellspacing="0" cellpadding="0">
    <tr>
        <td class="tdHeaderTable"><bean:message key="${titleKey}"/></td>
        <cyclos:help page="${helpPage}" />
    </tr>
    <tr></tr>
</table>

Pages are mapped on the tiles-def_*.xml file, which have the same suffix as the corresponding struts-config_*.xml file. The tiles-def is declared on the global struts-config.xml:

<plug-in className="org.apache.struts.tiles.TilesPlugin">
    <set-property property="definitions-config" value="
        ...
        /WEB-INF/tiles-defs/tiles-defs_externalPayment.xml
    " />
    ...
</plug-in>

Here is a definition:

<definition name="admin/_listConnectedUsers" extends=".adminLayout">
  <put name="body" value="/pages/access/listConnectedUsers.jsp"/>
</definition

As well as actions, there is a separate tiles definition for each user type, extending a specific layout. They are: .adminLayout, .memberLayout, .brokerLayout and .operatorLayout.

Javascripts

Each page includes by default several javascript libraries. They also should include an specific javascript for that page, which should apply the behavior for the page elements, using the Behaviour library.

Translation files

All text on Cyclos is written on external (translation files, which enables internationalization through properties files.

The keys are organized hierarchically using dots, like accountFee.groups, menu.member.account and so on.

We use an eclipse plugin called Jinto to edit the translation files.

Change Log

The changelog.xml file is used to read the Cyclos versions and to apply automatic database schema upgrade. If a new version contains new database tables or columns, then one or more statements should be added to a new version. Here is an example:

<?xml version="1.0"?>
...
<version label="3.1_dev9">
    <statements database="mysql">
        <item>alter table transfers add external_transfer_id integer</item>
        <item>create index fk_external_transfer on transfers (external_transfer_id)</item>
        <item>alter table transfers add index FK3EBE45E8617A8174 (external_transfer_id), 
add constraint FK3EBE45E8617A8174 foreign key (external_transfer_id) references external_transfers (id)</item>
        <item>alter table loan_payments add external_transfer_id integer</item>
        <item>create index fk_external_transfer on loan_payments (external_transfer_id)</item>
        <item>alter table loan_payments add index FKAF53099C617A8174 (external_transfer_id), 
add constraint FKAF53099C617A8174 foreign key (external_transfer_id) references external_transfers (id)</item>
</statements>
</version>

<version label="3.1_dev8">
    ....
</version>
...

The next time the Cyclos application is started, automatically the given statements will be executed, upgrading the database schema.

The changelog.xml file is also used to generate a textual changelog, allowing other elements than statements to be declared, such as description, bug-fixes, enhancements and so on.