diff --git a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java
index 7f5ed6c7bab1fe79619e07eaaf532c44ce23bd38..4e7a9fb3b8d0458db07bce63a4883fd8a573ec8e 100644
--- a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java
+++ b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java
@@ -6,7 +6,6 @@
 package it.inaf.ia2.transfer.controller;
 
 import it.inaf.ia2.transfer.auth.TokenPrincipal;
-import it.inaf.ia2.transfer.exception.PermissionDeniedException;
 import it.inaf.ia2.transfer.service.ArchiveJob;
 import it.inaf.ia2.transfer.service.ArchiveJob.Type;
 import it.inaf.ia2.transfer.service.ArchiveService;
diff --git a/src/main/java/it/inaf/ia2/transfer/controller/CopyController.java b/src/main/java/it/inaf/ia2/transfer/controller/CopyController.java
index 72041fb2085d56ff64244de9a033212d895fe0b9..96df91a43aa85cd11c3d79b189f713c6a2cdda7f 100644
--- a/src/main/java/it/inaf/ia2/transfer/controller/CopyController.java
+++ b/src/main/java/it/inaf/ia2/transfer/controller/CopyController.java
@@ -10,10 +10,14 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.beans.factory.annotation.Autowired;
+import it.inaf.ia2.transfer.service.CopyService;
 
 @RestController
 public class CopyController extends AuthenticatedFileController {
-   
+
+    @Autowired
+    private CopyService copyService;
 
     @PostMapping(value = "/copy", consumes = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<?> copyFiles(@RequestBody CopyRequest copyRequest) {
diff --git a/src/main/java/it/inaf/ia2/transfer/controller/CopyRequest.java b/src/main/java/it/inaf/ia2/transfer/controller/CopyRequest.java
index d725d46efa30dc0fe5174ecc0f94657f24aabb75..186e085175f96124caa1814cbd3732af8ea4e1a6 100644
--- a/src/main/java/it/inaf/ia2/transfer/controller/CopyRequest.java
+++ b/src/main/java/it/inaf/ia2/transfer/controller/CopyRequest.java
@@ -8,8 +8,8 @@ package it.inaf.ia2.transfer.controller;
 public class CopyRequest {
 
     String jobId;
-    String target;
-    String direction;
+    String sourceRootVosPath;
+    String destinationRootVosPath;
 
     public String getJobId() {
         return jobId;
@@ -19,20 +19,20 @@ public class CopyRequest {
         this.jobId = jobId;
     }
 
-    public String getTarget() {
-        return target;
+    public String getSourceRootVosPath() {
+        return sourceRootVosPath;
     }
 
-    public void setTarget(String target) {
-        this.target = target;
+    public void setSourceRootVosPath(String sourceRootVosPath) {
+        this.sourceRootVosPath = sourceRootVosPath;
     }
 
-    public String getDirection() {
-        return direction;
+    public String getDestinationRootVosPath() {
+        return destinationRootVosPath;
     }
 
-    public void setDirection(String direction) {
-        this.direction = direction;
+    public void setDestinationRootVosPath(String destinationRootVosPath) {
+        this.destinationRootVosPath = destinationRootVosPath;
     }
 
 }
diff --git a/src/main/java/it/inaf/ia2/transfer/service/CopyService.java b/src/main/java/it/inaf/ia2/transfer/service/CopyService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e31e655bc472cb92614fec0cc72cd216cfec88c
--- /dev/null
+++ b/src/main/java/it/inaf/ia2/transfer/service/CopyService.java
@@ -0,0 +1,153 @@
+/*
+ * This file is part of vospace-file-service
+ * Copyright (C) 2021 Istituto Nazionale di Astrofisica
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package it.inaf.ia2.transfer.service;
+
+import it.inaf.ia2.transfer.auth.TokenPrincipal;
+import it.inaf.ia2.transfer.exception.InsufficientStorageException;
+import it.inaf.ia2.transfer.exception.JobException;
+import it.inaf.ia2.transfer.exception.JobException.Type;
+import it.inaf.ia2.transfer.persistence.FileDAO;
+import it.inaf.ia2.transfer.persistence.JobDAO;
+import it.inaf.ia2.transfer.persistence.LocationDAO;
+import it.inaf.ia2.transfer.persistence.model.FileInfo;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import net.ivoa.xml.uws.v1.ExecutionPhase;
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarOutputStream;
+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.HttpMethod;
+import org.springframework.stereotype.Service;
+import org.springframework.util.FileSystemUtils;
+import org.springframework.util.unit.DataSize;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class CopyService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CopyService.class);
+
+    @Autowired
+    private FileDAO fileDAO;
+
+    @Autowired
+    private LocationDAO locationDAO;
+
+    @Autowired
+    private JobDAO jobDAO;
+
+    @Autowired
+    private AuthorizationService authorizationService;
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Value("${upload_location_id}")
+    private int uploadLocationId;
+
+    // Maximum size of the working directory for each registered user
+    @Value("${generated.dir.max-size}")
+    private DataSize generatedDirMaxSize;
+    
+    
+    
+    
+    public void copyFiles(String sourceRootVosPath, 
+            String destinationRootVosPath, String jobId) {
+        // We use jobId to identify nodes created by the REST part of CopyNode
+        // We expect them to be locked
+        
+                
+        
+        
+    }
+
+    
+
+    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;
+                for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()); i++) {
+                    if (commonParent.charAt(i) == vosPath.charAt(i)) {
+                        newCommonParent.append(commonParent.charAt(i));
+                    } else {
+                        same = false;
+                    }
+                }
+                commonParent = newCommonParent.toString();
+            }
+        }
+        return commonParent;
+    }
+
+    private <O extends OutputStream, E> void downloadFileIntoArchive(FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler, String baseUrl) {
+
+        if (baseUrl == null) {
+            LOG.error("Location URL not found for location " + fileInfo.getLocationId());
+            throw new JobException(Type.FATAL, "Internal Fault")
+                    .setErrorDetail("InternalFault: Unable to retrieve location of file " + fileInfo.getVirtualPath());
+        }
+
+        String url = baseUrl + "/" + fileInfo.getVirtualName();
+
+        LOG.trace("Downloading file from " + url);
+
+        restTemplate.execute(url, HttpMethod.GET, req -> {
+            HttpHeaders headers = req.getHeaders();
+            if (tokenPrincipal.getToken() != null) {
+                headers.setBearerAuth(tokenPrincipal.getToken());
+            }
+        }, res -> {
+            File tmpFile = Files.createTempFile("download", null).toFile();
+            try ( FileOutputStream os = new FileOutputStream(tmpFile)) {
+                res.getBody().transferTo(os);
+                handler.putNextEntry(tmpFile, relPath);
+                try ( FileInputStream is = new FileInputStream(tmpFile)) {
+                    is.transferTo(handler.getOutputStream());
+                }
+            } finally {
+                tmpFile.delete();
+            }
+            return null;
+        }, new Object[]{});
+    }
+
+    private <O extends OutputStream, E> void writeFileIntoArchive(FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler) throws IOException {
+        if (!authorizationService.isDownloadable(fileInfo, tokenPrincipal)) {
+            throw new JobException(Type.FATAL, "Permission Denied")
+                    .setErrorDetail("PermissionDenied: " + fileInfo.getVirtualPath());
+        }
+
+        File file = new File(fileInfo.getOsPath());
+        LOG.trace("Adding file " + file.getAbsolutePath() + " to tar archive");
+
+        try ( InputStream is = new FileInputStream(file)) {
+            handler.putNextEntry(file, relPath);
+            is.transferTo(handler.getOutputStream());
+        }
+    }
+}