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.VoidModel;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.model.api.IModelJson;
025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
026import ca.uhn.fhir.util.Logs;
027import org.apache.commons.lang3.Validate;
028import org.slf4j.Logger;
029
030import java.util.List;
031
032/**
033 * This immutable object is produced by reconciling a work notification message to its corresponding job definition.
034 * It holds information required to execute the current step and send data to the next step.
035 *
036 * @param <PT> Job Parameter type
037 * @param <IT> Current step input data type
038 * @param <OT> Current step output data type and next step input data type
039 */
040public class JobWorkCursor<PT extends IModelJson, IT extends IModelJson, OT extends IModelJson> {
041        private static final Logger ourLog = Logs.getBatchTroubleshootingLog();
042        public final JobDefinition<PT> jobDefinition;
043        public final boolean isFirstStep;
044        public final JobDefinitionStep<PT, IT, OT> currentStep;
045        public final JobDefinitionStep<PT, OT, ?> nextStep;
046
047        public JobWorkCursor(
048                        JobDefinition<PT> theJobDefinition,
049                        boolean theIsFirstStep,
050                        JobDefinitionStep<PT, IT, OT> theCurrentStep,
051                        JobDefinitionStep<PT, OT, ?> theNextStep) {
052                jobDefinition = theJobDefinition;
053                isFirstStep = theIsFirstStep;
054                currentStep = theCurrentStep;
055                nextStep = theNextStep;
056                validate();
057        }
058
059        private void validate() {
060                if (isFirstStep) {
061                        Validate.isTrue(currentStep.getInputType() == VoidModel.class);
062                }
063                // Note that if it is not the first step, it can have VoidModel as it's input type--not all steps require input
064                // from
065                // the previous step
066                if (nextStep != null) {
067                        Validate.isTrue(currentStep.getOutputType() == nextStep.getInputType());
068                }
069        }
070
071        public static <PT extends IModelJson> JobWorkCursor<PT, ?, ?> fromJobDefinitionAndRequestedStepId(
072                        JobDefinition<PT> theJobDefinition, String theRequestedStepId) {
073                boolean isFirstStep = false;
074                JobDefinitionStep<PT, ?, ?> currentStep = null;
075                JobDefinitionStep<PT, ?, ?> nextStep = null;
076
077                List<JobDefinitionStep<PT, ?, ?>> steps = theJobDefinition.getSteps();
078                for (int i = 0; i < steps.size(); i++) {
079                        JobDefinitionStep<PT, ?, ?> step = steps.get(i);
080                        if (step.getStepId().equals(theRequestedStepId)) {
081                                currentStep = step;
082                                if (i == 0) {
083                                        isFirstStep = true;
084                                }
085                                if (i < (steps.size() - 1)) {
086                                        nextStep = steps.get(i + 1);
087                                }
088                                break;
089                        }
090                }
091
092                if (currentStep == null) {
093                        String msg = "Unknown step[" + theRequestedStepId + "] for job definition ID["
094                                        + theJobDefinition.getJobDefinitionId() + "] version[" + theJobDefinition.getJobDefinitionVersion()
095                                        + "]";
096                        ourLog.warn(msg);
097                        throw new InternalErrorException(Msg.code(2042) + msg);
098                }
099
100                return new JobWorkCursor(theJobDefinition, isFirstStep, currentStep, nextStep);
101        }
102
103        public String getCurrentStepId() {
104                return currentStep.getStepId();
105        }
106
107        public boolean isFirstStep() {
108                return isFirstStep;
109        }
110
111        public boolean isFinalStep() {
112                return nextStep == null;
113        }
114
115        @SuppressWarnings("unchecked")
116        public JobWorkCursor<PT, IT, VoidModel> asFinalCursor() {
117                Validate.isTrue(isFinalStep());
118                return (JobWorkCursor<PT, IT, VoidModel>) this;
119        }
120
121        public JobDefinition<PT> getJobDefinition() {
122                return jobDefinition;
123        }
124
125        public JobDefinitionStep<PT, IT, OT> getCurrentStep() {
126                return currentStep;
127        }
128
129        public boolean isReductionStep() {
130                return currentStep.isReductionStep();
131        }
132}