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}