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.
I'm considering using AEM Forms for this, which saves to nodes under /content/usergenerated/content/forms/af/my-site
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'
workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
To work around this constraint,
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.
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.
In terms of forms, workflows and launchers.. This is basically the setup I have https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html
I have reviewed the following resources:
- How to change servlet request body in java filter?
- https://coderanch.com/t/364591/java/read-request-body-filter
- https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71
- https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html
Here is the code for my wrapper
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;
}
}
Here is my filter
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;
}
}
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.
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
I don't think the service ranking is working. When I view http://localhost:4502/system/console/status-slingfilter 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
0 : class my.site.servlets.DecorateUserGeneratedFilter (id: 8402, property: service.ranking=700); called: 0; time: 0ms; time/call: -1µs
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.
userId
is not really documented? and can change in future impl. I'd recommend going a different route with a servlet that triggers your workflows. (I'll post an answer shortly) – Ahmed Musallam