• 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

77.56
/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.security.NoSuchAlgorithmException;
30
import java.util.List;
31
import org.apache.commons.lang.StringUtils;
32
import org.commonwl.view.WebConfig;
33
import org.commonwl.view.cwl.CWLNotAWorkflowException;
34
import org.commonwl.view.cwl.CWLService;
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.graphviz.GraphVizService;
39
import org.eclipse.jgit.api.errors.GitAPIException;
40
import org.eclipse.jgit.api.errors.TransportException;
41
import org.slf4j.Logger;
42
import org.slf4j.LoggerFactory;
43
import org.springframework.beans.factory.annotation.Autowired;
44
import org.springframework.beans.factory.annotation.Value;
45
import org.springframework.core.io.FileSystemResource;
46
import org.springframework.core.io.InputStreamResource;
47
import org.springframework.core.io.PathResource;
48
import org.springframework.core.io.Resource;
49
import org.springframework.data.domain.Pageable;
50
import org.springframework.data.web.PageableDefault;
51
import org.springframework.stereotype.Controller;
52
import org.springframework.ui.Model;
53
import org.springframework.validation.BeanPropertyBindingResult;
54
import org.springframework.validation.BindingResult;
55
import org.springframework.web.bind.annotation.*;
56
import org.springframework.web.servlet.HandlerMapping;
57
import org.springframework.web.servlet.ModelAndView;
58
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
59

60
@Controller
61
public class WorkflowController {
62

63
  private final Logger logger = LoggerFactory.getLogger(this.getClass());
1✔
64

65
  private final WorkflowFormValidator workflowFormValidator;
66
  private final WorkflowService workflowService;
67
  private final CWLService cwlService;
68
  private final GraphVizService graphVizService;
69

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

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

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

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

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

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

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

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

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

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

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

266
  /**
267
   * Download the Research Object Bundle for a particular workflow
268
   *
269
   * @param branch The branch of repository
270
   */
271
  @GetMapping(
272
      value = "/robundle/*/*/*.git/{branch}/**",
273
      produces = "application/vnd.wf4ever.robundle+zip")
274
  @ResponseBody
275
  public Resource getROBundleGeneric(
276
      @PathVariable("branch") String branch,
277
      HttpServletRequest request,
278
      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("domain") String domain,
304
      @PathVariable("owner") String owner,
305
      @PathVariable("repoName") String repoName,
306
      @PathVariable("branch") 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("branch") String branch,
327
      HttpServletRequest request,
328
      HttpServletResponse response)
329
      throws IOException {
330
    String path =
1✔
331
        (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
1✔
332
    GitDetails gitDetails = getGitDetails(11, path, branch);
1✔
333
    response.setHeader("Content-Disposition", "inline; filename=\"graph.svg\"");
1✔
334
    return workflowService.getWorkflowGraph("svg", gitDetails);
1✔
335
  }
336

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

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

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

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

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

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

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

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

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

540
  /**
541
   * Constructs a GitDetails object for a generic path
542
   *
543
   * @param startIndex The start of the repository URL
544
   * @param path The entire URL path
545
   * @param branch The branch of the repository
546
   * @return A constructed GitDetails object
547
   */
548
  public static GitDetails getGitDetails(int startIndex, String path, String branch) {
549
    // The repository URL is the part after startIndex and up to and including .git
550
    String repoUrl = path.substring(startIndex);
1✔
551
    int extensionIndex = repoUrl.indexOf(".git");
1✔
552
    if (extensionIndex == -1) {
1✔
553
      throw new WorkflowNotFoundException();
×
554
    }
555
    repoUrl = "https://" + repoUrl.substring(0, extensionIndex + 4);
1✔
556

557
    // The path is after the branch
558
    int slashAfterBranch = path.indexOf("/", path.indexOf(branch));
1✔
559
    if (slashAfterBranch == -1 || slashAfterBranch == path.length()) {
1✔
560
      throw new WorkflowNotFoundException();
×
561
    }
562
    path = path.substring(slashAfterBranch + 1);
1✔
563

564
    // Construct GitDetails object for this workflow
565
    return new GitDetails(repoUrl, branch, path);
1✔
566
  }
567

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

657
    // Display this model along with the view
658
    if (queued != null) {
1✔
659
      // Retry creation if there has been an error in cwltool parsing
660
      if (queued.getCwltoolStatus() == CWLToolStatus.ERROR) {
1✔
661
        workflowService.retryCwltool(queued);
×
662
      }
663
      return new ModelAndView("loading", "queued", queued);
1✔
664
    } else {
665
      return new ModelAndView("workflow", "workflow", workflowModel)
1✔
666
          .addObject("lineSeparator", System.getProperty("line.separator"))
1✔
667
          .addObject("formats", WebConfig.Format.values());
1✔
668
    }
669
  }
670

671
  private Resource getGraphFromInputStream(InputStream in, String format)
672
      throws IOException, WorkflowNotFoundException, CWLValidationException {
673
    Workflow workflow =
1✔
674
        cwlService.parseWorkflowNative(in, null, "workflow"); // first workflow will do
1✔
675
    InputStream out = graphVizService.getGraphStream(workflow.getVisualisationDot(), format);
1✔
676
    return new InputStreamResource(out);
1✔
677
  }
678
}
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