Java EE拦截器中止响应

时间:2018-01-01 12:51:24

标签: java-ee cdi interceptor

我尝试制作多租户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);
        }
      }
    }
  }
}

提前致谢:)

2 个答案:

答案 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
}