title: Sling Filter for Wrapping a Request Body
body: <p>Use Case:
  We are developing an AEM Closed User Group site where users will need to submit forms which trigger workflows. Since the users are authenticated, part of the workflow payload needs to include the user who initiated the form. </p>
  
  <p>I'm considering using AEM Forms for this, which saves to nodes under <code>/content/usergenerated/content/forms/af/my-site</code> but the user is not mentioned in the payload (only the service user). In this case, there are two service users: workflow-service running the workflow, and fd-service which handled the form processing and initial saving. E.G. the following code called from the workflow step reports 'fd-service'</p>
  
  <pre><code>workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
  </code></pre>
  
  <p>To work around this constraint, </p>
  
  <blockquote>
    <p>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.</p>
  </blockquote>
  
  <p>I am adding a filter servlet to intercept the initial form submission before the AEM Forms servlet using a request wrapper to modify the request body adding the original userID. </p>
  
  <p>In terms of forms, workflows and launchers.. This is basically the setup I have
  <a href="https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html" rel="nofollow noreferrer">https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html</a></p>
  
  <p>I have reviewed the following resources:</p>
  
  <ul>
  <li><a href="https://stackoverflow.com/questions/34155480/how-to-change-servlet-request-body-in-java-filter">How to change servlet request body in java filter?</a></li>
  <li><a href="https://coderanch.com/t/364591/java/read-request-body-filter" rel="nofollow noreferrer">https://coderanch.com/t/364591/java/read-request-body-filter</a></li>
  <li><a href="https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71" rel="nofollow noreferrer">https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71</a></li>
  <li><a href="https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html" rel="nofollow noreferrer">https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html</a></li>
  </ul>
  
  <p>Here is the code for my wrapper</p>
  
  <pre><code>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)) &gt; 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;
  }
  }
  </code></pre>
  
  <p>Here is my filter</p>
  
  <pre><code>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;
  
      }
  
  }
  </code></pre>
  
  <p>When POST submissions get processed by this filter, I'm getting the error below stating the request body has already been read. So it seems the filter ranking might not be high enough.</p>
  
  <blockquote>
    <p>25.06.2018 13:11:13.200 <em>ERROR</em> [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</p>
  </blockquote>
  
  <p>I don't think the service ranking is working. When I view 
  <a href="http://localhost:4502/system/console/status-slingfilter" rel="nofollow noreferrer">http://localhost:4502/system/console/status-slingfilter</a>
  my filter is listed as shown. Judging from the other filters listed, I think the leftmost number is the filter ranking. For some reason my filter is ranked 0 even though I set is as service.ranking=700</p>
  
  <blockquote>
    <p>0 : class my.site.servlets.DecorateUserGeneratedFilter (id:
    8402, property: service.ranking=700); called: 0; time: 0ms; time/call:
    -1µs</p>
  </blockquote>
  
  <p>Update: I was able to fix the filter rank, making it 700 still gave the IllegalStateException. Making it 3000 made that problem go away. But when request.getInputStream() is called from my wrapper. It returns null.</p>