用于包装请求正文的 Sling 过滤器

Sling Filter for Wrapping a Request Body

用例: 我们正在开发一个 AEM 封闭用户组网站,用户需要在该网站上提交触发工作流的表单。由于用户已通过身份验证,因此工作流负载的一部分需要包括启动表单的用户。

我正在考虑为此使用 AEM Forms,它保存到 /content/usergenerated/content/forms/af/my-site 下的节点,但负载中未提及用户(仅服务用户)。在这种情况下,有两个服务用户:workflow-service 运行 工作流和处理表单处理和初始保存的 fd-service。例如。从工作流步骤报告 'fd-service'

调用的以下代码
workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);

要解决此限制,

Workflow initiated from publish AEM instance: All workflow instances are created using a service user when adaptive forms, interactive communications, or letters are submitted from AEM publish instance. In these cases, the user name of the logged-in user is not captured in the workflow instance data.

我正在添加一个过滤器 servlet 以在 AEM Forms servlet 之前拦截初始表单提交,使用请求包装器修改添加原始用户 ID 的请求正文。

在表单、工作流程和启动器方面..这基本上是我的设置 https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html

我查看了以下资源:

这是我的包装器的代码

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import java.io.*;

public class FormSubmitRequestWrapper extends SlingHttpServletRequestWrapper {
String requestPayload;
private static final Logger log = LoggerFactory.getLogger(FormSubmitRequestWrapper.class);

public FormSubmitRequestWrapper(SlingHttpServletRequest slingRequest) {
    super(slingRequest);

    // read the original payload into the requestPayload variable
    StringBuilder stringBuilder = new StringBuilder();
    BufferedReader bufferedReader = null;
    try {
        // read the payload into the StringBuilder
        InputStream inputStream = slingRequest.getInputStream();
        if (inputStream != null) {
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            char[] charBuffer = new char[128];
            int bytesRead = -1;
            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } else {
            // make an empty string since there is no payload
            stringBuilder.append("");
        }
    } catch (IOException ex) {
        log.error("Error reading the request payload", ex);
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException iox) {
                log.error("Error closing bufferedReader", iox);
            }
        }
    }
    requestPayload = stringBuilder.toString();
}

/**
 * Override of the getInputStream() method which returns an InputStream that reads from the
 * stored requestPayload string instead of from the request's actual InputStream.
 */
@Override
public ServletInputStream getInputStream ()
        throws IOException {

    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestPayload.getBytes());
    ServletInputStream inputStream = new ServletInputStream() {
        public int read ()
                throws IOException {
            return byteArrayInputStream.read();
        }
    };
    return inputStream;
}
}

这是我的过滤器

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.EngineConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import javax.jcr.Session;
import javax.servlet.*;
import java.io.IOException;


@Component(service = Filter.class,
        immediate = true,

        property = {
                Constants.SERVICE_DESCRIPTION + "=Add the CUG userID to any UGC posts",
                EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
                Constants.SERVICE_RANKING + ":Integer=3000",
                EngineConstants.SLING_FILTER_PATTERN + "=/content/forms/af/my-site.*"
        })


public class DecorateUserGeneratedFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(DecorateUserGeneratedFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse ) response;
        final SlingHttpServletRequest slingRequest= (SlingHttpServletRequest) request;

        FormSubmitRequestWrapper wrappedRequest = new FormSubmitRequestWrapper(slingRequest);

        log.info("starting ConfirmAlumniStatus workflow");
        log.info(getCurrentUserId(slingRequest));

        chain.doFilter(wrappedRequest, slingResponse);
    }

    @Override
    public void destroy() {

    }

    public String getCurrentUserId(SlingHttpServletRequest request) {
        ResourceResolver resolver = request.getResourceResolver();
        Session session = resolver.adaptTo(Session.class);
        String userId = session.getUserID();

        return userId;

    }

}

当 POST 提交被此过滤器处理时,我收到以下错误,指出请求正文已被读取。看来筛选排名可能不够高。

25.06.2018 13:11:13.200 ERROR [0:0:0:0:0:0:0:1 [1529946669719] POST /content/forms/af/my-site/request-access/jcr:content/guideContainer.af.internalsubmit.jsp HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl service: Uncaught Throwable java.lang.IllegalStateException: Request Data has already been read at org.apache.sling.engine.impl.request.RequestData.getInputStream(RequestData.java:669) at org.apache.sling.engine.impl.SlingHttpServletRequestImpl.getInputStream(SlingHttpServletRequestImpl.java:292) at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:136) at my.site.servlets.FormSubmitRequestWrapper.(FormSubmitRequestWrapper.java:26) at my.site.servlets.DecorateUserGeneratedFilter.doFilter(DecorateUserGeneratedFilter.java:75) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73) at com.cognifide.cq.includefilter.DynamicIncludeFilter.doFilter(DynamicIncludeFilter.java:82) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68) at org.apache.sling.engine.impl.debug.RequestProgressTrackerLogFilter.doFilter(RequestProgressTrackerLogFilter.java:10

我认为服务排名不起作用。当我查看 http://localhost:4502/system/console/status-slingfilter 我的过滤器如图所示列出。从列出的其他过滤器来看,我认为最左边的数字是过滤器排名。出于某种原因,我的过滤器排名 0,即使我设置为 service.ranking=700

0 : class my.site.servlets.DecorateUserGeneratedFilter (id: 8402, property: service.ranking=700); called: 0; time: 0ms; time/call: -1µs

更新:我能够修复过滤器等级,使其达到 700 时仍然会出现 IllegalStateException。把它设为 3000 就可以解决这个问题。但是当从我的包装器调用 request.getInputStream() 时。它 returns 无效。

正确的想法,错误的位置。

简短的回答是,当您实现 SlingHttpServletRequestWrapper 时,如果您动态添加参数,它会提供对原始 SlingHttpServletRequest 方法调用的默认处理,而您想要做的是确保方法正在与参数进行交互的参数被覆盖,以便您可以确定您的参数已被添加。因此在初始化时,调用原始参数映射,将这些项目复制到包含您自己的值的新映射中。

然后覆盖任何请求这些值的方法

getParameter(String)
getParameterMap()
getParameterNames()
getParameterValues(String)

不要碰 InputStream,它已经被处理以获取传入的任何参数。

此外,这是处理此类用例的两种方法之一,另一种选择是使用 SlingPOSTProcessors,如文档所述 https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html

它允许您检测正在写入存储库的内容并修改数据以包括一个附加字段,就像您的情况一样。

您尝试做的事情可能是最简单的方法,但对于新的 AEM 版本来说可能不是面向未来的。

您需要完全控制工作流程的触发方式!:

  1. 您的表单应该有一个包含工作流路径的字段(可能还有该工作流所需的其他信息)
  2. 创建您的表单将 post 添加到的自定义 servlet。
  3. 在该 servlet 中处理所有用户 posted 值(来自表单)。但尤其要掌握预期的工作流路径并使用工作流 API.
  4. 触发它

这样您就不必弄乱启动器,工作流程由您的用户使用他们的用户 ID 触发。

希望对您有所帮助。

如果您正在寻找代码示例:

      @SlingFilter(order = 1)
    public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    return;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
        FilterChain filterChain) throws IOException, ServletException {

    ServletRequest request = servletRequest;

    if (servletRequest instanceof SlingHttpServletRequest) {
        final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
            request = new SlingHttpServletRequestWrapper(slingRequest) {
              String userId = getCurrentUserId(slingRequest);
            };

    }

    filterChain.doFilter(request, servletResponse);
}

@Override
public void destroy() {
    return;
}