Java and JBoss related stuff
RSS icon Home icon
  • Object Services, or bridging anemic and rich models, in CDI/Weld

    Posted on May 27th, 2010 Adam Warski 3 comments

    Rich domain models are certainly a nice, object-oriented idea, but I always had one problem with them: what if they become bloated with completely unrelated methods? For objects that are frequently used in a system, we may want to add various methods, which depend on the actual class of the object. Also, what if we’d like to use some other (e.g. CDI) beans as part of the method logic? Normally in DI frameworks there’s no injection into model classes. Or we want to add a frontend-specific method, but we receive the instances from a backend service?

    Object Services try to address the issues above. Suppose we have a simple class hierarchy of animals:

    
    abstract class Animal
    class Elephant extends Animal
    class Ant extends Animal
    

    and we want to implement a paint method, which paints a picture of the given animal on a canvas. Quite obviously, painting an elephant is different from painting an ant. There are several solutions:

    • add a paint method to the Animal interface – problems outlined above
    • add a paint method, in which we check which Animal was passed using instanceof – quite ugly
    • use the visitor pattern – typesafe, but quite verbose

    I think the best solution would be to have type-safe “polymorphic extension methods”, so that in your code you could just add some methods to each class in a hierarchy, but unfortunately this isn’t supported by any Java (see also multiple dispatch).

    Another possibility is to use what I call “Object Services”. If we want to add some methods to a class hierarchy, we create a parallel hierarchy of “services” (which are normal classes):

    
    interface PaintService<T extends Animal> extends OS<T> {
       void paint(Canvas c);
    }
    
    // Implements OS<Elephant>
    class ElephantPaintService implements PaintService<Elephant> {
       // Here we can store the object, for which the service was invoked
       void setServiced(Elephant e) { ... }
       void paint(Canvas c) { ... }
    }
    
    // Implements OS<Ant>
    class AntPaintService implements PaintService<Ant> {
       // Injection works normally
       @Inject AnthillService anthill;
    
       void setServiced(Ant a) { ... }
       void paint(Canvas c) { ... }
    }
    

    OS is an interface marking some beans as object services; the class, to which the service corresponds is given as a type parameter.

    The ObjectServiceExtension will detect all beans that implement the OS interface, and register an OSP (Object Service Provider) bean which can be later injected to obtain a correct object service given an Animal:

    
    @Inject
    OSP<Animal, PaintService<Animal>> paintService;
    
    void paint(Animal a, Canvas c) {
       paintService.f(a).paint(c);
    }
    

    Each invocation of the f method will lookup the correct bean, based on the run-time type of the object passed, create a new instance of the found bean and set the object, for which the method was called. All beans created are CDI-managed, so injection etc works normally.

    
    void test(Canvas c) {
       // Will invoke paint(c) in AntPaintService
       paint(new Ant(), c);
    
       // Will invoke paint(c) in ElephantPaintService
       paint(new Elephant(), c);
    }
    

    The source code is available on GitHub in the cdiext project. To use it, just bundle the jar with your application.

    Thanks to Tomek SzymaƄski for discussing the implementation.

    So what’s next? The code could use a couple of improvements, but the biggest next task is to add deploy-time checking if there’s an object service for each class in a hierarchy (e.g. we have a PrintService and an ElephantPrintService, but forget to add an AntPrintService).

    Adam

  • CDI & Weld Extensions in Git

    Posted on May 17th, 2010 Adam Warski No comments

    Hello,

    I’ve created a new cdiext project at github, initially with two extensions:

    1. Stackable Security Interceptors, about which I blogged here and here. Example usage:

    
    @SecureBinding
    @Secure("#{loggedInUser.administrator}")
    public @interface AdministratorOnly {
    }
    
    public class SecureBean {
        @AdministratorOnly
        @Secure("#{additionalSecurityCheck}")
        public void doSecret() { ... }
    }
    

    2. Injectable ELEvaluator, which works both during a faces request and outside of one (e.g. during invocation of an MDB). Example usage:

    
    @Inject
    private ELEvaluator elEvaluator;
    
    void someMethod() {
        // ...
        Integer result = elEvaluator.evaluate(
                "#{testParam1 + 10 + testParam2}", Integer.class, params);
        // ...
    }
    

    Thanks to Dan Allen for helping out with this one.

    There are also some tests done using Arquillian – looks like it’s going to be a great testing tool! :)

    Adam