Tapestry already have a StrategyBuilder that is really useful when you want to write a generic service interface and let Tapestry IOC decides which implementation to call in function of the type of the arguments. But how to do it when your strategy does not depend on a Java type but a Enum type ?
We’ll see in this article a concrete use case of this type of strategy and see how easy it is to implement with Tapestry.
Use Case
We have a component that displays the activity logs on our Web site. This component is in charge of getting the list of activities to display. These latter will depend on the user context, that means not every user will see the same activities type and list.
ActivitySource
To avoid having boilerplate code in the component that will identify which finder method to call depending on the context. We define a simple interface that will be called by the component to obtain the list of strategy to display.
public interface ActivitySource {
List<Activity> listActivities(Long... context);
}
Note the ‘context’ parameters that consists in a list of ids that can be used by the implementation class to build its db query. Now we have the interface let’s create two implementations i.e. PublicActivitySource and AdminActivitySource that will list the activities in function of the logged user is a guest or a user with admin rights.
ActivitySourceType
This is the key concept of this article, now we have defined the interface and created two implementations classes, we will list all of these in a single Enum type called ActivitySourceType. Let’s see how it looks like :
public enum ActivitySourceType {
ADMIN_AS(AdminActivitySource.class),
PUBLIC_AS(PublicActivitySource.class);
private Class<? extends ActivitySource> sourceClass;
private ActivitySourceType(Class<? extends ActivitySource> source) {
this.sourceClass = source;
}
@SuppressWarnings("unchecked")
public Class<ActivitySource> getSourceClass() {
return (Class<ActivitySource>) sourceClass;
}
}
We create on Enum type per activity source. Doing this we have a centralized lists of available sources, now let’s see how to bind all these implementation classes so we can use them in our application.
Register your services
Tapestry provides a convenient way to automatically bind service implementation via the ServiceBinder class. Each implementation must have a different id so we can distinguish them at runtime. Here is the code used to iterate through the list of existing implementations. This will be localized in your AppModule class.
public static void bind(ServiceBinder binder) {
for(ActivitySourceType type : ActivitySourceType.values()){
binder.bind(ActivitySource.class, type.getSourceClass()).withId(type.toString());
}
}
Now this is done, all the existing services has been registered and you can easily and dynamically retrieve a ActivitySource service reference in your Tapestry pages.
Obtain the service instance
Use the Tapestry registry to dynamically access to the corresponding service in function of the context. i.e. You can see below how to get it work in a component using a dynamic parameter value of ActivitySourceType type.
@Parameter
private ActivitySourceType type;
@Inject
private Context context;
private ActivitySource source;
ActivitySourceType defaultType() {
return ActivitySourceType.PUBLIC_AS;
}
@SetupRender
public void defineSource() {
Registry reg = (Registry) context.getAttribute(TapestryFilter.REGISTRY_CONTEXT_NAME);
this.source = reg.getService(type.toString(), ActivitySource.class);
List<Activity> activities = source.listActivities();
for (Activity a : activities) {
System.out.println(a.getLog());
}
}
As you can see this is as simple as retrieving the service instance from the registry giving its interface and its id, and then call its ‘listActivities’ method. Doing this, we can say that we have implemented a type-safe Strategy pattern that will avoid to use ugly switch/case statements.
