Skip to content
Commits on Source (6)
...@@ -3,10 +3,9 @@ ...@@ -3,10 +3,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>it.inaf.ia2</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>vospace-parent</artifactId>
<version>2.4.5</version> <version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>it.inaf.ia2</groupId> <groupId>it.inaf.ia2</groupId>
<artifactId>vospace-file-service</artifactId> <artifactId>vospace-file-service</artifactId>
...@@ -18,61 +17,14 @@ ...@@ -18,61 +17,14 @@
<finalName>${project.artifactId}-${project.version}</finalName> <finalName>${project.artifactId}-${project.version}</finalName>
<!-- File catalog repository directory --> <!-- File catalog repository directory -->
<init_database_scripts_path>../../../vospace-file-catalog</init_database_scripts_path> <init_database_scripts_path>../../../vospace-file-catalog</init_database_scripts_path>
<zonky.postgres-binaries.version>12.5.0</zonky.postgres-binaries.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>it.inaf.ia2</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>vospace-parent-classes</artifactId>
</dependency> <version>0.0.1-SNAPSHOT</version>
<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-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>rap-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Embedded PostgreSQL: -->
<dependency>
<groupId>com.opentable.components</groupId>
<artifactId>otj-pg-embedded</artifactId>
<version>0.13.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>it.oats.inaf</groupId>
<artifactId>vospace-datamodel</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>org.kamranzafar</groupId> <groupId>org.kamranzafar</groupId>
<artifactId>jtar</artifactId> <artifactId>jtar</artifactId>
...@@ -80,49 +32,6 @@ ...@@ -80,49 +32,6 @@
</dependency> </dependency>
</dependencies> </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> <build>
<finalName>${finalName}</finalName> <finalName>${finalName}</finalName>
<testResources> <testResources>
...@@ -144,14 +53,7 @@ ...@@ -144,14 +53,7 @@
</excludes> </excludes>
</testResource> </testResource>
</testResources> </testResources>
<plugins> <plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<trimStackTrace>false</trimStackTrace>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
...@@ -162,7 +64,6 @@ ...@@ -162,7 +64,6 @@
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
...@@ -180,5 +81,13 @@ ...@@ -180,5 +81,13 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </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> </project>
...@@ -9,6 +9,7 @@ import it.inaf.ia2.transfer.auth.TokenPrincipal; ...@@ -9,6 +9,7 @@ import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.service.ArchiveJob; import it.inaf.ia2.transfer.service.ArchiveJob;
import it.inaf.ia2.transfer.service.ArchiveJob.Type; import it.inaf.ia2.transfer.service.ArchiveJob.Type;
import it.inaf.ia2.transfer.service.ArchiveService; import it.inaf.ia2.transfer.service.ArchiveService;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import java.io.File; import java.io.File;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
package it.inaf.ia2.transfer.controller; package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.auth.TokenPrincipal; import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.exception.PermissionDeniedException; import it.inaf.oats.vospace.exception.PermissionDeniedException;
public abstract class AuthenticatedFileController extends FileController { public abstract class AuthenticatedFileController extends FileController {
......
/*
* 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.ia2.transfer.controller;
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);
}
}
...@@ -5,8 +5,9 @@ ...@@ -5,8 +5,9 @@
*/ */
package it.inaf.ia2.transfer.controller; package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.exception.JobException;
import it.inaf.ia2.transfer.persistence.JobDAO; import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.VoSpaceErrorSummarizableException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
...@@ -44,13 +45,12 @@ public abstract class FileController { ...@@ -44,13 +45,12 @@ public abstract class FileController {
jobDAO.updateJobPhase(ExecutionPhase.COMPLETED, jobId); jobDAO.updateJobPhase(ExecutionPhase.COMPLETED, jobId);
} }
} catch (Throwable t) { } catch (Throwable t) {
JobException jobException; VoSpaceErrorSummarizableException jobException;
if (t instanceof JobException) { if (t instanceof VoSpaceErrorSummarizableException) {
jobException = (JobException) t; jobException = (VoSpaceErrorSummarizableException) t;
} else { } else {
LOG.error("Unexpected error happened", t); LOG.error("Unexpected error happened", t);
jobException = new JobException(JobException.Type.FATAL, "Internal Fault") jobException = new InternalFaultException("Unexpected error happened");
.setErrorDetail("InternalFault: Unexpected error happened");
} }
if (jobId != null) { if (jobId != null) {
jobDAO.setJobError(jobId, jobException); jobDAO.setJobError(jobId, jobException);
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
*/ */
package it.inaf.ia2.transfer.controller; package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.exception.FileNotFoundException; import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.ia2.transfer.exception.JobException; import it.inaf.oats.vospace.exception.NodeNotFoundException;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
...@@ -33,13 +33,12 @@ public class FileResponseUtil { ...@@ -33,13 +33,12 @@ public class FileResponseUtil {
if (!file.exists()) { if (!file.exists()) {
LOG.error("File not found: " + file.getAbsolutePath()); LOG.error("File not found: " + file.getAbsolutePath());
throw new FileNotFoundException(vosPath == null ? file.getAbsolutePath() : vosPath); throw new NodeNotFoundException(vosPath == null ? file.getAbsolutePath() : vosPath);
} }
if (!file.canRead()) { if (!file.canRead()) {
LOG.error("File not readable: " + file.getAbsolutePath()); LOG.error("File not readable: " + file.getAbsolutePath());
throw new JobException(JobException.Type.FATAL, "Internal Fault") throw new InternalFaultException("File " + file.getName() + " is not readable");
.setErrorDetail("InternalFault: File " + file.getName() + " is not readable");
} }
response.setHeader("Content-Disposition", "attachment; filename=" response.setHeader("Content-Disposition", "attachment; filename="
......
...@@ -7,12 +7,12 @@ package it.inaf.ia2.transfer.controller; ...@@ -7,12 +7,12 @@ package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.transfer.auth.TokenPrincipal; import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.exception.FileNotFoundException;
import it.inaf.ia2.transfer.exception.InvalidArgumentException;
import it.inaf.ia2.transfer.exception.PermissionDeniedException;
import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO; import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.ia2.transfer.service.AuthorizationService; import it.inaf.ia2.transfer.service.AuthorizationService;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import java.io.File; import java.io.File;
import java.util.Optional; import java.util.Optional;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
...@@ -65,13 +65,13 @@ public class GetFileController extends FileController { ...@@ -65,13 +65,13 @@ public class GetFileController extends FileController {
FileInfo fileInfo = optFileInfo.get(); FileInfo fileInfo = optFileInfo.get();
if (!authorizationService.isDownloadable(fileInfo, (TokenPrincipal) request.getUserPrincipal())) { if (!authorizationService.isDownloadable(fileInfo, (TokenPrincipal) request.getUserPrincipal())) {
throw new PermissionDeniedException("PermissionDenied Path: " + path); throw PermissionDeniedException.forPath(path);
} }
File file = new File(fileInfo.getOsPath()); File file = new File(fileInfo.getOsPath());
FileResponseUtil.getFileResponse(response, file, path); FileResponseUtil.getFileResponse(response, file, path);
} else { } else {
throw new FileNotFoundException(path); throw new NodeNotFoundException(path);
} }
}, jobId); }, jobId);
} }
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
*/ */
package it.inaf.ia2.transfer.controller; package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.exception.FileNotFoundException;
import it.inaf.ia2.transfer.exception.InsufficientStorageException;
import it.inaf.ia2.transfer.exception.InvalidArgumentException;
import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO; import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.QuotaExceededException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
...@@ -73,7 +73,7 @@ public class PutFileController extends FileController { ...@@ -73,7 +73,7 @@ public class PutFileController extends FileController {
// if MultipartFile provides file size it is possible to check // if MultipartFile provides file size it is possible to check
// quota limit before reading the stream // quota limit before reading the stream
if (remainingQuota != null && file != null && file.getSize() > remainingQuota) { if (remainingQuota != null && file != null && file.getSize() > remainingQuota) {
throw new InsufficientStorageException("QuotaExceeded Path: " + fileInfo.getVirtualPath()); throw new QuotaExceededException("Path: " + fileInfo.getVirtualPath());
} }
if (file != null) { if (file != null) {
...@@ -87,7 +87,7 @@ public class PutFileController extends FileController { ...@@ -87,7 +87,7 @@ public class PutFileController extends FileController {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} else { } else {
throw new FileNotFoundException(path); throw new NodeNotFoundException(path);
} }
}, jobId); }, jobId);
} }
...@@ -127,7 +127,7 @@ public class PutFileController extends FileController { ...@@ -127,7 +127,7 @@ public class PutFileController extends FileController {
// Quota limit is checked again to handle cases where MultipartFile is not used // Quota limit is checked again to handle cases where MultipartFile is not used
if (remainingQuota != null && fileSize > remainingQuota) { if (remainingQuota != null && fileSize > remainingQuota) {
file.delete(); file.delete();
throw new InsufficientStorageException("QuotaExceeded Path: " + fileInfo.getVirtualPath()); throw new QuotaExceededException("Path: " + fileInfo.getVirtualPath());
} }
String md5Checksum = makeMD5Checksum(file); String md5Checksum = makeMD5Checksum(file);
......
/*
* 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.ia2.transfer.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;
}
}
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class FileNotFoundException extends JobException {
public FileNotFoundException(String path) {
super(Type.FATAL, "Node Not Found");
setErrorDetail("NodeNotFound Path: " + path);
}
}
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.INSUFFICIENT_STORAGE)
public class InsufficientStorageException extends JobException {
public InsufficientStorageException(String errorDetail) {
super(Type.FATAL, "Quota Exceeded");
setErrorDetail(errorDetail);
}
}
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidArgumentException extends JobException {
public InvalidArgumentException(String message) {
super(Type.FATAL, "Invalid Argument");
setErrorDetail("InvalidArgument: " + message);
}
}
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class JobException extends RuntimeException {
public static enum Type {
TRANSIENT("transient"),
FATAL("fatal");
private final String value;
private Type(String v) {
value = v;
}
public String value() {
return value;
}
}
private final Type type;
private String errorDetail;
public JobException(Type type, String message) {
super(message);
this.type = type;
}
public Type getType() {
return type;
}
public String getErrorDetail() {
return errorDetail;
}
public final JobException setErrorDetail(String errorDetail) {
this.errorDetail = errorDetail;
return this;
}
}
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class PermissionDeniedException extends JobException {
public PermissionDeniedException(String errorDetail) {
super(Type.FATAL, "Permission Denied");
setErrorDetail(errorDetail);
}
}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
package it.inaf.ia2.transfer.persistence; package it.inaf.ia2.transfer.persistence;
import it.inaf.ia2.transfer.exception.JobException; import it.inaf.oats.vospace.exception.VoSpaceErrorSummarizableException;
import java.sql.Types; import java.sql.Types;
import javax.sql.DataSource; import javax.sql.DataSource;
import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.ExecutionPhase;
...@@ -64,7 +64,7 @@ public class JobDAO { ...@@ -64,7 +64,7 @@ public class JobDAO {
return result; return result;
} }
public void setJobError(String jobId, JobException jobError) { public void setJobError(String jobId, VoSpaceErrorSummarizableException jobError) {
String sql = "UPDATE job SET phase = ?, error_message = ?, error_type = ?,\n" String sql = "UPDATE job SET phase = ?, error_message = ?, error_type = ?,\n"
+ "error_has_detail = ?, error_detail = ?, end_time = NOW()\n" + "error_has_detail = ?, error_detail = ?, end_time = NOW()\n"
...@@ -74,9 +74,9 @@ public class JobDAO { ...@@ -74,9 +74,9 @@ public class JobDAO {
int i = 0; int i = 0;
ps.setObject(++i, ExecutionPhase.ERROR, Types.OTHER); ps.setObject(++i, ExecutionPhase.ERROR, Types.OTHER);
ps.setString(++i, jobError.getMessage()); ps.setString(++i, jobError.getMessage());
ps.setObject(++i, jobError.getType().value(), Types.OTHER); ps.setObject(++i, jobError.getFault().getType().value(), Types.OTHER);
ps.setBoolean(++i, jobError.getErrorDetail() != null); ps.setBoolean(++i, jobError.getDetailMessage() != null);
ps.setString(++i, jobError.getErrorDetail()); ps.setString(++i, jobError.getDetailMessage());
ps.setString(++i, jobId); ps.setString(++i, jobId);
}); });
} }
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
package it.inaf.ia2.transfer.service; package it.inaf.ia2.transfer.service;
import it.inaf.ia2.transfer.auth.TokenPrincipal; import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.exception.InsufficientStorageException;
import it.inaf.ia2.transfer.exception.JobException;
import it.inaf.ia2.transfer.exception.JobException.Type;
import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO; import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.ia2.transfer.persistence.LocationDAO; import it.inaf.ia2.transfer.persistence.LocationDAO;
import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.exception.QuotaExceededException;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
...@@ -140,8 +140,7 @@ public class ArchiveService { ...@@ -140,8 +140,7 @@ public class ArchiveService {
if (!parentDir.exists()) { if (!parentDir.exists()) {
if (!parentDir.mkdirs()) { if (!parentDir.mkdirs()) {
LOG.error("Unable to create directory " + parentDir.getAbsolutePath()); LOG.error("Unable to create directory " + parentDir.getAbsolutePath());
throw new JobException(Type.FATAL, "Internal Fault") throw new InternalFaultException("Unable to create temporary directory for job");
.setErrorDetail("InternalFault: Unable to create temporary directory for job");
} }
} }
...@@ -151,8 +150,7 @@ public class ArchiveService { ...@@ -151,8 +150,7 @@ public class ArchiveService {
if (!archiveFile.createNewFile()) { if (!archiveFile.createNewFile()) {
LOG.error("Unable to create file " + archiveFile.getAbsolutePath()); LOG.error("Unable to create file " + archiveFile.getAbsolutePath());
throw new JobException(Type.FATAL, "Internal Fault") throw new InternalFaultException("Unable to create archive file");
.setErrorDetail("InternalFault: Unable to create archive file");
} }
return archiveFile; return archiveFile;
...@@ -166,7 +164,7 @@ public class ArchiveService { ...@@ -166,7 +164,7 @@ public class ArchiveService {
long usedSpace = Files.walk(parentDir.toPath()).mapToLong(p -> p.toFile().length()).sum(); long usedSpace = Files.walk(parentDir.toPath()).mapToLong(p -> p.toFile().length()).sum();
if (usedSpace > generatedDirMaxSize.toBytes()) { if (usedSpace > generatedDirMaxSize.toBytes()) {
throw new InsufficientStorageException("Archive size limit exceeded."); throw new QuotaExceededException("Archive size limit exceeded.");
} }
} }
...@@ -182,7 +180,8 @@ public class ArchiveService { ...@@ -182,7 +180,8 @@ public class ArchiveService {
} else { } else {
StringBuilder newCommonParent = new StringBuilder(); StringBuilder newCommonParent = new StringBuilder();
boolean same = true; boolean same = true;
for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()); i++) { int lastSlashPos = vosPath.lastIndexOf("/");
for (int i = 0; same && i < Math.min(commonParent.length(), vosPath.length()) && i <= lastSlashPos; i++) {
if (commonParent.charAt(i) == vosPath.charAt(i)) { if (commonParent.charAt(i) == vosPath.charAt(i)) {
newCommonParent.append(commonParent.charAt(i)); newCommonParent.append(commonParent.charAt(i));
} else { } else {
...@@ -277,8 +276,7 @@ public class ArchiveService { ...@@ -277,8 +276,7 @@ public class ArchiveService {
if (baseUrl == null) { if (baseUrl == null) {
LOG.error("Location URL not found for location " + fileInfo.getLocationId()); LOG.error("Location URL not found for location " + fileInfo.getLocationId());
throw new JobException(Type.FATAL, "Internal Fault") throw new InternalFaultException("Unable to retrieve location of file " + fileInfo.getVirtualPath());
.setErrorDetail("InternalFault: Unable to retrieve location of file " + fileInfo.getVirtualPath());
} }
String url = baseUrl + "/" + fileInfo.getVirtualName(); String url = baseUrl + "/" + fileInfo.getVirtualName();
...@@ -307,8 +305,7 @@ public class ArchiveService { ...@@ -307,8 +305,7 @@ public class ArchiveService {
private <O extends OutputStream, E> void writeFileIntoArchive(FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler) throws IOException { private <O extends OutputStream, E> void writeFileIntoArchive(FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler) throws IOException {
if (!authorizationService.isDownloadable(fileInfo, tokenPrincipal)) { if (!authorizationService.isDownloadable(fileInfo, tokenPrincipal)) {
throw new JobException(Type.FATAL, "Permission Denied") throw PermissionDeniedException.forPath(fileInfo.getVirtualPath());
.setErrorDetail("PermissionDenied: " + fileInfo.getVirtualPath());
} }
File file = new File(fileInfo.getOsPath()); File file = new File(fileInfo.getOsPath());
......
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
package it.inaf.ia2.transfer.service; package it.inaf.ia2.transfer.service;
import it.inaf.ia2.transfer.auth.TokenPrincipal; import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.exception.JobException;
import it.inaf.ia2.transfer.exception.JobException.Type;
import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.LocationDAO; import it.inaf.ia2.transfer.persistence.LocationDAO;
import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
...@@ -160,8 +160,7 @@ public class FileCopyService { ...@@ -160,8 +160,7 @@ public class FileCopyService {
if (baseUrl == null) { if (baseUrl == null) {
LOG.error("Location URL not found for location " + sourceFile.getLocationId()); LOG.error("Location URL not found for location " + sourceFile.getLocationId());
throw new JobException(Type.FATAL, "Internal Fault") throw new InternalFaultException("Unable to retrieve location of file " + sourceFile.getVirtualPath());
.setErrorDetail("InternalFault: Unable to retrieve location of file " + sourceFile.getVirtualPath());
} }
String url = baseUrl + "/" + sourceFile.getVirtualName(); String url = baseUrl + "/" + sourceFile.getVirtualName();
...@@ -191,8 +190,7 @@ public class FileCopyService { ...@@ -191,8 +190,7 @@ public class FileCopyService {
private void copyLocalFile(FileInfo sourceFileInfo, private void copyLocalFile(FileInfo sourceFileInfo,
FileInfo destinationFileInfo, TokenPrincipal tokenPrincipal) { FileInfo destinationFileInfo, TokenPrincipal tokenPrincipal) {
if (!authorizationService.isDownloadable(sourceFileInfo, tokenPrincipal)) { if (!authorizationService.isDownloadable(sourceFileInfo, tokenPrincipal)) {
throw new JobException(Type.FATAL, "Permission Denied") throw PermissionDeniedException.forPath(sourceFileInfo.getVirtualPath());
.setErrorDetail("PermissionDenied: " + sourceFileInfo.getVirtualPath());
} }
File file = new File(sourceFileInfo.getOsPath()); File file = new File(sourceFileInfo.getOsPath());
......
...@@ -8,9 +8,9 @@ package it.inaf.ia2.transfer.controller; ...@@ -8,9 +8,9 @@ package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.aa.jwt.TokenParser; import it.inaf.ia2.aa.jwt.TokenParser;
import it.inaf.ia2.transfer.auth.GmsClient; import it.inaf.ia2.transfer.auth.GmsClient;
import it.inaf.ia2.transfer.exception.JobException;
import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO; import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.oats.vospace.exception.VoSpaceErrorSummarizableException;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
...@@ -226,9 +226,9 @@ public class GetFileControllerTest { ...@@ -226,9 +226,9 @@ public class GetFileControllerTest {
.andDo(print()) .andDo(print())
.andReturn().getResolvedException(); .andReturn().getResolvedException();
assertTrue(ex instanceof JobException); assertTrue(ex instanceof VoSpaceErrorSummarizableException);
JobException jobEx = (JobException) ex; VoSpaceErrorSummarizableException jobEx = (VoSpaceErrorSummarizableException) ex;
assertTrue(jobEx.getErrorDetail().contains("not readable"), jobEx.getErrorDetail()); assertTrue(jobEx.getDetailMessage().contains("not readable"), jobEx.getDetailMessage());
} catch (Throwable t) { } catch (Throwable t) {
throw t; throw t;
} finally { } finally {
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
*/ */
package it.inaf.ia2.transfer.controller; package it.inaf.ia2.transfer.controller;
import it.inaf.ia2.transfer.exception.InsufficientStorageException;
import it.inaf.ia2.transfer.persistence.model.FileInfo; import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.transfer.persistence.FileDAO; import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO; import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.oats.vospace.exception.QuotaExceededException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
...@@ -251,7 +251,7 @@ public class PutFileControllerTest { ...@@ -251,7 +251,7 @@ public class PutFileControllerTest {
verify(fileDao, times(1)).getRemainingQuota(eq("/path/to")); verify(fileDao, times(1)).getRemainingQuota(eq("/path/to"));
assertTrue(ex instanceof InsufficientStorageException); assertTrue(ex instanceof QuotaExceededException);
} }
@Test @Test
...@@ -284,7 +284,7 @@ public class PutFileControllerTest { ...@@ -284,7 +284,7 @@ public class PutFileControllerTest {
verify(fileDao, times(1)).getRemainingQuota(eq("/path/to")); verify(fileDao, times(1)).getRemainingQuota(eq("/path/to"));
assertTrue(ex instanceof InsufficientStorageException); assertTrue(ex instanceof QuotaExceededException);
} }
private FileInfo createBaseFileInfo() { private FileInfo createBaseFileInfo() {
......
...@@ -12,9 +12,12 @@ import java.io.File; ...@@ -12,9 +12,12 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.sql.DataSource; import javax.sql.DataSource;
...@@ -24,9 +27,7 @@ import org.springframework.boot.test.context.TestConfiguration; ...@@ -24,9 +27,7 @@ import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource; 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 * Generates a DataSource that can be used for testing DAO classes. It loads an
...@@ -82,15 +83,59 @@ public class DataSourceConfig { ...@@ -82,15 +83,59 @@ public class DataSourceConfig {
assertTrue(scriptDir.exists(), "DAO tests require " + scriptDir.getAbsolutePath() + " to exists.\n" 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"); + "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")); // load all sql files in vospace-file-catalog repo
Arrays.sort(scripts); // sort alphabetically 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) { for (File script : scripts) {
ByteArrayResource scriptResource = replaceDollarQuoting(script.toPath()); String scriptContent = Files.readString(script.toPath());
ScriptUtils.executeSqlScript(conn, scriptResource); 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 { ...@@ -100,9 +145,7 @@ public class DataSourceConfig {
* instead of inside the original files because dollar quoting provides a * instead of inside the original files because dollar quoting provides a
* better visibility. * better visibility.
*/ */
private ByteArrayResource replaceDollarQuoting(Path sqlScriptPath) throws Exception { private String replaceDollarQuoting(String scriptContent) {
String scriptContent = Files.readString(sqlScriptPath);
if (scriptContent.contains("$func$")) { if (scriptContent.contains("$func$")) {
...@@ -114,7 +157,7 @@ public class DataSourceConfig { ...@@ -114,7 +157,7 @@ public class DataSourceConfig {
scriptContent = scriptContent.replace(originalFunction, newFunction); scriptContent = scriptContent.replace(originalFunction, newFunction);
} }
return new ByteArrayResource(scriptContent.getBytes()); return scriptContent;
} }
private String extractFunctionDefinition(String scriptContent) { private String extractFunctionDefinition(String scriptContent) {
......