Reputation: 329
I try to make a multitenancy REST API.
Java EE 7
Application-Server: WildFly-Swarm 2017.11.0
JAX-RS: wildfly-swarm-weld
The aim is to get the tenant (german Mandant) name from the query param and set the name of the tenant to trigger a proxy EntityManager.
This is the Basic concept from TOMAS DVORAK: https://www.tomas-dvorak.cz/posts/jpa-multitenancy/
I struggle with the Interceptor and as said in the title i need to abort the response of the intercepted REST request with an HTTP Code and an JSON Error Message.
I cant use a filter cause EE is using another thread with the filter and i cant pass through the Tenantname over ThreadLocal.
I struggle to get the Response Object.
Here is what i Code so far:
import javax.inject.Inject;
import javax.interceptor.*;
import javax.servlet.http.*;
import org.multitenancy.test.beans.*;
/**
* Wrap every call with tenant identification, detected from list of parameters
* of called method.
*/
@Interceptor
public class TenantInterceptor
{
@Inject
private TenantRegistry tenantRegistry;
@Inject
HttpServletRequest servletRequest;
@AroundInvoke
public Object wrapWithTenant(final InvocationContext ctx) throws Exception
{
System.out.println("wrapWithTenant() Called");
printParameter();
if (servletRequest.getParameterMap().containsKey("mandant"))
{
String mandantNameReq = servletRequest.getParameterMap().get("mandant")[0];
if (tenantRegistry.verifyMandantByName(mandantNameReq))
{
System.out.println(mandantNameReq + " is verified");
final String oldValue = TenantHolder.getCurrentTenant();
System.out.println("old value " + oldValue);
try
{
TenantHolder.setTenant(mandantNameReq);
System.out.println("Mandant gesetzt: " + mandantNameReq);
return ctx.proceed();
}
finally
{
if (oldValue != null)
{
TenantHolder.setTenant(oldValue);
}
else
{
TenantHolder.cleanupTenant();
}
}
}
else
{
//TODO: Response einbauen
// containerRequestContext.abortWith(
// Response.status(Response.Status.BAD_REQUEST)
// .entity(new ApiError("Mandant not found"))
// .type(MediaType.APPLICATION_JSON)
// .build());
}
}
else
{
// containerRequestContext.abortWith(
// Response.status(Response.Status.BAD_REQUEST)
// .entity(new ApiError("Parameter doesnt contain mandant"))
// .type(MediaType.APPLICATION_JSON)
// .build());
}
return null;
}
private void printParameter()
{
if (servletRequest.getParameterMap().isEmpty())
{
System.out.println("No Properties given");
}
else
{
for (String key : servletRequest.getParameterMap().keySet())
{
for (String val : servletRequest.getParameterValues(key))
{
System.out.println(key + "\t" + val);
}
}
}
}
}
Thanks in advance :)
Upvotes: 0
Views: 767
Reputation: 3580
I cant use a filter cause EE is using another thread with the filter and i cant pass through the Tenantname over ThreadLocal.
Why is this so? How do you access the application? If you access it over REST this is the entry point into the application and the ThreadLocal should be accessable from this on. Important is that you set the @Priority
to a low value, so that the filter is one of the first executed.
We are using a similar approach in our application saving the tenant identifier in a ThreadLocal
@Provider
@Priority(1)
public class TenantFilter implements ContainerRequestFilter
{
private static final Logger LOG = LoggerFactory.getLogger(TenantFilter.class);
private static final String TENANT_IDENTIFIER = "mandant";
@Inject
private TenantRegistry tenantRegistry;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
List<String> identifierHeader = requestContext.getHeaders().get(TENANT_IDENTIFIER);
if ((identifierHeader == null || identifierHeader.isEmpty()))
{
LOG.error("No header " + TENANT_IDENTIFIER + " found. Access denied. [path: " + path + "]");
accessForbidden(requestContext);
return;
}
String tenantIdentifier = identifierHeader.get(0);
if (tenantRegistry.verifyMandantByName(tenantIdentifier)) {
LOG.trace("Filtering request for " + TENANT_IDENTIFIER + " header, using: " + tenantIdentifier);
TenantIdentifierResolver.tenantIdentifier.set(tenantIdentifier);
}
}
private void accessForbidden(ContainerRequestContext requestContext)
{
Response accessForbidden = Response.status(Status.FORBIDDEN).build();
requestContext.abortWith(accessForbidden);
}
}
The TenantIdentifierResolver is a thread local holding the tenand identifier and is used by Hibernate to setup the correct schema access for database access.
Upvotes: 1
Reputation: 3728
I would avoid storing stuff in thread locals as it's upto the container implementation whether to use the same thread or not for request dispatch. It may work in one container but not another one.
I would do the following:
1) Create an injectable bean definition for your tenant:
@javax.enterprise.inject.Produces
Tentant tenant(HttpServletRequest request) throws TenantNotFoundException {
// logic
}
2) Handle the exception in JAX-RS
@javax.ws.rs.ext.Provider
public class TenantNotFoundExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<TenantExceptionNotFound> {
public Response toResponse(TenantNotFoundException exception) {
// your 400 response here
}
}
3) Inject Tenant into your business logic
@Path("/foo")
public class Foo {
@javax.inject.Inject
private Tenant tenant
}
Upvotes: 1