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

common-workflow-language / cwlviewer / #1997

13 May 2026 05:20PM UTC coverage: 70.25% (-0.08%) from 70.334%
#1997

Pull #751

github

kinow
Undo code deletion, but replace string concatenation by log+parameters
Pull Request #751: Bump org.springframework.boot:spring-boot-starter-parent from 3.1.4 to 4.1.0-RC1

119 of 196 new or added lines in 32 files covered. (60.71%)

20 existing lines in 4 files now uncovered.

1712 of 2437 relevant lines covered (70.25%)

0.7 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.FileSystemResource;
45
import org.springframework.core.io.InputStreamResource;
46
import org.springframework.core.io.Resource;
47
import org.springframework.data.domain.Pageable;
48
import org.springframework.data.web.PageableDefault;
49
import org.springframework.stereotype.Controller;
50
import org.springframework.ui.Model;
51
import org.springframework.validation.BeanPropertyBindingResult;
52
import org.springframework.validation.BindingResult;
53
import org.springframework.web.bind.annotation.GetMapping;
54
import org.springframework.web.bind.annotation.PathVariable;
55
import org.springframework.web.bind.annotation.PostMapping;
56
import org.springframework.web.bind.annotation.RequestParam;
57
import org.springframework.web.bind.annotation.ResponseBody;
58
import org.springframework.web.servlet.HandlerMapping;
59
import org.springframework.web.servlet.ModelAndView;
60
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
61

62
@Controller
63
public class WorkflowController {
64

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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