One possible solution is to extract the significant tenant values in request processing e.g. ServletFilter
or some Interceptor and store it in a ThreadLocal
holder. This will only work if both components(e.q. filter and CDI producer) are executed in the same thread - therfore you might run into issues with EJBs.
You can retrieve the tenant identifier in your @Produces
method and return the config entry based on the @Key
annotation value and tenant id.
Some pseudo solution:
ThreadLocal holder
public class ThreadLocalHolder {
private static ThreadLocal<String> tenantIdThreadLocal = new ThreadLocal<>();
public static String getTenantId(){
return tenantIdThreadLocal.get();
}
public static void setTenantId(String tenantid){
return tenantIdThreadLocal.set(tenantid);
}
}
Request filter for tenant extraction
@WebFilter(value = "/*")
public class TenantExtractorFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
//obtain tenant id, and store in threadlocal
ThreadLocalHolder.setTenantId(req.getHeader("X-TENANT"));
chain.doFilter(request, response);
}
}
Config entry producer
public class Producer {
//get a hold of some DAO or other repository of you config
private ConfigRepository configRepo;
@Produces
@Config
public String produceConfigEntry(InjectionPoint ctx) {
Key anno = //get value of @Key annotation from the injection point, bean, property...
String tenantId = ThreadLocalHolder.getTenantId();
// adjust to your needs
return configRepo.getConfigValueForTenant(anno.value(), tenantId);
}
}
If ThreadLocal
is not an option, have a look at javax.transaction.TransactionSynchronizationRegistry
- which works regardless of thread pools, but requires a transaction presence obviously.
Update 14.12.2015
Alternative approach using request scoped bean as data holder
RequestScoped holder
@RequestScoped
public class RequestDataHolder {
private String tenantId;
public String getTenantId() {
return this.tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}
WebFilter
Extracts the values from request and stores them in our holder.
@WebFilter(value = "/*")
public class TenantExtractorFilter implements Filter {
@Inject private RequestDataHolder holder;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
//obtain tenant id, and store in threadlocal
holder.setTenantId(req.getHeader("X-TENANT"));
chain.doFilter(request, response);
}
}
CDI producer
Uses the data holder and produces the expected value forinjection point.
public class Producer {
//get a hold of some DAO or other repository of you config
private ConfigRepository configRepo;
@Inject
private RequestDataHolder dataHolder;
@Produces
@Config
public String produceConfigEntry(InjectionPoint ctx) {
Key anno = //get value of @Key annotation from the injection point, bean, property...
String tenantId = holder.getTenantId();
// adjust to your needs
return configRepo.getConfigValueForTenant(anno.value(), tenantId);
}
}
Our RequestDataHolder
bean can be injected into any CDI, EJB, JAXRS or Servlet component thereby allowing to pass variable from WEB context to other contexts.
Note: this solution requires proper integration of CDI container with EJB and WEB containers according to CDI spec.