我尝试制作多租户REST API
Java EE 7
应用程序服务器:WildFly-Swarm 2017.11.0
JAX-RS:wildfly-swarm-weld
目的是从查询参数中获取租户(德国Mandant)名称,并设置租户名称以触发代理EntityManager。
这是TOMAS DVORAK的基本概念:https://www.tomas-dvorak.cz/posts/jpa-multitenancy/
我在拦截器上挣扎,正如标题中所说,我需要通过HTTP代码和JSON错误消息中止拦截的REST请求的响应。
我不能使用过滤器,因为EE正在使用带过滤器的另一个线程,我无法通过ThreadLocal通过Tenantname。
我很难获得响应对象。
以下是我目前的代码:
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);
}
}
}
}
}
提前致谢:)
答案 0 :(得分:1)
我无法使用过滤器,因为EE正在使用带过滤器的另一个线程,并且我无法通过ThreadLocal传递Tenantname。
为什么会这样?你如何访问该应用程序?如果您通过REST访问它,那么这是应用程序的入口点,并且ThreadLocal应该可以从此访问。重要的是,您将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);
}
}
list = re.findall(r"PROGRAM S\d\d[^ ]*", contents)
TenantIdentifierResolver是一个持有tenand标识符的本地线程,Hibernate使用它来为数据库访问设置正确的模式访问。
答案 1 :(得分:0)
我会避免在线程本地存储东西,因为它是容器实现是否使用相同的线程进行请求分派。它可以在一个容器中工作,但不能在另一个容器中工作。
我会做以下事情:
1)为您的租户创建一个可注入的bean定义:
@javax.enterprise.inject.Produces
Tentant tenant(HttpServletRequest request) throws TenantNotFoundException {
// logic
}
2)处理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)将租户注入您的业务逻辑
@Path("/foo")
public class Foo {
@javax.inject.Inject
private Tenant tenant
}