Loading vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java +6 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ package it.inaf.ia2.vospace.ui; import it.inaf.ia2.aa.AuthConfig; import it.inaf.ia2.aa.LoginFilter; import it.inaf.ia2.aa.ServiceLocator; import it.inaf.ia2.aa.ServletRapClient; import it.inaf.ia2.aa.UserManager; import it.inaf.ia2.gms.client.GmsClient; import it.inaf.ia2.rap.client.ClientCredentialsRapClient; Loading Loading @@ -86,4 +87,9 @@ public class VOSpaceUiApplication { String gmsBaseUri = ServiceLocator.getInstance().getConfig().getGmsUri(); return new GmsClient(gmsBaseUri); } @Bean public ServletRapClient servletRapClient() { return (ServletRapClient) ServiceLocator.getInstance().getRapClient(); } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java +30 −3 Original line number Diff line number Diff line Loading @@ -175,9 +175,20 @@ public class VOSpaceClient { return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class)); } public List<Job> getJobs() { public List<Job> getAsyncRecallJobs() { return getJobs("direction=pullToVoSpace", Job.JobType.ASYNC_RECALL); } public List<Job> getArchiveJobs() { return getJobs("direction=pullFromVoSpace" + "&VIEW=ivo://ia2.inaf.it/vospace/views%23tar" + "&VIEW=ivo://ia2.inaf.it/vospace/views%23zip", Job.JobType.ARCHIVE); } private List<Job> getJobs(String queryString, Job.JobType type) { HttpRequest request = getRequest("/transfers?direction=pullToVoSpace") HttpRequest request = getRequest("/transfers?" + queryString) .header("Accept", useJson ? "application/json" : "text/xml") .header("Content-Type", useJson ? "application/json" : "text/xml") .GET() Loading @@ -185,11 +196,27 @@ public class VOSpaceClient { return call(request, BodyHandlers.ofInputStream(), 200, res -> { return unmarshal(res, Jobs.class).getJobref().stream() .map(jobDesc -> new Job(jobDesc)) .map(jobDesc -> new Job(jobDesc, type)) .collect(Collectors.toList()); }); } public String getArchiveJobHref(String jobId) { List<Protocol> protocols = getTransferDetails(jobId).getProtocols(); if (!protocols.isEmpty()) { return protocols.get(0).getEndpoint(); } return null; } private Transfer getTransferDetails(String jobId) { HttpRequest request = getRequest("/transfers/" + jobId + "/results/transferDetails") .GET().build(); return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class)); } public ExecutionPhase getJobPhase(String jobId) { HttpRequest request = getRequest("/transfers/" + jobId + "/phase") Loading vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/DownloadController.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * This file is part of vospace-ui * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.ia2.vospace.ui.controller; import it.inaf.ia2.aa.ServletRapClient; import it.inaf.ia2.aa.data.User; import it.inaf.ia2.rap.client.call.TokenExchangeRequest; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.exception.PermissionDeniedException; import it.inaf.oats.vospace.datamodel.NodeUtils; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Protocol; import net.ivoa.xml.vospace.v2.Transfer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class DownloadController { private static final Logger LOG = LoggerFactory.getLogger(DownloadController.class); @Value("${vospace-authority}") private String authority; @Autowired private VOSpaceClient client; @Autowired private HttpServletRequest servletRequest; @Autowired private ServletRapClient rapClient; @GetMapping(value = "/download/**") public ResponseEntity<?> directDownload() { String path = getPath("/download/"); LOG.debug("directDownload called for path {}", path); Transfer transfer = new Transfer(); transfer.setDirection("pullFromVoSpace"); transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path))); Protocol protocol = new Protocol(); protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); transfer.getProtocols().add(protocol); String url = client.getFileServiceEndpoint(transfer); HttpHeaders headers = new HttpHeaders(); headers.set("Location", url); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } @GetMapping(value = "/download") public ResponseEntity<?> downloadJobResult(@RequestParam("jobId") String jobId) { LOG.debug("job result download called for jobId {}", jobId); String token = ((User) servletRequest.getUserPrincipal()).getAccessToken(); if (token == null) { throw new PermissionDeniedException("Token is null"); } String endpoint = client.getArchiveJobHref(jobId); TokenExchangeRequest exchangeRequest = new TokenExchangeRequest() .setSubjectToken(token) .setResource(endpoint); String newToken = rapClient.exchangeToken(exchangeRequest, servletRequest); String url = endpoint + "?token=" + newToken; HttpHeaders headers = new HttpHeaders(); headers.set("Location", url); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } protected String getPath(String prefix) { String requestURL = servletRequest.getRequestURL().toString(); return NodeUtils.getPathFromRequestURLString(requestURL, prefix); } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java +22 −5 Original line number Diff line number Diff line Loading @@ -8,7 +8,9 @@ package it.inaf.ia2.vospace.ui.controller; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.data.Job; import it.inaf.ia2.vospace.ui.exception.BadRequestException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import net.ivoa.xml.uws.v1.ErrorType; import net.ivoa.xml.uws.v1.ExecutionPhase; Loading Loading @@ -59,7 +61,7 @@ public class JobController extends BaseController { if (job.getPhase() == ExecutionPhase.QUEUED || job.getPhase() == ExecutionPhase.PENDING || job.getPhase() == ExecutionPhase.EXECUTING) { return ResponseEntity.ok(new Job(job)); return ResponseEntity.ok(new Job(job, Job.JobType.ASYNC_RECALL)); } String errorMessage; if (job.getPhase() == ExecutionPhase.ERROR) { Loading Loading @@ -87,7 +89,22 @@ public class JobController extends BaseController { } @GetMapping(value = "/jobs", produces = MediaType.APPLICATION_JSON_VALUE) public List<Job> getJobs() { return client.getJobs(); public List<Job> getJobs() throws Exception { CompletableFuture<List<Job>> asyncRecallJobsCall = CompletableFuture.supplyAsync(() -> client.getAsyncRecallJobs(), Runnable::run); CompletableFuture<List<Job>> archiveJobsCall = CompletableFuture.supplyAsync(() -> client.getArchiveJobs(), Runnable::run); CompletableFuture.allOf(asyncRecallJobsCall, archiveJobsCall).join(); List<Job> jobs = new ArrayList<>(); jobs.addAll(asyncRecallJobsCall.get()); jobs.addAll(archiveJobsCall.get()); return jobs; } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java +67 −25 Original line number Diff line number Diff line Loading @@ -25,15 +25,15 @@ import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.JobSummary; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Param; import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.Protocol; import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.View; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; Loading Loading @@ -94,26 +94,6 @@ public class NodesController extends BaseController { return ResponseEntity.ok(listNodeData); } @GetMapping(value = "/download/**") public ResponseEntity<?> directDownload() { String path = getPath("/download/"); LOG.debug("directDownload called for path {}", path); Transfer transfer = new Transfer(); transfer.setDirection("pullFromVoSpace"); transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path))); Protocol protocol = new Protocol(); protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); transfer.getProtocols().add(protocol); String url = client.getFileServiceEndpoint(transfer); HttpHeaders headers = new HttpHeaders(); headers.set("Location", url); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } @PostMapping(value = "/folder") public void newFolder(@RequestBody Map<String, Object> params) { Loading Loading @@ -162,6 +142,68 @@ public class NodesController extends BaseController { return ResponseEntity.noContent().build(); } @PostMapping(value = "/zip", consumes = MediaType.APPLICATION_JSON_VALUE) public Job createZip(@RequestBody List<String> paths) { return createArchive(paths, "ivo://ia2.inaf.it/vospace/views#zip"); } @PostMapping(value = "/tar", consumes = MediaType.APPLICATION_JSON_VALUE) public Job createTar(@RequestBody List<String> paths) { return createArchive(paths, "ivo://ia2.inaf.it/vospace/views#tar"); } private Job createArchive(List<String> paths, String viewUri) { Transfer transfer = new Transfer(); View view = new View(); view.setUri(viewUri); transfer.setView(view); if (paths.size() == 1) { transfer.setTarget(Arrays.asList("vos://" + authority + paths.get(0))); } else { String parent = getCommonParent(paths); transfer.setTarget(Arrays.asList("vos://" + authority + parent)); for (String path : paths) { String childName = path.substring(parent.length() + 1); Param param = new Param(); param.setUri(viewUri + "/include"); param.setValue(childName); view.getParam().add(param); } } transfer.setDirection("pullFromVoSpace"); Protocol protocol = new Protocol(); protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); transfer.getProtocols().add(protocol); return new Job(client.startTransferJob(transfer), Job.JobType.ARCHIVE); } private String getCommonParent(List<String> vosPaths) { String commonParent = null; for (String vosPath : vosPaths) { if (commonParent == null) { commonParent = vosPath; } else { StringBuilder newCommonParent = new StringBuilder(); boolean same = true; int lastSlashPos = vosPath.lastIndexOf("/"); for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()) && i < lastSlashPos; i++) { if (commonParent.charAt(i) == vosPath.charAt(i)) { newCommonParent.append(commonParent.charAt(i)); } else { same = false; } } commonParent = newCommonParent.toString(); } } return commonParent; } @PostMapping(value = "/move") public ResponseEntity<Job> moveNode(@RequestBody Map<String, Object> params) { Loading Loading @@ -195,7 +237,7 @@ public class NodesController extends BaseController { job.setPhase(phase); return ResponseEntity.ok(new Job(job)); return ResponseEntity.ok(new Job(job, Job.JobType.MOVE)); } protected String getPath(String prefix) { Loading Loading
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java +6 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ package it.inaf.ia2.vospace.ui; import it.inaf.ia2.aa.AuthConfig; import it.inaf.ia2.aa.LoginFilter; import it.inaf.ia2.aa.ServiceLocator; import it.inaf.ia2.aa.ServletRapClient; import it.inaf.ia2.aa.UserManager; import it.inaf.ia2.gms.client.GmsClient; import it.inaf.ia2.rap.client.ClientCredentialsRapClient; Loading Loading @@ -86,4 +87,9 @@ public class VOSpaceUiApplication { String gmsBaseUri = ServiceLocator.getInstance().getConfig().getGmsUri(); return new GmsClient(gmsBaseUri); } @Bean public ServletRapClient servletRapClient() { return (ServletRapClient) ServiceLocator.getInstance().getRapClient(); } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java +30 −3 Original line number Diff line number Diff line Loading @@ -175,9 +175,20 @@ public class VOSpaceClient { return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class)); } public List<Job> getJobs() { public List<Job> getAsyncRecallJobs() { return getJobs("direction=pullToVoSpace", Job.JobType.ASYNC_RECALL); } public List<Job> getArchiveJobs() { return getJobs("direction=pullFromVoSpace" + "&VIEW=ivo://ia2.inaf.it/vospace/views%23tar" + "&VIEW=ivo://ia2.inaf.it/vospace/views%23zip", Job.JobType.ARCHIVE); } private List<Job> getJobs(String queryString, Job.JobType type) { HttpRequest request = getRequest("/transfers?direction=pullToVoSpace") HttpRequest request = getRequest("/transfers?" + queryString) .header("Accept", useJson ? "application/json" : "text/xml") .header("Content-Type", useJson ? "application/json" : "text/xml") .GET() Loading @@ -185,11 +196,27 @@ public class VOSpaceClient { return call(request, BodyHandlers.ofInputStream(), 200, res -> { return unmarshal(res, Jobs.class).getJobref().stream() .map(jobDesc -> new Job(jobDesc)) .map(jobDesc -> new Job(jobDesc, type)) .collect(Collectors.toList()); }); } public String getArchiveJobHref(String jobId) { List<Protocol> protocols = getTransferDetails(jobId).getProtocols(); if (!protocols.isEmpty()) { return protocols.get(0).getEndpoint(); } return null; } private Transfer getTransferDetails(String jobId) { HttpRequest request = getRequest("/transfers/" + jobId + "/results/transferDetails") .GET().build(); return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class)); } public ExecutionPhase getJobPhase(String jobId) { HttpRequest request = getRequest("/transfers/" + jobId + "/phase") Loading
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/DownloadController.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * This file is part of vospace-ui * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.ia2.vospace.ui.controller; import it.inaf.ia2.aa.ServletRapClient; import it.inaf.ia2.aa.data.User; import it.inaf.ia2.rap.client.call.TokenExchangeRequest; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.exception.PermissionDeniedException; import it.inaf.oats.vospace.datamodel.NodeUtils; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Protocol; import net.ivoa.xml.vospace.v2.Transfer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class DownloadController { private static final Logger LOG = LoggerFactory.getLogger(DownloadController.class); @Value("${vospace-authority}") private String authority; @Autowired private VOSpaceClient client; @Autowired private HttpServletRequest servletRequest; @Autowired private ServletRapClient rapClient; @GetMapping(value = "/download/**") public ResponseEntity<?> directDownload() { String path = getPath("/download/"); LOG.debug("directDownload called for path {}", path); Transfer transfer = new Transfer(); transfer.setDirection("pullFromVoSpace"); transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path))); Protocol protocol = new Protocol(); protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); transfer.getProtocols().add(protocol); String url = client.getFileServiceEndpoint(transfer); HttpHeaders headers = new HttpHeaders(); headers.set("Location", url); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } @GetMapping(value = "/download") public ResponseEntity<?> downloadJobResult(@RequestParam("jobId") String jobId) { LOG.debug("job result download called for jobId {}", jobId); String token = ((User) servletRequest.getUserPrincipal()).getAccessToken(); if (token == null) { throw new PermissionDeniedException("Token is null"); } String endpoint = client.getArchiveJobHref(jobId); TokenExchangeRequest exchangeRequest = new TokenExchangeRequest() .setSubjectToken(token) .setResource(endpoint); String newToken = rapClient.exchangeToken(exchangeRequest, servletRequest); String url = endpoint + "?token=" + newToken; HttpHeaders headers = new HttpHeaders(); headers.set("Location", url); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } protected String getPath(String prefix) { String requestURL = servletRequest.getRequestURL().toString(); return NodeUtils.getPathFromRequestURLString(requestURL, prefix); } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java +22 −5 Original line number Diff line number Diff line Loading @@ -8,7 +8,9 @@ package it.inaf.ia2.vospace.ui.controller; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.data.Job; import it.inaf.ia2.vospace.ui.exception.BadRequestException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import net.ivoa.xml.uws.v1.ErrorType; import net.ivoa.xml.uws.v1.ExecutionPhase; Loading Loading @@ -59,7 +61,7 @@ public class JobController extends BaseController { if (job.getPhase() == ExecutionPhase.QUEUED || job.getPhase() == ExecutionPhase.PENDING || job.getPhase() == ExecutionPhase.EXECUTING) { return ResponseEntity.ok(new Job(job)); return ResponseEntity.ok(new Job(job, Job.JobType.ASYNC_RECALL)); } String errorMessage; if (job.getPhase() == ExecutionPhase.ERROR) { Loading Loading @@ -87,7 +89,22 @@ public class JobController extends BaseController { } @GetMapping(value = "/jobs", produces = MediaType.APPLICATION_JSON_VALUE) public List<Job> getJobs() { return client.getJobs(); public List<Job> getJobs() throws Exception { CompletableFuture<List<Job>> asyncRecallJobsCall = CompletableFuture.supplyAsync(() -> client.getAsyncRecallJobs(), Runnable::run); CompletableFuture<List<Job>> archiveJobsCall = CompletableFuture.supplyAsync(() -> client.getArchiveJobs(), Runnable::run); CompletableFuture.allOf(asyncRecallJobsCall, archiveJobsCall).join(); List<Job> jobs = new ArrayList<>(); jobs.addAll(asyncRecallJobsCall.get()); jobs.addAll(archiveJobsCall.get()); return jobs; } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java +67 −25 Original line number Diff line number Diff line Loading @@ -25,15 +25,15 @@ import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.JobSummary; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Param; import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.Protocol; import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.View; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; Loading Loading @@ -94,26 +94,6 @@ public class NodesController extends BaseController { return ResponseEntity.ok(listNodeData); } @GetMapping(value = "/download/**") public ResponseEntity<?> directDownload() { String path = getPath("/download/"); LOG.debug("directDownload called for path {}", path); Transfer transfer = new Transfer(); transfer.setDirection("pullFromVoSpace"); transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path))); Protocol protocol = new Protocol(); protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); transfer.getProtocols().add(protocol); String url = client.getFileServiceEndpoint(transfer); HttpHeaders headers = new HttpHeaders(); headers.set("Location", url); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); } @PostMapping(value = "/folder") public void newFolder(@RequestBody Map<String, Object> params) { Loading Loading @@ -162,6 +142,68 @@ public class NodesController extends BaseController { return ResponseEntity.noContent().build(); } @PostMapping(value = "/zip", consumes = MediaType.APPLICATION_JSON_VALUE) public Job createZip(@RequestBody List<String> paths) { return createArchive(paths, "ivo://ia2.inaf.it/vospace/views#zip"); } @PostMapping(value = "/tar", consumes = MediaType.APPLICATION_JSON_VALUE) public Job createTar(@RequestBody List<String> paths) { return createArchive(paths, "ivo://ia2.inaf.it/vospace/views#tar"); } private Job createArchive(List<String> paths, String viewUri) { Transfer transfer = new Transfer(); View view = new View(); view.setUri(viewUri); transfer.setView(view); if (paths.size() == 1) { transfer.setTarget(Arrays.asList("vos://" + authority + paths.get(0))); } else { String parent = getCommonParent(paths); transfer.setTarget(Arrays.asList("vos://" + authority + parent)); for (String path : paths) { String childName = path.substring(parent.length() + 1); Param param = new Param(); param.setUri(viewUri + "/include"); param.setValue(childName); view.getParam().add(param); } } transfer.setDirection("pullFromVoSpace"); Protocol protocol = new Protocol(); protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); transfer.getProtocols().add(protocol); return new Job(client.startTransferJob(transfer), Job.JobType.ARCHIVE); } private String getCommonParent(List<String> vosPaths) { String commonParent = null; for (String vosPath : vosPaths) { if (commonParent == null) { commonParent = vosPath; } else { StringBuilder newCommonParent = new StringBuilder(); boolean same = true; int lastSlashPos = vosPath.lastIndexOf("/"); for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()) && i < lastSlashPos; i++) { if (commonParent.charAt(i) == vosPath.charAt(i)) { newCommonParent.append(commonParent.charAt(i)); } else { same = false; } } commonParent = newCommonParent.toString(); } } return commonParent; } @PostMapping(value = "/move") public ResponseEntity<Job> moveNode(@RequestBody Map<String, Object> params) { Loading Loading @@ -195,7 +237,7 @@ public class NodesController extends BaseController { job.setPhase(phase); return ResponseEntity.ok(new Job(job)); return ResponseEntity.ok(new Job(job, Job.JobType.MOVE)); } protected String getPath(String prefix) { Loading