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 java.util.Collection;
017import java.util.Collections;
018import java.util.HashMap;
019import java.util.HashSet;
020import java.util.LinkedHashSet;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Map;
024import java.util.Queue;
025import java.util.Set;
026import java.util.function.Function;
027import java.util.function.ToIntFunction;
028import java.util.stream.Collectors;
029
030import static org.gbif.api.model.pipelines.StepType.*;
031
032public class PipelinesWorkflow {
033
034  private PipelinesWorkflow() {
035    // NOP
036  }
037
038  private static final Graph<StepType> OCCURRENCE_WF_GRAPH = new Graph<>();
039  private static final Graph<StepType> EVENT_OCCURRENCE_WF_GRAPH = new Graph<>();
040  private static final Graph<StepType> EVENT_WF_GRAPH = new Graph<>();
041  private static final Graph<StepType> VALIDATON_WF_GRAPH = new Graph<>();
042
043  static {
044    // Pipelines occurrence workflow
045    // 0?
046    OCCURRENCE_WF_GRAPH.addNode(DWCDP_STAGE, DWCDP_TO_VERBATIM);
047    OCCURRENCE_WF_GRAPH.addNode(NFS_TO_HDFS, DWCDP_TO_VERBATIM);
048    // 1
049    OCCURRENCE_WF_GRAPH.addNode(DWCDP_TO_VERBATIM, VERBATIM_TO_IDENTIFIER);
050    OCCURRENCE_WF_GRAPH.addNode(DWCA_TO_VERBATIM, VERBATIM_TO_IDENTIFIER);
051    OCCURRENCE_WF_GRAPH.addNode(XML_TO_VERBATIM, VERBATIM_TO_IDENTIFIER);
052    OCCURRENCE_WF_GRAPH.addNode(ABCD_TO_VERBATIM, VERBATIM_TO_IDENTIFIER);
053    // 2
054    OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_IDENTIFIER, VERBATIM_TO_INTERPRETED);
055    // 3
056    OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, INTERPRETED_TO_INDEX);
057    OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, HDFS_VIEW);
058    OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, FRAGMENTER);
059
060    // Pipelines event-occurrence workflow
061    // 0?
062    EVENT_OCCURRENCE_WF_GRAPH.addNode(NFS_TO_HDFS, DWCDP_TO_VERBATIM);
063    EVENT_OCCURRENCE_WF_GRAPH.addNode(DWCDP_STAGE, DWCDP_TO_VERBATIM);
064    // 1
065    EVENT_OCCURRENCE_WF_GRAPH.addNode(DWCDP_TO_VERBATIM, VERBATIM_TO_IDENTIFIER);
066    EVENT_OCCURRENCE_WF_GRAPH.addNode(DWCA_TO_VERBATIM, VERBATIM_TO_IDENTIFIER);
067    // 2
068    EVENT_OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_IDENTIFIER, VERBATIM_TO_INTERPRETED);
069    // 3
070    EVENT_OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, INTERPRETED_TO_INDEX);
071    EVENT_OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, HDFS_VIEW);
072    EVENT_OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, FRAGMENTER);
073    EVENT_OCCURRENCE_WF_GRAPH.addNode(VERBATIM_TO_INTERPRETED, EVENTS_VERBATIM_TO_INTERPRETED);
074    // 4
075    EVENT_OCCURRENCE_WF_GRAPH.addNode(EVENTS_VERBATIM_TO_INTERPRETED, EVENTS_INTERPRETED_TO_INDEX);
076    EVENT_OCCURRENCE_WF_GRAPH.addNode(EVENTS_VERBATIM_TO_INTERPRETED, EVENTS_HDFS_VIEW);
077
078    // Pipelines event only workflow
079    // 0?
080    EVENT_WF_GRAPH.addNode(NFS_TO_HDFS, DWCDP_TO_VERBATIM);
081    EVENT_WF_GRAPH.addNode(DWCDP_STAGE, DWCDP_TO_VERBATIM);
082    // 1
083    EVENT_WF_GRAPH.addNode(DWCDP_TO_VERBATIM, EVENTS_VERBATIM_TO_INTERPRETED);
084    EVENT_WF_GRAPH.addNode(DWCA_TO_VERBATIM, EVENTS_VERBATIM_TO_INTERPRETED);
085    // 2
086    EVENT_WF_GRAPH.addNode(EVENTS_VERBATIM_TO_INTERPRETED, EVENTS_INTERPRETED_TO_INDEX);
087    EVENT_WF_GRAPH.addNode(EVENTS_VERBATIM_TO_INTERPRETED, EVENTS_HDFS_VIEW);
088
089    // Pipelines validator workflow
090    // 1
091    VALIDATON_WF_GRAPH.addNode(VALIDATOR_UPLOAD_ARCHIVE, VALIDATOR_VALIDATE_ARCHIVE);
092    // 2
093    VALIDATON_WF_GRAPH.addNode(VALIDATOR_VALIDATE_ARCHIVE, VALIDATOR_DWCA_TO_VERBATIM);
094    VALIDATON_WF_GRAPH.addNode(VALIDATOR_VALIDATE_ARCHIVE, VALIDATOR_XML_TO_VERBATIM);
095    VALIDATON_WF_GRAPH.addNode(VALIDATOR_VALIDATE_ARCHIVE, VALIDATOR_ABCD_TO_VERBATIM);
096    VALIDATON_WF_GRAPH.addNode(VALIDATOR_VALIDATE_ARCHIVE, VALIDATOR_TABULAR_TO_VERBATIM);
097    // 3
098    VALIDATON_WF_GRAPH.addNode(VALIDATOR_DWCA_TO_VERBATIM, VALIDATOR_VERBATIM_TO_INTERPRETED);
099    VALIDATON_WF_GRAPH.addNode(VALIDATOR_XML_TO_VERBATIM, VALIDATOR_VERBATIM_TO_INTERPRETED);
100    VALIDATON_WF_GRAPH.addNode(VALIDATOR_ABCD_TO_VERBATIM, VALIDATOR_VERBATIM_TO_INTERPRETED);
101    VALIDATON_WF_GRAPH.addNode(VALIDATOR_TABULAR_TO_VERBATIM, VALIDATOR_VERBATIM_TO_INTERPRETED);
102    // 4
103    VALIDATON_WF_GRAPH.addNode(VALIDATOR_VERBATIM_TO_INTERPRETED, VALIDATOR_INTERPRETED_TO_INDEX);
104    // 5
105    VALIDATON_WF_GRAPH.addNode(VALIDATOR_INTERPRETED_TO_INDEX, VALIDATOR_COLLECT_METRICS);
106  }
107
108  public static Graph<StepType> getOccurrenceWorkflow() {
109    return OCCURRENCE_WF_GRAPH;
110  }
111
112  public static Graph<StepType> getEventOccurrenceWorkflow() {
113    return EVENT_OCCURRENCE_WF_GRAPH;
114  }
115
116  public static Graph<StepType> getEventWorkflow() {
117    return EVENT_WF_GRAPH;
118  }
119
120  public static Graph<StepType> getValidatorWorkflow() {
121    return VALIDATON_WF_GRAPH;
122  }
123
124  public static Graph<StepType> getWorkflow(boolean containsOccurrences, boolean containsEvents) {
125    if(containsOccurrences && containsEvents){
126      return getEventOccurrenceWorkflow();
127    } else if(containsOccurrences){
128      return getOccurrenceWorkflow();
129    } else if (containsEvents){
130      return getEventWorkflow();
131    }
132    return new Graph<>();
133  }
134
135  public static class Graph<T> {
136
137    public class Edge {
138      private final T node;
139
140      public Edge(T node) {
141        this.node = node;
142      }
143
144      public T getNode() {
145        return node;
146      }
147    }
148
149    private final Map<T, LinkedList<Edge>> nodes = new HashMap<>();
150
151    private final Map<T, Integer> levels = new HashMap<>();
152
153    private final ToIntFunction<T> calculateLevelFn = t -> levels.get(t) != null ? levels.get(t) + 1 : 1;
154
155    private final ToIntFunction<T> findLevelFn = t -> nodes.values()
156      .stream()
157      .flatMap(Collection::stream)
158      .filter(x->x.getNode().equals(t))
159      .findAny()
160      .map(x->levels.get(x.getNode()))
161      .orElse(1);
162
163    public int getNodesQuantity() {
164      return nodes.size();
165    }
166
167    public List<Edge> getNodeEdges(T node) {
168      return nodes.get(node);
169    }
170
171    public Set<T> getAllNodes() {
172      return nodes.keySet();
173    }
174
175    public Set<T> getAllNodesFor(Set<T> fromTypesSet) {
176      return fromTypesSet.stream()
177        .map(ft -> bfs(this, ft))
178        .flatMap(Collection::stream)
179        .collect(Collectors.toSet());
180    }
181
182    /**
183     * Returns the depth level of a node in the workflow graph, calculated relative to the insertion
184     * order of edges in the static graph initializer.
185     *
186     * <p><b>Deprecated:</b> This method is fundamentally unreliable for graphs with multiple entry
187     * points, as there is no globally meaningful notion of depth in a directed acyclic graph with
188     * more than one root. The level of a node is only well-defined relative to a specific starting
189     * node, and will produce incorrect or inconsistent results when new entry points are added to
190     * the graph.
191     *
192     * <p>Use {@link Graph#getRootNodesFor(Set)} to determine which steps should be triggered first
193     * from a given set of requested steps. For execution history and step ordering, prefer deriving
194     * state from the persisted execution record in the registry rather than from static level
195     * arithmetic.
196     *
197     * @param t the node to look up
198     * @return the computed depth level of the node, unreliable in multi-root graphs
199     */
200    @Deprecated
201    public int getLevel(T t) {
202      return this.levels.get(t);
203    }
204
205    public Set<T> getRootNodesFor(Set<T> fromTypesSet) {
206      Map<T, Set<T>> map = fromTypesSet.stream()
207        .collect(Collectors.toMap(Function.identity(), ft -> bfs(this, ft)));
208
209      Set<T> result = new LinkedHashSet<>();
210
211      fromTypesSet.forEach(ts -> {
212        result.add(ts);
213        map.forEach((key, value) -> {
214          if (key != ts && value.contains(ts)) {
215            result.remove(ts);
216          }
217        });
218      });
219
220      return result;
221    }
222
223    private void addNode(T fromNode, T toNode) {
224      if (nodes.containsKey(fromNode)) {
225        LinkedList<Edge> edges = nodes.get(fromNode);
226
227        int order;
228        if (edges.isEmpty()) {
229          order = calculateLevelFn.applyAsInt(fromNode);
230        } else {
231          order = levels.get(edges.getLast().getNode());
232        }
233
234        edges.add(new Edge(toNode));
235        levels.put(toNode, order);
236      } else {
237        int order = calculateLevelFn.applyAsInt(fromNode);
238        nodes.put(fromNode, new LinkedList<>(Collections.singletonList(new Edge(toNode))));
239        levels.put(fromNode, order);
240        levels.put(toNode, order + 1);
241      }
242
243      nodes.computeIfAbsent(toNode, n -> new LinkedList<>(Collections.emptyList()));
244      levels.put(toNode, findLevelFn.applyAsInt(toNode));
245    }
246
247    private static <T> Set<T> bfs(Graph<T> graph, T startNode) {
248
249      Set<T> resultSet = new HashSet<>();
250      Set<T> visitedNodes = new HashSet<>(graph.getNodesQuantity());
251      Queue<T> queue = new LinkedList<>();
252      T currentNode;
253
254      visitedNodes.add(startNode);
255      queue.add(startNode);
256
257      while (!queue.isEmpty()) {
258        currentNode = queue.poll();
259        resultSet.add(currentNode);
260
261        graph.getNodeEdges(currentNode).forEach(edge -> {
262          T node = edge.getNode();
263          if (visitedNodes.contains(node)) {
264            return;
265          }
266          visitedNodes.add(node);
267          queue.add(node);
268        });
269      }
270
271      return resultSet;
272    }
273
274  }
275}