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

common-workflow-language / cwlviewer / #1992

12 May 2026 05:53PM UTC coverage: 70.861% (+0.5%) from 70.334%
#1992

Pull #751

github

kinow
Build CodeQL with JVM 25
Pull Request #751: Bump org.springframework.boot:spring-boot-starter-parent from 3.1.4 to 4.1.0-RC1

42 of 90 new or added lines in 23 files covered. (46.67%)

19 existing lines in 3 files now uncovered.

1695 of 2392 relevant lines covered (70.86%)

0.71 hits per line

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

77.83
/src/main/java/org/commonwl/view/workflow/WorkflowController.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 jakarta.servlet.http.HttpServletRequest;
23
import jakarta.servlet.http.HttpServletResponse;
24
import jakarta.validation.Valid;
25
import java.io.File;
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.nio.file.Path;
29
import java.util.List;
30
import org.apache.commons.lang.StringUtils;
31
import org.commonwl.view.WebConfig;
32
import org.commonwl.view.cwl.CWLNotAWorkflowException;
33
import org.commonwl.view.cwl.CWLService;
34
import org.commonwl.view.cwl.CWLToolStatus;
35
import org.commonwl.view.cwl.CWLValidationException;
36
import org.commonwl.view.git.GitDetails;
37
import org.commonwl.view.graphviz.GraphVizService;
38
import org.eclipse.jgit.api.errors.GitAPIException;
39
import org.eclipse.jgit.api.errors.TransportException;
40
import org.slf4j.Logger;
41
import org.slf4j.LoggerFactory;
42
import org.springframework.beans.factory.annotation.Autowired;
43
import org.springframework.beans.factory.annotation.Value;
44
import org.springframework.core.io.ClassPathResource;
45
import org.springframework.core.io.FileSystemResource;
46
import org.springframework.core.io.InputStreamResource;
47
import org.springframework.core.io.Resource;
48
import org.springframework.data.domain.Pageable;
49
import org.springframework.data.web.PageableDefault;
50
import org.springframework.stereotype.Controller;
51
import org.springframework.ui.Model;
52
import org.springframework.validation.BeanPropertyBindingResult;
53
import org.springframework.validation.BindingResult;
54
import org.springframework.web.bind.annotation.GetMapping;
55
import org.springframework.web.bind.annotation.PathVariable;
56
import org.springframework.web.bind.annotation.PostMapping;
57
import org.springframework.web.bind.annotation.RequestParam;
58
import org.springframework.web.bind.annotation.ResponseBody;
59
import org.springframework.web.servlet.HandlerMapping;
60
import org.springframework.web.servlet.ModelAndView;
61
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
62

63
@Controller
64
public class WorkflowController {
65

66
  private final Logger logger = LoggerFactory.getLogger(this.getClass());
1✔
67

68
  private final WorkflowFormValidator workflowFormValidator;
69
  private final WorkflowService workflowService;
70
  private final CWLService cwlService;
71
  private final GraphVizService graphVizService;
72

73
  /**
74
   * Autowired constructor to initialise objects used by the controller.
75
   *
76
   * @param workflowFormValidator Validator to validate the workflow form
77
   * @param workflowService Builds new Workflow objects
78
   * @param graphVizService Generates and stores images
79
   */
80
  @Autowired
81
  public WorkflowController(
82
      WorkflowFormValidator workflowFormValidator,
83
      WorkflowService workflowService,
84
      GraphVizService graphVizService,
85
      CWLService cwlService) {
1✔
86
    this.workflowFormValidator = workflowFormValidator;
1✔
87
    this.workflowService = workflowService;
1✔
88
    this.graphVizService = graphVizService;
1✔
89
    this.cwlService = cwlService;
1✔
90
  }
1✔
91

92
  /**
93
   * List all the workflows in the database, paginated
94
   *
95
   * @param model The model for the page
96
   * @param pageable Pagination for the list of workflows
97
   * @return The workflows view
98
   */
99
  @GetMapping(value = "/workflows")
100
  public String listWorkflows(Model model, @PageableDefault(size = 10) Pageable pageable) {
101
    model.addAttribute("workflows", workflowService.getPageOfWorkflows(pageable));
1✔
102
    model.addAttribute("pages", pageable);
1✔
103
    return "workflows";
1✔
104
  }
105

106
  /**
107
   * Search all the workflows in the database, paginated
108
   *
109
   * @param model The model for the page
110
   * @param pageable Pagination for the list of workflows
111
   * @return The workflows view
112
   */
113
  @GetMapping(value = "/workflows", params = "search")
114
  public String searchWorkflows(
115
      Model model,
116
      @PageableDefault(size = 10) Pageable pageable,
117
      @RequestParam(value = "search") String search) {
118
    model.addAttribute("workflows", workflowService.searchPageOfWorkflows(search, pageable));
×
119
    model.addAttribute("pages", pageable);
×
120
    model.addAttribute("search", search);
×
121
    return "workflows";
×
122
  }
123

124
  /**
125
   * Create a new workflow from the given URL in the form
126
   *
127
   * @param workflowForm The data submitted from the form
128
   * @param bindingResult Spring MVC Binding Result object
129
   * @return The workflow view with new workflow as a model
130
   */
131
  @PostMapping("/workflows")
132
  public ModelAndView createWorkflow(
133
      @Valid WorkflowForm workflowForm, BindingResult bindingResult) {
134

135
    // Run validator which checks the git URL is valid
136
    GitDetails gitInfo = workflowFormValidator.validateAndParse(workflowForm, bindingResult);
1✔
137

138
    if (bindingResult.hasErrors() || gitInfo == null) {
1✔
139
      // Go back to index if there are validation errors
140
      return new ModelAndView("index");
1✔
141
    } else {
142
      // Get workflow or create if does not exist
143
      Workflow workflow = workflowService.getWorkflow(gitInfo);
1✔
144
      if (workflow == null) {
1✔
145
        try {
146
          if (gitInfo.getPath().endsWith(".cwl")) {
1✔
147
            QueuedWorkflow result = workflowService.createQueuedWorkflow(gitInfo);
1✔
148
            if (result.getWorkflowList() == null) {
1✔
149
              workflow = result.getTempRepresentation();
×
150
            } else {
151
              if (result.getWorkflowList().size() == 1) {
1✔
NEW
152
                gitInfo.setPackedId(result.getWorkflowList().getFirst().fileName());
×
153
              }
154
              return new ModelAndView("redirect:" + gitInfo.getInternalUrl());
1✔
155
            }
156
          } else {
×
157
            return new ModelAndView("redirect:" + gitInfo.getInternalUrl());
1✔
158
          }
159
        } catch (TransportException ex) {
1✔
160
          logger.warn("git.sshError " + workflowForm, ex);
1✔
161
          bindingResult.rejectValue("url", "git.sshError");
1✔
162
          return new ModelAndView("index");
1✔
163
        } catch (GitAPIException ex) {
1✔
164
          logger.error("git.retrievalError " + workflowForm, ex);
1✔
165
          bindingResult.rejectValue("url", "git.retrievalError");
1✔
166
          return new ModelAndView("index");
1✔
167
        } catch (WorkflowNotFoundException ex) {
1✔
168
          logger.warn("git.notFound " + workflowForm, ex);
1✔
169
          bindingResult.rejectValue("url", "git.notFound");
1✔
170
          return new ModelAndView("index");
1✔
171
        } catch (CWLNotAWorkflowException ex) {
×
172
          logger.warn("cwl.notAWorkflow " + workflowForm, ex);
×
173
          bindingResult.rejectValue("url", "cwl.notAWorkflow");
×
174
          return new ModelAndView("index");
×
175
        } catch (Exception ex) {
1✔
176
          logger.warn("url.parsingError " + workflowForm, ex);
1✔
177
          bindingResult.rejectValue("url", "url.parsingError");
1✔
178
          return new ModelAndView("index");
1✔
179
        }
×
180
      }
181
      gitInfo = workflow.getRetrievedFrom();
×
182
      // Redirect to the workflow
183
      return new ModelAndView("redirect:" + gitInfo.getInternalUrl());
×
184
    }
185
  }
186

187
  /**
188
   * Display a page for a particular workflow from github.com or gitlab.com format URL
189
   *
190
   * @param domain The domain of the hosting site, github.com or gitlab.com
191
   * @param owner The owner of the repository
192
   * @param repoName The name of the repository
193
   * @param branch The branch of repository
194
   * @return The workflow view with the workflow as a model
195
   */
196
  @GetMapping(
197
      value = {
198
        "/workflows/{domain}.com/{owner}/{repoName}/tree/{branch}/**",
199
        "/workflows/{domain}.com/{owner}/{repoName}/blob/{branch}/**"
200
      })
201
  public ModelAndView getWorkflow(
202
      @PathVariable String domain,
203
      @PathVariable String owner,
204
      @PathVariable String repoName,
205
      @PathVariable String branch,
206
      HttpServletRequest request,
207
      RedirectAttributes redirectAttrs) {
208
    // The wildcard end of the URL is the path
209
    String path =
1✔
210
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
211
    path = extractPath(path, 7);
1✔
212

213
    // Construct a GitDetails object to search for in the database
214
    GitDetails gitDetails = getGitDetails(domain, owner, repoName, branch, path);
1✔
215

216
    // Get workflow
217
    return getWorkflow(gitDetails, redirectAttrs);
1✔
218
  }
219

220
  /**
221
   * Display page for a workflow from a generic Git URL
222
   *
223
   * @param branch The branch of the repository
224
   * @return The workflow view with the workflow as a model
225
   */
226
  @GetMapping(value = "/workflows/*/*.git/{branch}/**")
227
  public ModelAndView getWorkflowGeneric(
228
      @Value("${applicationURL}") String applicationURL,
229
      @PathVariable String branch,
230
      HttpServletRequest request,
231
      RedirectAttributes redirectAttrs) {
232
    String path =
×
233
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
×
234
    GitDetails gitDetails = getGitDetails(11, path, branch);
×
235
    return getWorkflow(gitDetails, redirectAttrs);
×
236
  }
237

238
  /**
239
   * Download the Research Object Bundle for a particular workflow
240
   *
241
   * @param domain The domain of the hosting site, github.com or gitlab.com
242
   * @param owner The owner of the repository
243
   * @param repoName The name of the repository
244
   * @param branch The branch of repository
245
   */
246
  @GetMapping(
247
      value = {
248
        "/robundle/{domain}.com/{owner}/{repoName}/tree/{branch}/**",
249
        "/robundle/{domain}.com/{owner}/{repoName}/blob/{branch}/**"
250
      },
251
      produces = {"application/vnd.wf4ever.robundle+zip", "application/zip"})
252
  @ResponseBody
253
  public Resource getROBundle(
254
      @PathVariable String domain,
255
      @PathVariable String owner,
256
      @PathVariable String repoName,
257
      @PathVariable String branch,
258
      HttpServletRequest request,
259
      HttpServletResponse response) {
260
    String path =
1✔
261
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
262
    path = extractPath(path, 7);
1✔
263
    GitDetails gitDetails = getGitDetails(domain, owner, repoName, branch, path);
1✔
264
    File bundleDownload = workflowService.getROBundle(gitDetails);
1✔
265
    response.setHeader("Content-Disposition", "attachment; filename=bundle.zip;");
1✔
266
    return new FileSystemResource(bundleDownload);
1✔
267
  }
268

269
  /**
270
   * Download the Research Object Bundle for a particular workflow
271
   *
272
   * @param branch The branch of repository
273
   */
274
  @GetMapping(
275
      value = "/robundle/*/*/*.git/{branch}/**",
276
      produces = "application/vnd.wf4ever.robundle+zip")
277
  @ResponseBody
278
  public Resource getROBundleGeneric(
279
      @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) {
280
    String path =
1✔
281
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
282
    GitDetails gitDetails = getGitDetails(10, path, branch);
1✔
283
    File bundleDownload = workflowService.getROBundle(gitDetails);
1✔
284
    response.setHeader("Content-Disposition", "attachment; filename=bundle.zip;");
1✔
285
    return new FileSystemResource(bundleDownload);
1✔
286
  }
287

288
  /**
289
   * Download a generated graph for a workflow in SVG format
290
   *
291
   * @param domain The domain of the hosting site, github.com or gitlab.com
292
   * @param owner The owner of the repository
293
   * @param repoName The name of the repository
294
   * @param branch The branch of repository
295
   */
296
  @GetMapping(
297
      value = {
298
        "/graph/svg/{domain}.com/{owner}/{repoName}/tree/{branch}/**",
299
        "/graph/svg/{domain}.com/{owner}/{repoName}/blob/{branch}/**"
300
      },
301
      produces = "image/svg+xml")
302
  @ResponseBody
303
  public Resource downloadGraphSvg(
304
      @PathVariable String domain,
305
      @PathVariable String owner,
306
      @PathVariable String repoName,
307
      @PathVariable String branch,
308
      HttpServletRequest request,
309
      HttpServletResponse response)
310
      throws IOException {
311
    String path =
1✔
312
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
313
    path = extractPath(path, 8);
1✔
314
    GitDetails gitDetails = getGitDetails(domain, owner, repoName, branch, path);
1✔
315
    response.setHeader("Content-Disposition", "inline; filename=\"graph.svg\"");
1✔
316
    return workflowService.getWorkflowGraph("svg", gitDetails);
1✔
317
  }
318

319
  /**
320
   * Download a generated graph for a workflow in SVG format
321
   *
322
   * @param branch The branch of repository
323
   */
324
  @GetMapping(value = "/graph/svg/*/*/*.git/{branch}/**", produces = "image/svg+xml")
325
  @ResponseBody
326
  public Resource downloadGraphSvgGeneric(
327
      @PathVariable String branch, HttpServletRequest request, HttpServletResponse response)
328
      throws IOException {
329
    String path =
1✔
330
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
331
    GitDetails gitDetails = getGitDetails(11, path, branch);
1✔
332
    response.setHeader("Content-Disposition", "inline; filename=\"graph.svg\"");
1✔
333
    return workflowService.getWorkflowGraph("svg", gitDetails);
1✔
334
  }
335

336
  /**
337
   * Download a generated graph for a workflow in PNG format
338
   *
339
   * @param domain The domain of the hosting site, github.com or gitlab.com
340
   * @param owner The owner of the repository
341
   * @param repoName The name of the repository
342
   * @param branch The branch of repository
343
   */
344
  @GetMapping(
345
      value = {
346
        "/graph/png/{domain}.com/{owner}/{repoName}/tree/{branch}/**",
347
        "/graph/png/{domain}.com/{owner}/{repoName}/blob/{branch}/**"
348
      },
349
      produces = "image/png")
350
  @ResponseBody
351
  public Resource downloadGraphPng(
352
      @PathVariable String domain,
353
      @PathVariable String owner,
354
      @PathVariable String repoName,
355
      @PathVariable String branch,
356
      HttpServletRequest request,
357
      HttpServletResponse response)
358
      throws IOException {
359
    String path =
1✔
360
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
361
    path = extractPath(path, 8);
1✔
362
    GitDetails gitDetails = getGitDetails(domain, owner, repoName, branch, path);
1✔
363
    response.setHeader("Content-Disposition", "inline; filename=\"graph.png\"");
1✔
364
    return workflowService.getWorkflowGraph("png", gitDetails);
1✔
365
  }
366

367
  /**
368
   * Download a generated graph for a workflow in PNG format
369
   *
370
   * @param branch The branch of repository
371
   */
372
  @GetMapping(value = "/graph/png/*/*/*.git/{branch}/**", produces = "image/png")
373
  @ResponseBody
374
  public Resource downloadGraphPngGeneric(
375
      @PathVariable String branch, HttpServletRequest request, HttpServletResponse response)
376
      throws IOException {
377
    String path =
1✔
378
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
379
    GitDetails gitDetails = getGitDetails(11, path, branch);
1✔
380
    response.setHeader("Content-Disposition", "inline; filename=\"graph.png\"");
1✔
381
    return workflowService.getWorkflowGraph("png", gitDetails);
1✔
382
  }
383

384
  /**
385
   * Download a generated graph for a workflow in XDOT format
386
   *
387
   * @param domain The domain of the hosting site, github.com or gitlab.com
388
   * @param owner The owner of the repository
389
   * @param repoName The name of the repository
390
   * @param branch The branch of repository
391
   */
392
  @GetMapping(
393
      value = {
394
        "/graph/xdot/{domain}.com/{owner}/{repoName}/tree/{branch}/**",
395
        "/graph/xdot/{domain}.com/{owner}/{repoName}/blob/{branch}/**"
396
      },
397
      produces = "text/vnd.graphviz")
398
  @ResponseBody
399
  public Resource downloadGraphDot(
400
      @PathVariable String domain,
401
      @PathVariable String owner,
402
      @PathVariable String repoName,
403
      @PathVariable String branch,
404
      HttpServletRequest request,
405
      HttpServletResponse response)
406
      throws IOException {
407
    String path =
1✔
408
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
409
    path = extractPath(path, 8);
1✔
410
    GitDetails gitDetails = getGitDetails(domain, owner, repoName, branch, path);
1✔
411
    response.setHeader("Content-Disposition", "inline; filename=\"graph.dot\"");
1✔
412
    return workflowService.getWorkflowGraph("xdot", gitDetails);
1✔
413
  }
414

415
  /**
416
   * Download a generated graph for a workflow in XDOT format
417
   *
418
   * @param branch The branch of repository
419
   */
420
  @GetMapping(value = "/graph/xdot/*/*/*.git/{branch}/**", produces = "text/vnd.graphviz")
421
  @ResponseBody
422
  public Resource downloadGraphDotGeneric(
423
      @PathVariable String branch, HttpServletRequest request, HttpServletResponse response)
424
      throws IOException {
425
    String path =
1✔
426
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
427
    GitDetails gitDetails = getGitDetails(12, path, branch);
1✔
428
    response.setHeader("Content-Disposition", "inline; filename=\"graph.dot\"");
1✔
429
    return workflowService.getWorkflowGraph("xdot", gitDetails);
1✔
430
  }
431

432
  /**
433
   * Get a temporary graph for a pending workflow
434
   *
435
   * @param queueID The ID in the queue
436
   * @return The visualisation image
437
   */
438
  @GetMapping(
439
      value = {"/queue/{queueID}/tempgraph.png"},
440
      produces = "image/png")
441
  @ResponseBody
442
  public ClassPathResource getTempGraphAsPng(
443
      @PathVariable String queueID, HttpServletResponse response) throws IOException {
444
    QueuedWorkflow queued = workflowService.getQueuedWorkflow(queueID);
×
445
    if (queued == null) {
×
446
      throw new WorkflowNotFoundException();
×
447
    }
448
    Path out =
×
449
        graphVizService.getGraphPath(
×
450
            queued.getId() + ".png", queued.getTempRepresentation().getVisualisationDot(), "png");
×
451
    response.setHeader("Content-Disposition", "inline; filename=\"graph.png\"");
×
NEW
452
    return new ClassPathResource(out.toString());
×
453
  }
454

455
  /**
456
   * Take a CWL workflow from the POST body and generate a PNG graph for it.
457
   *
458
   * @param in The workflow CWL
459
   */
460
  @PostMapping(
461
      value = "/graph/png",
462
      produces = "image/png",
463
      consumes = {"text/yaml", "text/x-yaml", "text/plain", "application/octet-stream"})
464
  @ResponseBody
465
  public Resource downloadGraphPngFromFile(InputStream in, HttpServletResponse response)
466
      throws IOException {
467
    response.setHeader("Content-Disposition", "inline; filename=\"graph.png\"");
1✔
468
    return getGraphFromInputStream(in, "png");
1✔
469
  }
470

471
  /**
472
   * Take a CWL workflow from the POST body and generate a SVG graph for it.
473
   *
474
   * @param in The workflow CWL as YAML text
475
   */
476
  @PostMapping(
477
      value = "/graph/svg",
478
      produces = "image/svg+xml",
479
      consumes = {"text/yaml", "text/x-yaml", "text/plain", "application/octet-stream"})
480
  @ResponseBody
481
  public Resource downloadGraphSvgFromFile(InputStream in, HttpServletResponse response)
482
      throws IOException {
483
    response.setHeader("Content-Disposition", "inline; filename=\"graph.svg\"");
1✔
484
    return getGraphFromInputStream(in, "svg");
1✔
485
  }
486

487
  /**
488
   * Extract the path from the end of a full request string
489
   *
490
   * @param path The full request string path
491
   * @param startSlashNum The ordinal slash index of the start of the path
492
   * @return The path from the end
493
   */
494
  public static String extractPath(String path, int startSlashNum) {
495
    int pathStartIndex = StringUtils.ordinalIndexOf(path, "/", startSlashNum);
1✔
496
    if (pathStartIndex > -1 && pathStartIndex < path.length() - 1) {
1✔
497
      return path.substring(pathStartIndex + 1).replaceAll("\\/$", "");
1✔
498
    } else {
499
      return "/";
×
500
    }
501
  }
502

503
  /**
504
   * Constructs a GitDetails object for github.com/gitlab.com details
505
   *
506
   * @param domain The domain name ("github" or "gitlab", without .com)
507
   * @param owner The owner of the repository
508
   * @param repoName The name of the repository
509
   * @param branch The branch of the repository
510
   * @param path The path within the repository
511
   * @return A constructed GitDetails object
512
   */
513
  public static GitDetails getGitDetails(
514
      String domain, String owner, String repoName, String branch, String path) {
515
    String repoUrl =
516
        switch (domain) {
1✔
517
          case "github" -> "https://github.com/" + owner + "/" + repoName + ".git";
1✔
NEW
518
          case "gitlab" -> "https://gitlab.com/" + owner + "/" + repoName + ".git";
×
NEW
519
          default -> throw new WorkflowNotFoundException();
×
520
        };
521
    String[] pathSplit = path.split("#");
1✔
522
    GitDetails details = new GitDetails(repoUrl, branch, path);
1✔
523
    if (pathSplit.length > 1) {
1✔
524
      details.setPath(pathSplit[pathSplit.length - 2]);
×
525
      details.setPackedId(pathSplit[pathSplit.length - 1]);
×
526
    }
527
    return details;
1✔
528
  }
529

530
  /**
531
   * Constructs a GitDetails object for a generic path
532
   *
533
   * @param startIndex The start of the repository URL
534
   * @param path The entire URL path
535
   * @param branch The branch of the repository
536
   * @return A constructed GitDetails object
537
   */
538
  public static GitDetails getGitDetails(int startIndex, String path, String branch) {
539
    // The repository URL is the part after startIndex and up to and including .git
540
    String repoUrl = path.substring(startIndex);
1✔
541
    int extensionIndex = repoUrl.indexOf(".git");
1✔
542
    if (extensionIndex == -1) {
1✔
543
      throw new WorkflowNotFoundException();
×
544
    }
545
    repoUrl = "https://" + repoUrl.substring(0, extensionIndex + 4);
1✔
546

547
    // The path is after the branch
548
    int slashAfterBranch = path.indexOf("/", path.indexOf(branch));
1✔
549
    if (slashAfterBranch == -1 || slashAfterBranch == path.length()) {
1✔
550
      throw new WorkflowNotFoundException();
×
551
    }
552
    path = path.substring(slashAfterBranch + 1);
1✔
553

554
    // Construct GitDetails object for this workflow
555
    return new GitDetails(repoUrl, branch, path);
1✔
556
  }
557

558
  /**
559
   * Get a workflow from Git Details, creating if it does not exist
560
   *
561
   * @param gitDetails The details of the Git repository
562
   * @param redirectAttrs Error attributes for redirect
563
   * @return The model and view to be returned by the controller
564
   */
565
  private ModelAndView getWorkflow(GitDetails gitDetails, RedirectAttributes redirectAttrs) {
566
    // Get workflow
567
    QueuedWorkflow queued = null;
1✔
568
    Workflow workflowModel = workflowService.getWorkflow(gitDetails);
1✔
569
    if (workflowModel == null) {
1✔
570
      // Check if already queued
571
      queued = workflowService.getQueuedWorkflow(gitDetails);
1✔
572
      if (queued == null) {
1✔
573
        // Validation
574
        String packedPart =
575
            (gitDetails.getPackedId() == null) ? "" : "#" + gitDetails.getPackedId();
1✔
576
        WorkflowForm workflowForm =
1✔
577
            new WorkflowForm(
578
                gitDetails.getRepoUrl(), gitDetails.getBranch(), gitDetails.getPath() + packedPart);
1✔
579
        BeanPropertyBindingResult errors = new BeanPropertyBindingResult(workflowForm, "errors");
1✔
580
        workflowFormValidator.validateAndParse(workflowForm, errors);
1✔
581
        if (!errors.hasErrors()) {
1✔
582
          try {
583
            if (gitDetails.getPath().endsWith(".cwl")) {
1✔
584
              queued = workflowService.createQueuedWorkflow(gitDetails);
1✔
585
              if (queued.getWorkflowList() != null) {
1✔
586
                // Packed workflow listing
587
                if (queued.getWorkflowList().size() == 1) {
×
NEW
588
                  gitDetails.setPackedId(queued.getWorkflowList().getFirst().fileName());
×
589
                  return new ModelAndView("redirect:" + gitDetails.getInternalUrl());
×
590
                }
591
                return new ModelAndView(
×
592
                        "selectworkflow", "workflowOverviews", queued.getWorkflowList())
×
593
                    .addObject("gitDetails", gitDetails);
×
594
              }
595
            } else {
596
              List<WorkflowOverview> workflowOverviews =
1✔
597
                  workflowService.getWorkflowsFromDirectory(gitDetails);
1✔
598
              if (workflowOverviews.size() > 1) {
1✔
599
                return new ModelAndView("selectworkflow", "workflowOverviews", workflowOverviews)
1✔
600
                    .addObject("gitDetails", gitDetails);
1✔
601
              } else if (workflowOverviews.size() == 1) {
1✔
602
                return new ModelAndView(
1✔
603
                    "redirect:"
604
                        + gitDetails.getInternalUrl()
1✔
605
                        + workflowOverviews.getFirst().fileName());
1✔
606
              } else {
607
                errors.rejectValue(
×
608
                    "url",
609
                    "url.noWorkflowsInDirectory",
610
                    "No workflow files were found in the given directory");
611
              }
612
            }
613
          } catch (TransportException ex) {
1✔
614
            logger.warn("git.sshError " + workflowForm, ex);
1✔
615
            errors.rejectValue(
1✔
616
                "url",
617
                "git.sshError",
618
                "SSH URLs are not supported, please provide a HTTPS URL for the repository or submodules");
619
          } catch (GitAPIException ex) {
1✔
620
            logger.error("git.retrievalError " + workflowForm, ex);
1✔
621
            errors.rejectValue(
1✔
622
                "url",
623
                "git.retrievalError",
624
                "The workflow could not be retrieved from the Git repository using the details given");
625
          } catch (WorkflowNotFoundException ex) {
1✔
626
            logger.warn("git.notFound " + workflowForm, ex);
1✔
627
            errors.rejectValue(
1✔
628
                "url", "git.notFound", "The workflow could not be found within the repository.");
629
          } catch (CWLValidationException ex) {
×
630
            logger.warn("cwl.invalid " + workflowForm, ex);
×
631
            errors.rejectValue(
×
632
                "url", "cwl.invalid", "The workflow had a parsing error: " + ex.getMessage());
×
633
          } catch (IOException ex) {
1✔
634
            logger.warn("url.parsingError " + workflowForm, ex);
1✔
635
            errors.rejectValue(
1✔
636
                "url", "url.parsingError", "The workflow could not be parsed from the given URL");
637
          }
1✔
638
        }
639
        // Redirect to main page with errors if they occurred
640
        if (errors.hasErrors()) {
1✔
641
          redirectAttrs.addFlashAttribute("errors", errors);
1✔
642
          return new ModelAndView("redirect:/?url=" + gitDetails.getUrl());
1✔
643
        }
644
      }
645
    }
646

647
    // Display this model along with the view
648
    if (queued != null) {
1✔
649
      // Retry creation if there has been an error in cwltool parsing
650
      if (queued.getCwltoolStatus() == CWLToolStatus.ERROR) {
1✔
651
        workflowService.retryCwltool(queued);
×
652
      }
653
      return new ModelAndView("loading", "queued", queued);
1✔
654
    } else {
655
      return new ModelAndView("workflow", "workflow", workflowModel)
1✔
656
          .addObject("lineSeparator", System.lineSeparator())
1✔
657
          .addObject("formats", WebConfig.Format.values());
1✔
658
    }
659
  }
660

661
  private Resource getGraphFromInputStream(InputStream in, String format)
662
      throws IOException, WorkflowNotFoundException, CWLValidationException {
663
    Workflow workflow =
1✔
664
        cwlService.parseWorkflowNative(in, null, "workflow"); // first workflow will do
1✔
665
    InputStream out = graphVizService.getGraphStream(workflow.getVisualisationDot(), format);
1✔
666
    return new InputStreamResource(out);
1✔
667
  }
668
}
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