
Custom action bean annotations are a powerful feature that are used in a few places in Strecks. Action bean annotations allow you to extend the contract between the action bean and the action controller without changing the interface which the action bean and the action controller use to communicate with each other. A good example is in allowing different forms of navigation to be used with the same controller.
Adding a custom action bean annotations is a bit more complex than adding other
forms of annotations, such as validation, data binding and dependency injection.
The best way to see how it works is to show an existing working example.
The example we use for this is the @DispatchMethod annotation, which by the 
LookupDispatchActionController implementations when implementing Streck's version of 
LookupDispatchAction. 
If you are not familiar with LookupDispatchAction, its purpose is to allow individual buttons
in a multi-button form to be mapped to methods within an action class.
The basic mechanisms behind custom action bean annotations are as follows:
All of this may sound somewhat abstract, so our concrete example should help to clarify.
We mentioned that the action bean identifies its collaborating action controller. The code below shows an
example usage of the @DispatchMethod annotation.
@Controller(name = BasicLookupDispatchController.class)
public class ExampleBasicLookupSubmitAction implements BasicSubmitAction
{
    public void preBind()
    {
    }
    public String execute()
    {
      //code omitted
        return "success";
    }
    @DispatchMethod(key = "button.add")
    public String insert()
    {
      //code omitted
        return "success";
    }
    @DispatchMethod(key = "button.delete")
    public String delete()
    {
      //code omitted
        return "success";
    }
    public String cancel()
    {
      //code omitted
        return "success";
    }
}
The @DispatchMethod is used to denote the resource key which will be mapped to the annotated 
method. In Struts's LookupDispatchAction, this role is performed by a implementing a method
returning a Map containing this mapping.
Notice how ExampleBasicLookupSubmitAction identifies 
BasicLookupDispatchController as the action controller.
ActionBeanAnnotationReader represents 
functionality for extracting some information from annotations contained within an action bean, and 
defines two methods:
public interface ActionBeanAnnotationReader<T extends Object>
{
    public boolean readAnnotations(Class actionBeanClass);
    public void populateController(T controller);
}
The job of the first method is to read the annotations in of an action bean Class instance, while
populateController() is the method used to transfer the extracted information to the controller.
The class which implements the logic for extracting message key to method name mappings from the
@DispatchMethod annotation is DispatchMethodLookupReader, which is shown in essence below:
public class DispatchMethodLookupReader 
 	implements ActionBeanAnnotationReader<LookupDispatchActionController>
{
    private Map<String, String> keyMethodMap = new HashMap<String, String>();
    public boolean readAnnotations(Class actionBeanClass)
    {
        Method[] methods = actionBeanClass.getMethods();
        boolean found = false;
        for (Method method : methods)
        {
            DispatchMethod annotation = method.getAnnotation(DispatchMethod.class);
            if (annotation != null)
            {
                String key = annotation.key();
                String methodName = method.getName();
                keyMethodMap.put(key, methodName);
                found = true;
            }
        }
        return found;
    }
    public void populateController(LookupDispatchActionController controller)
    {
        controller.setKeyMethodMap(Collections.unmodifiableMap(keyMethodMap));
    }
}
The implementation is pretty straightforward - readAnnotations() simply builds the key to method map
which in plain Struts would be returned via a concrete method implementation. 
The return value of readAnnotations() is used to indicate whether the annotation reader found the
annotations it was looking for. populateController() is then used to pass the keyMethodMap on 
to the controller, which clearly needs to be implement the LookupDispatchActionController interface.
Our action controller has two requirements. It needs some way of configuring which action bean annotation readers
should be used to inspect the collaborating action bean's annotations, 
and also must have a way to receive the information extracted.
To these ends, there are two important things to notice about the implementation of 
BasicLookupDispatchController,
shown below:
@ActionInterface(name = BasicSubmitAction.class)
@ReadDispatchLookups
public class BasicLookupDispatchController 
	extends BasicDispatchController 
	implements LookupDispatchActionController
{
    private Map<String, String> keyMethodMap;
    public void setKeyMethodMap(Map<String, String> keyMethodMap)
    {
        this.keyMethodMap = keyMethodMap;
    }
    @Override
    protected ViewAdapter executeAction(Object actionBean, ActionContext context)
    {
      //implementation omitted
    }
    
    //rest of class omitted
}
First, the controller action needs to implement an interface which the action bean annotation reader
can use to configure the controller action. As we have seen, 
the interface is LookupDispatchActionController, which
has the following form:
public interface LookupDispatchActionController extends ControllerAction
{
    void setKeyMethodMap(Map<String, String> keyMethodMap);
}
The purpose of this interface is to allow the action controller to be 
populated with the message key to method name mapping
read using the @DispatchMethod annotations.
Second, the action controller needs a way of letting Strecks know which 
ActionBeanAnnotationReaders should scan for annotations.
The mechanism is very similar to the Annotation Factory pattern used for identifying
data binding and conversion annotations. 
It is applied through the use of @ReadDispatchLookups 
to annotate the action controller class definition.
The implementation of @ReadDispatchLookups is shown below:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@BeanAnnotationReader(DispatchMethodLookupReader.class)
public @interface ReadDispatchLookups
{
}
The key is in the use of the @BeanAnnotationReader annotation. 
The role of this annotation is simply to define an 
ActionBeanAnnotationReader implementation to use to extract action bean
metadata. In other words, by using the @ReadDispatchLookups in our
action controller, we are saying "use the class @DispatchMethodLookupReader to find annotations
and extract information from the action bean, passing this on to the action controller instance".
The example we have used so far is quite straightforward in that the ActionBeanAnnotationReader
simply looks for occurrences of a single annotation. More advanced functionality can be
supported by applying the Annotation Factory Pattern to the ActionBeanAnnotationReader
implementation. Here, the ActionBeanAnnotationReader is not looking for a particular annotation,
but for annotations which themselves are annotated with a factory annotation, that is,
an annotation which can be used to identify a factory class which understands how to process
the annotation. This mechanism is applied to support pluggable navigation, as well as supporting
action bean source configuration (which allows, for example, action beans to be retrieved
as Spring beans).
For action bean source configuration, the ActionBeanAnnotationReader 
implementation is class is BeanSourceAnnotationReader. The essence of this
class's readAnnotations() method is shown below:
public boolean readAnnotations(Class actionClass)
{
    Annotation[] annotations = actionClass.getAnnotations();
    boolean found = false;
    for (Annotation annotation : annotations)
    {
        Class<? extends Annotation> annotationType = 
          annotation.annotationType();
        BeanSourceReaderClass beanSourceReaderClass = 
          annotationType.getAnnotation(BeanSourceReaderClass.class);
        if (beanSourceReaderClass != null)
        {
            BeanSourceReader beanSourceReader = 
            	ReflectHelper.createInstance(beanSourceReaderClass.value(),
                    BeanSourceReader.class);
            beanSource = beanSourceReader.readBeanSource(actionClass, annotation);
            found = true;
        }
    }
    return found;
    }
}
Notice how BeanSourceAnnotationReader is not reading the annotations itself, but instead
delegates this task to a BeanSourceReader instance. 
Each BeanSourceReader implementation would look
for a particular annotation or set of annotations. For example, Spring-managed action beans are 
configured using the class SpringBeanSourceReader, shown below:
public class SpringBeanSourceReader implements BeanSourceReader
{
    public ActionBeanSource readBeanSource(Class actionClass, Annotation annotation)
    {
        Assert.notNull(actionClass);
        Assert.notNull(annotation);
        SpringBean actionBean = (SpringBean) annotation;
        return new SpringActionBeanSource(actionClass, actionBean.name());
    }
}
Pluggable navigation works in a similar way. 
A navigation annotation uses a NavigationReader instance
to find particular navigation-related annotations. 
For example, @NavigateForward annotations use a
are read using an instance of NavigateReader, 
which implements NavigationReader.
We can see this is the case by looking at the implementation of
the @NavigateForward annotation itself.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@NavigationInfo(value = NavigateReader.class)
public @interface NavigateForward
{
    Class<? extends NavigationMarker> handler() 
    	default DefaultNavigationHandlerFactory.class;
}
Here, @NavigationInfo is the factory annotation 
used to identify the NavigationReader implemention class
used to read the annotation.