diff --git a/README.md b/README.md index f4c49631..7315a647 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,14 @@ Complete information is available in the documentation at [https://eclipse.githu ### Run Jifa Locally with a Single Command ```shell -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/HEAD/scripts/jifa.sh)" +# Default service address is at http://localhost:8102 +curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/main/jifa.sh | bash + +# Change the server port +curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/main/jifa.sh | bash -s -- -p + +# Analyze local files +curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/main/jifa.sh | bash -s -- ... ``` Note: Please make sure that Docker is installed. diff --git a/README_zh.md b/README_zh.md index 53bfcf1e..bd12d938 100644 --- a/README_zh.md +++ b/README_zh.md @@ -30,7 +30,14 @@ Eclipse Jifa 是一个致力于帮助 Java 研发人员排查应用中常见问 ### 一个命令运行 Jifa ```shell -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/HEAD/scripts/jifa.sh)" +# 默认服务地址是 http://localhost:8102 +curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/main/jifa.sh | bash + +# 修改服务端口 +curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/main/jifa.sh | bash -s -- -p + +# 分析本地文件 +curl -fsSL https://raw.githubusercontent.com/eclipse/jifa/main/jifa.sh | bash -s -- ... ``` 注:本地环境需要安装 docker diff --git a/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/GCLogAnalysisApiExecutor.java b/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/GCLogAnalysisApiExecutor.java index 7e254da5..d5c9e41e 100644 --- a/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/GCLogAnalysisApiExecutor.java +++ b/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/GCLogAnalysisApiExecutor.java @@ -16,9 +16,15 @@ import org.eclipse.jifa.analysis.listener.ProgressListener; import org.eclipse.jifa.gclog.model.GCModel; import org.eclipse.jifa.gclog.parser.GCLogAnalyzer; +import org.eclipse.jifa.gclog.parser.GCLogParserFactory; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; import java.nio.file.Path; import java.util.Map; +import java.util.function.Predicate; public class GCLogAnalysisApiExecutor extends AbstractApiExecutor { @@ -31,4 +37,17 @@ protected GCModel buildAnalyzer(Path target, Map options, Progre public String namespace() { return "gc-log"; } + + @Override + public Predicate matcher() { + return bytes -> { + GCLogParserFactory factory = new GCLogParserFactory(); + try (BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(new ByteArrayInputStream(bytes)))) { + return factory.getParser(bufferedReader) != null; + } catch (IOException e) { + return false; + } + }; + } } diff --git a/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/parser/GCLogAnalyzer.java b/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/parser/GCLogAnalyzer.java index 86dfe34b..882d39e3 100644 --- a/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/parser/GCLogAnalyzer.java +++ b/analysis/gc-log/src/main/java/org/eclipse/jifa/gclog/parser/GCLogAnalyzer.java @@ -26,8 +26,8 @@ @Slf4j public class GCLogAnalyzer { - private File file; - private ProgressListener listener; + private final File file; + private final ProgressListener listener; private final int MAX_SINGLE_LINE_LENGTH = 2048; // max length in hotspot @@ -36,10 +36,6 @@ public GCLogAnalyzer(File file, ProgressListener listener) { this.listener = listener; } - public GCLogAnalyzer(File file) { - this(file, new DefaultProgressListener()); - } - public GCModel parse() throws Exception { BufferedReader br = null; try { @@ -48,6 +44,7 @@ public GCModel parse() throws Exception { listener.sendUserMessage(ProgressListener.Level.INFO, "Deciding gc log format.", null); // decide log format + GCLogParserFactory logParserFactory = new GCLogParserFactory(); br.mark(GCLogParserFactory.MAX_ATTEMPT_LINE * MAX_SINGLE_LINE_LENGTH); GCLogParser parser = logParserFactory.getParser(br); diff --git a/analysis/heap-dump/provider/src/main/java/org/eclipse/jifa/hdp/provider/HeapDumpAnalysisApiExecutor.java b/analysis/heap-dump/provider/src/main/java/org/eclipse/jifa/hdp/provider/HeapDumpAnalysisApiExecutor.java index 34c2b6f8..0193bade 100644 --- a/analysis/heap-dump/provider/src/main/java/org/eclipse/jifa/hdp/provider/HeapDumpAnalysisApiExecutor.java +++ b/analysis/heap-dump/provider/src/main/java/org/eclipse/jifa/hdp/provider/HeapDumpAnalysisApiExecutor.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.Objects; import java.util.ServiceLoader; +import java.util.function.Predicate; @Slf4j public class HeapDumpAnalysisApiExecutor extends AbstractApiExecutor { @@ -156,6 +157,18 @@ public String namespace() { return "heap-dump"; } + @Override + public Predicate matcher() { + return new Predicate<>() { + static final String HEADER = "JAVA PROFILE 1.0.2"; + + @Override + public boolean test(byte[] bytes) { + return bytes.length > HEADER.length() && new String(bytes, 0, HEADER.length()).equals(HEADER); + } + }; + } + @Override public boolean needOptionsForAnalysis(Path target) { return !indexFile(target).exists() && !errorLogFile(target).exists() && !isActive(target); diff --git a/analysis/src/main/java/org/eclipse/jifa/analysis/ApiExecutor.java b/analysis/src/main/java/org/eclipse/jifa/analysis/ApiExecutor.java index 91ecd357..c2709608 100644 --- a/analysis/src/main/java/org/eclipse/jifa/analysis/ApiExecutor.java +++ b/analysis/src/main/java/org/eclipse/jifa/analysis/ApiExecutor.java @@ -14,6 +14,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; /** * Analysis api executor @@ -37,4 +38,11 @@ public interface ApiExecutor { * @return result */ CompletableFuture execute(ExecutionContext context); + + /** + * @return a matcher to tell the byte array is supported by this executor, default is null + */ + default Predicate matcher() { + return null; + } } diff --git a/analysis/src/main/java/org/eclipse/jifa/analysis/ApiService.java b/analysis/src/main/java/org/eclipse/jifa/analysis/ApiService.java index afe227a9..f9e7268d 100644 --- a/analysis/src/main/java/org/eclipse/jifa/analysis/ApiService.java +++ b/analysis/src/main/java/org/eclipse/jifa/analysis/ApiService.java @@ -42,4 +42,10 @@ public interface ApiService { static ApiService getInstance() { return ApiServiceImpl.instance(); } + + /** + * @param content the content + * @return the relevant namespace if the payload may be analyzed, otherwise null + */ + String deduceNamespaceByContent(byte[] content); } diff --git a/analysis/src/main/java/org/eclipse/jifa/analysis/ApiServiceImpl.java b/analysis/src/main/java/org/eclipse/jifa/analysis/ApiServiceImpl.java index 86d3479b..71365b2e 100644 --- a/analysis/src/main/java/org/eclipse/jifa/analysis/ApiServiceImpl.java +++ b/analysis/src/main/java/org/eclipse/jifa/analysis/ApiServiceImpl.java @@ -21,6 +21,7 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; public class ApiServiceImpl implements ApiService { @@ -28,6 +29,8 @@ public class ApiServiceImpl implements ApiService { private Map executors; + private Map> matchers; + private ApiServiceImpl() { loadExecutors(); } @@ -35,6 +38,7 @@ private ApiServiceImpl() { private void loadExecutors() { Map> apis = new HashMap<>(); Map executors = new HashMap<>(); + Map> matchers = new HashMap<>(); for (ApiExecutor executor : ServiceLoader.load(ApiExecutor.class)) { String namespace = executor.namespace(); @@ -42,10 +46,15 @@ private void loadExecutors() { apis.put(namespace, executor.apis()); executors.put(namespace, executor); + Predicate matcher = executor.matcher(); + if (matcher != null) { + matchers.put(namespace, matcher); + } } this.apis = Collections.unmodifiableMap(apis); this.executors = Collections.unmodifiableMap(executors); + this.matchers = Collections.unmodifiableMap(matchers); } @Override @@ -64,6 +73,16 @@ public CompletableFuture execute(Path target, String namespace, String api, O return executor.execute(new ExecutionContext(target, api, arguments)); } + @Override + public String deduceNamespaceByContent(byte[] content) { + for (Map.Entry> entry : matchers.entrySet()) { + if (entry.getValue().test(content)) { + return entry.getKey(); + } + } + return null; + } + static ApiService instance() { return Holder.INSTANCE; } diff --git a/analysis/thread-dump/src/main/java/org/eclipse/jifa/tda/ThreadDumpAnalysisApiExecutor.java b/analysis/thread-dump/src/main/java/org/eclipse/jifa/tda/ThreadDumpAnalysisApiExecutor.java index d14f7da9..abc2756b 100644 --- a/analysis/thread-dump/src/main/java/org/eclipse/jifa/tda/ThreadDumpAnalysisApiExecutor.java +++ b/analysis/thread-dump/src/main/java/org/eclipse/jifa/tda/ThreadDumpAnalysisApiExecutor.java @@ -19,6 +19,7 @@ import java.io.File; import java.nio.file.Path; import java.util.Map; +import java.util.function.Predicate; @Slf4j public class ThreadDumpAnalysisApiExecutor extends AbstractApiExecutor { @@ -43,4 +44,16 @@ public void clean(Path target) { public String namespace() { return "thread-dump"; } + + @Override + public Predicate matcher() { + return new Predicate<>() { + static final String HEADER = "Full thread dump"; + + @Override + public boolean test(byte[] bytes) { + return bytes.length > 20 + HEADER.length() && new String(bytes, 20, HEADER.length()).equals(HEADER); + } + }; + } } diff --git a/scripts/jifa.sh b/jifa.sh similarity index 54% rename from scripts/jifa.sh rename to jifa.sh index d5f6af15..6dc3d22d 100644 --- a/scripts/jifa.sh +++ b/jifa.sh @@ -15,6 +15,9 @@ set -eu #TAG="latest" TAG=0.2.0-SNAPSHOT PORT="8102" +MOUNTS="" +INPUT_FILES="" +INPUT_FILE_COUNT=0 check_docker() { if ! command -v docker &>/dev/null; then @@ -23,9 +26,9 @@ check_docker() { fi } -run_jifa() { +launch_jifa() { check_docker - docker run -p ${PORT}:8102 eclipsejifa/jifa:${TAG} + docker run -p ${PORT}:${PORT} $MOUNTS eclipsejifa/jifa:${TAG} $INPUT_FILES } while [ $# -gt 0 ]; do @@ -38,8 +41,21 @@ while [ $# -gt 0 ]; do PORT=$2 shift ;; + *) + ABSOLUTE_PATH=$(realpath "$1") + if [ ! -f "$ABSOLUTE_PATH" ]; then + echo "$1 does not exist or is not a regular file" + exit 1 + fi + + FILE_NAME=$(basename "$ABSOLUTE_PATH") + + MOUNTS="$MOUNTS -v $ABSOLUTE_PATH:/input-file-$INPUT_FILE_COUNT/$FILE_NAME" + INPUT_FILES="$INPUT_FILES --jifa.input-files[$INPUT_FILE_COUNT]=/input-file-$INPUT_FILE_COUNT/$FILE_NAME" + INPUT_FILE_COUNT=$((INPUT_FILE_COUNT+1)) + ;; esac shift done -run_jifa +launch_jifa \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/jifa/server/Configuration.java b/server/src/main/java/org/eclipse/jifa/server/Configuration.java index c0a8994d..f21cae7e 100644 --- a/server/src/main/java/org/eclipse/jifa/server/Configuration.java +++ b/server/src/main/java/org/eclipse/jifa/server/Configuration.java @@ -19,6 +19,7 @@ import jakarta.validation.constraints.PositiveOrZero; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.eclipse.jifa.common.util.Validate; import org.eclipse.jifa.server.enums.Role; import org.eclipse.jifa.server.enums.SchedulingStrategy; @@ -37,6 +38,7 @@ @Validated @Getter @Setter +@Slf4j public class Configuration { /** @@ -113,6 +115,11 @@ public class Configuration { */ private String rootPassword = "password"; + /** + * Input files is some specified files that will be added automatically when starting. + */ + private Path[] inputFiles; + @PostConstruct private void init() { if (role == Role.MASTER) { diff --git a/server/src/main/java/org/eclipse/jifa/server/Launcher.java b/server/src/main/java/org/eclipse/jifa/server/Launcher.java index 311370bc..e0a66b75 100644 --- a/server/src/main/java/org/eclipse/jifa/server/Launcher.java +++ b/server/src/main/java/org/eclipse/jifa/server/Launcher.java @@ -12,12 +12,23 @@ ********************************************************************************/ package org.eclipse.jifa.server; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.jifa.server.enums.FileType; +import org.eclipse.jifa.server.enums.Role; +import org.eclipse.jifa.server.service.AnalysisApiService; +import org.eclipse.jifa.server.service.FileService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.EnableTransactionManagement; +import java.io.IOException; +import java.nio.file.Path; + @SpringBootApplication @EnableConfigurationProperties({Configuration.class}) @EnableTransactionManagement @@ -27,4 +38,47 @@ public class Launcher { public static void main(String[] args) { SpringApplication.run(Launcher.class, args); } + + @Component + @Slf4j + static class ReadyListener extends ConfigurationAccessor { + + private final AnalysisApiService analysisApiService; + + private final FileService fileService; + + ReadyListener(AnalysisApiService analysisApiService, FileService fileService) { + this.analysisApiService = analysisApiService; + this.fileService = fileService; + } + + @EventListener(ApplicationReadyEvent.class) + public void fireReadyEvent() { + //noinspection HttpUrlsUsage + log.info("Jifa Server: http://{}:{}", "localhost", config.getPort()); + + if (config.getRole() == Role.STANDALONE_WORKER) { + Path[] paths = config.getInputFiles(); + if (paths != null) { + for (Path path : paths) { + try { + FileType type = analysisApiService.deduceFileType(path); + if (type != null) { + String uniqueName = fileService.handleLocalFileRequest(type, path); + //noinspection HttpUrlsUsage + log.info("{}: http://{}:{}/{}/{}", + path.getFileName(), + "localhost", + config.getPort(), + type.getAnalysisUrlPath(), + uniqueName); + } + } catch (IOException e) { + log.error("Failed to handle input file '{}': {}", path, e.getMessage()); + } + } + } + } + } + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/jifa/server/enums/FileType.java b/server/src/main/java/org/eclipse/jifa/server/enums/FileType.java index 5d5103f4..f6d0e50f 100644 --- a/server/src/main/java/org/eclipse/jifa/server/enums/FileType.java +++ b/server/src/main/java/org/eclipse/jifa/server/enums/FileType.java @@ -16,19 +16,22 @@ public enum FileType { - HEAP_DUMP("heap-dump", "heap-dump"), + HEAP_DUMP("heap-dump"), - GC_LOG("gc-log", "gc-log"), + GC_LOG("gc-log"), - THREAD_DUMP("thread-dump", "thread-dump"); + THREAD_DUMP("thread-dump" ); private final String storageDirectoryName; private final String apiNamespace; - FileType(String storageDirectoryName, String apiNamespace) { - this.storageDirectoryName = storageDirectoryName; - this.apiNamespace = apiNamespace; + private final String analysisUrlPath; + + FileType(String tag) { + storageDirectoryName = tag; + apiNamespace = tag; + analysisUrlPath = tag + "-analysis"; } public String getStorageDirectoryName() { @@ -39,6 +42,10 @@ public String getApiNamespace() { return apiNamespace; } + public String getAnalysisUrlPath() { + return analysisUrlPath; + } + public static FileType getByApiNamespace(String expected) { for (FileType type : FileType.values()) { if (expected.equals(type.apiNamespace)) { diff --git a/server/src/main/java/org/eclipse/jifa/server/service/AnalysisApiService.java b/server/src/main/java/org/eclipse/jifa/server/service/AnalysisApiService.java index f07e6b21..265eccb1 100644 --- a/server/src/main/java/org/eclipse/jifa/server/service/AnalysisApiService.java +++ b/server/src/main/java/org/eclipse/jifa/server/service/AnalysisApiService.java @@ -13,10 +13,14 @@ package org.eclipse.jifa.server.service; import org.eclipse.jifa.server.domain.dto.AnalysisApiRequest; +import org.eclipse.jifa.server.enums.FileType; +import java.nio.file.Path; import java.util.concurrent.CompletableFuture; public interface AnalysisApiService { CompletableFuture invoke(AnalysisApiRequest request); + + FileType deduceFileType(Path path); } diff --git a/server/src/main/java/org/eclipse/jifa/server/service/FileService.java b/server/src/main/java/org/eclipse/jifa/server/service/FileService.java index 9eaee600..2fd57b84 100644 --- a/server/src/main/java/org/eclipse/jifa/server/service/FileService.java +++ b/server/src/main/java/org/eclipse/jifa/server/service/FileService.java @@ -21,6 +21,9 @@ import org.eclipse.jifa.server.enums.FileType; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.nio.file.Path; + public interface FileService { PageView getUserFileViews(FileType type, int page, int pageSize); @@ -37,6 +40,8 @@ public interface FileService { long handleUploadRequest(FileType type, MultipartFile file) throws Throwable; + String handleLocalFileRequest(FileType type, Path path) throws IOException; + NamedResource handleDownloadRequest(long fileId) throws Throwable; FileEntity getFileByUniqueName(String uniqueName, FileType expectedFileType); diff --git a/server/src/main/java/org/eclipse/jifa/server/service/StorageService.java b/server/src/main/java/org/eclipse/jifa/server/service/StorageService.java index 2b323147..93a39b98 100644 --- a/server/src/main/java/org/eclipse/jifa/server/service/StorageService.java +++ b/server/src/main/java/org/eclipse/jifa/server/service/StorageService.java @@ -30,7 +30,9 @@ public interface StorageService { void handleTransfer(FileTransferRequest request, String destFilename, FileTransferListener listener); - long handleUpload(FileType type, MultipartFile file, String destFilename) throws Throwable; + long handleUpload(FileType type, MultipartFile file, String destFilename) throws IOException; + + void handleLocalFile(FileType type, Path path, String destFilename) throws IOException; void scavenge(FileType type, String name); diff --git a/server/src/main/java/org/eclipse/jifa/server/service/impl/AnalysisApiServiceImpl.java b/server/src/main/java/org/eclipse/jifa/server/service/impl/AnalysisApiServiceImpl.java index 08a50f61..f1cbd1b0 100644 --- a/server/src/main/java/org/eclipse/jifa/server/service/impl/AnalysisApiServiceImpl.java +++ b/server/src/main/java/org/eclipse/jifa/server/service/impl/AnalysisApiServiceImpl.java @@ -14,6 +14,7 @@ import jakarta.annotation.Nullable; import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; import org.eclipse.jifa.analysis.Api; import org.eclipse.jifa.analysis.ApiService; import org.eclipse.jifa.server.ConfigurationAccessor; @@ -32,6 +33,9 @@ import org.eclipse.jifa.server.support.AnalysisApiArgumentResolverFactory; import org.springframework.stereotype.Service; +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -45,6 +49,7 @@ import static org.eclipse.jifa.server.enums.ServerErrorCode.UNSUPPORTED_NAMESPACE; @Service +@Slf4j public class AnalysisApiServiceImpl extends ConfigurationAccessor implements AnalysisApiService { private final FileService fileService; @@ -59,7 +64,6 @@ public class AnalysisApiServiceImpl extends ConfigurationAccessor implements Ana private Map> apis; - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public AnalysisApiServiceImpl(FileService fileService, @Nullable WorkerService workerService, @Nullable CurrentElasticWorker currentElasticWorker, @@ -99,6 +103,7 @@ public final CompletableFuture invoke(AnalysisApiRequest request) { FileEntity file = fileService.getFileByUniqueName(request.target(), FileType.getByApiNamespace(request.namespace())); if (isMaster()) { + assert workerService != null; WorkerEntity worker = workerService.resolveForAnalysisApiRequest(file); return workerService.sendRequest(worker, createPostRequest(Constant.HTTP_ANALYSIS_API_MAPPING, request, byte[].class)); } @@ -107,6 +112,7 @@ public final CompletableFuture invoke(AnalysisApiRequest request) { AnalysisApiArgumentResolver resolver = ofNullable(ofNullable(apis.get(namespace)).orElseThrow(() -> CE(UNSUPPORTED_NAMESPACE)) .get(api)).orElseThrow(() -> CE(UNSUPPORTED_API)); + assert storageService != null; Path targetPath = storageService.locationOf(file.getType(), file.getUniqueName()); Object[] args = resolver.resolve(new AnalysisApiArgumentContext(file.getType(), targetPath, request.parameters(), fileService, storageService)); @@ -128,4 +134,34 @@ public final CompletableFuture invoke(AnalysisApiRequest request) { throw t; } } + + @Override + public FileType deduceFileType(Path path) { + if (!Files.exists(path)) { + log.warn("File '{}' does not exist", path); + return null; + } + if (!Files.isRegularFile(path)) { + log.warn("File '{}' is not a regular file", path); + return null; + } + + File file = path.toFile(); + byte[] content = new byte[(int) Math.min(file.length(), 16 * 1024)]; + + try { + try (FileInputStream input = new FileInputStream(file)) { + //noinspection ResultOfMethodCallIgnored + input.read(content); + } + String namespace = apiService.deduceNamespaceByContent(content); + if (namespace == null) { + log.warn("Failed to deduce the type of file '{}'", path); + } + return FileType.getByApiNamespace(namespace); + } catch (Exception e) { + log.warn("Failed to deduce the type of file '{}': {}", path, e.getMessage()); + return null; + } + } } diff --git a/server/src/main/java/org/eclipse/jifa/server/service/impl/FileServiceImpl.java b/server/src/main/java/org/eclipse/jifa/server/service/impl/FileServiceImpl.java index 2de2d6ef..0cd1b7b3 100644 --- a/server/src/main/java/org/eclipse/jifa/server/service/impl/FileServiceImpl.java +++ b/server/src/main/java/org/eclipse/jifa/server/service/impl/FileServiceImpl.java @@ -53,6 +53,10 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.Instant; import java.util.List; import java.util.UUID; @@ -70,6 +74,7 @@ import static org.eclipse.jifa.server.enums.ServerErrorCode.FILE_TYPE_MISMATCH; import static org.eclipse.jifa.server.enums.ServerErrorCode.UNAVAILABLE; +@SuppressWarnings("DataFlowIssue") @Component @Slf4j public class FileServiceImpl extends ConfigurationAccessor implements FileService { @@ -94,7 +99,6 @@ public class FileServiceImpl extends ConfigurationAccessor implements FileServic private final TaskScheduler taskScheduler; - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public FileServiceImpl(TransactionTemplate transactionTemplate, UserService userService, FileRepo fileRepo, @@ -241,6 +245,26 @@ public long handleUploadRequest(FileType type, MultipartFile file) throws Throwa }); } + @Override + public String handleLocalFileRequest(FileType type, Path path) throws IOException { + mustBe(STANDALONE_WORKER); + + Validate.isTrue(Files.exists(path) && Files.isRegularFile(path)); + + String uniqueName = generateFileUniqueName(); + storageService.handleLocalFile(type, path, uniqueName); + + FileEntity newFile = new FileEntity(); + File file = path.toFile(); + newFile.setUniqueName(uniqueName); + newFile.setOriginalName(file.getName()); + newFile.setType(type); + newFile.setSize(file.lastModified()); + fileRepo.save(newFile); + + return uniqueName; + } + @Override public NamedResource handleDownloadRequest(long fileId) throws Throwable { mustNotBe(ELASTIC_WORKER); diff --git a/server/src/main/java/org/eclipse/jifa/server/service/impl/StorageServiceImpl.java b/server/src/main/java/org/eclipse/jifa/server/service/impl/StorageServiceImpl.java index fc7ecea5..975e54f2 100644 --- a/server/src/main/java/org/eclipse/jifa/server/service/impl/StorageServiceImpl.java +++ b/server/src/main/java/org/eclipse/jifa/server/service/impl/StorageServiceImpl.java @@ -164,14 +164,25 @@ public void handleTransfer(FileTransferRequest request, String destFilename, Fil } @Override - public long handleUpload(FileType type, MultipartFile file, String destFilename) throws Throwable { + public long handleUpload(FileType type, MultipartFile file, String destFilename) throws IOException { Path destination = provision(type, destFilename); try { file.transferTo(destination); return destination.toFile().length(); - } catch (Throwable t) { + } catch (IOException e) { scavenge(type, destFilename); - throw t; + throw e; + } + } + + @Override + public void handleLocalFile(FileType type, Path path, String destFilename) throws IOException { + Path destination = provision(type, destFilename); + try { + Files.copy(path, destination); + } catch (IOException e) { + scavenge(type, destFilename); + throw e; } }