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.batch2.api.IJobInstance; 023import ca.uhn.fhir.model.api.IModelJson; 024import ca.uhn.fhir.rest.server.util.JsonDateDeserializer; 025import ca.uhn.fhir.rest.server.util.JsonDateSerializer; 026import ca.uhn.fhir.util.JsonUtil; 027import ca.uhn.fhir.util.Logs; 028import com.fasterxml.jackson.annotation.JsonProperty; 029import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 030import com.fasterxml.jackson.databind.annotation.JsonSerialize; 031import org.apache.commons.lang3.builder.ToStringBuilder; 032import org.apache.commons.lang3.builder.ToStringStyle; 033 034import java.util.Date; 035 036import static org.apache.commons.lang3.StringUtils.isBlank; 037 038public class JobInstance implements IModelJson, IJobInstance { 039 040 @JsonProperty(value = "jobDefinitionId") 041 private String myJobDefinitionId; 042 043 @JsonProperty(value = "parameters") 044 private String myParameters; 045 046 @JsonProperty(value = "jobDefinitionVersion") 047 private int myJobDefinitionVersion; 048 049 @JsonProperty(value = "instanceId", access = JsonProperty.Access.READ_ONLY) 050 private String myInstanceId; 051 052 @JsonProperty(value = "status") 053 private StatusEnum myStatus; 054 055 @JsonProperty(value = "cancelled") 056 private boolean myCancelled; 057 058 /** 059 * True if every step of the job has produced exactly 1 chunk. 060 */ 061 @JsonProperty(value = "fastTracking") 062 private boolean myFastTracking; 063 064 // time when the job instance was actually first created/stored 065 @JsonProperty(value = "createTime") 066 @JsonSerialize(using = JsonDateSerializer.class) 067 @JsonDeserialize(using = JsonDateDeserializer.class) 068 private Date myCreateTime; 069 070 // time when the current status was 'started' 071 @JsonProperty(value = "startTime") 072 @JsonSerialize(using = JsonDateSerializer.class) 073 @JsonDeserialize(using = JsonDateDeserializer.class) 074 private Date myStartTime; 075 076 @JsonProperty(value = "endTime") 077 @JsonSerialize(using = JsonDateSerializer.class) 078 @JsonDeserialize(using = JsonDateDeserializer.class) 079 private Date myEndTime; 080 081 @JsonProperty(value = "updateTime") 082 @JsonSerialize(using = JsonDateSerializer.class) 083 @JsonDeserialize(using = JsonDateDeserializer.class) 084 private Date myUpdateTime; 085 086 @JsonProperty(value = "combinedRecordsProcessed") 087 private Integer myCombinedRecordsProcessed; 088 089 @JsonProperty(value = "combinedRecordsProcessedPerSecond") 090 private Double myCombinedRecordsProcessedPerSecond; 091 092 @JsonProperty(value = "totalElapsedMillis") 093 private Integer myTotalElapsedMillis; 094 095 @JsonProperty(value = "workChunksPurged", access = JsonProperty.Access.READ_ONLY) 096 private boolean myWorkChunksPurged; 097 098 @JsonProperty(value = "progress", access = JsonProperty.Access.READ_ONLY) 099 private double myProgress; 100 101 @JsonProperty(value = "currentGatedStepId", access = JsonProperty.Access.READ_ONLY) 102 private String myCurrentGatedStepId; 103 104 @JsonProperty(value = "errorMessage", access = JsonProperty.Access.READ_ONLY) 105 private String myErrorMessage; 106 107 @JsonProperty(value = "errorCount", access = JsonProperty.Access.READ_ONLY) 108 private int myErrorCount; 109 110 @JsonProperty(value = "estimatedCompletion", access = JsonProperty.Access.READ_ONLY) 111 private String myEstimatedTimeRemaining; 112 113 @JsonProperty(value = "report", access = JsonProperty.Access.READ_WRITE) 114 private String myReport; 115 116 @JsonProperty(value = "warningMessages", access = JsonProperty.Access.READ_ONLY) 117 private String myWarningMessages; 118 119 @JsonProperty(value = "triggeringUsername", access = JsonProperty.Access.READ_ONLY) 120 private String myTriggeringUsername; 121 122 @JsonProperty(value = "triggeringClientId", access = JsonProperty.Access.READ_ONLY) 123 private String myTriggeringClientId; 124 125 /** 126 * Constructor 127 */ 128 public JobInstance() { 129 super(); 130 } 131 132 /** 133 * Copy constructor 134 */ 135 public JobInstance(JobInstance theJobInstance) { 136 setJobDefinitionId(theJobInstance.getJobDefinitionId()); 137 setParameters(theJobInstance.getParameters()); 138 setCancelled(theJobInstance.isCancelled()); 139 setFastTracking(theJobInstance.isFastTracking()); 140 setCombinedRecordsProcessed(theJobInstance.getCombinedRecordsProcessed()); 141 setCombinedRecordsProcessedPerSecond(theJobInstance.getCombinedRecordsProcessedPerSecond()); 142 setCreateTime(theJobInstance.getCreateTime()); 143 setEndTime(theJobInstance.getEndTime()); 144 setUpdateTime(theJobInstance.getUpdateTime()); 145 setErrorCount(theJobInstance.getErrorCount()); 146 setErrorMessage(theJobInstance.getErrorMessage()); 147 setEstimatedTimeRemaining(theJobInstance.getEstimatedTimeRemaining()); 148 setInstanceId(theJobInstance.getInstanceId()); 149 setJobDefinitionVersion(theJobInstance.getJobDefinitionVersion()); 150 setProgress(theJobInstance.getProgress()); 151 setStartTime(theJobInstance.getStartTime()); 152 setStatus(theJobInstance.getStatus()); 153 setTotalElapsedMillis(theJobInstance.getTotalElapsedMillis()); 154 setWorkChunksPurged(theJobInstance.isWorkChunksPurged()); 155 setCurrentGatedStepId(theJobInstance.getCurrentGatedStepId()); 156 setReport(theJobInstance.getReport()); 157 setWarningMessages(theJobInstance.getWarningMessages()); 158 setTriggeringUsername(theJobInstance.getTriggeringUsername()); 159 setTriggeringClientId(theJobInstance.getTriggeringClientId()); 160 } 161 162 public String getJobDefinitionId() { 163 return myJobDefinitionId; 164 } 165 166 public void setJobDefinitionId(String theJobDefinitionId) { 167 myJobDefinitionId = theJobDefinitionId; 168 } 169 170 public String getParameters() { 171 return myParameters; 172 } 173 174 public void setParameters(String theParameters) { 175 myParameters = theParameters; 176 } 177 178 public <T extends IModelJson> T getParameters(Class<T> theType) { 179 if (myParameters == null) { 180 return null; 181 } 182 return JsonUtil.deserialize(myParameters, theType); 183 } 184 185 public void setParameters(IModelJson theParameters) { 186 myParameters = JsonUtil.serializeOrInvalidRequest(theParameters); 187 } 188 189 public void setUpdateTime(Date theUpdateTime) { 190 myUpdateTime = theUpdateTime; 191 } 192 193 public Date getUpdateTime() { 194 return myUpdateTime; 195 } 196 197 public static JobInstance fromJobDefinition(JobDefinition<?> theJobDefinition) { 198 JobInstance instance = new JobInstance(); 199 instance.setJobDefinition(theJobDefinition); 200 if (theJobDefinition.isGatedExecution()) { 201 instance.setFastTracking(true); 202 instance.setCurrentGatedStepId(theJobDefinition.getFirstStepId()); 203 } 204 return instance; 205 } 206 207 public static JobInstance fromInstanceId(String theInstanceId) { 208 JobInstance instance = new JobInstance(); 209 instance.setInstanceId(theInstanceId); 210 return instance; 211 } 212 213 @Override 214 public String getCurrentGatedStepId() { 215 return myCurrentGatedStepId; 216 } 217 218 public void setCurrentGatedStepId(String theCurrentGatedStepId) { 219 myCurrentGatedStepId = theCurrentGatedStepId; 220 } 221 222 @Override 223 public int getErrorCount() { 224 return myErrorCount; 225 } 226 227 public JobInstance setErrorCount(int theErrorCount) { 228 myErrorCount = theErrorCount; 229 return this; 230 } 231 232 @Override 233 public String getEstimatedTimeRemaining() { 234 return myEstimatedTimeRemaining; 235 } 236 237 public void setEstimatedTimeRemaining(String theEstimatedTimeRemaining) { 238 myEstimatedTimeRemaining = theEstimatedTimeRemaining; 239 } 240 241 @Override 242 public boolean isWorkChunksPurged() { 243 return myWorkChunksPurged; 244 } 245 246 public void setWorkChunksPurged(boolean theWorkChunksPurged) { 247 myWorkChunksPurged = theWorkChunksPurged; 248 } 249 250 @Override 251 public StatusEnum getStatus() { 252 return myStatus; 253 } 254 255 public JobInstance setStatus(StatusEnum theStatus) { 256 myStatus = theStatus; 257 return this; 258 } 259 260 @Override 261 public int getJobDefinitionVersion() { 262 return myJobDefinitionVersion; 263 } 264 265 public void setJobDefinitionVersion(int theJobDefinitionVersion) { 266 myJobDefinitionVersion = theJobDefinitionVersion; 267 } 268 269 @Override 270 public String getInstanceId() { 271 return myInstanceId; 272 } 273 274 public void setInstanceId(String theInstanceId) { 275 myInstanceId = theInstanceId; 276 } 277 278 @Override 279 public Date getStartTime() { 280 return myStartTime; 281 } 282 283 public JobInstance setStartTime(Date theStartTime) { 284 myStartTime = theStartTime; 285 return this; 286 } 287 288 @Override 289 public Date getEndTime() { 290 return myEndTime; 291 } 292 293 public JobInstance setEndTime(Date theEndTime) { 294 myEndTime = theEndTime; 295 return this; 296 } 297 298 @Override 299 public Integer getCombinedRecordsProcessed() { 300 return myCombinedRecordsProcessed; 301 } 302 303 public void setCombinedRecordsProcessed(Integer theCombinedRecordsProcessed) { 304 myCombinedRecordsProcessed = theCombinedRecordsProcessed; 305 } 306 307 @Override 308 public Double getCombinedRecordsProcessedPerSecond() { 309 return myCombinedRecordsProcessedPerSecond; 310 } 311 312 public void setCombinedRecordsProcessedPerSecond(Double theCombinedRecordsProcessedPerSecond) { 313 myCombinedRecordsProcessedPerSecond = theCombinedRecordsProcessedPerSecond; 314 } 315 316 @Override 317 public Date getCreateTime() { 318 return myCreateTime; 319 } 320 321 public JobInstance setCreateTime(Date theCreateTime) { 322 myCreateTime = theCreateTime; 323 return this; 324 } 325 326 @Override 327 public Integer getTotalElapsedMillis() { 328 return myTotalElapsedMillis; 329 } 330 331 public void setTotalElapsedMillis(Integer theTotalElapsedMillis) { 332 myTotalElapsedMillis = theTotalElapsedMillis; 333 } 334 335 @Override 336 public double getProgress() { 337 return myProgress; 338 } 339 340 public void setProgress(double theProgress) { 341 myProgress = theProgress; 342 } 343 344 @Override 345 public String getErrorMessage() { 346 return myErrorMessage; 347 } 348 349 public JobInstance setErrorMessage(String theErrorMessage) { 350 myErrorMessage = theErrorMessage; 351 return this; 352 } 353 354 public String getWarningMessages() { 355 return myWarningMessages; 356 } 357 358 public JobInstance setWarningMessages(String theWarningMessages) { 359 myWarningMessages = theWarningMessages; 360 return this; 361 } 362 363 public void setJobDefinition(JobDefinition<?> theJobDefinition) { 364 setJobDefinitionId(theJobDefinition.getJobDefinitionId()); 365 setJobDefinitionVersion(theJobDefinition.getJobDefinitionVersion()); 366 } 367 368 @Override 369 public boolean isCancelled() { 370 return myCancelled; 371 } 372 373 public void setCancelled(boolean theCancelled) { 374 myCancelled = theCancelled; 375 } 376 377 @Override 378 public String getReport() { 379 return myReport; 380 } 381 382 public void setReport(String theReport) { 383 myReport = theReport; 384 } 385 386 public String getTriggeringUsername() { 387 return myTriggeringUsername; 388 } 389 390 public JobInstance setTriggeringUsername(String theTriggeringUsername) { 391 myTriggeringUsername = theTriggeringUsername; 392 return this; 393 } 394 395 public String getTriggeringClientId() { 396 return myTriggeringClientId; 397 } 398 399 public JobInstance setTriggeringClientId(String theTriggeringClientId) { 400 myTriggeringClientId = theTriggeringClientId; 401 return this; 402 } 403 404 @Override 405 public String toString() { 406 return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) 407 .append("jobDefinitionId", getJobDefinitionId() + "/" + myJobDefinitionVersion) 408 .append("instanceId", myInstanceId) 409 .append("status", myStatus) 410 .append("myCancelled", myCancelled) 411 .append("createTime", myCreateTime) 412 .append("startTime", myStartTime) 413 .append("endTime", myEndTime) 414 .append("updateTime", myUpdateTime) 415 .append("combinedRecordsProcessed", myCombinedRecordsProcessed) 416 .append("combinedRecordsProcessedPerSecond", myCombinedRecordsProcessedPerSecond) 417 .append("totalElapsedMillis", myTotalElapsedMillis) 418 .append("workChunksPurged", myWorkChunksPurged) 419 .append("progress", myProgress) 420 .append("errorMessage", myErrorMessage) 421 .append("errorCount", myErrorCount) 422 .append("estimatedTimeRemaining", myEstimatedTimeRemaining) 423 .append("report", myReport) 424 .append("warningMessages", myWarningMessages) 425 .append("triggeringUsername", myTriggeringUsername) 426 .append("triggeringClientId", myTriggeringClientId) 427 .toString(); 428 } 429 430 /** 431 * Returns true if the job instance is in: 432 * {@link StatusEnum#IN_PROGRESS} 433 * {@link StatusEnum#FINALIZE} 434 * and is not cancelled 435 */ 436 public boolean isRunning() { 437 if (isCancelled()) { 438 return false; 439 } 440 441 switch (getStatus()) { 442 case IN_PROGRESS: 443 case ERRORED: 444 case FINALIZE: 445 return true; 446 case COMPLETED: 447 case QUEUED: 448 case FAILED: 449 case CANCELLED: 450 default: 451 Logs.getBatchTroubleshootingLog().debug("Status {} is considered \"not running\"", myStatus); 452 } 453 return false; 454 } 455 456 public boolean isFinished() { 457 return myStatus == StatusEnum.COMPLETED || myStatus == StatusEnum.FAILED || myStatus == StatusEnum.CANCELLED; 458 } 459 460 public boolean hasGatedStep() { 461 return !isBlank(myCurrentGatedStepId); 462 } 463 464 public boolean isPendingCancellationRequest() { 465 return myCancelled && myStatus.isCancellable(); 466 } 467 468 /** 469 * @return true if every step of the job has produced exactly 1 chunk. 470 */ 471 @Override 472 public boolean isFastTracking() { 473 return myFastTracking; 474 } 475 476 @Override 477 public void setFastTracking(boolean theFastTracking) { 478 myFastTracking = theFastTracking; 479 } 480}