Skip to content

Action Controller

bristleback edited this page Aug 3, 2013 · 12 revisions

As written in introduction, Action Controller is a built in implementation of DataController interface. It’s main purpose is to process incoming websocket messages, prepared by Front Controller.

Table of Contents

Bristle messages

Incoming messages (messages that comes from client) are deserialized using SerializationEngine implementation (see chapter Message serialization for more details) into BristleMessage object. Then, a special dispatcher finds suitable action class and action method, previously defined by the application user. Below you can find an example of incoming message, formatted in JSON:

    {
    	"id":1,				(1)
    	"name":"Sample.hello",		(2)
    	"payload":["wojtek","25"]	(3)
    }

Incoming message consists of 3 parts: id, name and payload. Message ID (1) is sent by client to identify action requests. When server sends response, the same ID is sent to user so client is able to recognise it automatically. Thanks to that, User can define special callback methods on client side, suitable only for one specific request. In most scenarios, application user doesn’t have to worry about message ID.

Message name (2) is used to determine what action class and what action are requested. In this case, user requests action class with name “Sample” and action “hello”.

Last component - payload (3) - is used to store action parameters. Payload is coded in the same format as whole message. Depending on the situation, payload may be of array (more than one parameter) or normal object type (single or no parameter). In our example, payload has two parameters with values: “wojtek” and “25”.

In next two subsections, we will define simple action class containing normal and default actions.

Defining actions

Action classes are plain Java classes, defined as Spring beans. Defining new action classes as well as new actions is pretty straightforward. To be recognized as action class, is must be annotated with @ActionClass. If not specified (by setting name() field in @ActionClass annotation), action class name is equal to class simple name, for example class com.somedomain.package.action.MyHelloWorld will have name MyHelloWorld. You cannot define multiple classes with the same action class name. Now we’ll try to create the action class that will be found by controller while processing message from previous snippet:

    @ActionClass(name = "Sample")
    @Component
    public class HelloWorldActionClass {
    	//TODO - action methods 
    }

But still, the action dispatcher will throw BrokenActionProtocolException on incoming actions. This exception is thrown when incoming message cannot be processed into invoking action on proper action class. ReasonType enum is passed to BrokenActionProtocolException to help you quickly find the error. Although reason types are quite descriptive, we listed them below for consistency:

  • NO_MESSAGE_ID_FOUND - Incoming message doesn’t contain message ID
  • NO_ACTION_CLASS_FOUND - Incoming message doesn’t contain message name field
  • NO_DEFAULT_ACTION_FOUND - Incoming message name has no dot character, which means client wants to invoke default action, but the action class doesn’t have default action defined.
  • NO_ACTION_FOUND - No action with name requested by the client is found in this action class
Using Spring, you can define scope of the action classes. Bristleback automatically recognizes whether requested action class is a singleton and if true, initializes singleton instance on server startup, storing it in it’s information object.

Now it’s time to define hello action with two parameters:

    @ActionClass(name = "Sample")
    @Component
    public class HelloWorldActionClass {
    
      @Action(name = "hello")
      public String helloWorld(String userName, int age) {
    	return "Hello " + userName + ". You are " + age + " years old, right?";
      }
    }

As we said, it’s a normal method, taking two parameters and returning String object. We annotated it with @Action so it is visible by framework. It’s also not required to explicitly declare action name, framework will take method name by default (in our action, it would be “helloWorld”. It is forbidden to have two or more actions with the same name (overloading).

Action method can take any parameters, including Java Beans, Lists, Arrays, Maps, Enums, etc (there are some limitation related mainly to generic types, see chapter Message serialization). The same applies to action response. Actions may have return void/Void type. In that case, simply no response is sent.

Action methods may throw exceptions.

To learn how to code client side of action classes, visit chapter Javascript client.
To build application and test how it works in real world, visit chapter Creating Sample Chat Application.

Default actions

Default actions are less flexible but faster alternative to normal actions. They were introduced in previous versions of Bristleback, when action class could have only one action. They don’t need name, so message requesting default action contains “name” parameter value consisting of action class name only. Instead of using reflection, their signature is defined by the DefaultAction interface.

    public interface DefaultAction<U extends UserContext, T> {
    
      Object executeDefault(U user, T payload);
    }

As in normal actions, default actions must be marked with @Action annotation. Default action method takes two parameters, user instance and payload. User object is taken automatically by Bristleback so you don’t have to bother about it. Unlike in other actions, default action is restricted to only one payload parameter. Default actions may have Void (but not void) return type.

UserContext is an abstraction for single client connection, one instance of UserContext implementation is created per each connection. More about users in chapter Dealing with users instead of connectors

Let’s try to add default action to our previously created action class:

    @ActionClass(name = "Sample")
    @Component
    public class HelloWorldActionClass implements DefaultAction<BaseUserContext, Address> {
    
      @Autowired
      private SomeServiceImpl someService;
    
      // other actions
    
      @Action
      public List<Discount> executeDefault(BaseUserContext user, Address object) {
    	List<Discount> discountsForUser = someService.getDiscountsForUser(user, object);
    	return discountsForUser;
      }
    }

This example assumes that there are Discount and Address classes in the project. Action invokes service to resolve discounts available for given user. Serialized list of discounts is returned to user. Important thing in Bristleback is that you can code your applications in the same way you are used to do in other Spring applications. Action classes are similar to web framework concepts, like Spring MVC controllers.

Action response

The only thing about action responses that wasn't mentioned yet is about their serialization. Actions may have any response type as well as void/Void (no response being sent). Response type limitations are related to serialization engine limitations.

Exception handling

In every system there are situations when some service or dao method throws an exception. Bristleback Action Controller provides a way to handle such situations in an elegant way. Framework user is able to determine application reaction for more or less specified exception type. Yet, controller provides default exception handling that should be sufficient in most cases. Before we can deeply talk about exception handling, we need to say something about action execution stages first.

Action execution stages

Each time client requests some action, new ActionExecutionContext wrapper object is created. It contains BristleMessage with extracted action class and action name, UserContext implementation that represents user requesting that action. It also provides action execution stage enum value. Stages provide information about what is currently being done in single action execution. Execution stages change while message is being processed by the controller. Fallowing list shows all execution stages from first to last.

  1. MESSAGE_DESERIALIZATION - In this stage, message is in raw state, action class name and action name, payload and id must be deserialized.
  2. ACTION_EXTRACTION - Action information object is resolved using deserialized action name.
  3. PARAMETERS_EXTRACTION - Payload is deserialized into action parameters
  4. ACTION_EXECUTION - Main stage of the action execution. Body of the action method is executed in this stage.
  5. RESPONSE_CONSTRUCTION - Stage performed after action method execution ends properly (without exception).
In each of aforementioned stages there are exceptions that might occur. Message format incompatibilities may lead to serialization exception in the first stage. Such exceptions are not likely to occur when using the same server and client versions. When the controller cannot find a requested action or action class, BrokenActionProtocolException is thrown. Next stage is to deserialize action parameters. As every serialization process, various types of exceptions might occur there. For example validation exceptions, data format exceptions, etc. Action method invocation is done in ACTION_EXECUTION stage. Action methods are able to throw any kind of exception. Exceptions in this stage should be in most cases handled somehow by application. Response construction exceptions are similar to parameters extraction exceptions, they are mostly related to objects serialization.

Exception handling mechanism

Basic interface used for handling exceptions is presented below:

    public interface ActionExceptionHandler<T extends Exception> {
    
      Object handleException(T e, ActionExecutionContext context);
    
      ActionExecutionStage[] getHandledStages();
    }

All exception handlers are loaded in initialized on server start. Special container stores them in an orderly way. You may be wondering, how handler knows about which exception it should handle? ActionExceptionHandler interface is parametrized with T extends Exception parameter. On initialization, Bristleback resolves type of each implementation parameter. Now we should step forward to getHandledStages() method. As you know from previous subsection, action execution stage shows what part of action execution is being performed in current time. Handlers may be used for any of those stages. In this method you can define on which stages given handler should be invoked. But how exactly proper handler is chosen from the container? Here are steps performed by the controller when exception occurs:

  1. All action exception handlers for stage on which exception occured are resolved.
  2. Exception type is taken, container is checked whether it contains handler for that type. If exception is not found and exception type is java.lang.Exception, VoidExceptionHandler is returned.
  3. If handler is not found, exception superclass is taken, and step 2 is performed again.
  4. If handler is found, its handleException() method is performed.
  5. Object returned by handleException() method is sent as action response. If object “is a” ExceptionResponse, exception response is sent (with :exc suffix).
After system determined that given object should handle occuring exception, handleException() method is invoked. It returns Object, which is then sent to user. There is one important thing to understand. Although handle method may return any object, in most situations, you should use class/subclass of ExceptionResponse. Whenever object that is instance of this class is being sent, Bristleback treats it in a special way, adding suffix :exc to the response message name, showing that action threw an exception to the client. ExceptionResponse has two fields: name and stage. Thanks to adding suffix to the message name, client applications can handle exceptions out of the box.
And what if exception occurs while sending exception response? In the current version, excepton is just catched and logged. This is the one of areas where further changes will be made in next versions of Bristleback.

Default exception handling

Bristleback Action Controller provides basic handlers for every action execution stage. Fallowing table shows all default exception handlers bundled with Bristleback.

Stages Handler type Description
None VoidExceptionHandler Does nothing, no response message is sent.
MESSAGE_DESERIALIZATION MessageDeserializationExceptionHandler Disconnects user. Exceptions in this stage indicate protocol errors.
ACTION_EXTRACTION
RESPONSE_CONSTRUCTION
GenericRespondingExceptionHandler Sends ExceptionResponse object with stage passed but without information about real exception type. This information should be retained in most cases
ACTION_EXTRACTION ActionProtocolExceptionHandler Handles BrokenActionProtocolException exceptions. Sends ExceptionResponse object with stage and exception type information passed.
PARAMETERS_EXTRACTION
ACTION_EXECUTION
GenericActionExecutionExceptionHandler Sends ExceptionResponse object with stage and exception type information passed.

All default handlers, except for ActionProtocolExceptionHandler (which takes BrokenActionProtocolException), are parametrized with Exception class.

Creating custom exception handlers

Creating custom exception handler is pretty simple. The only thing to do is to create new class implementing generic interface ActionExceptionHandler and setting it as the Spring bean.

Action parameters validation

Starting from version 0.3.5, Bristleback Framework can be extended with special module for action parameters validation. Action validation module provides support for JSR 303: Bean Validation specification. By default, Bristleback uses Hibernate Validator as the standard implementation. To learn more about action validation module, check Action parameters validation chapter.

Parameter extractors

Action controller uses parameter extractors for resolving action parameters that can’t be resolved using serialization engine. The most often used example is UserContext implementation. Action methods can have UserContext (or subclass of UserContext) parameters, they will be extracted from current user requesting that action. Each connected client has individual UserContext instance, representing that client. When client requests action containing UserContext parameter, special implementation of ActionParameterExtractor resolves that parameter using fragment of incoming message (if necessary) and ActionExecutionContext object, containing information about requested action. ActionParameterExtractor has fallowing signature:

    public interface ActionParameterExtractor<T> extends ConfigurationAware {
    
      T fromTextContent(String text, ActionParameterInformation parameterInformation, ActionExecutionContext context) throws Exception;
    
      boolean isDeserializationRequired();
    }

Similar to exception handlers, the most suitable parameter extractor is chosen for each parameter at server start. T means the most generic parameter type that can be extracted by the implementation. This can be tricky because if your extractor handles parameters of type A and real action method parameter is of type B extends A, your extractor will be invoked to resolve that parameter and you must ensure that extractor will return object of B type, otherwise ClassCastException will be thrown. To recognise type of parameter, fromTextContent() method contains ActionParameterInformation parameter, which provides information about currently extracted action parameter. ConfigurationAware has method setting BristlebackConfiguration object. The last method, isDeserializationRequired(), indicates whether serialization information of given parameter should be retrieved. If your implementation is not going to use serialization engine, simply set that field to false.