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