com.adobe.cq.testing.client.WorkflowClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-cloud-testing-clients Show documentation
Show all versions of aem-cloud-testing-clients Show documentation
AEM related clients and testing utilities for AEM as a Cloud Service
/*
* Copyright 2017 Adobe Systems Incorporated
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.adobe.cq.testing.client;
import com.adobe.cq.testing.client.workflow.*;
import org.apache.http.NameValuePair;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.sling.testing.clients.ClientException;
import org.apache.sling.testing.clients.SlingClientConfig;
import org.apache.sling.testing.clients.SlingHttpResponse;
import org.apache.sling.testing.clients.util.FormEntityBuilder;
import org.apache.sling.testing.clients.util.HttpUtils;
import org.apache.sling.testing.clients.util.JsonUtils;
import org.apache.sling.testing.clients.util.ResourceUtil;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import org.joda.time.DateTime;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.http.HttpStatus.*;
/**
* Implements the Workflow REST API calls
* ModelID = handle to the model Node
*/
public class WorkflowClient extends CQClient {
/**
* Request Path to manage workflow instances
**/
public static final String MANAGE_WF_INSTANCES_PATH = "/etc/workflow/instances";
/**
* Request Path to manage workflow models
*/
public static final String MANAGE_WF_MODELS_PATH = "/etc/workflow/models";
/**
* Request Path to manage users workflow inbox
*/
public static final String MANAGE_WF_INBOX_PATH = "/bin/workflow/inbox";
/**
* Request Path to manage workflow engine
*/
public static final String MANAGE_WF_ENGINE_PATH = "/etc/workflow";
/**
* Type of model list to return, model id = model node handle
*/
public static final String MODEL_LIST_TYPE_ID = "id";
/**
* Type of models list to return, uri = root context + model id
*/
public static final String MODEL_LIST_TYPE_URI = "uri";
/**
* Defines what type of model definition gets passed, currently only JSON
* format is implemented
*/
public static final String MODEL_DESCR_TYPE_JSON = "JSON";
/**
* Type of payload referencing used when starting a workflow, in this case a
* JCR Path
*/
public static final String PAYLOAD_TYPE_JCR = "JCR_PATH";
/**
* Type of payload referencing used when starting a workflow, in this case a
* URL Path
*/
public static final String PAYLOAD_TYPE_URL = "URL";
/**
* Enumeration of the different Workflow Statuses
*/
public enum Status {
RUNNING("RUNNING"), SUSPENDED("SUSPENDED"), ABORTED("ABORTED"), COMPLETED("COMPLETED");
private String status;
/**
* Initiates a enum with a string
*
* @param status
* the string associated with this enum value
*/
Status(String status) {
this.status = status;
}
/**
* returns the string associated with this status enum
*
* @return the string value for this enum
*/
public String getString() {
return status;
}
}
public WorkflowClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
super(http, config);
}
public WorkflowClient(URI serverUrl, String user, String password) throws ClientException {
super(serverUrl, user, password);
}
/**
* Creates a new workflow model in the repository by sending a model
* definition.
*
* Handled by:
*
* granite\bundles\workflow\console\src\main\java\com\adobe\granite\workflow
* \console\servlet\ModelsServlet.java
*
* @param modelSourcePath
* path to model
* @param modelType
* if null or MODEL_DESCR_TYPE_JSON, the model description should
* be in JSON format.
* @param expectedStatus
* list of allowed HTTP Status to be returned. if not set, http
* status 201 (CREATED) is assumed.
*
* @throws ClientException
* If something fails during request/response cycle
* @return the path to the newly created model, equals to the models ID
* @throws IOException
* if access to to the JSON resource fails
*/
public String deployModel(String modelSourcePath, String modelType, int... expectedStatus) throws ClientException, IOException {
// check for default value
if (modelType == null)
modelType = MODEL_DESCR_TYPE_JSON;
// get the JSON definition
String model = ResourceUtil.readResourceAsString(modelSourcePath);
// build the request
SlingHttpResponse response = doPost(MANAGE_WF_MODELS_PATH,
FormEntityBuilder.create().addParameter("model", model).addParameter("type", modelType).build());
// check the returned status
HttpUtils.verifyHttpStatus(response, HttpUtils.getExpectedStatus(SC_CREATED, expectedStatus));
// if it was a valid request
if (response.getStatusLine().getStatusCode() == SC_CREATED) {
// the location header will contain the URL to the newly deployed
// model
String url = response.getHeaders("Location")[0].getValue();
// return the model id
return getPath(url).toString();
}
return null;
}
/**
* same as {@link #deployModel(String, String, int...)}, but with a list of strings to replace in the model
* @param modelSourcePath resource path of the model
* @param modelType model type
* @param replaces list of strings pairs to replace
* @param expectedStatus expected status of the http response
* @return model id
* @throws ClientException of the request failed
* @throws IOException if the model cannot be read
*/
public String deployModel(String modelSourcePath, String modelType, List replaces, int... expectedStatus)
throws ClientException, IOException {
// check for default value
if (modelType == null)
modelType = MODEL_DESCR_TYPE_JSON;
// get the JSON definition
String model = ResourceUtil.readResourceAsString(modelSourcePath);
for (NameValuePair replacePair : (replaces != null) ? replaces : new ArrayList(0)) {
model = model.replaceAll(replacePair.getName(), replacePair.getValue());
}
// build the request
SlingHttpResponse response = doPost(MANAGE_WF_MODELS_PATH,
FormEntityBuilder.create().addParameter("model", model).addParameter("type", modelType).build());
// check the returned status
HttpUtils.verifyHttpStatus(response, HttpUtils.getExpectedStatus(SC_CREATED, expectedStatus));
// if it was a valid request
if (response.getStatusLine().getStatusCode() == SC_CREATED) {
// the location header will contain the URL to the newly deployed
// model
String url = response.getHeaders("Location")[0].getValue();
// return the model id
return getPath(url).toString();
}
return null;
}
/**
* Advances a workflow item trough the next step.
*
* Handled by:
* granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\InboxServlet.java
*
* @param workItemId
* the work item to advance
* @param routeId
* the route it should take
* @param expectedStatus
* The HTTP status that should be returned, otherwise 200 is
* assumed.
* @return The request executor if access to response is needed.
* @throws ClientException
* if HTTP request fails for any reason.
*/
public SlingHttpResponse advanceWorkItem(String workItemId, String routeId, int... expectedStatus) throws ClientException {
SlingHttpResponse exec = doPost(MANAGE_WF_INBOX_PATH,
FormEntityBuilder.create().addParameter("item", workItemId).addParameter("route", routeId).build());
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return executor
return exec;
}
/**
* Choose the route and advance with it
*
* @param workflowInstancePath
* the source of the workflow instance
* @param chooseRoute
* based on the model at the expected step, the route that should
* be used (0, 1, 2, etc..)
* @return The request executor as list if access to response is needed for
* each of the workitems (in case more than one was found).
* @throws ClientException
* if HTTP request fails for any reason.
*/
public List chooseRouteAndAdvance(String workflowInstancePath, int chooseRoute)
throws ClientException {
List responses = new ArrayList<>();
WorkflowInstance wfInstance = getWorkflowInstance(workflowInstancePath);
List wfWkItemIds = wfInstance.getWorkItemIds();
for (String wfWkItemId : wfWkItemIds) {
WorkItem wkItem = getWorkItemByURI(wfWkItemId);
ArrayList arrayRoutes = wkItem.getRoutes();
responses.add(advanceWorkItem(wkItem.getId(), arrayRoutes.get(chooseRoute).getId()));
}
return responses;
}
/**
* Advances a workflow item back to a previous step.
*
* Handled by:
* granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\InboxServlet.java
*
* @param workItemId
* the work item to advance
* @param backrouteId
* the route it should take
* @param expectedStatus
* The HTTP status that should be returned, otherwise 200 is
* assumed.
* @return The request executor if access to response is needed.
* @throws ClientException
* if HTTP request fails for any reason.
*/
public SlingHttpResponse stepBackWorkItem(String workItemId, String backrouteId, int... expectedStatus) throws ClientException {
SlingHttpResponse exec = doPost(MANAGE_WF_INBOX_PATH,
FormEntityBuilder.create().addParameter("item", workItemId).addParameter("backroute", backrouteId).build());
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return executor
return exec;
}
/**
* Delegates a workflow item to an other user
*
* Handled by:
* granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\InboxServlet.java
*
* @param workItemId
* the work item to delegate
* @param delegatee
* the user/group this work item gets delegated to
* @param expectedStatus
* The HTTP status that should be returned, otherwise 200 is
* assumed.
* @return The request executor if access to response is needed.
* @throws ClientException
* if HTTP request fails for any reason.
*/
public SlingHttpResponse delegateWorkItem(String workItemId, String delegatee, int... expectedStatus) throws ClientException {
SlingHttpResponse exec = doPost(MANAGE_WF_INBOX_PATH,
FormEntityBuilder.create().addParameter("item", workItemId).addParameter("delegatee", delegatee).build());
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return executor
return exec;
}
/**
* Approves a workflow item and advanced to another node.
*
* Handled by:
* granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\InboxServlet.java
*
* @param workItem
* the work item to delegate
* @param routeId
* the user/group this work item gets delegated to
* @param expectedStatus
* The HTTP status that should be returned, otherwise 200 is
* assumed.
* @return The request executor if access to response is needed.
* @throws ClientException
* if HTTP request fails for any reason.
*/
public SlingHttpResponse approveAndAdvanceWorkItem(WorkItem workItem, String routeId, int... expectedStatus) throws ClientException {
doPost(workItem.getPayLoad(),
FormEntityBuilder.create()
.addParameter("item", workItem.getId())
.addParameter("route", routeId)
.addParameter("./approved", "true")
.addParameter("./approved@Delete", "true")
.addParameter("./approved@TypeHint", "Boolean")
.build());
SlingHttpResponse exec = doPost(MANAGE_WF_INBOX_PATH,
FormEntityBuilder.create()
.addParameter("item", workItem.getId())
.addParameter("route", routeId)
.addParameter("./approved", "true")
.addParameter("./approved@Delete", "true")
.addParameter("./approved@TypeHint", "Boolean")
.build());
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return executor
return exec;
}
public SlingHttpResponse approveComment(String workItemId, String routeId, int... expectedStatus) throws ClientException {
SlingHttpResponse exec = doPost(MANAGE_WF_INBOX_PATH,
FormEntityBuilder.create().addParameter("item", workItemId).addParameter("route", routeId).addParameter("./approved", "true")
.addParameter("./approved@Delete", "true").addParameter("./approved@TypeHint", "Boolean").build());
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return executor
return exec;
}
/**
* Returns the list of work item that are currently in the inbox of this
* client's user. The returned map is using the work items uri as key.
*
* handled by:
* granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\InboxServlet.java
*
* @param expectedStatus
* The HTTP status that should returned, othewise 200 is assumed.
* @return A map where key ist the uri of the stored work item
* @throws ClientException
* if requesting the JSON fails
*/
public Map getInboxItems(int... expectedStatus) throws ClientException {
// make the request
SlingHttpResponse exec = doGet(MANAGE_WF_INBOX_PATH + ".json");
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// get the map
JsonNode workItems = JsonUtils.getJsonNodeFromString(exec.getContent());
Map map = new HashMap<>();
for (int i = 0; i < workItems.size(); i++) {
JsonNode workItem = workItems.get(i);
map.put(workItem.get("uri").getValueAsText(), new InboxItem(workItem));
}
return map;
}
/**
* Returns all available information about a work item.
*
* @param uri the URI pointing to the work item node
* @param expectedStatus the HTTP status that should be returned, otherwise 200 is assumed
* @return the Work item wrapper
* @throws ClientException if requesting the JSON fails for any reason
*/
public WorkItem getWorkItemByURI(String uri, int... expectedStatus) throws ClientException {
// make the request
SlingHttpResponse exec = doGet(uri + ".json");
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return the wrapper
return new WorkItem(JsonUtils.getJsonNodeFromString(exec.getContent()));
}
public List getWorkItemBackRouteIds(String uri, int... expectedStatus) throws ClientException {
// make the request
SlingHttpResponse exec = doGet(uri + ".backroutes.json");
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return the wrapper
JsonNode rootNode = JsonUtils.getJsonNodeFromString(exec.getContent()).get("backroutes");
ArrayList backrouteIds = new ArrayList<>();
for (int i = 0; i < rootNode.size(); i++) {
backrouteIds.add(rootNode.get(i).get("rid").getValueAsText());
}
return backrouteIds;
}
/**
* Deletes a workflow model definition. Note that the model node does not
* get removed. It only gets a property set in its Metadata sub node named
* 'deleted' with a value of true.
*
* In order to solve firewall/proxy issues a {@code POST} that contains a
* {@code X-HTTP-Method-Override} header entry with value {@code DELETE} is
* sent instead of the proper {@code HTTP DELETE}.
*
* Handled by:
*
* granite\bundles\workflow\console\src\main\java\com\adobe\granite\workflow
* \console\servlet\ModelServlet.java
*
* @param modelId
* the id of the model to delete
* @param expectedStatus
* HTTP status to be returned, otherwise 20 (NO CONTENT) is
* assumed
* @throws ClientException
* if POST request fails for any reason
* @return the request executor containing the response object and content.
*/
public SlingHttpResponse deleteModel(String modelId, int... expectedStatus) throws ClientException {
String url = modelId + ".json";
SlingHttpResponse exec = doDelete(url, null, null);
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_NO_CONTENT, expectedStatus));
return exec;
}
/**
* Aborts/Terminates a running workflow instance.
*
* @param instanceId instanceId (nodepath) of the running workflow instance
* @param expectedStatus if not set it defaults to 200
* @return the request executor containing the repsone object and content
* @throws ClientException if POST fails for any reasons
*/
public SlingHttpResponse abortWorkflow(String instanceId, int... expectedStatus) throws ClientException {
// build the post request
SlingHttpResponse exec = doPost(instanceId, FormEntityBuilder.create().addParameter("state", "ABORTED")
.addParameter("terminateComment", "").addParameter("_charset_","utf-8").build());
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
return exec;
}
/**
* updates an existing workflow model in the repository by sending a updated
* model definition.
*
* Handled by:
*
* granite\bundles\workflow\console\src\main\java\com\adobe\granite\workflow
* \console\servlet\ModelServlet.java
*
* @param modelSourcePath
* path to model
* @param modelType
* if null or MODEL_DESCR_TYPE_JSON, the model description should
* bE in JSON format.
* @param modelId
* the model to be updated
* @param expectedStatus
* list of allowed HTTP Status to be returned. if not set, http
* status 200 (ok) is assumed.
*
* @throws ClientException
* If something fails during request/response cycle
* @return The request executor used to send the request
* @throws IOException
* if access to to the JSON resource fails
*/
public SlingHttpResponse updateModel(String modelSourcePath, String modelType, String modelId, int... expectedStatus)
throws ClientException, IOException {
// check for default value
if (modelType == null)
modelType = MODEL_DESCR_TYPE_JSON;
// get the JSON definition
String model = getJsonDefinition(modelSourcePath, modelId);
// build the request
SlingHttpResponse exec = doPost(modelId, FormEntityBuilder.create().addParameter("model", model).addParameter("type", modelType)
.build());
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
return exec;
}
private String getJsonDefinition(String modelSourcePath, String modelId) throws IOException {
// read data
String model = ResourceUtil.readResourceAsString(modelSourcePath);
// read as JSON
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.readValue(model, ObjectNode.class);
// add the modelId
node.put("id", modelId);
// return the updated form
return node.toString();
}
/**
* Gets the workflow engine info from {@value #MANAGE_WF_ENGINE_PATH}.
*
* Handled by:
* granite\bundles\workflow\content\src\main\content\jcr_root
* \libs\cq\workflow\components\engine\json.jsp
*
* @param expectedStatus
* The HTTP Status to be returned otherwise 200 is assumed
* @return the Workflow Engine wrapper
* @throws ClientException
* if the request for the JSON data fails.
*/
public WorkflowEngine getWorkflowEngineInfo(int... expectedStatus) throws ClientException {
SlingHttpResponse exec = doGet(MANAGE_WF_ENGINE_PATH + ".json", expectedStatus);
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// parse the returned content into a JSON node
JsonNode rootNode = JsonUtils.getJsonNodeFromString(exec.getContent());
// turn properties into a map
HashMap properties = new HashMap<>();
// iterate over properties
rootNode.getFieldNames();
for (Iterator it = rootNode.getFieldNames(); it.hasNext();) {
String propName = it.next();
properties.put(propName, rootNode.get(propName).getValueAsText());
}
// return the result
return new WorkflowEngine(properties);
}
/**
* Stops the Workflow Engine, equals to stopping the Workflow service.
*
* Handled by:
*
* granite\bundles\workflow\console\src\main\java\com\adobe\granite\workflow
* \console\servlet\EngineServlet.java
*
* @param expectedStatus
* The HTTP Status to be returned otherwise 200 is assumed
* @return returns the request executor, if more info from the response is
* required.
* @throws ClientException
* if the POST request fails.
*/
public SlingHttpResponse stopWorkflowEngine(int... expectedStatus) throws ClientException {
// sent the new engine state to 'DISABLED'
SlingHttpResponse exec = doPost(MANAGE_WF_ENGINE_PATH, FormEntityBuilder.create().addParameter("state", "DISABLED").build());
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
return exec;
}
/**
* Starts the Workflow Engine, equals to starting the Workflow service.
*
* Handled by:
*
* granite\bundles\workflow\console\src\main\java\com\adobe\granite\workflow
* \console\servlet\EngineServlet.java
*
* @param expectedStatus
* The HTTP Status to be returned otherwise 200 is assumed
* @return returns the request executor, if more info from the response is
* required.
* @throws ClientException
* if the POST request fails.
*/
public SlingHttpResponse startWorkflowEngine(int... expectedStatus) throws ClientException {
// sent the new engine state to 'ACTIVE'
SlingHttpResponse exec = doPost(MANAGE_WF_ENGINE_PATH, FormEntityBuilder.create().addParameter("state", "ACTIVE").build());
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
return exec;
}
/**
* Checks if the workflow engine is in an active state (actually its the
* workflow OSGi service that gets tested).
*
* @return true if the Workflow Engine is active otherwise false.
* @throws ClientException
* if requesting Workflow Info fails
*/
public boolean isWorkflowEngineActive() throws ClientException {
return "active".equals(getWorkflowEngineInfo().getProperty(WorkflowEngine.STATE).toLowerCase());
}
/**
* Convenience function for the most common case , using JCR path to
* reference the payload.
*
* @param modelId
* the workflow model id to be used.
* @param payload
* the payload JCR path to be set.
* @param expectedStatus
* The HTTP Status to be returned otherwise 201 is assumed
* @return the id of the new workflowInstance
* @throws ClientException
* if the POST request fails
*/
public String startWorkflow(String modelId, String payload, int... expectedStatus) throws ClientException {
return startWorkflow(modelId, PAYLOAD_TYPE_JCR, payload, null, null, null, expectedStatus);
}
/**
* Starts a new workflow using the given model id, with the payload
* specified.
*
* Handled by:
*
* granite\bundles\workflow\console\src\main\java\com\adobe\granite\workflow
* \console\servlet\InstancesServlet.java
*
* @param modelId
* The workflow model Id to be used.
* @param payloadType
* The type of payload referencing, either
* {@link #PAYLOAD_TYPE_JCR} or {@link #PAYLOAD_TYPE_URL} . if
* set to null , {#PAYLOAD_TYPE_JCR} is used as default
* @param metaData
* any meta data that needs to be passed when starting the
* workflow
* @param title
* title to be set for this workflow instance
* @param comment
* comment to be set when starting the workflow
* @param expectedStatus
* The HTTP Status to be returned otherwise 201 is assumed
* @param payload
* the JCR path(s) or URL(s) pointing to the server resource that
* act as payload. each payload entry will start a new workflow
* instance
* @return the id of the new workflowInstance
* @throws ClientException
* if the POST request fails
*/
public String startWorkflow(String modelId, String payloadType, String payload, Map metaData, String title,
String comment, int... expectedStatus) throws ClientException {
// if modelId is not set, fail
if (modelId == null || "".equals(modelId))
throw new ClientException("Invalid ModelId!");
// if no payload is given , fail
if (payload == null || "".equals(payload))
throw new ClientException(("No payload set!"));
// set the default for the type if needed
if (payloadType == null)
payloadType = PAYLOAD_TYPE_JCR;
// build the form entity
FormEntityBuilder form = FormEntityBuilder.create();
form.addParameter("model", modelId);
form.addParameter("payloadType", payloadType);
form.addParameter("payload", payload);
if (title != null && !"".equals(title)) {
form.addParameter("workflowTitle", title);
}
if (comment != null && !"".equals(comment)) {
form.addParameter("startComment", comment);
}
if (metaData != null) {
for (String key : metaData.keySet()) {
form.addParameter(key, metaData.get(key));
}
}
// do the post
SlingHttpResponse response = doPost(MANAGE_WF_INSTANCES_PATH, form.build());
// check returned status
HttpUtils.verifyHttpStatus(response, HttpUtils.getExpectedStatus(SC_CREATED, expectedStatus));
// if successfully created
if (response.getStatusLine().getStatusCode() == SC_CREATED) {
// return the instance path
return getPath(response.getSlingLocation()).toString();
} else {
return null;
}
}
/**
* Convenience functions to update/set the comment in the workflow instance
* metadata.
*
* @param instanceURI
* the workflow instance to be updated
* @param comment
* the comment of the workflow instance to be set
* @param expectedStatus
* The HTTP Status to be returned otherwise 200 is assumed
* @return the request executor containing the response object and content.
* @throws ClientException
* if the POST request fails.
*/
public SlingHttpResponse updateWfInstanceComment(String instanceURI, String comment, int... expectedStatus) throws ClientException {
HashMap map = new HashMap<>();
map.put("startComment", comment);
return updateWfInstanceMetaData(instanceURI, map, expectedStatus);
}
/**
* Convenience function to set edit/set the title of a workflow instance in
* its meta data.
*
* @param instanceURI
* the workflow instance to be updated
* @param title
* the title of the workflow instance to be set
* @param expectedStatus
* The HTTP Status to be returned otherwise 200 is assumed
* @return the request executor containing the response object and content.
* @throws ClientException
* if the POST request fails.
*/
public SlingHttpResponse updateWfInstanceTitle(String instanceURI, String title, int... expectedStatus) throws ClientException {
HashMap map = new HashMap<>();
map.put("workflowTitle", title);
return updateWfInstanceMetaData(instanceURI, map, expectedStatus);
}
/**
* Sends an update request to a workflow instance to update/add metadata
*
* @param instanceURI
* the workflow instance to be updated
* @param metaData
* the metadata to be added /updated
* @param expectedStatus
* The HTTP Status to be returned otherwise 200 is assumed
* @return the request executor containing the response object and content.
* @throws ClientException
* if the POST request fails.
*/
public SlingHttpResponse updateWfInstanceMetaData(String instanceURI, Map metaData, int... expectedStatus)
throws ClientException {
// build the form entity
FormEntityBuilder form = FormEntityBuilder.create();
form.addParameter("action", "UPDATE");
// add metadata
for (String key : metaData.keySet()) {
form.addParameter(key, metaData.get(key));
}
SlingHttpResponse exec = doPost(instanceURI, form.build());
// check returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
return exec;
}
/**
* Returns a list of workflow instance URI's that are visible for the user.
* Can be filtered by the instance status.
*
* Handled by :
*
* granite\bundles\workflow\content\src\main\content\jcr_root\libs\cq\
* workflow\components\instances\json.jsp
*
* @param wfStatus
* Only returns workflows that are in one of the possible States
* ( {@link Status#RUNNING}, {@link Status#ABORTED},
* {@link Status#COMPLETED} or {@link Status#SUSPENDED}), or, if
* set to null, returns all workflow instances.
* @param expectedStatus HTTP Status to check for or, if not set, 200 is assumed
* @return A String List containing all workflow instance handles
* visible to this user according to the filter set in {@code wfStatus}.
* @throws ClientException if the call to the backend requesting the JSON fails
*/
public List getWorkflowInstanceURLs(Status wfStatus, int... expectedStatus) throws ClientException {
// default Status
String status = "";
// if a specific Status filter is set
if (wfStatus != null) {
status = "." + wfStatus.getString();
}
// execute the request
SlingHttpResponse exec = doGet(MANAGE_WF_INSTANCES_PATH + status + ".json");
// check the returned status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// parse the returned content into a JSON node
JsonNode rootNode = JsonUtils.getJsonNodeFromString(exec.getContent());
// turn it into a string array
ArrayList wfInstances = new ArrayList<>();
// iterate over the URI's
for (JsonNode aRootNode : rootNode) {
try {
wfInstances.add(new URI(aRootNode.get("uri").getValueAsText()));
} catch (URISyntaxException e) {
throw new ClientException("Error parsing url: " + aRootNode.get("uri").getValueAsText(), e);
}
}
// return the result
return wfInstances;
}
/**
* Returns a list of all available workflow models for this user. Handled
* by: granite\bundles\workflow\content\src\main\content\jcr_root\libs\cq\
* workflow\components\models\json.jsp
*
* @param listType
* if set to MODEL_LIST_TYPE_ID , a list of model ID's gets
* returned (model ID = model Handle) if set to null or
* MODEL_LIST_TYPE_URI, a list of model URI's gets returned (URI
* = context path + model ID)
* @param expectedStatus
* HTTP Status to check for or, if not set, 200 is assumed
* @return A String array containing a list of all workflow model visible to
* this user
* @throws ClientException
* if requesting the JSON fails for any reason
*/
public ArrayList getWorkflowModels(String listType, int... expectedStatus) throws ClientException {
// set default value for listType if not set
if (listType == null)
listType = MODEL_LIST_TYPE_URI;
// execute the request
SlingHttpResponse exec = doGet(MANAGE_WF_MODELS_PATH + ".json",
Collections.singletonList(new BasicNameValuePair("format", listType)),
HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// parse the returned content into a JSON node
JsonNode rootNode = JsonUtils.getJsonNodeFromString(exec.getContent());
// turn it into a string array
ArrayList wfModels = new ArrayList<>();
// depending on list type we need to read a different node property
String nodeProperty = "uri";
if (listType.equals(MODEL_LIST_TYPE_ID))
nodeProperty = "value";
// iterate over the returned entries
for (JsonNode aRootNode : rootNode) {
wfModels.add(aRootNode.get(nodeProperty).getValueAsText());
}
// return the result
return wfModels;
}
/**
* Returns the model definition (metadata, nodes, transitions,title
* ,description etc)
*
* handled by:
* \granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\ModelServlet.java
*
* @param modelId
* the model id to request
* @param version
* the version to retrieve. Either 'HEAD' or null to get the
* latest version or in the format major.minor e.g. '1.2'.
* @param expectedStatus
* the expected HTTP Status, if not set 200 is assumed
* @return the requested version of the model definition in JSON format
*
* @throws ClientException
* If requesting the JSON fails for any reason
*/
public String getWorkflowModelAsJSON(String modelId, String version, int... expectedStatus) throws ClientException {
// if no specific version was requested
if (version == null || "HEAD".equals(version)) {
version = "";
} else {
// check the passed version pattern
Pattern p = Pattern.compile("^\\d+\\.\\d+$");
Matcher m = p.matcher(version);
// if pattern does not match
if (!m.find()) {
// and invalid version number was passed then
throw new IllegalArgumentException("Version identifier " + version + " is invalid!");
}
version = "." + version;
}
// request the json definition
String uri = modelId + version + ".json";
SlingHttpResponse exec = doGet(uri);
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return the response content
return exec.getContent();
}
/**
* Returns the model definition (metadata, nodes, transitions,title
* ,description etc) in a wrapper class.
*
* handled by:
* \granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\ModelServlet.java
*
* @param modelId
* the model id to request
* @param version
* the version to retrieve. Either 'HEAD' or null to get the
* latest version or in the format major.minor e.g. '1.2'.
* @param expectedStatus
* the expected HTTP Status, if not set 200 is assumed
* @return the requested version of the model definition in JSON format
*
* @throws ClientException
* If requesting the JSON fails for any reason
*/
public WorkflowModel getWorkflowModel(String modelId, String version, int... expectedStatus) throws ClientException {
// get the json definition from the server and wrap it
return new WorkflowModel(JsonUtils.getJsonNodeFromString(getWorkflowModelAsJSON(modelId, version, expectedStatus)));
}
/**
* Returns all the information about a workflow instance in JSON format.
*
* Handled by:
*
* granite\bundles\workflow\content\src\main\content\jcr_root\libs\cq\
* workflow\components\instance\json.jsp
*
* @param instanceURI
* the path to the workflow instance
* @param expectedStatus
* the expected HTTP status, otherwise 200 is assumed
* @return A WorkflowInstance that wraps the JSON response.
* @throws ClientException
* if requesting the JSON fails
*/
public WorkflowInstance getWorkflowInstance(String instanceURI, int... expectedStatus) throws ClientException {
// get the data
SlingHttpResponse exec = doGet(instanceURI + ".json");
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return the json in a light wrapper for easy data extraction
return new WorkflowInstance(JsonUtils.getJsonNodeFromString(exec.getContent()));
}
public List getWorkflowInstances(Status wfStatus, String model, String payload, DateTime startTimeLimit,
DateTime endTimeLimit) throws ClientException {
List urls = getWorkflowInstanceURLs(wfStatus);
List workflows = new ArrayList<>();
// get all the workflows and add them to a list
for (URI url : urls) {
WorkflowInstance workflowInstance;
try {
workflowInstance = getWorkflowInstance(getPath(url).toString());
} catch (ClientException e) {
continue;
}
Date start = workflowInstance.getStartTime();
Date end = workflowInstance.getEndTime();
DateTime wfStart = (null==start) ? null : new DateTime(start);
DateTime wfEnd = (null==end) ? null : new DateTime(end);
// skip the items if they don't match the criteria
if (model != null && !workflowInstance.getModelId().equals(model))
continue;
if (payload != null && !workflowInstance.getPayload().startsWith(payload))
continue;
if (null != startTimeLimit && null != wfStart && wfStart.isBefore(startTimeLimit)) {
continue;
}
if (null != endTimeLimit && null != wfEnd && wfEnd.isAfter(endTimeLimit)) {
continue;
}
workflows.add(workflowInstance);
}
return workflows;
}
public List getWorkflowInstances(Status wfStatus) throws ClientException {
return getWorkflowInstances(wfStatus, null, null, null, null);
}
public List getWorkflowInstances(Status wfStatus, DateTime startTimeLimit, DateTime endTimeLimit) throws ClientException {
return getWorkflowInstances(wfStatus, null, null, startTimeLimit, endTimeLimit);
}
public List getWorkflowInstances() throws ClientException {
return getWorkflowInstances(null);
}
/**
* Retrieve the workflow history of a specific workflow instance
*
* @param instanceURI
* the workflow instance uri
* @param expectedStatus
* provide the status code that is expected, default is 200 OK.
* @return the history items as a list
* @throws ClientException if the request failed
*/
public List getWorkflowInstanceHistory(String instanceURI, int... expectedStatus)
throws ClientException {
// get the data
List params = Collections.singletonList(new BasicNameValuePair("workflow", instanceURI));
SlingHttpResponse exec = doGet("/libs/cq/workflow/content/console/history.json", params,
HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return the json in a light wrapper for easy data extraction
List listHistoryItems = new ArrayList<>();
JsonNode workflowHistoryNode = JsonUtils.getJsonNodeFromString(exec.getContent());
JsonNode historyNode = workflowHistoryNode.get("historyItems");
Iterator historyEntryIterator = historyNode.getElements();
while (historyEntryIterator.hasNext()) {
// read the history items and store it in the list via wrapper
// object
listHistoryItems.add(new HistoryItem(historyEntryIterator.next()));
}
return listHistoryItems;
}
/**
* Set the status of an running workflow instance.
*
* handled by:
* granite\bundles\workflow\console\src\main\java\com\adobe\granite
* \workflow\console\servlet\InstanceServlet.java
*
* @param instanceURI
* path to the instance url
* @param status
* status to be set, either {@link Status#ABORTED},
* {@link Status#RUNNING} or {@link Status#SUSPENDED}
* @param expectedStatus
* Expected HTTP status, otherwise 200 is assumed
* @return the request executor if more info from response is required.
* @throws ClientException
* if POST request has problems
*/
public SlingHttpResponse setWorkflowInstanceStatus(String instanceURI, Status status, int... expectedStatus) throws ClientException {
SlingHttpResponse exec = doPost(instanceURI, FormEntityBuilder.create().addParameter("state", status.getString()).build());
// check return status
HttpUtils.verifyHttpStatus(exec, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
// return the request executor
return exec;
}
/**
* Turns a string date returned by JSON into a Date object.
*
* @param date
* the string to parse
* @return the parsed Date object or null if parsing cannot be done
*/
public static Date parseJSONDate(String date) {
Locale store = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
try {
return new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").parse(date);
} catch (ParseException e) {
return null;
} finally {
Locale.setDefault(store);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy