Difference between revisions of "Custom operations"

From Complete Cyclos documentation wiki
Jump to: navigation, search
(Error codes)
(Spring configuration)
Line 569: Line 569:
==Spring configuration==
==Spring configuration==
Se require anexar configuración a Spring, necesaria para instanciar los objetos de la operación personalizada (comando y manejador de errores).  
A configuration is required to be supplied to Spring, for the instantiation of objects for the personalized operation (command and error handler). Spring needs to instantiate on one side the bean correspoding to the proper personalized command, its configuration would be similar to the following:
Spring debe instanciar por una parte el bean correspondiente al comando personalizado propiamente dicho, su configuracion sera similar a la siguiente:
  <bean id="'''<IdCommandBean>'''"
  <bean id="'''<IdCommandBean>'''"
Line 583: Line 582:
Es importante detenerse en el valor '''<commandID>''' ya que el mismo será el identificador de referencia para la configuración del comando en el ''config.xml'' (ver [[Setup & Installation#Cyclos_instances | Cyclos instances]]->commands->name)
The value of <code><commandID></code> is quite important, as this is the ID referenced for the configuration of the command in the <code>config.xml</code> (see [[Setup & Installation#Cyclos_instances | Cyclos instances]]->commands->name)
Por otra parte tambien es necesario instanciar un bean para el manejador de errores del comando personalido, su configuración sera similar a la siguiente:
Also, it is important to instantiate a bean for the error handling of the personalized command, its configuration would be similar to the following:
  <bean id="'''<IdErrorHandlerBean>'''"  
  <bean id="'''<IdErrorHandlerBean>'''"  
Line 594: Line 593:
===Configuración de Spring en el ejemplo===
===Spring configuration in the example===
En nuestro comando personalido de ejemplo hemos puesto este archivo en el paquete /nl/strohalm/cyclos/controller/command/minfo/spring
In our personalized command we put this file into the package <code< /nl/strohalm/cyclos/controller/command/minfo/spring</code>
  <?xml version="1.0" encoding="UTF-8"?>
  <?xml version="1.0" encoding="UTF-8"?>

Revision as of 19:12, 20 February 2012

Documentation under construction

The SMS module has been developed for the Cyclos software, as explained in the page Architecture. It is however possible to create 'custom commands' (operations). There can be two main reasons who you would want to create custom commands:

  1. You have your own payment system and want to offer similar SMS services and do not want to develop an SMS module from scratch.
  2. You use Cyclos with the SMS module but need some additional functionality. The custom command can either connect to Cyclos or/and third party software. For example, one organization enhanced the authentication by SMS with external GPS checking. (In the example below the custom command connects to Cyclos for simplicity reasons)

This page will describe in detail how to create custom commands.

Note: In order to develop custom operations you will need solid knowledge of SMS messaging, Java, Cyclos, and the SMS module.



The code used with the examples can be downloaded at the section: Example code.

The example command permits retrieving information from other members registered in Cyclos. In the example you can search for a profile field of a specific member. This is just as example and might not be of use for live systems.

The format of the command consists of four words divided by spaces:

  1. Alias of the command. In the example the word MINFO is used
  2. PIN the PIN of the user that is sending the command
  3. Member This is the user that you want to retrieve the profile information from. Depending on the configuration this can be the Cyclos username or mobile phone number. See Installation steps. In the example the mobile phone number is used.
  4. Custom profile field - This is the profile field you want to retrieve from the member. This parameter is optional. If not given the address will be returned.

Operation flow

When the Driver will receive a message from the number 098654321 with message text Minfo 7410 099123456 the following will happen:

  1. The command MINFO will be invoked. Which will in its turn:
    1. Verify if the number 098654321 belongs to a valid member in Cyclos
    2. Verify if the credentials (PIN) of the member with phone number 098654321 is indeed "7410"
    3. Fetch the profile information of the member which mobilePhone value is "099123456"
    4. Generate a message to mobile phone number 098654321 with text address for 099123456: Minas 1486 (the address is retrieved because no custom field argument is passed in the initial message)

Development of custom command

In oder to create a custom command the following steps need to be done:

  1. Create a Command class that will contain the required functionality.
  2. Create a Error handling class that can handle (and notify if necessary) the possible errors generated during the execution of the command.
  3. Define the Spring configuration that will instantiate the command and auxiliary components.
  4. SMS module configuration define the custom command to be included.

The error handling can be implemented with the following classes:

  1. Error codes: Permits adding new error codes that will be handled by the Error handling class.
  2. Custom exception class: The exception that will be thrown from the custom command. It can use the Error codes and provide information about the environment and error. This is the class that is passed as an argument to the Error handling class.

Packages dependencies & deploy

The development interface will need the following libraries (all available in the distribution AIO of the SMS module):

  • controller-driver-xx.xx.xx.jar
  • controller-xx.xx.xx.jar
  • cyclos_3.6_client.jar (Web Service interfaces for Cyclos)

Example code

Command class

The command class will need to extend the class nl.strohalm.cyclos.controller.command.Command which is available in the library controller-xx.xx.xx.jar.

Note: The command object is a SINGLETON within a the scope of the cyclosInstance ( see Spring_configuration). Because of the fact that the class is singleton it cannot contain any variables that can change their value when receiving method calls (with the exception of the SET methods that can be used during the initialization of the controller, (see also Spring_configuration).

If you want to send or receive SMS messages from a command class please refer to the section Send SMS messages. Make sure the methods of the command class are implemented correctly (see next section)

Command class methods


protected void executeCommand(String[] commandParameters, DriverMessage driverMessage, CyclosInstanceConfig cyclosInstance)

  • Implementation required.

This method will be called by the Controller when a method is received which first word(s) match with one of the aliases configured in the configuration of the custom command. The method will receive the following parameters:

  • commandParameters - The words (aliases) received that define the command, (e.g. 'pay').
  • driverMessage - All data concerning the received message (message text, origin and destination number, Driver identification, indentificador de traza??, etc.)
  • cyclosInstance - The configuration of the cyclos instance that processes the command (permitting access to internationalization keys, Web Service clients etc.).


protected String[] getRequiredI18NKeys()

  • Implementation required.

This method will return the internationalization keys that are related to the operation command. For the implementataiton the following points are important:

  • Internationalization keys command.<command_name>.regularExpression, command.memberNotRegistered, and error.template are used for all commands and therefore it is not necessary to specify them in this list.
  • If you want the build-in Help command to return keys you will need to define the keys command.<command_name>.help.withConf, and command.<command_name>.help.withoutConf.
  • If you want to allow the option to enable the Security confirmation for the command you will have to define the key: command.<command_name>.confirmation.
  • More details about internationalization keys can be found at [#Anexos_a_cyclosInstance.properties|Anexos a cyclosInstance.properties]].??


public Set<Integer> getErrorCodes(CodeSupportedError error) -

  • Implementation required.

When an CodeSupportedError is generated it will return all possible error codes that were generated by it. It will return one of the error codes defined in Enumerado de códigos de error (TODO:change link) when it receives the value CodeSupportedError.CUSTOM_COMMAND_ERROR as parameter.


public String[] getHelpParameters(CyclosInstanceConfig cyclosInstance)

  • Implementation required.

Returns a list of parameters which can be used to substitute in the internationalization the keys command.<command_name>.help.withConf, y command.<command_name>.help.withoutConf (for more details please see Operations translation keys). Generally, the first element of the returned list is the shortest alias to the command.


  • public boolean isUsePin().
  • Implementation optional.

Defines if the command requires a PIN. PIN is enabled by default. The following points need to be considered.

  1. If PIN is used it will need to be first parameter after the command.
  2. If both PIN and OTP (Security confirmation) are enabled (see cyclosInstance -> commands -> requestConfirmation) the PIN will not be required in the original message.


protected CommandConfigParameter[] getRequiredConfigParameters()

  • Implementation optional.

Allows to define extra parameters required for the execution of a command. In case the extra parameters, or configured parameters in Config.xml file are missing, the SMS module will not be initialized, indicating the missing parameter as error cause.

Command class example

MemberInfoCommand (source)

package nl.strohalm.cyclos.controller.command.minfo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import nl.strohalm.cyclos.controller.command.Command;
import nl.strohalm.cyclos.controller.command.CommandConfigParameter;
import nl.strohalm.cyclos.controller.config.CyclosInstanceConfig;
import nl.strohalm.cyclos.controller.exception.CodeSupportedError;
import nl.strohalm.cyclos.controller.exception.CommandException;
import nl.strohalm.cyclos.driver.DriverMessage;
import nl.strohalm.cyclos.webservices.access.CheckCredentialsParameters;
import nl.strohalm.cyclos.webservices.access.CredentialsStatus;
import nl.strohalm.cyclos.webservices.members.MemberResultPage;
import nl.strohalm.cyclos.webservices.members.MemberSearchParameters;
import nl.strohalm.cyclos.webservices.model.FieldValueVO;
import nl.strohalm.cyclos.webservices.model.MemberVO;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

 * Text message syntax: COMMAND <PIN> <memberPrincipal> [<customField>]
 * Response with the Cyclos member memberPrincipal customField value
public class MemberInfoCommand extends Command {
    private static final Log logger   = LogFactory.getLog(MemberInfoCommand.class); 

    static final String      SMS_TYPE = "MEMBER_INFO";

    static {
        logger.info("****** MEMBER INFO SAMPLE CUSTOM COMMAND. VERSION: 1.0 ******");

     * @return the code assigned to a (supported) built-in error.
    private static Integer getCode(final CodeSupportedError error) {
        switch (error) {
                return 901;
            case UNKNOWN_COMMAND_ERROR:
                return 902;
                return 903;
                return 904;
                return null;

     * All returned codes must exist as error.<ERROR_TYPE>.code key in 
     * errors__<language>_<country>_<variant>.properties file
     * @see  nl.strohalm.cyclos.controller.exception.IErrorCodeDescriptor#getErrorCodes(nl.strohalm.cyclos.controller.exception.CodeSupportedError)
    public Set<Integer> getErrorCodes(final CodeSupportedError error) {
        if (CodeSupportedError.CUSTOM_COMMAND_ERROR == error) {
            final Set<Integer> codes = new HashSet<Integer>();
            for (final MemberInfoCommandErrorCode customCode : MemberInfoCommandErrorCode.values()) {
            return codes;
        } else {
            final Integer code = getCode(error);
            if (code != null) {
                return Collections.singleton(code);
            } else {
                return Collections.emptySet();

    public String[] getHelpParameters(final CyclosInstanceConfig cyclosInstance) {
        return new String[] { getShortestAlias(cyclosInstance) };

     * Command implementation
     * @see nl.strohalm.cyclos.controller.command.Command#executeCommand(java.lang.String[], nl.strohalm.cyclos.driver.DriverMessage,
     * nl.strohalm.cyclos.controller.config.CyclosInstanceConfig)
    protected void executeCommand(final String[] commandParameters, final DriverMessage message, final CyclosInstanceConfig cyclosInstance) {
        commandHelper.checkParametersCount(getCode(CodeSupportedError.INVALID_PARAMETERS_COUNT), commandParameters.length, 2, 3);
        checkMemberCredential(cyclosInstance, message.getMessage().getFrom(), commandParameters[0]);
        String customFieldToReturn =  getConfig(cyclosInstance.getName()).getParameter(MemberInfoCommandConfigParameter.DEFAULT_CUSTOM_FIELD_NAME);
        if (commandParameters.length > 2) {
            // we receive a specific custom field name (do not use the default)
            customFieldToReturn = commandParameters[2];

        final String customFieldValue = memberCustomFieldValue(commandParameters[1], customFieldToReturn, cyclosInstance); 

        final String[] msgTextParam = new String[] { commandParameters[1], customFieldToReturn, customFieldValue };
        facade.asyncNotifyToMember(message.getMessage().getFrom(), message.getMessage().getTraceData(), cyclosInstance, SMS_TYPE,  "command." + name + ".response", msgTextParam);

    protected CommandConfigParameter[] getRequiredConfigParameters() {
        return MemberInfoCommandConfigParameter.values();

     * The specified key must exist in cyclosInstance_<language>_<country>_<variant>.properties file
     * @see nl.strohalm.cyclos.controller.command.Command#getRequiredI18NKeys()
    protected String[] getRequiredI18NKeys() {
        return new String[] {
                "command." + name + ".help.withConf", // response to help message if you are using requestConfirmation
                "command." + name + ".help.withoutConf", // response to help message if you are not using requestConfirmation
                "command." + name + ".response", // message to response this command
                "command." + name + ".confirmation" // message to response this command

     * Check if given credentials are valid for CyclosInstance Member with phoneCustonFields equals originatorPhone.
     * if not, then exit trough CommandException or IllegalStateException
     * @throws CommandException, IllegalStateException
    private void checkMemberCredential(final CyclosInstanceConfig cyclosInstance, final String originatorPhone, final String credentials) {
        CodeSupportedError errorType;
        final String searchByPrincipalType = cyclosInstance.getMemberSettings().getPhoneCustomField();
        final CheckCredentialsParameters params = new CheckCredentialsParameters();
        final CredentialsStatus status = cyclosWsManager.checkCredentials(cyclosInstance.getName(), params);
        switch (status) {
            case VALID: // ok
            case BLOCKED:
                errorType = CodeSupportedError.ORIGINATE_MEMBER_BLOCKED_CREDENTIALS;
                throw new CommandException(errorType, getCode(errorType), String.format("The credentials are BLOCKED for member  (originatePhone: %1$s).", originatorPhone));
            case INVALID:
                // check if member is registered (is not good idea give this detail, but we are making a command example)
                final MemberSearchParameters mSearchParam = new MemberSearchParameters();
                final List<FieldValueVO> searchFields = new ArrayList<FieldValueVO>();
                searchFields.add(new FieldValueVO(searchByPrincipalType, originatorPhone));
                final MemberResultPage searchResult = cyclosWsManager.searchMember(cyclosInstance.getName(), mSearchParam);
                if (searchResult.getTotalCount() == 0) {
                    throw new CommandException(NoCodedError.ORIGINATE_MEMBER_NOT_FOUND, String.format("Member (originatePhone:  %1$s) not found.", originatorPhone));
                } else {
                    // the member exist then the problem is the credential
                    errorType = CodeSupportedError.ORIGINATE_MEMBER_INVALID_CREDENTIALS;
                    throw new CommandException(errorType, getCode(errorType), String.format("The credentials are INVALID for member (originatePhone: %1$s).", originatorPhone));
                throw new IllegalStateException(String.format("Unsupported credentials status result: %1$s", status));

     * @return the customFieldToReturn value for member with principal
     * @throws MemberInfoCommandException
    private String memberCustomFieldValue(final String principal, final String customFieldToRetun, final CyclosInstanceConfig  cyclosInstance) {
        final String msgParamPrincipalType = cyclosInstance.getMemberSettings().getMsgParamPrincipalType();
        final MemberSearchParameters memberSearchParam = new MemberSearchParameters();
        final FieldValueVO fieldValue = new FieldValueVO(msgParamPrincipalType, principal);
        final List<FieldValueVO> fieldValuesVO = new ArrayList<FieldValueVO>();
        final MemberResultPage result = cyclosWsManager.searchMember(cyclosInstance.getName(), memberSearchParam); 

        if (CollectionUtils.isNotEmpty(result.getMembers())) {
            final MemberVO member = result.getMembers().get(0);
            final FieldValueVO clientCustomFieldValue = (FieldValueVO) CollectionUtils.find(member.getFields(), new Predicate() {
                public boolean evaluate(final Object arg0) {
                    final FieldValueVO fieldValue = (FieldValueVO) arg0;
                    return fieldValue.getField().equals(customFieldToRetun);

            if (clientCustomFieldValue == null) {
                // (check if the custom field is hidden)
                final String exceptionMessage = "Member was found but it doesn't have a custom field with name: " +  customFieldToRetun + ". Member principal: " + principal;
                throw new MemberInfoCommandException(MemberInfoCommandErrorCode.CUSTOM_FIELD_NOT_FOUND, exceptionMessage);
            return clientCustomFieldValue.getValue();
        } else { // it does not find a member
            final String exceptionMessage = "Can not find a member for " + msgParamPrincipalType + ": " + principal;
            throw new MemberInfoCommandException(MemberInfoCommandErrorCode.MEMBER_NOT_FOUND, exceptionMessage);


SMS_TYPE constant

This constant defines the type of error code. See getErrorSmsTypeCode.

MemberInfoCommand.executeCommand method

During the execution process of the example command the following steps will be performed.

  1. Validation of the SMS text (will have to correspond with an alias of the command and its parameters): commandHelper.checkParametersCount()
  2. Validate if the sending member is registered and check authentification: checkMemberCredential()
  3. Obtener el custom field por defecto a utilizar de la configuracion (config.xml). ??, what arugment in config.xml what custom field? mobile phone? what configuration??
  4. Verify the parameter that identifies the custom field.
  5. Retrieve the value of the custom field memberCustomFieldValue()
  6. Generate and send response message: facade.asyncNotifyToMember()

MemberInfoCommand.getRequiredI18NKeys method

For the MemberInfo operations the following keys are required in the file: cyclosInstance_en_US_MINFO.properties:

  1. command.mInfo.regularExpression
  2. command.mInfo.help.withConf
  3. command.mInfo.help.withoutConf
  4. command.mInfo.response
  5. command.mInfo.confirmation

The command.mInfo.regularExpression key defines the command para identificar los mensajes de texto que hacen referencia a la operación de comando.?? La clave command.<commandName>.regularExpression es exigida por el controllador para todos los comandos definidos en su configuración.??

Desde el segundo punto en adelante (?? please be specific) se especifican claves puntuales de este comando que (al igual que .regularExpression) será obligatorio definir en los archivos de internacionalización.

If during the initialization process some keys are missing in the file cyclosInstance_en_US_MINFO.properties the SMS module will not start and generate an error indicating what key is missing.

Error handling class

Every command has an (obligatory) error handling class that extends the class nl.strohalm.cyclos.controller.command.handler.CommandErrorHandler. The error handling class will handle errors (normally sending notifications) when exceptions in the class CommandException of the executed command occur.

Note: The command object is a SINGLETON within a the scope of the cyclosInstance ( see Spring_configuration). Because of the fact that the class is singleton it cannot contain any variables that can change their value when receiving method calls (with the exception of the SET methods that can be used during the initialization of the controller, (see also Spring_configuration).

There are two types of errorHandler exceptions:

  • Exceptions generated by a particular message parameter.
  • Exceptions generated by the command because of specific (non desired) events within the command flow.

Error class handling methods


protected void handleException(final CommandException e, final CyclosInstanceConfig cyclosInstance)

  • Implementation optional.

This method will be called every time the associated command generates an exception of the type Clase CommandException (and not the standard exceptions that are handled by the handleException(CommandException) classs). The doHandleException method will be responsible to take necessary error handling actions. Generally this action will be to send a notification to the user indicating that the operation could not be processed and informing an error code. But the method could take other actions like a call to an external application. When sending a notification the text will be retrieved from an internationalization key, see Error translation keys.

If a Clase de exepción personalizada) is used , se tiene el error?? CodeSupportedError.CUSTOM_COMMAND_ERROR, en consecuencia las claves de internacionalización de las cuales se toman los mensajes de respuesta tienen el siguiente formato:

  • error.CUSTOM_COMMAND_ERROR.<codeNumber>

CodeNumber is defined when the exception thrown, that means it is defined by the command that was trying to be executed.

doHandleException parameters
  • CommandException this parameter is the very exception (see Clase de exepción personalizada)
    • String[] commandParameters contains the words (parameters) that make up the text (excluding the alias of the command).
    • Command command - this object represents the command that has been executed.
  • CyclosInstanceConfig cyclosInstance - contains the configuration parameters of the cyclos instance of the command that was executed.


public void handleException(final CommandException e, final CyclosInstanceConfig cyclosInstance)

  • Implementation optional

This method is called before the doHandleException is called, it only handles exceptions of the type NoCodedError.ORIGINATE_MEMBER_NOT_FOUND or NoCodedError.ORIGINATE_MEMBER_INVALID_CHANNEL. These errors do not need to generate a response message and are just used for logging.

In the case you want full control over the errors you can implement Clase CommandException in stead of doHandleException.


public void handleException(final CommandParameterException e, final CyclosInstanceConfig cyclosInstance)

  • Implementation optional

This method has a similar behavior as handleException(CommandException) but related to instance exceptions (class CommandParameterException).

The exception CommandParameterException extends the class Clase CommandException and is launched when a command generates a validation error related to wrong/missing paremetros. This exception will provide information about the position of the parameter that caused the exception.


protected String getErrorSmsTypeCode()

  • Implementation optional

This method returns the message code type (see Nuevo SmsType).

Send SMS messages

It is possible to send messages from a CommandErrorHandler with the following methods:

  • Sending message via Cyclos
  • Sending message directly via the Driver.
    • If it is required to send messages to unregistered members you can use:
    • facade.notifyErrorToDriver
    • facade.sendMsgToDriver

The same methods can be used to send response message from a Clase de comando.

Note: If a message is sent directly via de Driver it will not be logged in Cyclos. This means that it won't be possible to charge a member for these messages.

Error handling class of the example

Source MemberInfoCommandErrorHandler

package nl.strohalm.cyclos.controller.command.minfo;

import nl.strohalm.cyclos.controller.command.handler.CommandErrorHandler;
import nl.strohalm.cyclos.controller.config.CyclosInstanceConfig;
import nl.strohalm.cyclos.controller.exception.CommandException;

import org.apache.commons.lang.ArrayUtils;

public class MemberInfoCommandErrorHandler extends CommandErrorHandler {

     * Custom the handler for CodeSupportedError.CUSTOM_COMMAND_ERROR
     * @see nl.strohalm.cyclos.controller.command.handler.CommandErrorHandler#doHandleException(nl.strohalm.cyclos.controller.exception.CommandException,
     * nl.strohalm.cyclos.controller.config.CyclosInstanceConfig)
    protected void doHandleException(final CommandException e, final CyclosInstanceConfig cyclosInstance) {
        if (e instanceof MemberInfoCommandException) {
            // customize the way to process a CUSTOM_COMMAND_ERROR type
            String msgParam = null;
            if (e.getErrorCode() == MemberInfoCommandErrorCode.MEMBER_NOT_FOUND.code()) {
                msgParam = e.getCommandParameters()[1]; // the member principal value (given in text message)
            } else if (e.getErrorCode() == MemberInfoCommandErrorCode.CUSTOM_FIELD_NOT_FOUND.code()) {
                msgParam = e.getCommandParameters()[2]; // the principal customField (given in text message or configured as  default)
            facade.notifyErrorToMember(e.getDriverMessage(), cyclosInstance, getErrorSmsTypeCode(), e.getError(), e.getErrorCode(), msgParam == null ? ArrayUtils.EMPTY_STRING_ARRAY : new String[] { msgParam });
        } else {
            super.doHandleException(e, cyclosInstance);

    protected String getErrorSmsTypeCode() {
        return MemberInfoCommand.SMS_TYPE + "_ERROR";

MemberInfoCommandErrorHandler.doHandleException method

Se observa?? la utilización de la Clase de exepción personalizada (MemberInfoCommandException) para poder acceder a la información extra que esta aporta. En especial se muestra el manejo de dos códigos de error propios del comando customizado (ver Enumerado de códigos de error):

  • MemberInfoCommandErrorCode.MEMBER_NOT_FOUND
  • MemberInfoCommandErrorCode.CUSTOM_FIELD_NOT_FOUND

Custom exception class

A custom exception class will need to extend a CommandException. The custom class is responsible to pass the necessary information to the Clase de manejo de errores. Ademas de los datos ya existentes en una CommandException, esta?? clase puede incluir información extra referente al comando para el cual es construida.



The command class exception passes information about the error during the execution of the command (from the point where the command is launched to the error handling that processes it):

  • DriverMessage driverMessage objeto que representa el mensaje causante de la ejecución del comando. Posee datos de:
    • Identificador del driver a través del cual fue recibido
    • el número telefónico del móvil que lo enviado
    • el número al cual fue enviado
    • el texto completo del mensaje
  • String[] commandParameters un array de string conteniendo las distintas palabras del texto mensaj, excluyendo el alias de comando (representa los parámetros del comando)
  • Command command - un objeto representando el comando que se ha ejecutado.

Clase de exepción personalizada en el ejemplo

Source MemberInfoCommandException

package nl.strohalm.cyclos.controller.command.minfo;

import nl.strohalm.cyclos.controller.exception.CodeSupportedError;
import nl.strohalm.cyclos.controller.exception.CommandException;

 * Base command exception for Member info custom command errors
public class MemberInfoCommandException extends CommandException { 

    private static final long          serialVersionUID = 1L;
    private MemberInfoCommandErrorCode errorCode;

    public MemberInfoCommandException(final MemberInfoCommandErrorCode errorCode) {
        this(errorCode, null, null);

    public MemberInfoCommandException(final MemberInfoCommandErrorCode errorCode, final String detailedMessage) {
        this(errorCode, detailedMessage, null);

    public MemberInfoCommandException(final MemberInfoCommandErrorCode errorCode, final String detailedMessage, final Throwable  cause) {
        super(CodeSupportedError.CUSTOM_COMMAND_ERROR, errorCode.code(), detailedMessage, cause);
        this.errorCode = errorCode;

    public MemberInfoCommandException(final MemberInfoCommandErrorCode errorCode, final Throwable cause) {
        this(errorCode, null, cause);

    public MemberInfoCommandErrorCode getCustomCommandErrorCode() {
        return errorCode;

Error codes

Generally, the new personalized command requires the handling of specific error codes (which are not contained in the classnl.strohalm.cyclos.controller.exception.CodeSupportedError) in a particular form in the Error handler.

A command which can be triggered from a personalized command is CodeSupportedError.CUSTOM_COMMAND_ERROR, but as many error codes as needed can be created. Make sure to use enumerates. An enumeration of error codes is a pragmatic way of keeping error codes mapped to an enumerated constant.

Enumeration of error codes in the example

Source MemberInfoCommandErrorCode
package nl.strohalm.cyclos.controller.command.minfo;

 * The followings are the codes assigned to the CodeSupportedError.CUSTOM_COMMAND_ERROR
* The intent of codifying the custom command error is to differentiate each internal error
* and allow the handler work properly. This command doesn't use the codes in the same way as the built-in commands does. * */ public enum MemberInfoCommandErrorCode { CUSTOM_FIELD_NOT_FOUND(910), // The member does not have a custom field with specified name (or default name) MEMBER_NOT_FOUND(911); // Can not find (on Cyclos) a member with msgPrincipalType value received (in text message) public static MemberInfoCommandErrorCode error(final Integer code) { if (code == null) { return null; } for (final MemberInfoCommandErrorCode error : values()) { if (error.code == code) { return error; } } throw new IllegalArgumentException("Can't retrieve CustomCommandErrorCode. Unknown integer code: " + code); } private int code; private MemberInfoCommandErrorCode(final int code) { this.code = code; } public int code() { return code; } }

Spring configuration

A configuration is required to be supplied to Spring, for the instantiation of objects for the personalized operation (command and error handler). Spring needs to instantiate on one side the bean correspoding to the proper personalized command, its configuration would be similar to the following:

<bean id="<IdCommandBean>"
	class="<path to command class>">
	<property name="facade" ref="facade"/>
	<property name="langManager" ref="langManager"/>
	<property name="sessionHandler" ref="sessionHandler"/>
	<property name="name" value="<commandID>"/>
	<property name="errorHandler" ref="<IdErrorHandlerBean>"/>
	<property name="logUtils" ref="logUtils"/>
	<property name="commandHelper" ref="commandHelper"/>

The value of <commandID> is quite important, as this is the ID referenced for the configuration of the command in the config.xml (see Cyclos instances->commands->name)

Also, it is important to instantiate a bean for the error handling of the personalized command, its configuration would be similar to the following:

<bean id="<IdErrorHandlerBean>" 
	class="<path to Error handler class>">
	<property name="facade" ref="facade"/>
	<property name="command" ref="<IdCommandBean>"/>
	<property name="logUtils" ref="logUtils"/>

Spring configuration in the example


In our personalized command we put this file into the package <code< /nl/strohalm/cyclos/controller/command/minfo/spring

<?xml version="1.0" encoding="UTF-8"?>

<beans	default-autowire="byName"
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="mInfo"
		<property name="facade" ref="facade"/>
		<property name="langManager" ref="langManager"/>
		<property name="sessionHandler" ref="sessionHandler"/>
		<property name="name" value="mInfo"/>
		<property name="errorHandler" ref="mInfoErrorHandler"/>
		<property name="logUtils" ref="logUtils"/>
		<property name="commandHelper" ref="commandHelper"/>
	<bean id="mInfoErrorHandler"
		<property name="facade" ref="facade"/>
		<property name="command" ref="mInfo"/>
		<property name="logUtils" ref="logUtils"/>

SMS module configuration


El archivo /WebContent/WEB-INF/web.xml dentro del proyecto AIO debe ser modificado de forma tal que agregue la ruta al archivo XML de spring que instanciará los beans correspondientes al comando peronalizado.

El archivo web.xml debe agregar un texto similar al siguiente:

<web-app .....>

web.xml en el ejemplo

Hemos montado nuestro comando personalizado de ejemplo sobre un AIO Gateway (ver Gateway http), por tanto nuestro web.xml ha quedado de la siguiente forma:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 
  <display-name>Cyclos SMS Module (All-in-One: Http)</display-name>
    <display-name>Security Constraint</display-name>
      <web-resource-name>Restricted Area</web-resource-name>

    <realm-name>SMS Administrator</realm-name>
    <display-name>Access Control Filter</display-name>
      <param-value>/restricted/monitor, /restricted/login.jsp, /restricted/logout.jsp, /restricted/dwr</param-value>

Anexos a config.xml

El archivo de configuracion del controller WebContent/WEB-INF/classes/config.xml fue modificado para soportar el comando customizado desarrollado. Se debe cosiderar dos pequeñas secciones:

  1. El atributo variant para el elemento cyclosInstance - Este cambio permite agregar las customizaciones necesarias del comando en el archivo Webcontent/WEB-INF/classes/i18n/cyclosInstance_en_US_MINFO sin necesidad de afectar el archivo de internacionalización de ingles incluido en la distribución original.
  2. Se agregó un elemento <command ...> - Esta es la configuración del comando peronalizado con su atributo, se observa el parámetro defaultCustomFieldName siendo su valor el campo personalizado por defecto que se utilizará.

config.xml en el ejemplo

<?xml version="1.0" encoding="UTF-8"?>
<controller language="es" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="config.xsd">
    <cyclosInstance name="cyclos" language="en" country="US" variant="MINFO">
        <currency symbol="units" default="true">
          <alias name="U"/>
          <alias name="Un"/>
          <alias name="Unit"/>
          <alias name="Units"/>
		<command name="confirm" requestConfirmation="false"/>
		<command name="requestPayment" requestConfirmation="false"/>
		<command name="accountDetails" requestConfirmation="false">
			<parameter name="nameLen" value="0"/>
			<parameter name="pageSize" value="3"/>
		<command name="performPayment" requestConfirmation="false"/>
		<command name="help" requestConfirmation="false"/>
		<command name="registration" requestConfirmation="false">
                   <parameter name="notifyErrorByDriver" value="false"/>
	 	    <parameter name="useLoginName" value="true"/>
        	<parameter name="defaultInitialGroup" value="6"/>
        	<parameter name="groupPrefix" value="."/>
        	<paramGroup name="groupAliases">
            	<parameter name="full" value="5"/>
            	<parameter name="new" value="6"/>
		<command name="infoText" requestConfirmation="false"/>
		<command name="mInfo" requestConfirmation="false"> 
			<parameter name="defaultCustomFieldName" value="address" />
        <route fromProvider="*" toDriver="aioDriver" usedFromNumber="9999" default="true"/>
      <connectionSettings rootUrl="http://localhost:8080/cyclos" disableCNCheck="true" connectionTimeout="120000" readTimeout="120000" trustAllCert="true"/> 
      <memberSettings phoneCustomField="mobilePhone" msgParamPrincipalType="mobilePhone" providerCustomField="provider"  notifyNotRegisteredUser="false">
      	<principalSettings regexp="^(\d){7,9}$"/>
    <driverInstance name="aioDriver">
        <route fromTargetNumber="9999" toCyclos="cyclos"/>        
      <connectionSettings connectionTimeout="120000" readTimeout="120000" disableCNCheck="true" trustAllCert="true"/>
 <globalSettings responseInvalidMessages="false"/>
  <databaseSettings username="root" password="">
  <sessionSettings removeExpiredDelayInSeconds="120">
  	<control timeoutInSeconds="300" maxWrongTries="10"/>  		
  	<confirmation timeoutInSeconds="300" maxWrongTries="3" maxWrongPinTries="4" useKeyFromDictionaryFirst="true" keyLength="4" pendingsByCommand="3"/>

Anexos?? a cyclosInstance.properties

Los textos de mensajes (o componentes de textos de mensajes) que utilizará el comando se internacionalizan mediante el archivo cyclosInstance.properties.

Deben especificarse en este archivo (o archivos) todas las claves definidas en el método Command. getRequiredI18NKeys.

cyclosInstance.properties en el ejemplo


Para nuestro ejemplo se internacionalizan las siguientes claves:

  • Claves generales de comando
    • command.mInfo.regularExpression
  • Claves especificadas en MemberInfocommand.getRequiredI18NKeys()
    • command.minfo.response
    • command.mInfo.confirmation
    • command.mInfo.help.withConf
    • command.mInfo.help.withoutConf
  • Para ajuste de la ayuda general del sistema se personaliza la clave:
    • command.help.help
command.help.help=For information about SMS operations. Send a message with the word help followed by one of the following operation commands\: acinfo, minfo, pay, rq, reg, info.
command.mInfo.response={1} for {0}: {2}
command.mInfo.help.withoutConf=Member info command. Send a message with\: {0} pin principal [field_name]
command.mInfo.help.withConf=Member info command. Send a message with\: {0} principal [field_name]
command.mInfo.confirmation=Please confirm request for member info\: {0}

Anexos a error.properties

Los textos de errores (o componentes de textos de errores) que el comando notificara se internacionalizan mediante el archivo errors.properties.

Deben especificarse en este archivo (o archivos) todas las claves con codigos que pueden ser lanzadas durante el intento de ejecución de la operación personalizada.

errors.properties en el ejemplo

Por mayor detalle, ver las secciones Clase de manejo de errores y Clase de exepción personalizada.


Además de los errores codificados específicos que se lanzan en el comando MINFO se ha personalizado también la clave error.COMMAND_NOT_FOUND para el caso de que se utilice la variante de configuración notifyNotRegisteredUser="true" (ver Cyclos instances->memberSettings->notifyNotRegisteredUser).

error.COMMAND_NOT_FOUND=Invalid command. Send a message with the word help followed by one of the following\: acinfo, minfo, pay, rq, reg, info.
error.CUSTOM_COMMAND_ERROR.910=Can not find the custom field {0}, or field value is empty. Member info command failed 
error.CUSTOM_COMMAND_ERROR.911=Can not find the member for principal {0}. Member info command failed
error.INVALID_PARAMETERS_COUNT.901=Invalid message format. Member info command failed.
error.ORIGINATE_MEMBER_BLOCKED_CREDENTIALS.903=Your password is blocked. Member info command failed.
error.ORIGINATE_MEMBER_INVALID_CREDENTIALS.904=Invalid password. Member info command failed.
error.UNKNOWN_COMMAND_ERROR.902=Unknown error processing message. Member info command failed.

Configuraciones necesarias en Cyclos

Nuevo SmsType

A fin de que cyclos pueda clasificar los mensajes MT que el sistema envía (hacia un teléfono móvil) se deben realizar las siguientes configuraciones en Cyclos:

  • Agregar manualmente en la tabla sms_types de la base de datos de cyclos, los registros necesarios que reflejen los tipos de mensajes de texto que serán enviados por el sistema (en relacion al comando personalizado).
  • Configurar la internacionalizacion para los tipos creados en base de datos. En cyclos->adminMenu->Translation->Aplication definir los textos para las claves:
    • sms.type.MEMBER_INFO.description
    • sms.type.MEMBER_INFO_ERROR.description
  • Agregar registro en tabla cyclos3.sms_types
  • Definir claves en AdminMenu->Transaction->Applications->key=sms.types

SmsType en el ejemplo

Para soportar nuestro ejemplo se han ejecutados las siguientes instrucciones sobre la base de datos de Cyclos:

  • insert into sms_types set id="13", code="MEMBER_INFO", order_index="12"
  • insert into sms_types set id="14", code="MEMBER_INFO_ERROR", order_index="13"

Por otra parte se ha accedido a cyclos->adminMenu->Translation->Aplication y se han definido las siguientes claves:

  • sms.type.MEMBER_INFO.description - Member info
  • sms.type.MEMBER_INFO_ERROR.description - Member info error