Expand my Community achievements bar.

Enhance your AEM Assets & Boost Your Development: [AEM Gems | June 19, 2024] Improving the Developer Experience with New APIs and Events
SOLVED

Dispatching a request from a workflow process (5.6.1)

Avatar

Level 4

Hello,

We are creating a simple workflow process to automate a process that would generally be executed by a content manager.  The process is to click a button on the Page tab of the Sidekick.  The button is provided by ClayTablet (an integrated translation platform), but this is a superfluous detail; the important part is that the button results in a GET request to a path/servlet on the current author environment.

We've tried a few approaches:

  1. urlcaller process (out of the box) to make the HTTP request
  2. Custom WorkflowProcess which does nearly the same as urlcaller; uses Apache Commons HttpClient to submit the request
  3. Custom WorkflowProcess which creates a SyntheticResource bound to the path, attempting to dispatch that way; this was unsuccessful

We are currently reasonably happy with approach #2, included here:

package com.snip.workflow.process; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.adobe.granite.workflow.WorkflowException; import com.adobe.granite.workflow.WorkflowSession; import com.adobe.granite.workflow.exec.WorkItem; import com.adobe.granite.workflow.exec.WorkflowData; import com.adobe.granite.workflow.exec.WorkflowProcess; import com.adobe.granite.workflow.metadata.MetaDataMap; /** * The UpdateTranslationMemory workflow process step to support ClayTablet needs. */ @Component(enabled = true, immediate = true) @Service @Properties({ @Property(name = "process.label", value = "ClayTablet Update Translation Memory") }) public class UpdateTranslationMemoryProcessStep implements WorkflowProcess { private static final Logger LOG = LoggerFactory.getLogger(UpdateTranslationMemoryProcessStep.class); @Override public void execute(WorkItem item, WorkflowSession session, MetaDataMap args) throws WorkflowException { LOG.debug("Running UpdateTranslationMemoryProcessStep"); WorkflowData workflowData = item.getWorkflowData(); String path = workflowData.getPayload().toString(); String url = "<need hostname>/content/ctctranslation/updatetm/pagechangeinfo.html?action=submitUTM&path=" + path; HttpClient httpClient = new HttpClient(); GetMethod getMethod = new GetMethod(url); HttpClientParams httpClientParams = new HttpClientParams(); httpClientParams.setAuthenticationPreemptive(true); httpClient.setParams(httpClientParams); HttpState httpState = new HttpState(); httpState.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("updateTM", "superSecretPassword")); httpClient.setState(httpState); try { int responseCode = httpClient.executeMethod(getMethod); if (responseCode == 200) { LOG.debug("200 response for Update TM request for path: " + path); } else { LOG.error("Non-200 response while submitting Update TM request for path: " + path); } } catch (Exception e) { LOG.error("Exception while submitting Update TM request for path: " + path, e); } } }

In order to make the request with HttpClient, we've had to create a dummy user for submitting the GET request.  We would, however, prefer if the request were submitted by the same user that begins the workflow which includes this step.  Is this possible, either with this implementation, or with a completely different approach?

We would also like to determine if it is possible to make a request to the same hostname as the server, without using the Link Externalizer (which is not currently configured with the hostnames for all of our environments).  Is there a way to submit a domain-relative request, so this code does not need to determine the hostname?  Is there a way to dispatch a request internal to AEM/Sling, without going over HTTP?

Overall, is there a better way to solve the problem than the approach that I have posted?

 

Thanks,
Dave

1 Accepted Solution

Avatar

Correct answer by
Employee Advisor

If you would like to submit the request directly to be processed by Sling request processing engine then you can use SlingRequestProcessor API - https://docs.adobe.com/docs/en/cq/5-6/javadoc/index.html?org/apache/sling/engine/SlingRequestProcess...

Example - http://www.nateyolles.com/blog/2015/10/get-rendered-html-for-an-aem-resource-or-component

View solution in original post

7 Replies

Avatar

Correct answer by
Employee Advisor

If you would like to submit the request directly to be processed by Sling request processing engine then you can use SlingRequestProcessor API - https://docs.adobe.com/docs/en/cq/5-6/javadoc/index.html?org/apache/sling/engine/SlingRequestProcess...

Example - http://www.nateyolles.com/blog/2015/10/get-rendered-html-for-an-aem-resource-or-component

Avatar

Level 4

Thank you, kunal23.  

That was exactly what we were looking for in order to process the internal sling request.  Is there any way to determine the response code?  By logging the response output stream, I was able to determine that my initial attempt resulted in a 404 (because I included the query string in the request path, rather than breaking out to a params map), but it does not appear possible to determine the response code from the response object.  Any tips?

Also, I retrieve a ResourceResolver from the workflow's session parameter (session.adaptTo(ResourceResolver.class)), but I believe this is always authenticated as admin.  Is there any way to process the sling request as the user that starts the workflow?

Thanks again, this is incredibly useful.

Avatar

Employee Advisor

I guess you used the RequestResponseFactory service to get the dummy request, response objects for sending a request to the SlingRequestProcessor. The request, response objects this service creates are just mock objects and they do not have any implementation of setStatus and getStatus methods.  

To get the status of the response you can create your own response class implementing the HTTPServletResponse interface. In the setStatus method you can set the value passed in the method parameter to a global class level variable. And then in the getStatus method just return the value of the class level variable. 

The user who started the workflow can be determined by calling the following method - workItem.getWorkflow().getInitiator()

You can then get the resourceResolver of the initiator by using the impersonation API. Check the following example - https://cqwemblog.wordpress.com/2015/02/12/how-to-get-an-impersonated-resolver-in-cq5-aem/

Avatar

Level 4

Another excellent response, thank you so much.

Avatar

Level 4

kunal23 wrote...

I guess you used the RequestResponseFactory service to get the dummy request, response objects for sending a request to the SlingRequestProcessor. The request, response objects this service creates are just mock objects and they do not have any implementation of setStatus and getStatus methods.  

To get the status of the response you can create your own response class implementing the HTTPServletResponse interface. In the setStatus method you can set the value passed in the method parameter to a global class level variable. And then in the getStatus method just return the value of the class level variable. 

The user who started the workflow can be determined by calling the following method - workItem.getWorkflow().getInitiator()

You can then get the resourceResolver of the initiator by using the impersonation API. Check the following example - https://cqwemblog.wordpress.com/2015/02/12/how-to-get-an-impersonated-resolver-in-cq5-aem/

 

I've created an HttpServletResponseWrapper to implement setStatusCode, and wrapped the FakeResponse object with this class.  Unfortunately, it's still not working, as setStatusCode is never invoked.  Thoughts?  

I don't actually need an HTTP status code, I just need to know if the request was successful.  Is there another way to determine if the processed request was successful?

Avatar

Employee Advisor

It seems the response.setStatus is called only for the error conditions. So, if you don't get any status back then it will be a successful request. 

You can see the source yourself here - 

https://github.com/apache/sling/blob/7c4a53755aed1211c9af313a3973cd2543a7bbe0/bundles/engine/src/mai...

Avatar

Level 4

Thank you again, Kunal.  

I had already searched for the source of SlingRequestProcessorImpl for setStatus, and found none; I did, however, overlook the calls to sendError, which I suppose my wrapper also needs to implement, in order to set status code.  Once again, thank you.