diff --git a/CHANGELOG.md b/CHANGELOG.md index 499190aeb855837c6029f87e125731d2bd8632b7..6898772f8d7299037aa4806c036e9a0839e74ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] @@ -33,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - NaN Double Values (see #41) +- #14 - Handle files on file system without File entity: Those entries are + returned without ID but with a notice now. + ### Security (in case of vulnerabilities) - TLS is by default restricted to v1.2 and v1.3 now. diff --git a/src/main/java/caosdb/server/database/backend/transaction/GetFileRecordByPath.java b/src/main/java/caosdb/server/database/backend/transaction/GetFileRecordByPath.java index 8e0afcbdc50985f1fa1a939940c05f8429562c90..6366bd2884aca38a0d54eca4a926fcc7e8b9cda4 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/GetFileRecordByPath.java +++ b/src/main/java/caosdb/server/database/backend/transaction/GetFileRecordByPath.java @@ -27,6 +27,7 @@ package caosdb.server.database.backend.transaction; import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.backend.interfaces.GetFileRecordByPathImpl; +import caosdb.server.database.exceptions.EntityDoesNotExistException; import caosdb.server.database.exceptions.TransactionException; import caosdb.server.database.proto.SparseEntity; import org.apache.commons.jcs.access.behavior.ICacheAccess; @@ -62,7 +63,11 @@ public class GetFileRecordByPath extends CacheableBackendTransaction<String, Spa @Override public SparseEntity executeNoCache() throws TransactionException { final GetFileRecordByPathImpl t = getImplementation(GetFileRecordByPathImpl.class); - return t.execute(getKey()); + SparseEntity result = t.execute(getKey()); + if (result == null) { + throw new EntityDoesNotExistException(); + } + return result; } public Integer getId() { diff --git a/src/main/java/caosdb/server/entity/Message.java b/src/main/java/caosdb/server/entity/Message.java index 03e788251620b6c192eb86f1bbd172564a66235f..0ffedb4316fe372e09f3b69ca5824a72f98accaf 100644 --- a/src/main/java/caosdb/server/entity/Message.java +++ b/src/main/java/caosdb/server/entity/Message.java @@ -79,8 +79,8 @@ public class Message extends Exception implements Comparable<Message>, ToElement this(type, code, null, null); } - public Message(final Integer code, final String description) { - this("Message", code, description, null); + public Message(Integer code, String description) { + this(MessageType.Info, code, description); } public Message(final MessageType type, final Integer code, final String description) { @@ -96,6 +96,10 @@ public class Message extends Exception implements Comparable<Message>, ToElement this(type, code, description, null); } + public Message(MessageType type, String description) { + this(type.toString(), 0, description); + } + public Message( final String type, final Integer code, final String description, final String body) { this.code = code; diff --git a/src/main/java/caosdb/server/resource/FileSystemResource.java b/src/main/java/caosdb/server/resource/FileSystemResource.java index 290a88801b4f14401981f9d91a6ba00cf27c12a4..37f0e9ffda3f81b84c6001c5521ba5c8451b473e 100644 --- a/src/main/java/caosdb/server/resource/FileSystemResource.java +++ b/src/main/java/caosdb/server/resource/FileSystemResource.java @@ -26,9 +26,12 @@ import static caosdb.server.FileSystem.getFromFileSystem; import static java.net.URLDecoder.decode; import caosdb.server.database.backend.implementation.MySQL.ConnectionException; +import caosdb.server.database.exceptions.EntityDoesNotExistException; import caosdb.server.database.misc.TransactionBenchmark; import caosdb.server.entity.Entity; import caosdb.server.entity.FileProperties; +import caosdb.server.entity.Message; +import caosdb.server.entity.Message.MessageType; import caosdb.server.entity.RetrieveEntity; import caosdb.server.entity.container.TransactionContainer; import caosdb.server.permissions.EntityPermission; @@ -56,6 +59,11 @@ import org.restlet.representation.Representation; */ public class FileSystemResource extends AbstractCaosDBServerResource { + public static Message ORPHANED_FILE_WARNING = + new Message( + MessageType.Warning, + "Orphaned file. The file is not tracked. This is probably a harmless inconsistency but it might be a sign of other problems."); + /** * Download a File from the CaosDBFileSystem. Only one File per Request. * @@ -124,6 +132,13 @@ public class FileSystemResource extends AbstractCaosDBServerResource { } else { + try { + getEntity(specifier).checkPermission(EntityPermission.RETRIEVE_FILE); + } catch (EntityDoesNotExistException exception) { + // This file in the file system has no corresponding File record. + return error(ServerMessages.NOT_PERMITTED, Status.CLIENT_ERROR_FORBIDDEN); + } + final MediaType mt = MediaType.valueOf(FileUtils.getMimeType(file)); final FileRepresentation ret = new FileRepresentation(file, mt); ret.setDisposition(new Disposition(Disposition.TYPE_ATTACHMENT)); @@ -149,19 +164,39 @@ public class FileSystemResource extends AbstractCaosDBServerResource { return null; } + /** + * Return an element for the given file on a file system. + * + * <p>If there is no File entity for this file, an element without `id` is returned, instead a + * corresponding message is added to the element. + */ Element getFileElement(final String directory, final File file) throws Exception { final Element celem = new Element("file"); - celem.setAttribute( - "id", - getEntityID((directory.endsWith("/") ? directory : directory + "/") + file.getName())); celem.setAttribute("name", file.getName()); + + try { + final String entId = + getEntityID((directory.endsWith("/") ? directory : directory + "/") + file.getName()); + celem.setAttribute("id", entId); + } catch (EntityDoesNotExistException exception) { + // This file in the file system has no corresponding File record. + ORPHANED_FILE_WARNING.addToElement(celem); + } return celem; } protected String getEntityID(final String path) throws Exception { - return getEntity(path).getId().toString(); + final Entity fileEnt = getEntity(path); + return fileEnt.getId().toString(); } + /** + * Throws EntityDoesNotExistException when there is not entity with that path. + * + * @param path + * @return + * @throws Exception + */ private Entity getEntity(final String path) throws Exception { final long t1 = System.currentTimeMillis(); final TransactionContainer c = new TransactionContainer(); @@ -178,19 +213,9 @@ public class FileSystemResource extends AbstractCaosDBServerResource { protected File getFile(final String path) throws Exception { final File ret = getFromFileSystem(path); - if (ret != null && ret.isFile()) { - checkPermissions(path); - } return ret; } - private final void checkPermissions(final String path) throws Exception { - final long t1 = System.currentTimeMillis(); - getEntity(path).checkPermission(EntityPermission.RETRIEVE_FILE); - final long t2 = System.currentTimeMillis(); - getBenchmark().addMeasurement(this.getClass().getSimpleName() + ".checkPermissions", t2 - t1); - } - @Override protected Representation httpPostInChildClass(final Representation entity) throws ConnectionException, JDOMException {