diff --git a/.gitignore b/.gitignore
index 39be17dc807dc3e2f5c505dc9341027457403421..06c8c148f1e9f45493f574a11c6789398246defd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,6 @@ log/
 OUTBOX
 ConsistencyTest.xml
 testlog/
+
+# python
+__pycache__
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7161569a7e43017869d9bb0025c35388db6c3623..34e93fd70f3252e3dd04b2df14e06a63299d940c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -26,10 +26,6 @@ variables:
   CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb-server-testenv:latest
   # When using dind, it's wise to use the overlayfs driver for
   # improved performance.
-  DOCKER_DRIVER: overlay2
-
-services:
-  - docker:19.03-dind
 
 image: $CI_REGISTRY_IMAGE
 stages:
@@ -42,6 +38,7 @@ test:
   tags: [ docker ]
   stage: test
   script:
+    - make test_misc
     - make easy-units
     - mvn dependency:purge-local-repository
     - mvn antlr4:antlr4
@@ -55,20 +52,22 @@ trigger_build:
   stage: deploy
   script:
     - /usr/bin/curl -X POST
-      -F token=8f29e5eeb7db2123d9c2bb84634da2
+      -F token=$DEPLOY_TRIGGER_TOKEN
       -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:
-  tags: [ docker ]
+  tags: [ cached-dind ]
   image: docker:19.03
   stage: setup
+  only:
+    - schedules
   script:
     - cd src/test/docker
-    - docker login -u testuser -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+    - docker login -u indiscale -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
       # use here general latest or specific branch latest...
     - docker pull $CI_REGISTRY_IMAGE || true
     - docker build
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d8a87996586347cb32457bfbb06e524f380098b..8b65bafc9f260dabecd592fea4f08829f08db7c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
 
 All notable changes to this project will be documented in this file.
 
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
@@ -11,16 +11,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - 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.
+- [bend_symlinks.sh](misc/bend_symlinks/bend_symlinks.sh) (version 0.1, experimental)
+  fix broken symlinks in the internal file system. See
+  [README.md](misc/bend_symlinks/README.md)
+- [move_files.py](misc/move_files/move_files.py) (version 0.1, experimental)
+  Script for moving files (change their path) in the internal file system based
+  on a two-column tsv file (with columns "from" and "to"). See
+  [README.md](misc/move_files/README.md).
 
 ### Changed
 
-- 
-
+- The sever by default now only serves TLS 1.2 and 1.3, all previous versions
+  have been disabled in the default settings.  Make sure that your clients
+  (especially the Python client) are up to date.
 
 ### Deprecated
 
 - 
 
+### Removed
+
+- 
 
 ### Fixed
 
@@ -29,10 +40,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - NaN Double Values (see #41)
 
-
+- #14 - Handle files on file system without File entity: Those entries are
+  returned without ID but with a notice now.
 
 ### Security (in case of vulnerabilities)
 
+- TLS is by default restricted to v1.2 and v1.3 now.
+
+
 ## [0.1.0] - 2018-10-09
 
 Tag `v0.1` - Commit 3b17b49
diff --git a/caosdb-webui b/caosdb-webui
index 88566d42a698e939832fbff535a04fb5f12eec6b..b6677e848ed30cadeda5d1782bd4376ec6546031 160000
--- a/caosdb-webui
+++ b/caosdb-webui
@@ -1 +1 @@
-Subproject commit 88566d42a698e939832fbff535a04fb5f12eec6b
+Subproject commit b6677e848ed30cadeda5d1782bd4376ec6546031
diff --git a/conf/core/cache.ccf b/conf/core/cache.ccf
index 87d1338a5cf70da9b551744bc154190965d4bb46..821e5d7862efb21e0aa13f8410886c6c14b10a7c 100644
--- a/conf/core/cache.ccf
+++ b/conf/core/cache.ccf
@@ -1,3 +1,51 @@
+# 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
+jcs.region.BACKEND_UserRoles.cacheattributes.MaxObjects=101
+
+jcs.region.BACKEND_Users
+jcs.region.BACKEND_Users.cacheattributes.MaxObjects=102
+
+jcs.region.BACKEND_PermissionRules
+jcs.region.BACKEND_PermissionRules.cacheattributes.MaxObjects=1001
+
+jcs.region.BACKEND_EntityProperties
+jcs.region.BACKEND_EntityProperties.cacheattributes.MaxObjects=1003
+
+jcs.region.BACKEND_EntityParents
+jcs.region.BACKEND_EntityParents.cacheattributes.MaxObjects=1004
+
+jcs.region.BACKEND_SparseFileRecordsByPath
+jcs.region.BACKEND_SparseFileRecordsByPath.cacheattributes.MaxObjects=1005
+
+jcs.region.BACKEND_JobRules
+jcs.region.BACKEND_JobRules.cacheattributes.MaxObjects=103
+
+jcs.region.BACKEND_SparseEntities
+jcs.region.BACKEND_SparseEntities.cacheattributes.MaxObjects=1002
+
+
+# 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
+jcs.region.PAM_UnixUserGroups.elementattributes.IsEternal=false
+jcs.region.PAM_UnixUserGroups.elementattributes.MaxLife=601
+jcs.region.PAM_UnixUserGroups.elementattributes.IdleTime=61
+
+# PAM_UnixUserExists
+jcs.region.PAM_UnixUserExists
+jcs.region.PAM_UnixUserExists.elementattributes.IsEternal=false
+jcs.region.PAM_UnixUserExists.elementattributes.MaxLife=599
+jcs.region.PAM_UnixUserExists.elementattributes.IdleTime=59
+
+# PAM_Authentication
+jcs.region.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 00b3b1a33dbab19f9931424cc91b5b11f9d137e3..c3cf62bafe6951f1b1412e648ea2eb10f469a2a4 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -26,10 +26,10 @@ CONTEXT_ROOT=
 SERVER_PORT_HTTPS=443
 SERVER_PORT_HTTP=80
 
-HTTPS_ENABLED_PROTOCOLS=TLSv1.2 TLSv1.1 TLSv1
-HTTPS_DISABLED_PROTOCOLS=SSLv3 SSLv2Hello
-HTTPS_ENABLED_CIPHER_SUITES=TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDH_RSA_WITH_AES_256_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDH_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
-HTTPS_DISABLED_CIPHER_SUITES=SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_RC4_128_SHA TLS_DHE_RSA_WITH_AES_256_CBC_SHA TLS_DHE_DSS_WITH_AES_256_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+HTTPS_ENABLED_PROTOCOLS=TLSv1.3 TLSv1.2
+HTTPS_DISABLED_PROTOCOLS=SSLv3 SSLv2Hello TLSv1.1 TLSv1.0
+HTTPS_ENABLED_CIPHER_SUITES=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_CCM_SHA256 TLS_AES_128_CCM_8_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+HTTPS_DISABLED_CIPHER_SUITES=TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDH_RSA_WITH_AES_256_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDH_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHASSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_RC4_128_SHA TLS_DHE_RSA_WITH_AES_256_CBC_SHA TLS_DHE_DSS_WITH_AES_256_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
 
 INITIAL_CONNECTIONS=1
 
@@ -50,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=
@@ -70,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 688675fe7a1f73e113f9668b3772f470f365e4f1..ea03f993e5de1dbc19606e3dae4804b0e4c6bf7a 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
@@ -33,7 +36,7 @@ run: compile
 	mvn exec:java@run
 
 run-debug: jar
-	java -Dcaosdb.debug=true -jar target/caosdb-server-0.1-SNAPSHOT-jar-with-dependencies.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,18 @@ 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
+
+test_misc:
+	cd misc/bend_symlinks/ && /bin/bash -c /usr/bin/shunit2 test/test_suite.sh
 
 clean: clean-antlr
 	mvn clean
@@ -105,4 +111,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/misc/bend_symlinks/README.md b/misc/bend_symlinks/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f72357f8636a2cf961434108a52e98ef05181e62
--- /dev/null
+++ b/misc/bend_symlinks/README.md
@@ -0,0 +1,106 @@
+# About
+
+./bend_symlinks.sh - fix broken symlinks in the internal file system
+
+# Copyright and License Disclaimer:
+
+    Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+    Copyright (C) 2019 IndiScale (info@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/>.
+
+
+# Version
+    0.1
+
+# Usage
+
+    ./bend_symlinks.sh [-D] FILE_SYSTEM_DIR TARGET_PATTERN TARGET_REPLACEMENT
+
+        Find all broken symlinks below FILE_SYSTEM_DIR which targets match
+        TARGET_PATTERN and replace the targets (i.e. overwrite the symlinks)
+        with TARGET_REPLACEMENT according to the rules of sed's replace
+        function.
+
+        Print OLD_TARGET<tab>NEW_TARGET to stdout for each replaced symlink.
+
+    ./bend_symlinks.sh -d [-D] FILE_SYSTEM_DIR OLD_TARGET_PREFIX NEW_TARGET_PREFIX
+
+        Find all broken symlinks below FILE_SYSTEM_DIR which targets a
+        path prefixed by OLD_TARGET_PREFIX and replace the targets (i.e.
+        overwrite the symlinks) by changing only the directory prefix to
+        NEW_TARGET_PREFIX. This is the preferred way to fix symlinks which
+        targets have just been moved to another directory while the structure
+        under this directory stayed the same.
+
+        Print OLD_TARGET<tab>NEW_TARGET to stdout for each replaced symlink.
+
+    ./bend_symlinks.sh (-h|-v)
+
+# Parameters
+
+    FILE_SYSTEM_DIR     A directory of the internal file system's back-end
+                        storage.
+                        All symlinks below this directory are being process by
+                        this script.
+                        E.g. '/mnt/caosdb_fs/ExperimentalData/'
+    TARGET_PATTERN      A (extended sed-style) regular expression for matching
+                        broken symlink targets.
+    TARGET_REPLACEMENT  A (sed-style) replacement string for the new (fixed)
+                        symlink targets.
+    OLD_TARGET_PREFIX   The directory of old and broken symlink targets.
+                        E.g. '/mnt/data/current/experiments/'
+    NEW_TARGET_PREFIX   The directory of the new symlink targets.
+                        E.g. '/mnt/data/archive/experiments/2019/'
+    -d                  Bend all symlinks under FILE_SYSTEM_DIR which target a
+                        file prefixed by OLD_TARGET_PREFIX to point to
+                        NEW_TARGET_PREFIX and keep the substructure the same.
+                        This is the most useful special case for the scenario
+                        where orginal data files have just been moved from one
+                        folder into another and the symlinks need to updated
+                        accordingly.
+    -D                  Dry-run: Only print what would happen.
+    -h                  Print this help message and exit.
+    -v                  Print the version of this script and ext.
+
+# Examples
+
+  1. Files have been moved from '/mnt/data/current/experiments/' to
+     /mnt/data/archive/experiments/2019/'.  Execute the script in the root
+     directory of the caosdb server's internal file system:
+
+       $ ./bend_symlinks.sh -d ./ /mnt/data/current/experiments /mnt/data/archive/experiments/2019
+
+  2. A File was renamed from '/mnt/data/procotol.pdf' to
+     '/mnt/data/protocol.pdf'.  The symlink is located at
+     '/mnt/caosdb_fs/procotol.pdf'.  Execute the script in the root directory
+     of the caosdb server's internal file system:
+
+       $ ./bend_symlinks.sh ./ procotol\.pdf$ protocol.pdf
+
+  3. In order to print a table which contains the corrected name from example 2
+     and which can be understood by the the
+     [move_files.py](../move_files/move_files.py) script pipe the standard
+     output like this.
+
+       $ ./bend_symlinks.sh ./ procotol\.pdf$ protocol.pdf | sed -e 's/\/mnt\/data// > changes.tsv
+
+     Then the changes.tsv file contains 'procotol.pdf<tab>protocol.pdf<EOF>'.
+
+
+# Tests
+
+Run test suite with
+
+    $ shunit2 test/test_suite.sh
diff --git a/misc/bend_symlinks/bend_symlinks.sh b/misc/bend_symlinks/bend_symlinks.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5a296fcdf7c447619751b958b6dc411273e9c951
--- /dev/null
+++ b/misc/bend_symlinks/bend_symlinks.sh
@@ -0,0 +1,132 @@
+#!/bin/bash
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+# Copyright (C) 2019 IndiScale (info@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
+
+set -o errexit -o noclobber -o nounset -o pipefail
+
+VERSION=0.1
+
+LICENCE="# Copyright and License Disclaimer:
+
+    Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+    Copyright (C) 2019 IndiScale (info@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/>.
+"
+
+USAGE="# Usage
+
+    $0 [-D] FILE_SYSTEM_DIR TARGET_PATTERN TARGET_REPLACEMENT
+
+        Find all broken symlinks below FILE_SYSTEM_DIR which targets match
+        TARGET_PATTERN and replace the targets (i.e. overwrite the symlinks)
+        with TARGET_REPLACEMENT according to the rules of sed's replace
+        function.
+
+        Print OLD_TARGET<tab>NEW_TARGET to stdout for each replaced symlink.
+
+    $0 -d [-D] FILE_SYSTEM_DIR OLD_TARGET_PREFIX NEW_TARGET_PREFIX
+
+        Find all broken symlinks below FILE_SYSTEM_DIR which targets a
+        path prefixed by OLD_TARGET_PREFIX and replace the targets (i.e.
+        overwrite the symlinks) by changing only the directory prefix to
+        NEW_TARGET_PREFIX. This is the preferred way to fix symlinks which
+        targets have just been moved to another directory while the structure
+        under this directory stayed the same.
+
+        Print OLD_TARGET<tab>NEW_TARGET to stdout for each replaced symlink.
+
+    $0 (-h|-v)
+
+# Parameters
+
+    FILE_SYSTEM_DIR     A directory of the internal file system's back-end
+                        storage.
+                        All symlinks below this directory are being process by
+                        this script.
+                        E.g. '/mnt/caosdb_fs/ExperimentalData/'
+    TARGET_PATTERN      A (extended sed-style) regular expression for matching
+                        broken symlink targets.
+    TARGET_REPLACEMENT  A (sed-style) replacement string for the new (fixed)
+                        symlink targets.
+    OLD_TARGET_PREFIX   The directory of old and broken symlink targets.
+                        E.g. '/mnt/data/current/experiments/'
+    NEW_TARGET_PREFIX   The directory of the new symlink targets.
+                        E.g. '/mnt/data/archive/experiments/2019/'
+    -d                  Bend all symlinks under FILE_SYSTEM_DIR which target a
+                        file prefixed by OLD_TARGET_PREFIX to point to
+                        NEW_TARGET_PREFIX and keep the substructure the same.
+                        This is the most useful special case for the scenario
+                        where orginal data files have just been moved from one
+                        folder into another and the symlinks need to updated
+                        accordingly.
+    -D                  Dry-run: Only print what would happen.
+    -h                  Print this help message and exit.
+    -v                  Print the version of this script and ext.
+
+# Examples
+
+  1. Files have been moved from '/mnt/data/current/experiments/' to
+     /mnt/data/archive/experiments/2019/'.  Execute the script in the root
+     directory of the caosdb server's internal file system:
+
+       $ $0 -d ./ /mnt/data/current/experiments /mnt/data/archive/experiments/2019
+
+  2. A File was renamed from '/mnt/data/procotol.pdf' to
+     '/mnt/data/protocol.pdf'.  The symlink is located at
+     '/mnt/caosdb_fs/procotol.pdf'.  Execute the script in the root directory
+     of the caosdb server's internal file system:
+
+       $ $0 ./ procotol\\.pdf$ protocol.pdf
+
+  3. In order to print a table which contains the corrected name from example 2
+     and which can be understood by the the
+     [move_files.py](../move_files/move_files.py) script pipe the standard
+     output like this.
+
+       $ $0 ./ procotol\\.pdf$ protocol.pdf | sed -e 's/\/mnt\/data// > changes.tsv
+
+     Then the changes.tsv file contains 'procotol.pdf<tab>protocol.pdf<EOF>'.
+"
+
+HELP="$0 - fix broken symlinks in the internal file system
+
+$LICENCE
+
+# Version
+    $VERSION
+
+$USAGE
+"
+
+source src/main.sh
diff --git a/misc/bend_symlinks/src/main.sh b/misc/bend_symlinks/src/main.sh
new file mode 100644
index 0000000000000000000000000000000000000000..c2a6a94766d437e41619c2602d1c62417e09ee42
--- /dev/null
+++ b/misc/bend_symlinks/src/main.sh
@@ -0,0 +1,98 @@
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+# Copyright (C) 2019 IndiScale (info@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
+
+source "src/utils.sh"
+
+### PARSE COMMAND LINE ARGUMENTS ###
+
+# OPTIONS -v -h
+IS_DRY_RUN=0
+IS_MOVE=0
+while getopts ":hvdD" opt; do
+  case ${opt} in
+    h )
+      echo "$HELP"
+      exit 0
+      ;;
+    v )
+      echo "$VERSION"
+      exit 0
+      ;;
+    d )
+      IS_MOVE=1
+      ;;
+    D )
+      IS_DRY_RUN=1
+      ;;
+    \? )
+      echo "Invalid option. See '$0 -h' for more information" 1>&2
+      exit 1
+      ;;
+  esac
+done
+shift $((OPTIND -1))
+
+
+# POSTIONAL ARGUMENTS
+if [ $# -ne 3 ] ; then
+    echo "Illegal number of positional parameters. See '$0 -h' for more information" 1>&2
+    exit 1
+fi
+FILE_SYSTEM_ROOT=$1
+REGEX_OLD=$2
+REPLACEMENT=$3
+
+
+if [ $IS_MOVE -eq 1 ] ; then
+    REGEX_OLD=$(old_dir "$REGEX_OLD")
+    REPLACEMENT=$(new_dir "$REPLACEMENT")
+fi
+
+set -o noglob
+for syml in $(find -P $(realpath $FILE_SYSTEM_ROOT) -type l) ; do
+  OLD_TARGET=$(realpath -m "$syml" | sed -n -r "/$REGEX_OLD/p")
+  if [ -z "$OLD_TARGET" ] ; then
+    # filter non matching
+    continue
+  fi
+
+  if [ -e "$syml" ] ; then
+    echo "#IGNORING (not broken): $syml" 1>&2
+    continue
+  fi
+
+  NEW_TARGET=$(echo "$OLD_TARGET" | sed -r "s/$REGEX_OLD/$REPLACEMENT/g")
+  if [ ! -e "$NEW_TARGET" ] ; then
+    echo "#IGNORING (broken new): $NEW_TARGET" 1>&2
+    continue
+  fi
+
+  echo -e "$OLD_TARGET\t$NEW_TARGET"
+  if [ $IS_DRY_RUN -eq 1 ] ; then
+    continue
+  fi
+
+  # -f means force overwriting
+  ln -fs "$NEW_TARGET" "$syml" ;
+
+done
+set +o noglob
+
diff --git a/misc/bend_symlinks/src/utils.sh b/misc/bend_symlinks/src/utils.sh
new file mode 100644
index 0000000000000000000000000000000000000000..0e10fe9acc0c1c27fa3f2f58add8e7daf845e24c
--- /dev/null
+++ b/misc/bend_symlinks/src/utils.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2019-2020 Timm Fitschen (t.fitschen@indiscale.com)
+# Copyright (C) 2019-2020 IndiScale GmbH (info@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
+#
+set -o errexit -o noclobber -o nounset -o pipefail
+
+function escape_simple_path () {
+    SPATH=$(escape_slash "$1")
+    # ?
+    SPATH=$(echo "$SPATH" | sed "s/\?/\\?/g")
+    # .
+    SPATH=$(echo "$SPATH" | sed "s/\./\\\\./g")
+    # $
+    SPATH=$(echo "$SPATH" | sed "s/\\$/\\\\$/g")
+    # [
+    SPATH=$(echo "$SPATH" | sed -r "s/\[/\\\\[/g")
+    # (
+    SPATH=$(echo "$SPATH" | sed -r "s/\(/\\\\(/g")
+    # {
+    SPATH=$(echo "$SPATH" | sed -r "s/\{/\\\\{/g")
+    echo "$SPATH"
+}
+
+function escape_slash () {
+    echo "${1//\//\\\/}"
+}
+
+
+function old_dir () {
+    OLD_DIR=$(realpath -m "$1")
+    OLD_DIR=$(escape_simple_path "$OLD_DIR")
+    echo "^$OLD_DIR\/(.*)$"
+}
+
+function new_dir () {
+    NEW_DIR=$(realpath -m "$1")
+    NEW_DIR=$(escape_slash "$NEW_DIR")
+    echo "$NEW_DIR\/\1"
+}
diff --git a/misc/bend_symlinks/test/test_suite.sh b/misc/bend_symlinks/test/test_suite.sh
new file mode 100644
index 0000000000000000000000000000000000000000..781e477e1ffacda36b12687ec05ebf5c9d021e15
--- /dev/null
+++ b/misc/bend_symlinks/test/test_suite.sh
@@ -0,0 +1,229 @@
+
+
+source "./src/utils.sh"
+set +o errexit
+
+BEND=./bend_symlinks.sh
+FILE_SYSTEM_ROOT=test_dir/links
+DATA_DIR=test_dir/original
+
+oneTimeSetUp () {
+    mkdir -p $FILE_SYSTEM_ROOT $DATA_DIR
+}
+
+oneTimeTearDown () {
+    rm -rf test_dir
+}
+
+tearDown () {
+    rm -rf $FILE_SYSTEM_ROOT/*
+    rm -rf $DATA_DIR/*
+}
+
+_make_test_file () {
+    touch "$DATA_DIR/$1"
+    ln -s $(realpath "$DATA_DIR/$1") "$FILE_SYSTEM_ROOT/$1"
+    assertEquals "initial target $1" $(realpath "$FILE_SYSTEM_ROOT/$1") $(realpath "$DATA_DIR/$1")
+}
+
+_break_link_move_file () {
+    set -o noglob
+    OLD_PATH="$DATA_DIR/$1"
+    OLD_PATH_REAL=$(realpath "$OLD_PATH")
+    NEW_PATH="$DATA_DIR/$2"
+    NEW_PATH_REAL=$(realpath "$NEW_PATH")
+    LINK="$FILE_SYSTEM_ROOT/$1"
+    mv "$OLD_PATH_REAL" "$NEW_PATH_REAL"
+    assertEquals "still target $OLD_PATH_REAL" $(realpath "$LINK") "$OLD_PATH_REAL"
+    assertFalse "$LINK link is broken" "[ -f '$LINK' ]"
+    assertFalse "$OLD_PATH_REAL was moved" "[ -f '$OLD_PATH_REAL' ]"
+    assertTrue "$NEW_PATH_REAL is there" "[ -f '$NEW_PATH_REAL' ]"
+    set +o noglob
+}
+
+testVersion () {
+    assertEquals "version 0.1" "0.1" "$($BEND -v)"
+}
+
+assertLinkOk () {
+    set -o noglob
+    LINK=$(realpath "$FILE_SYSTEM_ROOT/$1")
+    TARGET=$(realpath "$DATA_DIR/$2")
+    assertTrue "target exists $LINK" "[ -f '$LINK' ]"
+    assertEquals "target matches $TARGET" $TARGET "$LINK"
+    set +o noglob
+}
+
+testIgnoreUnbroken () {
+    _make_test_file "fileA"
+    RESULTS=$($BEND $FILE_SYSTEM_ROOT "fileA" "fileA.new" 2>&1) # attempt to rename
+
+    assertEquals "ignoring not broken" "#IGNORING (not broken): $(realpath "$PWD")/test_dir/links/fileA" "$RESULTS"
+
+    assertLinkOk "fileA" "fileA"
+}
+
+testIgnoreMissingNew () {
+    _make_test_file "fileA"
+
+    _break_link_move_file "fileA" "fileA.new"
+
+    RESULTS=$($BEND $FILE_SYSTEM_ROOT "fileA" "fileA.non" 2>&1)
+
+    assertEquals "ignoring broken new" "#IGNORING (broken new): $(realpath $DATA_DIR)/fileA.non" "$RESULTS"
+
+    TARGET=$(realpath -m "$DATA_DIR/fileA")
+    LINK=$(realpath -m "$FILE_SYSTEM_ROOT/fileA")
+    assertFalse "symlink still broken" "[ -e '$LINK' ]"
+    assertEquals "target still old" "$TARGET" "$LINK"
+}
+
+
+testFileName () {
+    _make_test_file "fileA"
+    _make_test_file "fileB"
+
+    _break_link_move_file "fileA" "fileA.new"
+
+    RESULTS=$($BEND $FILE_SYSTEM_ROOT "fileA" "fileA.new" 2>&1) # rename all fileA to fileA.new
+
+    assertLinkOk "fileA" "fileA.new"
+    assertLinkOk "fileB" "fileB"
+}
+
+testFullPath () {
+    _make_test_file "fileA"
+    _make_test_file "fileB"
+
+    _break_link_move_file "fileA" "fileA.new"
+
+    REGEX_OLD=$(escape_simple_path "$DATA_DIR/fileA")
+    REPLACEMENT=$(escape_slash "$DATA_DIR/fileA.new")
+    RESULTS=$($BEND $FILE_SYSTEM_ROOT "$REGEX_OLD" "$REPLACEMENT" 2>&1)
+
+    assertLinkOk "fileA" "fileA.new"
+    assertLinkOk "fileB" "fileB"
+}
+
+_testFullPathWithStrageChars () {
+    file_name="fileA$1bla"
+    _make_test_file "$file_name"
+
+    _break_link_move_file "$file_name" "$file_name.new"
+
+    REGEX_OLD=$(escape_simple_path "$DATA_DIR/$file_name")
+    REPLACEMENT=$(escape_slash "$DATA_DIR/$file_name.new")
+    RESULTS=$($BEND "$FILE_SYSTEM_ROOT" "$REGEX_OLD" "$REPLACEMENT" 2>&1)
+
+    assertLinkOk "$file_name" "$file_name.new"
+    assertLinkOk "fileB" "fileB"
+}
+
+testFullPathWithStrangeChars () {
+    _make_test_file "fileB"
+    _testFullPathWithStrageChars "A"
+    _testFullPathWithStrageChars "#"
+    _testFullPathWithStrageChars "0"
+    _testFullPathWithStrageChars "!"
+    _testFullPathWithStrageChars "."
+    _testFullPathWithStrageChars ";"
+    _testFullPathWithStrageChars ","
+    _testFullPathWithStrageChars "$"
+    _testFullPathWithStrageChars "["
+    _testFullPathWithStrageChars "("
+    _testFullPathWithStrageChars "{"
+    _testFullPathWithStrageChars "]"
+    _testFullPathWithStrageChars "[.]"
+}
+
+testRegex () {
+    _make_test_file "fileA.0"
+    _make_test_file "dataA.1"
+    _make_test_file "fileA.0ok"
+    _make_test_file "fileB"
+
+    _break_link_move_file "fileA.0" "file0-A"
+    _break_link_move_file "dataA.1" "data1-A"
+
+    RESULTS=$($BEND "$FILE_SYSTEM_ROOT" "([a-z]+)([A-Z])+\.([01])$" "\1\3-\2" 2>&1)
+
+    assertLinkOk "fileA.0" "file0-A"
+    assertLinkOk "dataA.1" "data1-A"
+    assertLinkOk "fileB" "fileB"
+    assertLinkOk "fileA.0ok" "fileA.0ok"
+}
+
+
+testDryRun () {
+    _make_test_file "fileA"
+
+    OLD_TARGET=$(realpath "$DATA_DIR/fileA")
+    NEW_TARGET=$(realpath "$DATA_DIR/fileA.new")
+
+    SYMLINK=$(realpath "$FILE_SYSTEM_ROOT/fileA")
+    assertTrue "1 target exists $SYMLINK" "[ -f '$SYMLINK' ]"
+    assertEquals "1 target matches $OLD_TARGET" "$OLD_TARGET" "$SYMLINK"
+
+    _break_link_move_file "fileA" "fileA.new"
+
+    SYMLINK=$(realpath "$FILE_SYSTEM_ROOT/fileA")
+    assertFalse "2 target does not exist $SYMLINK" "[ -f '$SYMLINK' ]"
+    assertEquals "2 target matches $OLD_TARGET" "$OLD_TARGET" "$SYMLINK"
+
+    RESULTS=$($BEND -D "$FILE_SYSTEM_ROOT" "fileA" "fileA.new" 2>&1)
+
+    SYMLINK=$(realpath "$FILE_SYSTEM_ROOT/fileA")
+    assertFalse "3 target does not exist $SYMLINK" "[ -f '$SYMLINK' ]"
+    assertEquals "3 target matches $OLD_TARGET" $OLD_TARGET "$SYMLINK"
+
+    RESULTS=$($BEND "$FILE_SYSTEM_ROOT" "fileA" "fileA.new")
+
+    SYMLINK=$(realpath "$FILE_SYSTEM_ROOT/fileA")
+    assertTrue "4 target exists $SYMLINK" "[ -f '$SYMLINK' ]"
+    assertEquals "4 target matches $NEW_TARGET" $NEW_TARGET "$SYMLINK"
+
+}
+
+testUtilsOldAndNewDir () {
+    OLD_DIR="/root/to/old/"
+    assertTrue "old root does not exist" "[ ! -e '$OLD_DIR' ]"
+    NEW_DIR=$(realpath -m "$FILE_SYSTEM_ROOT/root/to/new/")
+    # new_dir must exist
+    mkdir -p "$NEW_DIR"
+
+    OLD_DIR=$(old_dir "$OLD_DIR")
+    NEW_DIR=$(new_dir "$NEW_DIR")
+    PWD_ESC=$(escape_simple_path $(realpath "$FILE_SYSTEM_ROOT"))
+
+    assertEquals "OLD_DIR correct" "^\/root\/to\/old\/(.*)$" "$OLD_DIR"
+    assertEquals "NEW_DIR correct" "$PWD_ESC\/root\/to\/new\/\1" "$NEW_DIR"
+
+    RESULT=$(echo "/root/to/old/subdir/fileA" | sed -r "s/$OLD_DIR/$NEW_DIR/g")
+    assertEquals "result" $(realpath -m "$FILE_SYSTEM_ROOT/root/to/new/subdir/fileA") "$RESULT"
+}
+
+
+testSymlinkToSymlink () {
+    touch "$DATA_DIR/fileA"
+    ln -s $(realpath "$DATA_DIR/fileA") "$DATA_DIR/symlinkA"
+    ln -s $(realpath -s "$DATA_DIR/symlinkA") "$FILE_SYSTEM_ROOT/symlinkA"
+    assertLinkOk "symlinkA" "symlinkA"
+    assertLinkOk "symlinkA" "fileA"
+
+
+    # move only the symlink in data_dir
+    mv $DATA_DIR/symlinkA $DATA_DIR/symlinkA.new
+    assertFalse "symlink in fs broken" "[ -e '$FILE_SYSTEM_ROOT/symlinkA' ]"
+    assertTrue "symlink in data ok ($TARGET)" "[ -e '$DATA_DIR/symlinkA.new' ]"
+
+    RESULT=$($BEND "$FILE_SYSTEM_ROOT" "symlinkA" "symlinkA.new" 2>&1)
+
+    assertTrue "simlink is fixed" "[ -e '$FILE_SYSTEM_ROOT/symlinkA' ]"
+
+
+    assertLinkOk "symlinkA" "symlinkA.new"
+    assertLinkOk "symlinkA" "fileA"
+
+}
+
+
diff --git a/misc/check_symlinks/check_symlinks b/misc/check_symlinks/check_symlinks
index 5545613982e7772189697eb596d9786af0d29b1b..0f6eb0d1d3626ed32fa99aa6a31891c6ddf90635 100755
--- a/misc/check_symlinks/check_symlinks
+++ b/misc/check_symlinks/check_symlinks
@@ -77,7 +77,6 @@ function check_recursively() {
         else
             echo "$subdir is empty"
         fi
-        
     done
 }
 
diff --git a/misc/move_files/README.md b/misc/move_files/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..df67f2025b8627a09e6886038646db4ec4436941
--- /dev/null
+++ b/misc/move_files/README.md
@@ -0,0 +1,24 @@
+# About
+
+Version: 0.1
+
+Usage: `./move_files.py [-h] changes`
+
+This script moves files in the internal file system of the CaosDB server. It
+reads file paths form a tsv file with columns from and to. For each line it
+creates an update of a caosdb file object where the path that equals "from" is
+changed to "to".
+
+positional arguments:
+  changes     The file that defines the renames
+
+optional arguments:
+  -h, --help  show this help message and exit
+
+# Tests
+
+The tests a integration tests which require a running test database and a sufficiently configured caosdb-pylib.
+
+Run `pytest test_move_files.py` to insert a bunch of test files, rename and subsequently delete them.
+
+
diff --git a/misc/move_files/move_files.py b/misc/move_files/move_files.py
new file mode 100755
index 0000000000000000000000000000000000000000..44cc3f4f7e61027b833a7774705577c59e756e72
--- /dev/null
+++ b/misc/move_files/move_files.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2019 Henrik tom Wörden
+#
+# 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
+#
+
+"""
+This script moves files in the internal file system of the CaosDB server. It
+reads file paths form a tsv file with columns from and to. For each line it
+creates an update of a caosdb file object where the path that equals "from" is
+changed to "to".
+
+Version: 0.1
+"""
+
+import argparse
+import sys
+import time
+from argparse import ArgumentParser
+
+import pandas as pd
+from tqdm import tqdm
+
+import caosdb as db
+
+
+def rename(changes, chunksize=10):
+    """change the path of files based on a two-column table (from, to).
+
+    Parameters
+    ----------
+
+    changes : pd.DataFrame
+        A table with two columns, the old path and the new path.
+    chunksize : int, optional
+        How many files are being moved in one go (default is 10).
+    """
+    i = 0
+
+    for i in tqdm(range(changes.shape[0]//chunksize+1)):
+        chunk = changes.iloc[i*chunksize:(i+1)*chunksize]
+
+        if chunk.shape[0] == 0:
+            continue
+        cont = db.Container()
+
+        for _, (old, new) in chunk.iterrows():
+            cont.append(db.File(path=old))
+
+        cont.retrieve()
+
+        for fi, (_, (old, new)) in zip(cont, chunk.iterrows()):
+            assert fi.path == old
+            fi.path = new
+        cont.update()
+        i += 1
+
+
+def main(argv=None):
+    '''Command line options.'''
+
+    if argv is None:
+        argv = sys.argv
+    else:
+        sys.argv.extend(argv)
+
+    # Setup argument parser
+    parser = ArgumentParser(description=__doc__)
+    parser.add_argument("changes", help="The file that defines the renames")
+    args = parser.parse_args()
+
+    changes = pd.read_csv(args.changes, sep="\t")
+
+    if ("to" not in changes.columns or "from" not in changes.columns):
+        raise ValueError("The file supplied under changes shall have a 'to'"
+                         " and a 'from' column.")
+
+    assert 0 == pd.isnull(changes).sum().sum()
+    rename(changes)
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/misc/move_files/test_move_files.py b/misc/move_files/test_move_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..2edb76509dc6a350057997cc6f894abd4a4bb163
--- /dev/null
+++ b/misc/move_files/test_move_files.py
@@ -0,0 +1,78 @@
+# encoding: utf-8
+#
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2019 Henrik tom Wörden
+#
+# 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/>.
+
+
+import os
+import random
+import unittest
+
+import pandas as pd
+
+import caosdb as db
+from move_files import rename
+
+
+def create_filename():
+    name = os.path.normpath("".join([
+        random.choice("qwertyuiopkkhgfdsxcvbnm/") for el in range(30)]))
+
+    if not name.startswith("/"):
+        name = "/" + name
+
+    return name
+
+
+class TestMoveFiles(unittest.TestCase):
+    """
+    Files are being created, changed and then it is checked whether the changes
+    were correct.
+    """
+
+    def setUp(self):
+        self.files_to_be_changed = [create_filename() for i in range(40)]
+        self.files_not_to_be_changed = [create_filename() for i in range(20)]
+        self.new_names = [f+"new" for f in self.files_to_be_changed]
+
+        table = pd.DataFrame([self.files_to_be_changed, self.new_names])
+        table = table.T
+        table.columns = ["from", "to"]
+        self.table = table
+        self.cont = db.Container()
+        self.cont.extend([db.File(path=f, file=__file__)
+                          for f in self.files_to_be_changed
+                          + self.files_not_to_be_changed])
+        self.cont.insert()
+        print("inserted")
+
+    def test_move(self):
+        rename(self.table)
+        self.cont.retrieve()
+
+        for i, (fi, name) in enumerate(zip(
+                self.cont,
+                self.files_to_be_changed + self.files_not_to_be_changed)):
+
+            if i < len(self.files_to_be_changed):
+                self.assertEqual(fi.path, self.new_names[i])
+            else:
+                self.assertEqual(fi.path, name)
+
+    def tearDown(self):
+        self.cont.delete()
+        print("deleted")
diff --git a/misc/mv_unknown_files_script/mv_unknown_files b/misc/mv_unknown_files_script/mv_unknown_files
deleted file mode 100755
index 8b26b464d8dd40a15f3418895dd440903a6da8a6..0000000000000000000000000000000000000000
--- a/misc/mv_unknown_files_script/mv_unknown_files
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# ** 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
-#
-
-grep ": Unknown file." $1 | sed 's/<Warning.*tion="//' | sed 's/:\sUnknown.*//' | awk '{ print "move ", $1 }'
-
-
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/bin/xls_from_csv.py b/scripting/bin/xls_from_csv.py
index b64173a34be8d00bcd40e225122d865768b77ebb..38dfbc1c9392ce60d338b102f9b9fd05309919d3 100755
--- a/scripting/bin/xls_from_csv.py
+++ b/scripting/bin/xls_from_csv.py
@@ -31,27 +31,18 @@ import sys
 import pandas as pd
 
 
-def _parse_to_dataframe(csv_string):
-    """Attempts to create a valid dataframe from a CSV string.
+def _parse_to_dataframe(tsv_file):
+    """Attempts to create a valid dataframe from a TSV file.
 
-The CSV string typically starts with a header like this:
-
-```
-data:text/csv;charset=utf-8,colname1	colname2
-value1	value2
-...
-```
 Parameters
 ----------
-csv_string : The URL encoded CSV content, starts with `data:text/csv`.
+tsv_file : path to a tsv file.
 
 Returns
 -------
 out : The created dataframe.
     """
-    csv_string = csv_string.split(",")[1]
-    sio = io.StringIO(csv_string)
-    dataframe = pd.read_csv(sio, sep="\t")
+    dataframe = pd.read_csv(tsv_file, sep="\t")
     return dataframe
 
 
@@ -91,6 +82,7 @@ out : str
 
     return filename
 
+
 def _parse_arguments():
     """Parses the command line arguments.
 
@@ -100,17 +92,17 @@ def _parse_arguments():
     tempdir = os.environ["SHARED_DIR"]
     parser.add_argument('-t', '--tempdir', required=False, default=tempdir,
                         help="Temporary dir for saving the result.")
-    parser.add_argument('-u', '--urlencoded', required=True,
-                        help="The URL encoded csv data.")
     parser.add_argument('-a', '--auth-token', required=False,
                         help="An authentication token (not needed, only for compatibility).")
+    parser.add_argument('tsv', help="The tsv file.")
     return parser.parse_args()
 
+
 def main():
-  args = _parse_arguments()
-  dataframe = _parse_to_dataframe(args.urlencoded)
-  filename = _write_xls(dataframe, directory=args.tempdir)
-  print(filename)
+    args = _parse_arguments()
+    dataframe = _parse_to_dataframe(args.tsv)
+    filename = _write_xls(dataframe, directory=args.tempdir)
+    print(filename)
 
 if __name__ == "__main__":
-  main()
+    main()
diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java
index 3245902d5721e00d1ae0418a33e7a5f468794ea6..19560aacd50ede38471c0fb02e31fd4560e9d8bb 100644
--- a/src/main/java/caosdb/server/CaosDBServer.java
+++ b/src/main/java/caosdb/server/CaosDBServer.java
@@ -27,7 +27,7 @@ import caosdb.server.accessControl.OneTimeTokenRealm;
 import caosdb.server.accessControl.Principal;
 import caosdb.server.accessControl.SessionToken;
 import caosdb.server.accessControl.SessionTokenRealm;
-import caosdb.server.database.Database;
+import caosdb.server.database.BackendTransaction;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.RetrieveDatatypes;
 import caosdb.server.database.misc.TransactionBenchmark;
@@ -49,17 +49,16 @@ import caosdb.server.resource.ScriptingResource;
 import caosdb.server.resource.ServerLogsResource;
 import caosdb.server.resource.ServerPropertiesResource;
 import caosdb.server.resource.SharedFileResource;
-import caosdb.server.resource.TestCaseFileSystemResource;
-import caosdb.server.resource.TestCaseResource;
 import caosdb.server.resource.ThumbnailsResource;
 import caosdb.server.resource.UserResource;
 import caosdb.server.resource.UserRolesResource;
 import caosdb.server.resource.Webinterface;
+import caosdb.server.resource.WebinterfaceBuildNumber;
 import caosdb.server.resource.transaction.EntityResource;
 import caosdb.server.terminal.CaosDBTerminal;
 import caosdb.server.terminal.StatsPanel;
 import caosdb.server.terminal.SystemErrPanel;
-import caosdb.server.utils.ChecksumUpdater;
+import caosdb.server.transaction.ChecksumUpdater;
 import caosdb.server.utils.FileUtils;
 import caosdb.server.utils.Initialization;
 import caosdb.server.utils.NullPrintStream;
@@ -111,7 +110,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 +220,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 +265,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);
@@ -278,8 +279,10 @@ public class CaosDBServer extends Application {
     try {
       // init backend
       if (START_BACKEND) {
+        BackendTransaction.init();
+
         // init benchmark
-        TransactionBenchmark.getInstance();
+        TransactionBenchmark.getRootInstance();
 
         // Role
         Role.init(init.getAccess());
@@ -315,7 +318,7 @@ public class CaosDBServer extends Application {
         Thread.sleep(1000);
 
         // add Benchmark
-        StatsPanel.addStat("TransactionBenchmark", TransactionBenchmark.getInstance());
+        StatsPanel.addStat("TransactionBenchmark", TransactionBenchmark.getRootInstance());
       } else {
         logger.info("NO GUI");
         System.setOut(new NullPrintStream());
@@ -356,7 +359,9 @@ public class CaosDBServer extends Application {
 
   private static void initDatatypes(final Access access) throws Exception {
     final RetrieveDatatypes t = new RetrieveDatatypes();
-    final Container<? extends EntityInterface> dts = Database.execute(t, access).getDatatypes();
+    t.setAccess(access);
+    t.executeTransaction();
+    final Container<? extends EntityInterface> dts = t.getDatatypes();
     AbstractDatatype.initializeDatatypes(dts);
   }
 
@@ -579,39 +584,8 @@ public class CaosDBServer extends Application {
           }
         };
 
-    // -- Section only for debug mode --
-    if (isDebugMode()) {
-      baseRouter.attach("/TestCase/", DefaultResource.class);
-
-      final Variable pathVariable =
-          baseRouter
-              .attach(
-                  "/TestCase/FileSystem/{path}",
-                  baseRouter.createFinder(TestCaseFileSystemResource.class))
-              .getTemplate()
-              .getDefaultVariable();
-      pathVariable.setRequired(false);
-      pathVariable.setType(Variable.TYPE_URI_PATH);
-      pathVariable.setDefaultValue("");
-
-      baseRouter
-          .attach("/TestCase/Thumbnails/{path}", ThumbnailsResource.class)
-          .getTemplate()
-          .getDefaultVariable()
-          .setType(Variable.TYPE_URI_PATH);
-
-      baseRouter
-          .attach("/TestCase/webinterface/{path}", Webinterface.class)
-          .getTemplate()
-          .getDefaultVariable()
-          .setType(Variable.TYPE_URI_PATH);
-      baseRouter.attach("/TestCase/Entity", TestCaseResource.class);
-      baseRouter.attach("/TestCase/Entity/", TestCaseResource.class);
-      baseRouter.attach("/TestCase/Entity/{specifier}", TestCaseResource.class);
-    }
-    // -- End of debug section --
-
     // These routes can be used without logging in:
+    baseRouter.attach("/webinterface/version/build", WebinterfaceBuildNumber.class);
     baseRouter
         .attach("/webinterface/{path}", Webinterface.class)
         .getTemplate()
@@ -829,7 +803,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/FileSystem.java b/src/main/java/caosdb/server/FileSystem.java
index 44b663e958841ac92a24f44244096034eb9154c3..79e764b96d3b477a57b90b83b1a3f437a1d21266 100644
--- a/src/main/java/caosdb/server/FileSystem.java
+++ b/src/main/java/caosdb/server/FileSystem.java
@@ -24,9 +24,10 @@
 
 package caosdb.server;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.GetFileRecordByPath;
+import caosdb.server.database.exceptions.EntityDoesNotExistException;
+import caosdb.server.database.misc.TransactionBenchmark;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.FileProperties;
 import caosdb.server.entity.Message;
@@ -300,8 +301,8 @@ public class FileSystem {
    * @return
    * @throws Message
    */
-  public static boolean checkTarget(final EntityInterface entity, final Access access)
-      throws Message {
+  public static boolean checkTarget(
+      final EntityInterface entity, final Access access, TransactionBenchmark b) throws Message {
 
     final FileProperties file = entity.getFileProperties();
     // target file name = where the file is to be stored.
@@ -321,16 +322,25 @@ public class FileSystem {
       if (file.getFile() != null && file.getFile().equals(target)) {
         return true;
       } else {
-        final GetFileRecordByPath t =
-            Database.execute(new GetFileRecordByPath(file.getPath()), access);
+        final GetFileRecordByPath t = new GetFileRecordByPath(file.getPath());
+        t.setAccess(access);
+        t.setTransactionBenchmark(b);
+        try {
+          t.executeTransaction();
+        } catch (EntityDoesNotExistException e) {
+          // could not determine which entity owns this path
+          // this is usually the case when target is a directory
+        }
         if (t.getEntity() != null) {
           final Integer foreign = t.getId();
           if (foreign != null && foreign.equals(entity.getId())) {
+            // entity already owns this path
             return true;
           }
-          throw ServerMessages.TARGET_PATH_EXISTS;
         }
       }
+      // another entity owns this path
+      throw ServerMessages.TARGET_PATH_EXISTS;
     }
 
     return true;
diff --git a/src/main/java/caosdb/server/ServerProperties.java b/src/main/java/caosdb/server/ServerProperties.java
index 929f845d8023dc9d81c62294a5318442fcfb52ea..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 =
diff --git a/src/main/java/caosdb/server/accessControl/Pam.java b/src/main/java/caosdb/server/accessControl/Pam.java
index c222fb83dabe0e4ad4e99d096d33192bf65086a9..d640a66f4fffdb6254c7d11d696a1f2a1bf5ebf6 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,13 @@ 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";
+
+  public static final String CACHE_REGION_GROUPS = "PAM_UnixUserGroups";
+  public static final String CACHE_REGION_USER_EXIST = "PAM_UnixUserExists";
+  public static final String CACHE_REGION_AUTH = "PAM_Authentication";
+
   private String[] EXCLUDE_USERS = null;
   private String[] INCLUDE_USERS = null;
   private String[] EXCLUDE_GROUPS = null;
@@ -92,16 +119,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(CACHE_REGION_GROUPS);
+  private ICacheAccess<String, Boolean> userExistsCache = Cache.getCache(CACHE_REGION_USER_EXIST);
+  private ICacheAccess<String, Boolean> passwordValidCache = Cache.getCache(CACHE_REGION_AUTH);
+
   @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 +177,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 +218,7 @@ public class Pam implements UserSource {
         throw new AuthorizationException(e);
       }
     }
-    return null;
+    return new HashSet<String>();
   }
 
   @Override
@@ -154,11 +226,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 +273,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 +409,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..55efaa191bcb777bd63881dfbd72cd292808064c
--- /dev/null
+++ b/src/main/java/caosdb/server/caching/JCSCacheHelper.java
@@ -0,0 +1,98 @@
+/*
+ * ** 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 {
+    init();
+  }
+
+  public static void init() {
+    init(CaosDBServer.getServerProperty(ServerProperties.KEY_CACHE_CONF_LOC));
+  }
+
+  public static void init(String configFileLocation) {
+    Properties config = null;
+    try {
+      Properties p = new Properties();
+      final InputStream is = new FileInputStream(configFileLocation);
+      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..42cce21af9f7e851d27c960b9704aae46b96350f 100644
--- a/src/main/java/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/caosdb/server/database/BackendTransaction.java
@@ -130,21 +130,21 @@ public abstract class BackendTransaction implements Undoable {
 
   private final UndoHandler undoHandler = new UndoHandler();
   private Access access;
-  BackendTransaction parent = null;
+  private TransactionBenchmark benchmark;
   private static HashMap<
           Class<? extends BackendTransactionImpl>, Class<? extends BackendTransactionImpl>>
       impl = new HashMap<>();
 
   protected abstract void execute();
 
-  final void executeTransaction() {
+  public final void executeTransaction() {
     final long t1 = System.currentTimeMillis();
     execute();
     final long t2 = System.currentTimeMillis();
-    TransactionBenchmark.getInstance().addBenchmark(this, t2 - t1);
+    this.addMeasurement(this, t2 - t1);
   }
 
-  private static void init() {
+  public static void init() {
     if (impl.isEmpty()) {
       setImpl(DeleteEntityPropertiesImpl.class, MySQLDeleteEntityProperties.class);
       setImpl(DeleteSparseEntityImpl.class, MySQLDeleteSparseEntity.class);
@@ -202,20 +202,22 @@ public abstract class BackendTransaction implements Undoable {
     assert t != this;
     this.undoHandler.append(t);
     t.setAccess(this.access);
-    t.parent = this;
+    if (benchmark != null) {
+      t.setTransactionBenchmark(benchmark.getBenchmark(t.getClass()));
+    }
     final long t1 = System.currentTimeMillis();
     t.execute();
     final long t2 = System.currentTimeMillis();
-    TransactionBenchmark.getInstance().addBenchmark(t, t2 - t1);
+    this.addMeasurement(t, t2 - t1);
     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);
   }
 
-  void setAccess(final Access access) {
+  public void setAccess(final Access access) {
     this.access = access;
   }
 
@@ -223,10 +225,14 @@ public abstract class BackendTransaction implements Undoable {
   protected <T extends BackendTransactionImpl> T getImplementation(final Class<T> clz) {
     init();
     try {
-      final T ret = (T) impl.get(clz).getConstructor(Access.class).newInstance(this.access);
+      Class<?> implclz = impl.get(clz);
+      final T ret = (T) implclz.getConstructor(Access.class).newInstance(this.access);
       if (ret instanceof Undoable) {
         this.undoHandler.append((Undoable) ret);
       }
+      if (benchmark != null) {
+        ret.setTransactionBenchmark(benchmark.getBenchmark(ret.getClass()));
+      }
       return ret;
     } catch (final Exception e) {
       throw new TransactionException(e);
@@ -253,7 +259,16 @@ public abstract class BackendTransaction implements Undoable {
 
   @Override
   public String toString() {
-    return (this.parent != null ? this.parent.toString() + " -> " : "")
-        + this.getClass().getSimpleName();
+    return this.getClass().getSimpleName();
+  }
+
+  public void setTransactionBenchmark(TransactionBenchmark b) {
+    this.benchmark = b;
+  }
+
+  public void addMeasurement(Object o, long time) {
+    if (this.benchmark != null) {
+      this.benchmark.addMeasurement(o, time);
+    }
   }
 }
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/Database.java b/src/main/java/caosdb/server/database/Database.java
index c30b68c7c16cecf4d59f3d6adda10e066a42763f..d67acdff9d8fbacdbdefdfdeb5013c3838156461 100644
--- a/src/main/java/caosdb/server/database/Database.java
+++ b/src/main/java/caosdb/server/database/Database.java
@@ -22,18 +22,17 @@
  */
 package caosdb.server.database;
 
-import caosdb.server.database.access.Access;
-import caosdb.server.database.misc.RollBackHandler;
-
 public class Database {
 
-  public static <K extends BackendTransaction> K execute(final K t, final Access access) {
-
-    final RollBackHandler handler = (RollBackHandler) access.getHelper("RollBack");
-    handler.append(t);
-    t.setAccess(access);
-
-    t.executeTransaction();
-    return t;
-  }
+  //  public static <K extends BackendTransaction> K execute(
+  //      final K t, final Access access, TransactionBenchmark b) {
+  //
+  //    final RollBackHandler handler = (RollBackHandler) access.getHelper("RollBack");
+  //    handler.append(t);
+  //    t.setAccess(access);
+  //
+  //    t.setTransactionBenchmark(b);
+  //    t.executeTransaction();
+  //    return t;
+  //  }
 }
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
index 329a4e81b211815ac3ab5059e4b789eed9dce1b9..8b9a547bac69cf221dbf7c204a93e95326bb484c 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
@@ -49,6 +49,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
       final PreparedStatement prepareStatement = prepareStatement(stmtStr);
 
       final List<FlatProperty> props = retrieveFlatPropertiesStage1(0, entity, prepareStatement);
+
       final ArrayList<ProtoProperty> protos = new ArrayList<ProtoProperty>();
       for (final FlatProperty p : props) {
         final ProtoProperty proto = new ProtoProperty();
@@ -56,6 +57,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
 
         final List<FlatProperty> subProps =
             retrieveFlatPropertiesStage1(entity, p.id, prepareStatement);
+
         proto.subProperties = subProps;
 
         protos.add(proto);
@@ -80,7 +82,10 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
       }
 
       stmt.setInt(2, entity);
+      long t1 = System.currentTimeMillis();
       rs = stmt.executeQuery();
+      long t2 = System.currentTimeMillis();
+      addMeasurement(this.getClass().getSimpleName() + ".retrieveFlatPropertiesStage1", t2 - t1);
 
       final List<FlatProperty> props = DatabaseUtils.parsePropertyResultset(rs);
 
@@ -107,7 +112,10 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
     try {
       stmt2.setInt(1, domain);
       stmt2.setInt(2, entity);
+      long t1 = System.currentTimeMillis();
       rs = stmt2.executeQuery();
+      long t2 = System.currentTimeMillis();
+      addMeasurement(this.getClass().getSimpleName() + ".retrieveOverrides", t2 - t1);
       DatabaseUtils.parseOverrides(props, rs);
     } finally {
       if (rs != null && !rs.isClosed()) {
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLTransaction.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLTransaction.java
index 1eb398634a665672bfbd266c51d819565fbea04d..f116a136c1b3e1942ddc2400139db3c82210fac8 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLTransaction.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLTransaction.java
@@ -23,13 +23,16 @@
 package caosdb.server.database.backend.implementation.MySQL;
 
 import caosdb.server.database.access.Access;
+import caosdb.server.database.backend.interfaces.BackendTransactionImpl;
 import caosdb.server.database.misc.DBHelper;
+import caosdb.server.database.misc.TransactionBenchmark;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 
-public class MySQLTransaction {
+public class MySQLTransaction implements BackendTransactionImpl {
 
   protected final Access access;
+  private TransactionBenchmark benchmark;
 
   public MySQLTransaction(final Access access) {
     this.access = access;
@@ -54,4 +57,20 @@ public class MySQLTransaction {
   public final void undo() {}
 
   public final void cleanUp() {}
+
+  @Override
+  public TransactionBenchmark getBenchmark() {
+    return benchmark;
+  }
+
+  @Override
+  public void setTransactionBenchmark(TransactionBenchmark b) {
+    this.benchmark = b;
+  }
+
+  protected void addMeasurement(String string, long l) {
+    if (benchmark != null) {
+      benchmark.addMeasurement(string, l);
+    }
+  }
 }
diff --git a/src/main/java/caosdb/server/database/backend/implementation/UnixFileSystem/UnixFileSystemTransaction.java b/src/main/java/caosdb/server/database/backend/implementation/UnixFileSystem/UnixFileSystemTransaction.java
index 851e0b0d6bfdbe0b29f9ce8ec51d26f1b26954e8..63e87d6a4f968d3d4833111f9ca9875daf18d9cc 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/UnixFileSystem/UnixFileSystemTransaction.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/UnixFileSystem/UnixFileSystemTransaction.java
@@ -24,19 +24,27 @@ package caosdb.server.database.backend.implementation.UnixFileSystem;
 
 import caosdb.server.FileSystem;
 import caosdb.server.database.access.Access;
+import caosdb.server.database.backend.interfaces.BackendTransactionImpl;
 import caosdb.server.database.misc.DBHelper;
+import caosdb.server.database.misc.TransactionBenchmark;
 import caosdb.server.entity.Message;
 import java.io.File;
 import java.io.IOException;
 
-public abstract class UnixFileSystemTransaction {
+public abstract class UnixFileSystemTransaction implements BackendTransactionImpl {
 
   private final Access access;
+  private TransactionBenchmark benchmark;
 
   public UnixFileSystemTransaction(final Access access) {
     this.access = access;
   }
 
+  @Override
+  public void setTransactionBenchmark(TransactionBenchmark b) {
+    this.benchmark = b;
+  }
+
   protected File getFile(final String path) throws IOException, Message {
     return new File(getHelper().getBasePath() + path);
   }
@@ -56,4 +64,9 @@ public abstract class UnixFileSystemTransaction {
       return (UnixFileSystemHelper) dbHelper;
     }
   }
+
+  @Override
+  public TransactionBenchmark getBenchmark() {
+    return benchmark;
+  }
 }
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/BackendTransactionImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/BackendTransactionImpl.java
index cc2af6e2d8a421eab23b466986413bf2819b10b5..d4751f94cda0b0bdea703d5f207527ea756681f3 100644
--- a/src/main/java/caosdb/server/database/backend/interfaces/BackendTransactionImpl.java
+++ b/src/main/java/caosdb/server/database/backend/interfaces/BackendTransactionImpl.java
@@ -22,4 +22,11 @@
  */
 package caosdb.server.database.backend.interfaces;
 
-public interface BackendTransactionImpl {}
+import caosdb.server.database.misc.TransactionBenchmark;
+
+public interface BackendTransactionImpl {
+
+  public void setTransactionBenchmark(TransactionBenchmark b);
+
+  public TransactionBenchmark getBenchmark();
+}
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..6366bd2884aca38a0d54eca4a926fcc7e8b9cda4 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,32 @@
  */
 package caosdb.server.database.backend.transaction;
 
+import caosdb.server.caching.Cache;
 import caosdb.server.database.CacheableBackendTransaction;
 import caosdb.server.database.backend.interfaces.GetFileRecordByPathImpl;
+import caosdb.server.database.exceptions.EntityDoesNotExistException;
 import caosdb.server.database.exceptions.TransactionException;
 import caosdb.server.database.proto.SparseEntity;
+import org.apache.commons.jcs.access.behavior.ICacheAccess;
 
 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;
@@ -49,7 +63,11 @@ public class GetFileRecordByPath extends CacheableBackendTransaction<String, Spa
   @Override
   public SparseEntity executeNoCache() throws TransactionException {
     final GetFileRecordByPathImpl t = getImplementation(GetFileRecordByPathImpl.class);
-    return t.execute(getKey());
+    SparseEntity result = t.execute(getKey());
+    if (result == null) {
+      throw new EntityDoesNotExistException();
+    }
+    return result;
   }
 
   public Integer getId() {
diff --git a/src/main/java/caosdb/server/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..487feb56cf848e948fcacb8f33f49e4a0bc2267d 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,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.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)));
+  public static final String CACHE_REGION = "BACKEND_EntityProperties";
+  private static final ICacheAccess<Integer, ArrayList<ProtoProperty>> cache =
+      Cache.getCache(CACHE_REGION);
 
   /**
    * To be called by DeleteEntityProperties on execution.
@@ -57,12 +55,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/database/misc/TransactionBenchmark.java b/src/main/java/caosdb/server/database/misc/TransactionBenchmark.java
index 80b60e80893e8b512abededb345e286438ec22bb..f7c9e80f811418392b6567fcda5bfb8a9948bbff 100644
--- a/src/main/java/caosdb/server/database/misc/TransactionBenchmark.java
+++ b/src/main/java/caosdb/server/database/misc/TransactionBenchmark.java
@@ -27,49 +27,122 @@ import caosdb.server.ServerProperties;
 import caosdb.server.utils.CronJob;
 import caosdb.server.utils.Info;
 import caosdb.server.utils.ServerStat;
+import java.io.Serializable;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Map.Entry;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.jdom2.Element;
 
-public class TransactionBenchmark implements ServerStat {
+class Counter implements Serializable {
+  private static final long serialVersionUID = 8679355597595634790L;
+  private int c;
 
-  private static final long serialVersionUID = -8916163825450491067L;
-  private long since = System.currentTimeMillis();
-  private final HashMap<String, Integer> counts = new HashMap<String, Integer>();
-  private final HashMap<String, Long> acc = new HashMap<String, Long>();
-  private transient boolean synced = false;
-  private static final TransactionBenchmark instance = new TransactionBenchmark();
-  private static final boolean isActive =
-      Boolean.valueOf(
-          CaosDBServer.getServerProperty(ServerProperties.KEY_TRANSACTION_BENCHMARK_ENABLED)
-              .toLowerCase());
+  public Counter(int initial) {
+    this.c = initial;
+  }
 
-  static {
-    if (isActive) {
-      instance.init();
-    }
+  public void add(int inc) {
+    this.c += inc;
+  }
+
+  public int toInteger() {
+    return c;
+  }
+}
+
+class Timer implements Serializable {
+  private static final long serialVersionUID = 7895352555775799409L;
+  private long c;
+
+  public Timer(long initial) {
+    this.c = initial;
+  }
+
+  public void add(long inc) {
+    this.c += inc;
+  }
+
+  public long toLong() {
+    return c;
+  }
+}
+
+class Measurement implements Serializable {
+  private static final long serialVersionUID = -2429348657382168470L;
+  private final Timer timer;
+  private final Counter counter;
+  private final String name;
+
+  public Measurement(String name, long time, int count) {
+    this.name = name;
+    this.timer = new Timer(time);
+    this.counter = new Counter(count);
+  }
+
+  public void add(long time, int count) {
+    this.timer.add(time);
+    this.counter.add(count);
+  }
+
+  public double getAvg() {
+    return (double) this.getAccTime() / this.getCount();
   }
 
+  public StringBuilder toStringBuilder() {
+    StringBuilder sb = new StringBuilder();
+    sb.append('#');
+    sb.append(name);
+    sb.append(": ");
+    sb.append('(');
+    sb.append(this.getCount());
+    sb.append(", ");
+    sb.append(this.getAccTime());
+    sb.append(", ");
+    sb.append(String.format(Locale.US, "%3.2f", this.getAvg()));
+    sb.append(')');
+    return sb;
+  }
+
+  public int getCount() {
+    return counter.toInteger();
+  }
+
+  public long getAccTime() {
+    return timer.toLong();
+  }
+
+  public String getName() {
+    return this.name;
+  }
+}
+
+class RootBenchmark extends TransactionBenchmark implements ServerStat {
+
+  private static final long serialVersionUID = 8070554107783826602L;
+  private transient boolean synced = false;
   /**
    * Fetch old data (from before last shutdown) and fill it into this instance.
    *
    * @return
    */
-  private TransactionBenchmark init() {
-    synchronized (this.counts) {
+  protected TransactionBenchmark init() {
+    final RootBenchmark b = this;
+    synchronized (this.measurements) {
       final Runnable updater =
           new Runnable() {
 
             @Override
             public void run() {
               try {
-                synchronized (TransactionBenchmark.this.counts) {
-                  Info.syncDatabase(TransactionBenchmark.this);
+                synchronized (b.measurements) {
+                  Info.getInstance().syncDatabase(b);
                 }
               } catch (final Exception e) {
-                e.printStackTrace();
+                logger.error(e);
               }
             }
           };
@@ -81,38 +154,166 @@ public class TransactionBenchmark implements ServerStat {
     return this;
   }
 
+  @Override
+  public final String getName() {
+    return "TransactionBenchmark";
+  }
+
   /**
-   * Add a benchmark for a certain object. The object will be toString()'ed. The string serves as a
-   * key.
+   * Add data from s to this instance.
    *
-   * @param object
-   * @param time
+   * @param s another TransactionBenchmark instance
    */
-  public void addBenchmark(final Object object, final long time) {
+  @Override
+  public final void update(final ServerStat s) {
+    if (!this.synced && isActive) {
+      final RootBenchmark t = (RootBenchmark) s;
+      this.synced = true;
+      merge(this, t);
+    }
+  }
+
+  public void merge(TransactionBenchmark target, TransactionBenchmark src) {
     if (isActive) {
-      addBenchmark(instance, object.toString(), time, 1);
+      target.since = Long.min(target.since, src.since);
+      synchronized (target.measurements) {
+        for (final Measurement m : src.measurements.values()) {
+          // add all measurements from src
+          target.addMeasurement(m);
+        }
+      }
+
+      // merge subBenchmarks
+      synchronized (src.subBenchmarks) {
+        for (final SubBenchmark srcsb : src.getSubBenchmarks()) {
+          synchronized (target.subBenchmarks) {
+            if (target.subBenchmarks.containsKey(srcsb.getName())) {
+              merge(target.subBenchmarks.get(srcsb.getName()), srcsb);
+            } else {
+              target.subBenchmarks.put(srcsb.getName(), srcsb);
+            }
+          }
+        }
+      }
     }
   }
+}
 
-  private static void addBenchmark(
-      final TransactionBenchmark b, final String name, final long time, final int count) {
-    synchronized (b.counts) {
-      if (b.counts.containsKey(name)) {
-        int c = b.counts.get(name);
-        c += count;
-        b.counts.put(name, c);
-        long a = b.acc.get(name);
-        a += time;
-        b.acc.put(name, a);
-      } else {
-        b.counts.put(name, count);
-        b.acc.put(name, time);
+class SubBenchmark extends TransactionBenchmark {
+
+  private static final long serialVersionUID = 323147917189195973L;
+  private final String name;
+
+  public SubBenchmark(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+}
+
+class JdomConverter {
+  public Element convert(Measurement m) {
+    Element ret = new Element("Measurement");
+    ret.setAttribute("name", m.getName());
+    ret.setAttribute("acc_time", Long.toString(m.getAccTime()));
+    ret.setAttribute("avg_time", String.format(Locale.US, "%3.2f", m.getAvg()));
+    ret.setAttribute("count", Integer.toString(m.getCount()));
+    return ret;
+  }
+
+  public Element convert(TransactionBenchmark b) {
+    Element ret = new Element(b.getName());
+    ret.setAttribute("since", new Date(b.since).toString());
+    Iterable<Measurement> measurements = b.getMeasurements();
+
+    synchronized (measurements) {
+      for (Measurement m : measurements) {
+        ret.addContent(this.convert(m));
       }
     }
+
+    // tree view of subBenchmarks
+    Iterable<SubBenchmark> subBenchmarks = b.getSubBenchmarks();
+    synchronized (subBenchmarks) {
+      for (SubBenchmark s : subBenchmarks) {
+        ret.addContent(this.convert(s));
+      }
+    }
+    return ret;
+  }
+}
+
+public abstract class TransactionBenchmark implements Serializable {
+
+  private static final long serialVersionUID = -8916163825450491067L;
+  private static final TransactionBenchmark rootService = new RootBenchmark().init();
+  public final transient Logger logger = LogManager.getLogger(getClass());
+  protected static final transient boolean isActive =
+      Boolean.valueOf(
+          CaosDBServer.getServerProperty(ServerProperties.KEY_TRANSACTION_BENCHMARK_ENABLED)
+              .toLowerCase());
+
+  long since = System.currentTimeMillis();
+
+  protected final Map<String, Measurement> measurements = new HashMap<>();
+  protected final Map<String, SubBenchmark> subBenchmarks = new HashMap<>();
+
+  protected TransactionBenchmark() {};
+
+  public Iterable<SubBenchmark> getSubBenchmarks() {
+    return this.subBenchmarks.values();
   }
 
-  public static TransactionBenchmark getInstance() {
-    return instance;
+  public Iterable<Measurement> getMeasurements() {
+    return this.measurements.values();
+  }
+
+  /**
+   * Add a measurement. The object will be toString()'ed. The string serves as a key.
+   *
+   * @param object
+   * @param time
+   */
+  public void addMeasurement(final Object object, final long time) {
+    this.addMeasurement(object, time, 1);
+  }
+
+  /**
+   * Add a measurement. The object will be toString()'ed. The string serves as a key.
+   *
+   * @param object
+   * @param time
+   * @param count
+   */
+  public void addMeasurement(final Object object, final long time, int count) {
+    if (isActive) {
+      String name = object.toString();
+
+      synchronized (this.measurements) {
+        Measurement existing = this.measurements.get(name);
+        if (existing == null) {
+          this.measurements.put(name, new Measurement(name, time, count));
+        } else {
+          existing.add(time, count);
+        }
+      }
+    }
+  }
+
+  /**
+   * Add a measurement.
+   *
+   * @param measurement
+   */
+  public void addMeasurement(Measurement measurement) {
+    this.addMeasurement(measurement.getName(), measurement.getAccTime(), measurement.getCount());
+  }
+
+  public static TransactionBenchmark getRootInstance() {
+    return rootService;
   }
 
   @Override
@@ -121,23 +322,11 @@ public class TransactionBenchmark implements ServerStat {
       final StringBuilder sb = new StringBuilder();
       sb.append(
           "\nNAME: (execution count, accumulated execution time [ms], avg time per execution [ms])\n");
-      synchronized (this.counts) {
-        for (final Entry<String, Integer> e : this.counts.entrySet()) {
-          final int c = e.getValue();
-          final long acc = this.acc.get(e.getKey());
-          final String name = e.getKey();
-          final double avg = (double) acc / c;
+      synchronized (this.measurements) {
+        for (final Entry<String, Measurement> e : this.measurements.entrySet()) {
+          final Measurement m = e.getValue();
           sb.append('\n');
-          sb.append('#');
-          sb.append(name);
-          sb.append(": ");
-          sb.append('(');
-          sb.append(c);
-          sb.append(", ");
-          sb.append(acc);
-          sb.append(", ");
-          sb.append(String.format(Locale.US, "%3.2f", avg));
-          sb.append(')');
+          sb.append(m.toStringBuilder());
         }
       }
       return sb.toString();
@@ -147,73 +336,54 @@ public class TransactionBenchmark implements ServerStat {
   }
 
   public Element toElememt() {
-    final Element ret = new Element("TransactionBenchmark");
     if (isActive) {
-      ret.setAttribute("since", new Date(this.since).toString());
-      synchronized (this.counts) {
-        for (final Entry<String, Integer> e : this.counts.entrySet()) {
-          final int c = e.getValue();
-          final long acc = TransactionBenchmark.this.acc.get(e.getKey());
-          final String name = e.getKey();
-          final double avg = (double) acc / c;
-
-          final Element b = new Element("Benchmark");
-          b.setAttribute("name", name);
-          b.setAttribute("execution_count", Integer.toString(c));
-          b.setAttribute("accumulated_execution_time", Long.toString(acc) + "ms");
-          b.setAttribute("avg_execution_time", String.format(Locale.US, "%3.2f", avg) + "ms");
-
-          ret.addContent(b);
-        }
-      }
+      return new JdomConverter().convert(this).setName("TransactionBenchmark");
     } else {
+      final Element ret = new Element("TransactionBenchmark");
       ret.setAttribute("info", "TransactionBenchmark is disabled.");
+      return ret;
     }
-    return ret;
   }
 
-  @Override
-  public String getName() {
-    return this.getClass().getSimpleName();
-  }
+  public abstract String getName();
 
   /**
-   * Add data from s to this instance
+   * Create a new independent {@link TransactionBenchmark}.
    *
-   * @param s another TransactionBenchmark instance
+   * <p>This is for starting a series of measurements from a clean slate.
+   *
+   * <p>However, the Measurements and Benchmarks are still recorded in the {@link RootBenchmark}
+   * too.
+   *
+   * @param name the name of the new {@link TransactionBenchmark}.
+   * @return a new {@link TransactionBenchmark}
    */
-  @Override
-  public void update(final ServerStat s) {
-    final TransactionBenchmark t = (TransactionBenchmark) s;
-    if (!this.synced) {
-      this.synced = true;
-      this.since = t.since;
-      for (final String key : t.counts.keySet()) {
-        addBenchmark(this, key, t.acc.get(key), t.counts.get(key));
-      }
+  public TransactionBenchmark createBenchmark(String name) {
+    synchronized (subBenchmarks) {
+      SubBenchmark b = new SubBenchmark(name);
+      subBenchmarks.put(name, b);
+      return b;
     }
   }
 
-  /**
-   * Get a SubBenchmark which can be used to produce a separate benchmark (e.g. for single
-   * transactions). The SubBenchmark's data will also available to the main benchmark.
-   *
-   * @return A SubBenchmark
-   */
-  public static TransactionBenchmark getSubBenchmark() {
-    return new SubBenchmark();
+  public TransactionBenchmark getBenchmark(String name) {
+    synchronized (subBenchmarks) {
+      SubBenchmark existing = subBenchmarks.get(name);
+      if (existing != null) {
+        return existing;
+      } else {
+        SubBenchmark b = new SubBenchmark(name);
+        subBenchmarks.put(name, b);
+        return b;
+      }
+    }
   }
 
-  private static class SubBenchmark extends TransactionBenchmark {
-
-    private static final long serialVersionUID = 1L;
+  public TransactionBenchmark getBenchmark(Class<?> class1) {
+    return this.getBenchmark(class1.getSimpleName());
+  }
 
-    @Override
-    public void addBenchmark(final Object object, final long time) {
-      if (isActive) {
-        super.addBenchmark(object, time);
-        TransactionBenchmark.addBenchmark(this, object.toString(), time, 1);
-      }
-    }
+  public TransactionBenchmark createBenchmark(Class<?> class1) {
+    return this.createBenchmark(class1.getSimpleName());
   }
 }
diff --git a/src/main/java/caosdb/server/entity/Message.java b/src/main/java/caosdb/server/entity/Message.java
index 03e788251620b6c192eb86f1bbd172564a66235f..0ffedb4316fe372e09f3b69ca5824a72f98accaf 100644
--- a/src/main/java/caosdb/server/entity/Message.java
+++ b/src/main/java/caosdb/server/entity/Message.java
@@ -79,8 +79,8 @@ public class Message extends Exception implements Comparable<Message>, ToElement
     this(type, code, null, null);
   }
 
-  public Message(final Integer code, final String description) {
-    this("Message", code, description, null);
+  public Message(Integer code, String description) {
+    this(MessageType.Info, code, description);
   }
 
   public Message(final MessageType type, final Integer code, final String description) {
@@ -96,6 +96,10 @@ public class Message extends Exception implements Comparable<Message>, ToElement
     this(type, code, description, null);
   }
 
+  public Message(MessageType type, String description) {
+    this(type.toString(), 0, description);
+  }
+
   public Message(
       final String type, final Integer code, final String description, final String body) {
     this.code = code;
diff --git a/src/main/java/caosdb/server/entity/Role.java b/src/main/java/caosdb/server/entity/Role.java
index 58cd7ba4f19ff89dad10a0a1c18fc3a417274255..79b6548f82f41568ec44c574c4ad35502379e58b 100644
--- a/src/main/java/caosdb/server/entity/Role.java
+++ b/src/main/java/caosdb/server/entity/Role.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.entity;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.GetIDByName;
 import caosdb.server.entity.xml.EntityToElementStrategy;
@@ -44,10 +43,16 @@ public enum Role {
     ids = new HashMap<Role, Integer>();
 
     for (final Role r : Role.values()) {
-      ids.put(r, Database.execute(new GetIDByName(r.name(), "ROLE"), access).getId());
+      ids.put(r, execute(new GetIDByName(r.name(), "ROLE"), access).getId());
     }
   }
 
+  private static GetIDByName execute(GetIDByName getIDByName, Access access) {
+    getIDByName.setAccess(access);
+    getIDByName.executeTransaction();
+    return getIDByName;
+  }
+
   public static Role parse(final String str) {
     for (final Role r : Role.values()) {
       if (r.toString().equalsIgnoreCase(str)) {
diff --git a/src/main/java/caosdb/server/entity/container/TransactionContainer.java b/src/main/java/caosdb/server/entity/container/TransactionContainer.java
index ff799a4df5eb74a0d15e48a990e735af609925ec..ea4cc02e7d16bda586ff3c700e3f2fb237b15426 100644
--- a/src/main/java/caosdb/server/entity/container/TransactionContainer.java
+++ b/src/main/java/caosdb/server/entity/container/TransactionContainer.java
@@ -103,15 +103,15 @@ public class TransactionContainer extends Container<Entity> implements ToElement
   @Override
   public void addToElement(final Element element) {
     element.setAttribute("count", Integer.toString(size()));
+    if (this.benchmark != null && CaosDBServer.isDebugMode()) {
+      element.addContent(this.benchmark.toElememt());
+    }
     for (final ToElementable m : this.messages) {
       m.addToElement(element);
     }
     for (final EntityInterface entity : this) {
       entity.addToElement(element);
     }
-    if (this.benchmark != null && CaosDBServer.isDebugMode()) {
-      element.addContent(this.benchmark.toElememt());
-    }
   }
 
   public void print() {
@@ -180,7 +180,7 @@ public class TransactionContainer extends Container<Entity> implements ToElement
 
   public TransactionBenchmark getTransactionBenchmark() {
     if (this.benchmark == null) {
-      this.benchmark = TransactionBenchmark.getSubBenchmark();
+      this.benchmark = TransactionBenchmark.getRootInstance().createBenchmark(getClass());
     }
     return this.benchmark;
   }
diff --git a/src/main/java/caosdb/server/jobs/Job.java b/src/main/java/caosdb/server/jobs/Job.java
index 681098b76a9deb90bc05dbea6f13ad2acd937ea6..b3551b4dd92084e6296a57be4166b341efa68262 100644
--- a/src/main/java/caosdb/server/jobs/Job.java
+++ b/src/main/java/caosdb/server/jobs/Job.java
@@ -23,7 +23,7 @@
 package caosdb.server.jobs;
 
 import caosdb.server.CaosDBException;
-import caosdb.server.database.Database;
+import caosdb.server.database.BackendTransaction;
 import caosdb.server.database.backend.transaction.GetIDByName;
 import caosdb.server.database.backend.transaction.IsSubType;
 import caosdb.server.database.backend.transaction.RetrieveFullEntity;
@@ -78,6 +78,10 @@ public abstract class Job extends AbstractObservable implements Observer {
     return getTransaction().getTransactor();
   }
 
+  public <K extends BackendTransaction> K execute(final K t) {
+    return getTransaction().execute(t, getTransaction().getAccess());
+  }
+
   protected ScheduledJob appendJob(final EntityInterface entity, final Class<? extends Job> clazz) {
     try {
       final Job job = clazz.newInstance();
@@ -156,8 +160,7 @@ public abstract class Job extends AbstractObservable implements Observer {
   }
 
   protected final boolean isValidSubTypeNoCache(final int child, final int parent) {
-    return child == parent
-        || Database.execute(new IsSubType(child, parent), getTransaction().getAccess()).isSubType();
+    return child == parent || execute(new IsSubType(child, parent)).isSubType();
   }
 
   protected final EntityInterface retrieveValidSparseEntityByName(final String name)
@@ -166,8 +169,7 @@ public abstract class Job extends AbstractObservable implements Observer {
   }
 
   protected final EntityInterface retrieveValidSparseEntityById(final Integer id) throws Message {
-    final EntityInterface ret =
-        Database.execute(new RetrieveSparseEntity(id), getTransaction().getAccess()).getEntity();
+    final EntityInterface ret = execute(new RetrieveSparseEntity(id)).getEntity();
     if (ret.getEntityStatus() == EntityStatus.NONEXISTENT) {
       throw ServerMessages.ENTITY_DOES_NOT_EXIST;
     }
@@ -175,17 +177,15 @@ public abstract class Job extends AbstractObservable implements Observer {
   }
 
   protected final EntityInterface retrieveValidEntity(Integer id) {
-    return Database.execute(new RetrieveFullEntity(id), getTransaction().getAccess())
-        .getContainer()
-        .get(0);
+    return execute(new RetrieveFullEntity(id)).getContainer().get(0);
   }
 
   protected final Integer retrieveValidIDByName(final String name) {
-    return Database.execute(new GetIDByName(name), getTransaction().getAccess()).getId();
+    return execute(new GetIDByName(name)).getId();
   }
 
   protected EntityInterface retrieveParentsOfValidEntity(final EntityInterface entity) {
-    Database.execute(new RetrieveParents(entity), getTransaction().getAccess());
+    execute(new RetrieveParents(entity));
     return entity;
   }
 
@@ -257,16 +257,16 @@ public abstract class Job extends AbstractObservable implements Observer {
   }
 
   protected List<Job> loadDataTypeSpecificJobs() {
-    return Job.loadDataTypeSpecificJobs(getEntity(), getTransaction());
+    return loadDataTypeSpecificJobs(getEntity(), getTransaction());
   }
 
-  public static List<Job> loadDataTypeSpecificJobs(
+  public List<Job> loadDataTypeSpecificJobs(
       final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) {
 
     return loadDataTypeSpecificJobs(entity.getDatatype(), entity, transaction);
   }
 
-  private static List<Job> loadDataTypeSpecificJobs(
+  private List<Job> loadDataTypeSpecificJobs(
       final AbstractDatatype dt,
       final EntityInterface entity,
       final Transaction<? extends TransactionContainer> transaction) {
@@ -275,19 +275,19 @@ public abstract class Job extends AbstractObservable implements Observer {
     }
     if (dt instanceof ReferenceDatatype2) {
       final RuleLoader t = new RuleLoader(0, 17, entity, transaction);
-      return Database.execute(t, transaction.getAccess()).getJobs();
+      return execute(t).getJobs();
     } else if (dt instanceof AbstractCollectionDatatype) {
       final AbstractDatatype datatype = ((AbstractCollectionDatatype) dt).getDatatype();
       return loadDataTypeSpecificJobs(datatype, entity, transaction);
     } else if (dt.getId() != null) {
       final RuleLoader t = new RuleLoader(0, dt.getId(), entity, transaction);
-      return Database.execute(t, transaction.getAccess()).getJobs();
+      return execute(t).getJobs();
     } else {
       return null;
     }
   }
 
-  public static List<Job> loadStandardJobs(
+  public List<Job> loadStandardJobs(
       final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) {
 
     final ArrayList<Job> jobs = new ArrayList<>();
@@ -302,13 +302,13 @@ public abstract class Job extends AbstractObservable implements Observer {
     // load general rules
     {
       final RuleLoader t = new RuleLoader(0, 0, entity, transaction);
-      jobs.addAll(Database.execute(t, transaction.getAccess()).getJobs());
+      jobs.addAll(execute(t).getJobs());
     }
 
     // load Role specific rules
     if (entity.hasRole()) {
       final RuleLoader t = new RuleLoader(0, entity.getRole().getId(), entity, transaction);
-      jobs.addAll(Database.execute(t, transaction.getAccess()).getJobs());
+      jobs.addAll(execute(t).getJobs());
     }
 
     // load data type specific rules
@@ -336,7 +336,7 @@ public abstract class Job extends AbstractObservable implements Observer {
     }
   }
 
-  public static List<Job> loadJobs(
+  public List<Job> loadJobs(
       final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) {
     final LinkedList<Job> jobs = new LinkedList<>();
 
diff --git a/src/main/java/caosdb/server/jobs/Schedule.java b/src/main/java/caosdb/server/jobs/Schedule.java
index 367fdcbd13c5830f46205ef05d386f98bb14492d..4e7f005f3d2f790008a991a0138d62013f6a545a 100644
--- a/src/main/java/caosdb/server/jobs/Schedule.java
+++ b/src/main/java/caosdb/server/jobs/Schedule.java
@@ -55,7 +55,7 @@ class ScheduledJob {
     this.job
         .getContainer()
         .getTransactionBenchmark()
-        .addBenchmark(this.job.getClass().getSimpleName(), this.runtime);
+        .addMeasurement(this.job.getClass().getSimpleName(), this.runtime);
   }
 
   void pause() {
diff --git a/src/main/java/caosdb/server/jobs/core/AccessControl.java b/src/main/java/caosdb/server/jobs/core/AccessControl.java
index 8b51cd582933d9c993c2427be7c399e7dc553df2..0dd2ef4740d1a17d6e6ed8b0cc04c8d66a56c918 100644
--- a/src/main/java/caosdb/server/jobs/core/AccessControl.java
+++ b/src/main/java/caosdb/server/jobs/core/AccessControl.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveSparseEntity;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.wrapper.Parent;
@@ -66,7 +65,7 @@ public class AccessControl extends ContainerJob {
       if (e.hasParents() && e.getParents().size() == 1) {
         final Parent par1 = e.getParents().get(0);
         if (par1.hasId() && par1.getId() > 0) {
-          Database.execute(new RetrieveSparseEntity(par1), getTransaction().getAccess());
+          execute(new RetrieveSparseEntity(par1));
         }
         if (par1.hasName()
             && par1.getName().equals("CommentAnnotation")
diff --git a/src/main/java/caosdb/server/jobs/core/CheckChildDependencyExistent.java b/src/main/java/caosdb/server/jobs/core/CheckChildDependencyExistent.java
index 407c6e549bdcb8968a7ca91093534574ec6e5e2e..a3d9f4e493b189e7fb49de69347c5b6149f9f885 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckChildDependencyExistent.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckChildDependencyExistent.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.GetChildren;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.jobs.EntityJob;
@@ -42,9 +41,7 @@ public class CheckChildDependencyExistent extends EntityJob {
   public final void run() {
     if (getEntity().getDomain() == null || getEntity().getDomain() == 0) {
 
-      final List<Integer> children =
-          Database.execute(new GetChildren(getEntity().getId()), getTransaction().getAccess())
-              .getList();
+      final List<Integer> children = execute(new GetChildren(getEntity().getId())).getList();
 
       // loop:
       for (final Integer id : children) {
diff --git a/src/main/java/caosdb/server/jobs/core/CheckFileStorageConsistency.java b/src/main/java/caosdb/server/jobs/core/CheckFileStorageConsistency.java
index b74a0319c981753a52d5fb962f9bf3add3347ba1..5af12ad71b4b3c5b12ac8321131b4d9cb29013d5 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckFileStorageConsistency.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckFileStorageConsistency.java
@@ -28,9 +28,9 @@ import caosdb.server.database.exceptions.TransactionException;
 import caosdb.server.entity.Message;
 import caosdb.server.jobs.FlagJob;
 import caosdb.server.jobs.JobAnnotation;
+import caosdb.server.transaction.FileStorageConsistencyCheck;
 import caosdb.server.transaction.Retrieve;
 import caosdb.server.transaction.Transaction;
-import caosdb.server.utils.FileStorageConsistencyCheck;
 import caosdb.server.utils.FileUtils;
 import caosdb.server.utils.Observable;
 import caosdb.server.utils.Observer;
diff --git a/src/main/java/caosdb/server/jobs/core/CheckParOblPropPresent.java b/src/main/java/caosdb/server/jobs/core/CheckParOblPropPresent.java
index ead4248f63fe5af4bf6e35778ecc34c7e3459da4..bee056c5763593ce6ef6a71f713019fae30a8bb5 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckParOblPropPresent.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckParOblPropPresent.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveFullEntity;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.StatementStatus;
@@ -49,8 +48,7 @@ public class CheckParOblPropPresent extends EntityJob {
     runJobFromSchedule(getEntity(), CheckParValid.class);
 
     if (getEntity().hasParents()) {
-      Database.execute(
-          new RetrieveFullEntity(getEntity().getParents()), getTransaction().getAccess());
+      execute(new RetrieveFullEntity(getEntity().getParents()));
     }
 
     // inherit properties
diff --git a/src/main/java/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java b/src/main/java/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java
index eec99f5ed00384d4be447a7ef3cd51b19178dccf..fd5eaab0f0d302e9b5586693ef502c32d607d773 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.GetDependentEntities;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.jobs.EntityJob;
@@ -45,9 +44,7 @@ public class CheckReferenceDependencyExistent extends EntityJob {
 
       // retrieve dependent entities
       final List<Integer> depends =
-          Database.execute(
-                  new GetDependentEntities(getEntity().getId()), getTransaction().getAccess())
-              .getList();
+          execute(new GetDependentEntities(getEntity().getId())).getList();
 
       // loop:
       for (final Integer id : depends) {
diff --git a/src/main/java/caosdb/server/jobs/core/CheckTargetPathValid.java b/src/main/java/caosdb/server/jobs/core/CheckTargetPathValid.java
index 3dc0c4bba460dfa7f6eb2eec338e5dea738da9e2..404e6b3a0d82950061ba4168a7a6f32e5f847400 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckTargetPathValid.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckTargetPathValid.java
@@ -55,7 +55,8 @@ public class CheckTargetPathValid extends EntityJob {
       }
 
       try {
-        FileSystem.checkTarget(getEntity(), getTransaction().getAccess());
+        FileSystem.checkTarget(
+            getEntity(), getTransaction().getAccess(), getTransaction().getTransactionBenchmark());
       } catch (final Message m) {
         getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
         getEntity().addError(m);
diff --git a/src/main/java/caosdb/server/jobs/core/History.java b/src/main/java/caosdb/server/jobs/core/History.java
index 7e7643040196333b923536b13d8b84ae6b962311..ee72413a46e65f8b8a020315e3c2e93c7b8ccd97 100644
--- a/src/main/java/caosdb/server/jobs/core/History.java
+++ b/src/main/java/caosdb/server/jobs/core/History.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveTransactionHistory;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.jobs.FlagJob;
@@ -44,7 +43,7 @@ public class History extends FlagJob {
           try {
             entity.checkPermission(EntityPermission.RETRIEVE_HISTORY);
             final RetrieveTransactionHistory t = new RetrieveTransactionHistory(entity);
-            Database.execute(t, getTransaction().getAccess());
+            execute(t);
           } catch (final AuthorizationException e) {
             entity.setEntityStatus(EntityStatus.UNQUALIFIED);
             entity.addError(ServerMessages.AUTHORIZATION_ERROR);
diff --git a/src/main/java/caosdb/server/jobs/core/Inheritance.java b/src/main/java/caosdb/server/jobs/core/Inheritance.java
index ed25c64cbf02d16566addc79961e36e0fdffad81..f7149eb3c74270012db011385e17bbf01fdd01a2 100644
--- a/src/main/java/caosdb/server/jobs/core/Inheritance.java
+++ b/src/main/java/caosdb/server/jobs/core/Inheritance.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveFullEntity;
 import caosdb.server.entity.Entity;
 import caosdb.server.entity.EntityInterface;
@@ -74,7 +73,7 @@ public class Inheritance extends EntityJob {
 
             runJobFromSchedule(getEntity(), CheckParValid.class);
 
-            Database.execute(new RetrieveFullEntity(parent), getTransaction().getAccess());
+            execute(new RetrieveFullEntity(parent));
 
             if (parent.hasProperties()) {
               // loop over all properties of the parent and
@@ -163,7 +162,7 @@ public class Inheritance extends EntityJob {
                 }
               }
             } else {
-              Database.execute(new RetrieveFullEntity(validProperty), getTransaction().getAccess());
+              execute(new RetrieveFullEntity(validProperty));
             }
 
             if (validProperty.getEntityStatus() == EntityStatus.VALID
diff --git a/src/main/java/caosdb/server/jobs/core/InsertFilesInDir.java b/src/main/java/caosdb/server/jobs/core/InsertFilesInDir.java
index 773ac27525e0b28a1a10f23f1a2ee9cfd9fbf799..6b3144eb08b471ea90b836978c76b4ff643c313d 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;
@@ -61,6 +62,9 @@ public class InsertFilesInDir extends FlagJob {
   private Pattern include = null;
   private Pattern exclude = null;
   private boolean forceSymLinks = false;
+  private Pattern valueParser =
+      Pattern.compile(
+          "(?:(?:-p\\s*([^\\s]*?)\\s+)|(?:-i\\s*([^\\s]*?)\\s+)|(?:-e\\s*([^\\s]*?)\\s+)|(--force-allow-symlinks\\s+))|([^-].*)");
 
   /**
    * @return a List of directories which subdirs are allowed to be batch-added. Needs to be
@@ -85,14 +89,10 @@ public class InsertFilesInDir extends FlagJob {
     return ret;
   }
 
-  @Override
-  protected void job(final String value) {
+  public String parseValue(String value) {
 
-    String dirStr = value;
-    final Pattern pattern =
-        Pattern.compile(
-            "(?:(?:-p\\s*([^\\s]*?)\\s+)|(?:-i\\s*([^\\s]*?)\\s+)|(?:-e\\s*([^\\s]*?)\\s+)|(--force-allow-symlinks\\s+))|([^-].*)");
-    final Matcher matcher = pattern.matcher(value);
+    String ret = value;
+    final Matcher matcher = valueParser.matcher(value);
     while (matcher.find()) {
       if (matcher.group(1) != null) {
         this.prefix = matcher.group(1).replaceFirst("/$", "") + "/";
@@ -107,9 +107,16 @@ public class InsertFilesInDir extends FlagJob {
         this.forceSymLinks = true;
       }
       if (matcher.group(5) != null) {
-        dirStr = matcher.group(5);
+        ret = matcher.group(5);
       }
     }
+    return ret;
+  }
+
+  @Override
+  protected void job(final String value) {
+
+    String dirStr = parseValue(value);
 
     final File dir = new File(dirStr);
 
@@ -153,7 +160,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 +170,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();
                   }
                 }
@@ -223,7 +230,10 @@ public class InsertFilesInDir extends FlagJob {
             // add create symlink and file record to this
             // container if the target
             // path is allowed
-            if (FileSystem.checkTarget(newFileEntity, getTransaction().getAccess())
+            if (FileSystem.checkTarget(
+                    newFileEntity,
+                    getTransaction().getAccess(),
+                    getTransaction().getTransactionBenchmark())
                 && newFileEntity.getEntityStatus() != EntityStatus.UNQUALIFIED) {
 
               final File link =
@@ -243,24 +253,32 @@ public class InsertFilesInDir extends FlagJob {
     return i;
   }
 
+  boolean isExcluded(File f) throws IOException {
+    return this.exclude != null && this.exclude.matcher(f.getCanonicalPath()).find();
+  }
+
+  boolean isNotIncluded(File f) throws IOException {
+    return this.include != null && !this.include.matcher(f.getCanonicalPath()).find();
+  }
+
   private boolean shouldBeProcessed(final File sub) throws IOException {
-    if (this.include != null && !this.include.matcher(sub.getCanonicalPath()).matches()) {
-      getContainer()
-          .addMessage(
-              new Message(
-                  MessageType.Warning,
-                  1,
-                  "Not explicitly included directory or file: " + sub.getCanonicalPath()));
-      return false;
-    }
-    if (this.exclude != null && this.exclude.matcher(sub.getCanonicalPath()).matches()) {
-      getContainer()
-          .addMessage(
-              new Message(
-                  MessageType.Warning,
-                  2,
-                  "Explicitly excluded directory or file: " + sub.getCanonicalPath()));
-      return false;
+    if (sub.isFile()) {
+      if (this.isNotIncluded(sub)) {
+        getContainer()
+            .addMessage(
+                new Message(
+                    MessageType.Warning,
+                    1,
+                    "Not explicitly included file: " + sub.getCanonicalPath()));
+        return false;
+      }
+      if (this.isExcluded(sub)) {
+        getContainer()
+            .addMessage(
+                new Message(
+                    MessageType.Warning, 2, "Explicitly excluded file: " + sub.getCanonicalPath()));
+        return false;
+      }
     }
     if (sub.isHidden()) {
       getContainer()
@@ -300,7 +318,7 @@ public class InsertFilesInDir extends FlagJob {
   }
 
   private void loadJobs(final Entity e) {
-    final List<Job> loadJobs = Job.loadJobs(e, getTransaction());
+    final List<Job> loadJobs = loadJobs(e, getTransaction());
     getTransaction().getSchedule().addAll(loadJobs);
   }
 
diff --git a/src/main/java/caosdb/server/jobs/core/ResolveNames.java b/src/main/java/caosdb/server/jobs/core/ResolveNames.java
index fd5498f468e44801279655ba21a1702da12c168e..f5e1695f34c30a78f136950f7aae3ae00be170e4 100644
--- a/src/main/java/caosdb/server/jobs/core/ResolveNames.java
+++ b/src/main/java/caosdb/server/jobs/core/ResolveNames.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.GetIDByName;
 import caosdb.server.database.exceptions.EntityDoesNotExistException;
 import caosdb.server.entity.Entity;
@@ -45,9 +44,7 @@ public class ResolveNames extends ContainerJob {
     for (final Entity e : container) {
       if (e.hasName() && !e.hasId()) {
         try {
-          final List<Integer> c =
-              Database.execute(new GetIDByName(e.getName(), false), getTransaction().getAccess())
-                  .getList();
+          final List<Integer> c = execute(new GetIDByName(e.getName(), false)).getList();
 
           e.setId(c.get(0));
           e.setEntityStatus(EntityStatus.QUALIFIED);
diff --git a/src/main/java/caosdb/server/jobs/core/RetrieveAllJob.java b/src/main/java/caosdb/server/jobs/core/RetrieveAllJob.java
index f522986c6b40ed73fdbb752226a7dd66de4d040e..0fba1223a3bd0e984c5c2b6ff43dd58070b5bbc5 100644
--- a/src/main/java/caosdb/server/jobs/core/RetrieveAllJob.java
+++ b/src/main/java/caosdb/server/jobs/core/RetrieveAllJob.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveAll;
 import caosdb.server.jobs.FlagJob;
 import caosdb.server.jobs.JobAnnotation;
@@ -37,7 +36,7 @@ public class RetrieveAllJob extends FlagJob {
       if (value == null) {
         value = "ENTITY";
       }
-      Database.execute(new RetrieveAll(getContainer(), value), getTransaction().getAccess());
+      execute(new RetrieveAll(getContainer(), value));
     }
   }
 }
diff --git a/src/main/java/caosdb/server/jobs/core/UpdateUnitConverters.java b/src/main/java/caosdb/server/jobs/core/UpdateUnitConverters.java
index 86f5b2d23721e97cef6e1b9ebd356cbf140b0cc6..8d0118b80582e3a65b613caae2200719af428819 100644
--- a/src/main/java/caosdb/server/jobs/core/UpdateUnitConverters.java
+++ b/src/main/java/caosdb/server/jobs/core/UpdateUnitConverters.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.jobs.core;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.InsertLinCon;
 import caosdb.server.jobs.EntityJob;
 import caosdb.server.jobs.JobAnnotation;
@@ -66,7 +65,7 @@ public class UpdateUnitConverters extends EntityJob {
     if (signature_from != signature_to) {
       final InsertLinCon t =
           new InsertLinCon(signature_from, signature_to, a, b_dividend, b_divisor, c);
-      Database.execute(t, getTransaction().getAccess());
+      execute(t);
     }
   }
 }
diff --git a/src/main/java/caosdb/server/jobs/extension/AWIBoxLoan.java b/src/main/java/caosdb/server/jobs/extension/AWIBoxLoan.java
index 65c9689623fd266dc01d77ff7b8dc97eed1eff81..8df3bc2f8b798a8495f9bb670fa7ce23c5eba9c2 100644
--- a/src/main/java/caosdb/server/jobs/extension/AWIBoxLoan.java
+++ b/src/main/java/caosdb/server/jobs/extension/AWIBoxLoan.java
@@ -11,8 +11,6 @@ import caosdb.server.entity.Message;
 import caosdb.server.entity.Message.MessageType;
 import caosdb.server.entity.wrapper.Property;
 import caosdb.server.jobs.JobAnnotation;
-import caosdb.server.jobs.core.CheckNoAdditionalPropertiesPresent;
-import caosdb.server.jobs.core.CheckNoOverridesPresent;
 import caosdb.server.jobs.core.CheckPropValid;
 import caosdb.server.permissions.EntityACL;
 import caosdb.server.permissions.EntityACLFactory;
@@ -392,7 +390,9 @@ public class AWIBoxLoan extends AWIBoxLoanModel {
         if (!isBoxRecord(e) || !hasOnlyAllowedBoxProperties4RequestLoan(e)) {
           return false;
         }
-        appendJob(e, CheckNoAdditionalPropertiesPresent.class);
+        // TODO this breaks the box loan functionality if any other prior changes have been made to
+        // the box
+        //        appendJob(e, CheckNoAdditionalPropertiesPresent.class);
       }
       return true;
     }
@@ -440,8 +440,10 @@ public class AWIBoxLoan extends AWIBoxLoanModel {
           setCuratorAsOwner(e);
         }
         setLoanRequestDate(e);
-        appendJob(e, CheckNoAdditionalPropertiesPresent.class);
-        appendJob(e, CheckNoOverridesPresent.class);
+        // TODO this check breaks the box loan functionality if any other changes have been made to
+        // the box entity
+        //        appendJob(e, CheckNoAdditionalPropertiesPresent.class);
+        //        appendJob(e, CheckNoOverridesPresent.class);
       }
       appendJob(AWIBoxLoanRequestLoanCuratorEmail.class);
       return true;
@@ -476,8 +478,10 @@ public class AWIBoxLoan extends AWIBoxLoanModel {
         && isPersonRecord(getContainer().get(0))
         && checkUniqueName(getContainer().get(0))
         && checkEmail(getContainer().get(0))) {
-      appendJob(getContainer().get(0), CheckNoAdditionalPropertiesPresent.class);
-      appendJob(getContainer().get(0), CheckNoOverridesPresent.class);
+      // TODO this check breaks the box loan functionality if any other changes have been made to
+      // the box entity
+      //      appendJob(getContainer().get(0), CheckNoAdditionalPropertiesPresent.class);
+      //      appendJob(getContainer().get(0), CheckNoOverridesPresent.class);
       logger.trace("isRequestReturnSetUser: true");
       return true;
     }
@@ -536,7 +540,7 @@ public class AWIBoxLoan extends AWIBoxLoanModel {
    */
   boolean hasOnlyAllowedLoanProperties4RequestReturn(EntityInterface e) {
     runJobFromSchedule(e, CheckPropValid.class);
-    appendJob(e, CheckNoOverridesPresent.class);
+    //    appendJob(e, CheckNoOverridesPresent.class);
 
     boolean foundReturnRequested = false;
     for (Property p : e.getProperties()) {
diff --git a/src/main/java/caosdb/server/permissions/EntityPermission.java b/src/main/java/caosdb/server/permissions/EntityPermission.java
index 7fae8a76a8cc87cbfb2525c34102d5418d58e94e..1747a55810cf9718abc9627cad8102f88b2a5cac 100644
--- a/src/main/java/caosdb/server/permissions/EntityPermission.java
+++ b/src/main/java/caosdb/server/permissions/EntityPermission.java
@@ -65,7 +65,7 @@ public class EntityPermission extends Permission {
           "This bitNumber is too big. This implementation only handles bitNumbers up to 61.");
     }
     if (instances.contains(this)) {
-      throw new CaosDBException("This EntityPermission is defined yet.");
+      throw new CaosDBException("This EntityPermission is defined already.");
     } else {
       instances.add(this);
     }
diff --git a/src/main/java/caosdb/server/query/Backreference.java b/src/main/java/caosdb/server/query/Backreference.java
index f8d4f86f5d04d6b724a3fdec66c1a24d739726af..c39ce43923372811caf8607550a9852d1a518c63 100644
--- a/src/main/java/caosdb/server/query/Backreference.java
+++ b/src/main/java/caosdb/server/query/Backreference.java
@@ -192,7 +192,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface {
     addBenchmark("initBackreference()", t2 - t1);
 
     if (this.entitiesTable != null) {
-      Query.applyQueryTemplates(query, this.entitiesTable);
+      getQuery().applyQueryTemplates(query, this.entitiesTable);
       addBenchmark("applyQueryTemplates()", System.currentTimeMillis() - t2);
     }
   }
diff --git a/src/main/java/caosdb/server/query/POV.java b/src/main/java/caosdb/server/query/POV.java
index 01d2cb1b9bb2bd51e18ccacc438ff8ac75788762..6a57caa780d76c5279a37eaba09d44c021215d7c 100644
--- a/src/main/java/caosdb/server/query/POV.java
+++ b/src/main/java/caosdb/server/query/POV.java
@@ -412,7 +412,7 @@ public class POV implements EntityFilterInterface {
     query.addBenchmark(getClass().getSimpleName() + ".initPOVPropertiesTable()", t3 - t2);
 
     if (this.refIdsTable != null) {
-      Query.applyQueryTemplates(query, this.refIdsTable);
+      query.getQuery().applyQueryTemplates(query, this.refIdsTable);
       query.addBenchmark(
           getClass().getSimpleName() + ".applyQueryTemplates()", System.currentTimeMillis() - t3);
     }
diff --git a/src/main/java/caosdb/server/query/Query.java b/src/main/java/caosdb/server/query/Query.java
index c9bff5a5f11aa5775582c613d80062419f9adb4c..a62a6acae9bcc413e7ab06c812312f8e8b8fe323 100644
--- a/src/main/java/caosdb/server/query/Query.java
+++ b/src/main/java/caosdb/server/query/Query.java
@@ -26,7 +26,6 @@ import static caosdb.server.database.DatabaseUtils.bytes2UTF8;
 
 import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import caosdb.server.database.backend.implementation.MySQL.MySQLHelper;
@@ -284,7 +283,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    * @throws SQLException
    * @throws QueryException
    */
-  public static void applyQueryTemplates(final QueryInterface query, final String resultSet)
+  public void applyQueryTemplates(final QueryInterface query, final String resultSet)
       throws QueryException {
     try {
       final Map<Integer, String> queryTemplates = getQueryTemplates(query, resultSet);
@@ -300,7 +299,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
         // ... check for RETRIEVE:ENTITY permission...
         final EntityInterface e =
-            Database.execute(new RetrieveSparseEntity(q.getKey()), query.getAccess()).getEntity();
+            execute(new RetrieveSparseEntity(q.getKey()), query.getAccess()).getEntity();
         final EntityACL entityACL = e.getEntityACL();
         if (!entityACL.isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
           // ... and ignore if not.
@@ -415,6 +414,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 +434,10 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     this.type = cq.t;
     this.filter = cq.filter;
     this.selections = cq.s;
+    final long t2 = System.currentTimeMillis();
+    if (t2 - t1 > 1000) {
+      addBenchmark("LONG_PARSING: " + this.query, t2 - t1);
+    }
   }
 
   private String executeStrategy() throws QueryException {
@@ -528,8 +532,6 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     this.access = access;
   }
 
-  private final TransactionBenchmark benchmark = TransactionBenchmark.getSubBenchmark();
-
   /**
    * Filter out all entities which may not be retrieved by this user due to a missing RETRIEVE
    * permission. This one is also designed for filtering of intermediate results.
@@ -539,7 +541,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    * @throws SQLException
    * @throws TransactionException
    */
-  public static void filterEntitiesWithoutRetrievePermission(
+  public void filterEntitiesWithoutRetrievePermission(
       final QueryInterface query, final String resultSet)
       throws SQLException, TransactionException {
     if (!filterEntitiesWithoutRetrievePermisions) {
@@ -551,7 +553,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
       while (rs.next()) {
         final long t1 = System.currentTimeMillis();
         final Integer id = rs.getInt("id");
-        if (!Database.execute(new RetrieveSparseEntity(id), query.getAccess())
+        if (!execute(new RetrieveSparseEntity(id), query.getAccess())
             .getEntity()
             .getEntityACL()
             .isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
@@ -584,7 +586,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     while (iterator.hasNext()) {
       final long t1 = System.currentTimeMillis();
       final Integer id = iterator.next();
-      if (!Database.execute(new RetrieveSparseEntity(id), getAccess())
+      if (!execute(new RetrieveSparseEntity(id), getAccess())
           .getEntity()
           .getEntityACL()
           .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) {
@@ -638,6 +640,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
   private int targetSetCount = -1;
 
+  private TransactionBenchmark benchmark;
+
   @Override
   public void addToElement(final Element parent) {
     final Element ret = new Element("Query");
@@ -692,8 +696,6 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
       ret.addContent(selection);
     }
 
-    ret.addContent(this.benchmark.toElememt());
-
     parent.addContent(ret);
   }
 
@@ -723,6 +725,19 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
   @Override
   public void addBenchmark(final String str, final long time) {
-    this.benchmark.addBenchmark(this.getClass().getSimpleName().toString() + "." + str, time);
+    getTransactionBenchmark()
+        .addMeasurement(this.getClass().getSimpleName().toString() + "." + str, time);
+  }
+
+  @Override
+  public TransactionBenchmark getTransactionBenchmark() {
+    if (benchmark == null) {
+      if (container != null) {
+        benchmark = container.getTransactionBenchmark().getBenchmark(getClass());
+      } else {
+        benchmark = TransactionBenchmark.getRootInstance().getBenchmark(getClass());
+      }
+    }
+    return benchmark;
   }
 }
diff --git a/src/main/java/caosdb/server/query/SubProperty.java b/src/main/java/caosdb/server/query/SubProperty.java
index a5abe0b7b6cd3940f16cfe29461c13a019416d41..12629640ec4e7780c1b1d5933e7d6e1711f635f7 100644
--- a/src/main/java/caosdb/server/query/SubProperty.java
+++ b/src/main/java/caosdb/server/query/SubProperty.java
@@ -87,7 +87,7 @@ public class SubProperty implements QueryInterface, EntityFilterInterface {
 
         this.filter.apply(this);
 
-        Query.filterEntitiesWithoutRetrievePermission(this, this.sourceSet);
+        getQuery().filterEntitiesWithoutRetrievePermission(this, this.sourceSet);
 
         final CallableStatement callFinishSubProperty =
             getConnection().prepareCall("call finishSubProperty(?,?,?)");
diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java
index c88bbd75a34aea81198b2c9d48702ca6f66a41e0..ea4e65f0a60d72cb5da6cb03b2cec44848dbc3c6 100644
--- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java
+++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java
@@ -33,6 +33,7 @@ import caosdb.server.accessControl.UserSources;
 import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import caosdb.server.entity.Message;
 import caosdb.server.utils.ServerMessages;
+import caosdb.server.utils.WebinterfaceUtils;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
@@ -55,7 +56,6 @@ import org.jdom2.JDOMException;
 import org.jdom2.input.SAXBuilder;
 import org.restlet.data.Form;
 import org.restlet.data.Header;
-import org.restlet.data.MediaType;
 import org.restlet.data.Parameter;
 import org.restlet.data.Status;
 import org.restlet.representation.Representation;
@@ -81,7 +81,12 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
   private String[] requestedItems = null;
   private ArrayList<Integer> requestedIDs = new ArrayList<Integer>();
   private ArrayList<String> requestedNames = new ArrayList<String>();
-  private String xslScript = "webcaosdb.xsl";
+  private WebinterfaceUtils utils;
+
+  /** Return the {@link WebinterfaceUtils} instance for this resource. */
+  public WebinterfaceUtils getUtils() {
+    return this.utils;
+  }
 
   public static class xmlNotWellFormedException extends Exception {
     private static final long serialVersionUID = -6836378704013776849L;
@@ -101,7 +106,8 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
   }
 
   public Subject getUser() {
-    return SecurityUtils.getSubject();
+    Subject ret = SecurityUtils.getSubject();
+    return ret;
   }
 
   /**
@@ -115,6 +121,8 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
       getRequest().setEntity(r);
     }
 
+    this.utils = WebinterfaceUtils.getInstance(getHostRef());
+
     this.timestamp = getRequest().getDate().getTime();
 
     this.sRID = getRequest().getAttributes().get("SRID").toString();
@@ -324,29 +332,36 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
   }
 
   protected String getXSLScript() {
-    return this.xslScript;
-  }
-
-  protected void setXSLScript(final String s) {
-    this.xslScript = s;
+    return this.utils.getWebinterfaceURI("webcaosdb.xsl");
   }
 
+  /** Wrap an element for an "OK" response. */
   protected JdomRepresentation ok(Element root) {
     return ok(new Document(root));
   }
 
+  /**
+   * Return a JDomRepresentation of the document and leave the {@link Response}'s {@link Status}
+   * where it is - which is usually 200 - OK when this method happens to be called.
+   */
   protected JdomRepresentation ok(final Document doc) {
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return new JdomRepresentation(doc, "  ", getXSLScript());
   }
 
+  /**
+   * Return a Representation containing the error message and set the {@link Response}'s {@link
+   * Status}.
+   *
+   * @param m - the error message.
+   * @param status - the response status
+   * @return A Representation of the error.
+   */
   protected Representation error(final Message m, final Status status) {
     final Document doc = new Document();
     final Element root = generateRootElement();
     root.addContent(m.toElement().setName("Error"));
     doc.setRootElement(root);
-    return error(
-        new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript()),
-        status);
+    return error(new JdomRepresentation(doc, "  ", getXSLScript()), status);
   }
 
   protected Representation error(Representation entity, Status status) {
@@ -362,12 +377,19 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
     return error((Representation) null, status);
   }
 
+  /**
+   * Return a Representation containing the warning message but leave the {@link Response}'s {@link
+   * Status} where it is - which is usually 200 - OK when this method happens to be called.
+   *
+   * @param m - the warning message.
+   * @return A Representation of the warning.
+   */
   protected JdomRepresentation warning(final Message m) {
     final Document doc = new Document();
     final Element root = generateRootElement();
     root.addContent(m.toElement().setName("Warning"));
     doc.setRootElement(root);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return new JdomRepresentation(doc, "  ", getXSLScript());
   }
 
   protected Representation noWellFormedNess() {
diff --git a/src/main/java/caosdb/server/resource/FileSystemResource.java b/src/main/java/caosdb/server/resource/FileSystemResource.java
index 0e8d59a107d3f8dbb83d966a9e251641ac9bdd8a..37f0e9ffda3f81b84c6001c5521ba5c8451b473e 100644
--- a/src/main/java/caosdb/server/resource/FileSystemResource.java
+++ b/src/main/java/caosdb/server/resource/FileSystemResource.java
@@ -26,9 +26,12 @@ import static caosdb.server.FileSystem.getFromFileSystem;
 import static java.net.URLDecoder.decode;
 
 import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
+import caosdb.server.database.exceptions.EntityDoesNotExistException;
 import caosdb.server.database.misc.TransactionBenchmark;
 import caosdb.server.entity.Entity;
 import caosdb.server.entity.FileProperties;
+import caosdb.server.entity.Message;
+import caosdb.server.entity.Message.MessageType;
 import caosdb.server.entity.RetrieveEntity;
 import caosdb.server.entity.container.TransactionContainer;
 import caosdb.server.permissions.EntityPermission;
@@ -56,6 +59,11 @@ import org.restlet.representation.Representation;
  */
 public class FileSystemResource extends AbstractCaosDBServerResource {
 
+  public static Message ORPHANED_FILE_WARNING =
+      new Message(
+          MessageType.Warning,
+          "Orphaned file. The file is not tracked. This is probably a harmless inconsistency but it might be a sign of other problems.");
+
   /**
    * Download a File from the CaosDBFileSystem. Only one File per Request.
    *
@@ -117,13 +125,20 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
       rootElem.addContent(folder);
       final long t2 = System.currentTimeMillis();
 
-      getBenchmark().addBenchmark(this.getClass().getSimpleName(), t2 - t1);
+      getBenchmark().addMeasurement(this.getClass().getSimpleName(), t2 - t1);
       rootElem.addContent(getBenchmark().toElememt());
       final Document doc = new Document(rootElem);
       return ok(doc);
 
     } else {
 
+      try {
+        getEntity(specifier).checkPermission(EntityPermission.RETRIEVE_FILE);
+      } catch (EntityDoesNotExistException exception) {
+        // This file in the file system has no corresponding File record.
+        return error(ServerMessages.NOT_PERMITTED, Status.CLIENT_ERROR_FORBIDDEN);
+      }
+
       final MediaType mt = MediaType.valueOf(FileUtils.getMimeType(file));
       final FileRepresentation ret = new FileRepresentation(file, mt);
       ret.setDisposition(new Disposition(Disposition.TYPE_ATTACHMENT));
@@ -132,6 +147,10 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
     }
   }
 
+  TransactionBenchmark getBenchmark() {
+    return TransactionBenchmark.getRootInstance().getBenchmark(getClass());
+  }
+
   protected Attribute getThumbnailAttribute(
       final File file, final File child, final String referenceString) {
     final String thpath =
@@ -145,28 +164,39 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
     return null;
   }
 
-  private TransactionBenchmark benchmark;
-
-  private TransactionBenchmark getBenchmark() {
-    if (this.benchmark == null) {
-      this.benchmark = TransactionBenchmark.getSubBenchmark();
-    }
-    return this.benchmark;
-  }
-
+  /**
+   * Return an element for the given file on a file system.
+   *
+   * <p>If there is no File entity for this file, an element without `id` is returned, instead a
+   * corresponding message is added to the element.
+   */
   Element getFileElement(final String directory, final File file) throws Exception {
     final Element celem = new Element("file");
-    celem.setAttribute(
-        "id",
-        getEntityID((directory.endsWith("/") ? directory : directory + "/") + file.getName()));
     celem.setAttribute("name", file.getName());
+
+    try {
+      final String entId =
+          getEntityID((directory.endsWith("/") ? directory : directory + "/") + file.getName());
+      celem.setAttribute("id", entId);
+    } catch (EntityDoesNotExistException exception) {
+      // This file in the file system has no corresponding File record.
+      ORPHANED_FILE_WARNING.addToElement(celem);
+    }
     return celem;
   }
 
   protected String getEntityID(final String path) throws Exception {
-    return getEntity(path).getId().toString();
+    final Entity fileEnt = getEntity(path);
+    return fileEnt.getId().toString();
   }
 
+  /**
+   * Throws EntityDoesNotExistException when there is not entity with that path.
+   *
+   * @param path
+   * @return
+   * @throws Exception
+   */
   private Entity getEntity(final String path) throws Exception {
     final long t1 = System.currentTimeMillis();
     final TransactionContainer c = new TransactionContainer();
@@ -177,25 +207,15 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
     final Transaction<?> t = new RetrieveSparseEntityByPath(c);
     t.execute();
     final long t2 = System.currentTimeMillis();
-    getBenchmark().addBenchmark(this.getClass().getSimpleName() + ".getEntity", t2 - t1);
+    getBenchmark().addMeasurement(this.getClass().getSimpleName() + ".getEntity", t2 - t1);
     return e;
   }
 
   protected File getFile(final String path) throws Exception {
     final File ret = getFromFileSystem(path);
-    if (ret != null && ret.isFile()) {
-      checkPermissions(path);
-    }
     return ret;
   }
 
-  private final void checkPermissions(final String path) throws Exception {
-    final long t1 = System.currentTimeMillis();
-    getEntity(path).checkPermission(EntityPermission.RETRIEVE_FILE);
-    final long t2 = System.currentTimeMillis();
-    getBenchmark().addBenchmark(this.getClass().getSimpleName() + ".checkPermissions", t2 - t1);
-  }
-
   @Override
   protected Representation httpPostInChildClass(final Representation entity)
       throws ConnectionException, JDOMException {
diff --git a/src/main/java/caosdb/server/resource/InfoResource.java b/src/main/java/caosdb/server/resource/InfoResource.java
index 0b55f9f3e54f0a63c6e573e50bb03753fd98fbac..924d66980e5da27ec998fd361b95a8f0769e4229 100644
--- a/src/main/java/caosdb/server/resource/InfoResource.java
+++ b/src/main/java/caosdb/server/resource/InfoResource.java
@@ -30,43 +30,30 @@ 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 the /Info GET request to CaosDB.
- */
+/** 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.
-     */
+  /**
+   * 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();
     final Element root = generateRootElement();
     root.addContent(Info.toElement());
     root.addContent(FlagInfo.toElement());
-    root.addContent(TransactionBenchmark.getInstance().toElememt());
+    root.addContent(TransactionBenchmark.getRootInstance().toElememt());
     doc.setRootElement(root);
     return ok(doc);
   }
 
-    /**
-     * There is no POST request specified for the /Info resource.
-     */
+  /** There is no POST request specified for the /Info resource. */
   @Override
   protected Representation httpPostInChildClass(final Representation entity)
       throws ConnectionException, JDOMException {
     return null;
   }
-
-    /**
-     * TODO: Can this stub be removed?
-     */
-  @Deprecated
-  @Options
-  public static Representation describeResource() {
-    return null;
-  }
 }
diff --git a/src/main/java/caosdb/server/resource/JdomRepresentation.java b/src/main/java/caosdb/server/resource/JdomRepresentation.java
index 2443b95c20b3f659d6a2f9e88c0dd9fabb563088..04fbdac3fdd13f9e5c4f025cb2364386deac71f0 100644
--- a/src/main/java/caosdb/server/resource/JdomRepresentation.java
+++ b/src/main/java/caosdb/server/resource/JdomRepresentation.java
@@ -56,30 +56,21 @@ public class JdomRepresentation extends WriterRepresentation {
 
   private final Document document;
   private final String indent;
-  private final Reference reference;
-  private final String xslPath;
 
   /**
    * Constructor.
    *
-   * @param mediaType This should be TEXT_XML, but we'll leave it open to use others.
    * @param document A JDOM Document parsed from elsewhere.
-   * @param indent To make that representation better to read by a human.
-   * @param reference
+   * @param indent Usually a number of whitespaces for better human readbility.
    * @param xslPath A String containing a meaningful path to a xslt file.
    */
-  public JdomRepresentation(
-      final Document document,
-      final MediaType mediaType,
-      final String indent,
-      final Reference reference,
-      final String xslPath) {
-    super(mediaType);
-    this.xslPath = xslPath;
-    this.reference = reference;
+  public JdomRepresentation(final Document document, final String indent, final String xslPath) {
+    super(MediaType.TEXT_XML);
     this.indent = indent;
     this.document = document;
-    addStyleSheet();
+    if (xslPath != null && document != null) {
+      addStyleSheet(document, xslPath);
+    }
   }
 
   public static final String getWebUIRef(final Reference reference) {
@@ -89,14 +80,10 @@ public class JdomRepresentation extends WriterRepresentation {
   }
 
   /** adds the xslt processing instruction to the document. */
-  protected final void addStyleSheet() {
-    if (this.document != null) {
-      final ProcessingInstruction pi =
-          new ProcessingInstruction(
-              "xml-stylesheet",
-              "type=\"text/xsl\" href=\"" + getWebUIRef(this.reference) + this.xslPath + "\" ");
-      this.document.getContent().add(0, pi);
-    }
+  protected final void addStyleSheet(Document document, String xslPath) {
+    final ProcessingInstruction pi =
+        new ProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"" + xslPath + "\" ");
+    document.getContent().add(0, pi);
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java
index 81a84ab631d9cd2206fd163b5c64eaac4c967286..b23a9593918ead75a6b38216838995e09ac67bc7 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,9 +206,15 @@ 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 {
+    // TODO getUser().checkPermission("SCRIPTING:EXECUTE:" + commandLine.get(0));
     caller =
         new ServerSideScriptingCaller(
             commandLine.toArray(new String[commandLine.size()]), timeoutMs, files, authToken);
diff --git a/src/main/java/caosdb/server/resource/ServerPropertiesResource.java b/src/main/java/caosdb/server/resource/ServerPropertiesResource.java
index 9b878e3338a9d0b0c74704acb412ac92ceb1f0c4..347919d9c7d0f604734790f2ea09b5e2bac54042 100644
--- a/src/main/java/caosdb/server/resource/ServerPropertiesResource.java
+++ b/src/main/java/caosdb/server/resource/ServerPropertiesResource.java
@@ -13,7 +13,11 @@ public class ServerPropertiesResource extends AbstractCaosDBServerResource {
   @Override
   protected void doInit() {
     super.doInit();
-    setXSLScript("xsl/server_properties.xsl");
+  }
+
+  @Override
+  protected String getXSLScript() {
+    return getUtils().getWebinterfaceURI("xsl/server_properties.xsl");
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/resource/TestCaseFileSystemResource.java b/src/main/java/caosdb/server/resource/TestCaseFileSystemResource.java
deleted file mode 100644
index 903ef0d5aca9a91eaeaff3693b7f77ef51e6cc46..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/resource/TestCaseFileSystemResource.java
+++ /dev/null
@@ -1,73 +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.resource;
-
-import caosdb.server.accessControl.Principal;
-import caosdb.server.entity.FileProperties;
-import caosdb.server.utils.FileUtils;
-import java.io.File;
-import org.jdom2.Element;
-
-public class TestCaseFileSystemResource extends FileSystemResource {
-
-  @Override
-  protected void doInit() {
-    super.doInit();
-    setXSLScript("webcaosdb.xsl");
-  }
-
-  private static final String BASEPATH = "./testfiles/";
-
-  @Override
-  protected File getFile(final String path) throws Exception {
-    final File f = new File(BASEPATH + path);
-    return (f != null && f.exists() ? f : null);
-  }
-
-  @Override
-  protected String getEntityID(final String path) throws Exception {
-    return "123412341234";
-  }
-
-  @Override
-  protected Element generateRootElement() {
-    final Element retRoot = new Element("Response");
-
-    if (getUser() != null && getUser().isAuthenticated()) {
-      retRoot.setAttribute("username", ((Principal) getUser().getPrincipal()).getUsername());
-      retRoot.setAttribute("realm", ((Principal) getUser().getPrincipal()).getRealm());
-    }
-    retRoot.setAttribute("srid", getSRID());
-    if (this.getCRID() != null) {
-      retRoot.setAttribute("crid", this.getCRID());
-    }
-    retRoot.setAttribute("timestamp", getTimestamp().toString());
-    retRoot.setAttribute("baseuri", getRootRef().toString() + "/TestCase/");
-    return retRoot;
-  }
-
-  public static FileProperties getFileProperties(final String path) {
-    final File f = new File(BASEPATH + path);
-    return new FileProperties(FileUtils.getChecksum(f), path, f.length());
-  }
-}
diff --git a/src/main/java/caosdb/server/resource/TestCaseResource.java b/src/main/java/caosdb/server/resource/TestCaseResource.java
deleted file mode 100644
index e8aef1f00e57335185b7b204c8de081570e6e188..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/resource/TestCaseResource.java
+++ /dev/null
@@ -1,481 +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.resource;
-
-import caosdb.datetime.DateTimeFactory2;
-import caosdb.datetime.UTCDateTime;
-import caosdb.server.CaosDBException;
-import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
-import caosdb.server.datatype.AbstractCollectionDatatype;
-import caosdb.server.datatype.AbstractDatatype;
-import caosdb.server.datatype.BooleanDatatype;
-import caosdb.server.datatype.BooleanValue;
-import caosdb.server.datatype.CollectionValue;
-import caosdb.server.datatype.DateTimeDatatype;
-import caosdb.server.datatype.DoubleDatatype;
-import caosdb.server.datatype.GenericValue;
-import caosdb.server.datatype.IntegerDatatype;
-import caosdb.server.datatype.ListDatatype;
-import caosdb.server.datatype.ReferenceDatatype2;
-import caosdb.server.datatype.ReferenceValue;
-import caosdb.server.datatype.TextDatatype;
-import caosdb.server.datatype.Value;
-import caosdb.server.entity.Entity;
-import caosdb.server.entity.EntityInterface;
-import caosdb.server.entity.MagicTypes;
-import caosdb.server.entity.Message;
-import caosdb.server.entity.Message.MessageType;
-import caosdb.server.entity.RetrieveEntity;
-import caosdb.server.entity.Role;
-import caosdb.server.entity.StatementStatus;
-import caosdb.server.entity.container.PropertyContainer;
-import caosdb.server.entity.wrapper.Parent;
-import caosdb.server.entity.wrapper.Property;
-import caosdb.server.entity.xml.ToElementable;
-import caosdb.server.query.Query;
-import caosdb.server.query.Query.ParsingException;
-import caosdb.server.utils.ServerMessages;
-import caosdb.server.utils.TransactionLogMessage;
-import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
-import java.sql.SQLException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import org.jdom2.Document;
-import org.jdom2.Element;
-import org.restlet.representation.Representation;
-
-public class TestCaseResource extends AbstractCaosDBServerResource {
-
-  @Override
-  protected void doInit() {
-    super.doInit();
-    setXSLScript("webcaosdb.xsl");
-  }
-
-  @Override
-  protected Element generateRootElement() {
-    final Element retRoot = new Element("Response");
-
-    retRoot.setAttribute("username", "TestUser");
-    retRoot.setAttribute("realm", "PAM");
-    retRoot.setAttribute("srid", getSRID());
-    if (this.getCRID() != null) {
-      retRoot.setAttribute("crid", this.getCRID());
-    }
-    retRoot.setAttribute("timestamp", getTimestamp().toString());
-    retRoot.setAttribute("baseuri", getRootRef().toString() + "/TestCase/");
-    return retRoot;
-  }
-
-  @Override
-  protected synchronized Representation httpGetInChildClass()
-      throws ConnectionException, IOException, SQLException, CaosDBException,
-          NoSuchAlgorithmException, Exception {
-
-    final Element rootElem = generateRootElement();
-    final Document doc = new Document();
-
-    final Collection<ToElementable> selectedTestCases = getSelectedTestCase();
-    rootElem.setAttribute("count", Integer.toString(selectedTestCases.size()));
-
-    int from = 0;
-    int len = Integer.MAX_VALUE;
-    if (getFlags().containsKey("query")) {
-      final String queryStr = getFlags().get("query");
-      if (queryStr != null && !queryStr.isEmpty()) {
-        final Query query = new Query(queryStr);
-        try {
-          query.parse();
-          query.addToElement(rootElem);
-        } catch (final ParsingException e) {
-          query.addToElement(rootElem);
-          rootElem.addContent(ServerMessages.QUERY_PARSING_ERROR.toElement());
-          doc.setRootElement(rootElem);
-          return ok(doc);
-        }
-      }
-    }
-    if (getFlags().containsKey("P")) {
-      final String[] split = getFlags().get("P").split("L");
-      from = Integer.valueOf(split[0]);
-      len = Integer.valueOf(split[1]);
-    }
-    int i = 0;
-    for (final ToElementable e : selectedTestCases) {
-      if (i++ >= from && i <= from + len) {
-        e.addToElement(rootElem);
-      }
-    }
-    // testEntities = null;
-    // entityCounter = 1;
-
-    doc.setRootElement(rootElem);
-    return ok(doc);
-  }
-
-  private Collection<ToElementable> getSelectedTestCase() {
-    final HashMap<String, ToElementable> testCases = getTestCase();
-    if (getFlags().containsKey("query")) {
-      final String queryStr = getFlags().get("query");
-      if (queryStr.startsWith("FIND Annotation WHICH REFERENCES")) {
-        final LinkedList<ToElementable> ret = new LinkedList<ToElementable>();
-        for (final ToElementable e : testCases.values()) {
-          if (e instanceof Entity
-              && ((Entity) e).hasParents()
-              && ((Entity) e).getParents().get(0).getName().equals("Annotation")) {
-            ret.add(e);
-          }
-        }
-        return ret;
-      }
-    }
-    if (getFlags().containsKey("all")) {
-      final LinkedList<ToElementable> ret = new LinkedList<ToElementable>();
-      for (final ToElementable e : testCases.values()) {
-        if (e instanceof Entity) {
-          if (getFlags().get("all").equalsIgnoreCase("Entity")) {
-            ret.add(e);
-          } else {
-            final Entity entity = (Entity) e;
-            if (entity.getRole().toString().equalsIgnoreCase(getFlags().get("all"))) {
-              ret.add(e);
-            }
-          }
-        }
-      }
-      return ret;
-    }
-    if (!getRequestedIDs().isEmpty() || !getRequestedNames().isEmpty()) {
-      final LinkedList<ToElementable> ret = new LinkedList<ToElementable>();
-      for (final Integer id : getRequestedIDs()) {
-        for (final ToElementable e : testCases.values()) {
-          if (e instanceof Entity) {
-            if (((Entity) e).getId().equals(id)) {
-              ret.add(e);
-            }
-          }
-        }
-      }
-      for (final String name : getRequestedNames()) {
-        for (final ToElementable e : testCases.values()) {
-          if (e instanceof Entity) {
-            if (((Entity) e).getName().equals(name)) {
-              ret.add(e);
-            }
-          }
-        }
-      }
-      return ret;
-    }
-    return testCases.values();
-  }
-
-  private HashMap<String, ToElementable> getTestCase() {
-    if (testEntities == null) {
-      testEntities = new HashMap<String, ToElementable>();
-
-      final Entity lp1 =
-          createProperty(
-              "LIST"
-                  + AbstractCollectionDatatype.LEFT_DELIMITER
-                  + "TEXT"
-                  + AbstractCollectionDatatype.RIGHT_DELIMITER);
-      final Entity lp2 =
-          createProperty(
-              "LIST"
-                  + AbstractCollectionDatatype.LEFT_DELIMITER
-                  + "INTEGER"
-                  + AbstractCollectionDatatype.RIGHT_DELIMITER,
-              "meter");
-      final Entity lp4 =
-          createProperty(
-              "LIST"
-                  + AbstractCollectionDatatype.LEFT_DELIMITER
-                  + "BOOLEAN"
-                  + AbstractCollectionDatatype.RIGHT_DELIMITER);
-      final Entity tp1 = createProperty("TEXT");
-      final Entity dp1 = createProperty("DOUBLE", "meter");
-      final Entity dtp1 = createProperty("DATETIME");
-      final Entity tp2 = createProperty("TEXT");
-      final Entity ip1 = createProperty("INTEGER", "meter");
-      final Entity bp1 = createProperty("BOOLEAN");
-      final Entity fp1 = createProperty("FILE");
-
-      final Entity rt1 = createRecordType(new Entity[] {}, new Entity[] {});
-      final Entity rt2 = createRecordType(new Entity[] {}, new Entity[] {tp2, ip1, bp1});
-
-      final Entity rp1 = createProperty(rt2);
-      final Entity lp3 =
-          createProperty(
-              "LIST"
-                  + AbstractCollectionDatatype.LEFT_DELIMITER
-                  + rt2.getName()
-                  + AbstractCollectionDatatype.RIGHT_DELIMITER);
-      final Entity rt3 =
-          createRecordType(
-              new Entity[] {rt1}, new Entity[] {lp1, tp1, dp1, dtp1, rp1, lp2, lp3, lp4});
-
-      createRecordFromRecordType(rt2);
-      createRecordFromRecordType(rt3);
-
-      createFileRecord("lorem_ipsum.txt");
-      createFileRecord("testsubfolder/lorem_ipsum.txt");
-      final Entity pngFile = createFileRecord("testimg.png");
-      createFileRecord("testimg.jpg");
-      createFileRecord("testimg.svg");
-      createFileRecord("testimg.gif");
-
-      createRecordWithPngFile(pngFile, fp1, rt1);
-
-      /* Test Error, Warning, Info */
-      createError();
-      createWarning();
-      createInfo();
-
-      /* Entities with Error, Warning, Info */
-      createRecordFromRecordType(rt2)
-          .addError(
-              new Message(
-                  MessageType.Error,
-                  666,
-                  "Description of this error",
-                  "Further information about this error."));
-      createRecordFromRecordType(rt2)
-          .addWarning(
-              new Message(
-                  MessageType.Warning,
-                  346,
-                  "Description of this warning",
-                  "Further information about this warning."));
-      createRecordFromRecordType(rt2)
-          .addInfo(
-              new Message(
-                  MessageType.Info,
-                  0,
-                  "Description of this info",
-                  "Further information about this info."));
-
-      createAnnotation();
-    }
-    return testEntities;
-  }
-
-  private void createAnnotation() {
-    final Entity annotationOf = createEntity(Role.Property, "annotationOf");
-    annotationOf.setDatatype("REFERENCE");
-    final Entity comment = createEntity(Role.Property, "comment");
-    comment.setDatatype("TEXT");
-    final Entity annotation = createEntity(Role.RecordType, "Annotation");
-    annotation.addProperty(new Property(annotationOf));
-    annotation.addProperty(new Property(comment));
-
-    final Entity rec = createRecordFromRecordType(annotation);
-    rec.addTransactionLog(
-        new TransactionLogMessage(
-            "INSERT",
-            rec,
-            "Adam",
-            UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis())));
-  }
-
-  private Entity createRecordWithPngFile(
-      final Entity pngFile, final Entity property, final Entity parent) {
-    final Entity entity = createRecordFromRecordType(parent);
-    final Property newP = new Property(property.toElement());
-    newP.setValue(new ReferenceValue(pngFile));
-    entity.addProperty(newP);
-    return entity;
-  }
-
-  private Entity createFileRecord(final String path) {
-    final Entity entity = createEntity(Role.File, "TestFile");
-    entity.setFileProperties(TestCaseFileSystemResource.getFileProperties(path));
-    return entity;
-  }
-
-  private Entity createProperty(final Entity rt2) {
-    final Entity entity = createProperty("REFERENCE");
-    entity.setDatatype(rt2.getName());
-    return entity;
-  }
-
-  private Value generateValue(final AbstractDatatype dt) {
-    if (dt instanceof IntegerDatatype) {
-      return new GenericValue(1337);
-    } else if (dt instanceof DoubleDatatype) {
-      return new GenericValue(3.14);
-    } else if (dt instanceof DateTimeDatatype) {
-      return DateTimeFactory2.valueOf(new GregorianCalendar());
-    } else if (dt instanceof BooleanDatatype) {
-      return BooleanValue.valueOf(entityCounter % 2 == 0);
-    } else if (dt instanceof TextDatatype) {
-      return new GenericValue("Don't panic.");
-    } else if (dt instanceof ReferenceDatatype2) {
-      final ReferenceDatatype2 dt2 = (ReferenceDatatype2) dt;
-      dt2.getId();
-      for (final ToElementable e : testEntities.values()) {
-        if (e instanceof Entity && ((Entity) e).getRole() == Role.Record) {
-          return new ReferenceValue((Entity) e);
-        }
-      }
-    } else if (dt instanceof ListDatatype) {
-      final AbstractDatatype dt2 = ((ListDatatype) dt).getDatatype();
-      final CollectionValue v = new CollectionValue();
-      for (int i = 0; i < 25; i++) {
-        v.add(generateValue(dt2));
-      }
-      return v;
-    }
-    return null;
-  }
-
-  private Entity createRecordFromRecordType(final Entity rt2) {
-    final Entity entity = createEntity(Role.Record, "TestRecord");
-    addParentsAndProperties(entity, rt2, rt2.getProperties(), "FIX");
-    for (final Property p : entity.getProperties()) {
-      p.setValue(generateValue(p.getDatatype()));
-    }
-    return entity;
-  }
-
-  private void addParentsAndProperties(
-      final Entity entity,
-      final Entity parent,
-      final PropertyContainer properties,
-      final String importance) {
-    final List<Entity> par = new LinkedList<Entity>();
-    par.add(parent);
-    addParentsAndProperties(entity, par, properties, importance);
-  }
-
-  private void addParentsAndProperties(
-      final Entity entity,
-      final Collection<? extends EntityInterface> parents,
-      final Collection<? extends EntityInterface> properties,
-      final String importance) {
-    for (final EntityInterface p : properties) {
-      final Property newP = new Property(p.toElement());
-      if (importance != null) {
-        newP.setStatementStatus(StatementStatus.valueOf(importance));
-      }
-      entity.addProperty(newP);
-    }
-    for (final EntityInterface p : parents) {
-      entity.addParent(new Parent(p.toElement()));
-    }
-  }
-
-  private Message createError() {
-    return createMessage(
-        MessageType.Error.toString(),
-        "This is a description of the error",
-        "Here is additional info about the error.",
-        666);
-  }
-
-  private Message createWarning() {
-    return createMessage(
-        MessageType.Warning.toString(),
-        "This is a description of the warning",
-        "Here is additional info about the warning.",
-        333);
-  }
-
-  private Message createInfo() {
-    return createMessage(
-        MessageType.Info.toString(),
-        "Here is useful information about the last activity of the user.",
-        "Here is additional information.",
-        null);
-  }
-
-  private Message createMessage(
-      String type, final String description, final String body, final Integer code) {
-    final Message m = new Message(type, code, description, body);
-    if (testEntities.containsKey(type)) {
-      int i = 0;
-      while (testEntities.containsKey(type + "-" + i)) {
-        i++;
-      }
-      type += "-" + i;
-    }
-    testEntities.put(type, m);
-    return m;
-  }
-
-  private static HashMap<String, ToElementable> testEntities = null;
-  private static int entityCounter = 1;
-
-  private Entity createProperty(final String datatype) {
-    return createProperty(datatype, null);
-  }
-
-  private Entity createRecordType(final Entity[] parents, final Entity[] properties) {
-    final Entity entity = createEntity(Role.RecordType, "TestRecordType");
-    addParentsAndProperties(entity, parents, properties, "OBLIGATORY");
-    return entity;
-  }
-
-  private void addParentsAndProperties(
-      final Entity entity,
-      final EntityInterface[] parents,
-      final EntityInterface[] properties,
-      final String importance) {
-    addParentsAndProperties(entity, Arrays.asList(parents), Arrays.asList(properties), importance);
-  }
-
-  private Entity createProperty(final String datatype, final String unit) {
-    final Entity entity = createEntity(Role.Property, "Test" + datatype + "Property");
-    entity.setDatatype(datatype);
-    if (unit != null) {
-      final EntityInterface magicUnit = MagicTypes.UNIT.getEntity();
-      final Property unitProp = new Property();
-      unitProp.setId(magicUnit.getId());
-      unitProp.setValue(new GenericValue(unit));
-      entity.addProperty(unitProp);
-    }
-    return entity;
-  }
-
-  private Entity createEntity(final Role role, String name) {
-    final Entity entity = new RetrieveEntity(entityCounter++);
-    entity.setRole(role);
-    entity.setDescription(
-        "An informative description of the idea, meaning, and purpose of this entity.");
-    if (testEntities.containsKey(name)) {
-      int i = 0;
-      while (testEntities.containsKey(name + "-" + i)) {
-        i++;
-      }
-      name += "-" + i;
-    }
-    testEntities.put(name, entity);
-    entity.setName(name);
-    return entity;
-  }
-}
diff --git a/src/main/java/caosdb/server/resource/UserResource.java b/src/main/java/caosdb/server/resource/UserResource.java
index 5cae0da2efa4716293407497fd896bf0b880f233..b77ff8a49035d2be930a3f957e743ed2f0dae067 100644
--- a/src/main/java/caosdb/server/resource/UserResource.java
+++ b/src/main/java/caosdb/server/resource/UserResource.java
@@ -42,7 +42,6 @@ import org.jdom2.Document;
 import org.jdom2.Element;
 import org.jdom2.JDOMException;
 import org.restlet.data.Form;
-import org.restlet.data.MediaType;
 import org.restlet.data.Status;
 import org.restlet.representation.Representation;
 
@@ -84,7 +83,7 @@ public class UserResource extends AbstractCaosDBServerResource {
     }
 
     doc.setRootElement(rootElem);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return ok(doc);
   }
 
   @Override
@@ -125,7 +124,7 @@ public class UserResource extends AbstractCaosDBServerResource {
 
       rootElem.addContent(t.getUserElement());
       doc.setRootElement(rootElem);
-      return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+      return ok(doc);
     } catch (final Message m) {
       if (m == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
         return error(m, Status.CLIENT_ERROR_NOT_FOUND);
@@ -177,7 +176,7 @@ public class UserResource extends AbstractCaosDBServerResource {
 
     rootElem.addContent(t.getUserElement());
     doc.setRootElement(rootElem);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return ok(doc);
   }
 
   @Override
@@ -200,6 +199,6 @@ public class UserResource extends AbstractCaosDBServerResource {
     rootElem.addContent(t.getUserElement());
 
     doc.setRootElement(rootElem);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return ok(doc);
   }
 }
diff --git a/src/main/java/caosdb/server/resource/Webinterface.java b/src/main/java/caosdb/server/resource/Webinterface.java
index 7343fc5ff681a56f1dcba633bd7f3de4ba27f813..f05ae4e6b9a98aba1c925677e0fc8e90a74e57e6 100644
--- a/src/main/java/caosdb/server/resource/Webinterface.java
+++ b/src/main/java/caosdb/server/resource/Webinterface.java
@@ -24,6 +24,7 @@ package caosdb.server.resource;
 
 import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
+import caosdb.server.utils.WebinterfaceUtils;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -42,13 +43,14 @@ import org.restlet.util.Series;
 
 public class Webinterface extends ServerResource {
 
+  private WebinterfaceUtils utils;
+
   @Override
   protected void doInit() throws ResourceException {
+    this.utils = WebinterfaceUtils.getInstance(getHostRef());
     super.doInit();
   }
 
-  private static final File PUBLIC_DIRECTORY = new File("caosdb-webui/public/").getAbsoluteFile();
-
   @Get
   public Representation deliver() throws IOException {
     final String path = (String) getRequest().getAttributes().get("path");
@@ -57,23 +59,15 @@ public class Webinterface extends ServerResource {
       getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
       return null;
     }
-    final File file = new File(this.PUBLIC_DIRECTORY.getAbsolutePath() + "/" + path);
-    // TODO   if (!FileUtils.isSubDirSymlinkSave(file, this.PUBLIC_DIRECTORY.getParentFile())) {
-    //      getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN);
-    //      return null;
-    //    }
-    if (!file.exists()) {
+
+    final File file = utils.getPublicFile(path);
+
+    if (file == null) {
       getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
       return null;
     }
 
-    Series<Header> headers = getRequest().getHeaders();
-    if (headers == null) {
-      headers = new Series<Header>(Header.class);
-      getResponse().getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers);
-    }
-    headers.set("Access-Control-Allow-Origin", getHostRef().toString());
-
+    // TODO move to FileUtils and use a third-party library
     final MediaType mt =
         path.endsWith(".json")
             ? MediaType.APPLICATION_JSON
@@ -91,6 +85,13 @@ public class Webinterface extends ServerResource {
 
     final FileRepresentation ret = new FileRepresentation(file, mt);
 
+    Series<Header> headers = getRequest().getHeaders();
+    if (headers == null) {
+      headers = new Series<Header>(Header.class);
+      getResponse().getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers);
+    }
+    headers.set("Access-Control-Allow-Origin", getHostRef().toString());
+
     List<CacheDirective> cacheDirectives = new ArrayList<>();
     cacheDirectives.add(
         new CacheDirective(
diff --git a/src/main/java/caosdb/server/resource/WebinterfaceBuildNumber.java b/src/main/java/caosdb/server/resource/WebinterfaceBuildNumber.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb0e47f40164a7480a547b74cda33c83d375fb76
--- /dev/null
+++ b/src/main/java/caosdb/server/resource/WebinterfaceBuildNumber.java
@@ -0,0 +1,63 @@
+/**
+ * ** header v3.0 This file is a part of the CaosDB Project.
+ *
+ * <p>Copyright (c) 2019 IndiScale GmbH Copyright (c) 2019 Daniel Hornung <d.hornung@indiscale.com>
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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/>.
+ *
+ * <p>** end header
+ */
+package caosdb.server.resource;
+
+import caosdb.server.utils.WebinterfaceUtils;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+
+/**
+ * This {@link ServerResource} exposes the current build number of the web interface.
+ *
+ * <p>It is mainly used for testing and debugging. A User can determine the current build number of
+ * the web interface.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class WebinterfaceBuildNumber extends ServerResource {
+
+  private WebinterfaceUtils utils;
+
+  @Override
+  protected void doInit() throws ResourceException {
+    super.doInit();
+    this.utils = WebinterfaceUtils.getInstance(getHostRef());
+  }
+
+  /**
+   * Return the current build number of the web interface.
+   *
+   * <p>If the build number could not be determined the server responds with 404 - Not Found.
+   * Reasons for this include an out-dated web interface, the web interface has not been build yet
+   * or the web interface is not available at all.
+   *
+   * @return the current build number.
+   */
+  @Get("text")
+  public String getBuildNumber() {
+    String buildNumber = utils.getBuildNumber();
+
+    if (buildNumber == null) {
+      setStatus(Status.CLIENT_ERROR_NOT_FOUND, "Build number could not be determined.");
+    }
+    return buildNumber;
+  }
+}
diff --git a/src/main/java/caosdb/server/resource/transaction/EntityResource.java b/src/main/java/caosdb/server/resource/transaction/EntityResource.java
index 0e78c46a1f20fd870aca972f5c272c38c78438d0..0c853096c1af71dcacf2ef775c46f4dceecb1673 100644
--- a/src/main/java/caosdb/server/resource/transaction/EntityResource.java
+++ b/src/main/java/caosdb/server/resource/transaction/EntityResource.java
@@ -29,7 +29,6 @@ import caosdb.server.entity.container.InsertContainer;
 import caosdb.server.entity.container.RetrieveContainer;
 import caosdb.server.entity.container.UpdateContainer;
 import caosdb.server.resource.AbstractCaosDBServerResource;
-import caosdb.server.resource.JdomRepresentation;
 import caosdb.server.resource.transaction.handlers.FileUploadHandler;
 import caosdb.server.resource.transaction.handlers.IDHandler;
 import caosdb.server.resource.transaction.handlers.RequestHandler;
@@ -99,6 +98,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,10 +112,14 @@ public class EntityResource extends AbstractCaosDBServerResource {
     final Retrieve retrieve = new Retrieve(entityContainer);
     retrieve.execute();
 
+    final long t2 = System.currentTimeMillis();
+    entityContainer
+        .getTransactionBenchmark()
+        .addMeasurement(getClass().getSimpleName() + ".httpGetInChildClass", t2 - t1);
     final Element rootElem = generateRootElement();
     entityContainer.addToElement(rootElem);
     doc.setRootElement(rootElem);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return ok(doc);
   }
 
   @Override
@@ -138,7 +142,7 @@ public class EntityResource extends AbstractCaosDBServerResource {
     entityContainer.addToElement(rootElem);
     doc.setRootElement(rootElem);
 
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+    return ok(doc);
   }
 
   @Override
@@ -162,7 +166,8 @@ public class EntityResource extends AbstractCaosDBServerResource {
     final Element rootElem = generateRootElement();
     entityContainer.addToElement(rootElem);
     doc.setRootElement(rootElem);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+
+    return ok(doc);
   }
 
   @Override
@@ -186,6 +191,7 @@ public class EntityResource extends AbstractCaosDBServerResource {
     final Element rootElem = generateRootElement();
     entityContainer.addToElement(rootElem);
     doc.setRootElement(rootElem);
-    return new JdomRepresentation(doc, MediaType.TEXT_XML, "  ", getReference(), getXSLScript());
+
+    return ok(doc);
   }
 }
diff --git a/src/main/java/caosdb/server/utils/ChecksumUpdater.java b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java
similarity index 92%
rename from src/main/java/caosdb/server/utils/ChecksumUpdater.java
rename to src/main/java/caosdb/server/transaction/ChecksumUpdater.java
index 6a1e0e927002eff7669d3a6dc7bcae39e761d859..30313478c263a131658005daa39da3b495c85eb2 100644
--- a/src/main/java/caosdb/server/utils/ChecksumUpdater.java
+++ b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java
@@ -20,10 +20,9 @@
  *
  * ** end header
  */
-package caosdb.server.utils;
+package caosdb.server.transaction;
 
 import caosdb.server.CaosDBException;
-import caosdb.server.database.Database;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.GetUpdateableChecksums;
@@ -34,7 +33,7 @@ import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.FileProperties;
 import caosdb.server.entity.Message;
 import caosdb.server.entity.container.TransactionContainer;
-import caosdb.server.transaction.WriteTransaction;
+import caosdb.server.utils.FileUtils;
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 
@@ -86,7 +85,7 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
           DatabaseMonitor.getInstance().acquireStrongAccess(this);
 
           // update
-          Database.execute(new UpdateSparseEntity(fileEntity), strongAccess);
+          execute(new UpdateSparseEntity(fileEntity), strongAccess);
           strongAccess.commit();
 
         } catch (final InterruptedException e) {
@@ -108,7 +107,7 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
       final EntityInterface fileEntity, final Access access, final long lastModified)
       throws NoSuchAlgorithmException, IOException, CaosDBException, Message {
     final EntityInterface checkEntity =
-        Database.execute(new RetrieveSparseEntity(fileEntity), access).getEntity();
+        execute(new RetrieveSparseEntity(fileEntity), access).getEntity();
     final FileProperties thatFP = checkEntity.getFileProperties();
     final FileProperties thisFP = fileEntity.getFileProperties();
 
@@ -136,13 +135,13 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
       synchronized (instance.running) {
 
         // test for updatable checksums
-        final Integer id = Database.execute(new GetUpdateableChecksums(), weakAccess).getID();
+        final Integer id = execute(new GetUpdateableChecksums(), weakAccess).getID();
         if (id == null) {
           // nothing to be updated...
           instance.running = false;
           return null;
         }
-        return Database.execute(new RetrieveSparseEntity(id), weakAccess).getEntity();
+        return execute(new RetrieveSparseEntity(id), weakAccess).getEntity();
       }
     } catch (final Exception e) {
       e.printStackTrace();
diff --git a/src/main/java/caosdb/server/transaction/Delete.java b/src/main/java/caosdb/server/transaction/Delete.java
index f371f0d64e32ddc5a6f4812943b2c9be08b5bcbc..1d3aacbc78043fef6982b2b7663dc5d033e50e50 100644
--- a/src/main/java/caosdb/server/transaction/Delete.java
+++ b/src/main/java/caosdb/server/transaction/Delete.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.transaction;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.DeleteEntity;
 import caosdb.server.database.backend.transaction.RetrieveFullEntity;
@@ -53,7 +52,7 @@ public class Delete extends WriteTransaction<DeleteContainer> {
     setAccess(getMonitor().allocateStrongAccess(this));
 
     // retrieve all entities which are to be deleted.
-    Database.execute(new RetrieveFullEntity(getContainer()), getAccess());
+    execute(new RetrieveFullEntity(getContainer()), getAccess());
 
     for (final EntityInterface e : getContainer()) {
       if (e.getEntityStatus() == EntityStatus.NONEXISTENT) {
@@ -104,10 +103,9 @@ public class Delete extends WriteTransaction<DeleteContainer> {
     delete(getContainer(), getAccess());
   }
 
-  private static void delete(final TransactionContainer container, final Access access)
-      throws Exception {
+  private void delete(final TransactionContainer container, final Access access) throws Exception {
     if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      Database.execute(new DeleteEntity(container), access);
+      execute(new DeleteEntity(container), access);
     }
   }
 
diff --git a/src/main/java/caosdb/server/transaction/DeleteRoleTransaction.java b/src/main/java/caosdb/server/transaction/DeleteRoleTransaction.java
index 3f3758823a2b554edbfb1e400c2a8f2f14916ba1..e1ba3bec80663cb3b30d2dbdc48d9f9024c8f2d7 100644
--- a/src/main/java/caosdb/server/transaction/DeleteRoleTransaction.java
+++ b/src/main/java/caosdb/server/transaction/DeleteRoleTransaction.java
@@ -23,7 +23,6 @@
 package caosdb.server.transaction;
 
 import caosdb.server.accessControl.ACMPermissions;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.DeleteRole;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.database.backend.transaction.SetPermissionRules;
@@ -42,10 +41,10 @@ public class DeleteRoleTransaction extends AccessControlTransaction {
   protected void transaction() throws Exception {
     SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_DELETE_ROLE(this.name));
 
-    if (Database.execute(new RetrieveRole(this.name), getAccess()).getRole() == null) {
+    if (execute(new RetrieveRole(this.name), getAccess()).getRole() == null) {
       throw ServerMessages.ROLE_DOES_NOT_EXIST;
     }
-    Database.execute(new SetPermissionRules(this.name, null), getAccess());
-    Database.execute(new DeleteRole(this.name), getAccess());
+    execute(new SetPermissionRules(this.name, null), getAccess());
+    execute(new DeleteRole(this.name), getAccess());
   }
 }
diff --git a/src/main/java/caosdb/server/transaction/DeleteUserTransaction.java b/src/main/java/caosdb/server/transaction/DeleteUserTransaction.java
index 729512a2a6dfae734b152e2ddd4c1e71362681d5..cdedce31b34153494ceb0be28e4c3a9da360bb6e 100644
--- a/src/main/java/caosdb/server/transaction/DeleteUserTransaction.java
+++ b/src/main/java/caosdb/server/transaction/DeleteUserTransaction.java
@@ -25,7 +25,6 @@ package caosdb.server.transaction;
 import caosdb.server.accessControl.ACMPermissions;
 import caosdb.server.accessControl.CredentialsValidator;
 import caosdb.server.accessControl.UserSources;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.DeletePassword;
 import caosdb.server.database.backend.transaction.DeleteUser;
 import caosdb.server.database.backend.transaction.RetrievePasswordValidator;
@@ -51,14 +50,14 @@ public class DeleteUserTransaction extends AccessControlTransaction {
         .checkPermission(ACMPermissions.PERMISSION_DELETE_USER(this.realm, this.user));
 
     final CredentialsValidator<String> validator =
-        Database.execute(new RetrievePasswordValidator(this.user), getAccess()).getValidator();
+        execute(new RetrievePasswordValidator(this.user), getAccess()).getValidator();
 
     if (validator == null) {
       throw ServerMessages.ACCOUNT_DOES_NOT_EXIST;
     }
 
-    Database.execute(new DeletePassword(this.user), getAccess());
-    Database.execute(new DeleteUser(this.realm, this.user), getAccess());
+    execute(new DeletePassword(this.user), getAccess());
+    execute(new DeleteUser(this.realm, this.user), getAccess());
   }
 
   public Element getUserElement() {
diff --git a/src/main/java/caosdb/server/utils/FileStorageConsistencyCheck.java b/src/main/java/caosdb/server/transaction/FileStorageConsistencyCheck.java
similarity index 84%
rename from src/main/java/caosdb/server/utils/FileStorageConsistencyCheck.java
rename to src/main/java/caosdb/server/transaction/FileStorageConsistencyCheck.java
index c750bb4e6f4de884cf39289d82df79138206f057..26cab30e898d2cd9f163ba3c9773b96c02110cbb 100644
--- a/src/main/java/caosdb/server/utils/FileStorageConsistencyCheck.java
+++ b/src/main/java/caosdb/server/transaction/FileStorageConsistencyCheck.java
@@ -20,10 +20,9 @@
  *
  * ** end header
  */
-package caosdb.server.utils;
+package caosdb.server.transaction;
 
 import caosdb.datetime.UTCDateTime;
-import caosdb.server.database.Database;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.FileConsistencyCheck;
@@ -31,10 +30,11 @@ import caosdb.server.database.backend.transaction.GetFileIterator;
 import caosdb.server.database.backend.transaction.GetFileRecordByPath;
 import caosdb.server.database.backend.transaction.RetrieveAllUncheckedFiles;
 import caosdb.server.database.backend.transaction.SetFileCheckedTimestamp;
+import caosdb.server.database.exceptions.EntityDoesNotExistException;
 import caosdb.server.database.proto.SparseEntity;
 import caosdb.server.entity.Message;
 import caosdb.server.entity.xml.ToElementable;
-import caosdb.server.transaction.TransactionInterface;
+import caosdb.server.utils.SHA512;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -69,7 +69,7 @@ public class FileStorageConsistencyCheck extends Thread
 
       // test all files in file system.
       final Iterator<String> iterator =
-          Database.execute(new GetFileIterator(this.location), this.access).getIterator();
+          execute(new GetFileIterator(this.location), this.access).getIterator();
 
       this.ts = System.currentTimeMillis();
       while (iterator != null && iterator.hasNext()) {
@@ -82,40 +82,43 @@ public class FileStorageConsistencyCheck extends Thread
         }
 
         final String path = iterator.next();
-        // FIXME this prevents all files with ".thumbnail" from being checked.
-        if (path.contains(".thumbnail")) {
+        // this prevents all thumbnails  from being checked.
+        if (path.contains(".thumbnails/")) {
           continue;
         }
 
-        final GetFileRecordByPath t = Database.execute(new GetFileRecordByPath(path), this.access);
-
-        if (t.getEntity() == null) {
+        try {
+          final GetFileRecordByPath t = execute(new GetFileRecordByPath(path), this.access);
+          final int result =
+              execute(
+                      new FileConsistencyCheck(
+                          path,
+                          t.getSize(),
+                          t.getHash(),
+                          t.getLastConsistencyCheck(),
+                          new SHA512()),
+                      this.access)
+                  .getResult();
+
+          if (result != FileConsistencyCheck.OK) {
+            this.results.put(path, result);
+          }
+
+          execute(new SetFileCheckedTimestamp(t.getId(), this.ts), this.access);
+        } catch (EntityDoesNotExistException e) {
           this.results.put(path, FileConsistencyCheck.UNKNOWN_FILE);
           continue;
         }
-        final int result =
-            Database.execute(
-                    new FileConsistencyCheck(
-                        path, t.getSize(), t.getHash(), t.getLastConsistencyCheck(), new SHA512()),
-                    this.access)
-                .getResult();
-
-        if (result != FileConsistencyCheck.OK) {
-          this.results.put(path, result);
-        }
-
-        Database.execute(new SetFileCheckedTimestamp(t.getId(), this.ts), this.access);
       }
 
       // test all remaining file records
       final Iterator<SparseEntity> iterator2 =
-          Database.execute(new RetrieveAllUncheckedFiles(this.ts, this.location), this.access)
-              .getIterator();
+          execute(new RetrieveAllUncheckedFiles(this.ts, this.location), this.access).getIterator();
       while (iterator2 != null && iterator2.hasNext()) {
 
         final SparseEntity entity = iterator2.next();
         final int result =
-            Database.execute(
+            execute(
                     new FileConsistencyCheck(
                         entity.filePath,
                         entity.fileSize,
@@ -129,7 +132,7 @@ public class FileStorageConsistencyCheck extends Thread
           this.results.put(entity.filePath, result);
         }
 
-        Database.execute(new SetFileCheckedTimestamp(entity.id, this.ts), this.access);
+        execute(new SetFileCheckedTimestamp(entity.id, this.ts), this.access);
       }
 
     } catch (final Exception e) {
diff --git a/src/main/java/caosdb/server/transaction/Insert.java b/src/main/java/caosdb/server/transaction/Insert.java
index 0dbda080ce33ea1fbcfdd4f43f36860f44e55db1..627ada52655c5e7802e98172d1c32f8280a95fb4 100644
--- a/src/main/java/caosdb/server/transaction/Insert.java
+++ b/src/main/java/caosdb/server/transaction/Insert.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.transaction;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.InsertEntity;
 import caosdb.server.entity.EntityInterface;
@@ -101,10 +100,9 @@ public class Insert extends WriteTransaction<InsertContainer> {
     insert(getContainer(), getAccess());
   }
 
-  public static void insert(final TransactionContainer container, final Access access)
-      throws Exception {
+  public void insert(final TransactionContainer container, final Access access) throws Exception {
     if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      Database.execute(new InsertEntity(container), access);
+      execute(new InsertEntity(container), access);
     }
   }
 
diff --git a/src/main/java/caosdb/server/transaction/InsertLogRecordTransaction.java b/src/main/java/caosdb/server/transaction/InsertLogRecordTransaction.java
index 89b48418843f326ac42bf49f96b0e6dfa8d0f17a..3762b0bed3f726dd521f754043d0e99a5d2a3913 100644
--- a/src/main/java/caosdb/server/transaction/InsertLogRecordTransaction.java
+++ b/src/main/java/caosdb/server/transaction/InsertLogRecordTransaction.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.transaction;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.InsertLogRecord;
@@ -41,7 +40,7 @@ public class InsertLogRecordTransaction implements TransactionInterface {
   public void execute() throws Exception {
     final Access access = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
     try {
-      Database.execute(new InsertLogRecord(this.toBeFlushed), access);
+      execute(new InsertLogRecord(this.toBeFlushed), access);
     } finally {
       access.release();
     }
diff --git a/src/main/java/caosdb/server/transaction/InsertRoleTransaction.java b/src/main/java/caosdb/server/transaction/InsertRoleTransaction.java
index 250ca37bba0eedaba25d105072617b20fbf94cd5..8377210690692c8819e5197e35a8e549d27f2a30 100644
--- a/src/main/java/caosdb/server/transaction/InsertRoleTransaction.java
+++ b/src/main/java/caosdb/server/transaction/InsertRoleTransaction.java
@@ -24,7 +24,6 @@ package caosdb.server.transaction;
 
 import caosdb.server.accessControl.ACMPermissions;
 import caosdb.server.accessControl.Role;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.InsertRole;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.utils.ServerMessages;
@@ -42,10 +41,10 @@ public class InsertRoleTransaction extends AccessControlTransaction {
   protected void transaction() throws Exception {
     SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_INSERT_ROLE());
 
-    if (Database.execute(new RetrieveRole(this.role.name), getAccess()).getRole() != null) {
+    if (execute(new RetrieveRole(this.role.name), getAccess()).getRole() != null) {
       throw ServerMessages.ROLE_NAME_IS_NOT_UNIQUE;
     }
 
-    Database.execute(new InsertRole(this.role), getAccess());
+    execute(new InsertRole(this.role), getAccess());
   }
 }
diff --git a/src/main/java/caosdb/server/transaction/InsertUserTransaction.java b/src/main/java/caosdb/server/transaction/InsertUserTransaction.java
index d6cc2f46b397cf194a32f6ab5d7ec7e7fdc7407c..75b876b8bc291ea246746b4a8e85ccd26055c173 100644
--- a/src/main/java/caosdb/server/transaction/InsertUserTransaction.java
+++ b/src/main/java/caosdb/server/transaction/InsertUserTransaction.java
@@ -25,7 +25,6 @@ package caosdb.server.transaction;
 import caosdb.server.accessControl.ACMPermissions;
 import caosdb.server.accessControl.UserSources;
 import caosdb.server.accessControl.UserStatus;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrievePasswordValidator;
 import caosdb.server.database.backend.transaction.SetPassword;
 import caosdb.server.database.backend.transaction.UpdateUser;
@@ -67,14 +66,14 @@ public class InsertUserTransaction extends AccessControlTransaction {
       UpdateUserTransaction.checkEntityExists(this.user.entity);
     }
 
-    if (Database.execute(new RetrievePasswordValidator(this.user.name), getAccess()).getValidator()
+    if (execute(new RetrievePasswordValidator(this.user.name), getAccess()).getValidator()
         == null) {
       if (this.password != null) {
         Utils.checkPasswordStrength(this.password);
       }
 
-      Database.execute(new SetPassword(this.user.name, this.password), getAccess());
-      Database.execute(new UpdateUser(this.user), getAccess());
+      execute(new SetPassword(this.user.name, this.password), getAccess());
+      execute(new UpdateUser(this.user), getAccess());
     } else {
       throw ServerMessages.ACCOUNT_NAME_NOT_UNIQUE;
     }
diff --git a/src/main/java/caosdb/server/transaction/Retrieve.java b/src/main/java/caosdb/server/transaction/Retrieve.java
index 3110ee97e68e38a955ad0ae146228a27eb7677d6..0de1b8e06b66a4293668fb499fb28f33fa398187 100644
--- a/src/main/java/caosdb/server/transaction/Retrieve.java
+++ b/src/main/java/caosdb/server/transaction/Retrieve.java
@@ -22,11 +22,9 @@
  */
 package caosdb.server.transaction;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.RetrieveFullEntity;
 import caosdb.server.entity.EntityInterface;
-import caosdb.server.entity.container.Container;
 import caosdb.server.entity.container.RetrieveContainer;
 import caosdb.server.entity.xml.SetFieldStrategy;
 import caosdb.server.entity.xml.ToElementStrategy;
@@ -137,9 +135,9 @@ public class Retrieve extends Transaction<RetrieveContainer> {
     retrieveFullEntities(getContainer(), getAccess());
   }
 
-  private static void retrieveFullEntities(
-      final Container<? extends EntityInterface> container, final Access access) throws Exception {
-    Database.execute(new RetrieveFullEntity(container), access);
+  private void retrieveFullEntities(final RetrieveContainer container, final Access access)
+      throws Exception {
+    execute(new RetrieveFullEntity(container), access);
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/transaction/RetrieveLogRecordTransaction.java b/src/main/java/caosdb/server/transaction/RetrieveLogRecordTransaction.java
index 14b5ea3140b685db77be79ff680180fea6e379b3..bbe2e1f11a7bd4c7705a790dec78903fb0e7c20a 100644
--- a/src/main/java/caosdb/server/transaction/RetrieveLogRecordTransaction.java
+++ b/src/main/java/caosdb/server/transaction/RetrieveLogRecordTransaction.java
@@ -23,7 +23,6 @@
 package caosdb.server.transaction;
 
 import caosdb.server.accessControl.ACMPermissions;
-import caosdb.server.database.Database;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.RetrieveLogRecord;
@@ -64,7 +63,7 @@ public class RetrieveLogRecordTransaction implements TransactionInterface {
     final Access access = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
     try {
       this.logRecords =
-          Database.execute(new RetrieveLogRecord(this.logger, this.level, this.message), access)
+          execute(new RetrieveLogRecord(this.logger, this.level, this.message), access)
               .getLogRecords();
     } finally {
       access.release();
diff --git a/src/main/java/caosdb/server/transaction/RetrievePasswordValidatorTransaction.java b/src/main/java/caosdb/server/transaction/RetrievePasswordValidatorTransaction.java
index 15817a5b405d18f80b23e26ac8b16d3a545483dd..d18b5c306373d6b89aa924bd154db444fbdb8f9a 100644
--- a/src/main/java/caosdb/server/transaction/RetrievePasswordValidatorTransaction.java
+++ b/src/main/java/caosdb/server/transaction/RetrievePasswordValidatorTransaction.java
@@ -23,7 +23,6 @@
 package caosdb.server.transaction;
 
 import caosdb.server.accessControl.CredentialsValidator;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrievePasswordValidator;
 import caosdb.server.utils.ServerMessages;
 
@@ -38,8 +37,7 @@ public class RetrievePasswordValidatorTransaction extends AccessControlTransacti
 
   @Override
   protected void transaction() throws Exception {
-    this.validator =
-        Database.execute(new RetrievePasswordValidator(this.name), getAccess()).getValidator();
+    this.validator = execute(new RetrievePasswordValidator(this.name), getAccess()).getValidator();
     if (this.validator == null) {
       throw ServerMessages.ACCOUNT_DOES_NOT_EXIST;
     }
diff --git a/src/main/java/caosdb/server/transaction/RetrievePermissionRulesTransaction.java b/src/main/java/caosdb/server/transaction/RetrievePermissionRulesTransaction.java
index 187a277bf8724eb343e52b84364b7b10fc781641..d02722fd9e89e70c26e8782252c65fb9e199158c 100644
--- a/src/main/java/caosdb/server/transaction/RetrievePermissionRulesTransaction.java
+++ b/src/main/java/caosdb/server/transaction/RetrievePermissionRulesTransaction.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.transaction;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrievePermissionRules;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.permissions.PermissionRule;
@@ -40,10 +39,10 @@ public class RetrievePermissionRulesTransaction extends AccessControlTransaction
 
   @Override
   protected void transaction() throws Exception {
-    if (Database.execute(new RetrieveRole(this.role), getAccess()).getRole() == null) {
+    if (execute(new RetrieveRole(this.role), getAccess()).getRole() == null) {
       throw ServerMessages.ROLE_DOES_NOT_EXIST;
     }
-    this.rules = Database.execute(new RetrievePermissionRules(this.role), getAccess()).getRules();
+    this.rules = execute(new RetrievePermissionRules(this.role), getAccess()).getRules();
   }
 
   public HashSet<PermissionRule> getRules() {
diff --git a/src/main/java/caosdb/server/transaction/RetrieveRoleTransaction.java b/src/main/java/caosdb/server/transaction/RetrieveRoleTransaction.java
index bcadbac85942a4b1e66e7676c1675647427251ff..4d019f87fb572ce8e75393302764e8146034ee22 100644
--- a/src/main/java/caosdb/server/transaction/RetrieveRoleTransaction.java
+++ b/src/main/java/caosdb/server/transaction/RetrieveRoleTransaction.java
@@ -23,7 +23,6 @@
 package caosdb.server.transaction;
 
 import caosdb.server.accessControl.Role;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.utils.ServerMessages;
 
@@ -38,7 +37,7 @@ public class RetrieveRoleTransaction extends AccessControlTransaction {
 
   @Override
   protected void transaction() throws Exception {
-    this.role = Database.execute(new RetrieveRole(this.name), getAccess()).getRole();
+    this.role = execute(new RetrieveRole(this.name), getAccess()).getRole();
     if (this.role == null) {
       throw ServerMessages.ROLE_DOES_NOT_EXIST;
     }
diff --git a/src/main/java/caosdb/server/transaction/RetrieveSparseEntityByPath.java b/src/main/java/caosdb/server/transaction/RetrieveSparseEntityByPath.java
index 3530d6dad9ec5a134a9613a0a7ece61f53a20edb..2b936247bba9ece2d4bb9ab5cb1a5a1b2cbada74 100644
--- a/src/main/java/caosdb/server/transaction/RetrieveSparseEntityByPath.java
+++ b/src/main/java/caosdb/server/transaction/RetrieveSparseEntityByPath.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.transaction;
 
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.GetFileRecordByPath;
 import caosdb.server.database.backend.transaction.RetrieveSparseEntity;
 import caosdb.server.entity.EntityInterface;
@@ -58,9 +57,9 @@ public class RetrieveSparseEntityByPath extends Transaction<TransactionContainer
   protected void transaction() throws Exception {
     for (final EntityInterface e : getContainer()) {
       final GetFileRecordByPath r =
-          Database.execute(new GetFileRecordByPath(e.getFileProperties().getPath()), getAccess());
+          execute(new GetFileRecordByPath(e.getFileProperties().getPath()), getAccess());
       e.setId(r.getId());
-      Database.execute(new RetrieveSparseEntity(e), getAccess());
+      execute(new RetrieveSparseEntity(e), getAccess());
     }
   }
 
diff --git a/src/main/java/caosdb/server/transaction/RetrieveUserTransaction.java b/src/main/java/caosdb/server/transaction/RetrieveUserTransaction.java
index 93c5eb5c638ff8d4ee36d0c1416bd7f980c10378..f2728a64664ad72f113e55a91ef87496810ad7c6 100644
--- a/src/main/java/caosdb/server/transaction/RetrieveUserTransaction.java
+++ b/src/main/java/caosdb/server/transaction/RetrieveUserTransaction.java
@@ -25,7 +25,6 @@ package caosdb.server.transaction;
 import caosdb.server.accessControl.Principal;
 import caosdb.server.accessControl.UserSources;
 import caosdb.server.accessControl.UserStatus;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveUser;
 import caosdb.server.database.proto.ProtoUser;
 import caosdb.server.utils.ServerMessages;
@@ -46,7 +45,7 @@ public class RetrieveUserTransaction extends AccessControlTransaction {
     if (!UserSources.isUserExisting(this.principal)) {
       throw ServerMessages.ACCOUNT_DOES_NOT_EXIST;
     }
-    this.user = Database.execute(new RetrieveUser(this.principal), getAccess()).getUser();
+    this.user = execute(new RetrieveUser(this.principal), getAccess()).getUser();
 
     if (this.user == null) {
       this.user = new ProtoUser();
diff --git a/src/main/java/caosdb/server/transaction/Transaction.java b/src/main/java/caosdb/server/transaction/Transaction.java
index de1e5e279ee4b1cd252dda0fdf6bb23e8f93e57f..28b8276e2b93699f2315fb754303194208bb37e5 100644
--- a/src/main/java/caosdb/server/transaction/Transaction.java
+++ b/src/main/java/caosdb/server/transaction/Transaction.java
@@ -25,11 +25,11 @@ package caosdb.server.transaction;
 import caosdb.datetime.UTCDateTime;
 import caosdb.server.accessControl.AuthenticationUtils;
 import caosdb.server.accessControl.Principal;
-import caosdb.server.database.Database;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.InsertTransactionHistory;
 import caosdb.server.database.exceptions.TransactionException;
+import caosdb.server.database.misc.TransactionBenchmark;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.Message;
 import caosdb.server.entity.Message.MessageType;
@@ -55,6 +55,11 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
   public static final Message ERROR_INTEGRITY_VIOLATION =
       new Message(MessageType.Error, 0, "This entity caused an unexpected integrity violation.");
 
+  @Override
+  public TransactionBenchmark getTransactionBenchmark() {
+    return getContainer().getTransactionBenchmark();
+  }
+
   private static final DatabaseMonitor monitor = DatabaseMonitor.getInstance();
 
   public static final String CLEAN_UP = "TransactionCleanUp";
@@ -93,7 +98,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     this.schedule.addAll(Job.loadPermanentContainerJobs(this));
 
     for (final EntityInterface e : getContainer()) {
-      final List<Job> loadJobs = Job.loadJobs(e, this);
+      final List<Job> loadJobs = loadContainerFlags.loadJobs(e, this);
       this.schedule.addAll(loadJobs);
 
       // additionally load datatype job
@@ -126,21 +131,22 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       this.schedule.runJobs(JobExecutionTime.INIT);
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(this.getClass().getSimpleName() + ".init", System.currentTimeMillis() - t1);
+          .addMeasurement(
+              this.getClass().getSimpleName() + ".init", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
       preCheck();
       this.schedule.runJobs(JobExecutionTime.PRE_CHECK);
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".pre_check", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
       check();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".check", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
@@ -148,7 +154,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       postCheck();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".post_check", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
@@ -156,7 +162,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       this.schedule.runJobs(JobExecutionTime.PRE_TRANSACTION);
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".pre_transaction",
               System.currentTimeMillis() - t1);
 
@@ -164,7 +170,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       transaction();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".transaction", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
@@ -172,7 +178,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       postTransaction();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".post_transaction",
               System.currentTimeMillis() - t1);
 
@@ -180,18 +186,17 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       writeHistory();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".history", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
       commit();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".commit", System.currentTimeMillis() - t1);
 
     } catch (final Exception e) {
-      e.printStackTrace();
       rollBack();
       throw e;
     } finally {
@@ -199,7 +204,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       cleanUp();
       getContainer()
           .getTransactionBenchmark()
-          .addBenchmark(
+          .addMeasurement(
               this.getClass().getSimpleName() + ".cleanup", System.currentTimeMillis() - t1);
       notifyObservers(CLEAN_UP);
     }
@@ -230,7 +235,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
           getTransactor().getPrincipal() == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()
               ? "anonymous"
               : ((Principal) getTransactor().getPrincipal()).getUsername();
-      Database.execute(
+      execute(
           new InsertTransactionHistory(
               getContainer(), this.getClass().getSimpleName(), realm, username, getTimestamp()),
           getAccess());
diff --git a/src/main/java/caosdb/server/transaction/TransactionInterface.java b/src/main/java/caosdb/server/transaction/TransactionInterface.java
index 2dfb3d4201a864795ca76bcdbe6872fa76ab8e9d..1aaf2786cc613709c59c92b7102474cead00ca1c 100644
--- a/src/main/java/caosdb/server/transaction/TransactionInterface.java
+++ b/src/main/java/caosdb/server/transaction/TransactionInterface.java
@@ -22,7 +22,26 @@
  */
 package caosdb.server.transaction;
 
+import caosdb.server.database.BackendTransaction;
+import caosdb.server.database.access.Access;
+import caosdb.server.database.misc.RollBackHandler;
+import caosdb.server.database.misc.TransactionBenchmark;
+
 public interface TransactionInterface {
 
   public void execute() throws Exception;
+
+  public default TransactionBenchmark getTransactionBenchmark() {
+    return TransactionBenchmark.getRootInstance().getBenchmark(getClass());
+  }
+
+  public default <K extends BackendTransaction> K execute(K t, Access access) {
+    final RollBackHandler handler = (RollBackHandler) access.getHelper("RollBack");
+    handler.append(t);
+    t.setAccess(access);
+
+    t.setTransactionBenchmark(getTransactionBenchmark().getBenchmark(t.getClass()));
+    t.executeTransaction();
+    return t;
+  }
 }
diff --git a/src/main/java/caosdb/server/transaction/Update.java b/src/main/java/caosdb/server/transaction/Update.java
index 8cb14a66716c711656acf6af2dd037110b0f5025..8edd27d0aa0250ffebadc8d9a9639490daf1e2c7 100644
--- a/src/main/java/caosdb/server/transaction/Update.java
+++ b/src/main/java/caosdb/server/transaction/Update.java
@@ -23,7 +23,6 @@
 package caosdb.server.transaction;
 
 import caosdb.server.CaosDBException;
-import caosdb.server.database.Database;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.RetrieveFullEntity;
 import caosdb.server.database.backend.transaction.UpdateEntity;
@@ -82,7 +81,7 @@ public class Update extends WriteTransaction<UpdateContainer> {
 
     // retrieve a container which contains all id of those entities
     // which are to be updated.
-    Database.execute(new RetrieveFullEntity(oldContainer), getAccess());
+    execute(new RetrieveFullEntity(oldContainer), getAccess());
 
     // Check if any updates are to be processed.
     for (final EntityInterface newEntity : getContainer()) {
@@ -169,10 +168,9 @@ public class Update extends WriteTransaction<UpdateContainer> {
     update(getContainer(), getAccess());
   }
 
-  public static void update(final TransactionContainer container, final Access access)
-      throws Exception {
+  private void update(final TransactionContainer container, final Access access) throws Exception {
     if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      Database.execute(new UpdateEntity(container), access);
+      execute(new UpdateEntity(container), access);
     }
   }
 
diff --git a/src/main/java/caosdb/server/transaction/UpdatePermissionRulesTransaction.java b/src/main/java/caosdb/server/transaction/UpdatePermissionRulesTransaction.java
index 450e89e0d900a34964976d668ff5cb0a3e500cf4..e537b001f2af65e35c65e8c8ad88d37a2fd2bc01 100644
--- a/src/main/java/caosdb/server/transaction/UpdatePermissionRulesTransaction.java
+++ b/src/main/java/caosdb/server/transaction/UpdatePermissionRulesTransaction.java
@@ -23,7 +23,6 @@
 package caosdb.server.transaction;
 
 import caosdb.server.accessControl.ACMPermissions;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.database.backend.transaction.SetPermissionRules;
 import caosdb.server.permissions.PermissionRule;
@@ -46,10 +45,10 @@ public class UpdatePermissionRulesTransaction extends AccessControlTransaction {
     SecurityUtils.getSubject()
         .checkPermission(ACMPermissions.PERMISSION_UPDATE_ROLE_PERMISSIONS(this.role));
 
-    if (Database.execute(new RetrieveRole(this.role), getAccess()).getRole() == null) {
+    if (execute(new RetrieveRole(this.role), getAccess()).getRole() == null) {
       throw ServerMessages.ROLE_DOES_NOT_EXIST;
     }
 
-    Database.execute(new SetPermissionRules(this.role, this.rules), getAccess());
+    execute(new SetPermissionRules(this.role, this.rules), getAccess());
   }
 }
diff --git a/src/main/java/caosdb/server/transaction/UpdateRoleTransaction.java b/src/main/java/caosdb/server/transaction/UpdateRoleTransaction.java
index e4ca75051e757dfb227585b11153b2ae5c802a82..dfec776005800469654f144a0c5ae6f0213a04ad 100644
--- a/src/main/java/caosdb/server/transaction/UpdateRoleTransaction.java
+++ b/src/main/java/caosdb/server/transaction/UpdateRoleTransaction.java
@@ -25,7 +25,6 @@ package caosdb.server.transaction;
 import caosdb.server.accessControl.ACMPermissions;
 import caosdb.server.accessControl.Role;
 import caosdb.server.accessControl.UserSources;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.InsertRole;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.utils.ServerMessages;
@@ -48,7 +47,7 @@ public class UpdateRoleTransaction extends AccessControlTransaction {
       throw ServerMessages.ROLE_DOES_NOT_EXIST;
     }
 
-    Database.execute(new InsertRole(this.role), getAccess());
+    execute(new InsertRole(this.role), getAccess());
     RetrieveRole.removeCached(this.role.name);
   }
 }
diff --git a/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java b/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java
index 6b8a93b0c3da1674e1ab9b1589c85b9b0116f1ec..257e8c06cc9d9c9f24774e541cea611663f5ff0d 100644
--- a/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java
+++ b/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java
@@ -25,7 +25,6 @@ package caosdb.server.transaction;
 import caosdb.server.accessControl.ACMPermissions;
 import caosdb.server.accessControl.Principal;
 import caosdb.server.accessControl.UserSources;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveRole;
 import caosdb.server.database.backend.transaction.UpdateUserRoles;
 import caosdb.server.utils.ServerMessages;
@@ -56,7 +55,7 @@ public class UpdateUserRolesTransaction extends AccessControlTransaction {
       SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_ASSIGN_ROLE(role));
 
       // test if role is existent
-      if (Database.execute(new RetrieveRole(role), getAccess()).getRole() == null) {
+      if (execute(new RetrieveRole(role), getAccess()).getRole() == null) {
         throw ServerMessages.ROLE_DOES_NOT_EXIST;
       }
     }
@@ -65,7 +64,7 @@ public class UpdateUserRolesTransaction extends AccessControlTransaction {
       throw ServerMessages.ACCOUNT_DOES_NOT_EXIST;
     }
 
-    Database.execute(new UpdateUserRoles(this.realm, this.user, this.roles), getAccess());
+    execute(new UpdateUserRoles(this.realm, this.user, this.roles), getAccess());
   }
 
   public Element getUserRolesElement() {
diff --git a/src/main/java/caosdb/server/transaction/UpdateUserTransaction.java b/src/main/java/caosdb/server/transaction/UpdateUserTransaction.java
index 663367c21a34c14392bcd0b60113d1e82e1e0539..ca29bb7977da8fc677c774b30a1f3fa78879c5a7 100644
--- a/src/main/java/caosdb/server/transaction/UpdateUserTransaction.java
+++ b/src/main/java/caosdb/server/transaction/UpdateUserTransaction.java
@@ -26,7 +26,6 @@ import caosdb.server.accessControl.ACMPermissions;
 import caosdb.server.accessControl.Principal;
 import caosdb.server.accessControl.UserSources;
 import caosdb.server.accessControl.UserStatus;
-import caosdb.server.database.Database;
 import caosdb.server.database.backend.transaction.RetrieveUser;
 import caosdb.server.database.backend.transaction.SetPassword;
 import caosdb.server.database.backend.transaction.UpdateUser;
@@ -88,23 +87,22 @@ public class UpdateUserTransaction extends AccessControlTransaction {
 
       if (this.password.isEmpty()) {
         // reset password
-        Database.execute(new SetPassword(this.user.name, null), getAccess());
+        execute(new SetPassword(this.user.name, null), getAccess());
       } else {
         Utils.checkPasswordStrength(this.password);
-        Database.execute(new SetPassword(this.user.name, this.password), getAccess());
+        execute(new SetPassword(this.user.name, this.password), getAccess());
       }
     }
 
     if (isToBeUpdated()) {
-      Database.execute(new UpdateUser(this.user), getAccess());
+      execute(new UpdateUser(this.user), getAccess());
     }
   }
 
   private boolean isToBeUpdated() throws Exception {
     boolean isToBeUpdated = false;
     final ProtoUser validUser =
-        Database.execute(
-                new RetrieveUser(new Principal(this.user.realm, this.user.name)), getAccess())
+        execute(new RetrieveUser(new Principal(this.user.realm, this.user.name)), getAccess())
             .getUser();
     if (this.user.status != null && (validUser == null || this.user.status != validUser.status)) {
       SecurityUtils.getSubject()
diff --git a/src/main/java/caosdb/server/transaction/WriteTransaction.java b/src/main/java/caosdb/server/transaction/WriteTransaction.java
index a666e9737a50af039d124178ddf507905e38d7ef..e67ae0600bc573efbdf67b9b341299fe1fe3ca00 100644
--- a/src/main/java/caosdb/server/transaction/WriteTransaction.java
+++ b/src/main/java/caosdb/server/transaction/WriteTransaction.java
@@ -25,7 +25,6 @@ package caosdb.server.transaction;
 import caosdb.server.database.misc.RollBackHandler;
 import caosdb.server.entity.FileProperties;
 import caosdb.server.entity.container.TransactionContainer;
-import caosdb.server.utils.ChecksumUpdater;
 
 public abstract class WriteTransaction<C extends TransactionContainer> extends Transaction<C> {
 
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 e0747548b3ff6bb4675355065567393f2b9c989c..bf85546d8303c072a322afcb9433c77eda928fec 100644
--- a/src/main/java/caosdb/server/utils/Info.java
+++ b/src/main/java/caosdb/server/utils/Info.java
@@ -24,7 +24,6 @@ package caosdb.server.utils;
 
 import caosdb.server.CaosDBServer;
 import caosdb.server.FileSystem;
-import caosdb.server.database.Database;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.GetInfo;
@@ -37,6 +36,8 @@ import java.io.File;
 import java.io.IOException;
 import java.sql.SQLException;
 import java.util.LinkedList;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.jdom2.Element;
 
 public class Info extends AbstractObservable implements Observer, TransactionInterface {
@@ -44,40 +45,39 @@ public class Info extends AbstractObservable implements Observer, TransactionInt
   private static Info instance = new Info();
   public static final String SYNC_DATABASE_EVENT = "SyncDatabaseEvent";
   private final Access access;
+  public Logger logger = LogManager.getLogger(getClass());
 
   @Override
   public boolean notifyObserver(final String e, final Observable o) {
     try {
       syncDatabase();
     } catch (final Exception exc) {
-      exc.printStackTrace();
+      logger.error(exc);
     }
     return true;
   }
 
-  static {
+  private Info() {
+    this.access = DatabaseMonitor.getInfoAccess(this);
     try {
       syncDatabase();
-    } catch (final Exception e) {
-      e.printStackTrace();
+    } catch (final Exception exc) {
+      logger.error(exc);
     }
   }
 
-  private Info() {
-    this.access = DatabaseMonitor.getInfoAccess(this);
-  }
-
   public static Info getInstance() {
     return instance;
   }
 
+  private Long lastSync = 0L;
   private static File dropOffBox = null;
   private static int filesCount = -1;
   private static int propertiesCount = -1;
   private static int recordsCount = -1;
   private static int recordTypesCount = -1;
   private static int tmpFilesCount;
-  private static String fssize;
+  private static String fssize = "";
 
   public static Integer getRecordsCount() throws Exception {
     return recordsCount;
@@ -95,17 +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.
-     */
+  /**
+   * 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>();
@@ -115,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);
         }
@@ -144,12 +145,22 @@ public class Info extends AbstractObservable implements Observer, TransactionInt
     return ret;
   }
 
-  public static void syncDatabase() throws Exception {
-    final Access access = getInstance().access;
+  public long timeSinceSync() {
+    long time = System.currentTimeMillis();
+    return time - lastSync;
+  }
+
+  public void syncDatabase() throws Exception {
+    synchronized (lastSync) {
+      if (timeSinceSync() < 10000) {
+        return;
+      }
+      lastSync = System.currentTimeMillis();
+    }
 
     final ProtoInfo i = new ProtoInfo();
     final GetInfo t = new GetInfo(i);
-    Database.execute(t, access);
+    execute(t, access);
     filesCount = i.filesCount;
     recordsCount = i.recordsCount;
     recordTypesCount = i.recordTypesCount;
@@ -157,7 +168,7 @@ public class Info extends AbstractObservable implements Observer, TransactionInt
     fssize = Utils.getReadableByteSize(i.fssize);
     tmpFilesCount = countTmpFiles();
 
-    getInstance().notifyObservers(SYNC_DATABASE_EVENT);
+    notifyObservers(SYNC_DATABASE_EVENT);
   }
 
   private static int countTmpFiles() throws IOException {
@@ -169,21 +180,26 @@ 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:
-     *            - The number of records
-     *            - The number of properties
-     *            - The number of record types
-     *            - The number of files
-     *            - The total file size
-     *            - The number of temp files
-     *            - The path of the DropOffBox
-     *            - A tree of files in the DropOffBox
-     * TODO: The error format for missing or not readable drop off box has to be specified.
-     */
+  /**
+   * 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 {
+    if (filesCount == -1 && recordsCount == -1) {
+      getInstance().syncDatabase();
+    }
     dropOffBox = new File(FileSystem.getDropOffBox());
     final Element info = new Element("Stats");
     final Element counts = new Element("counts");
@@ -198,31 +214,32 @@ public class Info extends AbstractObservable implements Observer, TransactionInt
     }
     final Element e = new Element("dropOffBox");
     if (dropOffBox.isDirectory()) {
-        if (dropOffBox.isReadable()) {
-            if (tree) {
-                e.setAttribute("path", dropOffBox.getAbsolutePath());
-                e.addContent(getTree(dropOffBox.listFiles()));
-            } else {
-                e.setAttribute("path", dropOffBox.getAbsolutePath());
-                e.addContent(getFlatList(dropOffBox.listFiles()));
-            }
+      if (dropOffBox.canRead()) {
+        if (tree) {
+          e.setAttribute("path", dropOffBox.getAbsolutePath());
+          e.addContent(getTree(dropOffBox.listFiles()));
         } else {
-            // TODO: return a message that the DropOffBox is not readable.
+          e.setAttribute("path", dropOffBox.getAbsolutePath());
+          e.addContent(getFlatList(dropOffBox.listFiles()));
         }
+      } else {
+        // TODO: return a message that the DropOffBox is not readable.
+      }
     } else {
-        // TODO: This function should at least return a message that the DropOffBox is diabled or not present.  
+      // TODO: This function should at least return a message that the DropOffBox is disabled or not
+      // present.
     }
     info.addContent(counts);
     info.addContent(e);
     return info;
   }
 
-  public static void syncDatabase(final ServerStat stat) throws TransactionException, Message {
+  public void syncDatabase(final ServerStat stat) throws TransactionException, Message {
 
     final Access access = getInstance().access;
 
     final SyncStats t = new SyncStats(stat);
-    Database.execute(t, access);
+    execute(t, access);
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/utils/WebinterfaceUtils.java b/src/main/java/caosdb/server/utils/WebinterfaceUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecf0b08630e58662c0abad5c5fe71a0c5b1a8889
--- /dev/null
+++ b/src/main/java/caosdb/server/utils/WebinterfaceUtils.java
@@ -0,0 +1,293 @@
+/**
+ * ** header v3.0 This file is a part of the CaosDB Project.
+ *
+ * <p>Copyright (c) 2019 IndiScale GmbH Copyright (c) 2019 Daniel Hornung <d.hornung@indiscale.com>
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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/>.
+ *
+ * <p>** end header
+ */
+package caosdb.server.utils;
+
+import caosdb.server.CaosDBServer;
+import caosdb.server.ServerProperties;
+import caosdb.server.resource.AbstractCaosDBServerResource;
+import caosdb.server.resource.Webinterface;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.restlet.data.Reference;
+
+/**
+ * This class is responsible for two things:
+ * <li>Resolving relative paths of files of the web interface to {@link File} objects.
+ * <li>Generating URIs to public files of the web interface from relative string paths.
+ *
+ *     <p>Especially the {@link AbstractCaosDBServerResource} and the {@link Webinterface} use this
+ *     class for these tasks.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class WebinterfaceUtils {
+
+  private Logger logger = LogManager.getLogger(this.getClass());
+  public static final String DEFAULT_WEBUI_PUBLIC = "caosdb-webui/public";
+  public static final String DEFAULT_BUILD_NUMBER_FILE = ".build_number";
+  public static final String DEFAULT_ROUTE = "webinterface";
+  private final String host;
+  private final String publicDir;
+  private final String route;
+  private final Path buildNumberFile;
+  private final String contextRoot;
+  private String buildNumber = null;
+  private long buildNumberDate;
+  private static final Map<String, WebinterfaceUtils> instances = new HashMap<>();
+
+  /**
+   * Retrieve an instance of {@link WebinterfaceUtils} for the host. The instance can be shared with
+   * other callers.
+   *
+   * @param host
+   * @return a shared instance of {@link WebinterfaceUtils}.
+   */
+  public static WebinterfaceUtils getInstance(Reference host) {
+    return getInstance(host.getHostIdentifier());
+  }
+
+  /**
+   * Retrieve an instance of {@link WebinterfaceUtils} for the host. The instance can be shared with
+   * other callers.
+   *
+   * @param host
+   * @return a shared instance of {@link WebinterfaceUtils}.
+   */
+  public static WebinterfaceUtils getInstance(String host) {
+    synchronized (instances) {
+      WebinterfaceUtils ret = instances.get(host);
+      if (ret == null) {
+        ret = new WebinterfaceUtils(host);
+        instances.put(host, ret);
+      }
+      return ret;
+    }
+  }
+
+  /**
+   * Create a utility class for creating references to resources of the web interface.
+   *
+   * @param host
+   * @param contextRoot
+   * @param publicDir
+   * @param route
+   * @param buildNumberFile
+   */
+  WebinterfaceUtils(
+      String host, String contextRoot, String publicDir, String route, String buildNumberFile) {
+    this.host = host;
+    this.contextRoot = contextRoot;
+    this.publicDir = publicDir;
+    this.route = route;
+    this.buildNumberFile = getPublicRootPath().resolve(buildNumberFile);
+  }
+
+  /**
+   * Create a utility class for creating references to resources of the web interface.
+   *
+   * <p>The route to the web interface, the build number file and the public folder of the web
+   * interface build directory are set to default values ({@link #DEFAULT_BUILD_NUMBER_FILE}, {@link
+   * #DEFAULT_ROUTE}, and {@link #DEFAULT_WEBUI_PUBLIC}).
+   *
+   * <p>The contextRoute defaults to the server property {@link ServerProperties.KEY_CONTEXT_ROOT}.
+   *
+   * @param host - the host of the web interface.
+   * @see {@link WebinterfaceUtils#WebinterfaceUtils(String, String, String, String)}.
+   */
+  WebinterfaceUtils(Reference host) {
+    this(host.getHostIdentifier());
+  }
+
+  /**
+   * Create a utility class for creating references to resources of the web interface.
+   *
+   * <p>The route to the web interface, the build number file and the public folder of the web
+   * interface build directory are set to default values ({@link #DEFAULT_BUILD_NUMBER_FILE}, {@link
+   * #DEFAULT_ROUTE}, and {@link #DEFAULT_WEBUI_PUBLIC}).
+   *
+   * <p>The contextRoute defaults to the server property {@link ServerProperties.KEY_CONTEXT_ROOT}.
+   *
+   * @param host - the host of the web interface.
+   * @see {@link WebinterfaceUtils#WebinterfaceUtils(String, String, String, String)}.
+   */
+  WebinterfaceUtils(String host) {
+    this(
+        host,
+        CaosDBServer.getServerProperty(ServerProperties.KEY_CONTEXT_ROOT),
+        DEFAULT_WEBUI_PUBLIC,
+        DEFAULT_ROUTE,
+        DEFAULT_BUILD_NUMBER_FILE);
+  }
+
+  /**
+   * Return the server root reference.
+   *
+   * <p>The server root is composed of the scheme (https://) the host name (example.com) the port if
+   * necessary (:10443) and the context root of the application (beta).
+   *
+   * @return the server root reference e.g. https://example.com:10443/beta.
+   */
+  public String getServerRootURI() {
+    if (contextRoot == null || contextRoot.length() == 0) {
+      return host;
+    }
+    return host + "/" + contextRoot;
+  }
+
+  /**
+   * Return the root reference for the web interface.
+   *
+   * <p>The root reference for the web interface is the server root reference ({@link
+   * #getServerRootURI()}) plus the route to the resource which routes to the individual resources
+   * of the web interface ({@link Webinterface}).
+   *
+   * @return root reference for the web interface.
+   */
+  public String getWebinterfaceRootURI() {
+    return getServerRootURI() + "/" + this.route + "/" + getBuildNumber();
+  }
+
+  /**
+   * Return the absolute path of the root of the public directory of the webui.
+   *
+   * @return Public directory of webui.
+   */
+  public Path getPublicRootPath() {
+    return Paths.get(this.publicDir).toAbsolutePath();
+  }
+
+  /**
+   * return the Path of the build number file.
+   *
+   * @return
+   */
+  public Path getBuildNumberFile() {
+    return this.buildNumberFile;
+  }
+
+  /**
+   * Determine the build number of the webui. Returns null if the build number could not be
+   * determined.
+   *
+   * <p>Because opening and reading the {@link #buildNumberFile} content is expensive, the
+   * buildNumber is cached.
+   *
+   * <p>The cached buildNumber invalidates when the files modified time stamp is younger than the
+   * {@link #buildNumberDate}. The validity of the cache is checked once every minute with a {@link
+   * CronJob}.
+   *
+   * @return Build number or null.
+   */
+  public String getBuildNumber() {
+    validateBuildNumber();
+    if (this.buildNumber == null) {
+      this.buildNumberDate = System.currentTimeMillis();
+      this.buildNumber = readBuildNumber();
+    }
+    return this.buildNumber;
+  }
+
+  /**
+   * Return the latest buildNumber, freshly read from the buildNumberfile.
+   *
+   * @return fresh build number.
+   */
+  public String readBuildNumber() {
+    Path buildNumberFile = getBuildNumberFile();
+    StringBuilder contentBuilder = new StringBuilder();
+    try (Stream<String> stream = Files.lines(buildNumberFile, StandardCharsets.UTF_8)) {
+      stream.forEach(s -> contentBuilder.append(s));
+    } catch (IOException e) {
+      logger.error(e);
+      return null;
+    }
+    return contentBuilder.toString();
+  }
+
+  /**
+   * Return the public file or null if the file does not exists or is not in the public directory of
+   * the web interface.
+   *
+   * <p>This method is used to resolve requests for files of the web interface.
+   *
+   * @param path
+   * @return The file or null
+   */
+  public File getPublicFile(String path) {
+    Path publicFilePath = getPublicFilePath(path);
+    if (publicFilePath != null) {
+      File publicFile = publicFilePath.toFile();
+      if (publicFile.exists() && !publicFile.isDirectory() && publicFile.canRead())
+        return publicFile;
+    }
+    return null;
+  }
+
+  /**
+   * Return the absolute path to the public file denoted by the path.
+   *
+   * <p>This method is used to resolve requests for files of the web interface.
+   *
+   * @param path - path to the file of the web interface, relative to the public directory of the
+   *     web interface.
+   * @return The absolute path of the web interface file.
+   */
+  public Path getPublicFilePath(String path) {
+    Path root = getPublicRootPath();
+    Path resolved = root.resolve(path).toAbsolutePath().normalize();
+    if (resolved.startsWith(root)) {
+      return resolved;
+    }
+    return null;
+  }
+
+  /**
+   * Return a URI to a public file of the web interface.
+   *
+   * <p>This method is used to generate references for *.xsl files or other resources of the web
+   * interface.
+   *
+   * @param publicFile - the path of the public file, relative to the public directory of the web
+   *     interface.
+   * @return a URI for the public file of the web interface.
+   */
+  public String getWebinterfaceURI(String publicFile) {
+    return getWebinterfaceRootURI() + "/" + publicFile;
+  }
+
+  /**
+   * Check the validity of the buildNumber and invalidate the buildNumber if the buildNumberDate is
+   * older than the {@link File#lastModified} time stamp of the buildNumberFile.
+   */
+  public void validateBuildNumber() {
+    if (buildNumber != null
+        && getBuildNumberFile().toFile().lastModified() > this.buildNumberDate) {
+      this.buildNumber = null;
+    }
+  }
+}
diff --git a/src/test/docker/Dockerfile b/src/test/docker/Dockerfile
index 9e7bd9a73a3a52e052f4b5953adf1178254f6f03..d8b831eebbda33a3143f89a8da1edb770788bce5 100644
--- a/src/test/docker/Dockerfile
+++ b/src/test/docker/Dockerfile
@@ -1,4 +1,4 @@
 FROM debian:stretch
 RUN apt-get update && \
 	apt-get install git make mariadb-server maven openjdk-8-jdk-headless \
-      python3-pip screen libpam0g-dev unzip curl -y
+      python3-pip screen libpam0g-dev unzip curl shunit2 -y
diff --git a/src/test/java/caosdb/server/Misc.java b/src/test/java/caosdb/server/Misc.java
index e94d91513be40459910a05559a6f526bb94e9872..bcdc1729f008dbb1393fe238a3eb763c67d69e0f 100644
--- a/src/test/java/caosdb/server/Misc.java
+++ b/src/test/java/caosdb/server/Misc.java
@@ -25,7 +25,6 @@ package caosdb.server;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import caosdb.server.database.misc.TransactionBenchmark;
@@ -37,12 +36,10 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.util.Properties;
 import java.util.regex.Matcher;
 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;
@@ -204,26 +201,6 @@ public class Misc {
     assertEquals(null, m.group(2));
   }
 
-  @Test
-  public void testCache() throws IOException {
-    final Properties p = new Properties();
-    p.load(
-        new FileInputStream(
-            System.getProperty("user.dir") + "/src/test/java/caosdb/server/cache.ccf"));
-    JCS.setConfigProperties(p);
-
-    final CacheAccess<Object, Object> cache = JCS.getInstance("default");
-    JCS.getInstance("default");
-
-    final String key = "KEY";
-    final String value = "VALUE";
-
-    cache.put(key, value);
-
-    assertEquals(value, cache.get(key));
-    assertSame(value, cache.get(key));
-  }
-
   @Test
   public void testCrobJob() throws InterruptedException {
     new CronJob(
@@ -294,11 +271,11 @@ public class Misc {
 
   @Test
   public void benchmarkSerialization() throws IOException, ClassNotFoundException {
-    TransactionBenchmark.getInstance().addBenchmark("bla", 1000);
+    TransactionBenchmark.getRootInstance().addMeasurement("bla", 1000);
 
     final FileOutputStream fileOut = new FileOutputStream("/tmp/benchmark.java.ser");
     final ObjectOutputStream out = new ObjectOutputStream(fileOut);
-    out.writeObject(TransactionBenchmark.getInstance());
+    out.writeObject(TransactionBenchmark.getRootInstance());
     out.close();
     fileOut.close();
     System.out.printf("Serialized data is saved in /tmp/benchmark.java.ser");
@@ -316,7 +293,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/cache.ccf b/src/test/java/caosdb/server/cache.ccf
deleted file mode 100644
index d57b1c9f63d44f6436532e686b3609dd33df00d3..0000000000000000000000000000000000000000
--- a/src/test/java/caosdb/server/cache.ccf
+++ /dev/null
@@ -1,4 +0,0 @@
-jcs.default=DC
-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
diff --git a/src/test/java/caosdb/server/caching/TestCaching.java b/src/test/java/caosdb/server/caching/TestCaching.java
new file mode 100644
index 0000000000000000000000000000000000000000..56d8e750ab75663e29808a0f97962df88006d080
--- /dev/null
+++ b/src/test/java/caosdb/server/caching/TestCaching.java
@@ -0,0 +1,52 @@
+package caosdb.server.caching;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import caosdb.server.CaosDBServer;
+import caosdb.server.accessControl.Pam;
+import caosdb.server.database.backend.transaction.RetrieveProperties;
+import java.io.IOException;
+import org.apache.commons.jcs.JCS;
+import org.apache.commons.jcs.access.CacheAccess;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestCaching {
+
+  @BeforeClass
+  public static void init() throws IOException {
+    CaosDBServer.initServerProperties();
+    JCSCacheHelper.init();
+  }
+
+  @Test
+  public void testCacheConfig() {
+    CacheAccess<String, String> retrieve_properties_cache =
+        JCS.getInstance(RetrieveProperties.CACHE_REGION);
+    assertEquals(1003, retrieve_properties_cache.getCacheAttributes().getMaxObjects());
+
+    CacheAccess<String, String> retrieve_entities_cache = JCS.getInstance("BACKEND_SparseEntities");
+    assertEquals(1002, retrieve_entities_cache.getCacheAttributes().getMaxObjects());
+
+    CacheAccess<String, String> pam_groups_cache = JCS.getInstance(Pam.CACHE_REGION_GROUPS);
+    assertEquals(1000, pam_groups_cache.getCacheAttributes().getMaxObjects());
+    assertEquals(false, pam_groups_cache.getCacheAttributes().isUseMemoryShrinker());
+    assertEquals(false, pam_groups_cache.getDefaultElementAttributes().getIsEternal());
+    assertEquals(61, pam_groups_cache.getDefaultElementAttributes().getIdleTime());
+    assertEquals(601, pam_groups_cache.getDefaultElementAttributes().getMaxLife());
+  }
+
+  @Test
+  public void testCacheElements() throws IOException {
+    final CacheAccess<Object, Object> cache = JCS.getInstance("default");
+
+    final String key = "KEY";
+    final String value = "VALUE";
+
+    cache.put(key, value);
+
+    assertEquals(value, cache.get(key));
+    assertSame(value, cache.get(key));
+  }
+}
diff --git a/src/test/java/caosdb/server/jobs/core/TestInsertFilesInDir.java b/src/test/java/caosdb/server/jobs/core/TestInsertFilesInDir.java
new file mode 100644
index 0000000000000000000000000000000000000000..37cad0eca28a50b006985a73edb07e45b60fdf9e
--- /dev/null
+++ b/src/test/java/caosdb/server/jobs/core/TestInsertFilesInDir.java
@@ -0,0 +1,19 @@
+package caosdb.server.jobs.core;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+
+public class TestInsertFilesInDir {
+
+  @Test
+  public void testExclude() throws IOException {
+    InsertFilesInDir job = new InsertFilesInDir();
+    job.init(null, null, null);
+    job.parseValue("-e ^.*test.*$ test");
+    File testFile = new File("test.dat");
+    assertTrue(job.isExcluded(testFile));
+  }
+}
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/query/TestCQL.java b/src/test/java/caosdb/server/query/TestCQL.java
index cae8b26175ebd0d0fceca0753e2bb5629203dbf8..d5a939577b274f4a01355be0a8a8fd1b2a948756 100644
--- a/src/test/java/caosdb/server/query/TestCQL.java
+++ b/src/test/java/caosdb/server/query/TestCQL.java
@@ -230,6 +230,8 @@ public class TestCQL {
   String filepath_pat03 = "/foo\\\\\\\\*/"; // -> \\\\* (4)
   String filepath_pat04 = "/foo**/";
 
+  String referenceByLikePattern = "FIND ENTITY WHICH IS REFERENCED BY *name*";
+
   @Test
   public void testQuery1()
       throws InterruptedException, SQLException, ConnectionException, QueryException {
@@ -6154,4 +6156,26 @@ public class TestCQL {
     assertTrue(StoredAt.requiresPatternMatching(filepath_pat03));
     assertTrue(StoredAt.requiresPatternMatching(filepath_pat04));
   }
+
+  /** String referenceByLikePattern = "FIND ENTITY WHICH IS REFERENCED BY *name*"; */
+  @Test
+  public void testReferencedByLike() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.referenceByLikePattern));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    assertEquals(Query.Role.ENTITY, sfq.r);
+    assertNotNull(sfq.filter);
+    assertEquals(Backreference.class.getName(), sfq.filter.getClass().getName());
+    final EntityFilterInterface f = sfq.filter;
+
+    assertNotNull(f);
+    assertEquals("@(%name%,null)", f.toString());
+
+    assertTrue(f instanceof Backreference);
+    assertEquals("%name%", ((Backreference) f).getEntity());
+  }
 }
diff --git a/src/test/java/caosdb/server/resource/TestScriptingResource.java b/src/test/java/caosdb/server/resource/TestScriptingResource.java
index 3b035fe703409605c59a77df70421e7516230376..2edfb82b53b56324996293a469fadf807264d86c 100644
--- a/src/test/java/caosdb/server/resource/TestScriptingResource.java
+++ b/src/test/java/caosdb/server/resource/TestScriptingResource.java
@@ -23,23 +23,147 @@
 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.misc.TransactionBenchmark;
+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;
+    }
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+  }
+
+  public static class RetrievePermissionRules implements RetrievePermissionRulesImpl {
+
+    public RetrievePermissionRules(Access a) {}
+
+    @Override
+    public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException {
+      return new HashSet<>();
+    }
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+  }
+
+  public static class RetrieveUser implements RetrieveUserImpl {
+
+    public RetrieveUser(Access a) {}
+
+    @Override
+    public ProtoUser execute(Principal principal) throws TransactionException {
+      return new ProtoUser();
+    }
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+  }
+
+  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;
+        }
+      };
+    }
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+  }
+
+  @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 +184,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 +200,24 @@ 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());
+    request.setHostRef("bla");
+    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 e66c646a4e5604f2b7df2dac0af689aa8797160a..f752c29c2f99b33d30a75ff3e09c7043c92fc464 100644
--- a/src/test/java/caosdb/server/scripting/TestServerSideScriptingCaller.java
+++ b/src/test/java/caosdb/server/scripting/TestServerSideScriptingCaller.java
@@ -613,9 +613,7 @@ public class TestServerSideScriptingCaller extends CaosDBTestClass {
     caller.cleanup();
   }
 
-/**
- * Does the order of directory creation matter?
- */
+  /** Does the order of directory creation matter? */
   @Test
   public void testDirectoriesInWrongOrder() throws Message {
     final String[] cmd = {testExecutable.getAbsolutePath()};
@@ -645,9 +643,7 @@ public class TestServerSideScriptingCaller extends CaosDBTestClass {
     caller.cleanup();
   }
 
-  /**
-   * Is the new home directory created correctly?
-   */
+  /** Is the new home directory created correctly? */
   @Test
   public void testWorkingDirCreation() throws Exception {
     final String[] cmd = {testExecutable.getAbsolutePath()};
@@ -660,9 +656,7 @@ public class TestServerSideScriptingCaller extends CaosDBTestClass {
     caller.cleanup();
   }
 
-  /**
-   * Does copying files over to the new home directory work?
-   */
+  /** Does copying files over to the new home directory work? */
   @Test
   public void testWorkingDirCopying() throws Exception {
     final String[] cmd = {testExecutable.getAbsolutePath()};
diff --git a/src/test/java/caosdb/server/utils/FileUtilsTest.java b/src/test/java/caosdb/server/utils/FileUtilsTest.java
index 183f5cf834783f7ad9c6e63bcdb25df1c7870822..e4b2411dc0e77ce0225c0637422c84bbab2cc4aa 100644
--- a/src/test/java/caosdb/server/utils/FileUtilsTest.java
+++ b/src/test/java/caosdb/server/utils/FileUtilsTest.java
@@ -31,7 +31,7 @@ import caosdb.server.CaosDBException;
 import caosdb.server.CaosDBServer;
 import caosdb.server.FileSystem;
 import caosdb.server.ServerProperties;
-import caosdb.server.database.Database;
+import caosdb.server.database.BackendTransaction;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.implementation.UnixFileSystem.UnixFileSystemGetFileIterator.FileNameIterator;
 import caosdb.server.database.backend.implementation.UnixFileSystem.UnixFileSystemHelper;
@@ -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();
   }
 
@@ -548,7 +557,7 @@ public class FileUtilsTest {
     access.setHelper("UnixFileSystemHelper", h);
 
     FileConsistencyCheck check =
-        Database.execute(new FileConsistencyCheck("test1.dat", 0, null, 0L, null), access);
+        execute(new FileConsistencyCheck("test1.dat", 0, null, 0L, null), access);
     assertEquals(FileConsistencyCheck.FILE_DOES_NOT_EXIST, check.getResult());
 
     final File f = new File(rootPath + "test1.dat");
@@ -564,9 +573,7 @@ public class FileUtilsTest {
     final long size = f.length();
     final long timestamp = f.lastModified();
 
-    check =
-        Database.execute(
-            new FileConsistencyCheck("test1.dat", size, hash, timestamp, hasher), access);
+    check = execute(new FileConsistencyCheck("test1.dat", size, hash, timestamp, hasher), access);
     assertEquals(FileConsistencyCheck.OK, check.getResult());
 
     // timestamp has 1 second accuracy
@@ -583,9 +590,7 @@ public class FileUtilsTest {
     assertTrue(timestamp2 > timestamp);
 
     assertTrue(f.exists());
-    check =
-        Database.execute(
-            new FileConsistencyCheck("test1.dat", size2, hash, timestamp, hasher), access);
+    check = execute(new FileConsistencyCheck("test1.dat", size2, hash, timestamp, hasher), access);
     assertEquals(FileConsistencyCheck.OK, check.getResult());
 
     out = new PrintStream(f);
@@ -593,12 +598,16 @@ public class FileUtilsTest {
     out.flush();
     out.close();
 
-    check =
-        Database.execute(
-            new FileConsistencyCheck("test1.dat", size, hash, timestamp, hasher), access);
+    check = execute(new FileConsistencyCheck("test1.dat", size, hash, timestamp, hasher), access);
     assertEquals(FileConsistencyCheck.FILE_MODIFIED, check.getResult());
   }
 
+  private <K extends BackendTransaction> K execute(K t, Access access2) {
+    t.setAccess(access2);
+    t.executeTransaction();
+    return t;
+  }
+
   @Test
   public void testFileIterator() throws IOException {
     final String rootPath = "./testFileIterator/";
@@ -665,8 +674,7 @@ public class FileUtilsTest {
     dat3.deleteOnExit();
     dat3.createNewFile();
 
-    final Iterator<String> iterator =
-        Database.execute(new GetFileIterator(rootPath), access).getIterator();
+    final Iterator<String> iterator = execute(new GetFileIterator(rootPath), access).getIterator();
 
     assertEquals(rootPath + "dat1", iterator.next());
     assertEquals(rootPath + "dat2", iterator.next());
diff --git a/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java b/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ff5d7185d43704e4bc97ef435a5c853e58e3eb8
--- /dev/null
+++ b/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java
@@ -0,0 +1,45 @@
+package caosdb.server.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import caosdb.server.CaosDBServer;
+import java.io.IOException;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.restlet.data.Reference;
+
+public class WebinterfaceUtilsTest {
+
+  @Rule public ExpectedException exceptionRule = ExpectedException.none();
+
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
+  @Test
+  public void testGetWebinterfaceReference() {
+    WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
+    String buildNumber = utils.getBuildNumber();
+    String ref = utils.getWebinterfaceURI("sub");
+    assertEquals("https://host:2345/webinterface/" + buildNumber + "/sub", ref);
+  }
+
+  @Test
+  public void testGetPublicFile() {
+    WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
+    assertNull(utils.getPublicFile("../"));
+  }
+
+  @Test
+  public void testGetPublicFilePath() {
+    WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
+    assertNull(utils.getPublicFilePath("../"));
+    assertNotNull(utils.getPublicFilePath("./"));
+    assertNotNull(utils.getPublicFilePath("bla"));
+  }
+}