diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7161569a7e43017869d9bb0025c35388db6c3623..1802b5bb9fe8cd2f2a21643ee44a380ed8621c37 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,7 +59,7 @@ trigger_build: -F "variables[SERVER]=$CI_COMMIT_REF_NAME" -F "variables[TriggerdBy]=SERVER" -F "variables[TriggerdByHash]=$CI_COMMIT_SHORT_SHA" - -F ref=master https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline + -F ref=dev https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline # Build a docker image in which tests for this repository can run build-testenv: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..4d8a87996586347cb32457bfbb06e524f380098b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,42 @@ +# Changelog + +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/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Scripting is simplified by adding a `home` directory, of which a copy is + created for each called script and set as the `HOME` environment variable. + +### Changed + +- + + +### Deprecated + +- + + +### Fixed + +- #46 - Server-side scripting failed as an unprivileged user because the was no + writable home directory. + +- NaN Double Values (see #41) + + + +### Security (in case of vulnerabilities) + +## [0.1.0] - 2018-10-09 + +Tag `v0.1` - Commit 3b17b49 + +### Added + +- everything diff --git a/caosdb-webui b/caosdb-webui index 8c43e0ed13d4e2baf34b80ac2e98d0494e3272a3..b4950db910523029c3b1f9a268e84576b2584fbf 160000 --- a/caosdb-webui +++ b/caosdb-webui @@ -1 +1 @@ -Subproject commit 8c43e0ed13d4e2baf34b80ac2e98d0494e3272a3 +Subproject commit b4950db910523029c3b1f9a268e84576b2584fbf diff --git a/conf/core/cache.ccf b/conf/core/cache.ccf index 87d1338a5cf70da9b551744bc154190965d4bb46..672c798d126e9c759e0c7a59a81702733aa4e7b3 100644 --- a/conf/core/cache.ccf +++ b/conf/core/cache.ccf @@ -1,3 +1,35 @@ +# default caching options jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes jcs.default.cacheattributes.MaxObjects=1000 -jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache \ No newline at end of file +jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes + +# Caching of the backend transactions +jcs.region.BACKEND_UserRoles.cacheattributes.MaxObjects=100 +jcs.region.BACKEND_Users.cacheattributes.MaxObjects=100 +jcs.region.BACKEND_PermissionRules.cacheattributes.MaxObjects=1000 + +jcs.region.BACKEND_SparseEntities.cacheattributes.MaxObjects=1000 +jcs.region.BACKEND_EntityProperties.cacheattributes.MaxObjects=1000 +jcs.region.BACKEND_EntityParents.cacheattributes.MaxObjects=1000 +jcs.region.BACKEND_SparseFileRecordsByPath.cacheattributes.MaxObjects=1000 + +jcs.region.BACKEND_JobRules.cacheattributes.MaxObjects=100 + + +# PAM UserSource Caching: Cached Items expire after 60 seconds if they are not requested (idle) and after 600 seconds max. +# PAM_UnixUserGroups +jcs.region.PAM_UnixUserGroups.elementattributes.IsEternal=false +jcs.region.PAM_UnixUserGroups.elementattributes.MaxLife=600 +jcs.region.PAM_UnixUserGroups.elementattributes.IdleTime=60 + +# PAM_UnixUserExists +jcs.region.PAM_UnixUserExists.elementattributes.IsEternal=false +jcs.region.PAM_UnixUserExists.elementattributes.MaxLife=600 +jcs.region.PAM_UnixUserExists.elementattributes.IdleTime=60 + +# PAM_Authentication +jcs.region.PAM_Authentication.elementattributes.IsEternal=false +jcs.region.PAM_Authentication.elementattributes.MaxLife=600 +jcs.region.PAM_Authentication.elementattributes.IdleTime=60 + + diff --git a/conf/core/log4j2-default.properties b/conf/core/log4j2-default.properties index ab7aed268e1e1e1a4088796d68b9e065086b5958..b1697a73fe6703850865406e1441947614d00f47 100644 --- a/conf/core/log4j2-default.properties +++ b/conf/core/log4j2-default.properties @@ -12,6 +12,11 @@ appender.stderr.name = stderr appender.stderr.layout.type = PatternLayout appender.stderr.layout.pattern = [%d{yy-MMM-dd HH:mm:ss:SSS}] [%p] [%c{1}:%L] - %m%n appender.stderr.target = SYSTEM_ERR +appender.stderr.filter.threshold.type = ThresholdFilter +appender.stderr.filter.threshold.level = INFO +appender.stderr.filter.threshold.onMatch = ACCEPT +appender.stderr.filter.threshold.onMismatch = DENY + # ${LOG_DIR}/request_errors/...log appender.request_errors.type = RollingRandomAccessFile diff --git a/conf/core/server.conf b/conf/core/server.conf index 8e71c561705fda384a2a87b2b45c3aa8ccfb8ea4..882c4453f1dd2f11eb36bf6a534e0ba26b249c62 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -3,6 +3,7 @@ SERVER_OWNER= SERVER_NAME=CaosDB Server SERVER_SIDE_SCRIPTING_BIN_DIR=./scripting/bin/ SERVER_SIDE_SCRIPTING_WORKING_DIR=./scripting/working/ +SERVER_SIDE_SCRIPTING_HOME_DIR=./scripting/home/ FILE_SYSTEM_ROOT=./CaosDBFileSystem/FileSystemRoot/ DROP_OFF_BOX=./CaosDBFileSystem/DropOffBox/ TMP_FILES=./CaosDBFileSystem/TMP/ @@ -49,13 +50,6 @@ BUGTRACKER_URI= TRANSACTION_BENCHMARK_ENABLED=true CACHE_CONF_LOC=./conf/core/cache.ccf -RULES_CACHE_CAPACITY=100 -SPARSE_ENTITY_CACHE_CAPACITY=1000 -PROPERTIES_CACHE_CAPACITY=1000 -PARENTS_CACHE_CAPACITY=1000 -USER_ACCOUNT_CACHE_CAPACITY=100 -GROUP_CACHE_CAPACITY=100 - INSERT_FILES_IN_DIR_ALLOWED_DIRS= SUDO_PASSWORD= @@ -69,4 +63,4 @@ CERTIFICATES_KEY_PASSWORD= CERTIFICATES_KEY_STORE_PATH= CERTIFICATES_KEY_STORE_PASSWORD= -WEBUI_HTTP_HEADER_CACHE_MAX_AGE=28800 \ No newline at end of file +WEBUI_HTTP_HEADER_CACHE_MAX_AGE=28800 diff --git a/doc/devel/Benchmarking.md b/doc/devel/Benchmarking.md new file mode 100644 index 0000000000000000000000000000000000000000..a244a3a64b771060fbf025fc0ee47054b9b95b48 --- /dev/null +++ b/doc/devel/Benchmarking.md @@ -0,0 +1,13 @@ +# Manual Java-Side Benchmarking # + +Benchmarking can be done using the `TransactionBenchmark` class (in package +`caosdb.server.database.misc`). + +- Single timings can be added to instances of that class via the + `addBenchmark(object, time)` method. Multiple benchmarks for the same object + (typically just strings) can be averaged. +- Benchmarks can be serialized into XML, `Container` and `Query` objects already + use this with their included benchmarks to output benchmarking results. +- To work with the benchmarks of often used objects, use these methods: + - `Container.getTransactionBenchmark().addBenchmark()` + - `Query.addBenchmark()` diff --git a/makefile b/makefile index 642e31c538a98b39fdb07aeaed5b8a2515adf947..53c822deac4faaa058d7b1a04329abf2eb03d0bb 100644 --- a/makefile +++ b/makefile @@ -4,6 +4,8 @@ # # Copyright (C) 2018 Research Group Biomedical Physics, # Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2019 IndiScale GmbH +# Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -22,6 +24,7 @@ # SHELL:=/bin/bash +JPDA_PORT ?= 9000 compile: easy-units mvn compile @@ -32,8 +35,8 @@ runserver: run: compile mvn exec:java@run -run-debug: - java -Dcaosdb.debug=true -jar target/caosdb-server-0.1-SNAPSHOT-jar-with-dependencies.jar +run-debug: jar + java -Xrunjdwp:transport=dt_socket,address=0.0.0.0:$(JPDA_PORT),server=y,suspend=n -Dcaosdb.debug=true -jar target/caosdb-server-0.1-SNAPSHOT-jar-with-dependencies.jar run-single: java -jar target/caosdb-server-0.1-SNAPSHOT-jar-with-dependencies.jar @@ -42,15 +45,15 @@ formatting: mvn fmt:format # Compile into a standalone jar file -jar: compile - mvn clean compile assembly:single -# mvn assembly:single +jar: easy-units + mvn package -DskipTests antlr: mvn antlr4:antlr4 test: easy-units - mvn test + MAVEN_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Dcaosdb.debug=true -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=0.0.0.0:9000" + mvn test -X clean: clean-antlr mvn clean @@ -105,4 +108,5 @@ stop-debug-screen: mkdir .m2-local easy-units: .m2-local + mvn clean mvn deploy:deploy-file -DgroupId=de.timmfitschen -DartifactId=easy-units -Dversion=0.0.1-SNAPSHOT -Durl=file:./.m2-local/ -DrepositoryId=local-maven-repo -DupdateReleaseInfo=true -Dfile=./lib/easy-units-0.0.1-SNAPSHOT-jar-with-dependencies.jar diff --git a/pom.xml b/pom.xml index 42e44ca49b82963a16ac655755ccf29b8fcde6e0..ada3403287a657f7684ba8984c57cc80a51820a9 100644 --- a/pom.xml +++ b/pom.xml @@ -49,16 +49,6 @@ </repository> </repositories> <dependencies> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-slf4j18-impl</artifactId> - <version>2.11.2</version> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - <version>1.8.0-beta4</version> - </dependency> <dependency> <groupId>de.timmfitschen</groupId> <artifactId>easy-units</artifactId> @@ -67,7 +57,7 @@ <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> - <version>1.3.2</version> + <version>1.4.1</version> </dependency> <dependency> <groupId>junit</groupId> @@ -162,15 +152,25 @@ <artifactId>jetty-util-ajax</artifactId> <version>9.2.14.v20151106</version> </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <version>2.11.1</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.21</version> + </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> - <version>2.11.2</version> + <version>2.11.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> - <version>2.11.2</version> + <version>2.11.1</version> </dependency> </dependencies> <build> @@ -180,6 +180,35 @@ <outputDirectory>${basedir}/target/classes</outputDirectory> <testOutputDirectory>${basedir}/target/test-classes</testOutputDirectory> <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.4.3</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <finalName>${project.artifactId}-${project.version}-jar-with-dependencies</finalName> + <transformers> + <transformer implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer"></transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>caosdb.server.CaosDBServer</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>com.github.edwgiz</groupId> + <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId> + <version>2.8.1</version> + </dependency> + </dependencies> + </plugin> <plugin> <groupId>org.antlr</groupId> <artifactId>antlr4-maven-plugin</artifactId> @@ -204,27 +233,6 @@ <target>1.8</target> </configuration> </plugin> - <plugin> - <artifactId>maven-assembly-plugin</artifactId> - <configuration> - <descriptorRefs> - <descriptorRef>jar-with-dependencies</descriptorRef> - </descriptorRefs> - <archive> - <manifest> - <mainClass>caosdb.server.CaosDBServer</mainClass> - </manifest> - </archive> - </configuration> - <executions> - <execution> - <phase>package</phase> - <goals> - <goal>single</goal> <!-- Run with: mvn assembly:single --> - </goals> - </execution> - </executions> - </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> @@ -238,8 +246,8 @@ <log4j2.debug>true</log4j2.debug> </systemPropertyVariables> <reuseForks>false</reuseForks> - <!-- Start 0.5 JVMs per CPU core --> - <!-- Higher numbers *seem* to lead to higher failure rates... :-/ --> + <!-- Start 0.5 JVMs per CPU core + Higher numbers *seem* to lead to higher failure rates... :-/ --> <forkCount>0.5C</forkCount> </configuration> </plugin> @@ -298,13 +306,11 @@ </execution> </executions> </plugin> - <!-- Remove easy-units from classpath generation because of - https://github.com/jdee-emacs/jdee/issues/125 (no sources inside - easy-units) --> + <!-- Remove easy-units from classpath generation because of https://github.com/jdee-emacs/jdee/issues/125 + (no sources inside easy-units) --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> - <version>2.8</version> <configuration> <excludeArtifactIds>easy-units</excludeArtifactIds> </configuration> diff --git a/scripting/home/readme.md b/scripting/home/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..9c9744a55cec41f33929a3953d9c582fa97f46e0 --- /dev/null +++ b/scripting/home/readme.md @@ -0,0 +1,3 @@ +# The `home` directory # +This directory will be copied to a temporary directy for each server-side +scripting invocation and set as the `HOME` environment variable. diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java index 3245902d5721e00d1ae0418a33e7a5f468794ea6..2586c2df7a388061a0986a4ff3a40f94b8657f1b 100644 --- a/src/main/java/caosdb/server/CaosDBServer.java +++ b/src/main/java/caosdb/server/CaosDBServer.java @@ -111,7 +111,6 @@ import org.slf4j.LoggerFactory; public class CaosDBServer extends Application { private static Logger logger = LoggerFactory.getLogger(CaosDBServer.class); - private static boolean DEBUG_MODE = false; private static boolean START_GUI = true; private static Properties SERVER_PROPERTIES = null; private static Component component = null; @@ -222,7 +221,26 @@ public class CaosDBServer extends Application { INSECURE = true; } } - DEBUG_MODE = Boolean.getBoolean("caosdb.debug"); + } + + public static Ini getShiroConfig() { + final Ini config = new Ini(); + final Section mainSec = config.addSection("main"); + mainSec.put("CaosDB", CaosDBDefaultRealm.class.getCanonicalName()); + mainSec.put("SessionTokenValidator", SessionTokenRealm.class.getCanonicalName()); + mainSec.put("OneTimeTokenValidator", OneTimeTokenRealm.class.getCanonicalName()); + mainSec.put("CaosDBAuthorizingRealm", CaosDBAuthorizingRealm.class.getCanonicalName()); + mainSec.put("AnonymousRealm", AnonymousRealm.class.getCanonicalName()); + mainSec.put( + "securityManager.realms", + "$CaosDB, $SessionTokenValidator, $OneTimeTokenValidator, $CaosDBAuthorizingRealm, $AnonymousRealm"); + + // disable shiro's default session management. We have quasi-stateless + // sessions + // using our SessionToken class. + mainSec.put( + "securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled", "false"); + return config; } /** @@ -248,28 +266,12 @@ public class CaosDBServer extends Application { System.exit(1); } - INSECURE = INSECURE && DEBUG_MODE; // only allow insecure in debug mode - START_BACKEND = START_BACKEND || !DEBUG_MODE; // always start backend if + INSECURE = INSECURE && isDebugMode(); // only allow insecure in debug mode + START_BACKEND = START_BACKEND || !isDebugMode(); // always start backend if // not in debug mode // init Shiro (user authentication/authorization and session management) - final Ini config = new Ini(); - final Section mainSec = config.addSection("main"); - mainSec.put("CaosDB", CaosDBDefaultRealm.class.getCanonicalName()); - mainSec.put("SessionTokenValidator", SessionTokenRealm.class.getCanonicalName()); - mainSec.put("OneTimeTokenValidator", OneTimeTokenRealm.class.getCanonicalName()); - mainSec.put("CaosDBAuthorizingRealm", CaosDBAuthorizingRealm.class.getCanonicalName()); - mainSec.put("AnonymousRealm", AnonymousRealm.class.getCanonicalName()); - mainSec.put( - "securityManager.realms", - "$CaosDB, $SessionTokenValidator, $OneTimeTokenValidator, $CaosDBAuthorizingRealm, $AnonymousRealm"); - - // disable shiro's default session management. We have quasi-stateless - // sessions - // using our SessionToken class. - mainSec.put( - "securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled", "false"); - + final Ini config = getShiroConfig(); final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config); final SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); @@ -829,7 +831,7 @@ public class CaosDBServer extends Application { } public static boolean isDebugMode() { - return DEBUG_MODE; + return Boolean.getBoolean("caosdb.debug"); } /** diff --git a/src/main/java/caosdb/server/ServerProperties.java b/src/main/java/caosdb/server/ServerProperties.java index 1f90b069df76760864385f6f090e123387bcbd0a..e68016894d65a3b77e38aa2f84f48ece7db05683 100644 --- a/src/main/java/caosdb/server/ServerProperties.java +++ b/src/main/java/caosdb/server/ServerProperties.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -90,13 +92,6 @@ public class ServerProperties extends Properties { public static final String KEY_CACHE_CONF_LOC = "CACHE_CONF_LOC"; - public static final String KEY_RULES_CACHE_CAPACITY = "RULES_CACHE_CAPACITY"; - public static final String KEY_SPARSE_ENTITY_CACHE_CAPACITY = "SPARSE_ENTITY_CACHE_CAPACITY"; - public static final String KEY_PROPERTIES_CACHE_CAPACITY = "PROPERTIES_CACHE_CAPACITY"; - public static final String KEY_PARENTS_CACHE_CAPACITY = "PARENTS_CACHE_CAPACITY"; - public static final String KEY_USER_ACCOUNT_CACHE_CAPACITY = "USER_ACCOUNT_CACHE_CAPACITY"; - public static final String KEY_GROUP_CACHE_CAPACITY = "GROUP_CACHE_CAPACITY"; - public static final String KEY_TRANSACTION_BENCHMARK_ENABLED = "TRANSACTION_BENCHMARK_ENABLED"; public static final String KEY_INSERT_FILES_IN_DIR_ALLOWED_DIRS = @@ -119,6 +114,7 @@ public class ServerProperties extends Properties { public static final String KEY_SERVER_OWNER = "SERVER_OWNER"; public static final String KEY_SERVER_SIDE_SCRIPTING_BIN_DIR = "SERVER_SIDE_SCRIPTING_BIN_DIR"; + public static final String KEY_SERVER_SIDE_SCRIPTING_HOME_DIR = "SERVER_SIDE_SCRIPTING_HOME_DIR"; public static final String KEY_SERVER_SIDE_SCRIPTING_WORKING_DIR = "SERVER_SIDE_SCRIPTING_WORKING_DIR"; diff --git a/src/main/java/caosdb/server/accessControl/Pam.java b/src/main/java/caosdb/server/accessControl/Pam.java index c222fb83dabe0e4ad4e99d096d33192bf65086a9..99f68faadc15c15d9404a504d15669f0c2604ea3 100644 --- a/src/main/java/caosdb/server/accessControl/Pam.java +++ b/src/main/java/caosdb/server/accessControl/Pam.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,23 +24,38 @@ */ package caosdb.server.accessControl; +import caosdb.server.caching.Cache; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import org.apache.commons.jcs.access.behavior.ICacheAccess; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.shiro.authz.AuthorizationException; import org.jvnet.libpam.PAMException; import org.jvnet.libpam.UnixUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +/** + * PAM UserSource for authenticating users via the Host's pam module. + * + * <p>A User's existence check and the retrieval of a user's groups is done by the org.jvnet.libpam + * library. + * + * <p>The authentication of a user via the password need root-access and is therefore done by a + * special shell script running with root privileges on the host. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public class Pam implements UserSource { + private Logger logger = LogManager.getLogger(Pam.class); + public static class DefaultPamScriptCaller implements PamScriptCaller { - private Logger logger = LoggerFactory.getLogger(getClass()); + private Logger logger = LogManager.getLogger(Pam.class); private final String pam_script; @@ -64,7 +81,7 @@ public class Pam implements UserSource { try { pam_authentication = getProcess(username, password); - logger.info("call pam script"); + logger.trace("call pam script"); return pam_authentication.waitFor() == 0; } catch (final IOException e) { throw new RuntimeException(e); @@ -75,6 +92,11 @@ public class Pam implements UserSource { } public static final String DEFAULT_PAM_SCRIPT = "./misc/pam_authentication/pam_authentication.sh"; + + /* + * following constants are the names of the configuration parameters and parts of parameters in + * the user_sources.ini for the pam user source. + */ public static final String KEY_PAM_SCRIPT = "pam_script"; public static final String KEY_DEFAULT_USER_STATUS = "default_status"; public static final String KEY_GROUP = "group"; @@ -83,8 +105,9 @@ public class Pam implements UserSource { public static final String KEY_INCLUDE = "include"; public static final String KEY_ROLES = "roles"; public static final String SEPARATOR = "."; + public static final String KEY_EMAIL = "email"; public static final String REGEX_SPLIT_CSV = "\\s*,\\s*"; - private static final String KEY_EMAIL = "email"; + private String[] EXCLUDE_USERS = null; private String[] INCLUDE_USERS = null; private String[] EXCLUDE_GROUPS = null; @@ -92,16 +115,28 @@ public class Pam implements UserSource { private Map<String, String> map = null; private PamScriptCaller pamScriptCaller = null; + /* Caches for groups, user-existence and password validation */ + + private ICacheAccess<String, HashSet<String>> groupsCache = Cache.getCache("PAM_UnixUserGroups"); + private ICacheAccess<String, Boolean> userExistsCache = Cache.getCache("PAM_UnixUserExists"); + private ICacheAccess<String, Boolean> passwordValidCache = Cache.getCache("PAM_Authentication"); + @Override public void setMap(final Map<String, String> map) { this.map = map; this.pamScriptCaller = null; } - public Map<String, String> getMap() { + /** + * Return the current configuration of this user source. + * + * @return configuration. + */ + private Map<String, String> getMap() { return this.map; } + /** @see {@link UserSource#resolveRolesForUsername(String)} */ @Override public Set<String> resolveRolesForUsername(final String username) { final Set<String> resulting_roles = new HashSet<String>(); @@ -138,7 +173,40 @@ public class Pam implements UserSource { return resulting_roles; } - private static Set<String> getGroups(final String username) { + /** + * Get the UNIX groups of the user. + * + * <p>First, try to get them from the cache. Only ask the back-end if necessary and cache the + * results. + * + * @param username + * @return A user's UNIX groups. + */ + private Set<String> getGroups(final String username) { + Set<String> cached = groupsCache.get(username); + if (cached != null) { + return cached; + } + + Set<String> uncached = getGroupsNoCache(username); + if (uncached instanceof HashSet) { + groupsCache.put(username, (HashSet<String>) uncached); + } else { + groupsCache.put(username, new HashSet<String>(uncached)); + } + return uncached; + } + + /** + * Get the UNIX groups of the user directly from the back-end. + * + * <p>If the user does not exist, an empty set is returned. + * + * @param username + * @return A user's UNIX groups or an empty set. + */ + private Set<String> getGroupsNoCache(final String username) { + logger.trace("Retrieving UnixGroups", username); if (UnixUser.exists(username)) { try { return new UnixUser(username).getGroups(); @@ -146,7 +214,7 @@ public class Pam implements UserSource { throw new AuthorizationException(e); } } - return null; + return new HashSet<String>(); } @Override @@ -154,11 +222,38 @@ public class Pam implements UserSource { return "PAM"; } + /** + * Check if that user is known by the host's PAM. + * + * <p>Try the cache first. Only ask PAM directly if necessary and cache the results. + * + * @see {@link UserSource#isUserExisting(String)}. + * @return true iff the user is known. + */ @Override public boolean isUserExisting(final String username) { + Boolean cached = userExistsCache.get(username); + if (cached != null) { + return cached; + } + + boolean uncached = isUserExistingNoCache(username); + userExistsCache.put(username, uncached); + return uncached; + } + + /** + * Check if that user is known by the host's PAM (without caching). + * + * @param username + * @return true iff the user is known. + */ + private boolean isUserExistingNoCache(final String username) { + logger.trace("Check UnixUser.exists", username); return username != null && UnixUser.exists(username) && isIncorporated(username); } + /** @see {@link UserSource#isValid(String, String)}. */ @Override public boolean isValid(final String username, final String password) { if (isUserExisting(username)) { @@ -174,8 +269,40 @@ public class Pam implements UserSource { return this.pamScriptCaller; } + /** + * Check the validity of the password for that user by asking the pam script caller. + * + * <p>Try the cache first, only call the pam script if necessary and cache the results. + * + * @param caller + * @param username + * @param password + * @return true iff the password is correct. + */ private boolean isValid( final PamScriptCaller caller, final String username, final String password) { + String key = "<" + password + "::" + username + ">"; + Boolean cached = passwordValidCache.get(key); + if (cached != null) { + return cached; + } + + boolean uncached = isValidNoCache(caller, username, password); + passwordValidCache.put(key, uncached); + return uncached; + } + + /** + * Check the validity of the password for that user by asking the pam script caller. + * + * @param caller + * @param username + * @param password + * @return true iff the password is correct. + */ + private boolean isValidNoCache( + final PamScriptCaller caller, final String username, final String password) { + logger.trace("Check Password", username); return caller.isValid(username, password); } @@ -278,10 +405,15 @@ public class Pam implements UserSource { ret = n; } else if (ret != n) { // conflict -> ignore, go for pam-wide setting + ret = null; break; } } } + + if (ret != null) { + return ret; + } // by pam-wide setting if (this.map.containsKey(KEY_DEFAULT_USER_STATUS)) { return UserStatus.valueOf(this.map.get(KEY_DEFAULT_USER_STATUS).toUpperCase()); diff --git a/src/main/java/caosdb/server/accessControl/UserSource.java b/src/main/java/caosdb/server/accessControl/UserSource.java index 23ce7c45ce6cb9982ae5c7fac05a3a41037a5afe..0ece8d5014227916feb1c50b986aaf4d9a9bf91b 100644 --- a/src/main/java/caosdb/server/accessControl/UserSource.java +++ b/src/main/java/caosdb/server/accessControl/UserSource.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -25,19 +27,84 @@ package caosdb.server.accessControl; import java.util.Map; import java.util.Set; +/** + * UserSources are sources for users - i.e. they authenticate users and contain basic information + * about users. + * + * <p>UserSources let you + * <li>check if a user exists - {@link #isUserExisting(String)} + * <li>authenticate a user via a password - {@link #isValid(String, String)} + * <li>get the default {@link UserStatus} - {@link #getDefaultUserStatus(String)} + * <li>get the default email address - {@link #getDefaultUserEmail(String)} + * <li>retrieve a users roles - {@link #resolveRolesForUsername(String)} The default email and + * default {@link UserStatus} might be overridden by other settings in CaosDB - that's why they + * are called "default". + * + * <p>Also, the user's roles might be overridden by the internal user source {@link + * InternalUserSource}. + * + * <p>A UserSource is configured via {@link #setMap(Map)}. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ public interface UserSource { + /** + * Every UserSource has a unique name, e.g. PAM, CaosDB (which is default name of the internal + * user source {@link InternalUserSource}). + * + * @return name + */ public String getName(); + /** + * Check if a user exists. + * + * @param username + * @return true iff this user source knows a user with that name + */ public boolean isUserExisting(String username); + /** + * Return all roles that a user is associated with. + * + * @param username + * @return a user's roles + */ public Set<String> resolveRolesForUsername(final String username); + /** + * Configure this user source. The needed parameters are to be defined and documented by the + * implementations. + * + * @param map the configuration + */ public void setMap(Map<String, String> map); + /** + * Return the {@link UserStatus} of that user. + * + * @param username + * @return The user status of that user + */ public UserStatus getDefaultUserStatus(String username); + /** + * Return the email address of that user, or null if none is available as per this user source. + * + * <p>This method does not check if a user exists. So it will return null for unknown users. + * + * @param username + * @return The email address or null + */ public String getDefaultUserEmail(String username); + /** + * Check if the user can be authenticated by the given password. + * + * @param username + * @param password + * @return true iff the password was correct. + */ public boolean isValid(String username, String password); } diff --git a/src/main/java/caosdb/server/caching/Cache.java b/src/main/java/caosdb/server/caching/Cache.java new file mode 100644 index 0000000000000000000000000000000000000000..338b1d853fd0610be780362a0242193fa46eb320 --- /dev/null +++ b/src/main/java/caosdb/server/caching/Cache.java @@ -0,0 +1,50 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2018 Research Group Biomedical Physics, + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * ** end header + */ +package caosdb.server.caching; + +import java.io.Serializable; +import org.apache.commons.jcs.access.behavior.ICacheAccess; + +/** + * Caching Helper Class used for all caches in the CaosDB Server. + * + * <p>The actual work is delegated to an instance of {@link JCSCacheHelper}. However, the delegate + * {@link #DELEGATE} can be be overridden for testing purposes with {@link #setDelegate(Cache)}. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ +public class Cache { + + /** The default cache helper delegate. */ + private static CacheHelper DELEGATE = new JCSCacheHelper(); + + public static void setDelegate(CacheHelper delegate) { + DELEGATE = delegate; + } + + public static final <K, V extends Serializable> ICacheAccess<K, V> getCache(String region) { + return DELEGATE.getCache(region); + } +} diff --git a/src/main/java/caosdb/server/caching/CacheHelper.java b/src/main/java/caosdb/server/caching/CacheHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..32c854400310959a7834fe32a6f90de9e1b584d1 --- /dev/null +++ b/src/main/java/caosdb/server/caching/CacheHelper.java @@ -0,0 +1,31 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * ** end header + */ +package caosdb.server.caching; + +import java.io.Serializable; +import org.apache.commons.jcs.access.behavior.ICacheAccess; + +public interface CacheHelper { + + public <K, V extends Serializable> ICacheAccess<K, V> getCache(String region); +} diff --git a/src/main/java/caosdb/server/caching/JCSCacheHelper.java b/src/main/java/caosdb/server/caching/JCSCacheHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..dab2b77ceaab3d0caaf49d798a4819ca890b5e82 --- /dev/null +++ b/src/main/java/caosdb/server/caching/JCSCacheHelper.java @@ -0,0 +1,91 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2018 Research Group Biomedical Physics, + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * ** end header + */ +package caosdb.server.caching; + +import caosdb.server.CaosDBServer; +import caosdb.server.ServerProperties; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Properties; +import org.apache.commons.jcs.JCS; +import org.apache.commons.jcs.access.behavior.ICacheAccess; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * A CacheHelper implementation which is configured statically via the {@link + * ServerProperties#KEY_CACHE_CONF_LOC} properties file. + * + * @author Timm Fitschen (t.fitschen@indiscale.com) + */ +public class JCSCacheHelper implements CacheHelper { + + protected static Logger logger = LogManager.getLogger(JCSCacheHelper.class); + + // A No-Operation configuration for JCS + private static Properties getNOPCachingProperties() { + Properties ret = new Properties(); + ret.setProperty( + "jcs.default.cacheattributes", "org.apache.commons.jcs.engine.CompositeCacheAttributes"); + ret.setProperty("jcs.default.cacheattributes.MaxObjects", "0"); + return ret; + } + + // configure JCS + static { + Properties config = null; + try { + Properties p = new Properties(); + final InputStream is = + new FileInputStream(CaosDBServer.getServerProperty(ServerProperties.KEY_CACHE_CONF_LOC)); + p.load(is); + is.close(); + config = p; + } catch (final FileNotFoundException e) { + logger.error(e); + config = getNOPCachingProperties(); + } catch (final IOException e) { + logger.error(e); + config = getNOPCachingProperties(); + } + logger.info("Configuring JCS Caching with {}", config); + JCS.setConfigProperties(config); + } + + @Override + public <K, V extends Serializable> ICacheAccess<K, V> getCache(final String name) { + final ICacheAccess<K, V> cache = JCS.getInstance(name); + logger.info( + "Caching configuration for {}:\n+----{}\n+----{}", + name, + cache.getCacheAttributes(), + cache.getDefaultElementAttributes()); + + return cache; + } +} diff --git a/src/main/java/caosdb/server/database/BackendTransaction.java b/src/main/java/caosdb/server/database/BackendTransaction.java index 1d5d5c2e68274e664c392a24a901aa4bcd65623b..4899996e6c4df2b18837259112ac7e90c534b793 100644 --- a/src/main/java/caosdb/server/database/BackendTransaction.java +++ b/src/main/java/caosdb/server/database/BackendTransaction.java @@ -210,7 +210,7 @@ public abstract class BackendTransaction implements Undoable { return t; } - private static <K extends BackendTransactionImpl, L extends K> void setImpl( + public static <K extends BackendTransactionImpl, L extends K> void setImpl( final Class<K> k, final Class<L> l) { impl.put(k, l); } diff --git a/src/main/java/caosdb/server/database/CacheableBackendTransaction.java b/src/main/java/caosdb/server/database/CacheableBackendTransaction.java index 390a177fe899f89330aeb3d0959aab5bf1d52cbc..fc4c9659d9a8ea80393bebad945bd6badf9580c4 100644 --- a/src/main/java/caosdb/server/database/CacheableBackendTransaction.java +++ b/src/main/java/caosdb/server/database/CacheableBackendTransaction.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -24,14 +26,19 @@ package caosdb.server.database; import caosdb.server.database.exceptions.TransactionException; import java.io.Serializable; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public abstract class CacheableBackendTransaction<K, V extends Serializable> extends BackendTransaction { - public abstract V executeNoCache() throws TransactionException; - private Boolean cached = null; + private ICacheAccess<K, V> cache; + + public CacheableBackendTransaction(ICacheAccess<K, V> cache) { + this.cache = cache; + } + + public abstract V executeNoCache() throws TransactionException; @Override public final void execute() throws TransactionException { @@ -67,8 +74,8 @@ public abstract class CacheableBackendTransaction<K, V extends Serializable> protected abstract K getKey(); - protected CacheAccess<K, V> getCache() { - return null; + protected final ICacheAccess<K, V> getCache() { + return cache; } private final boolean cacheIsEnabled() { diff --git a/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java index 4bd6e5b9706fe743be6c924c3e1f86cb6ff83371..39deb1416c743f838bfc1b96c29fd845dbcef742 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -41,6 +43,9 @@ public class DeleteSparseEntity extends BackendTransaction { @Override protected void execute() { RetrieveSparseEntity.removeCached(this.entity.getId()); + if (entity.hasFileProperties()) { + GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath()); + } final DeleteSparseEntityImpl ret = getImplementation(DeleteSparseEntityImpl.class); 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 195f16db5341fdcd204debbaad0217635d1fd222..8e0afcbdc50985f1fa1a939940c05f8429562c90 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/GetFileRecordByPath.java +++ b/src/main/java/caosdb/server/database/backend/transaction/GetFileRecordByPath.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,20 +24,31 @@ */ 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.TransactionException; import caosdb.server.database.proto.SparseEntity; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class GetFileRecordByPath extends CacheableBackendTransaction<String, SparseEntity> { + private static final ICacheAccess<String, SparseEntity> cache = + Cache.getCache("BACKEND_SparseFileRecordsByPath"); private final String path; private SparseEntity entity; public GetFileRecordByPath(final String path) { + super(cache); this.path = path; } + public static void removeCached(final String path) { + if (path != null && cache != null) { + cache.remove(path); + } + } + @Override protected String getKey() { return this.path; diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java index 5d1545d29b9bab295c9f335fe4eb083f12f748b2..5c1b0178f51496f83a4fac6a4ce3b2447abdccd5 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,26 +24,21 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.DatabaseUtils; import caosdb.server.database.backend.interfaces.RetrieveParentsImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; import caosdb.server.database.proto.VerySparseEntity; import caosdb.server.entity.EntityInterface; import java.util.ArrayList; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrieveParents extends CacheableBackendTransaction<Integer, ArrayList<VerySparseEntity>> { - private static final CacheAccess<Integer, ArrayList<VerySparseEntity>> cache = - Cache.getCache( - "ParentsCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_SPARSE_ENTITY_CACHE_CAPACITY))); + private static final ICacheAccess<Integer, ArrayList<VerySparseEntity>> cache = + Cache.getCache("BACKEND_EntityParents"); /** * To be called by DeleteEntityProperties on execution. @@ -54,14 +51,10 @@ public class RetrieveParents } } - @Override - protected CacheAccess<Integer, ArrayList<VerySparseEntity>> getCache() { - return cache; - } - private final EntityInterface entity; public RetrieveParents(final EntityInterface entity) { + super(cache); this.entity = entity; } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrievePermissionRules.java b/src/main/java/caosdb/server/database/backend/transaction/RetrievePermissionRules.java index c0daecbc46ac90630eac83d0c7334d7da148d429..7e9925777c21a6d041759cfdade7682b85458566 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrievePermissionRules.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrievePermissionRules.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,28 +24,24 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; import caosdb.server.permissions.PermissionRule; import java.util.HashSet; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrievePermissionRules extends CacheableBackendTransaction<String, HashSet<PermissionRule>> { - private static final CacheAccess<String, HashSet<PermissionRule>> cache = - Cache.getCache( - "PermissionRulesCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_GROUP_CACHE_CAPACITY))); + private static final ICacheAccess<String, HashSet<PermissionRule>> cache = + Cache.getCache("BACKEND_PermissionRules"); private HashSet<PermissionRule> rules; private final String role; public RetrievePermissionRules(final String role) { + super(cache); this.role = role; } @@ -51,11 +49,6 @@ public class RetrievePermissionRules cache.remove(role); } - @Override - protected CacheAccess<String, HashSet<PermissionRule>> getCache() { - return cache; - } - @Override public HashSet<PermissionRule> executeNoCache() throws TransactionException { final RetrievePermissionRulesImpl t = getImplementation(RetrievePermissionRulesImpl.class); diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java index b75d91acba76a4d97b16395701afe42ff05c5fac..650cae3676ed2b6b50f86c76a5ce92b195290fc3 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,29 +24,24 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.DatabaseUtils; import caosdb.server.database.backend.interfaces.RetrievePropertiesImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; import caosdb.server.database.proto.ProtoProperty; import caosdb.server.entity.EntityInterface; import caosdb.server.entity.Role; import caosdb.server.entity.wrapper.Property; import java.util.ArrayList; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrieveProperties extends CacheableBackendTransaction<Integer, ArrayList<ProtoProperty>> { private final EntityInterface entity; - private static final CacheAccess<Integer, ArrayList<ProtoProperty>> cache = - Cache.getCache( - "PropertiesCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_PROPERTIES_CACHE_CAPACITY))); + private static final ICacheAccess<Integer, ArrayList<ProtoProperty>> cache = + Cache.getCache("BACKEND_EntityProperties"); /** * To be called by DeleteEntityProperties on execution. @@ -57,12 +54,8 @@ public class RetrieveProperties } } - @Override - protected CacheAccess<Integer, ArrayList<ProtoProperty>> getCache() { - return cache; - } - public RetrieveProperties(final EntityInterface entity) { + super(cache); this.entity = entity; } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveRole.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveRole.java index e45879964ef8bafad0106238073ff8c9d87e2287..5bb6150a8640f45997a4a3432ad4ac27196cd15c 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveRole.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveRole.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,36 +24,26 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; import caosdb.server.accessControl.Role; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.backend.interfaces.RetrieveRoleImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrieveRole extends CacheableBackendTransaction<String, Role> { - private static final CacheAccess<String, Role> cache = - Cache.getCache( - "RolesCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_GROUP_CACHE_CAPACITY))); + private static final ICacheAccess<String, Role> cache = Cache.getCache("BACKEND_UserRoles"); private final String role_name; private Role role; - @Override - protected CacheAccess<String, Role> getCache() { - return cache; - } - public Role getRole() { return this.role; } public RetrieveRole(final String role) { + super(cache); this.role_name = role; } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java index df9f58671184fb70fca99ca7707f9270f75c87a1..a91b5021f1fa722a8e8831234fc8ebc2032ba115 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,30 +24,25 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.DatabaseUtils; import caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; import caosdb.server.database.proto.SparseEntity; import caosdb.server.entity.Entity; import caosdb.server.entity.EntityInterface; import caosdb.server.utils.EntityStatus; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, SparseEntity> { private final EntityInterface entity; - private static final CacheAccess<Integer, SparseEntity> cache = - Cache.getCache( - "SparseEntityCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_SPARSE_ENTITY_CACHE_CAPACITY))); + private static final ICacheAccess<Integer, SparseEntity> cache = + Cache.getCache("BACKEND_SparseEntities"); /** - * To be called by UpdateSparseEntity and DeleteEntity on execution. + * To be called by {@link UpdateSparseEntity} and {@link DeleteEntity} on execution. * * @param id */ @@ -55,17 +52,13 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, S } } - @Override - protected CacheAccess<Integer, SparseEntity> getCache() { - return cache; - } - public RetrieveSparseEntity(final EntityInterface entity) { + super(cache); this.entity = entity; } public RetrieveSparseEntity(final int id) { - this.entity = new Entity(id); + this(new Entity(id)); } @Override diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java index cb9cdd9e993b9399f2bec5c9bff7316e0754b9ed..72bc1e5b1ec3a3509cc5cbba23e38b908d4e969f 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveTransactionHistory.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -23,17 +25,15 @@ package caosdb.server.database.backend.transaction; import caosdb.datetime.UTCDateTime; -import caosdb.server.database.CacheableBackendTransaction; +import caosdb.server.database.BackendTransaction; import caosdb.server.database.backend.interfaces.RetrieveTransactionHistoryImpl; import caosdb.server.database.exceptions.TransactionException; import caosdb.server.database.proto.ProtoTransactionLogMessage; import caosdb.server.entity.EntityInterface; import caosdb.server.utils.TransactionLogMessage; import java.util.ArrayList; -import org.apache.commons.jcs.access.CacheAccess; -public class RetrieveTransactionHistory - extends CacheableBackendTransaction<Integer, ArrayList<ProtoTransactionLogMessage>> { +public class RetrieveTransactionHistory extends BackendTransaction { private final EntityInterface entity; @@ -42,25 +42,13 @@ public class RetrieveTransactionHistory } @Override - protected Integer getKey() { - return this.entity.getId(); - } - - @Override - protected CacheAccess<Integer, ArrayList<ProtoTransactionLogMessage>> getCache() { - return null; - } - - @Override - public ArrayList<ProtoTransactionLogMessage> executeNoCache() throws TransactionException { + protected void execute() { final RetrieveTransactionHistoryImpl t = getImplementation(RetrieveTransactionHistoryImpl.class); - return t.execute(getKey()); + process(t.execute(entity.getId())); } - @Override - protected void process(final ArrayList<ProtoTransactionLogMessage> l) - throws TransactionException { + private void process(final ArrayList<ProtoTransactionLogMessage> l) throws TransactionException { for (final ProtoTransactionLogMessage t : l) { final UTCDateTime dateTime = UTCDateTime.UTCSeconds(t.seconds, t.nanos); diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveUser.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveUser.java index 7492df38adfcfd24d18f1a263be5070a8916c9c8..9f27152030f5c953ee7bd4ecfff8c83d3cbc8a73 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveUser.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveUser.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,43 +24,29 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; import caosdb.server.accessControl.Principal; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.backend.interfaces.RetrieveUserImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; import caosdb.server.database.proto.ProtoUser; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RetrieveUser extends CacheableBackendTransaction<Principal, ProtoUser> { private ProtoUser user; - private static final CacheAccess<Principal, ProtoUser> cache = - Cache.getCache( - "UserAccountCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_USER_ACCOUNT_CACHE_CAPACITY))); + private static final ICacheAccess<Principal, ProtoUser> cache = Cache.getCache("BACKEND_Users"); private final Principal principal; - /** - * To be called by DeleteSparseEntity, SetPassword, and UpdateSparseEntity on execution. - * - * @param u - */ + /** To be called by DeleteSparseEntity, SetPassword, and UpdateSparseEntity on execution. */ public static void removeCached(final Principal principal) { if (principal != null && cache != null) { cache.remove(principal); } } - @Override - protected CacheAccess<Principal, ProtoUser> getCache() { - return cache; - } - public RetrieveUser(final Principal principal) { + super(cache); this.principal = principal; } diff --git a/src/main/java/caosdb/server/database/backend/transaction/RuleLoader.java b/src/main/java/caosdb/server/database/backend/transaction/RuleLoader.java index 1a8698ac1d698946e7a3e239c03e386bc200e2b6..303029b42179a697c939767e2caf4d490a803a69 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/RuleLoader.java +++ b/src/main/java/caosdb/server/database/backend/transaction/RuleLoader.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,22 +24,22 @@ */ package caosdb.server.database.backend.transaction; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; +import caosdb.server.caching.Cache; import caosdb.server.database.CacheableBackendTransaction; import caosdb.server.database.backend.interfaces.RuleLoaderImpl; import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.database.misc.Cache; import caosdb.server.database.proto.Rule; import caosdb.server.entity.EntityInterface; import caosdb.server.entity.container.TransactionContainer; import caosdb.server.jobs.Job; import caosdb.server.transaction.Transaction; import java.util.ArrayList; -import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.jcs.access.behavior.ICacheAccess; public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Rule>> { + private static final ICacheAccess<String, ArrayList<Rule>> cache = + Cache.getCache("BACKEND_JobRules"); private final Transaction<? extends TransactionContainer> transaction; private final EntityInterface e; private final Integer entity; @@ -49,6 +51,7 @@ public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Ru final Integer entity, final EntityInterface e, final Transaction<? extends TransactionContainer> transaction) { + super(cache); this.domain = domain; this.entity = entity; this.e = e; @@ -66,17 +69,6 @@ public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Ru + ">"; } - @Override - protected CacheAccess<String, ArrayList<Rule>> getCache() { - return rulesCache; - } - - private static CacheAccess<String, ArrayList<Rule>> rulesCache = - Cache.getCache( - "RulesCache", - Integer.valueOf( - CaosDBServer.getServerProperty(ServerProperties.KEY_RULES_CACHE_CAPACITY))); - public ArrayList<Job> getJobs() { return this.jobs; } diff --git a/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java index 57d22ef226e83caf7e3b37b48b53c1025afaa93f..c6a1c7683409e0c875484e14553e4aeac2757a71 100644 --- a/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java +++ b/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2019 IndiScale GmbH + * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -39,6 +41,9 @@ public class UpdateSparseEntity extends BackendTransaction { @Override public void execute() throws TransactionException { RetrieveSparseEntity.removeCached(this.entity.getId()); + if (entity.hasFileProperties()) { + GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath()); + } final UpdateSparseEntityImpl t = getImplementation(UpdateSparseEntityImpl.class); diff --git a/src/main/java/caosdb/server/database/misc/Cache.java b/src/main/java/caosdb/server/database/misc/Cache.java deleted file mode 100644 index 7f364d4ab3e2ddc2d3e5e0638be4fe5dfa252ccd..0000000000000000000000000000000000000000 --- a/src/main/java/caosdb/server/database/misc/Cache.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package caosdb.server.database.misc; - -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; -import caosdb.server.terminal.StatLabel; -import caosdb.server.terminal.StatsPanel; -import caosdb.server.utils.AbstractObservable; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.Properties; -import org.apache.commons.jcs.JCS; -import org.apache.commons.jcs.access.CacheAccess; - -public class Cache { - - static { - final Properties p = new Properties(); - try { - final InputStream is = - new FileInputStream(CaosDBServer.getServerProperty(ServerProperties.KEY_CACHE_CONF_LOC)); - p.load(is); - is.close(); - } catch (final FileNotFoundException e) { - e.printStackTrace(); - } catch (final IOException e) { - e.printStackTrace(); - } - JCS.setConfigProperties(p); - } - - public static <K, V extends Serializable> CacheAccess<K, V> getCache( - final String name, final int capacity) { - if (capacity > 0) { - final CacheAccess<K, V> cache = JCS.getInstance(name); - cache.getCacheAttributes().setMaxObjects(capacity); - StatsPanel.addStat( - name, - new AbstractObservable() { - @Override - public String toString() { - return cache.getStats(); - }; - }); - StatsPanel.addStat( - name, - new StatLabel( - "Configuration", cache.getCacheAttributes().toString().replaceAll(",", "\n"))); - - return cache; - } - return null; - } -} diff --git a/src/main/java/caosdb/server/datatype/GenericValue.java b/src/main/java/caosdb/server/datatype/GenericValue.java index d6140ff75a80908e0cf82880aeee336a6f21cb87..8e8f1dd41e49305ddba2090b1f897a35a3765f93 100644 --- a/src/main/java/caosdb/server/datatype/GenericValue.java +++ b/src/main/java/caosdb/server/datatype/GenericValue.java @@ -44,7 +44,11 @@ public class GenericValue implements SingleValue { throw new NullPointerException(); } this.value = d; - this.table = Table.double_data; + if (d.isNaN()) { + this.table = Table.text_data; + } else { + this.table = Table.double_data; + } } public GenericValue(final String s) { diff --git a/src/main/java/caosdb/server/jobs/core/InsertFilesInDir.java b/src/main/java/caosdb/server/jobs/core/InsertFilesInDir.java index 773ac27525e0b28a1a10f23f1a2ee9cfd9fbf799..9dbc2a68b684b7a5757a00c816f81f3716e55dc9 100644 --- a/src/main/java/caosdb/server/jobs/core/InsertFilesInDir.java +++ b/src/main/java/caosdb/server/jobs/core/InsertFilesInDir.java @@ -22,6 +22,7 @@ */ package caosdb.server.jobs.core; +import caosdb.server.CaosDBException; import caosdb.server.CaosDBServer; import caosdb.server.FileSystem; import caosdb.server.ServerProperties; @@ -153,7 +154,7 @@ public class InsertFilesInDir extends FlagJob { try { final Undoable delete = FileUtils.delete(InsertFilesInDir.this.tmp, true); delete.cleanUp(); - } catch (final IOException e) { + } catch (final IOException | CaosDBException | InterruptedException e) { e.printStackTrace(); } } @@ -163,7 +164,7 @@ public class InsertFilesInDir extends FlagJob { try { final Undoable delete = FileUtils.delete(InsertFilesInDir.this.tmp, true); delete.cleanUp(); - } catch (final IOException e) { + } catch (final IOException | CaosDBException | InterruptedException e) { e.printStackTrace(); } } diff --git a/src/main/java/caosdb/server/query/Query.java b/src/main/java/caosdb/server/query/Query.java index c9bff5a5f11aa5775582c613d80062419f9adb4c..d3e0e927d14d0b2021f056ad52f10df548c5734c 100644 --- a/src/main/java/caosdb/server/query/Query.java +++ b/src/main/java/caosdb/server/query/Query.java @@ -415,6 +415,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } public void parse() throws ParsingException { + final long t1 = System.currentTimeMillis(); CQLLexer lexer; lexer = new CQLLexer(CharStreams.fromString(this.query)); final CommonTokenStream tokens = new CommonTokenStream(lexer); @@ -434,6 +435,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac this.type = cq.t; this.filter = cq.filter; this.selections = cq.s; + final long t2 = System.currentTimeMillis(); + addBenchmark("parse (" + this.query + ")", t2 - t1); } private String executeStrategy() throws QueryException { diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java index c88bbd75a34aea81198b2c9d48702ca6f66a41e0..948b4eab2fda1c906f4acf2802c0d83184b875bc 100644 --- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -101,7 +101,8 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { } public Subject getUser() { - return SecurityUtils.getSubject(); + Subject ret = SecurityUtils.getSubject(); + return ret; } /** @@ -331,6 +332,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { this.xslScript = s; } + /** Wrap an element for an "OK" response. */ protected JdomRepresentation ok(Element root) { return ok(new Document(root)); } diff --git a/src/main/java/caosdb/server/resource/InfoResource.java b/src/main/java/caosdb/server/resource/InfoResource.java index 96459212a804d2451c2b978bd858bba954640bca..c2342cc46f7e62fd6a398218b39e804ab9f1767d 100644 --- a/src/main/java/caosdb/server/resource/InfoResource.java +++ b/src/main/java/caosdb/server/resource/InfoResource.java @@ -30,10 +30,15 @@ import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.restlet.representation.Representation; -import org.restlet.resource.Options; +/** This class represents the information retrieved by /Info requests (only GET) to CaosDB. */ public class InfoResource extends AbstractCaosDBServerResource { + /** + * The response to the HTTP GET request is generated here. + * + * @return The response code for OK and the resulting info XML document. + */ @Override protected Representation httpGetInChildClass() throws Exception { final Document doc = new Document(); @@ -45,14 +50,10 @@ public class InfoResource extends AbstractCaosDBServerResource { return ok(doc); } + /** There is no POST request specified for the /Info resource. */ @Override protected Representation httpPostInChildClass(final Representation entity) throws ConnectionException, JDOMException { return null; } - - @Options - public static Representation describeResource() { - return null; - } } diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java index 81a84ab631d9cd2206fd163b5c64eaac4c967286..57ada4aa7b4ed2a588e7de6e50c85de1b7fb6670 100644 --- a/src/main/java/caosdb/server/resource/ScriptingResource.java +++ b/src/main/java/caosdb/server/resource/ScriptingResource.java @@ -27,6 +27,7 @@ package caosdb.server.resource; import caosdb.server.FileSystem; import caosdb.server.accessControl.Principal; import caosdb.server.accessControl.SessionToken; +import caosdb.server.accessControl.UserSources; import caosdb.server.entity.FileProperties; import caosdb.server.entity.Message; import caosdb.server.scripting.CallerSerializer; @@ -82,6 +83,9 @@ public class ScriptingResource extends AbstractCaosDBServerResource { @Override protected Representation httpPostInChildClass(Representation entity) throws Exception { + if (isAnonymous()) { + return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN); + } MediaType mediaType = entity.getMediaType(); try { if (mediaType.equals(MediaType.MULTIPART_FORM_DATA, true)) { @@ -202,6 +206,11 @@ public class ScriptingResource extends AbstractCaosDBServerResource { return SessionToken.generate((Principal) getUser().getPrincipal(), null); } + boolean isAnonymous() { + boolean ret = getUser().hasRole(UserSources.ANONYMOUS_ROLE); + return ret; + } + public int callScript( List<String> commandLine, Integer timeoutMs, List<FileProperties> files, Object authToken) throws Message { diff --git a/src/main/java/caosdb/server/resource/transaction/EntityResource.java b/src/main/java/caosdb/server/resource/transaction/EntityResource.java index 0e78c46a1f20fd870aca972f5c272c38c78438d0..f55c2b7ae0cb736305086ae131c3a3eeac1ba0ab 100644 --- a/src/main/java/caosdb/server/resource/transaction/EntityResource.java +++ b/src/main/java/caosdb/server/resource/transaction/EntityResource.java @@ -99,6 +99,7 @@ public class EntityResource extends AbstractCaosDBServerResource { throws ConnectionException, IOException, SQLException, CaosDBException, NoSuchAlgorithmException, Exception { + final long t1 = System.currentTimeMillis(); if (!this.get) { getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); return null; @@ -112,6 +113,10 @@ public class EntityResource extends AbstractCaosDBServerResource { final Retrieve retrieve = new Retrieve(entityContainer); retrieve.execute(); + final long t2 = System.currentTimeMillis(); + entityContainer + .getTransactionBenchmark() + .addBenchmark(getClass().getSimpleName() + ".httpGetInChildClass", t2 - t1); final Element rootElem = generateRootElement(); entityContainer.addToElement(rootElem); doc.setRootElement(rootElem); diff --git a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java index 7af6ba1ca5ac15868f991e6a2a8c7e85b2847bd7..6fa5d1a479df47c20d9d3a5d3a7134e1c0898ad1 100644 --- a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java +++ b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java @@ -25,7 +25,9 @@ package caosdb.server.scripting; import caosdb.server.CaosDBException; +import caosdb.server.CaosDBServer; import caosdb.server.FileSystem; +import caosdb.server.ServerProperties; import caosdb.server.entity.FileProperties; import caosdb.server.entity.Message; import caosdb.server.utils.ServerMessages; @@ -54,6 +56,7 @@ public class ServerSideScriptingCaller { private ScriptingUtils utils; private List<FileProperties> files; private File workingDir; + private File tmpHome; private File sharedDir; private Object authToken; private Map<String, String> env; @@ -110,6 +113,7 @@ public class ServerSideScriptingCaller { this.authToken = authToken; this.env = env; this.workingDir = workingDir; + this.tmpHome = (new File(getTmpWorkingDir(), "temphome")).getAbsoluteFile(); this.stdOutFile = utils.getStdOutFile(workingDir); this.stdErrFile = utils.getStdErrFile(workingDir); } @@ -122,6 +126,7 @@ public class ServerSideScriptingCaller { createWorkingDir(); putFilesInWorkingDir(files); createSharedDir(); + createTmpHomeDir(); updateEnvironment(); } catch (final Exception e) { e.printStackTrace(); @@ -171,17 +176,41 @@ public class ServerSideScriptingCaller { } // create working dir - getTmpWorkingDir().mkdirs(); + if (!getTmpWorkingDir().mkdirs()) { + throw new Exception("Creating the temporary working dir failed."); + } } - /** Creates a temporary directory for shareing by the script and sets `sharedDir` accordingly. */ + /** + * Create a writable "home like" directory, needed for some Python libs, e.g. caosdb-pylib. + * + * <p>The temporary working directory must exist when this method is called. + */ + void createTmpHomeDir() throws Exception, IOException { + if (!getTmpWorkingDir().exists()) { + throw new Exception( + "The temp working dir (" + + getTmpWorkingDir().toString() + + ") must exist before the home dir can be created."); + } + if (getTmpHomeDir().exists()) { + throw new Exception("The home directory must be non-existing when the caller is invoked."); + } + File srcDir = + new File( + CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_SIDE_SCRIPTING_HOME_DIR)); + FileUtils.copyDirectory(srcDir, this.tmpHome); + } + + /** Creates a temporary directory for sharing by the script and sets `sharedDir` accordingly. */ void createSharedDir() throws Exception { sharedDir = new File(FileSystem.assertDir(null)); } /** Sets environment variables for the script, according to the current state of the caller. */ - void updateEnvironment() { + void updateEnvironment() throws Exception { env.put("SHARED_DIR", sharedDir.toString()); + env.put("HOME", tmpHome.toString()); } void cleanup() { @@ -263,6 +292,10 @@ public class ServerSideScriptingCaller { return this.workingDir; } + File getTmpHomeDir() { + return this.tmpHome; + } + File getSharedDir() { return this.sharedDir; } diff --git a/src/main/java/caosdb/server/utils/FileUtils.java b/src/main/java/caosdb/server/utils/FileUtils.java index 7441e73c1223f39d31ce4017618572486dfd8a64..824646caab6983863acd7d35a276a1213b393235 100644 --- a/src/main/java/caosdb/server/utils/FileUtils.java +++ b/src/main/java/caosdb/server/utils/FileUtils.java @@ -303,17 +303,34 @@ public class FileUtils { return java.nio.file.Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS); } - private static void moveReplace(File file, File target) throws IOException { + private static void moveReplace(File file, File target) + throws IOException, CaosDBException, InterruptedException { if (exists(target)) { org.apache.commons.io.FileUtils.forceDelete(target); } if (isDir(file)) { org.apache.commons.io.FileUtils.moveDirectory(file, target); + } else if (isSymlink(file)) { + moveSymlink(file, target); } else { org.apache.commons.io.FileUtils.moveFile(file, target); } } + /** + * @param oldlink + * @param newlink + * @throws IOException + * @throws InterruptedException + * @throws CaosDBException + */ + private static void moveSymlink(File oldlink, File newlink) + throws IOException, CaosDBException, InterruptedException { + File target = oldlink.toPath().toRealPath().toFile(); + createSymlink(newlink, target); + callPosixUnlink(oldlink); + } + public static Undoable rename(final File file, final File target) throws Message, IOException { final File backup; if (target.getAbsoluteFile().equals(file.getAbsoluteFile())) { @@ -325,16 +342,16 @@ public class FileUtils { public void cleanUp() {} }; } - if (exists(target)) { - // in case this is a update transaction, the old version of the file - // must be stored somewhere until the transaction is done. - final File tmp = getTmpFile(target.getName()); - moveReplace(target, tmp); - backup = tmp; - } else { - backup = null; - } try { + if (exists(target)) { + // in case this is a update transaction, the old version of the file + // must be stored somewhere until the transaction is done. + final File tmp = getTmpFile(target.getName()); + moveReplace(target, tmp); + backup = tmp; + } else { + backup = null; + } moveReplace(file, target); return new UndoHandler() { @@ -383,7 +400,8 @@ public class FileUtils { org.apache.commons.io.FileUtils.forceDelete(file); } - public static Undoable delete(final File file) throws IOException { + public static Undoable delete(final File file) + throws IOException, CaosDBException, InterruptedException { return delete(file, false); } @@ -397,8 +415,11 @@ public class FileUtils { * @param recursive * @return * @throws IOException + * @throws InterruptedException + * @throws CaosDBException */ - public static Undoable delete(final File file, final boolean recursive) throws IOException { + public static Undoable delete(final File file, final boolean recursive) + throws IOException, CaosDBException, InterruptedException { if (file.exists() && file.isFile()) { // delete file final File backup = getTmpFile(file.getName()); @@ -496,18 +517,22 @@ public class FileUtils { return isSymbolicLink(file.toPath()); } - public static Undoable unlink(final File file) - throws IOException, InterruptedException, CaosDBException { - final String target = file.getCanonicalPath(); + private static void callPosixUnlink(File file) throws IOException, InterruptedException { final Process cmd = Runtime.getRuntime().exec("unlink " + file.getAbsolutePath()); if (cmd.waitFor() != 0) { throw new CaosDBException("could not unlink " + file.getAbsolutePath()); } + } + + public static Undoable unlink(final File file) throws IOException, InterruptedException { + callPosixUnlink(file); + + final String path = file.getCanonicalPath(); return new Undoable() { @Override public void undo() { try { - Runtime.getRuntime().exec("ln -s " + target + " " + file.getAbsolutePath()); + Runtime.getRuntime().exec("ln -s " + path + " " + file.getAbsolutePath()); } catch (final IOException e) { e.printStackTrace(); } diff --git a/src/main/java/caosdb/server/utils/Info.java b/src/main/java/caosdb/server/utils/Info.java index bba346716624f3802aac449b86b2191c7cbcee10..445374a105fbe8fbff1bc95a90fcceba197b09f4 100644 --- a/src/main/java/caosdb/server/utils/Info.java +++ b/src/main/java/caosdb/server/utils/Info.java @@ -95,6 +95,18 @@ public class Info extends AbstractObservable implements Observer, TransactionInt return recordTypesCount; } + /** + * Utility function that takes an array of file objects and returns a linked list containing XML + * representations of all files within each contained directory. All files in the array that are + * directories are recursively traversed. This function is used for files within the drop off box + * only. + * + * @param files An array of file objects. + * @return A linked list containing XML elements. The path attribute of these elements is set to + * the absolute path of the filenames where the path to the drop off box is removed at the + * beginning. + * @fixme Should check if the files are inside the DropOffBox path. + */ private static LinkedList<Element> getFlatList(final File[] files) { try { final LinkedList<Element> ret = new LinkedList<Element>(); @@ -104,7 +116,7 @@ public class Info extends AbstractObservable implements Observer, TransactionInt } else { final Element element = new Element("file"); final String tempPath = - file.getAbsolutePath().substring(dropOffBox.getCanonicalPath().length() + 1); + file.getCanonicalPath().substring(dropOffBox.getCanonicalPath().length() + 1); element.setAttribute("path", tempPath); ret.add(element); } @@ -158,6 +170,22 @@ public class Info extends AbstractObservable implements Observer, TransactionInt return toElement(false); } + /** + * Generates an XML element showing the information for the /Info resource in CaosDB. + * + * @return An XML element containing: + * <ul> + * <li>The number of records + * <li>The number of properties + * <li>The number of record types + * <li>The number of files + * <li>The total file size + * <li>The number of temp files + * <li>The path of the DropOffBox + * <li>A tree of files in the DropOffBox + * </ul> + * TODO: The error format for missing or not readable drop off box has to be specified. + */ public static Element toElement(final boolean tree) throws Exception, SQLException { dropOffBox = new File(FileSystem.getDropOffBox()); final Element info = new Element("Stats"); @@ -173,14 +201,20 @@ public class Info extends AbstractObservable implements Observer, TransactionInt } final Element e = new Element("dropOffBox"); if (dropOffBox.isDirectory()) { - if (tree) { - e.setAttribute("path", dropOffBox.getAbsolutePath()); - e.addContent(getTree(dropOffBox.listFiles())); + if (dropOffBox.canRead()) { + if (tree) { + e.setAttribute("path", dropOffBox.getAbsolutePath()); + e.addContent(getTree(dropOffBox.listFiles())); + } else { + e.setAttribute("path", dropOffBox.getAbsolutePath()); + e.addContent(getFlatList(dropOffBox.listFiles())); + } } else { - e.setAttribute("path", dropOffBox.getAbsolutePath()); - e.addContent(getFlatList(dropOffBox.listFiles())); + // TODO: return a message that the DropOffBox is not readable. } } else { + // TODO: This function should at least return a message that the DropOffBox is disabled or not + // present. } info.addContent(counts); info.addContent(e); diff --git a/src/test/java/caosdb/server/Misc.java b/src/test/java/caosdb/server/Misc.java index e94d91513be40459910a05559a6f526bb94e9872..92e8b198bf8cd60b8407dac4e7fcd4e9ed671a25 100644 --- a/src/test/java/caosdb/server/Misc.java +++ b/src/test/java/caosdb/server/Misc.java @@ -43,6 +43,7 @@ import java.util.regex.Pattern; import org.apache.commons.jcs.JCS; import org.apache.commons.jcs.access.CacheAccess; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.config.Ini; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; @@ -316,7 +317,8 @@ public class Misc { @Test public void testShiro() { - final Factory<SecurityManager> factory = new IniSecurityManagerFactory(); + Ini config = CaosDBServer.getShiroConfig(); + final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config); final SecurityManager securityManager = factory.getInstance(); diff --git a/src/test/java/caosdb/server/logging/TestLogging.java b/src/test/java/caosdb/server/logging/TestLogging.java index 35667f35363af5165596ea1f32fa2f96963595aa..6e35d96585683d99c81a0fc2c669d4dc977bc09a 100644 --- a/src/test/java/caosdb/server/logging/TestLogging.java +++ b/src/test/java/caosdb/server/logging/TestLogging.java @@ -51,6 +51,8 @@ public class TestLogging { @Test public void testRequestTimeLogger() { + Assert.assertTrue(CaosDBServer.isDebugMode()); Assert.assertFalse(request_time_logger.isErrorEnabled()); + Assert.assertFalse(request_time_logger.isTraceEnabled()); } } diff --git a/src/test/java/caosdb/server/resource/TestScriptingResource.java b/src/test/java/caosdb/server/resource/TestScriptingResource.java index 3b035fe703409605c59a77df70421e7516230376..8ea1cc2e888ceacd51213ef1d1516ede498a8267 100644 --- a/src/test/java/caosdb/server/resource/TestScriptingResource.java +++ b/src/test/java/caosdb/server/resource/TestScriptingResource.java @@ -23,23 +23,114 @@ package caosdb.server.resource; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import caosdb.server.CaosDBServer; +import caosdb.server.accessControl.AuthenticationUtils; +import caosdb.server.accessControl.CredentialsValidator; +import caosdb.server.accessControl.Principal; +import caosdb.server.accessControl.Role; +import caosdb.server.accessControl.SessionToken; +import caosdb.server.database.BackendTransaction; +import caosdb.server.database.access.Access; +import caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; +import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; +import caosdb.server.database.backend.interfaces.RetrieveRoleImpl; +import caosdb.server.database.backend.interfaces.RetrieveUserImpl; +import caosdb.server.database.exceptions.TransactionException; +import caosdb.server.database.proto.ProtoUser; import caosdb.server.entity.Message; +import caosdb.server.permissions.PermissionRule; import java.io.IOException; import java.util.Date; +import java.util.HashSet; import java.util.List; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.config.Ini; +import org.apache.shiro.config.IniSecurityManagerFactory; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.Factory; +import org.junit.BeforeClass; import org.junit.Test; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Method; +import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; public class TestScriptingResource { + public static class RetrieveRole implements RetrieveRoleImpl { + + public RetrieveRole(Access a) {} + + @Override + public Role retrieve(String role) throws TransactionException { + Role ret = new Role(); + ret.name = "anonymous"; + ret.description = "bla"; + return ret; + } + } + + public static class RetrievePermissionRules implements RetrievePermissionRulesImpl { + + public RetrievePermissionRules(Access a) {} + + @Override + public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException { + return new HashSet<>(); + } + } + + public static class RetrieveUser implements RetrieveUserImpl { + + public RetrieveUser(Access a) {} + + @Override + public ProtoUser execute(Principal principal) throws TransactionException { + return new ProtoUser(); + } + } + + public static class RetrievePasswordValidator implements RetrievePasswordValidatorImpl { + + public RetrievePasswordValidator(Access a) {} + + @Override + public CredentialsValidator<String> execute(String name) throws TransactionException { + return new CredentialsValidator<String>() { + + @Override + public boolean isValid(String credential) { + return true; + } + }; + } + } + + @BeforeClass + public static void setupShiro() throws IOException { + BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRole.class); + BackendTransaction.setImpl(RetrievePermissionRulesImpl.class, RetrievePermissionRules.class); + BackendTransaction.setImpl(RetrieveUserImpl.class, RetrieveUser.class); + BackendTransaction.setImpl( + RetrievePasswordValidatorImpl.class, RetrievePasswordValidator.class); + + CaosDBServer.initServerProperties(); + Ini config = CaosDBServer.getShiroConfig(); + final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config); + + final SecurityManager securityManager = factory.getInstance(); + + SecurityUtils.setSecurityManager(securityManager); + } + ScriptingResource resource = new ScriptingResource() { @Override @@ -60,6 +151,12 @@ public class TestScriptingResource { @Test public void testUnsupportedMediaType() { + Subject user = SecurityUtils.getSubject(); + if (user.isAuthenticated()) { + user.logout(); + } + SessionToken t = SessionToken.generate(new Principal("CaosDB", "user"), null); + user.login(t); Representation entity = new StringRepresentation("asdf"); entity.setMediaType(MediaType.TEXT_ALL); Request request = new Request(Method.POST, "../test", entity); @@ -70,6 +167,23 @@ public class TestScriptingResource { assertEquals(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, resource.getResponse().getStatus()); } + @Test + public void testAnonymous() { + Subject user = SecurityUtils.getSubject(); + user.login(AuthenticationUtils.ANONYMOUS_USER); + assertTrue(resource.isAnonymous()); + Representation entity = new StringRepresentation("asdf"); + entity.setMediaType(MediaType.TEXT_ALL); + Request request = new Request(Method.POST, "../test", entity); + request.setRootRef(new Reference("bla")); + request.getAttributes().put("SRID", "asdf1234"); + request.setDate(new Date()); + resource.init(null, request, new Response(null)); + + resource.handle(); + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getResponse().getStatus()); + } + @Test public void testForm2invocation() throws Message { Form form = diff --git a/src/test/java/caosdb/server/scripting/TestServerSideScriptingCaller.java b/src/test/java/caosdb/server/scripting/TestServerSideScriptingCaller.java index 668d75fbc9271babc8df20a872f661d3bd0a9646..f752c29c2f99b33d30a75ff3e09c7043c92fc464 100644 --- a/src/test/java/caosdb/server/scripting/TestServerSideScriptingCaller.java +++ b/src/test/java/caosdb/server/scripting/TestServerSideScriptingCaller.java @@ -492,6 +492,9 @@ public class TestServerSideScriptingCaller extends CaosDBTestClass { public void putFilesInWorkingDir(final Collection<FileProperties> files) throws FileNotFoundException, CaosDBException {} + @Override + public void createTmpHomeDir() throws Exception {} + @Override public int callScript() throws IOException { throw new IOException(); @@ -609,4 +612,76 @@ public class TestServerSideScriptingCaller extends CaosDBTestClass { null, -1, null, "authToken", emptyEnv, new ScriptingUtils(), this.pwd); caller.cleanup(); } + + /** Does the order of directory creation matter? */ + @Test + public void testDirectoriesInWrongOrder() throws Message { + final String[] cmd = {testExecutable.getAbsolutePath()}; + final ServerSideScriptingCaller caller = + new ServerSideScriptingCaller( + cmd, -1, null, null, emptyEnv, new ScriptingUtils(), this.pwd); + this.exception.expect( + new BaseMatcher<Exception>() { + + @Override + public boolean matches(final Object item) { + return item == ServerMessages.SERVER_SIDE_SCRIPT_ERROR; + } + + @Override + public void describeTo(final Description description) { + description.appendValue(ServerMessages.SERVER_SIDE_SCRIPT_ERROR); + } + }); + try { + caller.createTmpHomeDir(); + caller.createWorkingDir(); + } catch (final Exception e) { + e.printStackTrace(); + throw ServerMessages.SERVER_SIDE_SCRIPT_ERROR; + } + caller.cleanup(); + } + + /** Is the new home directory created correctly? */ + @Test + public void testWorkingDirCreation() throws Exception { + final String[] cmd = {testExecutable.getAbsolutePath()}; + final ServerSideScriptingCaller caller = + new ServerSideScriptingCaller( + cmd, -1, null, null, emptyEnv, new ScriptingUtils(), this.pwd); + caller.createWorkingDir(); + caller.createTmpHomeDir(); + assertTrue(caller.getTmpHomeDir().exists()); + caller.cleanup(); + } + + /** Does copying files over to the new home directory work? */ + @Test + public void testWorkingDirCopying() throws Exception { + final String[] cmd = {testExecutable.getAbsolutePath()}; + final ServerSideScriptingCaller caller = + new ServerSideScriptingCaller( + cmd, -1, null, null, emptyEnv, new ScriptingUtils(), this.pwd); + caller.createWorkingDir(); + // Create source folder in working directory + String scriptingHomeDir = CaosDBServer.getServerProperty("SERVER_SIDE_SCRIPTING_HOME_DIR"); + File tempHomeDirSrc = new File(caller.getTmpWorkingDir(), "home_source"); + CaosDBServer.setProperty("SERVER_SIDE_SCRIPTING_HOME_DIR", tempHomeDirSrc.toString()); + tempHomeDirSrc.mkdir(); + String testFileName = "testfile.txt"; + String testContent = "sloikdfgu89w4t78930tvgyum9q238456t27ht2"; + File testFile = new File(tempHomeDirSrc, testFileName); + // testFile.createNewFile(); + FileUtils.write(testFile, testContent, "UTF-8"); + // Attempt to copy the source to the new home + caller.createTmpHomeDir(); + // Compare source and new home + File expectedFile = new File(caller.getTmpHomeDir(), testFileName); + assertTrue(expectedFile.exists()); + String expectedFileContent = FileUtils.readFileToString(expectedFile, "UTF-8"); + assertEquals(expectedFileContent, testContent); + caller.cleanup(); + CaosDBServer.setProperty("SERVER_SIDE_SCRIPTING_HOME_DIR", scriptingHomeDir); + } } diff --git a/src/test/java/caosdb/server/utils/FileUtilsTest.java b/src/test/java/caosdb/server/utils/FileUtilsTest.java index 183f5cf834783f7ad9c6e63bcdb25df1c7870822..c80f8ebb8b71785dcf8b6f5c8adaaec5e61b0332 100644 --- a/src/test/java/caosdb/server/utils/FileUtilsTest.java +++ b/src/test/java/caosdb/server/utils/FileUtilsTest.java @@ -69,9 +69,11 @@ public class FileUtilsTest { public static File tmpFolderCaosDB; @BeforeClass - public static void setup() throws Message, IOException { + public static void setup() throws Message, IOException, CaosDBException, InterruptedException { CaosDBServer.initServerProperties(); testRoot = tempFolder.newFolder("fileutils_testfolder"); + File basePath = tempFolder.newFolder("caosdbRoot"); + File dropOffBox = tempFolder.newFolder("dropOffBox"); someDir = testRoot.toPath().resolve("some_dir").toFile(); linkToSomeDir = testRoot.toPath().resolve("link_to_some_dir").toFile(); someFile = testRoot.toPath().resolve("some_file").toFile(); @@ -79,12 +81,15 @@ public class FileUtilsTest { linkToSomeFile = testRoot.toPath().resolve("link_to_some_file").toFile(); tmpFolderCaosDB = tempFolder.newFolder(); + CaosDBServer.setProperty(ServerProperties.KEY_FILE_SYSTEM_ROOT, basePath.toString()); + CaosDBServer.setProperty(ServerProperties.KEY_DROP_OFF_BOX, dropOffBox.toString()); CaosDBServer.setProperty(ServerProperties.KEY_TMP_FILES, tmpFolderCaosDB.toString()); FileSystem.init(); - Assert.assertTrue(new File(FileSystem.getBasepath()).canWrite()); - Assert.assertTrue(new File(FileSystem.getBasepath()).canRead()); - Assert.assertTrue(new File(FileSystem.getBasepath()).canExecute()); + basePath = new File(FileSystem.getBasepath()); + Assert.assertTrue(basePath.canWrite()); + Assert.assertTrue(basePath.canRead()); + Assert.assertTrue(basePath.canExecute()); Assert.assertTrue(new File(FileSystem.getTmp()).canWrite()); Assert.assertTrue(new File(FileSystem.getTmp()).canRead()); Assert.assertTrue(new File(FileSystem.getTmp()).canExecute()); @@ -102,7 +107,7 @@ public class FileUtilsTest { } @AfterClass - public static void teardown() throws IOException { + public static void teardown() throws IOException, CaosDBException, InterruptedException { System.err.println("teardown"); FileUtils.delete(testRoot, true); deleteTmp(); @@ -110,9 +115,13 @@ public class FileUtilsTest { // assertEquals(0, new File(FileSystem.getTmp()).listFiles().length); } - /** @fixme Currently still fails due to https://github.com/junit-team/junit4/issues/1223 */ + /** + * @throws InterruptedException + * @throws CaosDBException + * @fixme Currently still fails due to https://github.com/junit-team/junit4/issues/1223 + */ @Before - public void testTmpEmpty() throws IOException { + public void testTmpEmpty() throws IOException, CaosDBException, InterruptedException { if (new File(FileSystem.getTmp()).exists()) { if (0 != new File(FileSystem.getTmp()).list().length) { System.err.println("TMP not empty, aborting test!"); @@ -128,7 +137,7 @@ public class FileUtilsTest { } } - public static void deleteTmp() throws IOException { + public static void deleteTmp() throws IOException, CaosDBException, InterruptedException { File tmpDir = new File(FileSystem.getTmp()); if (tmpDir.exists()) for (File f : tmpDir.listFiles()) { @@ -138,7 +147,7 @@ public class FileUtilsTest { } @After - public void callDeleteTmp() throws IOException { + public void callDeleteTmp() throws IOException, CaosDBException, InterruptedException { deleteTmp(); }