001/*- 002 * #%L 003 * HAPI FHIR JPA Server - Batch2 Task Processor 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.batch2.model; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.util.Logs; 024import com.google.common.collect.Maps; 025import jakarta.annotation.Nonnull; 026import org.slf4j.Logger; 027 028import java.util.Collections; 029import java.util.EnumMap; 030import java.util.EnumSet; 031import java.util.Map; 032import java.util.Set; 033 034/** 035 * Status of a Batch2 Job Instance. 036 * The initial state is QUEUED. 037 * The terminal states are COMPLETED, CANCELLED, or FAILED. 038 */ 039public enum StatusEnum { 040 041 /** 042 * Task is waiting to execute and should begin with no intervention required. 043 */ 044 QUEUED(true, false, true), 045 046 /** 047 * Task is current executing 048 */ 049 IN_PROGRESS(true, false, true), 050 051 /** 052 * For reduction steps 053 */ 054 FINALIZE(true, false, true), 055 056 /** 057 * Task completed successfully 058 */ 059 COMPLETED(false, true, false), 060 061 /** 062 * Chunk execution resulted in an error but the error may be transient (or transient status is unknown). 063 * The job may still complete successfully. 064 * @deprecated this is basically a synonym for IN_PROGRESS - display should use the presence of an error message on the instance 065 * to indicate that there has been a transient error. 066 */ 067 @Deprecated(since = "6.6") 068 // wipmb For 6.8 - remove all inbound transitions, and allow transition back to IN_PROGRESS. use message in ui to 069 // show danger status 070 ERRORED(true, false, true), 071 072 /** 073 * Task has failed and is known to be unrecoverable. There is no reason to believe that retrying will 074 * result in a different outcome. 075 */ 076 FAILED(true, true, false), 077 078 /** 079 * Task has been cancelled by the user. 080 */ 081 CANCELLED(true, true, false); 082 083 private static final Logger ourLog = Logs.getBatchTroubleshootingLog(); 084 085 /** Map from state to Set of legal inbound states */ 086 static final Map<StatusEnum, Set<StatusEnum>> ourFromStates; 087 /** Map from state to Set of legal outbound states */ 088 static final Map<StatusEnum, Set<StatusEnum>> ourToStates; 089 090 static { 091 EnumMap<StatusEnum, Set<StatusEnum>> fromStates = new EnumMap<>(StatusEnum.class); 092 EnumMap<StatusEnum, Set<StatusEnum>> toStates = new EnumMap<>(StatusEnum.class); 093 094 for (StatusEnum nextEnum : StatusEnum.values()) { 095 fromStates.put(nextEnum, EnumSet.noneOf(StatusEnum.class)); 096 toStates.put(nextEnum, EnumSet.noneOf(StatusEnum.class)); 097 } 098 for (StatusEnum nextPriorEnum : StatusEnum.values()) { 099 for (StatusEnum nextNextEnum : StatusEnum.values()) { 100 if (isLegalStateTransition(nextPriorEnum, nextNextEnum)) { 101 fromStates.get(nextNextEnum).add(nextPriorEnum); 102 toStates.get(nextPriorEnum).add(nextNextEnum); 103 } 104 } 105 } 106 107 ourFromStates = Maps.immutableEnumMap(fromStates); 108 ourToStates = Maps.immutableEnumMap(toStates); 109 } 110 111 private final boolean myIncomplete; 112 private final boolean myEnded; 113 private final boolean myIsCancellable; 114 private static StatusEnum[] ourIncompleteStatuses; 115 private static Set<StatusEnum> ourEndedStatuses; 116 private static Set<StatusEnum> ourNotEndedStatuses; 117 118 StatusEnum(boolean theIncomplete, boolean theEnded, boolean theIsCancellable) { 119 myIncomplete = theIncomplete; 120 myEnded = theEnded; 121 myIsCancellable = theIsCancellable; 122 } 123 124 /** 125 * Statuses that represent a job that has not yet completed. I.e. 126 * all statuses except {@link #COMPLETED} 127 */ 128 public static StatusEnum[] getIncompleteStatuses() { 129 StatusEnum[] retVal = ourIncompleteStatuses; 130 if (retVal == null) { 131 EnumSet<StatusEnum> incompleteSet = EnumSet.noneOf(StatusEnum.class); 132 for (StatusEnum next : values()) { 133 if (next.myIncomplete) { 134 incompleteSet.add(next); 135 } 136 } 137 ourIncompleteStatuses = incompleteSet.toArray(new StatusEnum[0]); 138 retVal = ourIncompleteStatuses; 139 } 140 return retVal; 141 } 142 143 /** 144 * Statuses that represent a job that has ended. I.e. 145 * all statuses except {@link #QUEUED and #COMPLETED} 146 */ 147 @Nonnull 148 public static Set<StatusEnum> getEndedStatuses() { 149 Set<StatusEnum> retVal = ourEndedStatuses; 150 if (retVal == null) { 151 initializeStaticEndedStatuses(); 152 } 153 retVal = ourEndedStatuses; 154 return retVal; 155 } 156 157 /** 158 * Statuses that represent a job that has not ended. I.e. 159 * {@link #QUEUED and #COMPLETED} 160 */ 161 @Nonnull 162 public static Set<StatusEnum> getNotEndedStatuses() { 163 Set<StatusEnum> retVal = ourNotEndedStatuses; 164 if (retVal == null) { 165 initializeStaticEndedStatuses(); 166 } 167 retVal = ourNotEndedStatuses; 168 return retVal; 169 } 170 171 private static void initializeStaticEndedStatuses() { 172 EnumSet<StatusEnum> endedSet = EnumSet.noneOf(StatusEnum.class); 173 EnumSet<StatusEnum> notEndedSet = EnumSet.noneOf(StatusEnum.class); 174 for (StatusEnum next : values()) { 175 if (next.myEnded) { 176 endedSet.add(next); 177 } else { 178 notEndedSet.add(next); 179 } 180 } 181 ourEndedStatuses = Collections.unmodifiableSet(endedSet); 182 ourNotEndedStatuses = Collections.unmodifiableSet(notEndedSet); 183 } 184 185 public static boolean isLegalStateTransition(StatusEnum theOrigStatus, StatusEnum theNewStatus) { 186 boolean canTransition; 187 switch (theOrigStatus) { 188 case QUEUED: 189 // initial state can transition to anything 190 canTransition = true; 191 break; 192 case IN_PROGRESS: 193 case ERRORED: 194 canTransition = theNewStatus != QUEUED; 195 break; 196 case CANCELLED: 197 // terminal state cannot transition 198 canTransition = false; 199 break; 200 case COMPLETED: 201 canTransition = false; 202 break; 203 case FAILED: 204 canTransition = theNewStatus == FAILED; 205 break; 206 case FINALIZE: 207 canTransition = theNewStatus != QUEUED && theNewStatus != IN_PROGRESS; 208 break; 209 default: 210 throw new IllegalStateException(Msg.code(2131) + "Unknown batch state " + theOrigStatus); 211 } 212 213 if (!canTransition) { 214 // we have a bug? 215 ourLog.debug( 216 "Tried to execute an illegal state transition. [origStatus={}, newStatus={}]", 217 theOrigStatus, 218 theNewStatus); 219 } 220 return canTransition; 221 } 222 223 public boolean isIncomplete() { 224 return myIncomplete; 225 } 226 227 public boolean isEnded() { 228 return myEnded; 229 } 230 231 public boolean isCancellable() { 232 return myIsCancellable; 233 } 234 235 /** 236 * States that may transition to this state. 237 */ 238 public Set<StatusEnum> getPriorStates() { 239 return ourFromStates.get(this); 240 } 241 242 /** 243 * States this state may transotion to. 244 */ 245 public Set<StatusEnum> getNextStates() { 246 return ourToStates.get(this); 247 } 248}