Search code examples
cdivraptor

VRaptor4 @Named Components in velocity template


I have a vraptor4 project and i want to use apache velocity as the template engine.

So i specialized the br.com.caelum.vraptor.view.DefaultPathResolver as

@Specializes
public class VelocityPathResolver extends DefaultPathResolver {

    @Inject
    protected VelocityPathResolver(FormatResolver resolver) {
        super(resolver);
    }

    protected String getPrefix() {
        return "/WEB-INF/vm/";
    }

    protected String getExtension() {
        return "vm";
    }
}

That work fine, but I cannot have @Named components in my templates.

Having

@SessionScoped
@Named("mycmp")
public class MyComponent implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name = "My Component";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

I cannot refer to it as ${mycmp.name} in my velocity template (.vm), but if i use .jsp it works fine.

To solve it i specialized the br.com.caelum.vraptor.core.DefaultResult as

@Specializes
public class VelocityResult extends DefaultResult {

    private final MyComponent mycmp;

    @Inject
    public VelocityResult(HttpServletRequest request, Container container, ExceptionMapper exceptions, TypeNameExtractor extractor,
                      Messages messages, MyComponent mycmp) {

        super(request, container, exceptions, extractor, messages);
        this.mycmp = mycmp;
    }

    @PostConstruct
    public void init() {
        include("mycmp", mycmp);
    }
}

Is there a better approach for having @Named components in velocity templates?


Solution

  • it looks like that CDI's @Named won't work with velocity templates, but you can implement an Interceptor to do this job for you. An example would be:

    @Intercepts
    public class IncluderInterceptor {
    
        @Inject private MyComponent mycmp;
    
        @AfterCall public void after() {
            result.include("mycmp", mycmp);
            // any other bean you want to
        }
    }
    

    And thinking in a more flexible solution, you could create an annotation and use it to define which bean should be included... something like that:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Included {
    }
    

    So you could add @Included at your classes:

    @Included public class MyComponent { ... }
    

    And just add the accept method on the IncluderInterceptor:

    @Intercepts
    public class IncluderInterceptor {
    
        @Inject @Any Instance<Object> allBeans;
    
        @AfterCall public void after() {
            // foreach allBeans, if has @Included, so
            // include bean.getClass().getSimpleName()
            // with first letter lower case, or something
        }
    }
    

    Sure, if you'll include just a few beans the first solution should be enought. Best regards