Extending linkki

Custom message handling

It is possible to create your own annotation for message binding instead of using the provided @BindMessages, for instance to disable a button if there are error messages.

The custom annotation has to be annotated with the meta-annotation @LinkkiMessages.

Next, an implementation of LinkkiMessageHandler is required.

In the case of @BindMessages the implementation BindValidationMessagesHandler extends DefaultMessageHandler. When a PMO property, for instance getDateOfBirth(), is annotated with @BindMessages, the PMO class and annotated elements are passed to the constructor of BindValidationMessagesHandler and are used in BindValidationMessagesHandler.getRelevantMessages() to retrieve and call the PMO’s method getDateOfBirthMessages():

public class BindValidationMessagesHandler extends DefaultMessageHandler {

    private static final String NAME = "messages";
    private static final Map<CacheKey, Method> CACHE = new ConcurrentHashMap<>();

    private final Method validationMethod;

    public BindValidationMessagesHandler(Class<?> pmoClass, String property) {
        this.validationMethod = CACHE.computeIfAbsent(new CacheKey(pmoClass, property), this::getValidationMethod);
    }

    private Method getValidationMethod(CacheKey key) {
        String validationMethodName = "get" + StringUtils.capitalize(key.propertyName + StringUtils.capitalize(NAME));
        try {
            return key.pmoClass.getMethod(validationMethodName, MessageList.class);
        } catch (NoSuchMethodException | SecurityException e) {
            throw new LinkkiBindingException(String.format("Cannot find method %s(MessagesList) in %s",
                                                           validationMethodName, key.pmoClass.getName()),
                    e);
        }
    }

    @Override
    protected MessageList getRelevantMessages(MessageList messages, PropertyDispatcher propertyDispatcher) {
        try {
            if (propertyDispatcher.getBoundObject() instanceof Class<?>) {
                // The bound object is most likely a column from a grid.
                 return new MessageList();
            }
            return (MessageList)validationMethod.invoke(propertyDispatcher.getBoundObject(), messages);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new LinkkiBindingException(String.format("Cannot invoke method %s(MessagesList) on %s",
                                                           validationMethod.getName(),
                                                           propertyDispatcher.getBoundObject()),
                    e);
        }
    }

}

Finally, the custom annotation requires an implementation of MessageHandlerCreator, to create and return the implementation of LinkkiMessageHandler. E.g. with @BindMessages, the creator class looks like:

    class BindMessagesHandlerCreator implements MessageHandlerCreator<BindMessages> {

        @Override
        public LinkkiMessageHandler create(BindMessages annotation, AnnotatedElement annotatedElement) {
            Method method = (Method)annotatedElement;
            return new BindValidationMessagesHandler(method.getDeclaringClass(),
                    BoundPropertyAnnotationReader.getBoundProperty(method).getPmoProperty());
        }
    }