/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2015 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.workflow.bulk.removal.impl;
import com.adobe.acs.commons.util.InfoWriter;
import com.adobe.acs.commons.workflow.bulk.removal.WorkflowInstanceRemover;
import com.adobe.acs.commons.workflow.bulk.removal.WorkflowRemovalException;
import com.adobe.acs.commons.workflow.bulk.removal.WorkflowRemovalForceQuitException;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@Component(
label = "ACS AEM Commons - Workflow Instance Remover - Scheduled Service",
metatype = true,
configurationFactory = true,
policy = ConfigurationPolicy.REQUIRE
)
@Properties({
@Property(
label = "Cron expression defining when this Scheduled Service will run",
description = "[12:01am daily = 0 1 0 ? * *]; see www.cronmaker.com",
name = "scheduler.expression",
value = "0 1 0 ? * *"
),
@Property(
label = "Allow concurrent executions",
description = "Allow concurrent executions of this Scheduled Service",
name = "scheduler.concurrent",
boolValue = false,
propertyPrivate = true
),
@Property(
name = "webconsole.configurationFactory.nameHint",
propertyPrivate = true,
value = "Runs at '{scheduler.expression}' on models [{workflow.models}] with status [{workflow.statuses}]"
)
})
@Service
public class WorkflowInstanceRemoverScheduler implements Runnable {
private static final Logger log = LoggerFactory.getLogger(WorkflowInstanceRemoverScheduler.class);
@Reference
private ResourceResolverFactory resourceResolverFactory;
@Reference
private WorkflowInstanceRemover workflowInstanceRemover;
private static final String SERVICE_NAME = "workflow-remover";
private static final Map<String, Object> AUTH_INFO;
static {
AUTH_INFO = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) SERVICE_NAME);
}
private static final String[] DEFAULT_WORKFLOW_STATUSES = {"COMPLETED", "ABORTED"};
private List<String> statuses = new ArrayList<String>();
@Property(label = "Workflow Status",
description = "Only remove Workflow Instances that have one of these statuses.",
value = { "COMPLETED", "ABORTED" })
public static final String PROP_WORKFLOW_STATUSES = "workflow.statuses";
private static final String[] DEFAULT_WORKFLOW_MODELS = {};
private List<String> models = new ArrayList<String>();
@Property(label = "Workflow Models",
description = "Only remove Workflow Instances that belong to one of these WF Models.",
cardinality = Integer.MAX_VALUE,
value = { })
public static final String PROP_WORKFLOW_MODELS = "workflow.models";
private static final String[] DEFAULT_WORKFLOW_PAYLOADS = {};
private List<Pattern> payloads = new ArrayList<Pattern>();
@Property(label = "Payload Patterns",
description = "Only remove Workflow Instances whose payloads match one of these regex patterns",
cardinality = Integer.MAX_VALUE,
value = { })
public static final String PROP_WORKFLOW_PAYLOADS = "workflow.payloads";
private Calendar olderThan = null;
@Property(label = "Older Than UTC TS",
description = "Only remove Workflow Instances whose payloads are older than this UTC Time in Millis",
longValue = 0)
public static final String PROP_WORKFLOWS_OLDER_THAN = "workflow.older-than";
private static final int DEFAULT_BATCH_SIZE = 1000;
private int batchSize = DEFAULT_BATCH_SIZE;
@Property(label = "Batch Size",
description = "Save removals to JCR in batches of this defined size.",
intValue = DEFAULT_BATCH_SIZE)
public static final String PROP_BATCH_SIZE = "batch-size";
private static final int DEFAULT_MAX_DURATION = 0;
private int maxDuration = DEFAULT_MAX_DURATION;
@Property(label = "Max duration (in minutes)",
description = "Max number of minutes this workflow removal process can execute. 0 for no limit. "
+ "[ Default: 0 ]",
intValue = DEFAULT_MAX_DURATION)
public static final String PROP_MAX_DURATION = "max-duration";
@Override
public final void run() {
ResourceResolver adminResourceResolver = null;
try {
adminResourceResolver = resourceResolverFactory.getServiceResourceResolver(AUTH_INFO);
final long start = System.currentTimeMillis();
int count = workflowInstanceRemover.removeWorkflowInstances(
adminResourceResolver,
models,
statuses,
payloads,
olderThan,
batchSize,
maxDuration);
if (log.isInfoEnabled()) {
log.info("Removed [ {} ] Workflow instances in {} ms", count, System.currentTimeMillis() - start);
}
} catch (LoginException e) {
log.error("Login Exception when getting admin resource resolver", e);
} catch (PersistenceException e) {
log.error("Persistence Exception when saving Workflow Instances removal", e);
} catch (WorkflowRemovalException e) {
log.error("Workflow Removal exception during Workflow Removal", e);
} catch (InterruptedException e) {
log.error("Interrupted Exception during Workflow Removal", e);
} catch (WorkflowRemovalForceQuitException e) {
log.info("Workflow Removal force quit", e);
} finally {
if (adminResourceResolver != null) {
adminResourceResolver.close();
}
}
}
private List<String> arrayToList(String[] array) {
List<String> list = new ArrayList<String>();
for (String element : array) {
if (StringUtils.isNotBlank(element)) {
list.add(element);
}
}
return list;
}
@Activate
protected final void activate(final Map<String, String> config) {
statuses = arrayToList(PropertiesUtil.toStringArray(config.get(PROP_WORKFLOW_STATUSES), DEFAULT_WORKFLOW_STATUSES));
models = arrayToList(PropertiesUtil.toStringArray(config.get(PROP_WORKFLOW_MODELS), DEFAULT_WORKFLOW_MODELS));
final String[] payloadsArray =
PropertiesUtil.toStringArray(config.get(PROP_WORKFLOW_PAYLOADS), DEFAULT_WORKFLOW_PAYLOADS);
for (final String payload : payloadsArray) {
if (StringUtils.isNotBlank(payload)) {
final Pattern p = Pattern.compile(payload);
if (p != null) {
payloads.add(p);
}
}
}
final Long olderThanTs = PropertiesUtil.toLong(config.get(PROP_WORKFLOWS_OLDER_THAN), 0);
if (olderThanTs > 0) {
olderThan = Calendar.getInstance();
olderThan.setTimeInMillis(olderThanTs);
}
batchSize = PropertiesUtil.toInteger(config.get(PROP_BATCH_SIZE), DEFAULT_BATCH_SIZE);
if (batchSize < 1) {
batchSize = DEFAULT_BATCH_SIZE;
}
maxDuration = PropertiesUtil.toInteger(config.get(PROP_MAX_DURATION), DEFAULT_MAX_DURATION);
final InfoWriter iw = new InfoWriter();
iw.title("Workflow Instance Removal Configuration");
iw.message("Workflow status: {}", statuses);
iw.message("Workflow models: {}", models);
iw.message("Payloads: {}", Arrays.asList(payloadsArray));
iw.message("Older than: {}", olderThan);
iw.message("Batch size: {}", batchSize);
iw.message("Max Duration (minutes): {}", maxDuration);
iw.end();
log.info(iw.toString());
}
@Deactivate
protected final void deactivate(final Map<String, String> config) {
olderThan = null;
statuses = new ArrayList<String>();
models = new ArrayList<String>();
payloads = new ArrayList<Pattern>();
batchSize = DEFAULT_BATCH_SIZE;
maxDuration = DEFAULT_MAX_DURATION;
}
}