Skip to content

Commit

Permalink
Adobe-Consulting-Services#1953 - Create Bulk Workflow MPC process
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjgonzalez committed Jun 13, 2019
1 parent ca865f5 commit 16e9539
Show file tree
Hide file tree
Showing 4 changed files with 446 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2019 Adobe
* %%
* 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.
* #L%
*/
package com.adobe.acs.commons.mcp.impl.processes;

import com.adobe.acs.commons.fam.ActionManager;
import com.adobe.acs.commons.mcp.ProcessDefinition;
import com.adobe.acs.commons.mcp.ProcessInstance;
import com.adobe.acs.commons.mcp.form.FormField;
import com.adobe.acs.commons.mcp.form.SelectComponent;
import com.adobe.acs.commons.mcp.form.TextareaComponent;
import com.adobe.acs.commons.mcp.form.TextfieldComponent;
import com.adobe.acs.commons.mcp.model.GenericReport;
import com.adobe.acs.commons.mcp.util.StringUtil;
import com.adobe.acs.commons.util.QueryHelper;
import com.adobe.acs.commons.util.impl.QueryHelperImpl;
import com.adobe.acs.commons.workflow.synthetic.SyntheticWorkflowModel;
import com.adobe.acs.commons.workflow.synthetic.SyntheticWorkflowRunner;
import com.day.cq.workflow.WorkflowException;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;

public class BulkWorkflow extends ProcessDefinition implements Serializable {
private static final Logger log = LoggerFactory.getLogger(BulkWorkflow.class);

public static final String PROCESS_NAME = "Bulk Workflow";

private final QueryHelper queryHelper;
private final SyntheticWorkflowRunner syntheticWorkflowRunner;

public enum ItemStatus {
SUCCESS, FAILURE
}

public enum QueryLanguage {
QUERY_BUILDER(QueryHelperImpl.QUERY_BUILDER),
LIST(QueryHelperImpl.LIST),
XPATH(Query.XPATH),
JCR_SQL2(Query.JCR_SQL2),
JCR_SQL("JCR-SQL");

private String value;

QueryLanguage(String value) {
this.value = value;
}

public String getValue() {
return this.value;
}
}

public enum ReportColumns {
PAYLOAD_PATH, TIME_TAKEN_IN_MILLISECONDS, STATUS
}

@FormField(
name = "Workflow model",
description = "The workflow model to execute. This workflow model MUST be compatible with ACS AEM Commons Synthetic Workflow.",
component = BulkWorkflowFactory.WorkflowModelSelector.class,
options = {"required"}
)
public String workflowId = "";

@FormField(
name = "Query language",
description = "",
component = SelectComponent.EnumerationSelector.class,
options = {"default=QUERY_BUILDER", "required"}
)
public QueryLanguage queryLanguage = QueryLanguage.QUERY_BUILDER;

@FormField(
name = "Query statement",
description = "Ensure that this query is correct prior to submitting form as it will collect the resources for processing which can be an expensive operation for large bulk workflow processes.",
component = TextareaComponent.class,
options = {"required"}
)
public String queryStatement = "";

@FormField(
name = "Relative path",
description = "This can be used to select otherwise difficult to search for resources. Examples: jcr:content/renditions/original OR ../renditions/original"
)
public String relativePayloadPath = "";

private final transient GenericReport report = new GenericReport();
private final transient List<EnumMap<ReportColumns, Object>> reportRows = new ArrayList<>();

private transient List<Resource> payloads;
private transient SyntheticWorkflowModel syntheticWorkflowModel;

public BulkWorkflow(final QueryHelper queryHelper,
final SyntheticWorkflowRunner syntheticWorkflowRunner) {
this.queryHelper = queryHelper;
this.syntheticWorkflowRunner = syntheticWorkflowRunner;
}

@Override
public void buildProcess(ProcessInstance instance, ResourceResolver rr) throws LoginException {
report.setName(instance.getName());
instance.getInfo().setDescription("Bulk process payloads using synthetic workflow");

instance.defineCriticalAction("Process payloads with synthetic workflow", rr, this::processPayloads);
}

protected void queryPayloads(ActionManager manager) throws Exception {
manager.withResolver(resourceResolver -> {
payloads = queryHelper.findResources(resourceResolver, queryLanguage.getValue(), queryStatement, relativePayloadPath);
});
}

protected void prepareSyntheticWorkflowModel(ActionManager manager) throws Exception {
manager.withResolver(resourceResolver -> {
syntheticWorkflowModel = syntheticWorkflowRunner.getSyntheticWorkflowModel(
resourceResolver,
workflowId,
true);
});
}

public void processPayloads(ActionManager manager) throws Exception {
prepareSyntheticWorkflowModel(manager);
queryPayloads(manager);

payloads.stream()
.map((resource) -> resource.getPath())
.forEach((path) -> manager.deferredWithResolver((ResourceResolver resourceResolver) -> {
final long start = System.currentTimeMillis();

resourceResolver.adaptTo(Session.class).getWorkspace().getObservationManager().setUserData("changedByWorkflowProcess");

try {
syntheticWorkflowRunner.execute(resourceResolver, path, syntheticWorkflowModel, false, true);
record(path, ItemStatus.SUCCESS, System.currentTimeMillis() - start);
} catch (WorkflowException e) {
record(path, ItemStatus.FAILURE, System.currentTimeMillis() - start);
}
}));
}

public GenericReport getReport() {
return report;
}

public List<EnumMap<ReportColumns, Object>> getReportRows() {
return reportRows;
}

@Override
public void init() throws RepositoryException {
// nothing to do here
}

protected void record(String path, ItemStatus status, long timeTaken) {
final EnumMap<ReportColumns, Object> row = new EnumMap<>(ReportColumns.class);

row.put(ReportColumns.PAYLOAD_PATH, path);
row.put(ReportColumns.STATUS, StringUtil.getFriendlyName(status.name()));
row.put(ReportColumns.TIME_TAKEN_IN_MILLISECONDS, timeTaken);

reportRows.add(row);
}

@Override
public void storeReport(ProcessInstance instance, ResourceResolver resourceResolver)
throws RepositoryException, PersistenceException {
report.setRows(reportRows, ReportColumns.class);
report.persist(resourceResolver, instance.getPath() + "/jcr:content/report");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2017 Adobe
* %%
* 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.
* #L%
*/
package com.adobe.acs.commons.mcp.impl.processes;

import com.adobe.acs.commons.mcp.ProcessDefinitionFactory;
import com.adobe.acs.commons.mcp.form.SelectComponent;
import com.adobe.acs.commons.util.QueryHelper;
import com.adobe.acs.commons.workflow.synthetic.SyntheticWorkflowRunner;
import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.Workflow;
import com.adobe.granite.workflow.model.WorkflowModel;
import org.apache.lucene.analysis.util.CharArrayMap;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Component(service = ProcessDefinitionFactory.class)
public class BulkWorkflowFactory extends ProcessDefinitionFactory<BulkWorkflow> {
private static final Logger log = LoggerFactory.getLogger(BulkWorkflowFactory.class);

@Reference
private QueryHelper queryHelper;

@Reference
private SyntheticWorkflowRunner syntheticWorkflowRunner;

@Override
public String getName() {
return BulkWorkflow.PROCESS_NAME;
}

@Override
public BulkWorkflow createProcessDefinitionInstance() {
return new BulkWorkflow(queryHelper, syntheticWorkflowRunner);
}

/**
* Selector that lists available Workflow Models in alphabetical order by Title. The selection value is the Workflow Model ID.
*/
public static class WorkflowModelSelector extends SelectComponent {
@Override
public Map<String, String> getOptions() {
Map<String, String> options = new TreeMap<>();

final ResourceResolver resourceResolver = getHelper().getRequest().getResourceResolver();
final WorkflowSession workflowSession = resourceResolver.adaptTo(WorkflowSession.class);

try {
options = Arrays.stream(workflowSession.getModels())
.collect(Collectors.toMap(
WorkflowModel::getId,
WorkflowModel::getTitle))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
e -> e.getKey(),
e -> e.getValue(),
(k, v)-> { throw new IllegalArgumentException("cannot merge"); },
LinkedHashMap::new));

} catch (WorkflowException e) {
log.error("Could not collect workflow models", e);
}

return options;
}
}
}
Loading

0 comments on commit 16e9539

Please sign in to comment.