/*
* Copyright 2015 Collective, Inc.
*
* 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.collective.celos;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* The execution status of a slot.
*/
public class SlotState extends ValueObject {
/** Never null. */
protected final SlotID slotID;
/** Never null. */
protected final Status status;
/** Only set in RUNNING, SUCCESS, FAILURE states; null otherwise. */
private final String externalID;
/** Initially zero, increased every time the slot is rerun. */
private final int retryCount;
// JSON support
private static final String TIME_PROP = "time";
private static final String STATUS_PROP = "status";
private static final String EXTERNAL_ID_PROP = "externalID";
private static final String RETRY_COUNT_PROP = "retryCount";
public enum StatusType {
SUCCESS,
INDETERMINATE,
FAILURE
}
public enum Status {
/** No data availability yet. */
WAITING(StatusType.INDETERMINATE),
/** No data availability for too long. */
WAIT_TIMEOUT(StatusType.FAILURE),
/** Data is available and the workflow will be run shortly.
Workflow will also enter this state when it is retried. */
READY(StatusType.INDETERMINATE),
/** The workflow is currently running. */
RUNNING(StatusType.INDETERMINATE),
/** The workflow has succeeded. */
SUCCESS(StatusType.SUCCESS),
/** The workflow has failed and will not be retried. */
FAILURE(StatusType.FAILURE),
/** The workflow has been killed. */
KILLED(StatusType.FAILURE);
private final StatusType type;
Status(StatusType type) {
this.type = type;
}
public StatusType getType() {
return type;
}
};
public SlotState(SlotID slotID, Status status) {
this(slotID, status, null, 0);
}
public SlotState(SlotID slotID, Status status, String externalID, int retryCount) {
this.slotID = Util.requireNonNull(slotID);
this.status = Util.requireNonNull(status);
this.externalID = externalID;
this.retryCount = retryCount;
}
public SlotID getSlotID() {
return slotID;
}
public Status getStatus() {
return status;
}
public ScheduledTime getScheduledTime() {
return slotID.getScheduledTime();
}
public String getExternalID() {
return externalID;
}
public int getRetryCount() {
return retryCount;
}
public SlotState transitionToReady() {
assertStatus(Status.WAITING);
return new SlotState(slotID, Status.READY, null, retryCount);
}
public SlotState transitionToWaitTimeout() {
assertStatus(Status.WAITING);
return new SlotState(slotID, Status.WAIT_TIMEOUT, null, retryCount);
}
public SlotState transitionToRunning(String externalID) {
assertStatus(Status.READY);
return new SlotState(slotID, Status.RUNNING, externalID, retryCount);
}
public SlotState transitionToSuccess() {
assertStatus(Status.RUNNING);
return new SlotState(slotID, Status.SUCCESS, externalID, retryCount);
}
public SlotState transitionToFailure() {
assertStatus(Status.RUNNING);
return new SlotState(slotID, Status.FAILURE, externalID, retryCount);
}
public SlotState transitionToRetry() {
assertStatus(Status.RUNNING);
return new SlotState(slotID, Status.WAITING, null, retryCount + 1);
}
public SlotState transitionToKill() {
if (status.getType() != StatusType.INDETERMINATE) {
throw new IllegalStateException("Slot must be in indeterminate state, but is: " + status);
}
return new SlotState(slotID, Status.KILLED, externalID, 0); // reset retryCount to 0
}
public SlotState transitionToRerun() {
if (status.getType() == StatusType.INDETERMINATE) {
throw new IllegalStateException("Slot must be in successful or failed, but is: " + status);
}
return new SlotState(slotID, Status.WAITING, null, 0); // reset retryCount to 0
}
private void assertStatus(Status st) {
if (!status.equals(st)) {
throw new IllegalStateException("Expected status " + st + " but was " + status + " (slot: " + this + ")");
}
}
public ObjectNode toJSONNode() {
ObjectNode node = Util.MAPPER.createObjectNode();
node.put(TIME_PROP, this.getScheduledTime().toString());
node.put(STATUS_PROP, this.getStatus().toString());
node.put(EXTERNAL_ID_PROP, this.getExternalID());
node.put(RETRY_COUNT_PROP, this.getRetryCount());
return node;
}
public static SlotState fromJSONNode(WorkflowID id, JsonNode node) {
ScheduledTime time = new ScheduledTime(node.get(TIME_PROP).textValue());
return fromJSONNode(new SlotID(id, time), node);
}
public static SlotState fromJSONNode(SlotID id, JsonNode node) {
SlotState.Status status = SlotState.Status.valueOf(node.get(STATUS_PROP).textValue());
String externalID = node.get(EXTERNAL_ID_PROP).textValue();
int retryCount = node.get(RETRY_COUNT_PROP).intValue();
return new SlotState(id, status, externalID, retryCount);
}
}