Skip to content

Commit

Permalink
Merge branch 'master' into feature/dialog_provider_improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
badvision committed Jul 31, 2019
2 parents de0adee + 29918c6 commit 9ec6e0b
Show file tree
Hide file tree
Showing 6 changed files with 483 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com)
- #1980 - Fixing error when not using redirect map file
- #1981 - Fixing Redirect Map Manager issue where the edit button didn't work for pages and Assets
- #1993 - DialogProvider now supports styles for Dialog and Page dialogs
- #1953 - Bulk Workflow MCP process and relative path bug-fix for QueryHelperImpl when using QueryBuilder query type.
- #1997 - MCP Forms fixes for RTE configuration and NPE issue with AbstractResourceImpl when resource type is not set
- #2011 - Setting Travis platform to Trusty so that Oracle JDK 8 build will continue to work.

### Added
- #1993 - New components for autocomplete and rich text editor
- #1953 - Bulk Workflow MCP process and relative path bug-fix for QueryHelperImpl when using QueryBuilder query type.

## [4.2.2] - 2019-07-15

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* #%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.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);

log.info("Executing synthetic workflow [ {} ] against [ {} ] payloads via Bulk Workflow MCP process.", workflowId, payloads.size());

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);
final long duration = System.currentTimeMillis() - start;
record(path, ItemStatus.SUCCESS, duration);
log.debug("Successfully processed payload [ {} ] with synthetic workflow [ {} ] in [ {} ] milliseconds.", path, workflowId, duration);
} catch (WorkflowException e) {
final long duration = System.currentTimeMillis() - start;
record(path, ItemStatus.FAILURE, duration);
log.warn("Failed to process payload [ {} ] with synthetic workflow [ {} ] in [ {} ] milliseconds.", path, workflowId, duration);
}
}));
}

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ public class QueryHelperImpl implements QueryHelper {
@Reference
private CloseableQueryBuilder queryBuilder;

private static final String QUERY_BUILDER = "queryBuilder";
public static final String QUERY_BUILDER = "queryBuilder";

private static final String LIST = "list";
public static final String LIST = "list";

/**
* Find all the resources needed for the package definition.
Expand All @@ -78,7 +78,7 @@ public List<Resource> findResources(final ResourceResolver resourceResolver,
final String[] lines = StringUtils.split(statement, '\n');

if (QUERY_BUILDER.equalsIgnoreCase(language)) {
return getResourcesFromQueryBuilder(resourceResolver, lines);
return getResourcesFromQueryBuilder(resourceResolver, lines, relPath);
} else if (LIST.equalsIgnoreCase(language)) {
return getResourcesFromList(resourceResolver, lines, relPath);
} else {
Expand Down Expand Up @@ -119,7 +119,7 @@ private List<Resource> getResourcesFromList(ResourceResolver resourceResolver, S
return resources;
}

private List<Resource> getResourcesFromQueryBuilder(ResourceResolver resourceResolver, String[] lines) throws RepositoryException {
private List<Resource> getResourcesFromQueryBuilder(ResourceResolver resourceResolver, String[] lines, String relPath) throws RepositoryException {
final List<Resource> resources = new ArrayList<>();
final Map<String, String> params = ParameterUtil.toMap(lines, "=", false, null, true);

Expand All @@ -131,7 +131,12 @@ private List<Resource> getResourcesFromQueryBuilder(ResourceResolver resourceRes
try (CloseableQuery query = queryBuilder.createQuery(PredicateGroup.create(params), resourceResolver)) {
final List<Hit> hits = query.getResult().getHits();
for (final Hit hit : hits) {
resources.add(resourceResolver.getResource(hit.getPath()));
final Resource resource = resourceResolver.getResource(hit.getPath());
final Resource relativeAwareResource = getRelativeAwareResource(resource, relPath);

if (relativeAwareResource != null) {
resources.add(relativeAwareResource);
}
}
}

Expand Down

0 comments on commit 9ec6e0b

Please sign in to comment.