Search code examples
javainversion-of-controltapestry

Apache Tapestry: Define component to be injected at runtime


I have a few components, that should be injected at runtime when they're available. The basic flow works as documented in this article. But, the article shows a very simple example and I'd like to expand on it.

Consider the following:
When loading the index page of the website, menu components are added dynamically when they're available on the classpath. I'm able to find all the fully qualified classnames of the components at runtime, but am unable to instantiate them since they should be instantiated by the Tapestry IOC Container.

e.g. instead of the following

@InjectComponent
private Menu menu;

I'd like to instantiate it by code. This way, I can have a method to return the correct component based on its parameter as such:

public Object getMenuComponent(String fullyQualifiedClassName) {
    // Ask the Tapestry IOC Container to retrieve the component based on its classname or library name
    return new Menu();
}

Whereafter I can import the component through a delegate in the .tml file.

<t:delegate to="getMenuComponent()"/>

new Menu(); is of course a static placeholder, but that doesn't work neither of course since then I'm instantiating a class managed by the IOC container.

I have read several topics and articles, saying that Tapestry has a static structure which makes it not allowed. As in the documentation, the Tapestry mailing list or in other mailing lists.

My experience with Tapestry isn't that great neither, but is there any other way of solving this problem? e.g. injecting the IoC container itself and asking it to return my component based on its qualifier or class name?


Solution

  • A solution to my problem has been found. Tapestry loads the classes, so they're already available in the container. I just don't know how to get them out of there. So instead of trying to return the component, I resolve all available pages, load their classes and check for an annotation. When I got the ones I need, I return their page name which can be used in the .tml. Then in the .tml file I can use existing components to load the pages for me.

    Here's the logic in the controller:

    public List<String> getPages() {
        List<String> result = new ArrayList<String>();
        for (final String pageName : this.componentClassResolver.getPageNames()) {
            String className = this.componentClassResolver.resolvePageNameToClassName(pageName);
            Class clazz = loadClass(className);
            if (clazz.isAnnotationPresent(MainPage.class)) {
                result.add(pageName);
            }
        }
        return result;
    }
    

    And the correspoding .tml

    <li t:type="loop" source="pages" value="mainPage">
        <t:pagelink page="${mainPage}">${mainPage}</t:pagelink>
    </li>