• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

common-workflow-language / cwlviewer / #1694

30 Sep 2024 10:51AM UTC coverage: 70.306% (-0.5%) from 70.811%
#1694

push

github

mr-c
detect non-Workflow CWL documents and give a better error message

2 of 19 new or added lines in 4 files covered. (10.53%)

106 existing lines in 4 files now uncovered.

1700 of 2418 relevant lines covered (70.31%)

0.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.19
/src/main/java/org/commonwl/view/workflow/WorkflowService.java
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19

20
package org.commonwl.view.workflow;
21

22
import java.io.File;
23
import java.io.IOException;
24
import java.nio.file.Files;
25
import java.nio.file.Path;
26
import java.nio.file.Paths;
27
import java.util.ArrayList;
28
import java.util.Calendar;
29
import java.util.Date;
30
import java.util.List;
31
import java.util.Objects;
32
import java.util.Optional;
33
import org.commonwl.view.cwl.CWLService;
34
import org.commonwl.view.cwl.CWLToolRunner;
35
import org.commonwl.view.cwl.CWLToolStatus;
36
import org.commonwl.view.cwl.CWLValidationException;
37
import org.commonwl.view.git.GitDetails;
38
import org.commonwl.view.git.GitSemaphore;
39
import org.commonwl.view.git.GitService;
40
import org.commonwl.view.graphviz.GraphVizService;
41
import org.commonwl.view.researchobject.ROBundleFactory;
42
import org.commonwl.view.researchobject.ROBundleNotFoundException;
43
import org.commonwl.view.util.FileUtils;
44
import org.eclipse.jgit.api.Git;
45
import org.eclipse.jgit.api.errors.GitAPIException;
46
import org.eclipse.jgit.api.errors.RefNotFoundException;
47
import org.slf4j.Logger;
48
import org.slf4j.LoggerFactory;
49
import org.springframework.beans.factory.annotation.Autowired;
50
import org.springframework.beans.factory.annotation.Value;
51
import org.springframework.core.io.PathResource;
52
import org.springframework.data.domain.Page;
53
import org.springframework.data.domain.Pageable;
54
import org.springframework.stereotype.Service;
55

56
@Service
57
public class WorkflowService {
58

59
  private final Logger logger = LoggerFactory.getLogger(this.getClass());
1✔
60

61
  private final GitService gitService;
62
  private final CWLService cwlService;
63
  private final WorkflowRepository workflowRepository;
64
  private final QueuedWorkflowRepository queuedWorkflowRepository;
65
  private final ROBundleFactory ROBundleFactory;
66
  private final GraphVizService graphVizService;
67
  private final CWLToolRunner cwlToolRunner;
68
  private final GitSemaphore gitSemaphore;
69
  private final int cacheDays;
70

71
  @Autowired
72
  public WorkflowService(
73
      GitService gitService,
74
      CWLService cwlService,
75
      WorkflowRepository workflowRepository,
76
      QueuedWorkflowRepository queuedWorkflowRepository,
77
      ROBundleFactory ROBundleFactory,
78
      GraphVizService graphVizService,
79
      CWLToolRunner cwlToolRunner,
80
      GitSemaphore gitSemaphore,
81
      @Value("${cacheDays}") int cacheDays) {
1✔
82
    this.gitService = gitService;
1✔
83
    this.cwlService = cwlService;
1✔
84
    this.workflowRepository = workflowRepository;
1✔
85
    this.queuedWorkflowRepository = queuedWorkflowRepository;
1✔
86
    this.ROBundleFactory = ROBundleFactory;
1✔
87
    this.graphVizService = graphVizService;
1✔
88
    this.cwlToolRunner = cwlToolRunner;
1✔
89
    this.cacheDays = cacheDays;
1✔
90
    this.gitSemaphore = gitSemaphore;
1✔
91
  }
1✔
92

93
  /**
94
   * Gets a page of all workflows from the database
95
   *
96
   * @param pageable The details of the page to be requested
97
   * @return The resulting page of the workflow entries
98
   */
99
  public Page<Workflow> getPageOfWorkflows(Pageable pageable) {
100
    return workflowRepository.findAllByOrderByRetrievedOnDesc(pageable);
×
101
  }
102

103
  /**
104
   * Gets a page of all workflows from the database
105
   *
106
   * @param searchString The string to search for
107
   * @param pageable The details of the page to be requested
108
   * @return The resulting page of the workflow entries
109
   */
110
  public Page<Workflow> searchPageOfWorkflows(String searchString, Pageable pageable) {
111
    return workflowRepository.findByLabelContainingOrDocContainingIgnoreCase(
×
112
        searchString, searchString, pageable);
113
  }
114

115
  /**
116
   * Get a workflow from the database by its ID
117
   *
118
   * @param id The ID of the workflow
119
   * @return The model for the workflow
120
   */
121
  public Workflow getWorkflow(String id) {
122
    return workflowRepository.findById(id).orElse(null);
×
123
  }
124

125
  /**
126
   * Get a queued workflow from the database by its ID
127
   *
128
   * @param id The ID of the queued workflow
129
   * @return The model for the queued workflow
130
   */
131
  public QueuedWorkflow getQueuedWorkflow(String id) {
132
    return queuedWorkflowRepository.findById(id).orElse(null);
×
133
  }
134

135
  /**
136
   * Get a queued workflow from the database
137
   *
138
   * @param githubInfo Github information for the workflow
139
   * @return The queued workflow model
140
   */
141
  public QueuedWorkflow getQueuedWorkflow(GitDetails githubInfo) {
142
    QueuedWorkflow queued = queuedWorkflowRepository.findByRetrievedFrom(githubInfo);
1✔
143

144
    // Slash in branch fix
145
    boolean slashesInPath = true;
1✔
146
    while (queued == null && slashesInPath) {
1✔
147
      GitDetails correctedForSlash = gitService.transferPathToBranch(githubInfo);
1✔
148
      if (correctedForSlash != null) {
1✔
149
        githubInfo = correctedForSlash;
×
150
        queued = queuedWorkflowRepository.findByRetrievedFrom(githubInfo);
×
151
      } else {
152
        slashesInPath = false;
1✔
153
      }
154
    }
1✔
155

156
    return queued;
1✔
157
  }
158

159
  /**
160
   * Get a workflow from the database, refreshing it if cache has expired
161
   *
162
   * @param gitInfo Git information for the workflow
163
   * @return The workflow model associated with gitInfo
164
   */
165
  public Workflow getWorkflow(GitDetails gitInfo) {
166
    // Check database for existing workflows from this repository
167
    Workflow workflow = workflowRepository.findByRetrievedFrom(gitInfo);
1✔
168

169
    // Slash in branch fix
170
    boolean slashesInPath = true;
1✔
171
    while (workflow == null && slashesInPath) {
1✔
172
      GitDetails correctedForSlash = gitService.transferPathToBranch(gitInfo);
×
173
      if (correctedForSlash != null) {
×
174
        gitInfo = correctedForSlash;
×
175
        workflow = workflowRepository.findByRetrievedFrom(gitInfo);
×
176
      } else {
177
        slashesInPath = false;
×
178
      }
179
    }
×
180

181
    // Cache update
182
    if (workflow != null) {
1✔
183
      // Delete the existing workflow if the cache has expired
184
      if (cacheExpired(workflow)) {
1✔
185
        removeWorkflow(workflow);
1✔
186

187
        // Add the new workflow if it exists
188
        try {
189
          createQueuedWorkflow(workflow.getRetrievedFrom());
1✔
190

191
          // Add the old commit for the purposes of permalinks
192
          // TODO: Separate concept of commit from branch ref, see #164
193
          GitDetails byOldCommitId = workflow.getRetrievedFrom();
1✔
194
          byOldCommitId.setBranch(workflow.getLastCommit());
1✔
195
          if (getQueuedWorkflow(byOldCommitId) == null && getWorkflow(byOldCommitId) == null) {
1✔
196
            createQueuedWorkflow(byOldCommitId);
×
197
          }
198

199
          workflow = null;
1✔
200
        } catch (Exception e) {
×
201
          // Add back the old workflow if it is broken now
202
          logger.error("Could not parse updated workflow " + workflow.getID());
×
203
          workflowRepository.save(workflow);
×
204
        }
1✔
205
      }
206
    }
207

208
    return workflow;
1✔
209
  }
210

211
  /**
212
   * Get a list of workflows from a directory
213
   *
214
   * @param gitInfo The Git directory information
215
   * @return The list of workflow overviews
216
   */
217
  public List<WorkflowOverview> getWorkflowsFromDirectory(GitDetails gitInfo)
218
      throws IOException, GitAPIException {
219
    List<WorkflowOverview> workflowsInDir = new ArrayList<>();
1✔
220
    Git repo = null;
1✔
221
    try {
222
      boolean safeToAccess = gitSemaphore.acquire(gitInfo.getRepoUrl());
1✔
223
      while (repo == null) {
1✔
224
        try {
225
          repo = gitService.getRepository(gitInfo, safeToAccess);
1✔
226
        } catch (RefNotFoundException ex) {
×
227
          // Attempt slashes in branch fix
228
          GitDetails correctedForSlash = gitService.transferPathToBranch(gitInfo);
×
229
          if (correctedForSlash != null) {
×
230
            gitInfo = correctedForSlash;
×
231
          } else {
232
            throw ex;
×
233
          }
234
        }
1✔
235
      }
236

237
      Path localPath = repo.getRepository().getWorkTree().toPath();
1✔
238
      Path pathToDirectory = localPath.resolve(gitInfo.getPath()).normalize().toAbsolutePath();
1✔
239
      Path root = Paths.get("/").toAbsolutePath();
1✔
240
      if (pathToDirectory.equals(root)) {
1✔
241
        pathToDirectory = localPath;
1✔
242
      } else if (!pathToDirectory.startsWith(localPath.normalize().toAbsolutePath())) {
×
243
        // Prevent path traversal attacks
244
        throw new WorkflowNotFoundException();
×
245
      }
246

247
      File directory = new File(pathToDirectory.toString());
1✔
248
      if (directory.exists() && directory.isDirectory()) {
1✔
249
        for (final File file : directory.listFiles()) {
1✔
250
          int eIndex = file.getName().lastIndexOf('.') + 1;
1✔
251
          if (eIndex > 0) {
1✔
252
            String extension = file.getName().substring(eIndex);
1✔
253
            if (extension.equals("cwl")) {
1✔
254
              try {
255
                WorkflowOverview overview = cwlService.getWorkflowOverview(file);
1✔
256
                if (overview != null) {
1✔
257
                  workflowsInDir.add(overview);
1✔
258
                }
259
              } catch (IOException err) {
×
260
                logger.error("Skipping file due to IOException: " + file.toString(), err);
×
261
              }
1✔
262
            }
263
          }
264
        }
265
      }
266
    } finally {
267
      gitSemaphore.release(gitInfo.getRepoUrl());
1✔
268
      FileUtils.deleteTemporaryGitRepository(repo);
1✔
269
    }
270
    return workflowsInDir;
1✔
271
  }
272

273
  /**
274
   * Get the RO bundle for a Workflow, triggering re-download if it does not exist
275
   *
276
   * @param gitDetails The origin details of the workflow
277
   * @return The file containing the RO bundle
278
   * @throws ROBundleNotFoundException If the RO bundle was not found
279
   */
280
  public File getROBundle(GitDetails gitDetails) throws ROBundleNotFoundException {
281
    // Get workflow from database
282
    Workflow workflow = getWorkflow(gitDetails);
1✔
283

284
    // If workflow does not exist or the bundle doesn't yet
285
    if (workflow == null || workflow.getRoBundlePath() == null) {
1✔
286
      throw new ROBundleNotFoundException();
×
287
    }
288

289
    // 404 error with retry if the file on disk does not exist
290
    File bundleDownload = new File(workflow.getRoBundlePath());
1✔
291
    if (!bundleDownload.exists()) {
1✔
292
      // Clear current RO bundle link and create a new one (async)
293
      workflow.setRoBundlePath(null);
×
294
      workflowRepository.save(workflow);
×
295
      generateROBundle(workflow);
×
296
      throw new ROBundleNotFoundException();
×
297
    }
298

299
    return bundleDownload;
1✔
300
  }
301

302
  /**
303
   * Builds a new queued workflow from Git
304
   *
305
   * @param gitInfo Git information for the workflow
306
   * @return A queued workflow model
307
   * @throws GitAPIException Git errors
308
   * @throws WorkflowNotFoundException Workflow was not found within the repository
309
   * @throws IOException Other file handling exceptions
310
   */
311
  public QueuedWorkflow createQueuedWorkflow(GitDetails gitInfo)
312
      throws GitAPIException, WorkflowNotFoundException, IOException, CWLValidationException {
313
    QueuedWorkflow queuedWorkflow;
314

315
    Git repo = null;
1✔
316
    try {
317
      boolean safeToAccess = gitSemaphore.acquire(gitInfo.getRepoUrl());
1✔
318
      while (repo == null) {
1✔
319
        try {
320
          repo = gitService.getRepository(gitInfo, safeToAccess);
1✔
321
        } catch (RefNotFoundException ex) {
×
322
          // Attempt slashes in branch fix
323
          GitDetails correctedForSlash = gitService.transferPathToBranch(gitInfo);
×
324
          if (correctedForSlash != null) {
×
325
            gitInfo = correctedForSlash;
×
326
          } else {
327
            throw ex;
×
328
          }
329
        }
1✔
330
      }
331
      Path localPath = repo.getRepository().getWorkTree().toPath();
1✔
332
      String latestCommit = gitService.getCurrentCommitID(repo);
1✔
333

334
      Path workflowFile = localPath.resolve(gitInfo.getPath()).normalize().toAbsolutePath();
1✔
335
      // Prevent path traversal attacks
336
      if (!workflowFile.startsWith(localPath.normalize().toAbsolutePath())) {
1✔
337
        throw new WorkflowNotFoundException();
×
338
      }
339

340
      // Check workflow is readable
341
      if (!Files.isReadable(workflowFile)) {
1✔
NEW
342
        throw new WorkflowNotFoundException("Unable to read workflow file on disk.");
×
343
      }
344

345
      // Handling of packed workflows
346
      String packedWorkflowId = gitInfo.getPackedId();
1✔
347
      if (packedWorkflowId == null) {
1✔
348
        if (cwlService.isPacked(workflowFile.toFile())) {
1✔
349
          List<WorkflowOverview> overviews =
×
350
              cwlService.getWorkflowOverviewsFromPacked(workflowFile.toFile());
×
351
          if (overviews.size() == 0) {
×
352
            throw new IOException(
×
353
                "No workflow was found within the packed CWL file. " + gitInfo.toSummary());
×
354
          } else {
355
            // Dummy queued workflow object to return the list
356
            QueuedWorkflow overviewList = new QueuedWorkflow();
×
357
            overviewList.setWorkflowList(overviews);
×
358
            return overviewList;
×
359
          }
360
        }
361
      } else {
362
        // Packed ID specified but was not found
363
        if (!cwlService.isPacked(workflowFile.toFile())) {
×
364
          throw new WorkflowNotFoundException();
×
365
        }
366
      }
367

368
      Workflow basicModel = cwlService.parseWorkflowNative(workflowFile, packedWorkflowId);
1✔
369

370
      // Set origin details
371
      basicModel.setRetrievedOn(new Date());
1✔
372
      basicModel.setRetrievedFrom(gitInfo);
1✔
373
      basicModel.setLastCommit(latestCommit);
1✔
374

375
      // Save the queued workflow to database
376
      queuedWorkflow = new QueuedWorkflow();
1✔
377
      queuedWorkflow.setTempRepresentation(basicModel);
1✔
378
      queuedWorkflowRepository.save(queuedWorkflow);
1✔
379

380
      // ASYNC OPERATIONS
381
      // Parse with cwltool and update model
382
      try {
383
        cwlToolRunner.createWorkflowFromQueued(queuedWorkflow);
1✔
384
      } catch (Exception e) {
×
385
        logger.error("Could not update workflow with cwltool: " + gitInfo.toSummary(), e);
×
386
      }
1✔
387

388
    } catch (GitAPIException | RuntimeException | IOException e) {
1✔
389
      logger.warn(
1✔
390
          String.format(
1✔
391
              "Failed to create Queued Workflow: %s - Temporary files will be deleted for %s.",
392
              e.getMessage(), gitInfo.toSummary()),
1✔
393
          e);
394
      FileUtils.deleteGitRepository(repo);
1✔
395
      throw e;
1✔
396
    } finally {
397
      gitSemaphore.release(gitInfo.getRepoUrl());
1✔
398
      FileUtils.deleteTemporaryGitRepository(repo);
1✔
399
    }
400

401
    // Return this model to be displayed
402
    return queuedWorkflow;
1✔
403
  }
404

405
  /**
406
   * Retry the running of cwltool to create a new workflow
407
   *
408
   * @param queuedWorkflow The workflow to use to update
409
   */
410
  public void retryCwltool(QueuedWorkflow queuedWorkflow) {
411
    queuedWorkflow.setMessage(null);
×
412
    queuedWorkflow.setCwltoolStatus(CWLToolStatus.RUNNING);
×
413
    queuedWorkflowRepository.save(queuedWorkflow);
×
414
    try {
415
      cwlToolRunner.createWorkflowFromQueued(queuedWorkflow);
×
416
    } catch (Exception e) {
×
417
      logger.error("Could not update workflow " + queuedWorkflow.getId() + " with cwltool.", e);
×
418
    }
×
419
  }
×
420

421
  /**
422
   * Find a workflow by commit ID and path
423
   *
424
   * @param commitID The commit ID of the workflow
425
   * @param path The path to the workflow within the repository
426
   * @return A workflow model with the above two parameters
427
   */
428
  public Workflow findByCommitAndPath(String commitID, String path)
429
      throws WorkflowNotFoundException {
430
    return findByCommitAndPath(commitID, path, Optional.empty());
×
431
  }
432

433
  /**
434
   * Find a workflow by commit ID and path
435
   *
436
   * @param commitID The commit ID of the workflow
437
   * @param path The path to the workflow within the repository
438
   * @param part The #part within the workflow, or Optional.empty() for no part, or <code>null
439
   *     </code> for any part
440
   * @return A workflow model with the above two parameters
441
   */
442
  public Workflow findByCommitAndPath(String commitID, String path, Optional<String> part)
443
      throws WorkflowNotFoundException, MultipleWorkflowsException {
444
    List<Workflow> matches = workflowRepository.findByCommitAndPath(commitID, path);
×
445
    if (matches == null || matches.isEmpty()) {
×
446
      throw new WorkflowNotFoundException();
×
447
    } else if (part == null) {
×
448
      // any part will do - so we'll pick the first one
449
      return matches.get(0);
×
450
    } else {
451
      // Multiple matches means either added by both branch and ID
452
      // Or packed workflow
453
      for (Workflow workflow : matches) {
×
454
        if (Objects.equals(part.orElse(null), workflow.getRetrievedFrom().getPackedId())) {
×
455
          // either both are null; or both are non-null and equal
456
          return workflow;
×
457
        }
458
      }
×
459
      if (part.isPresent()) {
×
460
        // Sorry, don't know about your part!
461
        throw new WorkflowNotFoundException();
×
462
      } else {
463
        // No part requested, let's show some alternate permalinks,
464
        // in addition to the raw redirect
465
        throw new MultipleWorkflowsException(matches);
×
466
      }
467
    }
468
  }
469

470
  /**
471
   * Get a graph in a particular format and return it
472
   *
473
   * @param format The format for the graph file
474
   * @param gitDetails The Git details of the workflow
475
   * @return A FileSystemResource representing the graph
476
   * @throws WorkflowNotFoundException Error getting the workflow or format
477
   * @throws IOException Error reading the workflow files
478
   */
479
  public PathResource getWorkflowGraph(String format, GitDetails gitDetails)
480
      throws WorkflowNotFoundException, IOException {
481
    // Determine file extension from format
482
    String extension;
483
    switch (format) {
×
484
      case "svg":
485
      case "png":
486
        extension = format;
×
487
        break;
×
488
      case "xdot":
489
        extension = "dot";
×
490
        break;
×
491
      default:
492
        throw new WorkflowNotFoundException("Format " + format + " not recognized.");
×
493
    }
494

495
    // Get workflow
496
    Workflow workflow = getWorkflow(gitDetails);
×
497
    if (workflow == null) {
×
498
      throw new WorkflowNotFoundException(
×
499
          "Unable to retrieve workflow for " + gitDetails.toSummary());
×
500
    }
501

502
    // Generate graph and serve the file
503
    Path out =
×
504
        graphVizService.getGraphPath(
×
505
            workflow.getID() + "." + extension, workflow.getVisualisationDot(), format);
×
506
    return new PathResource(out);
×
507
  }
508

509
  /**
510
   * Generates the RO bundle for a Workflow and adds it to the model
511
   *
512
   * @param workflow The workflow model to create a Research Object for
513
   */
514
  private void generateROBundle(Workflow workflow) {
515
    try {
516
      ROBundleFactory.createWorkflowRO(workflow);
×
517
    } catch (Exception ex) {
×
518
      logger.error(
×
519
          "Error creating RO Bundle for workflow from " + workflow.getRetrievedFrom().toSummary(),
×
520
          ex);
521
    }
×
522
  }
×
523

524
  /**
525
   * Removes a workflow and its research object bundle
526
   *
527
   * @param workflow The workflow to be deleted
528
   */
529
  private void removeWorkflow(Workflow workflow) {
530
    // Delete the Research Object Bundle from disk
531
    if (workflow.getRoBundlePath() != null) {
1✔
532
      File roBundle = new File(workflow.getRoBundlePath());
1✔
533
      if (roBundle.delete()) {
1✔
534
        logger.debug("Deleted Research Object Bundle for workflow " + workflow.getID());
1✔
535
      } else {
536
        logger.info("Failed to delete Research Object Bundle for workflow " + workflow.getID());
×
537
      }
538
    }
539

540
    // Delete cached graphviz images if they exist
541
    graphVizService.deleteCache(workflow.getID());
1✔
542

543
    // Remove the workflow from the database
544
    workflowRepository.delete(workflow);
1✔
545

546
    // Remove any queued repositories pointing to the workflow
547
    queuedWorkflowRepository.deleteByTempRepresentation_RetrievedFrom(workflow.getRetrievedFrom());
1✔
548
  }
1✔
549

550
  /**
551
   * Check for cache expiration based on time and commit sha
552
   *
553
   * @param workflow The cached workflow model
554
   * @return Whether or not there are new commits
555
   */
556
  private boolean cacheExpired(Workflow workflow) {
557
    // If this is a branch and not added by commit ID
558
    if (!workflow.getRetrievedFrom().getBranch().equals(workflow.getLastCommit())) {
1✔
559
      try {
560
        // Calculate expiration
561
        Calendar expireCal = Calendar.getInstance();
1✔
562
        expireCal.setTime(workflow.getRetrievedOn());
1✔
563
        expireCal.add(Calendar.DATE, cacheDays);
1✔
564
        Date expirationDate = expireCal.getTime();
1✔
565

566
        // Check cached retrievedOn date
567
        if (expirationDate.before(new Date())) {
1✔
568
          // Cache expiry time has elapsed
569
          // Check current head of the branch with the cached head
570
          logger.info(
1✔
571
              "Time has expired for caching, checking commits for workflow " + workflow.getID());
1✔
572
          String currentHead;
573
          Git repo = null;
1✔
574
          boolean safeToAccess = gitSemaphore.acquire(workflow.getRetrievedFrom().getRepoUrl());
1✔
575
          try {
576
            repo = gitService.getRepository(workflow.getRetrievedFrom(), safeToAccess);
1✔
577
            currentHead = gitService.getCurrentCommitID(repo);
1✔
578
          } finally {
579
            gitSemaphore.release(workflow.getRetrievedFrom().getRepoUrl());
1✔
580
            FileUtils.deleteTemporaryGitRepository(repo);
1✔
581
          }
582
          logger.info(
1✔
583
              "Current: "
584
                  + workflow.getLastCommit()
1✔
585
                  + ", HEAD: "
586
                  + currentHead
587
                  + " for workflow "
588
                  + workflow.getID());
1✔
589

590
          // Reset date in database if there are still no changes
591
          boolean expired = !workflow.getLastCommit().equals(currentHead);
1✔
592
          if (!expired) {
1✔
593
            workflow.setRetrievedOn(new Date());
×
594
            workflowRepository.save(workflow);
×
595
          }
596

597
          // Return whether the cache has expired
598
          return expired;
1✔
599
        }
600
      } catch (Exception ex) {
×
601
        // Default to no expiry if there was an API error
602
        logger.error(
×
603
            "API Error when checking for latest commit ID for caching for workflow "
604
                + workflow.getID(),
×
605
            ex);
606
      }
×
607
    }
608
    return false;
1✔
609
  }
610

611
  public Optional<String> findRawBaseForCommit(String commitId) {
612
    for (Workflow w : workflowRepository.findByCommit(commitId)) {
×
613
      String potentialRaw = w.getRetrievedFrom().getRawUrl(commitId);
×
614
      String path = w.getRetrievedFrom().getPath();
×
615
      if (potentialRaw.endsWith(path)) {
×
616
        return Optional.of(potentialRaw.replace(path, ""));
×
617
      }
618
    }
×
619
    // Not found, or not at github.com/gitlab.com
620
    return Optional.empty();
×
621
  }
622
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc