Newer
Older
/*
* This file is part of vospace-file-service
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.inaf.ia2.transfer.service;
import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.oats.vospace.exception.QuotaExceededException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PutFileService {
private static final Logger LOG = LoggerFactory.getLogger(PutFileService.class);
@Autowired
private FileDAO fileDAO;
public synchronized void makeFoldersPath(File file) {
/**
* This method must be synchronized, to avoid concurrency issues when
* multiple files are uploaded to a new folder in parallel.
*/
if (!file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalStateException("Unable to create parent folder: " + file.getParentFile().getAbsolutePath());
}
}
}
public void copyLocalFile(FileInfo sourceFileInfo, FileInfo destinationFileInfo, Long remainingQuota) {
File destinationFile = this.prepareDestination(destinationFileInfo);
File sourceFile = new File(sourceFileInfo.getOsPath());
try {
Files.copy(sourceFile.toPath(), destinationFile.toPath());
this.finalizeFile(sourceFileInfo, destinationFileInfo, destinationFile, remainingQuota);
destinationFile.delete();
throw new UncheckedIOException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public void storeFileFromInputStream(FileInfo destinationFileInfo,
InputStream is, Long remainingQuota) {
this.storeFileFromInputStream(null, destinationFileInfo, is, remainingQuota);
}
public void storeFileFromInputStream(FileInfo sourceFileInfo, FileInfo destinationFileInfo,
InputStream is, Long remainingQuota) {
File destinationFile = this.prepareDestination(destinationFileInfo);
try {
Files.copy(is, destinationFile.toPath());
this.finalizeFile(sourceFileInfo, destinationFileInfo, destinationFile, remainingQuota);
} catch (IOException e) {
destinationFile.delete();
throw new UncheckedIOException(e);
} catch (NoSuchAlgorithmException e) {
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
destinationFile.delete();
throw new RuntimeException(e);
}
}
private File prepareDestination(FileInfo destinationFileInfo) {
File file = new File(destinationFileInfo.getOsPath());
makeFoldersPath(file);
String originalFileName = file.getName();
file = getEmptyFile(file, 1);
if (!originalFileName.equals(file.getName())) {
fileDAO.setOsName(destinationFileInfo.getNodeId(), file.getName());
}
return file;
}
private Long checkQuota(File destinationFile, Long remainingQuota) throws IOException {
Long fileSize = Files.size(destinationFile.toPath());
// Quota limit is checked again to handle cases where MultipartFile is not used
if (remainingQuota != null && fileSize > remainingQuota) {
destinationFile.delete();
throw new QuotaExceededException("Path: " + destinationFile.toPath().toString());
}
return fileSize;
}
private void finalizeFile(FileInfo sourceFileInfo, FileInfo destinationFileInfo,
File destinationFile, Long remainingQuota) throws IOException, NoSuchAlgorithmException {
Long fileSize = this.checkQuota(destinationFile, remainingQuota);
if (destinationFileInfo.getContentType() == null) {
destinationFileInfo.setContentType(Files.probeContentType(destinationFile.toPath()));
}
String md5Checksum = makeMD5Checksum(destinationFile);
// TODO: discuss if mismatches lead to taking actions
if (sourceFileInfo != null) {
if (!Objects.equals(sourceFileInfo.getContentLength(), fileSize)) {
LOG.warn("Destination file {} size mismatch with source", destinationFile.toPath().toString());
}
if (sourceFileInfo.getContentType() != null &&
!sourceFileInfo.getContentType().equals(destinationFileInfo.getContentType())) {
LOG.warn("Destination file {} content type mismatch with source {} {}", destinationFile.toPath().toString(),
destinationFileInfo.getContentType(), sourceFileInfo.getContentType());
}
if (sourceFileInfo.getContentMd5() != null &&
!sourceFileInfo.getContentMd5().equals(md5Checksum)) {
LOG.warn("Destination file {} md5 mismatch with source {} {}", destinationFile.toPath().toString(),
destinationFileInfo.getContentMd5(), sourceFileInfo.getContentMd5() );
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
}
}
fileDAO.updateFileAttributes(destinationFileInfo.getNodeId(),
destinationFileInfo.getContentType(),
destinationFileInfo.getContentEncoding(),
fileSize,
md5Checksum);
}
/**
* Handles duplicate file uploads generating a new non existent path. This
* is necessary in some edge cases, like when a file has been renamed in
* VOSpace only but the original file on disk still has the old name or if a
* file has been marked for deletion and a file with the same name is
* uploaded before the cleanup.
*/
private File getEmptyFile(File file, int index) {
if (file.exists()) {
String fileName = file.getName();
String nameWithoutExtension;
String extension = null;
if (fileName.contains(".")) {
nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf("."));
extension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
} else {
nameWithoutExtension = fileName;
}
Pattern pattern = Pattern.compile("(.*?)-(\\d+)");
Matcher matcher = pattern.matcher(nameWithoutExtension);
if (matcher.matches()) {
nameWithoutExtension = matcher.group(1);
int fileIndex = Integer.parseInt(matcher.group(2));
index = fileIndex + 1;
}
String newName = nameWithoutExtension + "-" + index;
if (extension != null) {
newName += "." + extension;
}
File newFile = file.toPath().getParent().resolve(newName).toFile();
return getEmptyFile(newFile, index + 1);
}
return file;
}
private String makeMD5Checksum(File file) throws NoSuchAlgorithmException, IOException {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(Files.readAllBytes(file.toPath()));
byte[] digest = md.digest();
String checksum = DatatypeConverter.printHexBinary(digest);
return checksum;
}
}