diff --git a/pom.xml b/pom.xml
index efb9ef488c2fb4d2522e73bbf197b859acb28a89..9aa64c0411432c5f08aad33884f74a936995e88e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,10 +3,9 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
-        <groupId>org.springframework.boot</groupId>
-        <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.4.5</version>
-        <relativePath/> <!-- lookup parent from repository -->
+        <groupId>it.inaf.ia2</groupId>
+        <artifactId>vospace-parent</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
     </parent>
     <groupId>it.inaf.oats</groupId>
     <artifactId>vospace-rest</artifactId>
@@ -18,134 +17,19 @@
         <!-- File catalog repository directory -->
         <init_database_scripts_path>../../../vospace-file-catalog</init_database_scripts_path>
         <finalName>${project.artifactId}-${project.version}</finalName>
-        <zonky.postgres-binaries.version>12.5.0</zonky.postgres-binaries.version>
     </properties>
 
     <dependencies>
-        
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-jdbc</artifactId>
-        </dependency>
-      
-        <dependency>
-            <groupId>org.postgresql</groupId>
-            <artifactId>postgresql</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        
-        <!-- Jackson-JAXB compatibility -->
-        <dependency>
-            <groupId>com.fasterxml.jackson.module</groupId>
-            <artifactId>jackson-module-jaxb-annotations</artifactId>
-        </dependency>
-        
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-devtools</artifactId>
-            <scope>runtime</scope>
-            <optional>true</optional>
-        </dependency>
-        
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.junit.vintage</groupId>
-                    <artifactId>junit-vintage-engine</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        
-        <dependency>
-            <groupId>it.oats.inaf</groupId>
-            <artifactId>vospace-datamodel</artifactId>
-            <version>1.0-SNAPSHOT</version>
-            <exclusions>
-                <!-- Transitive dependency excluded to avoid duplicated dependency issues.
-                We want to use always the version provided by Spring Boot -->
-                <exclusion>
-                    <groupId>com.fasterxml.jackson.module</groupId>
-                    <artifactId>jackson-module-jaxb-annotations</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-              
         <dependency>
             <groupId>it.inaf.ia2</groupId>
-            <artifactId>auth-lib</artifactId>
-            <version>2.0.0-SNAPSHOT</version>
+            <artifactId>vospace-parent-classes</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
         </dependency>
-                
         <dependency>
             <groupId>redis.clients</groupId>
             <artifactId>jedis</artifactId>
         </dependency>
-        
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-inline</artifactId>
-        </dependency>
-        
-        <!-- Embedded PostgreSQL: -->
-        <dependency>
-            <groupId>com.opentable.components</groupId>
-            <artifactId>otj-pg-embedded</artifactId>
-            <version>0.13.3</version>
-            <scope>test</scope>
-        </dependency>
-
     </dependencies>
-    
-    <profiles>
-        <profile>
-            <id>platform-linux</id>
-            <activation>
-                <os>
-                    <family>unix</family> 
-                </os>
-            </activation>
-            <dependencies>
-                <dependency>
-                    <groupId>io.zonky.test.postgres</groupId>
-                    <artifactId>embedded-postgres-binaries-linux-amd64</artifactId>
-                    <version>${zonky.postgres-binaries.version}</version>
-                    <scope>test</scope>
-                </dependency>
-            </dependencies>
-        </profile>
-        <profile>
-            <id>platform-windows</id>
-            <activation>
-                <os>
-                    <family>windows</family>
-                </os>
-            </activation>
-            <dependencies>
-                <dependency>
-                    <groupId>io.zonky.test.postgres</groupId>
-                    <artifactId>embedded-postgres-binaries-windows-amd64</artifactId>
-                    <version>${zonky.postgres-binaries.version}</version>
-                    <scope>test</scope>
-                </dependency>
-            </dependencies>
-        </profile>
-    </profiles>
-    
-    <repositories>
-        <repository>
-            <id>ia2-snapshots</id>
-            <name>your custom repo</name>
-            <url>http://repo.ia2.inaf.it/maven/repository/snapshots</url>
-        </repository>
-    </repositories>
 
     <build>
         <finalName>${finalName}</finalName>
@@ -173,17 +57,9 @@
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
-            <plugin>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.22.2</version>
-                <configuration>
-                    <trimStackTrace>false</trimStackTrace>
-                </configuration>
-            </plugin>
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.6</version>
                 <executions>
                     <execution>
                         <goals>
@@ -202,4 +78,12 @@
         </plugins>
     </build>
 
+    <repositories>
+        <repository>
+            <id>ia2-snapshots</id>
+            <name>IA2 snapshot repository</name>
+            <url>http://repo.ia2.inaf.it/maven/repository/snapshots</url>
+        </repository>
+    </repositories>
+
 </project>
diff --git a/src/main/java/it/inaf/oats/vospace/CreateNodeService.java b/src/main/java/it/inaf/oats/vospace/CreateNodeService.java
index 206a85e68115c5fc109f1692010987430f7a40cd..516f110643e7fdcb2e108527a740a938d6e61b72 100644
--- a/src/main/java/it/inaf/oats/vospace/CreateNodeService.java
+++ b/src/main/java/it/inaf/oats/vospace/CreateNodeService.java
@@ -70,7 +70,7 @@ public class CreateNodeService {
         }
 
         if (!NodeUtils.checkIfWritable(parentNode, principal.getName(), principal.getGroups())) {
-            throw new PermissionDeniedException(path);
+            throw PermissionDeniedException.forPath(path);
         }
 
         // Check if node creator property is set. If not set it according to 
@@ -87,7 +87,7 @@ public class CreateNodeService {
         } else {
             if (!creator.equals(principal.getName())) // maybe a more specific exception would be more appropriate?
             {
-                throw new PermissionDeniedException(path);
+                throw PermissionDeniedException.forPath(path);
             }
         }
 
diff --git a/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java b/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java
index 45faca5611607002dfc18f7757d2b5abe1a8b64b..6ca9f25a2392c39a5a1261f27a40098c987d8105 100644
--- a/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java
@@ -55,7 +55,7 @@ public class DeleteNodeController extends BaseNodeController  {
         if (pathComponents.isEmpty()) { 
             
             // Manage root node
-            throw new PermissionDeniedException("root");
+            throw PermissionDeniedException.forPath("/");
             
         } else {
             
@@ -71,8 +71,8 @@ public class DeleteNodeController extends BaseNodeController  {
                     
         }
         
-        if(!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) {
-            throw new PermissionDeniedException(path);
+        if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) {
+            throw PermissionDeniedException.forPath(path);
         }
                 
         try {
diff --git a/src/main/java/it/inaf/oats/vospace/ErrorController.java b/src/main/java/it/inaf/oats/vospace/ErrorController.java
new file mode 100644
index 0000000000000000000000000000000000000000..138bd4a32688ff5c8545ac399e028e15383d4590
--- /dev/null
+++ b/src/main/java/it/inaf/oats/vospace/ErrorController.java
@@ -0,0 +1,20 @@
+/*
+ * This file is part of vospace-rest
+ * Copyright (C) 2021 Istituto Nazionale di Astrofisica
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package it.inaf.oats.vospace;
+
+import it.inaf.oats.vospace.exception.DefaultErrorController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ErrorController extends DefaultErrorController {
+
+    @Autowired
+    public ErrorController(ErrorAttributes errorAttributes) {
+        super(errorAttributes);
+    }
+}
diff --git a/src/main/java/it/inaf/oats/vospace/FileServiceClient.java b/src/main/java/it/inaf/oats/vospace/FileServiceClient.java
index 55fe5798847809f491c49e845384c33a57e11588..ed3bf416d82480def75d3663a7bd2ffbbb1e7af3 100644
--- a/src/main/java/it/inaf/oats/vospace/FileServiceClient.java
+++ b/src/main/java/it/inaf/oats/vospace/FileServiceClient.java
@@ -7,6 +7,8 @@ package it.inaf.oats.vospace;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import it.inaf.ia2.aa.data.User;
+import it.inaf.oats.vospace.datamodel.Views;
+import it.inaf.oats.vospace.exception.InvalidArgumentException;
 import java.io.OutputStream;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -39,8 +41,33 @@ public class FileServiceClient {
 
     public String startArchiveJob(Transfer transfer, String jobId) {
 
-        List<String> vosPaths = transfer.getTarget().stream()
-                .map(p -> p.substring("vos://".length() + authority.length())).collect(Collectors.toList());
+        if (transfer.getTarget().size() != 1) {
+            throw new IllegalArgumentException("Target size is " + transfer.getTarget().size());
+        }
+
+        String target = transfer.getTarget().get(0)
+                .substring("vos://".length() + authority.length());
+
+        String viewUri = transfer.getView().getUri();
+
+        // Generate list of paths using view include parameters
+        List<String> vosPaths = transfer.getView().getParam().stream()
+                .map(p -> {
+                    if (p.getUri().equals(viewUri + "/include")) {
+                        if (p.getValue().contains("../")) {
+                            throw new InvalidArgumentException("Relative paths are not supported");
+                        }
+                        return target + "/" + p.getValue();
+                    } else {
+                        throw new InvalidArgumentException("Unsupported view parameter: " + p.getUri());
+                    }
+                })
+                .collect(Collectors.toList());
+
+        if (vosPaths.isEmpty()) {
+            // Add target path
+            vosPaths.add(target);
+        }
 
         ArchiveRequest archiveRequest = new ArchiveRequest();
         archiveRequest.setJobId(jobId);
@@ -65,7 +92,7 @@ public class FileServiceClient {
         }, new Object[]{});
     }
 
-    private static class ArchiveRequest {
+    public static class ArchiveRequest {
 
         private String type;
         private String jobId;
@@ -98,9 +125,9 @@ public class FileServiceClient {
 
     private static String archiveTypeFromViewUri(String viewUri) {
         switch (viewUri) {
-            case "ivo://ia2.inaf.it/vospace/views#tar":
+            case Views.TAR_VIEW_URI:
                 return "TAR";
-            case "ivo://ia2.inaf.it/vospace/views#zip":
+            case Views.ZIP_VIEW_URI:
                 return "ZIP";
             default:
                 throw new IllegalArgumentException("Archive type not defined for " + viewUri);
diff --git a/src/main/java/it/inaf/oats/vospace/JobService.java b/src/main/java/it/inaf/oats/vospace/JobService.java
index e74b84fafe775fef27778a839d63a4e034041772..15abb442b8181f9da0e44cc1a7f27915a69a8758 100644
--- a/src/main/java/it/inaf/oats/vospace/JobService.java
+++ b/src/main/java/it/inaf/oats/vospace/JobService.java
@@ -18,8 +18,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import it.inaf.oats.vospace.exception.VoSpaceErrorSummarizableException;
 import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
+import java.util.function.Function;
 import javax.servlet.http.HttpServletRequest;
+import net.ivoa.xml.uws.v1.ResultReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,9 +41,6 @@ public class JobService {
     @Autowired
     private CopyService copyService;
     
-    @Autowired
-    private NodeBranchService nodeBranchService;
-
     @Autowired
     private AsyncTransferService asyncTransfService;
 
@@ -105,16 +103,19 @@ public class JobService {
                 phase = ExecutionPhase.EXECUTING;
             }
             job.setPhase(phase);
+            
+            jobDAO.updateJob(job, null);
 
-            jobDAO.updateJob(job);
+            Transfer negotiatedTransfer = null;
 
             switch (getJobDirection(transfer)) {
                 case pullToVoSpace:
-                    handlePullToVoSpace(job, transfer);
+                    negotiatedTransfer = handlePullToVoSpace(job, transfer);
                     break;
                 case pullFromVoSpace:
                 case pushToVoSpace:
-                    handleVoSpaceUrlsListResult(job, transfer);
+                    negotiatedTransfer = uriService.getNegotiatedTransfer(job, transfer);
+                    setJobResults(job, transfer);
                     break;
                 case moveNode:
                     handleMoveNode(job, transfer);
@@ -130,16 +131,18 @@ public class JobService {
             // the previous job are asynchronous. Each job has to set its
             // completion independently. Only jobs started from the /synctrans
             // endpoints are completed immediately (see createSyncJobResult() method)
+            
+            return negotiatedTransfer;
         });
     }
 
-    private void handlePullToVoSpace(JobSummary job, Transfer transfer) {
+    private Transfer handlePullToVoSpace(JobSummary job, Transfer transfer) {
 
         for (Protocol protocol : transfer.getProtocols()) {
             switch (protocol.getUri()) {
                 case "ia2:async-recall":
                     asyncTransfService.startJob(job);
-                    return;
+                    return transfer;
                 case "ivo://ivoa.net/vospace/core#httpget":
                     if (transfer.getTarget().size() != 1) {
                         throw new InvalidArgumentException("Invalid target size for pullToVoSpace: " + transfer.getTarget().size());
@@ -147,19 +150,18 @@ public class JobService {
                     String nodeUri = transfer.getTarget().get(0);
                     String contentUri = protocol.getEndpoint();
                     uriService.setNodeRemoteLocation(nodeUri, contentUri);
-                    uriService.setTransferJobResult(job, transfer);
+                    Transfer negotiatedTransfer = uriService.getNegotiatedTransfer(job, transfer);
+                    setJobResults(job, transfer);
                     // Special case: import of a node from a portal file server
                     // doesn't imply file transfer, so it can be set to completed
                     job.setPhase(ExecutionPhase.COMPLETED);
-                    return;
+                    return negotiatedTransfer;
                 default:
-                    throw new InternalFaultException("Unsupported pullToVoSpace protocol: " + protocol.getUri());
+                    throw new InvalidArgumentException("Unsupported pullToVoSpace protocol: " + protocol.getUri());
             }
         }
-    }
 
-    private void handleVoSpaceUrlsListResult(JobSummary job, Transfer transfer) {
-        uriService.setTransferJobResult(job, transfer);
+        throw new InvalidArgumentException("Transfer contains no protocols");
     }
 
     private void handleMoveNode(JobSummary jobSummary, Transfer transfer) {
@@ -170,6 +172,7 @@ public class JobService {
             handleJobErrors(jobSummary, job -> {
                 moveService.processMoveJob(transfer, user);
                 job.setPhase(ExecutionPhase.COMPLETED);
+                return null;
             });
         });
     }
@@ -185,13 +188,16 @@ public class JobService {
                 
                 // the file service part will unlock nodes and set job phase
                 // to completed
+                
+                return null;
             });
         });
     }
 
-    private void handleJobErrors(JobSummary job, Consumer<JobSummary> jobConsumer) {
+    private void handleJobErrors(JobSummary job, Function<JobSummary, Transfer> jobConsumer) {
+        Transfer negotiatedTransfer = null;        
         try {
-            jobConsumer.accept(job);
+            negotiatedTransfer = jobConsumer.apply(job);
         } catch (VoSpaceErrorSummarizableException e) {
             job.setPhase(ExecutionPhase.ERROR);
             job.setErrorSummary(ErrorSummaryFactory.newErrorSummary(e));
@@ -200,7 +206,7 @@ public class JobService {
             job.setErrorSummary(ErrorSummaryFactory.newErrorSummary(
                     new InternalFaultException(e)));
         } finally {
-            jobDAO.updateJob(job);
+            jobDAO.updateJob(job, negotiatedTransfer);
         }
     }
 
@@ -217,21 +223,51 @@ public class JobService {
      *
      */
     public void createSyncJobResult(JobSummary job) {
+        Transfer negotiatedTransfer = null;
         try {
-            uriService.setSyncTransferEndpoints(job);
+            Transfer transfer = uriService.getTransfer(job);
+            negotiatedTransfer = uriService.getNegotiatedTransfer(job, transfer);
+            setJobResults(job, transfer);
             job.setPhase(ExecutionPhase.COMPLETED);
             // Need to catch other exceptions too to avoid inconsistent job status
         } catch (VoSpaceErrorSummarizableException e) {
             job.setPhase(ExecutionPhase.ERROR);
-            uriService.getTransfer(job).getProtocols().clear();
+            stripProtocols(job, negotiatedTransfer);
             job.setErrorSummary(ErrorSummaryFactory.newErrorSummary(e));
         } catch (Exception e) {
             job.setPhase(ExecutionPhase.ERROR);
-            uriService.getTransfer(job).getProtocols().clear();
+            stripProtocols(job, negotiatedTransfer);
             job.setErrorSummary(ErrorSummaryFactory.newErrorSummary(
                     new InternalFaultException(e)));
         } finally {
-            jobDAO.createJob(job);
+            jobDAO.createJob(job, negotiatedTransfer);
+        }
+    }
+    
+    private void stripProtocols(JobSummary job, Transfer negotiatedTransfer) {
+        uriService.getTransfer(job).getProtocols().clear();
+        if (negotiatedTransfer != null) {
+            negotiatedTransfer.getProtocols().clear();
+        }
+    }
+
+    private void setJobResults(JobSummary jobSummary, Transfer transfer) {
+        String baseUrl = servletRequest.getRequestURL().substring(0,
+                servletRequest.getRequestURL().indexOf(servletRequest.getContextPath()));
+        String href = baseUrl + servletRequest.getContextPath()
+                + "/transfers/" + jobSummary.getJobId() + "/results/transferDetails";
+        ResultReference transferDetailsRef = new ResultReference();
+        transferDetailsRef.setId("transferDetails");
+        transferDetailsRef.setHref(href);
+        jobSummary.getResults().add(transferDetailsRef);
+        switch (getJobDirection(transfer)) {
+            case pullFromVoSpace:
+            case pushToVoSpace:
+                ResultReference dataNodeRef = new ResultReference();
+                dataNodeRef.setId("dataNode");
+                dataNodeRef.setHref(transfer.getTarget().get(0));
+                jobSummary.getResults().add(dataNodeRef);
+                break;
         }
     }
 }
diff --git a/src/main/java/it/inaf/oats/vospace/ListNodeController.java b/src/main/java/it/inaf/oats/vospace/ListNodeController.java
index bcc4b50a2407a435c47ed1d6f98dac34fb5ac5d5..1da35f704589be2336e568229bc25c7aca5e1dbc 100644
--- a/src/main/java/it/inaf/oats/vospace/ListNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/ListNodeController.java
@@ -47,7 +47,7 @@ public class ListNodeController extends BaseNodeController {
         } else {
             if (!NodeUtils.checkIfReadable(
                     optNode.get(), principal.getName(), principal.getGroups())) {
-                throw new PermissionDeniedException(path);
+                throw PermissionDeniedException.forPath(path);
             }
         }
         
diff --git a/src/main/java/it/inaf/oats/vospace/MoveService.java b/src/main/java/it/inaf/oats/vospace/MoveService.java
index 5ff1f24f0f153c950709792819c1918d7d9911b1..f83bcdc5fac7359111d7eb368f9937c0bcf7280d 100644
--- a/src/main/java/it/inaf/oats/vospace/MoveService.java
+++ b/src/main/java/it/inaf/oats/vospace/MoveService.java
@@ -7,6 +7,7 @@ package it.inaf.oats.vospace;
 
 import it.inaf.ia2.aa.data.User;
 import it.inaf.oats.vospace.datamodel.NodeUtils;
+import it.inaf.oats.vospace.exception.InternalFaultException;
 import it.inaf.oats.vospace.exception.InvalidArgumentException;
 import it.inaf.oats.vospace.exception.NodeBusyException;
 import it.inaf.oats.vospace.exception.NodeNotFoundException;
@@ -66,7 +67,7 @@ public class MoveService extends AbstractNodeService {
             }
 
             if (!nodeDao.isBranchWritable(sourceId, user.getName(), user.getGroups())) {
-                throw new PermissionDeniedException(sourcePath);
+                throw PermissionDeniedException.forPath(sourcePath);
             }           
 
             Optional<ShortNodeDescriptor> destShortNodeDescriptor 
@@ -75,8 +76,13 @@ public class MoveService extends AbstractNodeService {
             String  destinationNodeLtreePath = null;
             if (destShortNodeDescriptor.isPresent()) {
                 // When the destination is an existing ContainerNode, the source SHALL be placed under it (i.e., within the container)
-                ShortNodeDescriptor snd = destShortNodeDescriptor.get();                
-                this.validateDestinationContainer(snd, destinationPath);                
+                ShortNodeDescriptor snd = destShortNodeDescriptor.get();
+                
+                if(snd.isBusy()) throw new NodeBusyException(destinationPath);
+                if(snd.isPermissionDenied()) throw PermissionDeniedException.forPath(destinationPath);                                                
+                if(!snd.isWritable()) throw new InternalFaultException("Destination is not writable: "+ destinationPath);
+                if(!snd.isContainer()) throw new InternalFaultException("Existing destination is not a container: " + destinationPath);
+                
                 destinationNodeLtreePath = snd.getDestinationNodeLtreePath();
                 
             } else {
diff --git a/src/main/java/it/inaf/oats/vospace/SetNodeController.java b/src/main/java/it/inaf/oats/vospace/SetNodeController.java
index 16de28d6b009e88aa5d57f22852154395e6fff45..91b6d7fa4ef332fe22fbd1534b7faee4d9e79ab1 100644
--- a/src/main/java/it/inaf/oats/vospace/SetNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/SetNodeController.java
@@ -50,7 +50,7 @@ public class SetNodeController extends BaseNodeController {
         // The service SHALL throw a HTTP 403 status code including a PermissionDenied fault 
         // in the entity-body if the user does not have permissions to perform the operation
         if (!NodeUtils.checkIfWritable(toBeModifiedNode, principal.getName(), principal.getGroups())) {
-            throw new PermissionDeniedException(path);
+            throw PermissionDeniedException.forPath(path);
         }
 
         // The service SHALL throw a HTTP 403 status code including a PermissionDenied fault 
@@ -60,7 +60,7 @@ public class SetNodeController extends BaseNodeController {
         String newNodeType = node.getType();
         if (!storedNodeType.equals(newNodeType)) {
             LOG.debug("setNode trying to modify type. Stored ", storedNodeType + ", requested " + newNodeType);
-            throw new PermissionDeniedException(path);
+            throw PermissionDeniedException.forPath(path);
         }
 
         // This method cannot be used to modify the accepts or provides list of Views for the Node.
diff --git a/src/main/java/it/inaf/oats/vospace/TransferController.java b/src/main/java/it/inaf/oats/vospace/TransferController.java
index eda5ff0f1ea8fe9af09ac8685242c6baee924bd5..c7c2d512381c907e0f0e85c1909f271c6ca36b62 100644
--- a/src/main/java/it/inaf/oats/vospace/TransferController.java
+++ b/src/main/java/it/inaf/oats/vospace/TransferController.java
@@ -53,7 +53,7 @@ public class TransferController {
 
         JobSummary jobSummary = newJobSummary(transfer, principal);
 
-        jobDAO.createJob(jobSummary);
+        jobDAO.createJob(jobSummary, null);
 
         if (phase.isPresent()) {
             jobService.setJobPhase(jobSummary, phase.get());
@@ -157,8 +157,7 @@ public class TransferController {
                 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
             }
             
-            // TODO: check type
-            return ResponseEntity.ok((Transfer) (job.getJobInfo().getAny().get(0)));
+            return ResponseEntity.ok(jobDAO.getTransferDetails(jobId));
 
         }).orElse(ResponseEntity.notFound().build());
     }
@@ -174,6 +173,7 @@ public class TransferController {
             @RequestParam(value = "PHASE", required = false) Optional<List<ExecutionPhase>> phase,
             @RequestParam(value = "AFTER", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Optional<LocalDateTime> after,
             @RequestParam(value = "LAST", required = false) Optional<Integer> last,
+            @RequestParam(value = "VIEW", required = false) Optional<List<String>> views,
             @RequestParam(value = "direction", required = false) Optional<List<JobService.JobDirection>> direction,
             User principal) {
 
@@ -185,21 +185,11 @@ public class TransferController {
 
         String userId = principal.getName();
 
-        List<ExecutionPhase> phaseList;
-        if (phase.isPresent()) {
-            phaseList = phase.get();
-        } else {
-            phaseList = List.of();
-        }
-
-        List<JobService.JobDirection> directionList;
-        if (direction.isPresent()) {
-            directionList = direction.get();
-        } else {
-            directionList = List.of();
-        }
+        List<ExecutionPhase> phaseList = phase.orElse(List.of());
+        List<JobService.JobDirection> directionList = direction.orElse(List.of());
+        List<String> viewsList = views.orElse(List.of());
 
-        Jobs jobs = jobDAO.getJobs(userId, phaseList, directionList, after, last);
+        Jobs jobs = jobDAO.getJobs(userId, phaseList, directionList, viewsList, after, last);
 
         return ResponseEntity.ok(jobs);
     }
diff --git a/src/main/java/it/inaf/oats/vospace/UriService.java b/src/main/java/it/inaf/oats/vospace/UriService.java
index aa8db7b479e895e9fff4c0a87d2b22facf433940..06ed2b41cbf29ad73aeaa2c9484958f94356b749 100644
--- a/src/main/java/it/inaf/oats/vospace/UriService.java
+++ b/src/main/java/it/inaf/oats/vospace/UriService.java
@@ -31,7 +31,6 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.JobSummary;
-import net.ivoa.xml.uws.v1.ResultReference;
 import net.ivoa.xml.vospace.v2.DataNode;
 import net.ivoa.xml.vospace.v2.Node;
 import net.ivoa.xml.vospace.v2.Protocol;
@@ -67,25 +66,18 @@ public class UriService {
     @Autowired
     private FileServiceClient fileServiceClient;
 
-    public void setTransferJobResult(JobSummary job, Transfer transfer) {
-
-        List<ResultReference> results = new ArrayList<>();
-
-        ResultReference result = new ResultReference();
-        result.setHref(getEndpoint(job, transfer));
-        results.add(result);
-
-        job.setResults(results);
-        // Moved phase setting to caller method for ERROR management
-    }
-
     /**
-     * Sets the endpoint value for all valid protocols (protocol negotiation).
+     * For a given job, returns a new transfer object containing only valid
+     * protocols (protocol negotiation) and sets proper endpoints on them.
      */
-    public void setSyncTransferEndpoints(JobSummary job) {
-
-        Transfer transfer = getTransfer(job);
-
+    public Transfer getNegotiatedTransfer(JobSummary job, Transfer transfer) {
+
+        // Original transfer object shouldn't be modified, so a new transfer object is created
+        Transfer negotiatedTransfer = new Transfer();
+        negotiatedTransfer.setTarget(transfer.getTarget());
+        negotiatedTransfer.setDirection(transfer.getDirection());
+        // according to examples found in specification view is not copied
+        
         if (transfer.getProtocols().isEmpty()) {
             // At least one protocol is expected from client
             throw new InvalidArgumentException("Transfer contains no protocols");
@@ -97,6 +89,7 @@ public class UriService {
         List<String> validProtocolUris = new ArrayList<>();
         switch (jobDirection) {
             case pullFromVoSpace:
+            case pullToVoSpace:
                 validProtocolUris.add("ivo://ivoa.net/vospace/core#httpget");
                 break;
             case pushToVoSpace:
@@ -109,20 +102,24 @@ public class UriService {
 
         List<Protocol> validProtocols
                 = transfer.getProtocols().stream()
+                        // discard invalid protocols
                         .filter(protocol -> validProtocolUris.contains(protocol.getUri()))
-                        .collect(Collectors.toList());
+                        .map(p -> {
+                            // set endpoints
+                            Protocol protocol = new Protocol();
+                            protocol.setUri(p.getUri());
+                            protocol.setEndpoint(getEndpoint(job, transfer));
+                            return protocol;
+                        }).collect(Collectors.toList());
 
         if (validProtocols.isEmpty()) {
             Protocol protocol = transfer.getProtocols().get(0);
             throw new ProtocolNotSupportedException(protocol.getUri());
         }
 
-        String endpoint = getEndpoint(job, transfer);
-        validProtocols.stream().forEach(p -> p.setEndpoint(endpoint));
-
-        // Returns modified transfer containing only valid protocols
-        transfer.getProtocols().clear();
-        transfer.getProtocols().addAll(validProtocols);
+        negotiatedTransfer.getProtocols().addAll(validProtocols);
+        
+        return negotiatedTransfer;
     }
 
     private Node getEndpointNode(String relativePath,
@@ -168,13 +165,13 @@ public class UriService {
             case pushToVoSpace:
             case pullToVoSpace:
                 if (!NodeUtils.checkIfWritable(node, creator, groups)) {
-                    throw new PermissionDeniedException(relativePath);
+                    throw PermissionDeniedException.forPath(relativePath);
                 }
                 break;
 
             case pullFromVoSpace:
                 if (!NodeUtils.checkIfReadable(node, creator, groups)) {
-                    throw new PermissionDeniedException(relativePath);
+                    throw PermissionDeniedException.forPath(relativePath);
                 }
                 break;
 
diff --git a/src/main/java/it/inaf/oats/vospace/exception/ContainerNotFoundException.java b/src/main/java/it/inaf/oats/vospace/exception/ContainerNotFoundException.java
deleted file mode 100644
index 95fdd32dd6ab812e2122ac2d009cd616569afc98..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/ContainerNotFoundException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.NOT_FOUND)
-public class ContainerNotFoundException extends VoSpaceErrorSummarizableException {
-
-    public ContainerNotFoundException(String path) {
-        super("Path: " + path, 
-                VOSpaceFaultEnum.NODE_NOT_FOUND);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/DuplicateNodeException.java b/src/main/java/it/inaf/oats/vospace/exception/DuplicateNodeException.java
deleted file mode 100644
index 95c0c241583bace74160127b2fdafa14beea9444..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/DuplicateNodeException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.CONFLICT)
-public class DuplicateNodeException extends VoSpaceErrorSummarizableException {
-
-    public DuplicateNodeException(String path) {
-        super("Path: " + path,
-                VOSpaceFaultEnum.DUPLICATE_NODE);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/ErrorController.java b/src/main/java/it/inaf/oats/vospace/exception/ErrorController.java
deleted file mode 100644
index 17d2091445134e663d2312a23a4a9f785e4e8932..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/ErrorController.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
-import org.springframework.boot.web.error.ErrorAttributeOptions;
-import org.springframework.boot.web.servlet.error.ErrorAttributes;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("${server.error.path:${error.path:/error}}")
-public class ErrorController extends AbstractErrorController {
-
-    @Autowired
-    public ErrorController(ErrorAttributes errorAttributes) {
-        super(errorAttributes);
-    }
-
-    @RequestMapping(produces = MediaType.TEXT_XML_VALUE)
-    public void errorText(HttpServletRequest request, HttpServletResponse response) throws Exception {
-        ErrorAttributeOptions options = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE);
-        Map<String, Object> errors = super.getErrorAttributes(request, options);
-        response.setContentType("text/plain;charset=UTF-8");
-        response.setCharacterEncoding("UTF-8");
-        String errorMessage = (String) errors.get("message");
-        if (errorMessage != null) {
-            response.getOutputStream().write(errorMessage.getBytes(StandardCharsets.UTF_8));
-        }
-    }
-
-    @Override
-    public String getErrorPath() {
-        return null;
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/ErrorSummaryFactory.java b/src/main/java/it/inaf/oats/vospace/exception/ErrorSummaryFactory.java
deleted file mode 100644
index 7db96116ffa38fe5075e9243396d5a075d7c77c3..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/ErrorSummaryFactory.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import net.ivoa.xml.uws.v1.ErrorSummary;
-
-public class ErrorSummaryFactory {    
-
-    public static ErrorSummary newErrorSummary(VOSpaceFaultEnum error, String detailMessage) {
-        ErrorSummary result = new ErrorSummary();
-        result.setMessage(error.getFaultRepresentation());
-        result.setType(error.getType());
-
-        if (detailMessage == null || detailMessage.isBlank()) {
-            result.setHasDetail(false);
-        } else {
-            result.setHasDetail(true);
-            result.setDetailMessage(error.getFaultCaptionForDetails()
-                    + " "
-                    + detailMessage);
-        }
-
-        return result;
-    }
-
-    public static ErrorSummary newErrorSummary(VOSpaceFaultEnum error) {
-        return newErrorSummary(error, null);
-    }
-    
-    public static ErrorSummary newErrorSummary(VoSpaceErrorSummarizableException e)
-    {
-        return newErrorSummary(e.getFault(), e.getDetailMessage());
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java b/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java
deleted file mode 100644
index 1e78a4fb1e90363e68f636995b81c1dbcc2b0c74..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)   // Status code 500
-public class InternalFaultException extends VoSpaceErrorSummarizableException {
-
-    private static final Logger LOG = LoggerFactory.getLogger(InternalFaultException.class);
-
-    public InternalFaultException(String msg) {
-        super("Description: " + msg,
-                VOSpaceFaultEnum.INTERNAL_FAULT);
-    }
-
-    public InternalFaultException(Throwable cause) {
-        super("Description: " + getMessage(cause),
-                VOSpaceFaultEnum.INTERNAL_FAULT);
-    }
-
-    private static String getMessage(Throwable cause) {
-        LOG.error("Exception caught", cause);
-        return cause.getMessage() != null ? cause.getMessage() : cause.getClass().getCanonicalName();
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/InvalidArgumentException.java b/src/main/java/it/inaf/oats/vospace/exception/InvalidArgumentException.java
deleted file mode 100644
index d3bf46ec98b40acd2f552f89143dc1c811a04302..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/InvalidArgumentException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.BAD_REQUEST)
-public class InvalidArgumentException extends VoSpaceErrorSummarizableException {
-
-    public InvalidArgumentException(String message) {
-        super("Description: " + message, VOSpaceFaultEnum.NODE_NOT_FOUND);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/InvalidURIException.java b/src/main/java/it/inaf/oats/vospace/exception/InvalidURIException.java
deleted file mode 100644
index 3c9248e6db690c17c1dd4be9cfaf8e49c5152200..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/InvalidURIException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.BAD_REQUEST)
-public class InvalidURIException extends VoSpaceErrorSummarizableException {
-
-    public InvalidURIException(String URI, String path) {
-        super("Payload node URI: " + URI
-                + " is not consistent with request path: " + path,
-                VOSpaceFaultEnum.INVALID_URI);
-    }
-
-    public InvalidURIException(String URI) {
-        super("URI: " + URI + " is not in a valid format",
-                VOSpaceFaultEnum.INVALID_URI);
-    }
-
-    public InvalidURIException(IllegalArgumentException ex) {
-        super("Description: " + ex.getMessage(),
-                VOSpaceFaultEnum.INVALID_URI);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/LinkFoundException.java b/src/main/java/it/inaf/oats/vospace/exception/LinkFoundException.java
deleted file mode 100644
index 55d3f99cb0cf7b5dce9d8fd834c60f2d5d57c751..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/LinkFoundException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.BAD_REQUEST)
-public class LinkFoundException extends VoSpaceErrorSummarizableException {
-
-    public LinkFoundException(String path) {
-        super("Link Node found at path: " + path, 
-                VOSpaceFaultEnum.INVALID_URI);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/NodeBusyException.java b/src/main/java/it/inaf/oats/vospace/exception/NodeBusyException.java
deleted file mode 100644
index 7e0d8fc3d58e538de3f56e5253f03ebf00fdf277..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/NodeBusyException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-public class NodeBusyException extends VoSpaceErrorSummarizableException {
-
-    public NodeBusyException(String path) {
-        super("Path: " + path,
-                 VOSpaceFaultEnum.NODE_BUSY);
-    }
-
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/NodeNotFoundException.java b/src/main/java/it/inaf/oats/vospace/exception/NodeNotFoundException.java
deleted file mode 100644
index 9024dc2fe9b4cc0cf5d2a69715d9add1802b9ba5..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/NodeNotFoundException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.NOT_FOUND)
-public class NodeNotFoundException extends VoSpaceErrorSummarizableException {
-
-    public NodeNotFoundException(String path) {
-        super("Path: " + path,
-                VOSpaceFaultEnum.NODE_NOT_FOUND);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/PermissionDeniedException.java b/src/main/java/it/inaf/oats/vospace/exception/PermissionDeniedException.java
deleted file mode 100644
index 446e410a900c53f210c514836b37942104b0df1e..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/PermissionDeniedException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.FORBIDDEN)
-public class PermissionDeniedException extends VoSpaceErrorSummarizableException {
-
-    public PermissionDeniedException(String path) {
-        super("Path: " + path,
-                VOSpaceFaultEnum.PERMISSION_DENIED);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/ProtocolNotSupportedException.java b/src/main/java/it/inaf/oats/vospace/exception/ProtocolNotSupportedException.java
deleted file mode 100644
index 6a767964c4bd9c5465d591f9663c1e0a450800fc..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/ProtocolNotSupportedException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-public class ProtocolNotSupportedException extends VoSpaceErrorSummarizableException{
-    
-    public ProtocolNotSupportedException(String protocol) {
-        super("Protocol: " + protocol, 
-                VOSpaceFaultEnum.PROTOCOL_NOT_SUPPORTED);
-    }
-    
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/VOSpaceFaultEnum.java b/src/main/java/it/inaf/oats/vospace/exception/VOSpaceFaultEnum.java
deleted file mode 100644
index 82bf0b5e27dd14d4b41555878cadc605dfd0800b..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/VOSpaceFaultEnum.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-// NFC: ErrorType usage is not covered in documentation, as far as I can see
-// these are tentative default values.
-import net.ivoa.xml.uws.v1.ErrorType;
-
-public enum VOSpaceFaultEnum {
-    // pushto
-    OPERATION_NOT_SUPPORTED("Operation Not Supported", ErrorType.FATAL, "OperationNotSupported"),
-    INTERNAL_FAULT("Internal Fault", ErrorType.TRANSIENT, "InternalFault"),
-    PERMISSION_DENIED("Permission Denied", ErrorType.FATAL, "PermissionDenied"),
-    VIEW_NOT_SUPPORTED("View Not Supported", ErrorType.FATAL, "ViewNotSupported"),
-    PROTOCOL_NOT_SUPPORTED("Protocol Not Supported", ErrorType.FATAL, "ProtocolNotSupported"),
-    INVALID_ARGUMENT("Invalid Argument", ErrorType.FATAL, "InvalidArgument"),
-    NODE_BUSY("Node Busy", ErrorType.TRANSIENT, "NodeBusy"),
-    // additional for pullto
-    INVALID_URI("Invalid URI", ErrorType.FATAL, "InvalidURI"),
-    INVALID_DATA("Invalid Data", ErrorType.FATAL, "InvalidData"),
-    // additional for pullfrom
-    NODE_NOT_FOUND("Node Not Found", ErrorType.FATAL, "NodeNotFound"),
-    // additional for pushfrom
-    TRANSFER_FAILED("Transfer Failed", ErrorType.FATAL, "TransferFailed"),
-    // additional for movenode/copynode
-    DUPLICATE_NODE("Duplicate Node", ErrorType.FATAL, "DuplicateNode");
-
-    private final String faultRepresentation;
-    private final ErrorType type;
-    private final String faultCaptionForDetails;
-
-    private VOSpaceFaultEnum(String faultRepresentation,
-            ErrorType type,
-            String faultCaptionForDetails) {
-        this.faultRepresentation = faultRepresentation;
-        this.type = type;
-        this.faultCaptionForDetails = faultCaptionForDetails;
-    }
-
-    public String getFaultRepresentation() {
-        return this.faultRepresentation;
-    }
-
-    public ErrorType getType() {
-        return this.type;
-    }
-
-    public String getFaultCaptionForDetails() {
-        return faultCaptionForDetails;
-    }
-
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/VoSpaceErrorSummarizableException.java b/src/main/java/it/inaf/oats/vospace/exception/VoSpaceErrorSummarizableException.java
deleted file mode 100644
index ebd8b2ab4a4b195ec34170c320ff3913cc63f6dc..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/VoSpaceErrorSummarizableException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
-public abstract class VoSpaceErrorSummarizableException extends VoSpaceException {
-    
-    VOSpaceFaultEnum fault;
-    private String detailMessage;
-    
-    public VoSpaceErrorSummarizableException(String detailMessage, VOSpaceFaultEnum fault)
-    {
-        super(fault.getFaultCaptionForDetails() + " " + detailMessage);
-        this.detailMessage = detailMessage;
-        this.fault = fault;        
-    }
-        
-    public VOSpaceFaultEnum getFault()
-    {
-        return this.fault;
-    }
-        
-    public String getDetailMessage()
-    {
-        return this.detailMessage;
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/exception/VoSpaceException.java b/src/main/java/it/inaf/oats/vospace/exception/VoSpaceException.java
deleted file mode 100644
index 4d3c983630f051f8a5b358df4399261e36b8f82a..0000000000000000000000000000000000000000
--- a/src/main/java/it/inaf/oats/vospace/exception/VoSpaceException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace.exception;
-
-public class VoSpaceException extends RuntimeException {
-
-    public VoSpaceException(String message) {
-        super(message);
-    }
-}
diff --git a/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java
index 03a9e05968ccfe371a310562b5ae1bbceaa39aed..e2b52369f65d270900f8ac0eed9642ed226f9516 100644
--- a/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java
+++ b/src/main/java/it/inaf/oats/vospace/persistence/JobDAO.java
@@ -31,6 +31,7 @@ import org.springframework.stereotype.Repository;
 import java.util.ArrayList;
 import java.time.LocalDateTime;
 import java.math.BigDecimal;
+import java.util.Collections;
 import net.ivoa.xml.uws.v1.ErrorType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,12 +50,12 @@ public class JobDAO {
         jdbcTemplate = new JdbcTemplate(dataSource);
     }
 
-    public void createJob(JobSummary jobSummary) {
+    public void createJob(JobSummary jobSummary, Transfer transferDetails) {
 
-        String sql = 
-                "INSERT INTO job(job_id, owner_id, job_type, phase, job_info,"
-                + " error_message, error_type, error_has_detail, error_detail) "
-                + "VALUES (?, ?, ?, ?, ?, ? ,? ,? ,?)";
+        String sql
+                = "INSERT INTO job(job_id, owner_id, job_type, phase, job_info, transfer_details, "
+                + " results, error_message, error_type, error_has_detail, error_detail) "
+                + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
 
         jdbcTemplate.update(sql, ps -> {
             int i = 0;
@@ -63,9 +64,11 @@ public class JobDAO {
             ps.setObject(++i, getJobDirection(jobSummary), Types.VARCHAR);
             ps.setObject(++i, jobSummary.getPhase().value(), Types.OTHER);
             ps.setObject(++i, toJson(jobSummary.getJobInfo()), Types.OTHER);
-            
+            ps.setObject(++i, toJson(transferDetails), Types.OTHER);
+            ps.setObject(++i, toJson(jobSummary.getResults()), Types.OTHER);
+
             ErrorSummary errorSummary = jobSummary.getErrorSummary();
-            if(errorSummary != null) {
+            if (errorSummary != null) {
                 ps.setString(++i, errorSummary.getMessage());
                 ps.setObject(++i, errorSummary.getType().value(), Types.OTHER);
                 ps.setBoolean(++i, errorSummary.isHasDetail());
@@ -125,7 +128,10 @@ public class JobDAO {
         jobSummary.setPhase(ExecutionPhase.fromValue(rs.getString("phase")));
         jobSummary.setJobInfo(getJobPayload(rs.getString("job_info")));
         jobSummary.setResults(getResults(rs.getString("results")));
-        
+        jobSummary.setCreationTime(toXMLGregorianCalendar(rs.getTimestamp("creation_time")));
+        jobSummary.setStartTime(toXMLGregorianCalendar(rs.getTimestamp("start_time")));
+        jobSummary.setEndTime(toXMLGregorianCalendar(rs.getTimestamp("end_time")));
+
         // Retrieve error information if any
         String errorType = rs.getString("error_type");
         if (errorType != null) {
@@ -137,13 +143,14 @@ public class JobDAO {
 
             jobSummary.setErrorSummary(errorSummary);
         }
-        
+
         return jobSummary;
     }
 
     public Jobs getJobs(String userId,
             List<ExecutionPhase> phaseList,
             List<JobService.JobDirection> directionList,
+            List<String> viewList,
             Optional<LocalDateTime> after,
             Optional<Integer> last
     ) {
@@ -193,6 +200,18 @@ public class JobDAO {
             }
             sb.append(")");
         }
+        
+        // Fill conditions on views list
+        if (!viewList.isEmpty()) {
+            sb.append(" AND (")
+                    .append(String.join(" OR ",
+                            Collections.nCopies(viewList.size(), "job_info->'transfer'->'view'->>'uri' = ?")))
+                    .append(")");
+            for (String view : viewList) {
+                queryParams.add(view);
+                queryParamTypes.add(Types.VARCHAR);
+            }
+        }
 
         // Fill conditions on creation date
         if (after.isPresent()) {
@@ -260,41 +279,58 @@ public class JobDAO {
         }
     }
 
-    public void updateJob(JobSummary job) {
+    public void updateJob(JobSummary job, Transfer transferDetails) {
+
+        String sql = "UPDATE job SET (phase, results, transfer_details ";
 
-        String sql = "UPDATE job SET (phase, results";
-        
         ErrorSummary errorSummary = job.getErrorSummary();
-        if(errorSummary != null)
-        {
+        if (errorSummary != null) {
             sql += ", error_message, error_type, error_has_detail, error_detail";
-        }                
-       
-        sql += ") = (?, ?";
-        
-        if(errorSummary != null)
-        {
+        }
+
+        sql += ") = (?, ?, ?";
+
+        if (errorSummary != null) {
             sql += ", ?, ?, ?, ?";
         }
-        
+
         sql += ") WHERE job_id = ?";
 
         jdbcTemplate.update(sql, ps -> {
             int i = 0;
             ps.setObject(++i, job.getPhase().name(), Types.OTHER);
             ps.setObject(++i, toJson(job.getResults()), Types.OTHER);
-            if(errorSummary != null)
-            {
+            ps.setObject(++i, toJson(transferDetails), Types.OTHER);
+            if (errorSummary != null) {
                 ps.setString(++i, errorSummary.getMessage());
-                ps.setObject(++i, errorSummary.getType().value(), Types.OTHER);                
+                ps.setObject(++i, errorSummary.getType().value(), Types.OTHER);
                 ps.setBoolean(++i, errorSummary.isHasDetail());
                 ps.setString(++i, errorSummary.getDetailMessage());
             }
             ps.setString(++i, job.getJobId());
         });
     }
+    
+    public Transfer getTransferDetails(String jobId) {
+
+        String sql = "SELECT transfer_details FROM job WHERE job_id = ?";
+
+        String json = jdbcTemplate.queryForObject(sql, String.class, new Object[]{jobId});
+        if (json == null) {
+            return null;
+        }
+
+        try {
+            return MAPPER.readValue(json, Transfer.class);
+        } catch (JsonProcessingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
 
     private String toJson(Object data) {
+        if (data == null) {
+            return null;
+        }
         try {
             return MAPPER.writeValueAsString(data);
         } catch (JsonProcessingException ex) {
@@ -303,24 +339,28 @@ public class JobDAO {
     }
 
     public static XMLGregorianCalendar toXMLGregorianCalendar(Timestamp t) {
-        XMLGregorianCalendar cal = null;
-        try {
-            cal = DatatypeFactory.newInstance().newXMLGregorianCalendar();
-
-            LocalDateTime ldt = t.toLocalDateTime();
-
-            cal.setYear(ldt.getYear());
-            cal.setMonth(ldt.getMonthValue());
-            cal.setDay(ldt.getDayOfMonth());
-            cal.setHour(ldt.getHour());
-            cal.setMinute(ldt.getMinute());
-            cal.setSecond(ldt.getSecond());
-            cal.setFractionalSecond(new BigDecimal("0." + ldt.getNano()));
-
-        } catch (Exception e) {
-            LOG.error("Error while generating XMLGregorianCalendar", e);
+        if (t != null) {
+            try {
+                XMLGregorianCalendar cal = DatatypeFactory.newInstance().newXMLGregorianCalendar();
+
+                LocalDateTime ldt = t.toLocalDateTime();
+
+                cal.setYear(ldt.getYear());
+                cal.setMonth(ldt.getMonthValue());
+                cal.setDay(ldt.getDayOfMonth());
+                cal.setHour(ldt.getHour());
+                cal.setMinute(ldt.getMinute());
+                cal.setSecond(ldt.getSecond());
+                cal.setFractionalSecond(new BigDecimal("0." + ldt.getNano()));
+
+                // return calendar only if it has been fully initialized (otherwise
+                // toString issue could appear); return null in other cases.
+                return cal;
+            } catch (Exception e) {
+                LOG.error("Error while generating XMLGregorianCalendar", e);
+            }
         }
 
-        return cal;
+        return null;
     }
 }
diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
index 802e5c8c8daf3bf5a4f676b4bebf06921a1a200c..b245083a981f3bd8b6ee9dffd05e587af22957a0 100644
--- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
+++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
@@ -10,7 +10,6 @@ import it.inaf.oats.vospace.URIUtils;
 import it.inaf.oats.vospace.datamodel.NodeProperties;
 import it.inaf.oats.vospace.datamodel.NodeUtils;
 import it.inaf.oats.vospace.exception.InternalFaultException;
-import java.net.URISyntaxException;
 import java.sql.Array;
 import net.ivoa.xml.vospace.v2.Node;
 import java.sql.PreparedStatement;
@@ -82,10 +81,10 @@ public class NodeDAO {
             } else {
                 ps.setString(++i, jobId);
             }
-            ps.setString(++i, NodeProperties.getStandardNodePropertyByName(myNode, "creator"));
-            ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getStandardNodePropertyByName(myNode, "groupread")));
-            ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getStandardNodePropertyByName(myNode, "groupwrite")));
-            ps.setBoolean(++i, Boolean.valueOf(NodeProperties.getStandardNodePropertyByName(myNode, "publicread")));
+            ps.setString(++i, NodeProperties.getNodePropertyByURI(myNode, NodeProperties.CREATOR_URI));
+            ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getNodePropertyByURI(myNode, NodeProperties.GROUP_READ_URI)));
+            ps.setArray(++i, fromPropertyToArray(ps, NodeProperties.getNodePropertyByURI(myNode, NodeProperties.GROUP_WRITE_URI)));
+            ps.setBoolean(++i, Boolean.valueOf(NodeProperties.getNodePropertyByURI(myNode, NodeProperties.PUBLIC_READ_URI)));
             ps.setObject(++i, paths.get(0).getPath(), Types.OTHER);
             ps.setObject(++i, paths.get(0).getRelativePath(), Types.OTHER);
             ps.setObject(++i, NodeUtils.getDbNodeType(myNode), Types.OTHER);
@@ -99,7 +98,7 @@ public class NodeDAO {
 
         String sql = "SELECT (CASE WHEN c.path = n.path THEN ? ELSE (? || ? || c.name) END) AS vos_path, c.node_id, c.name,\n"
                 + "c.type, c.async_trans, c.sticky, c.job_id IS NOT NULL AS busy_state, c.creator_id, c.group_read, c.group_write,\n"
-                + "c.is_public, c.content_length, c.created_on, c.last_modified, c.accept_views, c.provide_views, c.quota\n"
+                + "c.is_public, c.content_length, c.created_on, c.last_modified, c.accept_views, c.provide_views, c.quota, c.content_md5\n"
                 + "FROM node n\n"
                 + "JOIN node c ON c.path ~ (n.path::varchar || ? || '*{1}')::lquery OR c.path = n.path\n"
                 + "WHERE n.node_id = id_from_vos_path(?)\n"
@@ -232,6 +231,9 @@ public class NodeDAO {
         addProperty(NodeProperties.QUOTA_URI, String.valueOf(rs.getString("quota")),
                 properties);
 
+        addProperty(NodeProperties.MD5_URI, String.valueOf(rs.getString("content_md5")),
+                properties);
+
         addProperty("urn:async_trans", String.valueOf(rs.getBoolean("async_trans")),
                 properties);
 
@@ -242,26 +244,9 @@ public class NodeDAO {
     }
 
     public Optional<Long> getNodeId(String nodeVosPath) {
-        String sql = "SELECT node_id FROM node_vos_path WHERE vos_path = ?";
-
-        List<Long> nodeIdList = jdbcTemplate.query(conn -> {
-            PreparedStatement ps = conn.prepareStatement(sql);
-            ps.setString(1, nodeVosPath);
-            return ps;
-        }, (row, index) -> {
-            return row.getLong("node_id");
-        });
-
-        switch (nodeIdList.size()) {
-            case 0:
-                return Optional.empty();
-
-            case 1:
-                return Optional.of(nodeIdList.get(0));
-
-            default:
-                throw new InternalFaultException("More than 1 node id at path: " + nodeVosPath);
-        }
+        String sql = "SELECT id_from_vos_path(?) AS node_id";
+        Long nodeId = jdbcTemplate.queryForObject(sql, Long.class, nodeVosPath);
+        return Optional.ofNullable(nodeId);
     }
 
     public Optional<ShortNodeDescriptor> getShortNodeDescriptor(String nodeVosPath,
@@ -275,9 +260,8 @@ public class NodeDAO {
                 + "n.type = 'container' AS is_container,\n"
                 + "n.job_id IS NOT NULL AS busy_state\n"
                 + "FROM node n \n"
-                + "JOIN node_vos_path p ON n.node_id = p.node_id \n"
                 + "LEFT JOIN location loc ON loc.location_id = n.location_id\n"
-                + "WHERE vos_path = ?\n";
+                + "WHERE n.node_id = id_from_vos_path(?)\n";
 
         Optional<ShortNodeDescriptor> sndOpt = jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
diff --git a/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java b/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java
index 3bd1aa8fda4be077c4bf22fe963ae2cdc67a336a..98045c08155443004610d97e827be81244055487 100644
--- a/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java
+++ b/src/test/java/it/inaf/oats/vospace/CopyServiceTest.java
@@ -101,18 +101,6 @@ public class CopyServiceTest {
 
     @Test
     @Order(5)
-    public void testDontMoveIfStickySource() {
-        User user = mock(User.class);
-        when(user.getName()).thenReturn("user3");
-
-        assertThrows(PermissionDeniedException.class, () -> {
-            copyService.processCopyNodes(getTransfer("/test3/mstick", "/test4"), "job_pippo", user);
-        }
-        );
-    }
-
-    @Test
-    @Order(6)
     public void testPermissionDeniedOnExistingDestination() {
         User user = mock(User.class);
         when(user.getName()).thenReturn("user1");
@@ -125,7 +113,7 @@ public class CopyServiceTest {
     }
 
     @Test
-    @Order(7)
+    @Order(6)
     public void testDestinationExistsAndIsBusy() {
         User user = mock(User.class);
         when(user.getName()).thenReturn("user3");
@@ -137,7 +125,7 @@ public class CopyServiceTest {
     }
 
     @Test
-    @Order(9)
+    @Order(7)
     public void testCopyToExistingDestination() {
         User user = mock(User.class);
         when(user.getName()).thenReturn("user3");
@@ -172,7 +160,7 @@ public class CopyServiceTest {
     }
 
     @Test
-    @Order(10)
+    @Order(8)
     public void testCopyToExistingParent() {
         User user = mock(User.class);
         when(user.getName()).thenReturn("user3");
@@ -204,7 +192,7 @@ public class CopyServiceTest {
     }
 
     @Test
-    @Order(11)
+    @Order(9)
     public void testCopyDeniedToExistingDestination() {
 
         User user = mock(User.class);
diff --git a/src/test/java/it/inaf/oats/vospace/FileServiceClientTest.java b/src/test/java/it/inaf/oats/vospace/FileServiceClientTest.java
index aded67f208d1b9fe9e48ef23c1190e6416e9a2e0..c5c10183d1c2ca8ae6c90a4b0ca6199cf65da559 100644
--- a/src/test/java/it/inaf/oats/vospace/FileServiceClientTest.java
+++ b/src/test/java/it/inaf/oats/vospace/FileServiceClientTest.java
@@ -5,15 +5,22 @@
  */
 package it.inaf.oats.vospace;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import it.inaf.ia2.aa.data.User;
+import it.inaf.oats.vospace.FileServiceClient.ArchiveRequest;
 import it.inaf.oats.vospace.datamodel.Views;
+import it.inaf.oats.vospace.exception.InvalidArgumentException;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.net.URI;
 import java.util.Arrays;
 import javax.servlet.http.HttpServletRequest;
+import net.ivoa.xml.vospace.v2.Param;
 import net.ivoa.xml.vospace.v2.Transfer;
 import net.ivoa.xml.vospace.v2.View;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.fail;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -41,6 +48,8 @@ import org.springframework.web.client.RestTemplate;
 @MockitoSettings(strictness = Strictness.LENIENT)
 public class FileServiceClientTest {
 
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
     @Mock
     private RestTemplate restTemplate;
 
@@ -75,24 +84,97 @@ public class FileServiceClientTest {
         }
     }
 
+    @Test
+    public void testArchiveNoInclude() {
+
+        Transfer transfer = new Transfer();
+        transfer.setDirection("pullFromVoSpace");
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/mydir"));
+        View view = new View();
+        view.setUri(Views.ZIP_VIEW_URI);
+        transfer.setView(view);
+
+        ArchiveRequest archiveRequest = testStartArchiveJob(transfer);
+
+        assertEquals(1, archiveRequest.getPaths().size());
+        assertEquals("/mydir", archiveRequest.getPaths().get(0));
+    }
+
+    @Test
+    public void testInvalidViewParam() {
+
+        Transfer transfer = new Transfer();
+        transfer.setDirection("pullFromVoSpace");
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/parent_dir"));
+        View view = new View();
+        view.setUri(Views.TAR_VIEW_URI);
+        transfer.setView(view);
+
+        Param param1 = new Param();
+        param1.setUri("invalid");
+        param1.setValue("file1");
+        view.getParam().add(param1);
+
+        assertThrows(InvalidArgumentException.class, () -> testStartArchiveJob(transfer));
+    }
+
+    @Test
+    public void testInvalidViewParamPath() {
+
+        Transfer transfer = new Transfer();
+        transfer.setDirection("pullFromVoSpace");
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/parent_dir"));
+        View view = new View();
+        view.setUri(Views.TAR_VIEW_URI);
+        transfer.setView(view);
+
+        Param param1 = new Param();
+        param1.setUri(Views.TAR_VIEW_URI + "/include");
+        param1.setValue("../file1");
+        view.getParam().add(param1);
+
+        assertThrows(InvalidArgumentException.class, () -> testStartArchiveJob(transfer));
+    }
+
     private void testStartArchiveJob(String viewUri) {
 
         Transfer transfer = new Transfer();
         transfer.setDirection("pullFromVoSpace");
-        transfer.setTarget(Arrays.asList("vos://example.com!vospace/file1", "vos://example.com!vospace/file2"));
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/parent_dir"));
         View view = new View();
         view.setUri(viewUri);
         transfer.setView(view);
 
+        Param param1 = new Param();
+        param1.setUri(viewUri + "/include");
+        param1.setValue("file1");
+        view.getParam().add(param1);
+
+        Param param2 = new Param();
+        param2.setUri(viewUri + "/include");
+        param2.setValue("file2");
+        view.getParam().add(param2);
+
+        ArchiveRequest archiveRequest = testStartArchiveJob(transfer);
+
+        assertEquals(2, archiveRequest.getPaths().size());
+        assertEquals("/parent_dir/file1", archiveRequest.getPaths().get(0));
+        assertEquals("/parent_dir/file2", archiveRequest.getPaths().get(1));
+    }
+
+    private ArchiveRequest testStartArchiveJob(Transfer transfer) {
+
         User user = mock(User.class);
         when(user.getAccessToken()).thenReturn("<token>");
         when(request.getUserPrincipal()).thenReturn(user);
 
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
         doAnswer(invocation -> {
             RequestCallback requestCallback = invocation.getArgument(2);
             ClientHttpRequest mockedRequest = mock(ClientHttpRequest.class);
             HttpHeaders mockedRequestHeaders = mock(HttpHeaders.class);
-            when(mockedRequest.getBody()).thenReturn(new ByteArrayOutputStream());
+            when(mockedRequest.getBody()).thenReturn(baos);
             when(mockedRequest.getHeaders()).thenReturn(mockedRequestHeaders);
             requestCallback.doWithRequest(mockedRequest);
 
@@ -109,5 +191,11 @@ public class FileServiceClientTest {
         String redirect = fileServiceClient.startArchiveJob(transfer, "job123");
 
         assertEquals("http://file-service/archive/result", redirect);
+
+        try {
+            return MAPPER.readValue(baos.toByteArray(), ArchiveRequest.class);
+        } catch (IOException ex) {
+            throw new UncheckedIOException(ex);
+        }
     }
 }
diff --git a/src/test/java/it/inaf/oats/vospace/JobServiceTest.java b/src/test/java/it/inaf/oats/vospace/JobServiceTest.java
index 1d19b72cdb38c84383debc716371f6acf2319124..f276397fc5a30473e0a04465863632d7e7c9d6ff 100644
--- a/src/test/java/it/inaf/oats/vospace/JobServiceTest.java
+++ b/src/test/java/it/inaf/oats/vospace/JobServiceTest.java
@@ -8,6 +8,7 @@ package it.inaf.oats.vospace;
 import it.inaf.oats.vospace.exception.NodeBusyException;
 import it.inaf.oats.vospace.persistence.JobDAO;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.ExecutionPhase;
@@ -15,10 +16,14 @@ import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.vospace.v2.Protocol;
 import net.ivoa.xml.vospace.v2.Transfer;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import static org.mockito.Mockito.doAnswer;
@@ -46,21 +51,27 @@ public class JobServiceTest {
 
     @Mock
     private HttpServletRequest servletRequest;
-    
+
     @Mock
     private MoveService moveService;
 
     @InjectMocks
     private JobService jobService;
 
+    @BeforeEach
+    public void setUp() {
+        when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost/vospace/transfer"));
+        when(servletRequest.getContextPath()).thenReturn("/vospace");
+    }
+
     @Test
     public void testStartJobDefault() {
-        when(uriService.getTransfer(any())).thenReturn(getHttpTransfer());
+        when(uriService.getTransfer(any())).thenReturn(getPullFromVoSpaceHttpTransfer());
 
         JobSummary job = new JobSummary();
         jobService.setJobPhase(job, "RUN");
 
-        verify(jobDAO, times(2)).updateJob(job);
+        verify(jobDAO, times(2)).updateJob(eq(job), any());
     }
 
     @Test
@@ -70,7 +81,7 @@ public class JobServiceTest {
         JobSummary job = new JobSummary();
         jobService.setJobPhase(job, "RUN");
 
-        verify(jobDAO, times(2)).updateJob(job);
+        verify(jobDAO, times(2)).updateJob(eq(job), any());
     }
 
     @Test
@@ -82,7 +93,7 @@ public class JobServiceTest {
         JobSummary job = new JobSummary();
         jobService.setJobPhase(job, "RUN");
 
-        verify(jobDAO, times(2)).updateJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())));
+        verify(jobDAO, times(2)).updateJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())), any());
     }
 
     @Test
@@ -94,23 +105,47 @@ public class JobServiceTest {
         JobSummary job = new JobSummary();
         jobService.setJobPhase(job, "RUN");
 
-        verify(jobDAO, times(2)).updateJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())));
+        verify(jobDAO, times(2)).updateJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())), any());
     }
 
     @Test
     public void testSyncJobResultVoSpaceError() {
-        when(uriService.getTransfer(any())).thenReturn(getHttpTransfer());
-        doThrow(new NodeBusyException("/foo")).when(uriService).setSyncTransferEndpoints(any());
+        Transfer transfer = getPullFromVoSpaceHttpTransfer();
+        assertFalse(transfer.getProtocols().isEmpty());
+        when(uriService.getTransfer(any())).thenReturn(transfer);
+        doThrow(new NodeBusyException("/foo")).when(uriService).getNegotiatedTransfer(any(), any());
         jobService.createSyncJobResult(new JobSummary());
-        verify(jobDAO, times(1)).createJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())));
+        verify(jobDAO, times(1)).createJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())), any());
+        assertTrue(transfer.getProtocols().isEmpty());
     }
 
     @Test
     public void testSyncJobResultUnexpectedError() {
-        when(uriService.getTransfer(any())).thenReturn(getHttpTransfer());
-        doThrow(new NullPointerException()).when(uriService).setSyncTransferEndpoints(any());
+        Transfer transfer = getPullFromVoSpaceHttpTransfer();
+        assertFalse(transfer.getProtocols().isEmpty());
+        when(uriService.getTransfer(any())).thenReturn(transfer);
+        doThrow(new NullPointerException()).when(uriService).getNegotiatedTransfer(any(), any());
         jobService.createSyncJobResult(new JobSummary());
-        verify(jobDAO, times(1)).createJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())));
+        verify(jobDAO, times(1)).createJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())), any());
+        assertTrue(transfer.getProtocols().isEmpty());
+    }
+
+    @Test
+    public void testSyncJobResultErrorAfterNegotiatedTransfer() {
+        Transfer transfer = getPullFromVoSpaceHttpTransfer();
+        assertFalse(transfer.getProtocols().isEmpty());
+        when(uriService.getTransfer(any())).thenReturn(transfer);
+
+        Transfer negotiatedTransfer = getPullFromVoSpaceHttpTransfer();
+        assertFalse(negotiatedTransfer.getProtocols().isEmpty());
+        when(uriService.getNegotiatedTransfer(any(), any())).thenReturn(negotiatedTransfer);
+
+        doThrow(new NullPointerException()).when(servletRequest).getContextPath();
+        jobService.createSyncJobResult(new JobSummary());
+
+        verify(jobDAO, times(1)).createJob(argThat(j -> ExecutionPhase.ERROR.equals(j.getPhase())), any());
+        assertTrue(transfer.getProtocols().isEmpty());
+        assertTrue(negotiatedTransfer.getProtocols().isEmpty());
     }
 
     @Test
@@ -134,7 +169,7 @@ public class JobServiceTest {
     @Test
     public void testStartJobSetExecutingPhaseForAsyncPullFromVoSpace() {
 
-        Transfer httpTransfer = getHttpTransfer();
+        Transfer httpTransfer = getPullFromVoSpaceHttpTransfer();
 
         JobSummary job = new JobSummary();
         setJobInfo(job, httpTransfer);
@@ -149,7 +184,7 @@ public class JobServiceTest {
 
     @Test
     public void testStartJobMoveNode() {
-        
+
         Transfer moveNode = new Transfer();
         moveNode.setDirection("vos://example.com!vospace/myfile");
 
@@ -163,20 +198,27 @@ public class JobServiceTest {
             JobSummary j = invocation.getArgument(0);
             phases.add(j.getPhase());
             return null;
-        }).when(jobDAO).updateJob(any());
+        }).when(jobDAO).updateJob(any(), any());
 
         jobService.setJobPhase(job, "RUN");
 
         verify(moveService, timeout(1000).times(1)).processMoveJob(any(), any());
 
-        verify(jobDAO, times(3)).updateJob(any());
+        verify(jobDAO, timeout(1000).times(3)).updateJob(any(), any());
+
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException ex) {
+        }
+
         assertEquals(ExecutionPhase.EXECUTING, phases.get(0));
         assertEquals(ExecutionPhase.EXECUTING, phases.get(1));
         assertEquals(ExecutionPhase.COMPLETED, phases.get(2));
     }
 
-    private Transfer getHttpTransfer() {
+    private Transfer getPullFromVoSpaceHttpTransfer() {
         Transfer transfer = new Transfer();
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/myfile"));
         transfer.setDirection("pullFromVoSpace");
         Protocol protocol = new Protocol();
         protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
index 87754f08f67bd70e59c57f9043ce86a710ee7b2e..2b6fa1f48d3cdeb6f745d59b20595c8ede9a6a13 100644
--- a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
+++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
@@ -8,6 +8,7 @@ package it.inaf.oats.vospace;
 import it.inaf.ia2.aa.data.User;
 import static it.inaf.oats.vospace.VOSpaceXmlTestUtil.loadDocument;
 import it.inaf.oats.vospace.datamodel.NodeProperties;
+import it.inaf.oats.vospace.datamodel.Views;
 import it.inaf.oats.vospace.exception.ErrorSummaryFactory;
 import it.inaf.oats.vospace.exception.PermissionDeniedException;
 import it.inaf.oats.vospace.persistence.JobDAO;
@@ -20,6 +21,7 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.sql.Timestamp;
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Optional;
 import net.ivoa.xml.uws.v1.ExecutionPhase;
@@ -55,14 +57,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import org.w3c.dom.Document;
 import java.util.List;
 import net.ivoa.xml.uws.v1.ErrorSummary;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.BeforeEach;
+import org.mockito.ArgumentCaptor;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.doAnswer;
 
 @SpringBootTest
 @AutoConfigureMockMvc
 @ContextConfiguration(classes = {TokenFilterConfig.class})
-@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
+@TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true", "file-service-url=http://file-service"})
 public class TransferControllerTest {
 
     @MockBean
@@ -100,21 +105,10 @@ public class TransferControllerTest {
 
     @Test
     public void testPullFromVoSpaceAsync() throws Exception {
-
-        Node node = mockPublicDataNode();
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));
-
-        String requestBody = getResourceFileContent("pullFromVoSpace.xml");
-
-        String redirect = mockMvc.perform(post("/transfers?PHASE=RUN")
-                .content(requestBody)
-                .contentType(MediaType.APPLICATION_XML)
-                .accept(MediaType.APPLICATION_XML))
-                .andDo(print())
-                .andExpect(status().is3xxRedirection())
-                .andReturn().getResponse().getHeader("Location");
-
-        assertThat(redirect, matchesPattern("^/transfers/.*"));
+        // job completion will be set by file service
+        String endpoint = testAsyncTransferNegotiation("/mynode",
+                getResourceFileContent("pullFromVoSpace.xml"), ExecutionPhase.EXECUTING);
+        assertTrue(endpoint.startsWith("http://file-service/mynode?jobId="));
     }
 
     @Test
@@ -134,15 +128,22 @@ public class TransferControllerTest {
                 .andReturn().getResponse().getHeader("Location");
 
         assertThat(redirect, matchesPattern("^/transfers/.*/results/transferDetails"));
+
+        verify(jobDao, times(1)).createJob(argThat(j -> {
+            return ExecutionPhase.COMPLETED == j.getPhase()
+                    && j.getResults().get(0).getHref().contains("/transferDetails");
+        }), argThat(t -> {
+            return t.getProtocols().get(0).getEndpoint().startsWith("http://file-service/mynode?jobId=");
+        }));
     }
 
     @Test
     public void testPullToVoSpaceTape() throws Exception {
-        testPullToVoSpace("/mynode", getResourceFileContent("pullToVoSpace-tape.xml"));
+        testVoSpaceAsyncTransfer("/mynode", getResourceFileContent("pullToVoSpace-tape.xml"));
 
         verify(asyncTransfService, times(1)).startJob(any());
-        
-        verify(jobDao, times(2)).updateJob(argThat(j -> ExecutionPhase.QUEUED == j.getPhase()));
+
+        verify(jobDao, times(2)).updateJob(argThat(j -> ExecutionPhase.QUEUED == j.getPhase()), any());
     }
 
     @Test
@@ -150,29 +151,52 @@ public class TransferControllerTest {
 
         when(nodeDao.getNodeOsName(eq("/portalnode"))).thenReturn("file.fits");
 
-        testPullToVoSpace("/portalnode", getResourceFileContent("pullToVoSpace-portal.xml"));
+        String endpoint = testAsyncTransferNegotiation("/portalnode",
+                getResourceFileContent("pullToVoSpace-portal.xml"), ExecutionPhase.COMPLETED);
 
-        verify(nodeDao, times(1)).setNodeLocation(eq("/portalnode"), eq(2), eq("lbcr.20130512.060722.fits.gz"));
+        assertTrue(endpoint.startsWith("http://archive.lbto.org"));
 
-        verify(jobDao, times(2)).updateJob(argThat(j -> {
-            assertTrue(j.getResults().get(0).getHref().startsWith("http://archive.lbto.org"));
-            assertEquals(ExecutionPhase.COMPLETED, j.getPhase());
-            return true;
-        }));
+        verify(nodeDao, times(1)).setNodeLocation(eq("/portalnode"), eq(2), eq("lbcr.20130512.060722.fits.gz"));
     }
 
     @Test
     public void testPushToVoSpace() throws Exception {
+        // job completion will be set by file service
+        String endpoint = testAsyncTransferNegotiation("/uploadedfile",
+                getResourceFileContent("pushToVoSpace.xml"), ExecutionPhase.EXECUTING);
+        assertTrue(endpoint.startsWith("http://file-service/uploadedfile?jobId="));
+    }
 
-        when(nodeDao.getNodeOsName(eq("/uploadedfile"))).thenReturn("file.fits");
+    private String testAsyncTransferNegotiation(String path, String requestBody, ExecutionPhase endPhase) throws Exception {
 
-        testPullToVoSpace("/uploadedfile", getResourceFileContent("pushToVoSpace.xml"));
-        
-        // job completion will be set by file service
-        verify(jobDao, times(2)).updateJob(argThat(j -> ExecutionPhase.EXECUTING == j.getPhase()));
+        // detect phase updates
+        List<ExecutionPhase> phases = new ArrayList<>();
+        List<Transfer> negotiatedTransfers = new ArrayList<>();
+        doAnswer(invocation -> {
+            phases.add(((JobSummary) invocation.getArgument(0)).getPhase());
+            negotiatedTransfers.add(invocation.getArgument(1));
+            return null;
+        }).when(jobDao).updateJob(any(), any());
+
+        testVoSpaceAsyncTransfer(path, requestBody);
+
+        ArgumentCaptor<JobSummary> jobCaptor = ArgumentCaptor.forClass(JobSummary.class);
+        verify(jobDao, times(2)).updateJob(jobCaptor.capture(), any());
+
+        assertEquals(2, phases.size());
+        assertEquals(ExecutionPhase.EXECUTING, phases.get(0));
+        assertEquals(endPhase, phases.get(1));
+
+        JobSummary job = jobCaptor.getAllValues().get(1);
+        assertEquals(endPhase, job.getPhase());
+        assertTrue(job.getResults().get(0).getHref().contains("/transferDetails"));
+
+        assertNull(negotiatedTransfers.get(0));
+        Transfer negotiatedTransfer = negotiatedTransfers.get(1);
+        return negotiatedTransfer.getProtocols().get(0).getEndpoint();
     }
 
-    private void testPullToVoSpace(String path, String requestBody) throws Exception {
+    private void testVoSpaceAsyncTransfer(String path, String requestBody) throws Exception {
 
         Node node = mockPublicDataNode();
         when(nodeDao.listNode(eq(path))).thenReturn(Optional.of(node));
@@ -209,7 +233,7 @@ public class TransferControllerTest {
                 .andExpect(status().is3xxRedirection())
                 .andReturn().getResponse().getHeader("Location");
 
-        verify(jobDao, times(2)).updateJob(any());
+        verify(jobDao, times(2)).updateJob(any(), any());
 
         assertThat(redirect, matchesPattern("^/transfers/.*"));
     }
@@ -219,6 +243,8 @@ public class TransferControllerTest {
 
         JobSummary job = getFakePendingJob();
         when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
+        
+        when(jobDao.getTransferDetails(eq("123"))).thenReturn(new Transfer());
 
         mockMvc.perform(get("/transfers/123/results/transferDetails")
                 .header("Authorization", "Bearer user1_token")
@@ -280,59 +306,59 @@ public class TransferControllerTest {
 
         verify(jobDao, times(1)).getJob(eq("123"));
     }
-    
+
     @Test
-    public void testErrorEndpoint() throws Exception {        
+    public void testErrorEndpoint() throws Exception {
         JobSummary job = new JobSummary();
         job.setJobId("123");
         job.setPhase(ExecutionPhase.EXECUTING);
         ErrorSummary e = ErrorSummaryFactory.newErrorSummary(
-                new PermissionDeniedException("/pippo1/pippo2")
+                PermissionDeniedException.forPath("/pippo1/pippo2")
         );
-        job.setErrorSummary(e);          
+        job.setErrorSummary(e);
 
         when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
-                
+
         String response = mockMvc.perform(get("/transfers/123/error")
                 .accept(MediaType.TEXT_PLAIN_VALUE))
                 .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn().getResponse().getContentAsString();
-        
+
         assertEquals("Job is not in ERROR phase", response);
-        
+
         job.setPhase(ExecutionPhase.ERROR);
-        
+
         response = mockMvc.perform(get("/transfers/123/error")
                 .accept(MediaType.TEXT_PLAIN_VALUE))
                 .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn().getResponse().getContentAsString();
-        
+
         assertEquals(e.getDetailMessage(), response);
-        
-        e.setHasDetail(false);        
-        
+
+        e.setHasDetail(false);
+
         response = mockMvc.perform(get("/transfers/123/error")
                 .accept(MediaType.TEXT_PLAIN_VALUE))
                 .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn().getResponse().getContentAsString();
-        
+
         assertEquals("No error details available", response);
-        
+
         when(jobDao.getJob(eq("124"))).thenReturn(Optional.ofNullable(null));
-        
+
         mockMvc.perform(get("/transfers/124/error")
                 .accept(MediaType.TEXT_PLAIN_VALUE))
                 .andDo(print())
-                .andExpect(status().is4xxClientError());        
+                .andExpect(status().is4xxClientError());
     }
 
     @Test
     public void testGetJobs() throws Exception {
 
-        when(jobDao.getJobs(eq("user1"), any(), any(), any(), any()))
+        when(jobDao.getJobs(eq("user1"), any(), any(), any(), any(), any()))
                 .thenReturn(this.getFakeJobs());
 
         mockMvc.perform(get("/transfers")
@@ -342,12 +368,48 @@ public class TransferControllerTest {
                 .andDo(print())
                 .andExpect(status().is4xxClientError());
 
-        String xml2 = mockMvc.perform(get("/transfers")
+        mockMvc.perform(get("/transfers")
                 .header("Authorization", "Bearer user1_token")
                 .accept(MediaType.APPLICATION_XML))
                 .andDo(print())
-                .andExpect(status().isOk())
-                .andReturn().getResponse().getContentAsString();
+                .andExpect(status().isOk());
+
+        // direction query parameter
+        mockMvc.perform(get("/transfers")
+                .param("direction", "pullFromVoSpace")
+                .header("Authorization", "Bearer user1_token")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().isOk());
+
+        verify(jobDao, times(1)).getJobs(eq("user1"), any(), argThat(v -> {
+            return v.size() == 1 && v.contains(JobService.JobDirection.pullFromVoSpace);
+        }), any(), any(), any());
+
+        // PHASE query parameter
+        mockMvc.perform(get("/transfers")
+                .param("PHASE", ExecutionPhase.EXECUTING.value())
+                .header("Authorization", "Bearer user1_token")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().isOk());
+
+        verify(jobDao, times(1)).getJobs(eq("user1"), argThat(v -> {
+            return v.size() == 1 && v.contains(ExecutionPhase.EXECUTING);
+        }), any(), any(), any(), any());
+
+        // VIEW query parameters
+        mockMvc.perform(get("/transfers")
+                .param("VIEW", Views.TAR_VIEW_URI)
+                .param("VIEW", Views.ZIP_VIEW_URI)
+                .header("Authorization", "Bearer user1_token")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().isOk());
+
+        verify(jobDao, times(1)).getJobs(eq("user1"), any(), any(), argThat(v -> {
+            return v.size() == 2 && v.contains(Views.TAR_VIEW_URI) && v.contains(Views.ZIP_VIEW_URI);
+        }), any(), any());
     }
 
     @Test
@@ -409,7 +471,7 @@ public class TransferControllerTest {
     }
 
     protected static String getResourceFileContent(String fileName) throws Exception {
-        try (InputStream in = TransferControllerTest.class.getClassLoader().getResourceAsStream(fileName)) {
+        try ( InputStream in = TransferControllerTest.class.getClassLoader().getResourceAsStream(fileName)) {
             return new String(in.readAllBytes(), StandardCharsets.UTF_8);
         }
     }
diff --git a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
index d1dbe7d475aaad453e0dd7fc4f281a9275bf0617..4a59ad7ad6855ce840b7da304ce92fc13589190d 100644
--- a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
+++ b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
@@ -24,12 +24,14 @@ import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.vospace.v2.ContainerNode;
 import net.ivoa.xml.vospace.v2.DataNode;
 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 static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -65,7 +67,7 @@ public class UriServiceTest {
 
     @MockBean
     private CreateNodeService createNodeService;
-    
+
     @MockBean
     private FileServiceClient fileServiceClient;
 
@@ -110,9 +112,9 @@ public class UriServiceTest {
         when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
 
         JobSummary job = getJob();
-        uriService.setTransferJobResult(job, uriService.getTransfer(job));
+        Transfer negotiatedTransfer = uriService.getNegotiatedTransfer(job, uriService.getTransfer(job));
 
-        assertEquals("http://file-service/mydata1?jobId=job-id", job.getResults().get(0).getHref());
+        assertEquals("http://file-service/mydata1?jobId=job-id", negotiatedTransfer.getProtocols().get(0).getEndpoint());
     }
 
     @Test
@@ -145,11 +147,11 @@ public class UriServiceTest {
 
         JobSummary job = getJob();
         Transfer tr = uriService.getTransfer(job);
-        uriService.setTransferJobResult(job, tr);
+        Transfer negotiatedTransfer = uriService.getNegotiatedTransfer(job, tr);
 
-        assertEquals("http://file-service/mydata1?jobId=job-id&token=<new-token>", job.getResults().get(0).getHref());
+        assertEquals("http://file-service/mydata1?jobId=job-id&token=<new-token>", negotiatedTransfer.getProtocols().get(0).getEndpoint());
     }
-    
+
     @Test
     public void testPrivateUrlPermissionDenied() {
 
@@ -180,10 +182,11 @@ public class UriServiceTest {
 
         JobSummary job = getJob();
         Transfer tr = uriService.getTransfer(job);
-        assertThrows(PermissionDeniedException.class, 
-                ()->{ uriService.setTransferJobResult(job, tr);});        
+        assertThrows(PermissionDeniedException.class, () -> {
+            uriService.getNegotiatedTransfer(job, tr);
+        });
     }
-    
+
     @Test
     public void testPrivateUrlNodeBusy() {
 
@@ -197,8 +200,8 @@ public class UriServiceTest {
         readgroup.setUri(NodeProperties.GROUP_READ_URI);
         readgroup.setValue("group1");
         node.getProperties().add(readgroup);
-        
-        node.setBusy(Boolean.TRUE);        
+
+        node.setBusy(Boolean.TRUE);
 
         when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
 
@@ -216,8 +219,9 @@ public class UriServiceTest {
 
         JobSummary job = getJob();
         Transfer tr = uriService.getTransfer(job);
-        assertThrows(NodeBusyException.class, 
-                ()->{ uriService.setTransferJobResult(job, tr);});        
+        assertThrows(NodeBusyException.class, () -> {
+            uriService.getNegotiatedTransfer(job, tr);
+        });
     }
 
     @Test
@@ -265,11 +269,11 @@ public class UriServiceTest {
 
         when(createNodeService.createNode(any(), any(), eq(user))).thenReturn(dnode);
 
-        uriService.setTransferJobResult(job, tr);
+        Transfer negotiatedTransfer = uriService.getNegotiatedTransfer(job, tr);
 
         verify(createNodeService, times(1)).createNode(any(), any(), eq(user));
 
-        assertEquals("http://file-service/mydata1/mydata2?jobId=job-id2&token=<new-token>", job.getResults().get(0).getHref());
+        assertEquals("http://file-service/mydata1/mydata2?jobId=job-id2&token=<new-token>", negotiatedTransfer.getProtocols().get(0).getEndpoint());
     }
 
     @Test
@@ -312,11 +316,11 @@ public class UriServiceTest {
 
         assertEquals(2, transfer.getProtocols().size());
 
-        uriService.setSyncTransferEndpoints(job);
+        Transfer negotiatedTransfer = uriService.getNegotiatedTransfer(job, transfer);
 
         // invalid protocol is removed
-        assertEquals(1, transfer.getProtocols().size());
-        assertEquals("ivo://ivoa.net/vospace/core#httpget", transfer.getProtocols().get(0).getUri());
+        assertEquals(1, negotiatedTransfer.getProtocols().size());
+        assertEquals("ivo://ivoa.net/vospace/core#httpget", negotiatedTransfer.getProtocols().get(0).getUri());
     }
 
     @Test
@@ -352,11 +356,11 @@ public class UriServiceTest {
 
         assertEquals(2, transfer.getProtocols().size());
 
-        uriService.setSyncTransferEndpoints(job);
+        Transfer negotiatedTransfer = uriService.getNegotiatedTransfer(job, transfer);
 
         // invalid protocol is removed
-        assertEquals(1, transfer.getProtocols().size());
-        assertEquals("ivo://ivoa.net/vospace/core#httpput", transfer.getProtocols().get(0).getUri());
+        assertEquals(1, negotiatedTransfer.getProtocols().size());
+        assertEquals("ivo://ivoa.net/vospace/core#httpput", negotiatedTransfer.getProtocols().get(0).getUri());
     }
 
     @Test
@@ -376,7 +380,7 @@ public class UriServiceTest {
         job.setJobInfo(jobInfo);
 
         try {
-            uriService.setSyncTransferEndpoints(job);
+            uriService.getNegotiatedTransfer(job, transfer);
             fail("Expected ProtocolNotSupportedException");
         } catch (ProtocolNotSupportedException ex) {
         }
@@ -395,7 +399,7 @@ public class UriServiceTest {
         job.setJobInfo(jobInfo);
 
         try {
-            uriService.setSyncTransferEndpoints(job);
+            uriService.getNegotiatedTransfer(job, transfer);
             fail("Expected InvalidArgumentException");
         } catch (InvalidArgumentException ex) {
         }
@@ -411,13 +415,48 @@ public class UriServiceTest {
         testArchiveViewEndpoint(Views.ZIP_VIEW_URI);
     }
 
+    @Test
+    public void testInvalidTransferNoProtocols() {
+
+        Transfer transfer = new Transfer();
+        transfer.setDirection("pullFromVoSpace");
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/file1"));
+
+        JobSummary job = new JobSummary();
+        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
+        jobInfo.getAny().add(transfer);
+        job.setJobInfo(jobInfo);
+
+        mockPublicNode("file1");
+        mockPublicNode("file2");
+
+        InvalidArgumentException ex = assertThrows(InvalidArgumentException.class, () -> {
+            uriService.getNegotiatedTransfer(job, transfer);
+        });
+        assertTrue(ex.getMessage().contains("no protocol"));
+    }
+
     private void testArchiveViewEndpoint(String viewUri) {
 
         Transfer transfer = new Transfer();
         transfer.setDirection("pullFromVoSpace");
-        transfer.setTarget(Arrays.asList("vos://example.com!vospace/file1", "vos://example.com!vospace/file2"));
+        transfer.setTarget(Arrays.asList("vos://example.com!vospace/parent_dir"));
+        Protocol protocol = new Protocol();
+        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
+        transfer.getProtocols().add(protocol);
         View view = new View();
         view.setUri(viewUri);
+
+        Param param1 = new Param();
+        param1.setUri(viewUri + "/include");
+        param1.setValue("file1");
+        view.getParam().add(param1);
+
+        Param param2 = new Param();
+        param2.setUri(viewUri + "/include");
+        param2.setValue("file2");
+        view.getParam().add(param2);
+
         transfer.setView(view);
 
         JobSummary job = new JobSummary();
@@ -426,10 +465,11 @@ public class UriServiceTest {
         jobInfo.getAny().add(transfer);
         job.setJobInfo(jobInfo);
 
-        mockPublicNode("file1");
-        mockPublicNode("file2");
+        mockPublicNode("parent_dir");
+        mockPublicNode("parent_dir/file1");
+        mockPublicNode("parent_dir/file2");
 
-        uriService.setTransferJobResult(job, transfer);
+        uriService.getNegotiatedTransfer(job, transfer);
 
         verify(fileServiceClient, times(1)).startArchiveJob(transfer, "archive-job-id");
     }
@@ -454,6 +494,9 @@ public class UriServiceTest {
         Transfer transfer = new Transfer();
         transfer.setTarget(Arrays.asList("vos://example.com!vospace/mydata1"));
         transfer.setDirection(JobService.JobDirection.pullFromVoSpace.toString());
+        Protocol protocol = new Protocol();
+        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
+        transfer.getProtocols().add(protocol);
 
         JobSummary job = new JobSummary();
         job.setJobId("job-id");
@@ -470,6 +513,9 @@ public class UriServiceTest {
         Transfer transfer = new Transfer();
         transfer.setTarget(Arrays.asList("vos://example.com!vospace/mydata1/mydata2"));
         transfer.setDirection(JobService.JobDirection.pushToVoSpace.toString());
+        Protocol protocol = new Protocol();
+        protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
+        transfer.getProtocols().add(protocol);
 
         JobSummary job = new JobSummary();
         job.setJobId("job-id2");
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java b/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java
index b9af33a7efabceb7ef77bc3968a9a7ff3462249c..884f870e053c31d69fbed06bc1cd9f88f07462de 100644
--- a/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java
+++ b/src/test/java/it/inaf/oats/vospace/persistence/DataSourceConfig.java
@@ -12,9 +12,12 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.sql.DataSource;
@@ -24,9 +27,7 @@ import org.springframework.boot.test.context.TestConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Primary;
 import org.springframework.context.annotation.Scope;
-import org.springframework.core.io.ByteArrayResource;
 import org.springframework.core.io.ClassPathResource;
-import org.springframework.jdbc.datasource.init.ScriptUtils;
 
 /**
  * Generates a DataSource that can be used for testing DAO classes. It loads an
@@ -82,15 +83,59 @@ public class DataSourceConfig {
             assertTrue(scriptDir.exists(), "DAO tests require " + scriptDir.getAbsolutePath() + " to exists.\n"
                     + "Please clone the repository from https://www.ict.inaf.it/gitlab/vospace/vospace-file-catalog.git");
 
-            File[] scripts = scriptDir.listFiles(f -> f.getName().endsWith(".sql"));
-            Arrays.sort(scripts); // sort alphabetically
+            // load all sql files in vospace-file-catalog repo
+            File[] repoScripts = scriptDir.listFiles(f -> f.getName().endsWith(".sql"));
+            Arrays.sort(repoScripts); // sort alphabetically
+
+            // add test-data.sql
+            List<File> scripts = new ArrayList<>(Arrays.asList(repoScripts));
+            scripts.add(new ClassPathResource("test-data.sql").getFile());
 
             for (File script : scripts) {
-                ByteArrayResource scriptResource = replaceDollarQuoting(script.toPath());
-                ScriptUtils.executeSqlScript(conn, scriptResource);
+                String scriptContent = Files.readString(script.toPath());
+                for (String sql : splitScript(scriptContent)) {
+                    executeSql(conn, replaceDollarQuoting(sql));
+                }
             }
+        }
+    }
 
-            ScriptUtils.executeSqlScript(conn, new ClassPathResource("test-data.sql"));
+    /**
+     * Spring ScriptUtils is not able to correctly split the SQL statements if a
+     * function definition contains semicolon characters, so this method is used
+     * instead of it.
+     */
+    private List<String> splitScript(String script) {
+
+        List<String> parts = new ArrayList<>();
+
+        StringBuilder sb = new StringBuilder();
+
+        boolean insideFunc = false;
+        for (int i = 0; i < script.length(); i++) {
+            char c = script.charAt(i);
+            sb.append(c);
+
+            if (insideFunc) {
+                if (i > 6 && "$func$".equals(script.substring(i - 6, i))) {
+                    insideFunc = false;
+                }
+            } else {
+                if (i > 6 && "$func$".equals(script.substring(i - 6, i))) {
+                    insideFunc = true;
+                } else if (c == ';') {
+                    parts.add(sb.toString());
+                    sb = new StringBuilder();
+                }
+            }
+        }
+
+        return parts;
+    }
+
+    private void executeSql(Connection conn, String sqlStatement) throws SQLException {
+        try ( Statement stat = conn.createStatement()) {
+            stat.execute(sqlStatement);
         }
     }
 
@@ -100,9 +145,7 @@ public class DataSourceConfig {
      * instead of inside the original files because dollar quoting provides a
      * better visibility.
      */
-    private ByteArrayResource replaceDollarQuoting(Path sqlScriptPath) throws Exception {
-
-        String scriptContent = Files.readString(sqlScriptPath);
+    private String replaceDollarQuoting(String scriptContent) {
 
         if (scriptContent.contains("$func$")) {
 
@@ -114,7 +157,7 @@ public class DataSourceConfig {
             scriptContent = scriptContent.replace(originalFunction, newFunction);
         }
 
-        return new ByteArrayResource(scriptContent.getBytes());
+        return scriptContent;
     }
 
     private String extractFunctionDefinition(String scriptContent) {
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java
index a18f8d4a891fd836882b548d0b71dc4e242477e1..69b0f8e0a1a2206c14ba85e67b8d47765b616e8a 100644
--- a/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java
+++ b/src/test/java/it/inaf/oats/vospace/persistence/JobDAOTest.java
@@ -6,6 +6,7 @@
 package it.inaf.oats.vospace.persistence;
 
 import it.inaf.oats.vospace.JobService;
+import it.inaf.oats.vospace.datamodel.Views;
 import java.util.List;
 import javax.sql.DataSource;
 import net.ivoa.xml.uws.v1.ExecutionPhase;
@@ -29,6 +30,9 @@ import net.ivoa.xml.uws.v1.Jobs;
 import it.inaf.oats.vospace.exception.ErrorSummaryFactory;
 import it.inaf.oats.vospace.exception.PermissionDeniedException;
 import java.util.Arrays;
+import net.ivoa.xml.uws.v1.ResultReference;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 @ExtendWith(SpringExtension.class)
 @ContextConfiguration(classes = {DataSourceConfig.class})
@@ -73,18 +77,64 @@ public class JobDAOTest {
 
         JobSummary job = getJob();
 
-        dao.createJob(job);
+        dao.createJob(job, null);
 
         assertTrue(dao.getJob("123").isPresent());
         assertEquals(ExecutionPhase.PENDING, dao.getJob("123").get().getPhase());
 
         // uses the job retrieved from DAO to perform the update (reproduced a bug in job update)
         job = dao.getJob("123").get();
+        assertNotNull(job.getCreationTime());
+        assertNull(job.getStartTime());
 
         job.setPhase(ExecutionPhase.EXECUTING);
-        dao.updateJob(job);
+        dao.updateJob(job, null);
 
-        assertEquals(ExecutionPhase.EXECUTING, dao.getJob("123").get().getPhase());
+        job = dao.getJob("123").get();
+        assertEquals(ExecutionPhase.EXECUTING, job.getPhase());
+        assertNotNull(job.getStartTime());
+        assertNull(job.getEndTime());
+
+        assertNull(dao.getTransferDetails(job.getJobId()));
+
+        Transfer negotiatedTransfer = new Transfer();
+        job.setPhase(ExecutionPhase.COMPLETED);
+        dao.updateJob(job, negotiatedTransfer);
+
+        job = dao.getJob("123").get();
+        assertEquals(ExecutionPhase.COMPLETED, job.getPhase());
+        assertNotNull(job.getStartTime());
+        assertNotNull(job.getEndTime());
+        assertNotNull(dao.getTransferDetails(job.getJobId()));
+    }
+    
+    /**
+     * Jobs created by the /synctrans endpoint contains results list at creation time.
+     */
+    @Test
+    public void testCreateJobWithResults() {
+        JobSummary job = getJob();
+        job.setPhase(ExecutionPhase.COMPLETED);
+
+        ResultReference result = new ResultReference();
+        result.setId("transferDetails");
+        result.setHref("http://ia2.inaf.it");
+        job.getResults().add(result);
+
+        Transfer negotiatedTransfer = new Transfer();
+
+        dao.createJob(job, negotiatedTransfer);
+
+        // Retrieve it back        
+        Optional<JobSummary> retrievedJobOpt = dao.getJob(job.getJobId());
+        assertTrue(retrievedJobOpt.isPresent());
+
+        JobSummary retrievedJob = retrievedJobOpt.get();
+        assertEquals(1, retrievedJob.getResults().size());
+        assertNotNull(retrievedJob.getStartTime());
+        assertNotNull(retrievedJob.getEndTime());
+
+        assertNotNull(dao.getTransferDetails(retrievedJob.getJobId()));
     }
 
     @Test
@@ -96,7 +146,7 @@ public class JobDAOTest {
         // Generate it from exception        
         ErrorSummary errorSummary
                 = ErrorSummaryFactory.newErrorSummary(
-                        new PermissionDeniedException("/pippo1/pippo2"));
+                        PermissionDeniedException.forPath("/pippo1/pippo2"));
         
         // Check if properly generated
         assertTrue(errorSummary.isHasDetail());
@@ -104,7 +154,7 @@ public class JobDAOTest {
 
         job.setErrorSummary(errorSummary);
 
-        dao.createJob(job);
+        dao.createJob(job, null);
 
         // Retrieve it back        
         Optional<JobSummary> retrievedJobOpt = dao.getJob(job.getJobId());
@@ -113,20 +163,21 @@ public class JobDAOTest {
         JobSummary retrievedJob = retrievedJobOpt.get();
         assertEquals(ExecutionPhase.ERROR, retrievedJob.getPhase());
         assertTrue(areEqual(job.getErrorSummary(), retrievedJob.getErrorSummary()));
-
+        assertNotNull(retrievedJob.getStartTime());
+        assertNotNull(retrievedJob.getEndTime());
     }
 
     @Test
     public void testUpdateJobWithError() {
         JobSummary job = getJob();
 
-        dao.createJob(job);
+        dao.createJob(job, null);
 
         job.setPhase(ExecutionPhase.ERROR);
         // Generate it from exception        
         ErrorSummary errorSummary
                 = ErrorSummaryFactory.newErrorSummary(
-                        new PermissionDeniedException("/pippo1/pippo2"));
+                        PermissionDeniedException.forPath("/pippo1/pippo2"));
         
         // Check if properly generated        
         assertTrue(errorSummary.isHasDetail());
@@ -134,7 +185,7 @@ public class JobDAOTest {
                 
         job.setErrorSummary(errorSummary);
 
-        dao.updateJob(job);
+        dao.updateJob(job, null);
 
         // Retrieve it back        
         Optional<JobSummary> retrievedJobOpt = dao.getJob(job.getJobId());
@@ -143,6 +194,7 @@ public class JobDAOTest {
         JobSummary retrievedJob = retrievedJobOpt.get();
         assertEquals(ExecutionPhase.ERROR, retrievedJob.getPhase());
         assertTrue(areEqual(job.getErrorSummary(), retrievedJob.getErrorSummary()));
+        assertNotNull(retrievedJob.getEndTime());
     }
 
     @Test
@@ -151,10 +203,11 @@ public class JobDAOTest {
         String user = "user1";
         List<ExecutionPhase> phaseList = List.of();
         List<JobService.JobDirection> directionList = List.of();
+        List<String> viewList = List.of();
         Optional<LocalDateTime> after = Optional.ofNullable(null);
         Optional<Integer> last = Optional.ofNullable(null);
 
-        Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last);
+        Jobs jobs = dao.getJobs(user, phaseList, directionList, viewList, after, last);
 
         assertTrue(jobs != null);
         List<ShortJobDescription> sjdList = jobs.getJobref();
@@ -176,10 +229,11 @@ public class JobDAOTest {
         String user = "user1";
         List<ExecutionPhase> phaseList = List.of();
         List<JobService.JobDirection> directionList = List.of();
+        List<String> viewList = List.of();
         Optional<LocalDateTime> after = Optional.ofNullable(null);
         Optional<Integer> last = Optional.of(2);
 
-        Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last);
+        Jobs jobs = dao.getJobs(user, phaseList, directionList, viewList, after, last);
         List<ShortJobDescription> sjdList = jobs.getJobref();
         assertEquals(2, sjdList.size());
 
@@ -192,10 +246,11 @@ public class JobDAOTest {
         List<ExecutionPhase> phaseList
                 = List.of(ExecutionPhase.PENDING, ExecutionPhase.EXECUTING);
         List<JobService.JobDirection> directionList = List.of();
+        List<String> viewList = List.of();
         Optional<LocalDateTime> after = Optional.ofNullable(null);
         Optional<Integer> last = Optional.ofNullable(null);
 
-        Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last);
+        Jobs jobs = dao.getJobs(user, phaseList, directionList, viewList, after, last);
         List<ShortJobDescription> sjdList = jobs.getJobref();
         assertEquals(sjdList.size(), 2);
         assertEquals("pippo5", sjdList.get(0).getId());
@@ -210,10 +265,11 @@ public class JobDAOTest {
         List<JobService.JobDirection> directionList
                 = List.of(JobService.JobDirection.pullFromVoSpace,
                         JobService.JobDirection.pullToVoSpace);
+        List<String> viewList = List.of();
 
         Optional<LocalDateTime> after = Optional.ofNullable(null);
         Optional<Integer> last = Optional.ofNullable(null);
-        Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last);
+        Jobs jobs = dao.getJobs(user, phaseList, directionList, viewList, after, last);
         List<ShortJobDescription> sjdList = jobs.getJobref();
         assertEquals(2, sjdList.size());
         assertEquals("pippo3", sjdList.get(0).getId());
@@ -226,13 +282,14 @@ public class JobDAOTest {
         String user = "user1";
         List<ExecutionPhase> phaseList = List.of();
         List<JobService.JobDirection> directionList = List.of();
+        List<String> viewList = List.of();
 
         LocalDateTime ldt
                 = LocalDateTime.of(2013, Month.FEBRUARY, 7, 18, 15);
         Optional<LocalDateTime> after = Optional.of(ldt);
 
         Optional<Integer> last = Optional.ofNullable(null);
-        Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last);
+        Jobs jobs = dao.getJobs(user, phaseList, directionList, viewList, after, last);
         List<ShortJobDescription> sjdList = jobs.getJobref();
         assertEquals(2, sjdList.size());
         assertEquals("pippo5", sjdList.get(0).getId());
@@ -248,13 +305,14 @@ public class JobDAOTest {
         List<JobService.JobDirection> directionList
                 = List.of(JobService.JobDirection.pullFromVoSpace,
                         JobService.JobDirection.pullToVoSpace);
+        List<String> viewList = List.of(Views.TAR_VIEW_URI, Views.ZIP_VIEW_URI);
 
         LocalDateTime ldt
                 = LocalDateTime.of(2013, Month.FEBRUARY, 7, 18, 15);
         Optional<LocalDateTime> after = Optional.of(ldt);
 
         Optional<Integer> last = Optional.of(2);
-        Jobs jobs = dao.getJobs(user, phaseList, directionList, after, last);
+        Jobs jobs = dao.getJobs(user, phaseList, directionList, viewList, after, last);
         List<ShortJobDescription> sjdList = jobs.getJobref();
         assertEquals(1, sjdList.size());
         assertEquals("pippo3", sjdList.get(0).getId());
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
index dd03781ce8d5f39dd06b5047d9830d598cafa278..98e4e10644c57b7b897d4ab14bf0526b793a3d0a 100644
--- a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
+++ b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
@@ -89,6 +89,17 @@ public class NodeDAOTest {
         assertFalse(children.isEmpty());
         assertTrue(children.size() == 2);
         assertTrue(children.containsAll(List.of("f4", "f5")));
+        
+    }
+    
+    @Test
+    public void testGetQuotaAndMD5() {
+        
+        ContainerNode node = (ContainerNode) dao.listNode("/test1/f1/f2_renamed").get();
+        assertEquals("50000", NodeProperties.getNodePropertyByURI(node, NodeProperties.QUOTA_URI));
+        DataNode child = (DataNode) node.getNodes().get(0);
+        assertEquals("4000", NodeProperties.getNodePropertyByURI(child, NodeProperties.LENGTH_URI));
+        assertEquals("<md5sum>", NodeProperties.getNodePropertyByURI(child, NodeProperties.MD5_URI));
     }
 
     @Test
diff --git a/src/test/resources/test-data.sql b/src/test/resources/test-data.sql
index 4b0af3bd45cb04a2b2f1d4739741f3bae4cec594..44a025cd80f440faad28393851b4e25892da41f7 100644
--- a/src/test/resources/test-data.sql
+++ b/src/test/resources/test-data.sql
@@ -16,8 +16,8 @@ INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, loc
 
 INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, group_read, group_write, location_id) VALUES ('', NULL, 'test1', 'container', 'user1', '{"group1","group2"}','{"group2"}', 1);      -- /test1
 INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, location_id) VALUES ('2', '', 'f1', 'container', 'user1', 1);      -- /test1/f1 (rel: /f1)
-INSERT INTO node (parent_path, parent_relative_path, name, os_name, type, creator_id, location_id) VALUES ('2.3', '3', 'f2_renamed', 'f2', 'container', 'user1', 1);      -- /test1/f1/f2_renamed (rel: /f1/f2)
-INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, location_id) VALUES ('2.3.4', '3.4', 'f3', 'data', 'user1', 1);      -- /test1/f1/f2_renamed/f3 (rel: /f1/f2/f3)
+INSERT INTO node (parent_path, parent_relative_path, name, os_name, type, creator_id, location_id, quota) VALUES ('2.3', '3', 'f2_renamed', 'f2', 'container', 'user1', 1, 50000);      -- /test1/f1/f2_renamed (rel: /f1/f2)
+INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, location_id, content_md5, content_length) VALUES ('2.3.4', '3.4', 'f3', 'data', 'user1', 1, '<md5sum>', 4000);      -- /test1/f1/f2_renamed/f3 (rel: /f1/f2/f3)
 
 INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, is_public, location_id) VALUES ('', NULL, 'test2', 'container', 'user2', true, 1);      -- /test2
 INSERT INTO node (parent_path, parent_relative_path, name, type, creator_id, is_public, location_id) VALUES ('6', '', 'f4', 'container', 'user2', true, 1);    -- /test2/f4 (rel: /f4)
@@ -40,7 +40,7 @@ DELETE FROM job;
 
 INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo1', 'user1', 'pullFromVoSpace', 'ARCHIVED', NULL, NULL, '2011-06-22 19:10:25', NULL, NULL);
 INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo2', 'user1', 'pullToVoSpace', 'PENDING', NULL, NULL, '2012-06-22 19:10:25', NULL, NULL);
-INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo3', 'user1', 'pullFromVoSpace', 'QUEUED', NULL, NULL, '2013-06-22 19:10:25', NULL, NULL);
+INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo3', 'user1', 'pullFromVoSpace', 'QUEUED', NULL, NULL, '2013-06-22 19:10:25', '{"transfer": {"view": {"uri": "ivo://ia2.inaf.it/vospace/views#zip"}}}', NULL);
 INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo4', 'user2', 'copyNode', 'PENDING', NULL, NULL, '2014-06-22 19:10:25', NULL, NULL);
 INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo5', 'user1', 'pushToVoSpace', 'EXECUTING', NULL, NULL, '2015-06-22 19:10:25', NULL, NULL);
 INSERT INTO job (job_id, owner_id, job_type, phase, start_time, end_time, creation_time, job_info, results) VALUES ('pippo6', 'user2', 'pullFromVoSpace', 'PENDING', NULL, NULL, '2015-06-22 19:10:25', NULL, NULL);