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}