001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.api.model.pipelines;
015
016import org.gbif.api.jackson.OffsetDateTimeSerDe;
017import org.gbif.api.util.OffsetDateTimeUtils;
018
019import java.io.Serializable;
020import java.time.OffsetDateTime;
021import java.util.Comparator;
022import java.util.Objects;
023import java.util.Optional;
024import java.util.Set;
025import java.util.StringJoiner;
026import java.util.TreeSet;
027import java.util.UUID;
028
029import com.fasterxml.jackson.annotation.JsonInclude;
030import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
031import com.fasterxml.jackson.databind.annotation.JsonSerialize;
032
033import static org.gbif.api.model.pipelines.PipelineStep.Status.RUNNING;
034
035/**
036 * Models a pipeline process for a specific dataset and attempt.
037 */
038public class PipelineProcess implements Serializable {
039
040  private static final long serialVersionUID = -3992826055732414678L;
041
042  @JsonInclude(JsonInclude.Include.NON_DEFAULT)
043  private long key;
044
045  private UUID datasetKey;
046  private String datasetTitle;
047  private int attempt;
048
049  @JsonSerialize(using = OffsetDateTimeSerDe.OffsetDateTimeSerializer.class)
050  @JsonDeserialize(using = OffsetDateTimeSerDe.OffsetDateTimeDeserializer.class)
051  private OffsetDateTime created;
052
053  private String createdBy;
054  private Set<PipelineExecution> executions =
055    new TreeSet<>(PIPELINE_EXECUTION_BY_CREATED_ASC.reversed());
056
057  public static final Comparator<PipelineExecution> PIPELINE_EXECUTION_BY_CREATED_ASC =
058    (e1, e2) -> {
059      OffsetDateTime created1 = e1 != null ? e1.getCreated() : null;
060      OffsetDateTime created2 = e2 != null ? e2.getCreated() : null;
061
062      return OffsetDateTimeUtils.compareOffsetDateTime(created1, created2);
063    };
064
065  /**
066   * Comparator that sorts the pipeline processes by the created date of the latest execution.
067   */
068  public static final Comparator<PipelineProcess> PIPELINE_PROCESS_BY_LATEST_EXEUCTION_ASC =
069    (p1, p2) -> {
070      if (p1 == null) {
071        return p2 == null ? 0 : 1;
072      } else if (p2 == null) {
073        return -1;
074      }
075
076      PipelineExecution latestExecution1 =
077        p1.getExecutions().isEmpty() ? null : p1.getExecutions().iterator().next();
078      PipelineExecution latestExecution2 =
079        p2.getExecutions().isEmpty() ? null : p2.getExecutions().iterator().next();
080
081      return Objects.compare(
082        latestExecution1, latestExecution2, PIPELINE_EXECUTION_BY_CREATED_ASC);
083    };
084
085  /**
086   * Comparator that sorts pipeline processes by the start date of their latest step in ascending
087   * order. The steps that are running have preference, and we take into account only the steps of
088   * the latest execution.
089   */
090  public static final Comparator<PipelineProcess> PIPELINE_PROCESS_BY_LATEST_STEP_RUNNING_ASC =
091    (p1, p2) -> {
092      if (p1 == null) {
093        return p2 == null ? 0 : 1;
094      } else if (p2 == null) {
095        return -1;
096      }
097
098      PipelineExecution latestExecution1 =
099        p1.getExecutions().isEmpty() ? null : p1.getExecutions().iterator().next();
100      PipelineExecution latestExecution2 =
101        p2.getExecutions().isEmpty() ? null : p2.getExecutions().iterator().next();
102
103      Optional<PipelineStep> lastStepOpt1 = Optional.empty();
104      if (latestExecution1 != null && latestExecution1.getSteps() != null) {
105        lastStepOpt1 =
106          latestExecution1.getSteps().stream()
107            .filter(p -> p.getState() != null)
108            .max(Comparator.comparing(pipelineStep -> pipelineStep.getStarted().toInstant()));
109      }
110
111      Optional<PipelineStep> lastStepOpt2 = Optional.empty();
112      if (latestExecution2 != null && latestExecution2.getSteps() != null) {
113        lastStepOpt2 =
114          latestExecution2.getSteps().stream()
115            .filter(p -> p.getState() != null)
116            .max(Comparator.comparing(PipelineStep::getStarted));
117      }
118
119      if (!lastStepOpt1.isPresent()) {
120        return !lastStepOpt2.isPresent() ? 0 : 1;
121      } else if (!lastStepOpt2.isPresent()) {
122        return -1;
123      }
124
125      PipelineStep step1 = lastStepOpt1.get();
126      PipelineStep step2 = lastStepOpt2.get();
127
128      if (step1.getStarted() == null) {
129        return step2.getStarted() == null ? 0 : 1;
130      } else if (step2.getStarted() == null) {
131        return -1;
132      }
133
134      // steps that are running have preference
135      if (step1.getState() == RUNNING) {
136        return step2.getState() == RUNNING ? OffsetDateTimeUtils.compareOffsetDateTime(step1.getStarted(), step2.getStarted()) : 1;
137      } else if (step2.getState() == RUNNING) {
138        return -1;
139      } else {
140        return OffsetDateTimeUtils.compareOffsetDateTime(step1.getStarted(), step2.getStarted());
141      }
142    };
143
144  public long getKey() {
145    return key;
146  }
147
148  public UUID getDatasetKey() {
149    return datasetKey;
150  }
151
152  public PipelineProcess setDatasetKey(UUID datasetKey) {
153    this.datasetKey = datasetKey;
154    return this;
155  }
156
157  public String getDatasetTitle() {
158    return datasetTitle;
159  }
160
161  public void setDatasetTitle(String datasetTitle) {
162    this.datasetTitle = datasetTitle;
163  }
164
165  public int getAttempt() {
166    return attempt;
167  }
168
169  public PipelineProcess setAttempt(int attempt) {
170    this.attempt = attempt;
171    return this;
172  }
173
174  public OffsetDateTime getCreated() {
175    return created;
176  }
177
178  public PipelineProcess setCreated(OffsetDateTime created) {
179    this.created = created;
180    return this;
181  }
182
183  public String getCreatedBy() {
184    return createdBy;
185  }
186
187  public PipelineProcess setCreatedBy(String createdBy) {
188    this.createdBy = createdBy;
189    return this;
190  }
191
192  public Set<PipelineExecution> getExecutions() {
193    return executions;
194  }
195
196  public void setExecutions(Set<PipelineExecution> executions) {
197    this.executions.clear();
198    this.executions.addAll(executions);
199  }
200
201  public PipelineProcess addExecution(PipelineExecution execution) {
202    executions.add(execution);
203    return this;
204  }
205
206  @Override
207  public String toString() {
208    return new StringJoiner(", ", PipelineProcess.class.getSimpleName() + "[", "]")
209      .add("key=" + key)
210      .add("datasetKey=" + datasetKey)
211      .add("datasetTitle=" + datasetTitle)
212      .add("attempt=" + attempt)
213      .add("created=" + created)
214      .add("createdBy='" + createdBy + "'")
215      .add("executions=" + executions)
216      .toString();
217  }
218
219  @Override
220  public boolean equals(Object o) {
221    if (this == o) return true;
222    if (o == null || getClass() != o.getClass()) return false;
223    PipelineProcess process = (PipelineProcess) o;
224    return attempt == process.attempt && Objects.equals(datasetKey, process.datasetKey);
225  }
226
227  @Override
228  public int hashCode() {
229    return Objects.hash(datasetKey, attempt);
230  }
231}