Conditional Injection in Seam

Posted on Wednesday, February 17, 2010

0


Recently we got into a situation which required us to handle conditional injection. The use case is pretty simple.

There can be multiple classes of Users say Large Enterprise, Medium Enterprise and Small Enterprise. Depending on the class of the user the functionality would change. There could be some restrictions for the small scale users which would cease to exist for the large scale ones. The knee jerk implementation for this would be to have conditional statements and decide the functionality to be called on the basis of the user type

So we would have something like this

public void doSomethingService(User loggedInUser){
		if (loggedInUser.getUserType().equals(UserType.C1)){
			doSomethingElseServiceC1();
		}
		else if (loggedInUser.getUserType().equals(UserType.C2)){
			doSomethingElseServiceC2();
		}
	}

As most of you would agree that this implementation is not easily modifiable and violated the Open Closed Principle. This also violates the SRP since the doSomethingService is trying not only to do something but also trying to check the type of the user and then make a call to the respective method. Now if we have another UserType then we would have to make changes to the above code and add another if..else block. That sounds ugly!

A better option would be to possibly have a factory which would return the right implementation of the service on the basis of UserType. So we would have something like this

public void doSomethingService(User loggedInUser){
		DoSomethingServiceFactory factory = new DoSomethingServiceFactory();
		factory.getDoSomethingService(loggedInUser.getUserType());
	}

	public DoSomethingService getDoSomethingService(UserType userType){
		if (userType.equals(UserType.C1)){
			return new DoSomethingServiceC1();
		}else if (userType.equals(UserType.C2)){
			return new DoSomethingServiceC2();
		}
	}

This is probably cleaner than the first approach but it still suffers from the problems of approach 1.

The third approach, the one that we used to resolve this issue was a combination of approach based on Dependency Injection and using the logic of Convention over configuration.

We created a class called ServiceInjector which looks like this

@Name("serviceInjector")
public class ServiceInjector {

    @In
    private User loggedInUser;

    @In
    private WebAppContextService webAppContextService;

    public Object injectService(String service) {
    	Object result = webAppContextService.lookupBeanOrComponent(service + loggedInUser.getUserType());
    	if (result == null) {
    		result = webAppContextService.lookupBeanOrComponent(service);
    	}
    	return result;
    }

}

Here you would notice that on the basis of the ‘service’ requested we append UserType to that and that becomes the component name that we want find from the Seam Context.

Hence if we have a service like CompanyService then after appending the userType we would be looking at a component defined by the name CompanyServiceC1. This is where the convention over configuration comes into play.

Hence we could have different implementations of the CompanyService interface defined like

public interface CompanyService {

	List<Company> findCompanies(CompanySearchCriteria companySearchCriteria);
}
public abstract class AbstractCompanyService {

	@In
	private WebAppContextService webAppContextService;
	@In
	protected MasterRepository masterRepository;

	public AbstractCompanyService() {
		super();
	}

	public List<Company> findCompanies(
			CompanySearchCriteria companySearchCriteria) {

		List<Company> result = new ArrayList<Company>();
		result = applyUserPreferences(findUserSpecificCompanies(companySearchCriteria));
		return result;
	}

	abstract public List<Company> findUserSpecificCompanies(
			CompanySearchCriteria companySearchCriteria);

	protected List<Company> applyUserPreferences(List<Company> result) {
		....
		....
		return result;
	}

	public void setWebAppContextService(
			WebAppContextService webAppContextService) {
		this.webAppContextService = webAppContextService;
	}

	public void setMasterRepository(MasterRepository masterRepository) {
		this.masterRepository = masterRepository;
	}
}
@Name("companyServiceC1")
@AutoCreate
public class CompanyServiceC1 extends AbstractCompanyService implements CompanyService {

    public List<Company> findUserSpecificCompanies(CompanySearchCriteria companySearchCriteria) {
        List<Company> result = new ArrayList<Company>();

        for (Company company : masterRepository.getAllCompanies()) {
            if (companySearchCriteria.matches(company)) {
                result.add(company);
                break;
            }
        }

        return result;
    }

Note the @Name definition

Now when we want to inject this CompanyService into any object then the way we do that in Seam would be like this

@In(value="#{serviceInjector.injectService('companyService')}")
	private CompanyService companyService;

The advantage here is that we are not violating the OCP any more. If I have a new userType called C3 tomorrow which has a specific implementation, then nothing changes in the way we would do the injection. We do not need to define a configuration anywhere. The only thing that needs to be done is creation of a new Class named as @Name(“companyServiceC3”)

I hope this way would be advantageous in various situations and help in keeping the code clean. This also helps you stay away from the dangers of if .. else.


Get in touch

Interested to know more about our JBoss Seam capabilities? Get in touch with us on seam@inphina.com

Advertisements
Posted in: Java