/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2016 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.execution.model;
import com.adobe.acs.commons.workflow.bulk.execution.BulkWorkflowRunner;
import com.day.cq.commons.jcr.JcrUtil;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@Model(adaptables = Resource.class)
public class Workspace {
private static final Logger log = LoggerFactory.getLogger(Workspace.class);
public static final String NT_UNORDERED = "oak:Unstructured";
public static final String NN_FAILURES = "failures";
public static final String NN_FAILURE = "failure";
public static final String NN_WORKSPACE = "workspace";
public static final String NN_PAYLOADS = "payloads";
public static final String PN_ACTIVE_PAYLOAD_GROUPS = "activePayloadGroups";
public static final String PN_ACTIVE_PAYLOADS = "activePayloads";
public static final String PN_STATUS = "status";
public static final String PN_SUB_STATUS = "subStatus";
private static final String PN_INITIALIZED = "initialized";
private static final String PN_COMPLETED_AT = "completedAt";
private static final String PN_COUNT_COMPLETE = "completeCount";
private static final String PN_COUNT_FAILURE = "failCount";
private static final String PN_COUNT_TOTAL = "totalCount";
private static final String PN_STARTED_AT = "startedAt";
private static final String PN_STOPPED_AT = "stoppedAt";
private static final String PN_MESSAGE = "message";
private static final String PN_ACTION_MANAGER_NAME = "actionManagerName";
public static final String NN_PAYLOADS_LAUNCHPAD = "payloads_zero";
private Resource resource;
private ModifiableValueMap properties;
private BulkWorkflowRunner runner;
private Config config;
@Inject
private List<BulkWorkflowRunner> runners;
@Inject
@Optional
private String jobName;
@Inject
private ResourceResolver resourceResolver;
@Inject
@Default(values = {})
private String[] activePayloads;
@Inject
@Default(values = {})
private String[] activePayloadGroups;
@Inject
@Default(values = "NOT_STARTED")
private String status;
@Inject
@Optional
private String subStatus;
@Inject
@Default(booleanValues = false)
private boolean initialized;
@Inject
@Default(intValues = 0)
private int totalCount;
@Inject
@Default(intValues = 0)
private int completeCount;
@Inject
@Default(intValues = 0)
private int failCount;
@Inject
@Optional
private Calendar startedAt;
@Inject
@Optional
private Calendar stoppedAt;
@Inject
@Optional
private Calendar completedAt;
@Inject
@Optional
private String message;
@Inject
private List<Failure> failures;
@Inject
@Optional
private String actionManagerName;
public Workspace(Resource resource) {
this.resource = resource;
this.properties = resource.adaptTo(ModifiableValueMap.class);
this.jobName = "acs-commons@bulk-workflow-execution:/" + this.resource.getPath();
}
@PostConstruct
protected void activate() throws Exception {
this.config = resource.getParent().adaptTo(Config.class);
for (BulkWorkflowRunner candidate : runners) {
if (StringUtils.equals(this.config.getRunnerType(), candidate.getClass().getName())) {
runner = candidate;
break;
}
}
}
/** Getters **/
public Calendar getCompletedAt() {
return completedAt;
}
public void setCompletedAt(Calendar completedAt) {
this.completedAt = completedAt;
properties.put(PN_COMPLETED_AT, this.completedAt);
}
public int getCompleteCount() {
return completeCount;
}
public int getFailCount() {
return failCount;
}
public String getJobName() {
return jobName;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
properties.put(PN_COUNT_TOTAL, this.totalCount);
}
public BulkWorkflowRunner getRunner() {
return runner;
}
public Calendar getStartedAt() {
return startedAt;
}
public void setStartedAt(Calendar startedAt) {
this.startedAt = startedAt;
properties.put(PN_STARTED_AT, this.startedAt);
}
public Status getStatus() {
// Refresh state before getting the status.
// Note, this gets the value from the session state, and not the cached Sling Model value as this value can change over the life of the SlingModel.
resourceResolver.refresh();
status = resource.getValueMap().get(PN_STATUS, Status.NOT_STARTED.toString());
return EnumUtils.getEnum(Status.class, status);
}
public Calendar getStoppedAt() {
return stoppedAt;
}
public boolean isInitialized() {
return initialized;
}
public boolean isRunning() {
return Status.RUNNING.equals(getStatus());
}
public boolean isStopped() {
return Status.STOPPED.equals(getStatus());
}
public boolean isStopping() {
return Status.RUNNING.equals(getStatus()) && SubStatus.STOPPING.equals(getSubStatus());
}
public Config getConfig() {
return config;
}
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
public boolean isActive(PayloadGroup payloadGroup) {
return ArrayUtils.contains(activePayloadGroups, payloadGroup.getDereferencedPath());
}
public String getPath() {
return resource.getPath();
}
public String getMessage() {
return message;
}
public String getActionManagerName() {
return actionManagerName;
}
public List<Failure> getFailures() {
return failures;
}
/** Setters **/
public void setStatus(Status status) {
this.status = status.toString();
properties.put(PN_STATUS, this.status);
// Clear subStatus
subStatus = null;
properties.remove(PN_SUB_STATUS);
}
public SubStatus getSubStatus() {
// Refresh state before getting the status.
// Note, this gets the value from the session state, and not the cached Sling Model value as this value can change over the life of the SlingModel.
resourceResolver.refresh();
subStatus = resource.getValueMap().get(PN_SUB_STATUS, String.class);
if (subStatus != null) {
return EnumUtils.getEnum(SubStatus.class, subStatus);
} else {
return null;
}
}
public void setStoppedAt(Calendar stoppedAt) {
this.stoppedAt = stoppedAt;
properties.put(PN_STOPPED_AT, this.stoppedAt);
}
public void setInitialized(boolean initialized) {
this.initialized = initialized;
properties.put(PN_INITIALIZED, this.initialized);
}
public void setStatus(Status status, SubStatus subStatus) {
setStatus(status);
if (subStatus != null) {
this.subStatus = subStatus.toString();
properties.put(PN_SUB_STATUS, this.subStatus);
}
}
public int incrementCompleteCount() {
setCompleteCount(completeCount + 1);
return completeCount;
}
public int setCompleteCount(int count) {
completeCount = count;
properties.put(PN_COUNT_COMPLETE, count);
return completeCount;
}
public int incrementFailCount() {
setFailureCount(failCount + 1);
return failCount;
}
public int setFailureCount(int count) {
failCount = count;
properties.put(PN_COUNT_FAILURE, count);
return failCount;
}
public void setError(String message) {
setStatus(Status.STOPPED, SubStatus.ERROR);
setMessage(message);
}
public void setMessage(String message) {
this.message = message;
properties.put(PN_MESSAGE, message);
}
public void setActionManagerName(String name) {
this.actionManagerName = name;
properties.put(PN_ACTION_MANAGER_NAME, name);
}
/** Internal logic proxies **/
public void addActivePayload(Payload payload) {
if (!ArrayUtils.contains(activePayloads, payload.getDereferencedPath())) {
activePayloads = (String[]) ArrayUtils.add(activePayloads, payload.getDereferencedPath());
properties.put(PN_ACTIVE_PAYLOADS, activePayloads);
addActivePayloadGroup(payload.getPayloadGroup());
}
}
public void addActivePayloads(List<Payload> payloads) {
for (Payload payload : payloads) {
addActivePayload(payload);
}
}
public void removeActivePayload(Payload payload) {
if (ArrayUtils.contains(activePayloads, payload.getDereferencedPath())) {
activePayloads = (String[]) ArrayUtils.removeElement(activePayloads, payload.getDereferencedPath());
properties.put(PN_ACTIVE_PAYLOADS, activePayloads);
}
}
/**
* @return a list of the payloads that are being actively processed by bulk workflow manager.
*/
public List<Payload> getActivePayloads() {
List<Payload> payloads = new ArrayList<Payload>();
for (String path : activePayloads) {
Resource r = resourceResolver.getResource(Payload.reference(path));
if (r != null) {
Payload p = r.adaptTo(Payload.class);
if (p != null) {
payloads.add(p);
}
}
}
return payloads;
}
/**
* @return a list of the payload groups that have atleast 1 payload being process by bulk workflow manager.
*/
public List<PayloadGroup> getActivePayloadGroups() {
List<PayloadGroup> payloadGroups = new ArrayList<PayloadGroup>();
if (activePayloadGroups != null) {
for (String path : activePayloadGroups) {
Resource r = resourceResolver.getResource(PayloadGroup.reference(path));
if (r == null) {
continue;
}
PayloadGroup pg = r.adaptTo(PayloadGroup.class);
if (pg == null) {
continue;
}
payloadGroups.add(pg);
}
}
return payloadGroups;
}
/**
* Adds the payload group to the list of active payload groups.
*
* @param payloadGroup the payload group to add as active
*/
public void addActivePayloadGroup(PayloadGroup payloadGroup) {
if (payloadGroup != null && !ArrayUtils.contains(activePayloadGroups, payloadGroup.getDereferencedPath())) {
activePayloadGroups = (String[]) ArrayUtils.add(activePayloadGroups, payloadGroup.getDereferencedPath());
properties.put(PN_ACTIVE_PAYLOAD_GROUPS, activePayloadGroups);
}
}
/**
* Removes the payload group from the list of active payload groups.
*
* @param payloadGroup the payload group to remove from the active list.
*/
public void removeActivePayloadGroup(PayloadGroup payloadGroup) {
if (payloadGroup != null && ArrayUtils.contains(activePayloadGroups, payloadGroup.getDereferencedPath())) {
activePayloadGroups = (String[]) ArrayUtils.removeElement(activePayloadGroups, payloadGroup.getDereferencedPath());
properties.put(PN_ACTIVE_PAYLOAD_GROUPS, activePayloadGroups);
}
}
public void addFailure(Payload payload) throws RepositoryException {
addFailure(payload.getDereferencedPayloadPath(), payload.getDereferencedPath(), Calendar.getInstance());
}
public void addFailure(String payloadPath, String trackPath, Calendar failedAt) throws RepositoryException {
Node failure = JcrUtils.getOrCreateByPath(resource.getChild(Workspace.NN_FAILURES).adaptTo(Node.class),
Workspace.NN_FAILURE, true, Workspace.NT_UNORDERED, Workspace.NT_UNORDERED, false);
JcrUtil.setProperty(failure, Failure.PN_PAYLOAD_PATH, payloadPath);
if (StringUtils.isNotBlank(trackPath)) {
JcrUtil.setProperty(failure, Failure.PN_PATH, Payload.dereference(trackPath));
}
if (failedAt != null) {
JcrUtil.setProperty(failure, Failure.PN_FAILED_AT, failedAt);
}
}
/**
* Commit the changes for this bulk workflow manager execution.
*
* @throws PersistenceException
*/
public void commit() throws PersistenceException {
config.commit();
}
}