Custom operations

From Complete Cyclos documentation wiki
Jump to: navigation, search

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.


Contents

Example

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 controller context 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

TODO En el ambiente de desarrollo necesitará las siguientes librerias (todas ellas disponibles en una distribucion del módulo AIO):

  • 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 controller context 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 controller context configuration).

If you want to send SMS messages (notifications or responses) 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

executeCommand

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

  • Implementation required.

This method will be called by the Controller when a message 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.).

getRequiredI18NKeys

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 Translation page.

getErrorCodes

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 Error codes enum (TODO:change link) when it receives the value CodeSupportedError.CUSTOM_COMMAND_ERROR as parameter.

getHelpParameters

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.

isUsePin

  • 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 [[Setup#Cyclos_instances | cyclosInstance] -> commands -> requestConfirmation) the PIN will not be required in the original message.

getRequiredConfigParameters

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.controller.exception.NoCodedError;
import nl.strohalm.cyclos.controller.validation.ParamValidator;
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 principal [customField] 
* This command allows retrieve information related to a member's custom field */ 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. */ static Integer getErrorCode(final CodeSupportedError error) { switch (error) { case INVALID_PARAMETERS_COUNT: return 901; case UNKNOWN_COMMAND_ERROR: return 902; case ORIGINATE_MEMBER_BLOCKED_CREDENTIALS: return 903; case ORIGINATE_MEMBER_INVALID_CREDENTIALS: return 904; default: 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) */ @Override public Set<Integer> getErrorCodes(final CodeSupportedError error) { if (CodeSupportedError.CUSTOM_COMMAND_ERROR == error) { final Set<Integer> codes = new HashSet<Integer>(); for (final MemberInfoErrorCode customCode : MemberInfoErrorCode.values()) { codes.add(customCode.code()); } return codes; } else { final Integer code = getErrorCode(error); if (code != null) { return Collections.singleton(code); } else { return Collections.emptySet(); } } } @Override 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) */ @Override protected void executeCommand(final String[] params, final DriverMessage message, final CyclosInstanceConfig cyclosInstance) { final ParamValidator<MemberInfoParameter> validator = validate(params, message, cyclosInstance); final String pin = validator.getValue(MemberInfoParameter.PIN); final String principal = validator.getValue(MemberInfoParameter.PRINCIPAL); final String customFieldName = validator.getValue(MemberInfoParameter.CUSTOM_FIELD_NAME); checkMemberCredential(cyclosInstance, message.getMessage().getFrom(), pin); final String customFieldValue = getCustomFieldValue(principal, customFieldName, cyclosInstance); final String[] msgTextParam = new String[] { principal, customFieldName, customFieldValue }; facade.asyncNotifyToMember(message.getMessage().getFrom(), message.getMessage().getTraceData(), cyclosInstance, SMS_TYPE, "command." + name + ".response", msgTextParam); } @Override protected CommandConfigParameter[] getRequiredConfigParameters() { return MemberInfoConfigParameter.values(); } /** * The specified key must exist in cyclosInstance_<language>_<country>_<variant>.properties file * @see nl.strohalm.cyclos.controller.command.Command#getRequiredI18NKeys() */ @Override 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(); params.setPrincipalType(searchByPrincipalType); params.setPrincipal(originatorPhone); params.setCredentials(credentials); final CredentialsStatus status = cyclosWsManager.checkCredentials(cyclosInstance.getName(), params); switch (status) { case VALID: // ok break; case BLOCKED: errorType = CodeSupportedError.ORIGINATE_MEMBER_BLOCKED_CREDENTIALS; throw new CommandException(errorType, getErrorCode(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)); mSearchParam.setFields(searchFields); 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, getErrorCode(errorType), String.format("The credentials are INVALID for member (originatePhone: %1$s).", originatorPhone)); } default: throw new IllegalStateException(String.format("Unsupported credentials status result: %1$s", status)); } } /** * @return the customFieldToReturn value for member with principal * @throws MemberInfoException */ private String getCustomFieldValue(final String principal, final String customFieldName, final CyclosInstanceConfig cyclosInstance) { final String msgParamPrincipalType = cyclosInstance.getMemberSettings().getMsgParamPrincipalType(); final MemberSearchParameters memberSearchParam = new MemberSearchParameters(); memberSearchParam.setShowCustomFields(true); final FieldValueVO fieldValue = new FieldValueVO(msgParamPrincipalType, principal); memberSearchParam.setFields(Collections.singletonList(fieldValue)); 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() { @Override public boolean evaluate(final Object arg0) { final FieldValueVO fieldValue = (FieldValueVO) arg0; return fieldValue.getField().equals(customFieldName); } }); if (clientCustomFieldValue == null) { final String exceptionMessage = "Member was found but it doesn't have a custom field with name: " + customFieldName + " or the custom field value is empty. Member principal: " + principal; throw new MemberInfoException(MemberInfoErrorCode.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 MemberInfoException(MemberInfoErrorCode.MEMBER_NOT_FOUND, exceptionMessage); } } private ParamValidator<MemberInfoParameter> validate(final String[] params, final DriverMessage message, final CyclosInstanceConfig cyclosInstance) { final String defaultCFName = getConfig(cyclosInstance.getName()).getValue(MemberInfoConfigParameter.DEFAULT_CUSTOM_FIELD_NAME); final ParamValidator<MemberInfoParameter> validator = ParamValidator.newInstance("memberInfo"); validator.param(MemberInfoParameter.PIN).required(); validator.param(MemberInfoParameter.PRINCIPAL).required(); validator.param(MemberInfoParameter.CUSTOM_FIELD_NAME).defaultValue(defaultCFName); validator.setErrorHandler(MemberInfoValidationHandler.instance(), true); validator.validate(params); infoExecutingCommandParams(logger, cyclosInstance.getName(), message, validator, MemberInfoParameter.PIN); return validator; } }

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): validate();
  2. Validate if the sending member is registered and check authentification: checkMemberCredential()
  3. Retrieve the value of the custom field memberCustomFieldValue()
  4. 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 key command.mInfo.regularExpression is a regular expression that identies the message text that needs to be processed by the custom command. The key command.<commandName>.regularExpression is requird by the controller for all the commands that are defined within the controller configuratin.

The keys command.mInfo.help.withConf, command.mInfo.help.withoutConf, command.mInfo.response, and command.mInfo.confirmation are specific for the custom command, as well as the key .regularExpression) the need to be defined in the internationalization files.

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.CommandErrorHandler. The error handling class will handle errors (normally sending notifications) when exceptions TODO del tipo CommandException ocurran en la ejecución del comando personalizado.

Note: The command object is a SINGLETON within a the scope of the cyclosInstance ( see controller context 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 controller context 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

doHandleException

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 CommandException (and not the standard exceptions that are handled by the handleException(CommandException) class). 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.

When you generate a Custom exception class a CodeSupportedError.CUSTOM_COMMAND_ERROR (enum) and an error code needs to be passed. Those arguments will permit to use internationalization (see Error translation keys) for the return notifications. The internationalization keys will be of the type * error.CUSTOM_COMMAND_ERROR.<codeNumber> (see example).

doHandleException parameters
  • CommandException this parameter is a Custom exception class
    • 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.

handleException(CommandException)

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.

If you want to have total control when a CommandException is generated you can overwrite it (in stead of implementing the doHandleException).

handleException(CommandParameterException)

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

  • Implementation optional

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

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

getErrorSmsTypeCode

protected String getErrorSmsTypeCode()

  • Implementation optional

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

Send SMS messages

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

  • Sending messages via cyclos (recomended when the members are registered), you can use:
    • facade.notifyErrorToMember
    • facade.notifyToMember
  • Sending messages directly to the Driver (when the destiny user is not a registered Cyclos member), you can use:
    • facade.notifyErrorToDriver
    • facade.sendMsgToDriver

The same methods can be used to send response message from a Command class.

Note: If a message is sent directly via 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 MemberInfoErrorHandler

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

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

public class MemberInfoErrorHandler extends CommandErrorHandler { 

    /**
     * Custom the handler for CodeSupportedError.CUSTOM_COMMAND_ERROR
     * @see nl.strohalm.cyclos.controller.command.CommandErrorHandler#doHandleException(nl.strohalm.cyclos.controller.exception.CommandException,
     * nl.strohalm.cyclos.controller.config.CyclosInstanceConfig)
     */
    @Override
    protected void doHandleException(final CommandException e, final CyclosInstanceConfig cyclosInstance) {
        // customize the way to process a CUSTOM_COMMAND_ERROR type 

        if (e.getErrorCode() == MemberInfoErrorCode.MEMBER_NOT_FOUND.code()) {
            // the member principal value (given in text message)
            facade.notifyErrorToMember(e.getDriverMessage(), cyclosInstance, getErrorSmsTypeCode(), e.getError(), e.getErrorCode(), new String[] { e.getCommandParameters()[1] });
        } else if (e.getErrorCode() == MemberInfoErrorCode.CUSTOM_FIELD_NOT_FOUND.code()) {
            String msgParam = null;
            if (e.getCommandParameters().length == 3) {
                // the principal customField (was given in text message)
                msgParam = e.getCommandParameters()[2];
            } else {
                // using principal customField with default value (config.xml)
                msgParam = e.getCommand().getConfig(cyclosInstance.getName()).getValue(MemberInfoConfigParameter.DEFAULT_CUSTOM_FIELD_NAME);
            }
            facade.notifyErrorToMember(e.getDriverMessage(), cyclosInstance, getErrorSmsTypeCode(), e.getError(), e.getErrorCode(), new String[] { msgParam });
        } else {
            super.doHandleException(e, cyclosInstance);
        }
    }

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

MemberInfoErrorHandler.doHandleException method

The method doHandleException is overwritten and receives a Custom exception class and a MemberInfoException. The implementation class contains the error handling code of the custom command (see Error codes, and doHandleException):

  • MemberInfoErrorCode.MEMBER_NOT_FOUND
  • MemberInfoErrorCode.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 Error handling class. Besides the data that is already available in a CommandException, the custom exception class can included additional information related to the custom command.

CommandException

Package:nl.strohalm.cyclos.controller.exception

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 object that represents the message that que represents the messages that caused the error. It has the following information:
    • Identifier of the Driver through which the error came
    • The number of the mobile phone that sent the message
    • The number of the mobile phone to which the message was sent
    • The complete text of the message
  • String[] commandParameters - a string array containing the words of the text message, excluding the alias and command parameters
  • Command command - the object that represents the command that was executed

Custom exception class (of the example)

Source MemberInfoException

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 MemberInfoException extends CommandException {

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

    public MemberInfoException(final MemberInfoErrorCode errorCode) {
        this(errorCode, null, null);
    }

    public MemberInfoException(final MemberInfoErrorCode errorCode, final String detailedMessage) {
        this(errorCode, detailedMessage, null);
    }

    public MemberInfoException(final MemberInfoErrorCode errorCode, final String detailedMessage, final Throwable cause) {
        super(CodeSupportedError.CUSTOM_COMMAND_ERROR, errorCode.code(), detailedMessage, cause);
        this.errorCode = errorCode;
    }

    public MemberInfoException(final MemberInfoErrorCode errorCode, final Throwable cause) {
        this(errorCode, null, cause);
    }

    public MemberInfoErrorCode getCustomCommandErrorCode() {
        return errorCode;
    }
}

Error codes

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

An exception 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 MemberInfoErrorCode
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 MemberInfoErrorCode { 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 MemberInfoErrorCode error(final Integer code) { if (code == null) { return null; } for (final MemberInfoErrorCode error : values()) { if (error.code == code) { return error; } } throw new IllegalArgumentException("Can't retrieve CustomCommandErrorCode. Unknown integer code: " + code); } private int code; private MemberInfoErrorCode(final int code) { this.code = code; } public int code() { return code; } }

SMS module configuration

The following steps need to be followed to in order to run a custom command in the module AIO:

  1. Add the JAR with all the classes of the custom command in the directory WEB-INF/lib of the AIO un JAR
  2. Replace the file WEB-INF/classes/controllerContext.xml with the one provided in the example ( web/WEB-INF/classes/controllerContext.xml)
  3. Replace the file WEB-INF/classes/config.xml with the one provided in the example ( /web/WEB-INF/classess/config.xml).
  4. Copy the file /web/WEB-INF/classes/i18n/cyclosInstance_en_US_MINFO.properties from the example and place in the WEB-INF/classes/i18n directory
  5. Copy the file /web/WEB-INF/classes/i18n/errors_en_US_MINFO.properties from the example and place in the WEB-INF/classes/i18n directory
  6. Configure Cyclos with the necesary SMS types

Attachments to controllerContext.xml

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 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"/>
</bean>

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"/>
</bean>


controllerContext.xml in the example

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

<beans	default-autowire="byName"
		xmlns="http://www.springframework.org/schema/beans"	
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:util="http://www.springframework.org/schema/util"
		xmlns:context="http://www.springframework.org/schema/context"
		xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	
	<bean name="messageProcessErrorHandler" class="nl.strohalm.cyclos.controller.facade.MessageProcessErrorHandlerImpl">
		<property name="facade" ref="facade"/>
		<property name="controllerConfig" ref="controllerConfig"/>
		<property name="sessionHandler" ref="sessionHandler"/>
		<property name="langManager" ref="langManager"/>
	</bean>
	
	'
	<bean id="mInfo" class="nl.strohalm.cyclos.controller.command.minfo.MemberInfoCommand">
		<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"/>
	</bean>
	<bean id="mInfoErrorHandler" class="nl.strohalm.cyclos.controller.command.minfo.MemberInfoErrorHandler">
		<property name="facade" ref="facade"/>
		<property name="command" ref="mInfo"/>
		<property name="logUtils" ref="logUtils"/>
	</bean>
</beans>

Attachments to config.xml

The controller configuration file WebContent/WEB-INF/classes/config.xml has been modified to support the new customized command. Two little sections need to be considered:

  1. The attribute variant for the element cyclosInstance - This change allows to add the customizations of the command needed in the file Webcontent/WEB-INF/classes/i18n/cyclosInstance_en_US_MINFO without having to change the English internationalization file included in the original distribution.
  2. An element <command ...> has been added - This is the configuration of the customized command with its attribute, please observe the parameter defaultCustomFieldName, being its value the personalized field which will be used by default.

config.xml in the example

<?xml version="1.0" encoding="UTF-8"?>
<controller language="es" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="config.xsd">
  <cyclosInstances>
    <cyclosInstance name="cyclos" language="en" country="US" variant="MINFO">
      <currencies>
        <currency symbol="units" default="true">
          <alias name="U"/>
          <alias name="Un"/>
          <alias name="Unit"/>
          <alias name="Units"/>
        </currency>
      </currencies>
      <commands> 
		<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> 
		<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="new"/>
        	    <parameter name="groupPrefix" value="."/>
        	    <paramGroup name="groupAliases">
            	        <parameter name="full" value="5"/>
            	        <parameter name="new" value="6"/>
        	    </paramGroup> 
               </command>
               <command name="infoText" requestConfirmation="false"/>
               <command name="mInfo" requestConfirmation="false">
                   <parameter name="defaultCustomFieldName" value="address" />
               </command> 
      </commands>
      <driverRouting>
        <route fromProvider="*" toDriver="aioDriver" usedFromNumber="9999" default="true"/>
      </driverRouting>
      <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}$"/>
      </memberSettings>
    </cyclosInstance>
  </cyclosInstances>
  <driverInstances>
    <driverInstance name="aioDriver">
      <cyclosRouting>
        <route fromTargetNumber="9999" toCyclos="cyclos"/>        
     </cyclosRouting>
     <connectionSettings connectionTimeout="120000" readTimeout="120000" disableCNCheck="true" trustAllCert="true"/>
   </driverInstance>
 </driverInstances>

<globalSettings responseInvalidMessages="false"/>
  
 <databaseSettings username="root" password="">
 	<url>jdbc:mysql://localhost/cyclos3_sms_aio</url>
 	<driverClassName>com.mysql.jdbc.Driver</driverClassName>
 	<querydslTemplatesClassName>com.mysema.query.sql.MySQLTemplates</querydslTemplatesClassName>
 </databaseSettings>
 <sessionSettings removeExpiredDelayInSeconds="120">
 	<control timeoutInSeconds="300" maxWrongTries="10"/>  		
 	<confirmation timeoutInSeconds="300" maxWrongTries="3" maxWrongPinTries="4" useKeyFromDictionaryFirst="true" keyLength="4" pendingsByCommand="3"/>
 </sessionSettings>
</controller>

Additions to cyclosInstance.properties

The message texts (or components of the message texts) the command will be used are internationalized through the cyclosInstance.properties file (see Internationalization files).

The translation keys need to be defined (as a String Array) in the method Command.getRequiredI18NKeys (ver #cyclosInstance_en_US_MINFO.properties_in_the_example).

cyclosInstance_en_US_MINFO.properties in the example

In our example, the following keys are internationalized:

  • General command keys
    • command.mInfo.regularExpression
  • Keys specified in MemberInfocommand.getRequiredI18NKeys()
    • command.minfo.response
    • command.mInfo.confirmation
    • command.mInfo.help.withConf
    • command.mInfo.help.withoutConf
  • To configure the general help system, customize the key:
    • 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.regularExpression=(\\s*)(minfo)(\\s+.*)*$
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}

Additions to error.properties

The error texts (or components of error texts) the command is issuing are to be internationalized through the errors.properties file (see Internationalization files).

In this file(s), all keys with codes which can be triggered during the execution of the customized commands need to be specified.

errors_en_US_MINFO.properties in the example

On top of the specific error codes triggered with the MINFO command, also the key error.COMMAND_NOT_FOUND has been customized in the case that the configuration variation notifyNotRegisteredUser="true" has been set (see 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.

Necessary configurations in Cyclos

New SmsType

To allow cyclos to classify the MT messages the system sends (to a mobile phone), in cyclos the following configurations need to be done:

  • Manually add in the sms_types table of the cyclos database the entries reflecting the text message type which will be sent by the system (of course in relationship to the personalized command).
  • Configure the internationalization for the types created in the database. In cyclos->adminMenu->Translation->Application define the texts for the keys:
    • sms.type.<CustomCommandSmsType>.description
    • sms.type.<CustomCommandSmsType>_ERROR.description
  • Add an entry in the cyclos3.sms_types table
  • Define keys in AdminMenu->Transaction->Applications->key=sms.types

SmsType in the example

To support our example, the following instructions have been issued on the cyclos database (the CustomCommandSmsType=MEMBER_INFO was defined in MemberInfoCommand class):

  • 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"

Also, at cyclos->adminMenu->Translation->Aplication the following keys have been defined:

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