diff --git a/.gitignore b/.gitignore
index c30895c42d4a4bae9f1bea8750f63899102b2fa9..2b4a625324a2696c6ff9d8ecf55254451d57ef9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,8 @@ build/
 bin/
 target/
 _apidoc/
+src/doc/development/api/xml/out/
+src/doc/_static/api/
 
 # But include server-side scripting
 !/scripting/bin
@@ -38,3 +40,7 @@ authtoken/
 
 # python
 __pycache__
+*~
+
+# Documentation binaries
+src/doc/development/api/xml/*.jar
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0852bbaa5e63ed6ceae1fc8ddad79db80d9fc16a..d3331a3b24145747836c0898ff852a8ade438bb4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -76,20 +76,21 @@ trigger_build:
 
 # Build the sphinx documentation and make it ready for deployment by Gitlab Pages
 # Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages
-pages:
+pages_prepare: &pages_prepare
   tags: [ cached-dind ]
   stage: deploy
   only:
     refs:
       - /^release-.*$/i
-      - master
-    variables:
-      # run pages only on gitlab.com
-      - $CI_SERVER_HOST == "gitlab.com"
   script:
-    - echo "Deploying"
+    - echo "Deploying..."
     - make doc
     - cp -r build/doc/html public
   artifacts:
     paths:
       - public
+pages:
+  <<: *pages_prepare
+  only:
+    refs:
+      - main
diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md
index a8c5b719ad5f8e18c2fd68d2daa1e5c62f94d450..ac68afc814247e5a64395315e630d5de44a5f306 100644
--- a/.gitlab/merge_request_templates/Default.md
+++ b/.gitlab/merge_request_templates/Default.md
@@ -40,7 +40,7 @@ guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md)
 - [ ] The test environment setup works and the intended behavior is
   reproducible in the test environment
 - [ ] In-code documentation and comments are up-to-date.
-- [ ] Check: Are there spezifications? Are they satisfied?
+- [ ] Check: Are there specifications? Are they satisfied?
 
 For further good practices have a look at [our review guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md).
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7996c53a80d5a7a363baa5bcddbec7e4bf0a6440..07467d9e119418dfa74b83a539ed3572678d0425 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+* An openAPI specification of the XML api
+
+### Changed
+
+### Deprecated
+
+### Removed
+
+### Fixed
+
+* #127 "nan" as value (list item) in properties with data type "LIST<DOUBLE>"
+  return with "Cannot parse value to double" error.
+* #170 Updating an abstract list-type property with a default value fails with
+  "unkown error".
+* #145 Documentation of importances and inheritance
+* Missing sources of the easy-unit dependency.
+
+### Security
+
+## [v0.4.0] - 2021-06-21
+
+### Added
+
+* Related to #146, a new flag for entities and complete transactions:
+  `force-missing-obligatory=[ignore|warn|error]`. The flag overrides the
+  default behavior of the server (throwing an error when an obligatory property
+  is missing). `ignore` just discards the consistency check, `warn` only issues
+  a warning when obligatory properties are missing and `error` throws an error
+  in that case. The flag can be set for the complete transaction and each
+  single entity, while the entity flag takes precedence.
 * New EntityState plug-in. The plug-in disabled by default and can be enabled
   by setting the server property `EXT_ENTITY_STATE=ENABLED`. See
   [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more
@@ -20,18 +50,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   to determine whether the server's state has changed between queries.
 * Basic caching for queries. The caching is enabled by default and can be
   controlled by the usual "cache" flag.
+* Documentation for the overall server structure.
 * Add `BEFORE`, `AFTER`, `UNTIL`, `SINCE` keywords for query transaction
-  filters.
-
 
 ### Changed
 
+* The default session timeout changed from 10 min to 60 min. Please set it to
+  your needs via the server config option `SESSION_TIMEOUT_MS`.
+
 ### Deprecated
 
 ### Removed
 
 ### Fixed
 
+* #146 - Default behavior for missing obligatory properties
 * #131 - CQL Parsing error when white space characters before some units.
 * #134 - CQL Parsing error when multiple white space characters after `FROM`.
 * #130 - Error during `FIND ENTITY` when
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index 3744c7b7d654c5f2379b396c5894f8c054907ee4..3af219c2748c568d29de9044a1e3bdb077335f6e 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -1,4 +1,5 @@
-* caosdb-mysqlbackend == 4.0.0
+* caosdb-mysqlbackend == 4.1.0
 * Java 11
 * Apache Maven >= 3.6.0
 * make >= 4.2.0
+- easy-units >= 0.0.1    https://gitlab.com/timm.fitschen/easy-units
diff --git a/Makefile b/Makefile
index 1543286b67c6c7be20772a3b6932b9e0dcec27d5..7cafdb949cc213d1468383ce7c6a280452ef9904 100644
--- a/Makefile
+++ b/Makefile
@@ -129,7 +129,7 @@ stop-debug-screen:
 
 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
+	mvn deploy:deploy-file -DgroupId=de.timmfitschen -DartifactId=easy-units -Dversion=0.0.1 -Durl=file:./.m2-local/ -DrepositoryId=local-maven-repo -DupdateReleaseInfo=true -Dfile=./lib/easy-units-0.0.1-jar-with-dependencies.jar
 
 # Compile the standalone documentation
 .PHONY: doc
diff --git a/README.md b/README.md
index 21d5400e5383d4c2571f8a409f50cd72f926187a..62da38e86df18a34deddf7c31a651dd12d81b7e8 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,9 @@ when creating the merge request. This allows our team to work with you on your r
 - If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-server/),
 the preferred way is also a merge request as describe above (the documentation resides in `src/doc`).
 However, you can also create an issue for it.
-- You can also contact us at **info (AT) caosdb.de**.
+- You can also contact us at **info (AT) caosdb.de** and join the
+  CaosDB community on
+  [#caosdb:matrix.org](https://matrix.to/#/!unwwlTfOznjEnMMXxf:matrix.org).
 
 ## License
 
diff --git a/README_SETUP.md b/README_SETUP.md
index 9d81763615f0cc36ff4baa0ac55b471be737b9e6..f9ba2a93e82ac6ca5673cada28f6c706c802dd02 100644
--- a/README_SETUP.md
+++ b/README_SETUP.md
@@ -21,6 +21,7 @@ Here, you find information on requirements, the installation, configuration and
 * `unzip`
 * `openpyxl` (for XLS/ODS export)
 * `openssl` (if a custom TLS certificate is required)
+- `easy-units` >= 0.0.1    https://gitlab.com/timm.fitschen/easy-units
 
 #### Install the requirements on Debian
 On Debian, the required packages can be installed with:
@@ -43,7 +44,7 @@ versa.
 - If the WebUI shall run, check out the respective submodule: `git submodule
   update --init caosdb-webui`
 - Then configure and compile it according to its
-  [documentation](https://http://caosdb.gitlab.io/caosdb-webui/getting_started.html).
+  [documentation](https://docs.indiscale.com/caosdb-webui/getting_started.html).
 
 #### PAM ###
 Authentication via PAM is possible, for this the PAM development library must be
@@ -79,7 +80,7 @@ server:
      Replace `localhost` by your host name, if you want.
    - `keytool -importkeystore -srckeystore caosdb.jks -destkeystore caosdb.p12 -deststoretype PKCS12 -srcalias selfsigned`
    - Export the public part only: `openssl pkcs12 -in caosdb.p12 -nokeys -out cert.pem`.
-	 The resulting `cert.pem` can safely be given to users to allow ssl verification.
+     The resulting `cert.pem` can safely be given to users to allow ssl verification.
    - You can check the content of the certificate with `openssl x509 -in cert.pem -text`
 
    Alternatively, you can create a keystore from certificate files that you already have:
@@ -92,14 +93,14 @@ server:
    appropriately:
     * Setup for MySQL back-end:
       specify the fields `MYSQL_USER_NAME`, `MYSQL_USER_PASSWORD`,
-	  `MYSQL_DATABASE_NAME`, and `MYSQL_HOST`.
+      `MYSQL_DATABASE_NAME`, and `MYSQL_HOST`.
     * Choose the ports under which CaosDB will be accessible.
     * Setup the SSL certificate: Assuming that there is an appropriate `Java Key
       Store` file (see above), change the fields `CERTIFICATES_KEY_PASSWORD`,
       `CERTIFICATES_KEY_STORE_PATH`, and `CERTIFICATES_KEY_STORE_PASSWORD`.
       Make sure that the conf file is not readable by other users because the
       certificate passwords are stored in plaintext.
-	- Set the path to the authtoken config (see step 4)
+    - Set the path to the authtoken config (see step 4)
     * Set the file system paths:
       - `FILE_SYSTEM_ROOT`: The root for all the files managed by CaosDB.
       - `DROP_OFF_BOX`: Files can be put here for insertion into CaosDB.
@@ -117,10 +118,10 @@ server:
       - `INSERT_FILES_IN_DIR_ALLOWED_DIRS`: add mounted filesystems here that
         shall be accessible by CaosDB
     * Maybe set another `SESSION_TIMEOUT_MS`.
-    * See also [README_CONFIGURATION.md](README_CONFIGURATION.md)
+    * See also [CONFIGURATION.rst](src/doc/administration/configuration.rst)
 6. Copy `conf/core/usersources.ini.template` to `conf/ext/usersources.ini`.
     * You can skip this if you do not want to use an external authentication. 
-	  Local users (CaosDB realm) are always available.
+      Local users (CaosDB realm) are always available.
     * Define the users/groups who you want to include/exclude.
     * Assign at least one user the `administration` role.
       * For example, if the admin user is called `caosdb`, there should be the
@@ -197,7 +198,11 @@ Stand-alone documentation is built using Sphinx: `make doc`
 
 ### Requirements ##
 
+- plantuml
+- recommonmark
 - sphinx
+- sphinx-rtd-theme
+- sphinxcontrib-plantuml
 - javasphinx :: `pip3 install --user javasphinx`
   - Alternative, if javasphinx fails because python3-sphinx is too recent:
     (`l_` not found):
diff --git a/caosdb-webui b/caosdb-webui
index 5dfe879722bd01acc5209c581b60bf0ac49635b6..c2dc8e9e9e0517ee7fb0d280717211a015906f64 160000
--- a/caosdb-webui
+++ b/caosdb-webui
@@ -1 +1 @@
-Subproject commit 5dfe879722bd01acc5209c581b60bf0ac49635b6
+Subproject commit c2dc8e9e9e0517ee7fb0d280717211a015906f64
diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv
new file mode 100644
index 0000000000000000000000000000000000000000..84795bee8657288473188cd9e7a6af81f6295221
--- /dev/null
+++ b/conf/core/jobs.csv
@@ -0,0 +1,110 @@
+#
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2021 Timm Fitsche <t.fitschen@indiscale.com>
+# Copyright (C) 2021 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/>.
+#
+
+# Five columns:
+# DOMAIN_ID, ENTITY_ID, TRANSACTION, JOB, JOB_FAILURE_SEVERITY
+
+# general rules
+0,0,INSERT,CheckPropValid,ERROR
+0,0,INSERT,CheckParValid,ERROR
+0,0,INSERT,CheckParOblPropPresent,ERROR
+0,0,INSERT,CheckValueParsable,ERROR
+0,0,UPDATE,CheckPropValid,ERROR
+0,0,UPDATE,CheckParValid,ERROR
+0,0,UPDATE,CheckParOblPropPresent,ERROR
+0,0,UPDATE,CheckValueParsable,ERROR
+0,0,DELETE,CheckReferenceDependencyExistent,ERROR
+0,0,DELETE,CheckChildDependencyExistent,ERROR
+
+
+# role specific rules
+## recordtype rules
+0,1,INSERT,CheckNamePresent,ERROR
+0,1,INSERT,CheckPropPresent,WARN
+0,1,INSERT,SetImpToRecByDefault,ERROR
+
+0,1,UPDATE,CheckNamePresent,ERROR
+0,1,UPDATE,CheckPropPresent,WARN
+0,1,UPDATE,SetImpToRecByDefault,ERROR
+
+## record rules
+0,2,INSERT,CheckNamePresent,WARN
+0,2,INSERT,CheckPropPresent,WARN
+0,2,INSERT,CheckParPresent,ERROR
+0,2,INSERT,SetImpToFix,ERROR
+
+0,2,UPDATE,CheckNamePresent,WARN
+0,2,UPDATE,CheckPropPresent,WARN
+0,2,UPDATE,CheckParPresent,ERROR
+0,2,UPDATE,SetImpToFix,ERROR
+
+## file rules
+0,3,INSERT,CheckNamePresent,WARN
+0,3,INSERT,MatchFileProp,ERROR
+0,3,INSERT,CheckTargetPathValid,ERROR
+0,3,INSERT,SetImpToFix,ERROR
+
+0,3,UPDATE,CheckNamePresent,WARN
+0,3,UPDATE,MatchFileProp,ERROR
+0,3,UPDATE,CheckTargetPathValid,ERROR
+0,3,UPDATE,SetImpToFix,ERROR
+
+## property rules
+0,4,INSERT,CheckDatatypePresent,ERROR
+0,4,UPDATE,CheckDatatypePresent,ERROR
+0,4,INSERT,CheckNamePresent,ERROR
+0,4,UPDATE,CheckNamePresent,ERROR
+0,4,INSERT,SetImpToFix,ERROR
+0,4,UPDATE,SetImpToFix,ERROR
+
+## query template rules
+0,8,UPDATE,CheckQueryTemplate,ERROR
+0,8,INSERT,CheckQueryTemplate,ERROR
+
+# data type specific rules
+## reference rules
+0,11,INSERT,CheckRefidPresent,WARN
+0,11,INSERT,CheckRefidValid,ERROR
+0,11,INSERT,CheckRefidIsaParRefid,WARN
+
+0,11,UPDATE,CheckRefidPresent,WARN
+0,11,UPDATE,CheckRefidValid,ERROR
+0,11,UPDATE,CheckRefidIsaParRefid,WARN
+
+## integer rules
+0,12,INSERT,CheckUnitPresent,WARN
+0,12,INSERT,ParseUnit,WARN
+
+0,12,UPDATE,CheckUnitPresent,WARN
+0,12,UPDATE,ParseUnit,WARN
+
+## double rules
+0,13,INSERT,CheckUnitPresent,WARN
+0,13,INSERT,ParseUnit,WARN
+
+0,13,UPDATE,CheckUnitPresent,WARN
+0,13,UPDATE,ParseUnit,WARN
+
+## filereference rules
+0,17,INSERT,CheckRefidValid,ERROR
+0,17,INSERT,CheckRefidIsaParRefid,ERROR
+
+0,17,UPDATE,CheckRefidValid,ERROR
+0,17,UPDATE,CheckRefidIsaParRefid,ERROR
diff --git a/conf/core/server.conf b/conf/core/server.conf
index 76ed6030523f24f977f41a510c703fe075f30042..153f76914618d8c524ea417586cb48481e16bf88 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -68,7 +68,7 @@ MYSQL_USER_NAME=caosdb
 # Password for the user
 MYSQL_USER_PASSWORD=caosdb
 # Schema of mysql procedures and tables which is required by this CaosDB instance
-MYSQL_SCHEMA_VERSION=v4.0.0
+MYSQL_SCHEMA_VERSION=v5.0
 
 
 # --------------------------------------------------
@@ -124,6 +124,9 @@ ONE_TIME_TOKEN_EXPIRES_MS=604800000
 # Path to config file for one time tokens, see authtoken.example.yml.
 AUTHTOKEN_CONFIG=
 
+# Path to config file for the job rules (e.g. which job is loaded for which kind of entity)
+JOB_RULES_CONFIG=conf/core/jobs.csv
+
 # Timeout after which a one-time token expires once it has been first consumed,
 # regardless of the maximum of replays that are allowed for that token. This is
 # only a default value. The actual timeout of tokens can be configured
@@ -190,9 +193,5 @@ GLOBAL_ENTITY_PERMISSIONS_FILE=./conf/core/global_entity_permissions.xml
 ENTITY_VERSIONING_ENABLED=true
 
 
-# --------------------------------------------------
-# Extension settings
-# --------------------------------------------------
-
 # Enabling the state machine extension
 # EXT_STATE_ENTITY=ENABLE
diff --git a/lib/easy-units-0.0.1-SNAPSHOT-jar-with-dependencies.jar b/lib/easy-units-0.0.1-jar-with-dependencies.jar
similarity index 94%
rename from lib/easy-units-0.0.1-SNAPSHOT-jar-with-dependencies.jar
rename to lib/easy-units-0.0.1-jar-with-dependencies.jar
index c9bad485f59d2694cbcb64dee8b5242b2e4b0d72..325862a45f9bb338541e99c24ad1b8421577a61c 100644
Binary files a/lib/easy-units-0.0.1-SNAPSHOT-jar-with-dependencies.jar and b/lib/easy-units-0.0.1-jar-with-dependencies.jar differ
diff --git a/lib/easy-units-0.0.1-javadoc.jar b/lib/easy-units-0.0.1-javadoc.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2785b00e4119fb201fb9a0a373c2dd2ac01ee701
Binary files /dev/null and b/lib/easy-units-0.0.1-javadoc.jar differ
diff --git a/lib/easy-units-0.0.1-sources.jar b/lib/easy-units-0.0.1-sources.jar
new file mode 100644
index 0000000000000000000000000000000000000000..83d50d59bad73ea067475d1cf247c8b31d82fb40
Binary files /dev/null and b/lib/easy-units-0.0.1-sources.jar differ
diff --git a/misc/move_files/move_files.py b/misc/move_files/move_files.py
index 44cc3f4f7e61027b833a7774705577c59e756e72..7498feef96e3a6e2aee465518b34ebd64e4561e4 100755
--- a/misc/move_files/move_files.py
+++ b/misc/move_files/move_files.py
@@ -63,14 +63,20 @@ def rename(changes, chunksize=10):
         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()
+            try:
+                fi = db.File(path=old)
+                fi.retrieve()
+
+                if not fi.is_valid():
+                    continue
+                assert fi.path == old
+                fi.path = new
+                cont.append(fi)
+            except Exception as e:
+                print(e)
+
+        if len(cont) > 0:
+            cont.update(unique=False)
         i += 1
 
 
diff --git a/pom.xml b/pom.xml
index 4c4435934c97633ecadf7bc1ca2d2b6917198bd6..d2616a454bee0031285fa82b51bffdb97c53e4b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.caosdb</groupId>
   <artifactId>caosdb-server</artifactId>
-  <version>0.4.0-SNAPSHOT</version>
+  <version>0.5.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>CaosDB Server</name>
   <properties>
@@ -52,7 +52,7 @@
     <dependency>
       <groupId>de.timmfitschen</groupId>
       <artifactId>easy-units</artifactId>
-      <version>0.0.1-SNAPSHOT</version>
+      <version>0.0.1</version>
     </dependency>
     <dependency>
       <groupId>org.quartz-scheduler</groupId>
@@ -241,7 +241,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>3.0.0-M3</version>
+        <version>3.0.0-M5</version>
         <configuration>
           <includes>
             <include>**/*.java</include>
diff --git a/src/doc/CaosDB-Query-Language.md b/src/doc/CaosDB-Query-Language.md
index 07fbdbc310b8d22de4642f041918710e6e707488..ffa889c6b8acd0cd635049ed2aaceb748d56f2a9 100644
--- a/src/doc/CaosDB-Query-Language.md
+++ b/src/doc/CaosDB-Query-Language.md
@@ -121,7 +121,7 @@ Examples:
 ##### `d1>d2`: Transitive, non-symmetric relation.
 Semantics depend on the flavors of d1 and d2. If both are... 
 ###### [UTCDateTime](Datatype#datetime) 
-* ''True'' iff the time of d1 is after the the time of d2 according to [https://en.wikipedia.org/wiki/Coordinated_Universal_Time](UTC)
+* ''True'' iff the time of d1 is after the the time of d2 according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time).
 * ''False'' otherwise.
 
 ###### [SemiCompleteDateTime](Datatype#datetime)
diff --git a/src/doc/Data-Model.md b/src/doc/Data-Model.md
index 3fa64ec2fdab693552b742572d05b1e3be88d11b..d11a88f67141e366a28ed84895903df7ff062822 100644
--- a/src/doc/Data-Model.md
+++ b/src/doc/Data-Model.md
@@ -1,40 +1,67 @@
 
 # CaosDB Data Model
 
-The data structure is build from some basic building blocks which are shown in the following picture:
+The data structure is built from some basic building blocks which are
+shown in the following picture:
 
 ![entities](entities.png)
 
-It has a base object called **Entity**. Entities are either **Record Types**, **Records**, or **Abstract
-Properties** (and Record like objects for files).
-What *kind of data* is stored is defined by the Record Types. Actual data is then stored in CaosDB as Records which are of some Record Type. Those Records can have Properties that contain information about the Record. The following is a more detailed explanation (also see this [paper](https://www.mdpi.com/2306-5729/4/2/83)).
-
-> Record Types and Abstract Properties are used to define the ontology for a particular
-> domain in which the RDMS is used. Records are used to store the actual data and therefore
-> represent individuals or particular things, e.g. a particular experiment, a particular time series, etc.
-
-**Record Types** define classes or types of things, e.g. persons, experiments, timeseries, etc. Records
-can be viewed as members of the class defined by its Record Type. These classes can contain
-Abstract Properties which define key-value relationships for properties of the things along
-with the expected data type and possibly the default unit, a default value, or a range of permitted
-values. As files on the back-end file system are a major focus of this database management system,
-there is a special entity File that encapsulates typical file properties like path, size and checksum.
-
-**Entities** can be related via binary, directed, transitive is-a relations which model both subtyping
-and instantiation, depending on the relata. These relations construct a directed graph of the
-Entities. If A is-a B we call A the child of B and B the parent of A. No adamant restrictions are
-imposed on the relate of the is-a relation and thus, Entities can be children of multiple Entities.
-
-Each Entity has a list of Entity Properties, or in short just **Properties**. An Entity Property is not an Entity of its own, but a triple of an Abstract Property, a value or Null, and
-an Importance. The values can be numericals, strings, dates, any other valid value that fits into
-one of several builtin data types, or, most notably, references to other Entities. The importance
-is either obligatory, recommended, suggested, or fix. A valid child of an Entity implicitly
-inherits its parent’s Properties according to their Importance, which means that it is obliged,
-recommended or only suggested to have a Property with the same Abstract Property (or
-any subtype thereof).
-
-As opposed to Properties with other priorities, **Fixed Properties** have no effect on the Entity’s children. During the creation or update of Entities, the importances of the parents are
-being checked by the Server. Missing obligatory Properties invalidate the transaction and
-result in an error, by default. Missing Properties, when they are recommended, result in a
-warning, but the transaction is considered valid. Entities with missing suggested Properties
-are silently accepted as valid.
+It has a base object called **Entity**. Entities are either **Record
+Types**, **Records**, or **Abstract Properties** (and Record like
+objects for files). What *kind of data* is stored is defined by the
+Record Types. Actual data is then stored in CaosDB as Records which
+are of some Record Type. Those Records can have Properties that
+contain information about the Record. The following is a more detailed
+explanation (also see this
+[paper](https://www.mdpi.com/2306-5729/4/2/83)).
+
+> Record Types and Abstract Properties are used to define the ontology
+> for a particular domain in which the RDMS is used. Records are used
+> to store the actual data and therefore represent individuals or
+> particular things, e.g. a particular experiment, a particular time
+> series, etc.
+
+## Record Types
+
+**Record Types** define classes or types of things, e.g. persons,
+experiments, timeseries, etc. Records can be viewed as members of the
+class defined by its Record Type. These classes can contain Abstract
+Properties which define key-value relationships for properties of the
+things along with the expected data type and possibly the default
+unit, a default value, or a range of permitted values. As files on the
+backend file system are a major focus of this database management
+system, there is a special File entity that encapsulates typical file
+properties like path, size and checksum.
+
+## Entities
+
+**Entities** can be related via binary, directed, transitive is-a
+relations which model both subtyping and instantiation, depending on
+the relata. These relations construct a directed graph of the
+Entities. If A is-a B we call A the child of B and B the parent of
+A. No adamant restrictions are imposed on the relate of the is-a
+relation and thus, Entities can be children of multiple Entities.
+
+## Properties
+
+Each Entity has a list of Entity Properties, or in short just
+**Properties**. An Entity Property is not an Entity of its own, but a
+triple of an Abstract Property, a value or Null, and an
+Importance. The values can be numerals, strings, dates, any other
+valid value that fits into one of several builtin data types, or, most
+notably, references to other Entities. The Importance is either
+`obligatory`, `recommended`, `suggested`, or `fix`. A valid child of
+an Entity implicitly inherits its parent’s Properties according to
+their Importance, which means that it is obliged, recommended or only
+suggested to have a Property with the same Abstract Property (or any
+subtype thereof). As opposed to Properties with other priorities,
+**Fixed Properties** have no effect on the Entity’s children.
+
+During the creation or update of Entities, the importances of the
+parents are checked by the server. Missing obligatory Properties
+invalidate the transaction and result in an error, by default. Missing
+Properties, when they are recommended, result in a warning, but the
+transaction is considered valid. Entities with missing suggested
+Properties are silently accepted as valid. See [the detailed
+specification of RecordTypes](specification/RecordType) for more
+information on importances and inheritance.
diff --git a/src/doc/Glossary.md b/src/doc/Glossary.md
index 080c2aea4ff393d349553f7d7211d35518ff7c11..39baab24d17fae531dd18d0f4a1fcc2329ec1035 100644
--- a/src/doc/Glossary.md
+++ b/src/doc/Glossary.md
@@ -1,5 +1,9 @@
 # Glossary
 
+## Valid ID
+
+The ID of an existing entity. It is by definition unique among the IDs of all existing entities and is a positive integer."
+
 ## Valid Unique Existing Name
 
 A name of an exiting entity which is unique among the names of all existing entities.
@@ -8,10 +12,6 @@ A name of an exiting entity which is unique among the names of all existing enti
 
 A name of a to-be-inserted/updated entity _e_ which is unique among the names of all other entities that are to be inserted or updated along with the entity _e_.
 
-## Valid ID
-
-The ID of an existing entity. It is by definition unique among the IDs of all existing entities and is a positive integer."
-
 ## Valid Unique Temporary ID
 
 The negative integer ID of a to-be-inserted entity _e_ which is unique among the ids of all other entities that are to be inserted along with the entity _e_. 
diff --git a/src/doc/Makefile b/src/doc/Makefile
index 4f177fd3850423c3005829a75a2b37625dc68a87..1f0af46e75a27dc9ff8d124782fca27400e4a240 100644
--- a/src/doc/Makefile
+++ b/src/doc/Makefile
@@ -1,4 +1,3 @@
-# ** header v3.0
 # This file is a part of the CaosDB Project.
 #
 # Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
@@ -16,8 +15,7 @@
 #
 # 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 Makefile is a wrapper for sphinx scripts.
 #
@@ -28,10 +26,15 @@
 SPHINXOPTS    ?= -a
 SPHINXBUILD   ?= sphinx-build
 SPHINXAPIDOC   ?= javasphinx-apidoc
+
+APIXMLDIR     = development/api/xml/
 SOURCEDIR     = .
 BUILDDIR      = ../../build/doc
 
-.PHONY: doc-help Makefile apidoc
+# Don't make the documentation of the XML api at least until
+# https://github.com/OpenAPITools/openapi-generator/issues/9923 is
+# resolved
+.PHONY: doc-help Makefile apidoc # apixml
 
 # Put it first so that "make" without argument is like "make help".
 doc-help:
@@ -39,9 +42,12 @@ doc-help:
 
 # Catch-all target: route all unknown targets to Sphinx using the new
 # "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile apidoc
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+%: Makefile apidoc # apixml
+	$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 #	sphinx-build -M html . ../../build/doc
 
 apidoc:
-	@$(SPHINXAPIDOC) -o _apidoc --update --title="CaosDB Server" ../main/
+	$(SPHINXAPIDOC) -o _apidoc --update --title="CaosDB Server" ../main/
+
+apixml:
+	$(MAKE) -C $(APIXMLDIR) doc
diff --git a/src/doc/_static/css/server_docu.css b/src/doc/_static/css/server_docu.css
new file mode 100644
index 0000000000000000000000000000000000000000..b3eeaacec8769adffcd8ee6f2b25eb0f350ef4cf
--- /dev/null
+++ b/src/doc/_static/css/server_docu.css
@@ -0,0 +1,9 @@
+table, .wy-table-responsive table {
+    border-collapse: collapse;
+    table-layout: fixed;
+}
+
+table td, .wy-table-responsive table td {
+    word-wrap: break-word;
+    white-space: unset;
+}
diff --git a/src/doc/administration/configuration.rst b/src/doc/administration/configuration.rst
index bbafcf22bd8576e141cb9d7e8388212ccf24d934..c91ac16b1068b9261638aa697f3d11fd42a0d5cc 100644
--- a/src/doc/administration/configuration.rst
+++ b/src/doc/administration/configuration.rst
@@ -1,56 +1,78 @@
-Configuration
-=============
+Server Configuration
+====================
 
-The server is configured through configuration files. There are two directories with config files:
+Main Configuration File
+-----------------------
 
-``conf/core``
-   Upstream defaults are stored here.
-``conf/ext``
-   User specific configuration should be stored here, settings in ``ext`` override settings in
-   ``core``.  Additionally, configuration files may be stored in ``*.d`` directories here, named
-   after the original config file name.  For example, the general server configuration will be
-   assembled from ``conf/core/server.conf``, ``conf/ext/server.conf`` and any ``*.conf`` files found
-   in ``conf/ext/server.conf.d``.
+The server is configured through configuration files. The main file is the
+`server.conf`.
 
-Configuration files
--------------------
+The `server.conf` is a list of key-value pairs. A configuration file may
+contain empty lines, comment lines, and key-value lines.  Comment lines begin
+with a hash (`#`). Key-value lines must have the format `KEY_NAME=VALUE` or
+`KEY_NAME = VALUE`.
 
-In each of these directories, the server looks for the following files:
 
-``server.conf``
-   General server configuration options.  The possible configuration options are documented inside
-   the `default file <https://gitlab.com/caosdb/caosdb-server/-/blob/dev/conf/core/server.conf>`_.
+The server default configuration is located at `./conf/core/server.conf`.
+Upstream defaults are stored here. The possible configuration options are
+documented inside the
+`default file <https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/dev/conf/core/server.conf>`__.
+
+User specific configuration should be in `./conf/ext/` and  override settings
+in `./conf/core/`.
+
+The default configuration can be overriden by
+
+    1. the file ./conf/ext/server.conf
+
+    2. any file in ./conf/ext/server.conf.d/ in (approximately?) alphabetical order
+
+    3. environment variables with the prefix `CAOSDB_CONFIG_`
+
+in this order.
+
+Further Configuration Files
+---------------------------
+
+Further settings are to be set in files which are by default stored in `./conf/core/`:
 
 ``global_entity_permissions.xml``
-   :ref:`Permissions <concepts:Permissions>` which are automatically set, based on user roles.
-   See the `default file <https://gitlab.com/caosdb/caosdb-server/-/blob/dev/conf/core/global_entity_permissions.xml>`_.
+   :doc:`Permissions<../permissions>` which are automatically set, based on user roles.  See the
+   `default file
+   <https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/dev/conf/core/global_entity_permissions.xml>`__.
 
 ``usersources.ini``
    This file defines possible sources which are checked when a user tries to authenticate.  Each
    defined source has a special section, the possible options are defined separately for each user
    source.  At the moment the best place to look for this specific documentation is at the API
    documentation of :java:type:`UserSource` and its implementing classes.  The provided `template
-   file <https://gitlab.com/caosdb/caosdb-server/-/blob/dev/conf/core/usersources.ini.template>`_
+   file
+   <https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/dev/conf/core/usersources.ini.template>`__
    also has some information.  The general concept about authentication realms is described in
    :java:type:`UserSources`.
 
 ``authtoken.yaml``
    Configuration for dispensed authentication tokens, which can be used to authenticate to CaosDB
-   without the need of a user/password combination.  Possible use cases are server-side scripts or
-   initial setup after the server start.  There is more documentation inside the `template file
-   <https://gitlab.com/caosdb/caosdb-server/-/blob/dev/conf/core/authtoken.example.yaml>`_.
+   without the need of a user/password combination. One-time Authentication Tokens can be configure
+   to be issued for special purposes (e.g. a call of a server-side script or initial setup after the server start)
+   or to be written to a file on a regular basis. An example of a configuration is located at `./conf/core/authtoken.example.yaml`.
+   There is more documentation inside the `template file
+   <https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/dev/conf/core/authtoken.example.yaml>`__.
 
 ``cache.ccf``
    Configuration for the Java Caching System (JCS) which can be used by the server.  More
    documentation is `upstream
    <http://commons.apache.org/proper/commons-jcs/getting_started/intro.html>`_ and inside `the file
-   <https://gitlab.com/caosdb/caosdb-server/-/blob/dev/conf/core/cache.ccf>`_.
+   <https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/dev/conf/core/cache.ccf>`_.
 
 ``log4j2-default.properties``, ``log4j2-debug.properties``
    Configuration for logging, following the standard described by the `log4j library
    <https://logging.apache.org/log4j/2.x/>`_.  The ``default`` file is always loaded, in debug mode
    the ``debug`` file iss added as well.
 
+The administrator may set the corresponding options in the main configuration
+file (`./conf/core/server.conf` and it's friends) to replace these special
+files with custom configuration.
 
 Changing the configuration at runtime
 -------------------------------------
diff --git a/src/doc/administration/maintenance.rst b/src/doc/administration/maintenance.rst
index 67d8475bf469957416a6f42e495db07b1533f151..5b9faeb1c5da3e68def79acae2d6901efa2e78de 100644
--- a/src/doc/administration/maintenance.rst
+++ b/src/doc/administration/maintenance.rst
@@ -21,12 +21,17 @@ The command could look like::
     tar czvf /path/to/new/backup /path/to/caosdb/filesystem.tar.gz
 
 
-You can also save the content of CaosDB using XML. This is **not recommended** since it is less reliable than a real SQL backup. However there may be cases in which an XML backup is desirable, e.g., when transferring entities between two different CaosDB instances.
-Collect the entities that you want to export in a :any:`caosdb-pylib:caosdb.common.models.Container`, here 
-named ``cont``. Then you can export the XML with::
+You can also save the content of CaosDB using XML. This is **not recommended** since it produces
+less reproducible results than a plain SQL backup. However there may be cases in which an XML backup
+is necessary, e.g., when transferring entities between two different CaosDB instances.
+
+Collect the entities that you want to export in a
+:any:`Container<caosdb-pylib:caosdb.common.models.Container>`, named ``cont`` here. Then you can
+export the XML with::
 
      from caosadvancedtools.export_related import invert_ids
      from lxml import etree
+
      invert_ids(cont)
      xml = etree.tounicode(cont.to_xml(
          local_serialization=True), pretty_print=True)
@@ -38,11 +43,11 @@ named ``cont``. Then you can export the XML with::
 Restoring a Backup
 ------------------
 
-.. note :
+.. warning::
     CaosDB should be offline before restoring data.
 
 If you want to restore the internal file system, simply replace it. E.g. if your
-Backup is a tarball::
+backup is a tarball::
 
     tar xvf /path/to/caosroot.tar.gz
 
@@ -52,16 +57,17 @@ You find the documentation on how to restore the data in the SQL-Backend  :any:`
 
 If you want to restore the entities exported to XML, you can do::
 
-     cont = db.Container()                                                                                                                                
-     with open("caosdb_data.xml") as fi:                                                                                                                           
-         cont = cont.from_xml(fi.read())                                                                                                                  
+     cont = db.Container()
+     with open("caosdb_data.xml") as fi:
+         cont = cont.from_xml(fi.read())
      cont.insert()
-                                            
+
 User Management
 ---------------
-The configuration of authentication mechanisms is done via the 
-``usersources.ini`` file (see :any:`configuration`).
 
-We recommend the Python tools (:any:`caosdb-pylib:Administration`) for further administrative tasks (e.g. setting
-user passwords).
+The configuration of authentication mechanisms is done via the ``usersources.ini`` file (see
+:any:`configuration`).
+
+We recommend the Python tools (:any:`caosdb-pylib:administration`) for further administrative tasks
+(e.g. setting user passwords).
 
diff --git a/src/doc/administration/server_side_scripting.rst b/src/doc/administration/server_side_scripting.rst
index 1f2b9a1d80e11abed48ae89502ab6f80c099507e..6dc08999f38e1912e72440c609b8cde1ea7ce0bb 100644
--- a/src/doc/administration/server_side_scripting.rst
+++ b/src/doc/administration/server_side_scripting.rst
@@ -31,7 +31,7 @@ The script has to be executable and must be placed somewhere in one of the direc
 
 Users will need the ``SCRIPTING:EXECUTE:path:to:the:script`` permission. Here the path to the script is of course relativet to the ``SERVER_SIDE_SCRIPTING_BIN_DIRS`` where it is located.
 
-For more information see the :doc:`specification of the API <../specs/Server-side-scripting>`
+For more information see the :doc:`specification of the API <../specification/Server-side-scripting>`
 
 Environment
 ------------
diff --git a/src/doc/concepts.rst b/src/doc/concepts.rst
index 0db6186302302767c49735bcdfa81364685573f9..4403c95810f9b2edaa67e8561306b80031ac803e 100644
--- a/src/doc/concepts.rst
+++ b/src/doc/concepts.rst
@@ -7,7 +7,7 @@ Basic concepts of the CaosDB server
    :glob:
 
    Data Model <Data-Model>
-   Permissions
+   permissions
    roles
 
 The CaosDB server provides the HTTP API resources to users and client libraries.  It uses a plain
diff --git a/src/doc/conf.py b/src/doc/conf.py
index 7b6ac359abf8d88d3c60cb37001bc43e00b97872..16e049be56d85a6246ebd58b921dd6a5adcf1938 100644
--- a/src/doc/conf.py
+++ b/src/doc/conf.py
@@ -25,9 +25,9 @@ copyright = '2020, IndiScale GmbH'
 author = 'Daniel Hornung'
 
 # The short X.Y version
-version = '0.3'
+version = '0.5'
 # The full version, including alpha/beta/rc tags
-release = '0.3'
+release = '0.5.0-SNAPSHOT'
 
 
 # -- General configuration ---------------------------------------------------
@@ -47,6 +47,7 @@ extensions = [
     "recommonmark",            # For markdown files.
     "sphinx.ext.autosectionlabel",  # Allow reference sections using its title
     "sphinx_rtd_theme",
+    "sphinxcontrib.plantuml",  # PlantUML diagrams
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -93,7 +94,13 @@ html_theme = 'sphinx_rtd_theme'
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
+html_static_path = ['_static']
+
+# These paths are either relative to html_static_path
+# or fully qualified paths (eg. https://...)
+html_css_files = [
+    'css/server_docu.css',
+]
 
 # Custom sidebar templates, must be a dictionary that maps document names
 # to template names.
@@ -190,14 +197,18 @@ epub_exclude_files = ['search.html']
 #     '<namespace_here>' : ('<base_url_here>', 'javadoc'),
 # }
 
+javadoc_url_map = {
+    'org.restlet': ('https://javadocs.restlet.talend.com/2.4/jse/api', 'javadoc'),
+}
+
 
 # -- Options for intersphinx -------------------------------------------------
 
 # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping
 intersphinx_mapping = {
     "python": ("https://docs.python.org/", None),
-    "caosdb-pylib": ("https://caosdb.gitlab.io/caosdb-pylib/", None),
-    "caosdb-mysqlbackend": ("https://caosdb.gitlab.io/caosdb-mysqlbackend/", None),
+    "caosdb-pylib": ("https://docs.indiscale.com/caosdb-pylib/", None),
+    "caosdb-mysqlbackend": ("https://docs.indiscale.com/caosdb-mysqlbackend/", None),
 }
 
 
diff --git a/src/doc/development/api/xml/Makefile b/src/doc/development/api/xml/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b2314adb191575895a89770332261b505543e0f3
--- /dev/null
+++ b/src/doc/development/api/xml/Makefile
@@ -0,0 +1,35 @@
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2021 Daniel Hornung <d.hornung@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/>.
+
+
+# Build to sphinx' static dir
+BUILDDIR      ?= ../../../_static/api/xml/
+
+
+.PHONY: doc
+doc:
+	mkdir -p $(BUILDDIR)
+	[ -e openapi-generator-cli.jar ] || wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.2.0/openapi-generator-cli-5.2.0.jar -O openapi-generator-cli.jar
+	java -jar openapi-generator-cli.jar generate -i caosdb_openapi.yaml --config openapi_config.json -g html2 -o $(BUILDDIR)
+
+
+
+## Not needed (the same code anyway?)
+
+# [ -e swagger-codegen-cli.jar ] || wget https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.27/swagger-codegen-cli-3.0.27.jar -O swagger-codegen-cli.jar
+# java -jar swagger-codegen-cli.jar generate -i caosdb_openapi.yaml -l html2 -o $(BUILDDIR)
diff --git a/src/doc/development/api/xml/caosdb_openapi.yaml b/src/doc/development/api/xml/caosdb_openapi.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b2e0b7449d0ab9c4bc147692862aa69107828bac
--- /dev/null
+++ b/src/doc/development/api/xml/caosdb_openapi.yaml
@@ -0,0 +1,482 @@
+openapi: "3.0.3"
+info:
+  title: CaosDB XML REST API
+  description: The XML API of CaosDB, see also https://docs.indiscale.com/caosdb-server
+  version: 0.0-poc.1
+servers:
+  - url: https://demo.indiscale.com
+    description: Demo server, will reset its content from time to time.
+paths:
+  /Entity/{id}:
+    get:
+      operationId: getEntity
+      summary: Return an Entity with a given integer id.
+      description: This is the most direct way to return a single Entity.
+      parameters:
+        - name: id
+          in: path
+          required: true
+          description: The internal integer id of the entity to be retrieved.
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: The entity
+          content:
+            application/xml:
+              schema:
+                $ref: "#/components/schemas/Response"
+              example:
+                username: admin
+                realm: PAM
+                srid: "some-srid"
+                timestamp: 12345
+                baseuri: "https://demo.indiscale.com"
+                count: 1
+                UserInfo:
+                  username: admin
+                  realm: PAM
+                  Roles:
+                    - administration
+                entity:
+                  id: 146
+                  name: "John Lennon's guitar"
+                  Permissions:
+                    - name: "RETRIEVE:ENTITY"
+                    - name: "RETRIEVE:ACL"
+                  Parent:
+                    - name: Guitar
+                      id: 110
+                  Version:
+                    id: "some-version-string"
+                    head: true
+                    Predecessor:
+                      id: "some-old-version-string"
+                  Property:
+                    - name: electric
+                      id: 106
+                      datatype: "BOOLEAN"
+                      value: "TRUE"
+                      importance: FIX
+                      Permissions:
+                        - name: "RETRIEVE:ENTITY"
+                        - name: "RETRIEVE:ACL"
+    delete:
+      operationId: deleteEntity
+      summary: Delete the entity with the given id
+      parameters:
+        - name: id
+          in: path
+          required: true
+          description: integer id of the entity to be deleted
+          schema:
+            type: integer
+      responses:
+        '200':
+          description: The entity that was deleted
+          content:
+            application/xml:
+              schema:
+                $ref: "#/components/schemas/Response"
+        
+  /Entity/:
+    get:
+      operationId: executeQuery
+      summary: Return the entities matching the given query
+      description: Use arbitrary CaosDB queries to retrieve all matching entities
+      parameters:
+        - name: query
+          in: query
+          required: true
+          description: The actual query that is to be executed
+          schema:
+            type: string
+        - name: P
+          in: query
+          required: false
+          description: |
+            Start index within query results of the first entity and
+            total number of entities to be shown on one page,
+            separated by an 'L'
+          schema:
+            type: string
+            example: 0L10
+            pattern: "[0-9]+L[0-9]+"
+      responses:
+        '200':
+          description: "The query result"
+          content:
+            application/xml:
+              schema:
+                $ref: "#/components/schemas/Response"
+              example:
+                UserInfo:
+                  Roles:
+                    - anonymous
+                Query:
+                  string: "FIND RECORD"
+                  results: 82
+                  cached: true
+                  etag: "some-etag-hash"
+                entity:
+                  - name: "Result 1"
+                    id: 101
+                  - name: "Result 2"
+                    id: 102
+    post:
+      operationId: insertEntity
+      summary: Insert one or more entities
+      description: Upload one or several entities to be inserted on the server
+      requestBody:
+        required: true
+        description: The entity or the entities to be inserted
+        content:
+          application/xml:
+            schema:
+              $ref: "#/components/schemas/Request"
+            example:
+              entity:
+                - name: "MyNewRecord"
+                  description: "Test"
+                  Parent:
+                    - name: "ParentType"
+                      id: 123
+                  Property:
+                    - name: "SomeProperty"
+                      id: 124
+                      datatype: "INTEGER"
+                      value: 3
+      responses:
+        '200':
+          description: The inserted entity object(s) with information added by the server
+          content:
+            application/xml:
+              schema:
+                $ref: "#/components/schemas/Response"
+              example:
+                username: admin
+                realm: PAM
+                srid: "some-srid"
+                timestamp: 12345
+                baseuri: "https://demo.indiscale.com"
+                count: 1
+                UserInfo:
+                  username: admin
+                  realm: PAM
+                  roles:
+                    - administration
+                entity:
+                  id: 365
+                  name: "MyNewRecord"
+                  description: "Test"
+                  Version:
+                    id: "some-version-string"
+                    head: true
+                  Permissions:
+                    - name: "RETRIEVE:ENTITY"
+                    - name: "RETRIEVE:ACL"
+                  Parent:
+                    - name: "ParentType"
+                      id: 123
+                  Property:
+                    - name: "SomeProperty"
+                      id: 124
+                      datatype: "INTEGER"
+                      value: 3
+    put:
+      operationId: updateEntity
+      summary: Update one or more entities
+      description: Attach one or several entities to be updated on the server
+      requestBody:
+        required: true
+        description: The entity or the entities to be updated
+        content:
+          application/xml:
+            schema:
+              $ref: "#/components/schemas/Request"
+            example:
+              entity:
+                - name: "MyNewRecord"
+                  id: 365
+                  description: "Test"
+                  Parent:
+                    - name: "ParentType"
+                      id: 123
+                  Property:
+                    - name: "SomeProperty"
+                      id: 124
+                      datatype: "INTEGER"
+                      value: 3
+                    - name: "SomeOtherProperty"
+                      id: 125
+                      datatype: "String"
+                      value: "Hello"
+      responses:
+        '200':
+          description: The updated entity object(s) with information added by the server
+          content:
+            application/xml:
+              schema:
+                $ref: "#/components/schemas/Response"
+              example:
+                username: admin
+                realm: PAM
+                srid: "some-srid"
+                timestamp: 12346
+                baseuri: "https://demo.indiscale.com"
+                count: 1
+                UserInfo:
+                  username: admin
+                  realm: PAM
+                  roles:
+                    - administration
+                entity:
+                  id: 365
+                  name: "MyNewRecord"
+                  description: "Test"
+                  Version:
+                    id: "some-new-version-string"
+                    head: true
+                    Predecessor:
+                      id: "some-version-string"
+                  Permissions:
+                    - name: "RETRIEVE:ENTITY"
+                    - name: "RETRIEVE:ACL"
+                  Parent:
+                    - name: "ParentType"
+                      id: 123
+                  Property:
+                    - name: "SomeProperty"
+                      id: 124
+                      datatype: "INTEGER"
+                      value: 3
+                    - name: "SomeOtherProperty"
+                      id: 125
+                      datatype: "String"
+                      value: "Hello"
+components:
+  schemas:
+    Response:
+      type: object
+      description: Server response containing metadata and the requested entity
+      properties:
+        UserInfo:
+          $ref: "#/components/schemas/UserInfo"
+        username:
+          $ref: "#/components/schemas/username"
+        realm:
+          $ref: "#/components/schemas/realm"
+        srid:
+          $ref: "#/components/schemas/srid"
+        timestamp:
+          $ref: "#/components/schemas/timestamp"
+        baseuri:
+          type: string
+          description: URI to which the request was sent
+          xml:
+            attribute: true
+        count:
+          type: integer
+          description: Number of entities contained in this response
+          xml:
+            attribute: true
+        entity:
+          type: array
+          items:
+            $ref: "#/components/schemas/entity"
+        Query:
+          $ref: "#/components/schemas/Query"
+    Request:
+      type: object
+      description: Request to be inserted or updated
+      properties:
+        entity:
+          type: array
+          items:
+            $ref: "#/components/schemas/entity"
+    entity:
+      type: object
+      properties:
+        id:
+          $ref: "#/components/schemas/id"
+        name:
+          $ref: "#/components/schemas/name"
+        description: 
+          type: string
+          example: This is a measurement.
+          xml:
+            attribute: true
+        Permissions:
+          type: array
+          items:
+            $ref: "#/components/schemas/Permission"
+          xml:
+            wrapped: true
+        Property:
+          type: array
+          items:
+            $ref: "#/components/schemas/Property"
+        Parent:
+          type: array
+          items:
+            $ref: "#/components/schemas/Parent"
+        Version:
+          $ref: "#/components/schemas/Version"
+      xml:
+        name: 'xml-entity'
+    UserInfo:
+      type: object
+      description: Information about the user that submitted the request
+      properties:
+        Roles:
+          type: array
+          items:
+            $ref: "#/components/schemas/Role"
+          xml:
+            wrapped: true
+        username:
+          $ref: "#/components/schemas/username"
+        realm:
+          $ref: "#/components/schemas/realm"
+    Role:
+      type: object
+      properties:
+        text:
+          type: string
+          example: "anonymous"
+      xml:
+        name: Role
+    username:
+      type: string
+      example: admin
+      xml:
+        attribute: true
+    realm:
+      type: string
+      example: PAM
+      xml:
+        attribute: true
+    srid:
+      type: string
+      xml:
+        attribute: true
+    timestamp:
+      type: integer
+      xml:
+        attribute: true
+    id:
+      description: The integer id of this entity
+      type: integer
+      xml:
+        attribute: true
+      example: 3
+    name:
+      type: string
+      example: Measurement
+      xml:
+        attribute: true
+    Permission:
+      type: object
+      properties:
+        name:
+          type: string
+          example: "RETRIEVE:ENTITY"
+          xml:
+            attribute: true
+      xml:
+        name: Permission
+    importance:
+      type: string
+      description: Importance of this property
+      xml:
+        attribute: true
+      
+    Property:
+      type: object
+      required:
+        - id
+        - datatype
+        - importance
+      properties:
+        datatype:
+          type: string
+          description: The CaosDB datatype of this property
+          example: BOOLEAN
+          xml:
+            attribute: true
+        id:
+          $ref: "#/components/schemas/id"
+        name:
+          $ref: "#/components/schemas/name"
+        importance:
+          $ref: "#/components/schemas/importance"
+        Value:
+          type: array
+          items:
+            type: string
+        unit:
+          type: string
+          xml:
+            attribute: true
+        Permissions:
+          type: array
+          items:
+            $ref: "#/components/schemas/Permission"
+          xml:
+            wrapped: true
+
+    Parent:
+      type: object
+      properties:
+        id:
+          $ref: "#/components/schemas/id"
+        name:
+          $ref: "#/components/schemas/name"
+
+    Version:
+      type: object
+      properties:
+        id:
+          type: string
+          xml:
+            attribute: true
+        head:
+          type: boolean
+          xml:
+            attribute: true
+        Predecessor:
+          type: object
+          properties:
+            id:
+              type: string
+              xml:
+                attribute: true
+        Successor:
+          type: object
+          properties:
+            id:
+              type: string
+              xml:
+                attribute: true
+
+    Query:
+      type: object
+      properties:
+        string:
+          type: string
+          xml:
+            attribute: true
+        results:
+          type: integer
+          xml:
+            attribute: true
+        cached:
+          type: boolean
+          xml:
+            attribute: true
+        etag:
+          type: string
+          xml:
+            attribute: true
+        # TODO: Add remaining query properties. Also add POV Filter?
+
diff --git a/src/doc/development/api/xml/openapi_config.json b/src/doc/development/api/xml/openapi_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..f27abea7e89a96ba0b39aa0f1ee5f3fdc836c854
--- /dev/null
+++ b/src/doc/development/api/xml/openapi_config.json
@@ -0,0 +1,8 @@
+{
+    "appName": "CaosDB",
+    "appDescription": "CaosDB, the open data management toolkit",
+    "infoUrl": "https://docs.indiscale.com/caosdb-server",
+    "infoEmail": "info@indiscale.com",
+    "licenseInfo": "License: AGPLv3",
+    "licenseUrl": "https://gitlab.com/caosdb/caosdb-server/-/blob/dev/LICENSE.md"
+}
diff --git a/src/doc/development/benchmarking.md b/src/doc/development/benchmarking.md
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0be781453a6f85577dd95f89844fd95f2b4141ba 100644
--- a/src/doc/development/benchmarking.md
+++ b/src/doc/development/benchmarking.md
@@ -0,0 +1,4 @@
+# Benchmarking CaosDB #
+
+Please refer to the file `doc/devel/Benchmarking.md` in the CaosDB sources for developer resources
+how to do benchmarking and profiling of CaosDB.
diff --git a/src/doc/development/devel.rst b/src/doc/development/devel.rst
new file mode 100644
index 0000000000000000000000000000000000000000..141b045594fe6f8a58c86f758734e160e5a8c2fe
--- /dev/null
+++ b/src/doc/development/devel.rst
@@ -0,0 +1,25 @@
+Developing CaosDB
+=================
+
+.. toctree::
+   :glob:
+   :maxdepth: 2
+
+   Structure of the Java code <structure>
+   Benchmarking CaosDB <benchmarking>
+
+CaosDB is an Open-Source project, so anyone may modify the source as they like. These pages aim to
+provide some help for all CaosDB developers.
+
+More generally, these are the most common ways to contribute to CaosDB:
+
+- You found a bug, have a question, or want to request a feature? Please `create an issue
+  <https://gitlab.com/caosdb/caosdb-server/-/issues>`_.
+- You want to contribute code? Please fork the repository and create a merge request in GitLab and
+  choose this repository as target. Make sure to select "Allow commits from members who can merge
+  the target branch" under Contribution when creating the merge request. This allows our team to
+  work with you on your request.
+- If you have a suggestion for this `documentation <https://docs.indiscale.com/caosdb-server/>`_,
+  the preferred way is also a merge request as describe above (the documentation resides in
+  ``src/doc``).  However, you can also create an issue for it.
+- You can also contact the developers at *info (AT) caosdb.de*.
diff --git a/src/doc/development/structure.rst b/src/doc/development/structure.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3848e983fca5d05871212e0985b98d539c04b7fe
--- /dev/null
+++ b/src/doc/development/structure.rst
@@ -0,0 +1,178 @@
+CaosDB's Internal Structure
+===========================
+
+The CaosDB server
+
+- builds upon the `Restlet <https://restlet.talend.com/>`_ framework to provide a REST interface to
+  the network.  See the :ref:`HTTP Resources` section for more information.
+- uses an SQL database (MariaDB or MySQL) as the backend for data storage.  This is documented in
+  the :ref:`MySQL Backend` section.
+- has an internal scheduling framework to organize the required backend jobs.  Read more on this in
+  the :ref:`Transactions and Schedules<transactions>` section.
+- may use a number of authentication providers. Documentation for this is still to come.
+
+.. _HTTP Resources:
+
+HTTP Resources
+--------------
+
+HTTP resources are implemented in the :java:ref:`resource<org.caosdb.server.resource>` package, in
+classes inheriting from :java:ref:`AbstractCaosDBServerResource` (which inherits Restlet's
+:java:ref:`Resource<org.restlet.resource.Resource>` class).  The main :java:ref:`CaosDBServer` class
+defines which HTTP resource (for example ``/Entity/{specifier}``) will be handled by which class
+(:java:ref:`EntityResource` in this case).
+
+Implementing classes need to overwrite for example the ``httpGetInChildClass()`` method (or methods
+corresponding to other HTTP request methods such as POST, PUT, ...).  Typically, they might call the
+``execute()`` method of a :java:ref:`Transaction<org.caosdb.server.transaction.Transaction>` object.
+Transactions are explained in detail in the :ref:`Transactions and Schedules<transactions>` section.
+
+.. uml::
+
+  @startuml
+  abstract AbstractCaosDBServerResource {
+    {abstract} httpGetInChildClass()
+    {abstract} httpPostInChildClass()
+    {abstract} ...InChildClass()
+  }
+  abstract RetrieveEntityResource
+  class EntityResource
+  AbstractCaosDBServerResource <|-- RetrieveEntityResource
+  RetrieveEntityResource <|-- EntityResource
+  @enduml
+
+.. _MySQL Backend:
+
+MySQL Backend
+-------------
+
+The MySQL backend in CaosDB may be substituted by other backends, but at the time of writing this
+documentation, only MySQL (MariaDB is used for testing) is implemented.  There are the following
+main packages which handle the backend:
+
+:java:ref:`backend.interfaces<interfaces>`
+    Interfaces which backends may implement.  The main method for most interfaces is
+    ``execute(...)`` with arguments depending on the specific interface, and benchmarking methods
+    (``getBenchmark()`` and ``setTransactionBenchmark(b)`` may also be required.
+
+:java:ref:`backend.implementation.MySQL<MySQL>`
+    MySQL implementations of the interfaces.  Typical "simple" implementations create a prepared SQL
+    statement from the arguments to ``execute(...)`` and send it to the SQL server.  They may also
+    have methods for undoing and cleanup, using an :java:ref:`UndoHandler`.
+
+:java:ref:`backend.transaction<backend.transaction>` classes
+    Subclasses of the abstract :java:ref:`BackendTransaction` which implement the ``execute()``
+    method.  These classes may use specific backend implementations (like for example the MySQL
+    implementations) to interact with the backend database.
+
+For example, the structure when getting an Entity ID by name looks like this:
+
+.. uml::
+
+  @startuml
+  together {
+    abstract BackendTransaction {
+      HashMap impl // stores all implementations
+      {abstract} execute()
+    }
+    note left of BackendTransaction::impl
+      Stores the
+      implementation
+      for each
+      interface."
+    end note
+    package ...backend.interfaces {
+      interface GetIDByNameImpl {
+        {abstract} execute(String name, String role, String limit)
+      }
+    }
+  }
+  together {
+    package ...backend.transaction {
+      class GetIDByName extends BackendTransaction {
+        execute()
+      }
+    }
+    package ...backend.implementation.MySQL {
+      class MySQLGetIDByName implements GetIDByNameImpl {
+        execute(String name, String role, String limit)
+      }
+    }
+  }
+
+  GetIDByName::execute --r-> MySQLGetIDByName
+  @enduml
+
+.. _transactions:
+
+Transactions and Schedules
+--------------------------
+
+In CaosDB, several client requests may be handled concurrently.  This poses no problem as long as
+only read-only requests are processed, but writing transactions need to block other requests.
+Therefore all transactions (between their first and last access) block write transactions other than
+themselves from writing to the backend, while read transactions may happen at any time, except when
+a write transaction actually writes to the backend.
+
+.. note::
+
+  There is a fine distinction between write transactions on the CaosDB server and actually writing
+  to the backend, since even transactions which need only very short write access to the backend may
+  require extensive read access before, for example to check for permissions or to check if the
+  intended write action makes sense (linked entities must exist, they may need to be of the correct
+  RecordType, etc.).
+
+The request handling in CaosDB is organized in the following way:
+
+- HTTP resources usually create a :java:ref:`Transaction` object and call its
+  :java:ref:`Transaction.execute()` method.  Entities are passed to and from the transaction via
+  :java:ref:`TransactionContainers<TransactionContainer>` (basically normal
+  :java:ref:`Containers<Container>`, enriched with some metadata).
+- The Transaction keeps a :java:ref:`Schedule` of related :java:ref:`Jobs<Job>` (each also wrapping
+  a specific Transaction), which may be called at different stages, called
+  :java:ref:`TransactionStages<TransactionStage>`.
+- The Transaction's ``execute()`` method, when called, in turn calls a number of methods for
+  initialization, checks, preparations, cleanup etc.  Additionally the scheduled jobs are executed
+  at their specified stages, for example all jobs scheduled for ``INIT`` are executed immediately
+  after calling ``Transaction.init()``.  Please consult the API documentation for
+  :java:ref:`Transaction.execute()` for details.
+
+  Most importantly, the (abstract) method ``transaction()`` is called by ``execute()``, which in
+  inheriting classes typically interacts with the backend via :java:ref:`execute(BackendTransaction,
+  Access)<Transaction.execute(K t, Access access)>`, which in turn calls the
+  ``BackendTransaction``'s :java:ref:`BackendTransaction.executeTransaction()` method (just a thin
+  wrapper around its ``execute()`` method).
+
+Summarized, the classes are connected like this:
+
+.. uml::
+
+  @startuml
+  hide empty members
+
+  class Container
+  class TransactionContainer extends Container
+
+  abstract Transaction {
+    Schedule schedule
+    TransactionContainer container
+    execute()
+    execute(BackendTransaction t, Access access)\n    // -> t.executeTransaction(t)
+  }
+
+  class Schedule
+  class ScheduledJob
+  abstract Job {
+    TransactionStage stage
+    Transaction transaction
+    execute(BackendTransaction t)\n    // -> transaction.execute(t, transaction.access)
+  }
+
+  Schedule "*" *- ScheduledJob
+  ScheduledJob *- Job
+  Job o--d- Transaction
+
+  TransactionContainer -* Transaction::container
+  Transaction::schedule *- Schedule
+  @enduml
+
diff --git a/src/doc/index.rst b/src/doc/index.rst
index 870db0251049dfaaff6a6c3a2f2c38b9e95aff97..21fd891c083e7932d8b342d93992b2d02158364e 100644
--- a/src/doc/index.rst
+++ b/src/doc/index.rst
@@ -12,15 +12,17 @@ Welcome to caosdb-server's documentation!
    Concepts <concepts>
    Query Language <CaosDB-Query-Language>
    administration
-   development/*
+   Development <development/devel>
    specification/index.rst
    Glossary
    API documentation<_apidoc/packages>
 
 Welcome to the CaosDB, the flexible semantic data management toolkit!
 
-This documentation helps you to :doc:`get started<getting_started>`, explains the most important
-:doc:`concepts<concepts>` and offers a range of :doc:`tutorials<tutorials>`.
+This documentation helps you to :doc:`get started<README_SETUP>`,
+explains the most important :doc:`concepts<concepts>` and has
+information if you want to :doc:`develop<development/devel>` CaosDB
+yourself.
 
 
 Indices and tables
diff --git a/src/doc/Permissions.rst b/src/doc/permissions.rst
similarity index 96%
rename from src/doc/Permissions.rst
rename to src/doc/permissions.rst
index a5fae0bbfaf8d021691f7f8af93c3db9b5995497..e61ee49a948e905f1800ee5fdb2f0efb6a4ab207 100644
--- a/src/doc/Permissions.rst
+++ b/src/doc/permissions.rst
@@ -1,4 +1,5 @@
-#Permissions
+Permissions
+===========
 
 CaosDB has a fine grained role based permission system. Each interaction
 with the server is governed by the current rules of the user, by default
@@ -59,7 +60,7 @@ Possible actions
 Until it is completely added to this documentation, a detailed
 description of the actions governed by these permissions can be found
 `in the
-sources <https://gitlab.com/caosdb/caosdb-server/blob/dev/src/main/java/caosdb/server/permissions/EntityPermission.java#L119>`__.
+sources <https://gitlab.com/caosdb/caosdb-server/-/blob/dev/src/main/java/org/caosdb/server/permissions/EntityPermission.java#L119>`__.
 
 Typical permissions are:
 
diff --git a/src/doc/roles.md b/src/doc/roles.md
index b70f54506e8adb3ad3caf46af94479af49370409..2d1d36c47c34658a306384e39033f7ea4e5eb040 100644
--- a/src/doc/roles.md
+++ b/src/doc/roles.md
@@ -10,7 +10,7 @@ users may have the same role, and there may be roles without any users.
 
 The user and their roles are always returned by the server in answers to requests
 and can thus be interpreted and used by clients.  The most important use though
-is [permission](permissions) checking in the server: Access and
+is [permission](doc:`permissions`) checking in the server: Access and
 modification of
 entities can be controlled via roles, so that users of a given role are allowed
 or denied certain actions.  Incidentally, the permission to edit the permissions
@@ -32,4 +32,4 @@ There are some special roles, which are automatically assigned to users:
 
 Except for the `anonymous` role, these special roles are not returned by the
 server, but can nevertheless be used to define
-[permissions](permissions.rst).
+[permissions](doc:`permissions`).
diff --git a/src/doc/specification/AbstractProperty.md b/src/doc/specification/AbstractProperty.md
index d594f81967cacd708699ceaeccd9188685a7184d..dee88e665c1609224bdab185ea147b91c49dc8f3 100644
--- a/src/doc/specification/AbstractProperty.md
+++ b/src/doc/specification/AbstractProperty.md
@@ -1,7 +1,14 @@
+# Note #
+
+>   This document has not been updated for a long time. Although it is concerned with the mostly
+>   stable API, its content may no longer reflect the actual CaosDB behavior.
+
 # AbstractProperty Specification
 
+**Warning:** This specification is outdated. It is included to serve as a starting point for a more up-to-date description of the `Property` entity.
+
 ## Introduction
-An `AbstractProperty` is one of the basal [objects of HeartDB](./HeartDBObject).
+An `AbstractProperty` is one of the basal objects of CaosDB.
 An `AbstractProperty` MUST have the following _qualities_ (shortcut in brackets):
 * a persistent id (`id`)
 * an unique name (`name`)
@@ -34,7 +41,7 @@ An `AbstractProperty` is represented in xml by a `<Property/>` tag. It's _qualit
 Depending on the purpose of the xml document (shall it represent an object in the database or an object _to be posted _to the database?) the `<Property/>` tag may actually have just a few of the mentioned "quality-attributes".
 
 ### GET AbstractProperty
-Any xml representation of an `AbstractProperty` that is retrieved from the HeartDB Server MUST have exactly ONE of the following forms, depending on the `AbstractProperty's` type:
+Any xml representation of an `AbstractProperty` that is retrieved from the CaosDB Server MUST have exactly ONE of the following forms, depending on the `AbstractProperty's` type:
 #### text
 
         <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="text" />
@@ -54,10 +61,10 @@ Any xml representation of an `AbstractProperty` that is retrieved from the Heart
 
         <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="file" />
 '''General Notes:
-* If the called Property does not exist or if the Property called without permission, the HeartDB Server will return an [Error](./Errorcodes).
+* If the called Property does not exist or if the Property called without permission, the CaosDB Server will return an Error.
 
 ### POST AbstractProperty
-Any xml representation of an `AbstractProperty` that is to be posted to the HeartDB server MUST have exactly ONE of the following forms, depending on the `AbstractProperty's` type:
+Any xml representation of an `AbstractProperty` that is to be posted to the CaosDB server MUST have exactly ONE of the following forms, depending on the `AbstractProperty's` type:
 #### text
 
         <Property name="$name" description="$description" generator="$generator" type="text" />
@@ -77,10 +84,10 @@ Any xml representation of an `AbstractProperty` that is to be posted to the Hear
 
         <Property name="$name" description="$description" generator="$generator" type="file" />
 *General Notes:*
-* The `AbstractProperty's` `id` and timestamp (`created`) will be generated by the HeartDB Server.
-* The `AbstractProperty's` creator will be determined by the HeartDB Server depending on it's policy configuration.
+* The `AbstractProperty's` `id` and timestamp (`created`) will be generated by the CaosDB Server.
+* The `AbstractProperty's` creator will be determined by the CaosDB Server depending on it's policy configuration.
 * Any given attribute beyond these will be *ignored*.
-* If the `<Property/>` tag isn't compliant with these the HeartDB Server will return an [Error](./Errorcodes).
+* If the `<Property/>` tag isn't compliant with these the CaosDB Server will return an Error.
 
 ----
 ## Examples
diff --git a/src/doc/specification/Authentication.md b/src/doc/specification/Authentication.md
index 9ec5bcd74ffb68ee33084f739d6d547a3e7a9bb8..6040d3792db553aea8cdd7206367be2bfec9257b 100644
--- a/src/doc/specification/Authentication.md
+++ b/src/doc/specification/Authentication.md
@@ -1,5 +1,5 @@
 # Authentication
- Some features of HeartDB are available to registered users only. Making any changes to the data stock via HTTP requires authentication by `username` _plus_ `password`. They are to be send as a HTTP header, while the password is to be hashed by the sha512 algorithm:
+ Some features of CaosDB are available to registered users only. Making any changes to the data stock via HTTP requires authentication by `username` _plus_ `password`. They are to be send as a HTTP header, while the password is to be hashed by the sha512 algorithm:
 
 | `username:` | `$username` | 
 |-------------|-------------|-
diff --git a/src/doc/specification/Specification-of-the-Entity-API.md b/src/doc/specification/Entity_Overview.md
similarity index 97%
rename from src/doc/specification/Specification-of-the-Entity-API.md
rename to src/doc/specification/Entity_Overview.md
index 93b39f5333b9f80c9f4485fc00c2f827ad60792a..ca197e50050d9ba2c593973148627c4d56708a67 100644
--- a/src/doc/specification/Specification-of-the-Entity-API.md
+++ b/src/doc/specification/Entity_Overview.md
@@ -1,4 +1,7 @@
-# Specification of the Entity API
+# Overview of the CaosDB Entities' structure
+
+**Warning:** This overview may be outdated.
+
 Version: 0.1.0r1
 
 Author: Timm Fitschen
diff --git a/src/doc/specification/Fileserver.md b/src/doc/specification/Fileserver.md
index fda2ec183c909e494a407f74eb1f4d03a5a23625..dd3e0f37f4da98aef413c3a4aed15da2e8230b66 100644
--- a/src/doc/specification/Fileserver.md
+++ b/src/doc/specification/Fileserver.md
@@ -1,12 +1,12 @@
 # Fileserver
 
 ## Info
-There are several ways to utilize the file server component of HeartDB. It is possible to upload a file or a whole folder including subfolders via HTTP and the _drop off box_. It is possible to download a file via HTTP identified by its ID or by its path in the internal file system. Furthermore, it is possible to get the files metadata via HTTP as an xml. 
+There are several ways to utilize the file server component of CaosDB. It is possible to upload a file or a whole folder including subfolders via HTTP and the _drop off box_. It is possible to download a file via HTTP identified by its ID or by its path in the internal file system. Furthermore, it is possible to get the files metadata via HTTP as an xml. 
 
 ## File upload
 ### Drop off box
 
-The drop off box is a directory on the HeartDB server's local file system, specified in the `server.conf` file in the server's basepath (something like `~/HeartDB/server/server.conf`). The key in the `server.conf` is called `dropoffbox`. Since the drop off box directory is writable for all, users can push their files or complete folders via a `mv` or a `cp` (recommended!) in that folder. The server deletes files older than their maximum lifetime (24 hours by default, specified `in server.conf`). But within their lifetime a user can prompt the server to pick up the file (or folder) from the drop off box in order to transfer it to the internal file system. 
+The drop off box is a directory on the CaosDB server's local file system, specified in the `server.conf` file in the server's basepath (something like `~/CaosDB/server/server.conf`). The key in the `server.conf` is called `dropoffbox`. Since the drop off box directory is writable for all, users can push their files or complete folders via a `mv` or a `cp` (recommended!) in that folder. The server deletes files older than their maximum lifetime (24 hours by default, specified `in server.conf`). But within their lifetime a user can prompt the server to pick up the file (or folder) from the drop off box in order to transfer it to the internal file system. 
 
 Now, the user may send a pick up request to `POST http://host:port/mpidsserver/FilesDropOff` with a similar body:
 
@@ -35,11 +35,12 @@ where
 ### HTTP upload stream
 #### Files
 
-There is an example on file upload using cURL described in detail in [the curl section of this wiki](manuals/curl/curl-access).
+There is an example on file upload using cURL described in detail in [the curl section of this
+documentation](../administration/curl-access.md).
 
-File upload via HTTP is implemented in a [rfc1867](http://www.ietf.org/rfc/rfc1867.txt) consistent way. This is a de-facto standard that defines a file upload as a part of an HTML form submission. This concept shall not be amplified here. But it has to be noticed that this protocol is not designed for uploads of complete structured folders. Therefore the HeartDB file components have to impose that structure on the upload protocol. 
+File upload via HTTP is implemented in a [rfc1867](http://www.ietf.org/rfc/rfc1867.txt) consistent way. This is a de-facto standard that defines a file upload as a part of an HTML form submission. This concept shall not be amplified here. But it has to be noticed that this protocol is not designed for uploads of complete structured folders. Therefore the CaosDB file components have to impose that structure on the upload protocol. 
 
-HeartDB's file upload resource does exclusively accept POST requests of MIME media type `multipart/form-data`. The first part of each POST body is expected to be a form-data text field, containing information about the files to be uploaded. It has to meet the following requirements:
+CaosDB's file upload resource does exclusively accept POST requests of MIME media type `multipart/form-data`. The first part of each POST body is expected to be a form-data text field, containing information about the files to be uploaded. It has to meet the following requirements:
 * `Content-type: text/plain; charset=UTF-8`
 * `Content-disposition: form-data; name="FileRepresentation"`
 
diff --git a/src/doc/specification/Record.md b/src/doc/specification/Record.md
index 1e43e372f754d884da2ff15cfb7b71f6813f1785..d41896962540486282e7b98be1a1f982ee9e1c74 100644
--- a/src/doc/specification/Record.md
+++ b/src/doc/specification/Record.md
@@ -1,5 +1,6 @@
 # Record
 
+**Warning:** This specification is outdated. It is included to serve as a starting point for a more up-to-date description of the `Record` entity.
 
 = GET records = 
 
diff --git a/src/doc/specification/RecordType.md b/src/doc/specification/RecordType.md
deleted file mode 100644
index 068c433d8c5390882176c0ff8783576127a2da2e..0000000000000000000000000000000000000000
--- a/src/doc/specification/RecordType.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# RecordType
-----
-## Overview
-RecordTypes function as templates for [[Record|Records]], they provide a description for a type of Record and define which [[Property|Properties]] should be present.  Properties come with an _importance_ attribute which tells the user or client program how strongly necessary the Property is.  (As all other entities,) RecordTypes can be inherited from other RecordTypes (or any Entities).  When RecordTypes inherit from other RecordTypes, the _inheritance_ flag tells which properties shall be inherited.
-
-### Importance
-
-Importances are more of a recommendation, Records with omitted Properties can technically still be committed.
-
-| *Importance* | *Meaning*            | *Consequence when omitted* |
-|--------------|----------------------|----------------------------|
-| `OBLIGATORY`     | Property must be present | *FIXME* Can be forced to be committed the server, but usually an error will be returned. |
-| `RECOMMENDED`    | Property should be present (makes sense for most users) | *FIXME* Usually an error will be returned? |
-| `SUGGESTED` (*FIXME* the default?)      | Property may be present (may make sense for some users) | *FIXME* No negative consequence? |
-
-*FIXME* Did I get recommended and suggested right?
-
-### Inheritance
-
-The _inheritance_ flag decides which [[Property|Properties]] are inherited from parents.
-
-| *Inheritance*  | *Meaning* |
-|----------------|-----------|
-| _None_ (default) | Nothing is being inherited |
-| `OBLIGATORY`     | *FIXME* Properteis of importance `OBLIGATORY` and above? |
-| `SUGGESTED`      | *FIXME* Properteis of importance `SIGGESTED` and above, so the same effect as `ALL`? |
-| `ALL`            | Copy everything from the parent. |
-| `FIX`            | *FIXME* in `tests/test_inheritance.py` of pyint_test this is used, what does it mean???|
-
-
-## Examples
-### GET Requests
-#### Single-Get
-*Request:*
-
-        GET http://localhost:8122/mpidsserver/RecordType/1
-
-*Response:*
-
-        <?xml version="1.0" encoding="UTF-8"?>
-        <Response>
-          <RecordType id="1" name="Experiment" description="Description Experiment">
-            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory" />
-            <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended" />
-            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory" />
-            <Property id="7" name="stopDate" type="datetime" description="stop" importance="recommended" />
-            <Property id="12" name="file" type="file" description="file" importance="suggested" />
-          </RecordType>
-        </Response>
-----
-#### Multi-Get
-*Request:*
-
-        GET http://localhost:8122/mpidsserver/RecordType/1&2&3
-
-*Response:*
-
-        <?xml version="1.0" encoding="UTF-8"?>
-        <?xml version="1.0" encoding="UTF-8"?>
-        <Response>
-          <RecordType id="1" name="Experiment" description="Description Experiment">
-            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory" />
-            <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended" />
-            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory" />
-            <Property id="7" name="stopDate" type="datetime" description="stop" importance="recommended" />
-            <Property id="12" name="file" type="file" description="file" importance="suggested" />
-          </RecordType>
-          <RecordType id="2" name="Measurement" description="Description Measurement">
-            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory" />
-            <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended" />
-            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory" />
-            <Property id="7" name="stopDate" type="datetime" description="stop" importance="recommended" />
-            <Property id="12" name="file" type="file" description="file" importance="suggested" />
-          </RecordType>
-          <RecordType id="3" name="Video" description="Description Video">
-            <Property id="1" name="explanation" type="text" description="explains the thing" importance="recommended" />
-            <Property id="6" name="startDate" type="datetime" description="start" importance="recommended" />
-            <Property id="12" name="file" type="file" description="file" importance="obligatory" />
-          </RecordType>
-        </Response>
-----
-#### Get all
-*Request:*
-
-        GET http://localhost:8122/mpidsserver/RecordType/
-        GET http://localhost:8122/mpidsserver/RecordType
-
-*Response:*
-
-        <?xml version="1.0" encoding="UTF-8"?>
-        <Response />
-[View Ticket #2](http://dev.bmp.ds.mpg.de/heartdb/ticket/2)
-
-----
-### POST Requests
-
-*Request is to be send to:*
-
-        POST http://localhost:8122/mpidsserver/RecordType/
-        POST http://localhost:8122/mpidsserver/RecordType/
-----
-#### Single-Post
-*HTTP Body:*
-
-        <Post>  
-          <RecordType name="TimmsRecordType11" >
-            <Property id="3" importance="recommended"/>
-            <Property id="4" importance="obligatory"/>
-            <Property name="age" />
-          </RecordType>
-        </Post>
-
-*Response:*
-
-        <?xml version="1.0" encoding="UTF-8"?>
-        <Response>
-          <RecordType id="40" name="TimmsRecordType11">
-            <Property id="3" name="name" type="text" description="Name" importance="recommended" />
-            <Property id="4" name="id" type="integer" description="identifier" importance="suggested" />
-          </RecordType>
-        </Response>
-----
-#### Multi-Post
-*HTTP Body:*
-
-        <Post>  
-          <RecordType name="TimmsRecordType11" >
-            <Property id="3" importance="recommended"/>
-            <Property id="4" importance="obligatory"/>
-            <Property name="age" />
-          </RecordType>
-          <RecordType name="TimmsRecordType12" >
-            <Property id="6" importance="recommended"/>
-            <Property id="7" importance="obligatory"/>
-            <Property id="15" />
-          </RecordType>
-        </Post>
-
-*Response:*
-
-        <?xml version="1.0" encoding="UTF-8"?>
-        <Response>
-          <RecordType id="40" name="TimmsRecordType11" description="null">
-            <Property id="3" name="name" type="text" description="Name" importance="recommended" />
-            <Property id="4" name="id" type="integer" description="identifier" importance="suggested" />
-            <Property id="3" name="name" type="text" description="Name" importance="recommended" />
-            <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="suggested" />
-          </RecordType>
-          <RecordType id="41" name="TimmsRecordType12">
-            <Property id="6" name="startDate" type="datetime" description="start" importance="recommended" />
-            <Property id="7" name="stopDate" type="datetime" description="stop" importance="suggested" />
-            <Property id="15" name="TimmsIntProperty" type="integer" unit="kg" description="This is TimmsIntProperty" importance="suggested" />
-          </RecordType>
-        </Response>
-
-= DELETE RecordTypes = 
-
-HTTP-DELETE-requests are to be send to `http://${host}:${port}/mpidsserver/RecordType/...`. Default port is 8123.
-
-*Example*
-{{{ 
-DELETE http://${host}:${port}/mpidsserver/RecordType/1&2&3&4
-}}}
-
diff --git a/src/doc/specification/RecordType.rst b/src/doc/specification/RecordType.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0c196fea86e74f1014e34c0bd1e5b096acd1a5d9
--- /dev/null
+++ b/src/doc/specification/RecordType.rst
@@ -0,0 +1,283 @@
+RecordType
+==========
+
+Overview
+--------
+
+RecordTypes act as templates for :doc:`Records <Record>`. They
+provide a description for a type of Record and define which
+:doc:`Properties <AbstractProperty>` could, should, or must be
+present. Properties come with an `importance` attribute which tells
+the user or client program how strongly necessary the Property is. (As
+all other entities,) RecordTypes can inherit from other
+RecordTypes (or any Entities). When RecordTypes inherit from other
+RecordTypes, the `inheritance` flag tells which properties shall be
+inherited from the parents to the children.
+
+Importance
+~~~~~~~~~~
+
+The `importance` value of a Property states whether it may, should, or
+must be present in all children of this Entity. The table below
+lists the possible importances, together with the default behavior of
+the server when the property is not present in a child.
+
+.. list-table::
+   :widths: 25 25 50
+   :width: 100%
+   :header-rows: 1
+
+   * - Importance
+     - Meaning
+     - Consequence when omitted
+   * - ``OBLIGATORY``
+     - Property must be present
+     - Can be forced to be committed the server, but usually
+       an error will be returned.
+   * - ``RECOMMENDED``
+     - Property should be present (makes sense for most users)
+     - No consequence right now, but in future versions a warning will
+       be returned.
+   * - ``SUGGESTED`` (default)
+     - Property may be present (may make sense for some users)
+     - No negative consequence
+   * - ``FIX``
+     - Property is not present in any child
+     - ---
+
+The default behavior can be overridden for individual transaction by setting the
+`force-missing-obligatory` flag to ``ERROR`` (the default), ``WARN``, or ``IGNORE``.  These flags
+cause a transaction that would result in an entity with a missing `obligatory` property to return an
+error, a warning, or to execute without a warning, respectively. The importance value ``FIX`` is
+used for properties that are specific to the Entity itself and is explained below.
+       
+.. note::
+
+   Note that in contrast to RecordTypes, all properties of a
+   :doc:`Record` have the importance ``FIX`` since in general, even in the
+   case of a Record having children, its properties are not
+   inherited.
+
+Inheritance
+~~~~~~~~~~~
+
+The `inheritance` flag specifies which :doc:`Properties
+<AbstractProperty>` are inherited from the parent RecordType(s) to the
+child.
+
+.. list-table::
+   :widths: 40 60
+   :header-rows: 1
+
+   * - Inheritance
+     - Meaning
+   * - `None` (default)
+     - Nothing is inherited
+   * - ``OBLIGATORY``
+     - Properties of importance ``OBLIGATORY`` and above
+   * - ``RECOMMENDED``
+     - Properties of importance ``RECOMMENDED`` and above
+   * - ``SUGGESTED``
+     - Properties of importance ``SUGGESTED`` and above
+   * - ``ALL``
+     - Copy everything from the parent.
+
+Note that inheritance ``ALL`` is essentially the same as ``SUGGESTED``
+right now, but this behavior might change in the future. Also note
+that properties of importance ``FIX`` are never inherited this
+way. This is precisely what this importance level is meant for:
+properties that are specific to the parent entity and are not
+inherited by default. You can still add this property to the child
+manually if you wish, though, but this is not covered by the
+automatic inheritance.
+
+.. note::
+
+   The behavior of `inheritance` is currently not well-defined if the
+   child is a :doc:`Record`, not a RecordType.
+
+Examples
+--------
+
+.. warning::
+
+   The following examples have not been updated in a while and may
+   serve as a starting point, but are not guaranteed to be fully
+   applicable to the most recent versions of CaosDB.
+
+GET Requests
+~~~~~~~~~~~~
+
+Single-Get
+^^^^^^^^^^
+
+*Request:*
+
+.. code-block::
+
+   GET http://localhost:8122/RecordType/1
+
+*Response:*
+
+.. code-block:: XML
+		
+   <?xml version="1.0" encoding="UTF-8"?>
+     <Response>
+       <RecordType id="1" name="Experiment" description="Description Experiment">
+         <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory" />
+	 <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended" />
+	 <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory" />
+	 <Property id="7" name="stopDate" type="datetime" description="stop" importance="recommended" />
+	 <Property id="12" name="file" type="file" description="file" importance="suggested" />
+       </RecordType>
+     </Response>
+
+Multi-Get
+^^^^^^^^^
+
+*Request:*
+
+.. code-block::
+
+   GET http://localhost:8122/RecordType/1&2&3
+
+*Response:*
+
+.. code-block:: XML
+
+   <?xml version="1.0" encoding="UTF-8"?>
+     <Response>
+       <RecordType id="1" name="Experiment" description="Description Experiment">
+         <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory" />
+	 <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended" />
+         <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory" />
+         <Property id="7" name="stopDate" type="datetime" description="stop" importance="recommended" />
+         <Property id="12" name="file" type="file" description="file" importance="suggested" />
+       </RecordType>
+       <RecordType id="2" name="Measurement" description="Description Measurement">
+         <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory" />
+         <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended" />
+         <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory" />
+         <Property id="7" name="stopDate" type="datetime" description="stop" importance="recommended" />
+         <Property id="12" name="file" type="file" description="file" importance="suggested" />
+       </RecordType>
+       <RecordType id="3" name="Video" description="Description Video">
+         <Property id="1" name="explanation" type="text" description="explains the thing" importance="recommended" />
+         <Property id="6" name="startDate" type="datetime" description="start" importance="recommended" />
+         <Property id="12" name="file" type="file" description="file" importance="obligatory" />
+       </RecordType>
+     </Response>
+
+	
+Get all
+^^^^^^^
+
+*Request:*
+
+.. code-block::
+
+   GET http://localhost:8122/RecordType/
+   GET http://localhost:8122/RecordType
+
+*Response:*
+
+.. note::
+
+   Outdated? At least the bug tracker doesn't exist anymore.
+
+.. code-block:: XML
+
+   <?xml version="1.0" encoding="UTF-8"?>
+     <Response />
+
+[View Ticket #2](http://caosdb.example.com/caosdb/ticket/2)
+
+
+POST Requests
+~~~~~~~~~~~~~
+
+*Request is to be send to:*
+
+.. code-block::
+
+   POST http://localhost:8122/RecordType/
+   POST http://localhost:8122/RecordType/
+
+Single-Post
+^^^^^^^^^^^
+
+*HTTP Body:*
+
+.. code-block:: XML
+
+   <Post>  
+     <RecordType name="TimmsRecordType11" >
+       <Property id="3" importance="recommended"/>
+       <Property id="4" importance="obligatory"/>
+       <Property name="age" />
+     </RecordType>
+   </Post>
+
+*Response:*
+
+.. code-block:: XML
+
+   <?xml version="1.0" encoding="UTF-8"?>
+     <Response>
+       <RecordType id="40" name="TimmsRecordType11">
+         <Property id="3" name="name" type="text" description="Name" importance="recommended" />
+         <Property id="4" name="id" type="integer" description="identifier" importance="suggested" />
+       </RecordType>
+     </Response>
+
+Multi-Post
+^^^^^^^^^^
+
+*HTTP Body:*
+
+.. code-block:: XML
+
+   <Post>  
+     <RecordType name="TimmsRecordType11" >
+       <Property id="3" importance="recommended"/>
+       <Property id="4" importance="obligatory"/>
+       <Property name="age" />
+     </RecordType>
+     <RecordType name="TimmsRecordType12" >
+       <Property id="6" importance="recommended"/>
+       <Property id="7" importance="obligatory"/>
+       <Property id="15" />
+     </RecordType>
+   </Post>
+
+*Response:*
+
+.. code-block:: XML
+
+   <?xml version="1.0" encoding="UTF-8"?>
+     <Response>
+       <RecordType id="40" name="TimmsRecordType11" description="null">
+         <Property id="3" name="name" type="text" description="Name" importance="recommended" />
+         <Property id="4" name="id" type="integer" description="identifier" importance="suggested" />
+         <Property id="3" name="name" type="text" description="Name" importance="recommended" />
+         <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="suggested" />
+       </RecordType>
+       <RecordType id="41" name="TimmsRecordType12">
+         <Property id="6" name="startDate" type="datetime" description="start" importance="recommended" />
+         <Property id="7" name="stopDate" type="datetime" description="stop" importance="suggested" />
+         <Property id="15" name="TimmsIntProperty" type="integer" unit="kg" description="This is TimmsIntProperty" importance="suggested" />
+       </RecordType>
+     </Response>
+
+DELETE RecordTypes
+~~~~~~~~~~~~~~~~~~
+
+HTTP-DELETE-requests are to be send to ``http://${host}:${port}/RecordType/...``. Default port is 8123.
+
+*Example*
+
+.. code-block:: 
+
+   DELETE http://${host}:${port}/RecordType/1&2&3&4
+
+
diff --git a/src/doc/specification/entity_api.rst b/src/doc/specification/entity_api.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3f6bdbcf700aaeebbe7d49fe79ac47c4ad4303ad
--- /dev/null
+++ b/src/doc/specification/entity_api.rst
@@ -0,0 +1,24 @@
+The Entity API
+==============
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+   :hidden:
+
+   Overview of the CaosDB Entities' structure<Entity_Overview>
+
+.. note::
+
+   Until the corresponding `bug of openapi-codegen <https://github.com/OpenAPITools/openapi-generator/issues/9923>`_
+   is resolved, the documentation of the OpenAPI specification of the
+   entity API is best rendered by gitlab.
+
+The details of the specification, including interactive examples, can
+be found `here
+<https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/tree/dev/src/doc/development/api/xml/caosdb_openapi.yaml>`_.
+Note that some commands may require authentication, which can be done with
+an authentication cookie as described in the :doc:`api
+documentation<Authentication>`. Specialities of the :doc:`scripting
+api<Server-side-scripting>` and the :doc:`file api<Fileserver>` are
+explained in the corresponding sections of the specifications.
diff --git a/src/doc/specification/index.rst b/src/doc/specification/index.rst
index e9683072f555725ee8b74a0ae468c6a407e244b2..69677277fc793d513aa21f506295567340d462e1 100644
--- a/src/doc/specification/index.rst
+++ b/src/doc/specification/index.rst
@@ -1,22 +1,18 @@
-                                                                                
 Specification
 =============
-                                                                                
-.. toctree::                                                                    
-   :maxdepth: 2                                                                 
-   :caption: Contents:                                                          
-   :hidden:                                                                     
-                                                                                
-   AbstractProperty                                                                  
-   C-Client  
-   Fileserver  
-   Record      
-   Server-side-scripting       
-   Specification of the Entity API <Specification-of-the-Entity-API>
-   Authentication    
-   Datatype  
-   Paging      
-   RecordType  
-   Server side scripting <Server-side-scripting-v0.1>
-   Specification of the Message API <Specification-of-the-Message-API>
 
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+   :hidden:
+
+   AbstractProperty
+   Fileserver
+   Record
+   Authentication
+   Datatype
+   Paging
+   RecordType
+   Server side scripting <Server-side-scripting>
+   Specification of the Message API <Specification-of-the-Message-API>
+   Specification of the Entity API <entity_api>
diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java
index 3172a3e88e7790580af1b7865c024fa2b40c017b..176492d691cc63c422e5996bb055763f8d05a751 100644
--- a/src/main/java/org/caosdb/server/ServerProperties.java
+++ b/src/main/java/org/caosdb/server/ServerProperties.java
@@ -133,6 +133,7 @@ public class ServerProperties extends Properties {
   public static final String KEY_WEBUI_HTTP_HEADER_CACHE_MAX_AGE =
       "WEBUI_HTTP_HEADER_CACHE_MAX_AGE";
   public static final String KEY_AUTHTOKEN_CONFIG = "AUTHTOKEN_CONFIG";
+  public static final String KEY_JOB_RULES_CONFIG = "JOB_RULES_CONFIG";
 
   /**
    * Read the config files and initialize the server properties.
@@ -150,11 +151,11 @@ public class ServerProperties extends Properties {
     loadConfigFile(serverProperties, new File(basepath + "/conf/ext/server.conf"));
 
     // load ext/server.conf.d/ (ordering is determined by system collation)
-    File confDir = new File(basepath + "/conf/ext/server.conf.d");
+    final File confDir = new File(basepath + "/conf/ext/server.conf.d");
     if (confDir.exists() && confDir.isDirectory()) {
-      String[] confFiles = confDir.list();
+      final String[] confFiles = confDir.list();
       Arrays.sort(confFiles, Comparator.naturalOrder());
-      for (String confFile : confFiles) {
+      for (final String confFile : confFiles) {
         // prevent backup files from being read
         if (confFile.endsWith(".conf")) {
           loadConfigFile(serverProperties, new File(confDir, confFile));
@@ -163,7 +164,7 @@ public class ServerProperties extends Properties {
     }
 
     // load env vars
-    for (java.util.Map.Entry<String, String> envvar : System.getenv().entrySet()) {
+    for (final java.util.Map.Entry<String, String> envvar : System.getenv().entrySet()) {
       if (envvar.getKey().startsWith("CAOSDB_CONFIG_") && envvar.getKey().length() > 14) {
         serverProperties.setProperty(envvar.getKey().substring(14), envvar.getValue());
       }
@@ -171,11 +172,11 @@ public class ServerProperties extends Properties {
 
     // log configuration alphabetically
     if (logger.isInfoEnabled()) {
-      ArrayList<String> names = new ArrayList<>(serverProperties.stringPropertyNames());
+      final ArrayList<String> names = new ArrayList<>(serverProperties.stringPropertyNames());
       Collections.sort(names);
-      for (String name : names) {
-        String val =
-            (name.contains("PASSW") || name.contains("SECRET"))
+      for (final String name : names) {
+        final String val =
+            name.contains("PASSW") || name.contains("SECRET")
                 ? "****"
                 : serverProperties.getProperty(name);
         logger.info(name + "=" + val);
@@ -184,7 +185,7 @@ public class ServerProperties extends Properties {
     return serverProperties;
   }
 
-  private static void loadConfigFile(Properties serverProperties, File confFile)
+  private static void loadConfigFile(final Properties serverProperties, final File confFile)
       throws IOException {
     if (confFile.exists() && confFile.isFile()) {
       logger.info("Reading configuration from " + confFile.getAbsolutePath());
diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java
index 61aebf42a2dfd65fb9ed1c84459064e3aa274c06..7c26db1ed05e76c30f9d30f0c3d9965ed0c59b6b 100644
--- a/src/main/java/org/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java
@@ -1,11 +1,10 @@
 /*
- * ** 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) 2020 IndiScale GmbH
- * Copyright (C) 2020 Timm Fitschen (t.fitschen@indiscale.com)
+ * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.database;
 
@@ -61,7 +58,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveRole
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveSparseEntity;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveUser;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveVersionHistory;
-import org.caosdb.server.database.backend.implementation.MySQL.MySQLRuleLoader;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetFileCheckedTimestampImpl;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetFileChecksum;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetPassword;
@@ -117,7 +113,6 @@ import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveUserImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl;
-import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl;
 import org.caosdb.server.database.backend.interfaces.SetFileCheckedTimestampImpl;
 import org.caosdb.server.database.backend.interfaces.SetFileChecksumImpl;
 import org.caosdb.server.database.backend.interfaces.SetPasswordImpl;
@@ -129,8 +124,6 @@ import org.caosdb.server.database.backend.interfaces.UpdateUserImpl;
 import org.caosdb.server.database.backend.interfaces.UpdateUserRolesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.misc.TransactionBenchmark;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.RetrieveEntity;
 import org.caosdb.server.utils.UndoHandler;
 import org.caosdb.server.utils.Undoable;
 
@@ -145,6 +138,7 @@ public abstract class BackendTransaction implements Undoable {
 
   protected abstract void execute();
 
+  /** Like execute(), but with benchmarking measurement. */
   public final void executeTransaction() {
     final long t1 = System.currentTimeMillis();
     execute();
@@ -182,7 +176,6 @@ public abstract class BackendTransaction implements Undoable {
       setImpl(GetFileRecordByPathImpl.class, MySQLGetFileRecordByPath.class);
       setImpl(RetrievePropertiesImpl.class, MySQLRetrieveProperties.class);
       setImpl(RetrieveSparseEntityImpl.class, MySQLRetrieveSparseEntity.class);
-      setImpl(RuleLoaderImpl.class, MySQLRuleLoader.class);
       setImpl(SyncStatsImpl.class, MySQLSyncStats.class);
       setImpl(FileExists.class, UnixFileSystemFileExists.class);
       setImpl(FileWasModifiedAfter.class, UnixFileSystemFileWasModifiedAfter.class);
@@ -250,7 +243,7 @@ public abstract class BackendTransaction implements Undoable {
   protected <T extends BackendTransactionImpl> T getImplementation(final Class<T> clz) {
     init();
     try {
-      Class<?> implclz = impl.get(clz);
+      final 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);
@@ -288,18 +281,13 @@ public abstract class BackendTransaction implements Undoable {
   }
 
   /** Set the benchmark object for this AbstractTransaction. */
-  public void setTransactionBenchmark(TransactionBenchmark b) {
+  public void setTransactionBenchmark(final TransactionBenchmark b) {
     this.benchmark = b;
   }
 
-  public void addMeasurement(Object o, long time) {
+  public void addMeasurement(final Object o, final long time) {
     if (this.benchmark != null) {
       this.benchmark.addMeasurement(o, time);
     }
   }
-
-  /** Return the type of transaction of an entity, e.g. "Retrieve" for a {@link RetrieveEntity}. */
-  public String getTransactionType(EntityInterface e) {
-    return e.getClass().getSimpleName().replace("Entity", "");
-  }
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java
index f77f810a93428a66247b6559e2bef2ac344cd5d9..997de3141db6fd49ac510fa61150a2d16e7d5416 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java
@@ -175,25 +175,69 @@ class DatabaseConnectionPool {
     logger.debug("####################");
   }
 
+  /**
+   * Check the version of the SQL server's database.
+   *
+   * <p>Current behaviour: Major versions must match and the database's minor version must be at
+   * least the expected minor version as defined in {@link
+   * ServerProperties.KEY_MYSQL_SCHEMA_VERSION}.
+   *
+   * <p>@todo Patch versions are not handled yet.
+   */
   private static void checkVersion(final Connection con)
       throws SQLException, ConnectionException, CaosDBException {
     try {
+      final String[] expected_version =
+          CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_SCHEMA_VERSION)
+              .toLowerCase()
+              .split("\\.");
+      final String expected_major = expected_version[0];
+      final String expected_minor = expected_version[1];
+
       con.setReadOnly(false);
-      final PreparedStatement prepareStatement = con.prepareStatement("SELECT CaosDBVersion()");
+      final PreparedStatement prepared = con.prepareStatement("SELECT CaosDBVersion()");
       try {
-        final ResultSet executeQuery = prepareStatement.executeQuery();
+        final ResultSet executeQuery = prepared.executeQuery();
         if (executeQuery.next()) {
-          final String v_e =
-              CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_SCHEMA_VERSION)
-                  .toLowerCase();
-          final String v_a = executeQuery.getString(1).toLowerCase();
-          if (!Objects.equal(v_a, v_e)) {
-            logger.error(
-                "Version of the MySQL schema is wrong.\n\tExpected: {}\n\tActual: {}n\nPlease upgrade the mysql backend.\n\n",
-                v_e,
-                v_a);
+
+          final String actual_version = executeQuery.getString(1).toLowerCase();
+          final String[] actual_version_split = actual_version.split("\\.");
+          final String actual_major = actual_version_split[0];
+          final String actual_minor = actual_version_split[1];
+
+          if (!Objects.equal(actual_major, expected_major)) {
+            if (Integer.parseInt(actual_major.replace("v", ""))
+                < Integer.parseInt(expected_major.replace("v", ""))) {
+              logger.error(
+                  "Version of the MySQL/MariaDB schema is incompatible.\n\tExpected: {}.{}\n\tActual: {}\nPlease upgrade the MySQL backend.\n\n",
+                  expected_major,
+                  expected_minor,
+                  actual_version);
+            } else {
+              logger.error(
+                  "Version of the MySQL/MariaDB schema is incompatible.\n\tExpected: {}.{}\n\tActual: {}\nPlease upgrade the CaosDB server.\n\n",
+                  expected_major,
+                  expected_minor,
+                  actual_version);
+            }
             System.exit(1);
           }
+          if (!Objects.equal(actual_minor, expected_minor)) {
+            if (Integer.parseInt(actual_minor) < Integer.parseInt(expected_minor)) {
+              logger.error(
+                  "Version of the MySQL/MariaDB schema is incompatible.\n\tExpected: {}.{}\n\tActual: {}\nPlease upgrade the MySQL backend.\n\n",
+                  expected_major,
+                  expected_minor,
+                  actual_version);
+              System.exit(1);
+            }
+          }
+        } else {
+          logger.error(
+              "Version of the MySQL schema could not be determined. This probably means that the version is too old.\n\tExpected: {}.{}\n\tActual: unknown\nPlease upgrade the MySQL backend.\n\n",
+              expected_major,
+              expected_minor);
+          System.exit(1);
         }
       } catch (final SQLException e) {
         logger.error("Could not check the version of the MySQL schema.", e);
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
index 7af4022595f64b909231c72c83ae1e151a9d06c1..77618e92d56a166eb9d0df96258b943fb9df52d5 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.database.backend.implementation.MySQL;
 
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java
deleted file mode 100644
index 525c3973e5267d336727e723a383aff575dd8493..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java
+++ /dev/null
@@ -1,80 +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 org.caosdb.server.database.backend.implementation.MySQL;
-
-import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
-
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.Rule;
-import org.caosdb.server.jobs.core.Mode;
-
-public class MySQLRuleLoader extends MySQLTransaction implements RuleLoaderImpl {
-
-  public MySQLRuleLoader(final Access access) {
-    super(access);
-  }
-
-  public static final String STMT_GET_RULES =
-      "SELECT rules.transaction, rules.criterion, rules.modus from rules where rules.domain_id=? AND rules.entity_id=? AND rules.transaction=?";
-
-  @Override
-  public ArrayList<Rule> executeNoCache(
-      final Integer domain, final Integer entity, final String transaction)
-      throws TransactionException {
-    try {
-      final PreparedStatement stmt = prepareStatement(STMT_GET_RULES);
-
-      stmt.setInt(1, domain);
-      stmt.setInt(2, entity);
-      stmt.setString(3, transaction);
-      stmt.execute();
-
-      final ResultSet rs = stmt.executeQuery();
-      try {
-        final ArrayList<Rule> ret = new ArrayList<Rule>();
-        while (rs.next()) {
-          final Rule r = new Rule();
-          r.mode = Mode.valueOf(bytes2UTF8(rs.getBytes("modus")));
-          r.job = bytes2UTF8(rs.getBytes("criterion"));
-          r.domain = domain;
-          r.entity = entity;
-          r.transaction = transaction;
-          ret.add(r);
-        }
-        return ret;
-      } finally {
-        rs.close();
-      }
-    } catch (final SQLException e) {
-      throw new TransactionException(e);
-    } catch (final ConnectionException e) {
-      throw new TransactionException(e);
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java
deleted file mode 100644
index 151e3d9d21d02d953cffeec8a55b4e01377fc023..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java
+++ /dev/null
@@ -1,33 +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 org.caosdb.server.database.backend.interfaces;
-
-import java.util.ArrayList;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.Rule;
-
-public interface RuleLoaderImpl extends BackendTransactionImpl {
-
-  public ArrayList<Rule> executeNoCache(Integer domain, Integer entity, String transaction)
-      throws TransactionException;
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
index 9c86ba33bf47b8051bc365bb32a6f36d0342ff92..5871c97e8ff172fe58b37627cc93824e96c7b32e 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.database.backend.transaction;
 
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityValue.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityValue.java
deleted file mode 100644
index 8ec6b0d1fab731d3d51b3a6793c69293575e6462..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityValue.java
+++ /dev/null
@@ -1,59 +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 org.caosdb.server.database.backend.transaction;
-
-import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.FlatProperty;
-import org.caosdb.server.datatype.SingleValue;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.StatementStatus;
-
-public class InsertEntityValue extends BackendTransaction {
-
-  private final EntityInterface entity;
-
-  public InsertEntityValue(final EntityInterface entity) {
-    this.entity = entity;
-  }
-
-  @Override
-  protected void execute() throws TransactionException {
-    final InsertEntityPropertiesImpl t = getImplementation(InsertEntityPropertiesImpl.class);
-
-    final FlatProperty p = new FlatProperty();
-    if (this.entity.hasValue()) {
-      p.id = this.entity.getId();
-      p.value = ((SingleValue) this.entity.getValue()).toDatabaseString();
-      p.status = StatementStatus.FIX.toString();
-      p.idx = 0;
-      t.execute(
-          this.entity.getDomain(),
-          this.entity.getId(),
-          p,
-          ((SingleValue) this.entity.getValue()).getTable(),
-          (this.entity.hasUnit() ? this.entity.getUnit().getSignature() : null));
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java
index 9b798de4279de8034168c4ecfbc1738c70370a65..6282ede52aa514353c21f0b8dd3345a85305725b 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.database.backend.transaction;
 
@@ -34,7 +33,9 @@ import org.caosdb.server.utils.EntityStatus;
  * Record the current transaction in the entities transaction history.
  *
  * @author Timm Fitschen (t.fitschen@indiscale.com)
+ * @deprecated To be replaced by versioning history.
  */
+@Deprecated
 public class InsertTransactionHistory extends BackendTransaction {
 
   private final TransactionContainer container;
@@ -62,7 +63,7 @@ public class InsertTransactionHistory extends BackendTransaction {
           || e.getEntityStatus() == EntityStatus.VALID) {
 
         t.execute(
-            getTransactionType(e),
+            e.getClass().getSimpleName().replace("Entity", ""),
             this.realm,
             this.user,
             this.datetime.getUTCSeconds(),
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java b/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
deleted file mode 100644
index c73023bd4429db19ca0a968aa99b657f0d0cbea4..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
+++ /dev/null
@@ -1,96 +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
- * 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 org.caosdb.server.database.backend.transaction;
-
-import java.util.ArrayList;
-import org.apache.commons.jcs.access.behavior.ICacheAccess;
-import org.caosdb.server.caching.Cache;
-import org.caosdb.server.database.CacheableBackendTransaction;
-import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.Rule;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.entity.wrapper.Property;
-import org.caosdb.server.jobs.Job;
-import org.caosdb.server.transaction.Transaction;
-
-/**
- * Load transaction rules (e.g. "Referenced entities must always exist") and their configuration
- * from the back-end.
- *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
- */
-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;
-  private final Integer domain;
-  private ArrayList<Job> jobs;
-  private String transactionType;
-
-  public RuleLoader(
-      final Integer domain,
-      final Integer entity,
-      final EntityInterface e,
-      final Transaction<? extends TransactionContainer> transaction) {
-    super(cache);
-    this.domain = domain;
-    this.entity = entity;
-    this.e = e;
-    if (e instanceof Property) {
-      this.transactionType = getTransactionType(((Property) e).getDomainEntity());
-    } else {
-      this.transactionType = getTransactionType(e);
-    }
-    this.transaction = transaction;
-  }
-
-  @Override
-  protected String getKey() {
-    return "<" + this.domain + "," + this.entity + "," + this.transactionType + ">";
-  }
-
-  public ArrayList<Job> getJobs() {
-    return this.jobs;
-  }
-
-  @Override
-  public ArrayList<Rule> executeNoCache() throws TransactionException {
-    final RuleLoaderImpl t = getImplementation(RuleLoaderImpl.class);
-    return t.executeNoCache(this.domain, this.entity, this.transactionType);
-  }
-
-  @Override
-  protected void process(final ArrayList<Rule> t) throws TransactionException {
-    this.jobs = new ArrayList<Job>();
-    for (final Rule r : t) {
-      this.jobs.add(Job.getJob(r.job, r.mode, this.e, this.transaction));
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java
index 275947abd9b42a2424141bc28111889859fb1475..39d1c76ba89d486388ef085b5cf38a25f0a15d15 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.database.backend.transaction;
 
@@ -50,8 +49,6 @@ public class UpdateEntityTransaction extends BackendTransaction {
 
         execute(new InsertParents(e));
 
-        execute(new InsertEntityValue(e));
-
         execute(new InsertEntityProperties(e));
 
         VersionTransaction.removeCached(e.getId());
diff --git a/src/main/java/org/caosdb/server/database/proto/Rule.java b/src/main/java/org/caosdb/server/database/proto/Rule.java
deleted file mode 100644
index 3d25f7662052b5b59f45255218c1191cf011761e..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/proto/Rule.java
+++ /dev/null
@@ -1,37 +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 org.caosdb.server.database.proto;
-
-import java.io.Serializable;
-import org.caosdb.server.jobs.core.Mode;
-
-public class Rule implements Serializable {
-
-  private static final long serialVersionUID = 1122097540596265945L;
-
-  public int domain = 0;
-  public int entity = 0;
-  public String job = null;
-  public String transaction = null;
-  public Mode mode = null;
-}
diff --git a/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java b/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java
index f2f8d2823e4483c59945c168f5e83d029bc6da6a..e9e2526521d044fc89746eb0e3204de97f473e0c 100644
--- a/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java
+++ b/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java
@@ -33,16 +33,19 @@ public class DoubleDatatype extends AbstractDatatype {
   public SingleValue parseValue(final Object value) throws Message {
     try {
       if (value instanceof GenericValue) {
-        return new GenericValue(Double.parseDouble(((GenericValue) value).toDatabaseString()));
+        return parse(((GenericValue) value).toDatabaseString());
       } else {
-        String valueStr = value.toString();
-        if (valueStr.toLowerCase().equals("nan")) {
-          valueStr = "NaN";
-        }
-        return new GenericValue(Double.parseDouble(valueStr));
+        return parse(value.toString());
       }
     } catch (final NumberFormatException e) {
       throw ServerMessages.CANNOT_PARSE_DOUBLE_VALUE;
     }
   }
+
+  private GenericValue parse(String valueStr) {
+    if (valueStr.toLowerCase().equals("nan")) {
+      valueStr = "NaN";
+    }
+    return new GenericValue(Double.parseDouble(valueStr));
+  }
 }
diff --git a/src/main/java/org/caosdb/server/entity/Message.java b/src/main/java/org/caosdb/server/entity/Message.java
index 3e469b2dc84b07e3ac3de378f8990cc520989cc0..91720aed492a8b1875bb5613df2aaace7df3bef5 100644
--- a/src/main/java/org/caosdb/server/entity/Message.java
+++ b/src/main/java/org/caosdb/server/entity/Message.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.entity;
 
diff --git a/src/main/java/org/caosdb/server/entity/StatementStatus.java b/src/main/java/org/caosdb/server/entity/StatementStatus.java
index b3d7a613d26eba432308272b3bd932d2fdb54b26..0a7971751bffcdfc0323835433fc006bf5269ec9 100644
--- a/src/main/java/org/caosdb/server/entity/StatementStatus.java
+++ b/src/main/java/org/caosdb/server/entity/StatementStatus.java
@@ -23,13 +23,20 @@
 package org.caosdb.server.entity;
 
 /**
- * The statement status has two purposes.
+ * The statement status has two purposes:
  *
- * <p>1. Storing the importance of an entity (any of OBLIGATORY, RECOMMENDED, SUGGESTED, or FIX). 2.
- * Marking an entity as a REPLACEMENT which is needed for flat representation of deeply nested
- * properties. This constant is only used for internal processes and has no meaning in the API. That
- * is also the reason why this enum is not called "Importance". Apart from that, in most cases its
- * meaning is identical to the importance of an entity.
+ * <p>1. Storing the importance of an entity (any of ``OBLIGATORY``, ``RECOMMENDED``, ``SUGGESTED``,
+ * or ``FIX``). ``OBLIGATORY``, ``RECOMMENDED``, and ``SUGGESTED`` specify whether this Entity must,
+ * should or may be present, and whether the server will throw an error, a warning or nothing,
+ * respectively, if it is missing. The importance is also used to specify the level of inheritance
+ * whereby ``FIX`` means that the entity is never inherited. See <a
+ * href="../../../../../specification/RecordType.html">the documentation of RecordTypes</a> for more
+ * information.
+ *
+ * <p>2. Marking an entity as a ``REPLACEMENT`` which is needed for flat representation of deeply
+ * nested properties. This constant is only used for internal processes and has no meaning in the
+ * API. That is also the reason why this enum is not called "Importance". Apart from that, in most
+ * cases its meaning is identical to the importance of an entity.
  *
  * @author Timm Fitschen (t.fitschen@indiscale.com)
  */
diff --git a/src/main/java/org/caosdb/server/entity/UpdateEntity.java b/src/main/java/org/caosdb/server/entity/UpdateEntity.java
index 884632b5ed3f92d740f2eea69ed49ef77511fd1b..221888b3da57694e5eabb64d0955cd6fca5e0b0e 100644
--- a/src/main/java/org/caosdb/server/entity/UpdateEntity.java
+++ b/src/main/java/org/caosdb/server/entity/UpdateEntity.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.entity;
 
diff --git a/src/main/java/org/caosdb/server/entity/WritableEntity.java b/src/main/java/org/caosdb/server/entity/WritableEntity.java
index ab8ccbb995430e267e84d1f2a235c0d928e6762d..22ffd6e869e871cf568e87363e5ead762e444400 100644
--- a/src/main/java/org/caosdb/server/entity/WritableEntity.java
+++ b/src/main/java/org/caosdb/server/entity/WritableEntity.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.entity;
 
diff --git a/src/main/java/org/caosdb/server/entity/container/WritableContainer.java b/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
index 5fdb8c39ec7f01280278bec0a1f2928bfed7f5c2..fcc8de4e7a44ddcfff1e85b282c4f70311c80f34 100644
--- a/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
+++ b/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.entity.container;
 
diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java
index 86ba49fabeedf04833f4e82214876be698aaa8cd..2de6b08537d82504bb989a4cf661f89ff473e4c2 100644
--- a/src/main/java/org/caosdb/server/jobs/Job.java
+++ b/src/main/java/org/caosdb/server/jobs/Job.java
@@ -1,11 +1,10 @@
 /*
- * ** 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) 2020 IndiScale GmbH <info@indiscale.com>
- * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020-2021 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
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.jobs;
 
@@ -40,15 +37,17 @@ import org.caosdb.server.database.backend.transaction.IsSubType;
 import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.database.backend.transaction.RetrieveParents;
 import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity;
-import org.caosdb.server.database.backend.transaction.RuleLoader;
+import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.datatype.AbstractCollectionDatatype;
 import org.caosdb.server.datatype.AbstractDatatype;
 import org.caosdb.server.datatype.ReferenceDatatype2;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.container.ParentContainer;
 import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.jobs.core.Mode;
+import org.caosdb.server.entity.wrapper.Parent;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
 import org.caosdb.server.transaction.Transaction;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -60,13 +59,20 @@ import org.reflections.Reflections;
  * @todo Describe me.
  */
 public abstract class Job {
-  private Transaction<? extends TransactionContainer> transaction = null;
-  private Mode mode = null;
+  /** All known Job classes, by name (actually lowercase getSimpleName()). */
+  static HashMap<String, Class<? extends Job>> allClasses = null;
 
-  public abstract JobTarget getTarget();
+  private final JobConfig jobConfig = JobConfig.getInstance();
+
+  private static List<Class<? extends Job>> loadAlways;
 
+  private Transaction<? extends TransactionContainer> transaction = null;
+  private JobFailureSeverity failureSeverity = null;
+  private final TransactionStage stage;
   private EntityInterface entity = null;
 
+  public abstract JobTarget getTarget();
+
   protected <S, T> HashMap<S, T> getCache(final String name) {
     return getTransaction().getCache(name);
   }
@@ -90,7 +96,7 @@ public abstract class Job {
   protected ScheduledJob appendJob(final EntityInterface entity, final Class<? extends Job> clazz) {
     try {
       final Job job = clazz.getDeclaredConstructor().newInstance();
-      job.init(getMode(), entity, getTransaction());
+      job.init(getFailureSeverity(), entity, getTransaction());
       return getTransaction().getSchedule().add(job);
     } catch (InstantiationException
         | IllegalAccessException
@@ -112,17 +118,17 @@ public abstract class Job {
 
   protected Job() {
     if (this.getClass().isAnnotationPresent(JobAnnotation.class)) {
-      this.time = this.getClass().getAnnotation(JobAnnotation.class).time();
+      this.stage = this.getClass().getAnnotation(JobAnnotation.class).stage();
     } else {
-      this.time = JobExecutionTime.CHECK;
+      this.stage = TransactionStage.CHECK;
     }
   }
 
   public final Job init(
-      final Mode mode,
+      final JobFailureSeverity severity,
       final EntityInterface entity,
       final Transaction<? extends TransactionContainer> transaction) {
-    this.mode = mode;
+    this.failureSeverity = severity;
     this.entity = entity;
     this.transaction = transaction;
     return this;
@@ -140,14 +146,85 @@ public abstract class Job {
     return this.entity;
   }
 
-  protected final Mode getMode() {
-    return this.mode;
+  protected final JobFailureSeverity getFailureSeverity() {
+    return this.failureSeverity;
+  }
+
+  protected final void setFailureSeverity(final JobFailureSeverity severiy) {
+    this.failureSeverity = severiy;
   }
 
   TransactionContainer getContainer() {
     return getTransaction().getContainer();
   }
 
+  /**
+   * Check if an entity ('child') is a direct or indirect child of another entity ('targetParent').
+   *
+   * <p>This assumes that the parent has either an id (persistent or temporary) or a name.
+   *
+   * <p>If the targetParent is not in the set of parents of the child, this method iterates through
+   * the direct parents of the child.
+   *
+   * <p>If both entities are part of this transaction, they are resolved within this transaction.
+   * Otherwise they are fetched from the database backend or the whole evaluation takes place in the
+   * backend (if both have persistent ids and are not part of this transaction).
+   *
+   * <p>If both entities have the same id or name, the return value is true without any further
+   * checks.
+   *
+   * @param child the child entity
+   * @param targetParent the parent entity
+   * @return true iff targetParent is a direct or indirect parent of the child or when the ids or
+   *     names match.
+   * @throws Message
+   */
+  protected final boolean isSubType(final EntityInterface child, final EntityInterface targetParent)
+      throws EntityWasNotUniqueException {
+    if (targetParent.hasId()) {
+      if (targetParent.getId().equals(child.getId())) {
+        return true;
+      }
+
+    } else if (targetParent.hasName()) {
+      if (targetParent.getName().equals(child.getName())) {
+        return true;
+      }
+    }
+
+    // check direct parents of child
+    ParentContainer directParents;
+    if (child.hasParents()) {
+      directParents = child.getParents();
+    } else {
+      directParents = resolve(child).getParents();
+    }
+    for (final Parent directParent : directParents) {
+      EntityInterface resolvedDirectParent = null;
+      if (directParent.hasId()) {
+        resolvedDirectParent = getEntityById(directParent.getId());
+      } else if (directParent.hasName()) {
+        resolvedDirectParent = getEntityByName(directParent.getName());
+      }
+
+      if (resolvedDirectParent != null) {
+        if (isSubType(resolvedDirectParent, targetParent)) {
+          return true;
+        }
+      } else if (directParent.hasId()) {
+        if (isValidSubType(directParent.getId(), targetParent.getId())) {
+          return true;
+        }
+      } else if (directParent.hasName()) {
+        if (isValidSubType(resolve(directParent).getId(), targetParent.getId())) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
   protected final boolean isValidSubType(final int child, final int parent) {
     if (!"false".equals(getContainer().getFlags().get("cache"))) {
       final HashMap<String, Boolean> isSubTypeCache = getCache("isSubType");
@@ -182,17 +259,17 @@ public abstract class Job {
       // the targeted entity version is the entity after the transaction or the
       // entity without a specific version. Thus we have to fetch the entity
       // from the container if possible.
-      EntityInterface ret = getEntityById(id);
+      final EntityInterface ret = getEntityById(id);
       if (ret != null) {
         return ret;
       }
     } else if (version.startsWith("HEAD~")) {
-      EntityInterface entById = getEntityById(id);
+      final EntityInterface entById = getEntityById(id);
       if (entById != null && entById.getEntityStatus() != EntityStatus.VALID) {
         // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be
         // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD
         // *after* the successful transaction, so that it is consistent with subsequent retrieves.
-        int offset = Integer.parseInt(version.substring(5)) - 1;
+        final int offset = Integer.parseInt(version.substring(5)) - 1;
         if (offset == 0) {
           // special case HEAD~1
           resulting_version = "HEAD";
@@ -210,7 +287,7 @@ public abstract class Job {
     return ret;
   }
 
-  protected final EntityInterface retrieveValidEntity(Integer id) {
+  protected final EntityInterface retrieveValidEntity(final Integer id) {
     return execute(new RetrieveFullEntityTransaction(id)).getContainer().get(0);
   }
 
@@ -230,22 +307,33 @@ public abstract class Job {
     }
   }
 
-  static HashMap<String, Class<? extends Job>> allClasses = null;
-  private static List<Class<? extends Job>> loadAlways;
-
+  /**
+   * Create a Job object with the given parameters.
+   *
+   * <p>This static method is used by other classes to create Job objects, instead of the private
+   * constructor.
+   *
+   * @return The generated Job object.
+   */
   public static Job getJob(
       final String job,
-      final Mode mode,
+      final JobFailureSeverity severiy,
       final EntityInterface entity,
       final Transaction<? extends TransactionContainer> transaction) {
+    // Fill `allClasses` with available subclasses
     scanJobClasspath();
 
+    // Get matching class for Job and generate it.
     final Class<? extends Job> jobClass = allClasses.get(job.toLowerCase());
-    return getJob(jobClass, mode, entity, transaction);
+    return getJob(jobClass, severiy, entity, transaction);
   }
 
+  /**
+   * Initialize {@code allClasses} with all {@code Job} classes found in the classpath.
+   *
+   * @todo Details when this has any effect.
+   */
   private static void scanJobClasspath() {
-
     if (allClasses == null || loadAlways == null) {
       allClasses = new HashMap<>();
       loadAlways = new ArrayList<>();
@@ -300,14 +388,12 @@ public abstract class Job {
       return new ArrayList<Job>();
     }
     if (dt instanceof ReferenceDatatype2) {
-      final RuleLoader t = new RuleLoader(0, 17, entity, transaction);
-      return execute(t).getJobs();
+      return jobConfig.getConfiguredJobs(0, 17, entity, transaction);
     } 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 execute(t).getJobs();
+      return jobConfig.getConfiguredJobs(0, dt.getId(), entity, transaction);
     } else {
       return null;
     }
@@ -318,42 +404,48 @@ public abstract class Job {
 
     final ArrayList<Job> jobs = new ArrayList<>();
     // load permanent jobs
-    for (Class<? extends Job> j : loadAlways) {
+    for (final Class<? extends Job> j : loadAlways) {
       if (EntityJob.class.isAssignableFrom(j)
           && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) {
-        jobs.add(getJob(j, Mode.MUST, entity, transaction));
+        jobs.add(getJob(j, JobFailureSeverity.ERROR, entity, transaction));
       }
     }
 
     // load general rules
-    {
-      final RuleLoader t = new RuleLoader(0, 0, entity, transaction);
-      jobs.addAll(execute(t).getJobs());
+    final List<Job> generalRules = jobConfig.getConfiguredJobs(0, 0, entity, transaction);
+    if (generalRules != null) {
+      jobs.addAll(generalRules);
     }
 
     // load Role specific rules
     if (entity.hasRole()) {
-      final RuleLoader t = new RuleLoader(0, entity.getRole().getId(), entity, transaction);
-      jobs.addAll(execute(t).getJobs());
+      final List<Job> roleRules =
+          jobConfig.getConfiguredJobs(0, entity.getRole().getId(), entity, transaction);
+      if (roleRules != null) {
+        jobs.addAll(roleRules);
+      }
     }
 
     // load data type specific rules
-    jobs.addAll(loadDataTypeSpecificJobs(entity, transaction));
+    final List<Job> datatypeRules = loadDataTypeSpecificJobs(entity, transaction);
+    if (datatypeRules != null) {
+      jobs.addAll(datatypeRules);
+    }
 
     return jobs;
   }
 
   private static Job getJob(
-      Class<? extends Job> jobClass,
-      Mode mode,
-      EntityInterface entity,
-      Transaction<? extends TransactionContainer> transaction) {
+      final Class<? extends Job> jobClass,
+      final JobFailureSeverity severity,
+      final EntityInterface entity,
+      final Transaction<? extends TransactionContainer> transaction) {
     Job ret;
     try {
 
       if (jobClass != null) {
         ret = jobClass.getDeclaredConstructor().newInstance();
-        ret.init(mode, entity, transaction);
+        ret.init(severity, entity, transaction);
         return ret;
       }
       return null;
@@ -377,7 +469,7 @@ public abstract class Job {
     // load flag jobs
     if (!entity.getFlags().isEmpty()) {
       for (final String key : entity.getFlags().keySet()) {
-        final Job j = getJob(key, Mode.MUST, entity, transaction);
+        final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction);
         if (j != null) {
           if (j instanceof FlagJob) {
             ((FlagJob) j).setValue(entity.getFlag(key));
@@ -392,7 +484,7 @@ public abstract class Job {
       for (final EntityInterface p : entity.getParents()) {
         if (!p.getFlags().isEmpty()) {
           for (final String key : p.getFlags().keySet()) {
-            final Job j = getJob(key, Mode.MUST, entity, transaction);
+            final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction);
             if (j != null) {
               if (j instanceof FlagJob) {
                 ((FlagJob) j).setValue(p.getFlag(key));
@@ -411,7 +503,7 @@ public abstract class Job {
       for (final EntityInterface p : entity.getProperties()) {
         if (!p.getFlags().isEmpty()) {
           for (final String key : p.getFlags().keySet()) {
-            final Job j = getJob(key, Mode.MUST, entity, transaction);
+            final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction);
             if (j != null) {
               if (j instanceof FlagJob) {
                 ((FlagJob) j).setValue(p.getFlag(key));
@@ -444,7 +536,7 @@ public abstract class Job {
         + "-"
         + (getEntity() != null ? getEntity().toString() : "NOENTITY")
         + " #"
-        + getMode().toString()
+        + getFailureSeverity().toString()
         + "]";
   }
 
@@ -452,19 +544,52 @@ public abstract class Job {
     System.out.println(toString());
   }
 
-  private final JobExecutionTime time;
+  public TransactionStage getTransactionStage() {
+    return this.stage;
+  }
+
+  /**
+   * Resolve an entity (which might only be specified by it's name or id) to its full representation
+   * (with properties, parents and all).
+   *
+   * @param entity the entity to be resolved.
+   * @return the resolved entity.
+   * @throws EntityWasNotUniqueException if the resolution failed due to ambuiguity of the name.
+   */
+  protected EntityInterface resolve(final EntityInterface entity)
+      throws EntityWasNotUniqueException {
+    EntityInterface resolvedEntity = null;
+    if (!entity.hasId() && entity.hasName()) {
+      resolvedEntity = getEntityByName(entity.getName());
+      if (resolvedEntity == null) {
+        final Integer eid = retrieveValidIDByName(entity.getName());
+        entity.setId(eid);
+      }
+    }
+
+    if (entity.hasId()) {
+      // get entity from container
+      resolvedEntity = getEntityById(entity.getId());
+      if (resolvedEntity == null && entity.getId() > 0) {
+        resolvedEntity = retrieveValidEntity(entity.getId());
+      }
+    }
 
-  public JobExecutionTime getExecutionTime() {
-    return this.time;
+    return resolvedEntity;
   }
 
-  public static List<Job> loadPermanentContainerJobs(Transaction<?> transaction) {
+  /**
+   * Return those matching jobs which are annotated with the "loadAlways" attribute.
+   *
+   * @return A list with the jobs.
+   */
+  public static List<Job> loadPermanentContainerJobs(final Transaction<?> transaction) {
     final ArrayList<Job> jobs = new ArrayList<>();
-    // load permanent jobs
-    for (Class<? extends Job> j : loadAlways) {
+    // load permanent jobs: ContainerJob classes with the correct transaction
+    for (final Class<? extends Job> j : loadAlways) {
       if (ContainerJob.class.isAssignableFrom(j)
           && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) {
-        jobs.add(getJob(j, Mode.MUST, null, transaction));
+        jobs.add(getJob(j, JobFailureSeverity.ERROR, null, transaction));
       }
     }
     return jobs;
diff --git a/src/main/java/org/caosdb/server/jobs/JobAnnotation.java b/src/main/java/org/caosdb/server/jobs/JobAnnotation.java
index 15193dcdd590745773f308c71b9ed3bb1351a413..bebebd75ed386c19f8abfb7c09e802dda5ec2941 100644
--- a/src/main/java/org/caosdb/server/jobs/JobAnnotation.java
+++ b/src/main/java/org/caosdb/server/jobs/JobAnnotation.java
@@ -26,9 +26,17 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import org.caosdb.server.transaction.TransactionInterface;
 
+/**
+ * Jobs may be annotated with @JobAnnotation(...).
+ *
+ * <p>Without a JobAnnotation, the Job will run at the default {@link
+ * org.caosdb.server.transaction.Transaction#check() CHECK} stage.
+ *
+ * @see {@link TransactionStage}
+ */
 @Retention(RetentionPolicy.RUNTIME)
 public @interface JobAnnotation {
-  JobExecutionTime time() default JobExecutionTime.CHECK;
+  TransactionStage stage() default TransactionStage.CHECK;
 
   String flag() default "";
 
diff --git a/src/main/java/org/caosdb/server/jobs/JobConfig.java b/src/main/java/org/caosdb/server/jobs/JobConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..732a985a78b5f67c88d1c01dff1565e887aab13c
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/JobConfig.java
@@ -0,0 +1,203 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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/>.
+ */
+
+package org.caosdb.server.jobs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.ServerProperties;
+import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
+import org.caosdb.server.transaction.Transaction;
+import org.caosdb.server.utils.ConfigurationException;
+
+/**
+ * Configuration of the jobs which have to run during all entity transactions with "job rules".
+ *
+ * <p>A job rule contains of a quintuple (domain, entity, transaction type, job name, job failure
+ * severity).
+ *
+ * <p>The domain and entity define for which entities the job (of the given name) should be loaded.
+ * The transaction type (e.g. Insert, Update, Delete) defines the transaction where this rule
+ * applies. The job failure severity sets the related property of a job.
+ *
+ * <p>The configuration is being read by default from a config file {@link
+ * ServerProperties#KEY_JOB_RULES_CONFIG}
+ *
+ * <p>This class works as a singleton, most of the time. It is possible to instanciate it otherwise,
+ * though.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public class JobConfig {
+
+  private static JobConfig instance;
+
+  static {
+    try {
+      instance = new JobConfig();
+    } catch (final Exception e) {
+      e.printStackTrace();
+      // CaosDB Server cannot work without a proper job configuration.
+      System.exit(1);
+    }
+  }
+
+  /**
+   * The keys of these rules consist of the domain, entity and transaction. The value is a pair (job
+   * name, job failure severity).
+   */
+  private final Map<String, List<Object[]>> rules;
+
+  public JobConfig() throws FileNotFoundException, IOException {
+    this(new File(CaosDBServer.getServerProperty(ServerProperties.KEY_JOB_RULES_CONFIG)));
+  }
+
+  public JobConfig(final File config) throws FileNotFoundException, IOException {
+    this.rules = getRulesFromCSV(config);
+  }
+
+  /**
+   * Read the rules from the CSV file.
+   *
+   * @param file
+   * @return A map with all the rules.
+   * @throws FileNotFoundException
+   * @throws IOException
+   */
+  private Map<String, List<Object[]>> getRulesFromCSV(final File file)
+      throws FileNotFoundException, IOException {
+    try (final BufferedReader reader = new BufferedReader(new FileReader(file))) {
+      final Map<String, List<Object[]>> result = new HashMap<>();
+      reader
+          .lines()
+          .forEach(
+              line -> {
+                // cut away comments, trim, then split into columns
+                final String[] row = line.replaceFirst("#.*$", "").strip().split("\\s*,\\s*");
+                if (row.length == 1 && row[0].isBlank()) {
+                  // comment or empty line
+                  // ignore
+                  return;
+                }
+                addRule(result, row);
+              });
+      return result;
+    }
+  }
+
+  /** Generate a unique key (for class-internal use only). */
+  private String getKey(final Integer domain, final Integer entity, final String transaction) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append("<");
+    sb.append(domain);
+    sb.append(",");
+    sb.append(transaction.toLowerCase());
+    sb.append(",");
+    sb.append(entity);
+    sb.append(">");
+    return sb.toString();
+  }
+
+  /**
+   * Read a rule from the csv-row and put it into the 'result' map
+   *
+   * @param result map with all the rules.
+   * @param row represents a single rule as a comma-separated quintuple.
+   */
+  private void addRule(final Map<String, List<Object[]>> result, final String[] row) {
+    if (row.length != 5) {
+      throw new ConfigurationException(
+          "Could not parse the job rules. Lines of five comma-separated values expected");
+    }
+    try {
+      final Integer domain = Integer.parseInt(row[0]);
+      final Integer entity = Integer.parseInt(row[1]);
+      final String transaction = row[2];
+      final String job = row[3];
+      final JobFailureSeverity severity = JobFailureSeverity.valueOf(row[4]);
+      final String key = getKey(domain, entity, transaction);
+      if (!result.containsKey(key)) {
+        result.put(key, new ArrayList<>());
+      }
+      result.get(key).add(new Object[] {job, severity});
+
+    } catch (final Exception e) {
+      throw new ConfigurationException("Could not parse the job rules.", e);
+    }
+  }
+
+  /**
+   * Factory method for the instanciation and configuration of jobs for a specific entity.
+   *
+   * @param domain the domain of the rule
+   * @param entity the entity of the rule (this might be a particular datatype or a role, like
+   *     "RecordType")
+   * @param target the entity for which the job will run.
+   * @param transaction the transaction for which the job will run.
+   * @return Fresh job instances, one for each matching job rule.
+   */
+  public List<Job> getConfiguredJobs(
+      final Integer domain,
+      final Integer entity,
+      final EntityInterface target,
+      final Transaction<? extends TransactionContainer> transaction) {
+    String transactionType;
+    if (target instanceof Property) {
+      transactionType = getTransactionType(((Property) target).getDomainEntity());
+    } else {
+      transactionType = getTransactionType(target);
+    }
+    final String key = getKey(domain, entity, transactionType);
+    final List<Object[]> jobRules = rules.get(key);
+    if (jobRules != null) {
+      final List<Job> result = new LinkedList<>();
+      jobRules.forEach(
+          config -> {
+            result.add(
+                Job.getJob(
+                    (String) config[0], (JobFailureSeverity) config[1], target, transaction));
+          });
+      return result;
+    }
+    return null;
+  }
+
+  public static JobConfig getInstance() {
+    return instance;
+  }
+
+  /** Return the type of transaction of an entity, e.g. "Retrieve" for a {@link RetrieveEntity}. */
+  String getTransactionType(final EntityInterface e) {
+    return e.getClass().getSimpleName().replace("Entity", "");
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/JobExecutionTime.java b/src/main/java/org/caosdb/server/jobs/JobExecutionTime.java
deleted file mode 100644
index 9374ab9b1c2f4c285b538b1721441f0bc7272600..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/jobs/JobExecutionTime.java
+++ /dev/null
@@ -1,35 +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 org.caosdb.server.jobs;
-
-public enum JobExecutionTime {
-  INIT,
-  PRE_CHECK,
-  CHECK,
-  POST_CHECK,
-  PRE_TRANSACTION,
-  TRANSACTION,
-  POST_TRANSACTION,
-  CLEANUP,
-  ROLL_BACK
-}
diff --git a/src/main/java/org/caosdb/server/jobs/Schedule.java b/src/main/java/org/caosdb/server/jobs/Schedule.java
index 77bc57c9e3f4183bf572b6a95dfe16fc79b6e003..f33400099a951aa8fbcf1975208988085813a8b8 100644
--- a/src/main/java/org/caosdb/server/jobs/Schedule.java
+++ b/src/main/java/org/caosdb/server/jobs/Schedule.java
@@ -1,9 +1,10 @@
 /*
- * ** 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) 2020-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.jobs;
 
@@ -30,13 +29,20 @@ import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import org.caosdb.server.entity.EntityInterface;
 
+/**
+ * Keeps track of Jobs.
+ *
+ * <p>The Schedule class orders jobs by {@link TransactionStage} and also assures that jobs are
+ * skipped when appropriate and to prevent that jobs run more than once (because sometimes they
+ * trigger each other).
+ */
 public class Schedule {
 
   private final Map<Integer, List<ScheduledJob>> jobLists = new HashMap<>();
   private ScheduledJob running = null;
 
   public List<ScheduledJob> addAll(final Collection<Job> jobs) {
-    List<ScheduledJob> result = new ArrayList<ScheduledJob>(jobs.size());
+    final List<ScheduledJob> result = new ArrayList<ScheduledJob>(jobs.size());
     for (final Job j : jobs) {
       result.add(add(j));
     }
@@ -44,18 +50,19 @@ public class Schedule {
   }
 
   public ScheduledJob add(final Job j) {
-    ScheduledJob ret = new ScheduledJob(j);
-    List<ScheduledJob> jobs = jobLists.get(ret.getExecutionTime().ordinal());
+    final ScheduledJob ret = new ScheduledJob(j);
+    List<ScheduledJob> jobs = jobLists.get(ret.getTransactionStage().ordinal());
     if (jobs == null) {
       jobs = new CopyOnWriteArrayList<ScheduledJob>();
-      jobLists.put(ret.getExecutionTime().ordinal(), jobs);
+      jobLists.put(ret.getTransactionStage().ordinal(), jobs);
     }
     jobs.add(ret);
     return ret;
   }
 
-  public void runJobs(final JobExecutionTime time) {
-    List<ScheduledJob> jobs = this.jobLists.get(time.ordinal());
+  /** Run all Jobs from the specified {@link TransactionStage}. */
+  public void runJobs(final TransactionStage stage) {
+    final List<ScheduledJob> jobs = this.jobLists.get(stage.ordinal());
     if (jobs != null) {
       for (final ScheduledJob scheduledJob : jobs) {
         runJob(scheduledJob);
@@ -81,11 +88,14 @@ public class Schedule {
     }
   }
 
+  /** Run all scheduled Jobs of a given class for the given entity. */
   public void runJob(final EntityInterface entity, final Class<? extends Job> jobclass) {
-    List<ScheduledJob> jobs =
+
+    // the jobs of this class are in the jobList for the TransactionStage of the jobClass.
+    final List<ScheduledJob> jobs =
         jobclass.isAnnotationPresent(JobAnnotation.class)
-            ? this.jobLists.get(jobclass.getAnnotation(JobAnnotation.class).time().ordinal())
-            : this.jobLists.get(JobExecutionTime.CHECK.ordinal());
+            ? this.jobLists.get(jobclass.getAnnotation(JobAnnotation.class).stage().ordinal())
+            : this.jobLists.get(TransactionStage.CHECK.ordinal());
     for (final ScheduledJob scheduledJob : jobs) {
       if (jobclass.isInstance(scheduledJob.job)) {
         if (scheduledJob.job.getEntity() == entity) {
diff --git a/src/main/java/org/caosdb/server/jobs/ScheduledJob.java b/src/main/java/org/caosdb/server/jobs/ScheduledJob.java
index ee0c29805d7500987f5823f2c7d3eaec8f6ed50b..3affdfd21421961edc4721c25d80f392d914d1bb 100644
--- a/src/main/java/org/caosdb/server/jobs/ScheduledJob.java
+++ b/src/main/java/org/caosdb/server/jobs/ScheduledJob.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
@@ -17,8 +16,6 @@
  *
  * 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 org.caosdb.server.jobs;
@@ -54,10 +51,12 @@ public class ScheduledJob {
     return this.job.toString();
   }
 
+  /** Does not actually start the job, but only sets the startTime. */
   private void start() {
     this.startTime = System.currentTimeMillis();
   }
 
+  /** Calculate and set the runtime, and add the measurement. */
   private void finish() {
     this.runtime += System.currentTimeMillis() - this.startTime;
     this.job
@@ -78,8 +77,9 @@ public class ScheduledJob {
     return this.startTime != -1;
   }
 
-  public JobExecutionTime getExecutionTime() {
-    return this.job.getExecutionTime();
+  /** Return the state of the inner Job. */
+  public TransactionStage getTransactionStage() {
+    return this.job.getTransactionStage();
   }
 
   public boolean skip() {
diff --git a/src/main/java/org/caosdb/server/jobs/TransactionStage.java b/src/main/java/org/caosdb/server/jobs/TransactionStage.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e15b4e556269def79d58e532bbe3c9e6cbb9bbd
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/TransactionStage.java
@@ -0,0 +1,62 @@
+/*
+ * 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) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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/>.
+ */
+package org.caosdb.server.jobs;
+
+import org.caosdb.server.jobs.core.Strict;
+import org.caosdb.server.transaction.Transaction;
+import org.caosdb.server.utils.UndoHandler;
+
+/**
+ * Any {@link Transaction} of an Entity consists of sequence of stages. Jobs which have a {@link
+ * JobAnnotation} can specify the transaction stage in which they are to be executed. By default,
+ * any job is executed during the CHECK stage.
+ * <li>INIT - The transaction is being initialized (create schedule, aquire locks (one writing
+ *     thread, many reading threads permitted).
+ * <li>PRE_CHECK - Prepare entities (e.g. check if any updates are to be processed, load/generate
+ *     acl, cast objects into more specialized classes.)
+ * <li>CHECK - Do the actual consistency checking.
+ * <li>POST_CHECK - Do more consistency checking (reserved for those jobs which need the normal
+ *     checks to be done already, e.g. the {@link Strict} job).
+ * <li>PRE_TRANSACTION - Prepare the entities for the transaction (e.g. for paging, translate the
+ *     state messages into state properties). Also, in this stage, the full read and write lock is
+ *     aquired.
+ * <li>TRANSACTION - Do the actual transaction.
+ * <li>POST_TRANSACTION - Post-process entities (Success messages, write history, transform stage
+ *     properties into messages).
+ * <li>CLEANUP - Release all locks, remove temporary files, clean-up the {@link UndoHandler}s.
+ * <li>ROLL_BACK - In case an error occured in any of the stages, this special stage rolls-back any
+ *     changes and calls the {@link UndoHandler#undo()} of the {@link UndoHandler}s.
+ *
+ * @see {@link Transaction#execute()}.
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public enum TransactionStage {
+  INIT,
+  PRE_CHECK,
+  CHECK,
+  POST_CHECK,
+  PRE_TRANSACTION,
+  TRANSACTION,
+  POST_TRANSACTION,
+  CLEANUP,
+  ROLL_BACK
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/AccessControl.java b/src/main/java/org/caosdb/server/jobs/core/AccessControl.java
index d1a2113a2f87018cec63d381cce64ad6c8ad09f2..408384f416e1249501d5383ea1028ced56914ded 100644
--- a/src/main/java/org/caosdb/server/jobs/core/AccessControl.java
+++ b/src/main/java/org/caosdb/server/jobs/core/AccessControl.java
@@ -29,12 +29,12 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.jobs.ContainerJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.Retrieve;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
-@JobAnnotation(time = JobExecutionTime.INIT)
+@JobAnnotation(stage = TransactionStage.INIT)
 public class AccessControl extends ContainerJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/Atomic.java b/src/main/java/org/caosdb/server/jobs/core/Atomic.java
index 45b5de8e4d5835bd835a797290e2d3c875687e44..8639615ee6ac4ddd648a19d15262a1c526ef10c6 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Atomic.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Atomic.java
@@ -26,7 +26,7 @@ import org.caosdb.server.entity.Entity;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.ContainerJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.WriteTransaction;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
@@ -34,7 +34,7 @@ import org.caosdb.server.utils.Observer;
 import org.caosdb.server.utils.ServerMessages;
 
 @JobAnnotation(
-    time = JobExecutionTime.PRE_TRANSACTION,
+    stage = TransactionStage.PRE_TRANSACTION,
     transaction = WriteTransaction.class,
     loadAlways = true)
 public class Atomic extends ContainerJob implements Observer {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
index f46013d278f268b95b1b105130e1eabd74f3a9f4..f9c7704c8bd0ff00214b41d5793c8fdb15bcc61a 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
@@ -82,9 +82,12 @@ public final class CheckDatatypePresent extends EntityJob {
 
         // run jobsreturn this.entities;
         final List<Job> datatypeJobs = loadDataTypeSpecificJobs();
-        List<ScheduledJob> scheduledJobs = getTransaction().getSchedule().addAll(datatypeJobs);
-        for (final ScheduledJob job : scheduledJobs) {
-          getTransaction().getSchedule().runJob(job);
+        if (datatypeJobs != null) {
+          final List<ScheduledJob> scheduledJobs =
+              getTransaction().getSchedule().addAll(datatypeJobs);
+          for (final ScheduledJob job : scheduledJobs) {
+            getTransaction().getSchedule().runJob(job);
+          }
         }
       } else {
 
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java
index fc3a5114f240c796c114f92f8e6271e4fd3bd9ac..e430a0817e9083013330bc4d59d8f7377d10d62a 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java
@@ -35,12 +35,12 @@ public class CheckDescPresent extends EntityJob {
   @Override
   public final void run() {
     if (!getEntity().hasDescription()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_DESCRIPTION);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_DESCRIPTION);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java
index f1600ce42117ab5f37fccd33b79d821efeccb774..3166057c95671273efe27357ce11b2ffe10e2c78 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java
@@ -35,12 +35,12 @@ public class CheckNamePresent extends EntityJob {
   @Override
   public final void run() {
     if (!getEntity().hasName()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_NAME);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_NAME);
           break;
         default:
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java
index b04a9151ab5c142e97a6efc9b19607896ab38daf..fb6919649242a906b36526e3725fc25e710c9d60 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java
@@ -45,7 +45,7 @@ public class CheckNoAdditionalPropertiesPresent extends EntityJob {
   }
 
   private void addMessage(EntityInterface property, Message message) {
-    if (getMode() == Mode.MUST) {
+    if (getFailureSeverity() == JobFailureSeverity.ERROR) {
       property.addError(message);
     } else {
       property.addWarning(message);
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java
index e439354e88f39675491682877dd27aa54fd7a3ef..d6cae3ee40222d3b3d6eb89e1b2d0c8bce1da0e5 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java
@@ -28,7 +28,7 @@ public class CheckNoOverridesPresent extends EntityJob {
   }
 
   private void addMessage(Property p, Message message) {
-    if (getMode() == Mode.MUST) {
+    if (getFailureSeverity() == JobFailureSeverity.ERROR) {
       p.addError(message);
     } else {
       p.addWarning(message);
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
index 7aca2bac3dd3709006497f9ce8f556f267c4ca81..9588a3056e15f7fb3847db37468bb6a628741203 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,14 +18,15 @@
  *
  * 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 org.caosdb.server.jobs.core;
 
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.Message.MessageType;
 import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.wrapper.Parent;
+import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -32,96 +34,113 @@ import org.caosdb.server.utils.ServerMessages;
 /**
  * Check whether an entity implements all obligatory properties of all parents.
  *
- * @author tf
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
 public class CheckParOblPropPresent extends EntityJob {
-  @Override
-  public final void run() {
 
-    // This test needs the valid/qualified ids of all properties.
-    // Thus, run a CheckPropValid first.
-    runJobFromSchedule(getEntity(), CheckPropValid.class);
+  public static final String OBL_IMPORTANCE_FLAG_KEY = "force-missing-obligatory";
+  public static final Message ENTITY_NOT_UNIQUE =
+      new Message(
+          MessageType.Error, 0, "Could not check importance. Parent was not uniquely resolvable.");
+  public static final Message ILLEGAL_FLAG_VALUE =
+      new Message(MessageType.Warning, "Illegal value for flag 'force-missing-obligatory'.");
 
-    // This test needs the valid/qualified parents. If a parent is only
-    // referenced by name it has to be translated into a (hopefully) unique
-    // id.
-    runJobFromSchedule(getEntity(), CheckParValid.class);
-
-    if (getEntity().hasParents()) {
-      execute(new RetrieveFullEntityTransaction(getEntity().getParents()));
+  @Override
+  public final void run() {
+    if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED
+        || getEntity() instanceof Property
+        || getEntity() instanceof Parent) {
+      return;
     }
-
-    // inherit properties
-    runJobFromSchedule(getEntity(), Inheritance.class);
-
-    // loop over all parents
     if (getEntity().hasParents()) {
-      for (final EntityInterface parent : getEntity().getParents()) {
 
-        // if the parent has an id>0, it is to be a valid entity.
-        if (parent.hasId() && parent.getId() > 0) {
-          if (parent.getEntityStatus() == EntityStatus.NONEXISTENT) {
-            continue;
-          }
+      handleImportanceFlags();
+      if (getFailureSeverity() == JobFailureSeverity.IGNORE) {
+        // importance is to be ignored
+        return;
+      }
 
-          // does this parent have properties?
-          if (parent.hasProperties()) {
+      // inherit properties
+      runJobFromSchedule(getEntity(), Inheritance.class);
 
-            // loop over all properties of this parent
-            outerLoop:
-            for (final EntityInterface parentProperty : parent.getProperties()) {
+      // get importance-flags
 
-              // only obligatory properties are of interest.
-              if (parentProperty.hasStatementStatus()
-                  && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) {
+      // loop over all parents
+      for (final EntityInterface parent : getEntity().getParents()) {
+        final EntityInterface resolvedParent = resolve(parent);
+        checkObligatoryPropertiesMissing(resolvedParent);
+      }
+    }
+  }
+
+  /**
+   * Check if all obligatory properties of the given parent are present.
+   *
+   * <p>Add an error or warning if not.
+   *
+   * @param parent
+   * @throws Message
+   */
+  private void checkObligatoryPropertiesMissing(final EntityInterface parent) {
+    // does this parent have properties?
+    if (parent.hasProperties()) {
 
-                // loop over all properties of the entity
-                innerLoop:
-                for (final EntityInterface entityProperty : getEntity().getProperties()) {
+      // loop over all properties of this parent
+      outerLoop:
+      for (final EntityInterface parentProperty : parent.getProperties()) {
 
-                  if (!entityProperty.hasId()) {
-                    // continue inner loop means that this
-                    // is not the entityProperty in
-                    // question, continue search for an
-                    // entityProperty.
-                    continue innerLoop;
-                  }
+        // only obligatory properties are of interest.
+        if (parentProperty.hasStatementStatus()
+            && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) {
 
-                  // TODO sub type instead of equals!!!
-                  if (parentProperty.getId().equals(entityProperty.getId())) {
-                    // continue outer loop means that we
-                    // found an entityProperty that
-                    // implements the obligatory
-                    // parentProperty, continue with the
-                    // other parentProperties.
-                    continue outerLoop;
-                  }
-                }
+          // loop over all properties of the entity
+          for (final EntityInterface entityProperty : getEntity().getProperties()) {
 
-                // No entityProperty has been found which
-                // implements this parentProperty. Add the
-                // respective messages.
-                switch (getMode()) {
-                  case MUST:
-                    // TODO add information WHICH property is
-                    // missing.
-                    getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
-                    break;
-                  case SHOULD:
-                    // TODO add information WHICH property is
-                    // missing.
-                    getEntity().addWarning(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
-                    break;
-                  default:
-                    break;
-                }
-              }
+            if (isSubType(entityProperty, parentProperty)) {
+              // continue outer loop means that we
+              // found an entityProperty that
+              // implements the obligatory
+              // parentProperty, continue with the
+              // other parentProperties.
+              continue outerLoop;
             }
           }
-        } else {
-          // TODO add processing of new parents, new properties
+
+          // No entityProperty has been found which
+          // implements this parentProperty. Add the
+          // respective messages.
+          switch (getFailureSeverity()) {
+            case ERROR:
+              // TODO add information WHICH property is
+              // missing.
+              getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
+              break;
+            case WARN:
+              // TODO add information WHICH property is
+              // missing.
+              getEntity().addWarning(ServerMessages.OBLIGATORY_PROPERTY_MISSING);
+              break;
+            default:
+              // should never be reached, but ignoring is the way to go anyway
+              break;
+          }
         }
       }
     }
   }
+
+  /** Look at the entity's importance flag first, at the container's flag second. */
+  private void handleImportanceFlags() {
+    final String globalFlag =
+        getTransaction().getContainer().getFlags().get(OBL_IMPORTANCE_FLAG_KEY);
+    final String entityFlag =
+        getEntity().getFlags().getOrDefault(OBL_IMPORTANCE_FLAG_KEY, globalFlag);
+    if (entityFlag != null) {
+      try {
+        setFailureSeverity(JobFailureSeverity.valueOf(entityFlag.toUpperCase()));
+      } catch (final IllegalArgumentException e) {
+        getEntity().addWarning(ILLEGAL_FLAG_VALUE);
+      }
+    }
+  }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java
index 8851456c236c0afd9f922f187089adc5f4e35ab1..058710c6bbbf5c2bbd3f95f7ae70a243d3bfcd20 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java
@@ -35,12 +35,12 @@ public class CheckParPresent extends EntityJob {
   @Override
   public final void run() {
     if (!getEntity().hasParents() || getEntity().getParents().isEmpty()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_PARENTS);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_PARENTS);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
index 02cb96fc1214712c7a8ef1bb98327ba9fb39e9d0..e663874189ee0301a5b30452468391711624b606 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
@@ -33,7 +33,7 @@ import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -43,7 +43,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
-@JobAnnotation(time = JobExecutionTime.PRE_CHECK)
+@JobAnnotation(stage = TransactionStage.PRE_CHECK)
 public class CheckParValid extends EntityJob {
   @Override
   public final void run() {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java
index 31a460971bc80ccf61d775c96b4632fa38f14f66..cc23d980152c44cfdbf88500e9041044a0418034 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java
@@ -35,12 +35,12 @@ public class CheckPropPresent extends EntityJob {
   @Override
   public final void run() {
     if (getEntity().getProperties().isEmpty()) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_PROPERTIES);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_PROPERTIES);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
index f95104bd2f2552a642e7e4bcd2fc13f7fd2788d4..390deedde211c0931eca1c3677ac5ff9c8ee9d8f 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
@@ -32,7 +32,7 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -42,7 +42,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
-@JobAnnotation(time = JobExecutionTime.PRE_CHECK)
+@JobAnnotation(stage = TransactionStage.PRE_CHECK)
 public class CheckPropValid extends EntityJob {
   @Override
   public final void run() {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java
index cd187cf17ad72c5431fcd3f2eb0b95890941faf9..1a159910044a079baba3b64c62176883ad31b8ef 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
@@ -17,8 +16,6 @@
  *
  * 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 org.caosdb.server.jobs.core;
@@ -30,7 +27,7 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Message.MessageType;
 import org.caosdb.server.entity.UpdateEntity;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.WriteTransaction;
 import org.caosdb.server.utils.ServerMessages;
 
@@ -40,9 +37,9 @@ import org.caosdb.server.utils.ServerMessages;
  * <p>This job checks if the attempted state transition is in compliance with the state model. This
  * job runs during the CHECK phase and should do all necessary consistency and permission checks.
  *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
-@JobAnnotation(time = JobExecutionTime.POST_CHECK, transaction = WriteTransaction.class)
+@JobAnnotation(stage = TransactionStage.POST_CHECK, transaction = WriteTransaction.class)
 public class CheckStateTransition extends EntityStateJob {
 
   private static final String PERMISSION_STATE_FORCE_FINAL = "STATE:FORCE:FINAL";
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java
index aa87ecfff69635407bc90d372c89f089e4f308dc..5e3e6120fadef8f4b519ec694ebbf7588d7c2488 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java
@@ -40,12 +40,12 @@ public class CheckUnitPresent extends EntityJob {
     runJobFromSchedule(getEntity(), Inheritance.class);
 
     if (!hasUnit(getEntity())) {
-      switch (getMode()) {
-        case MUST:
+      switch (getFailureSeverity()) {
+        case ERROR:
           getEntity().addError(ServerMessages.ENTITY_HAS_NO_UNIT);
           getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
           break;
-        case SHOULD:
+        case WARN:
           getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_UNIT);
         default:
           break;
diff --git a/src/main/java/org/caosdb/server/jobs/core/DebugCalls.java b/src/main/java/org/caosdb/server/jobs/core/DebugCalls.java
index 8527560d02e498bf3812162a8b01b9641aad7260..8e0ae7435693ba1010f023da6c585911825f5a5e 100644
--- a/src/main/java/org/caosdb/server/jobs/core/DebugCalls.java
+++ b/src/main/java/org/caosdb/server/jobs/core/DebugCalls.java
@@ -25,11 +25,11 @@ package org.caosdb.server.jobs.core;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 
 @JobAnnotation(
     flag = "debug",
-    time = JobExecutionTime.INIT,
+    stage = TransactionStage.INIT,
     values = {DebugCalls.ex_null_p},
     description =
         "This job for debugging and is available in debug mode only. Otherwise the flag is ignored. It throws an exception on purpose.",
diff --git a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java
index c20b18b6e4950c07967d2cfcdd776c95c25f4a53..e2f59a8827a682df508a26176a45a9fc1a874b4e 100644
--- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java
+++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
@@ -17,8 +16,6 @@
  *
  * 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 org.caosdb.server.jobs.core;
@@ -81,7 +78,7 @@ import org.jdom2.Element;
  * <p>Only the 4th job ({@link MakeStateMessage}) runs during Retrieve transitions. During
  * WriteTransactions all four jobs do run.
  *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
 public abstract class EntityStateJob extends EntityJob {
 
diff --git a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java
index 7f5444b36104526101ebf9b3f2aa547b3d91cbc9..399039a072d2fd7b5f54566b2f448791169a356b 100644
--- a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java
+++ b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java
@@ -26,13 +26,13 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.query.Query;
 import org.caosdb.server.query.Query.ParsingException;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
-@JobAnnotation(flag = "query", time = JobExecutionTime.INIT)
+@JobAnnotation(flag = "query", stage = TransactionStage.INIT)
 public class ExecuteQuery extends FlagJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/History.java b/src/main/java/org/caosdb/server/jobs/core/History.java
index 5594b5afdb841436dbdd63f96b8e2c44faef51c6..d39a6e1f570400194b3a530c3c323a92d0374b7c 100644
--- a/src/main/java/org/caosdb/server/jobs/core/History.java
+++ b/src/main/java/org/caosdb/server/jobs/core/History.java
@@ -27,7 +27,7 @@ import org.caosdb.server.database.backend.transaction.RetrieveVersionHistory;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -37,7 +37,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author Timm Fitschen (t.fitschen@indiscale.com)
  */
-@JobAnnotation(time = JobExecutionTime.POST_TRANSACTION, flag = "H")
+@JobAnnotation(stage = TransactionStage.POST_TRANSACTION, flag = "H")
 public class History extends FlagJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
index bda4b8ebe885aa49df13ecf36ab14663c7ead270..b905bfa0fcbf98f107506d1fb1e7d2142b3b8657 100644
--- a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
+++ b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
@@ -1,32 +1,56 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 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/>.
+ */
 package org.caosdb.server.jobs.core;
 
 import java.util.List;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
-import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
+import org.caosdb.server.utils.EntityStatus;
 
-@JobAnnotation(time = JobExecutionTime.CHECK)
+@JobAnnotation(stage = TransactionStage.CHECK)
 public class InheritInitialState extends EntityStateJob {
 
   @Override
   protected void run() {
+    if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED) {
+      return;
+    }
     try {
-      State parentState = getFirstParentState();
+      final State parentState = getFirstParentState();
       if (parentState != null) {
         getEntity().addMessage(parentState);
         parentState.getStateEntity();
         getEntity().setEntityACL(parentState.getStateACL());
       }
-    } catch (Message e) {
+    } catch (final Message e) {
       getEntity().addError(e);
     }
   }
 
   private State getFirstParentState() throws Message {
-    for (Parent par : getEntity().getParents()) {
-      List<Property> stateProperties = getStateProperties(par, false);
+    for (EntityInterface par : getEntity().getParents()) {
+      par = resolve(par);
+      final List<Property> stateProperties = getStateProperties(par, false);
       if (stateProperties.size() > 0) {
         return createState(stateProperties.get(0));
       }
diff --git a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
index 107c5768a5c15fbb05ea6d68b0cb956cbb235687..56ac72fab9f980581cf755c5018dad48ad841f96 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -40,7 +42,7 @@ import org.caosdb.server.utils.EntityStatus;
 /**
  * Add all those properties from the parent to the child which have the same importance (or higher).
  *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
 public class Inheritance extends EntityJob {
 
@@ -65,7 +67,7 @@ public class Inheritance extends EntityJob {
   @Override
   protected void run() {
     if (getEntity() instanceof InsertEntity || getEntity() instanceof UpdateEntity) {
-      if (getEntity().hasParents()) {
+      if (getEntity().getEntityStatus() == EntityStatus.QUALIFIED && getEntity().hasParents()) {
         final ArrayList<Property> transfer = new ArrayList<>();
         parentLoop:
         for (final EntityInterface parent : getEntity().getParents()) {
@@ -108,7 +110,7 @@ public class Inheritance extends EntityJob {
             }
           }
           // prop's Datatype might need to be resolved.
-          ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
+          final ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
           getTransaction().getSchedule().runJob(job);
 
           getEntity().addProperty(new Property(prop.getWrapped()));
@@ -133,12 +135,13 @@ public class Inheritance extends EntityJob {
               break propertyLoop;
             }
 
-            runJobFromSchedule(getEntity(), CheckPropValid.class);
-
             EntityInterface validProperty = new Entity(property.getId());
             if (getEntity().hasParents()) {
               outer:
-              for (final EntityInterface par : getEntity().getParents()) {
+              for (EntityInterface par : getEntity().getParents()) {
+                if (!par.hasProperties()) {
+                  par = resolve(par);
+                }
                 for (final EntityInterface prop : par.getProperties()) {
                   if (validProperty.hasId() && validProperty.getId().equals(prop.getId())) {
                     validProperty = prop;
@@ -166,7 +169,7 @@ public class Inheritance extends EntityJob {
               }
             }
             // prop's Datatype might need to be resolved.
-            ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
+            final ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
             getTransaction().getSchedule().runJob(job);
 
             property.addProperty(new Property(prop.getWrapped()));
@@ -187,7 +190,9 @@ public class Inheritance extends EntityJob {
    * @param inheritance
    */
   private void collectInheritedProperties(
-      List<Property> transfer, EntityInterface from, INHERITANCE_MODE inheritance) {
+      final List<Property> transfer,
+      final EntityInterface from,
+      final INHERITANCE_MODE inheritance) {
     if (from.hasProperties()) {
       for (final Property propProperty : from.getProperties()) {
         switch (inheritance) {
diff --git a/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java
index 7dbff695a342e5dcd2a19a2d72116c14fba78a6c..5cd6d67aa7eca6a231eecf627606f272ac0f0775 100644
--- a/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java
+++ b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
@@ -17,10 +16,7 @@
  *
  * 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 org.caosdb.server.jobs.core;
 
 import java.util.ArrayList;
@@ -39,7 +35,7 @@ import org.caosdb.server.entity.UpdateEntity;
 import org.caosdb.server.entity.WritableEntity;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.WriteTransaction;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
@@ -53,11 +49,11 @@ import org.caosdb.server.utils.Observer;
  * have a state anymore but the previous version had, because it has to be checked if the stateModel
  * allows to leave in this state.
  *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
 @JobAnnotation(
     loadAlways = true,
-    time = JobExecutionTime.INIT,
+    stage = TransactionStage.INIT,
     transaction = WriteTransaction.class)
 public class InitEntityStateJobs extends EntityStateJob implements Observer {
 
diff --git a/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java b/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java
index 35ce49297291accd41a7c293fc909cc0a27f9317..8133e4659d55caf29faa56eeb80ced194cd26cfc 100644
--- a/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java
+++ b/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java
@@ -46,7 +46,7 @@ import org.caosdb.server.entity.Role;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.Job;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.Retrieve;
 import org.caosdb.server.transaction.WriteTransactionInterface;
 import org.caosdb.server.utils.EntityStatus;
@@ -57,7 +57,7 @@ import org.caosdb.server.utils.Utils;
 @JobAnnotation(
     flag = "InsertFilesInDir",
     loadOnDefault = false,
-    time = JobExecutionTime.INIT,
+    stage = TransactionStage.INIT,
     description =
         "For expert users only! Risk of creating spam records!\nValue of this flag might be any directory on the servers local file system which is part of the server's back-end file storage. This job will insert every readable, nonhidden file in said directory into the database and link the file with a symlink. This is useful to add a huge amount of files without actully copying them to the back-end file storage. If you call this job on a directory more than once every file that was recently added to the source directory is inserted. Every yet known file is left untouched. \nOptional parameter -e EXCLUDE: A regular expression of files which are to be ignored. \n Optional parameter -i INCLUDE: a regular expression of files which are to be included. By default, all files are included. The -e takes precedence. \nOptional parameter -p PREFIX: Stores all new files into the directory PREFIX in the server's file system.\nOptional parameter --force-allow-symlinks: Simlinks in your data are a source of problems for the database. Therefore, simlinks are ignored by default. This option allows symlinks (but still generates simlink warnings). \nPrepend/Dry run: Call this flag with a retrieve transaction (HTTP GET) and it will only count all files and list them without actually inserting them.")
 public class InsertFilesInDir extends FlagJob {
diff --git a/src/main/java/org/caosdb/server/jobs/core/Mode.java b/src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java
similarity index 51%
rename from src/main/java/org/caosdb/server/jobs/core/Mode.java
rename to src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java
index 561a4b0c5fcb4e2c8c66a2b9f9a02ea91c44c119..e95bef34b7b9bea8e1b2b2126da196c1c7c53c12 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Mode.java
+++ b/src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2021 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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,9 +24,22 @@
  */
 package org.caosdb.server.jobs.core;
 
-public enum Mode {
-  MUST,
-  SHOULD,
-  SHOULDNT,
-  MUSTNOT
+/**
+ * Describes the severity of a failure of a job (esp. consitency checks).
+ *
+ * <ul>
+ *   <li>{@link #ERROR} - a failure throws an error and the transaction will not succeed.
+ *   <li>{@link #WARN} - a failure produces a warning but the transaction may succeed anyway.
+ *   <li>{@link #IGNORE} - the job may fail silently. Jobs can also decide to not run at all if that
+ *       is not necessary in this case.
+ *       <p>Jobs where this doesn't make sense at all may also ignore the severity. It is rather a
+ *       convenient way to configure jobs where this makes sense than a definitive normative
+ *       property of a job.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public enum JobFailureSeverity {
+  ERROR,
+  WARN,
+  IGNORE,
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
index 89f381d648144e274325c5c71a57c9f4daf04895..c04fd21d588c9a9a2f6e59ce7c1fff445a225c67 100644
--- a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
+++ b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
@@ -27,16 +27,16 @@ import org.caosdb.server.jobs.ContainerJob;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.Job;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 
-@JobAnnotation(time = JobExecutionTime.INIT)
+@JobAnnotation(stage = TransactionStage.INIT)
 public class LoadContainerFlagJobs extends ContainerJob {
 
   @Override
   protected void run() {
     if (getContainer().getFlags() != null) {
       for (final Entry<String, String> flag : getContainer().getFlags().entrySet()) {
-        final Job j = Job.getJob(flag.getKey(), Mode.MUST, null, getTransaction());
+        final Job j = Job.getJob(flag.getKey(), JobFailureSeverity.ERROR, null, getTransaction());
         if (j != null) {
           if (j instanceof FlagJob) {
             ((FlagJob) j).setValue(flag.getValue());
diff --git a/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java
index 6e98704a5f6d73199993f2ed531e6d6277934e64..d497a2ab5d955f0d6db0205b360a1a56ef90a4b7 100644
--- a/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java
+++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
@@ -17,10 +16,7 @@
  *
  * 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 org.caosdb.server.jobs.core;
 
 import java.util.List;
@@ -30,7 +26,7 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.Retrieve;
 import org.jdom2.Element;
 
@@ -46,7 +42,7 @@ import org.jdom2.Element;
 @JobAnnotation(
     loadAlways = true,
     transaction = Retrieve.class,
-    time = JobExecutionTime.POST_TRANSACTION)
+    stage = TransactionStage.POST_TRANSACTION)
 public class MakeStateMessage extends EntityStateJob {
 
   public static final String SPARSE_FLAG = "sparseState";
diff --git a/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java
index 56682921563d77f462903567a2c128fcd5d92555..c2aebc681b38a9902bede69883cce4025bc8afe6 100644
--- a/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java
+++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java
@@ -25,14 +25,14 @@ package org.caosdb.server.jobs.core;
 
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.WriteTransaction;
 
 /**
  * This job constructs an ordinary Property from the State right before the entity is being written
  * to the back-end and after any checks run.
  */
-@JobAnnotation(transaction = WriteTransaction.class, time = JobExecutionTime.PRE_TRANSACTION)
+@JobAnnotation(transaction = WriteTransaction.class, stage = TransactionStage.PRE_TRANSACTION)
 public class MakeStateProperty extends EntityStateJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/NoCache.java b/src/main/java/org/caosdb/server/jobs/core/NoCache.java
index a4f639392ac63a97f2f025dfe6717a664114c561..e408330ee2639d1c5d7dc5f145848e292daa9b3b 100644
--- a/src/main/java/org/caosdb/server/jobs/core/NoCache.java
+++ b/src/main/java/org/caosdb/server/jobs/core/NoCache.java
@@ -24,7 +24,7 @@ package org.caosdb.server.jobs.core;
 
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 
 @JobAnnotation(
     flag = "cache",
@@ -32,7 +32,7 @@ import org.caosdb.server.jobs.JobExecutionTime;
     description =
         "Depending on the configuraion of the server some transactions with the backend database might be cached internally for performance reasons. Disable the internal caching for this transaction with 'false'. Note: You cannot enable the caching with 'true', if the server is not configured to use caching.",
     values = {"true", "false"},
-    time = JobExecutionTime.INIT)
+    stage = TransactionStage.INIT)
 public class NoCache extends FlagJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/Paging.java b/src/main/java/org/caosdb/server/jobs/core/Paging.java
index 43683639f75c08be394a38ce03e91282405f333d..5f2a6ed62f48f20d09c34c8ee34190231903ffe7 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Paging.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Paging.java
@@ -27,11 +27,11 @@ import java.util.regex.Pattern;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.transaction.Retrieve;
 import org.caosdb.server.utils.EntityStatus;
 
-@JobAnnotation(flag = "P", time = JobExecutionTime.PRE_TRANSACTION)
+@JobAnnotation(flag = "P", stage = TransactionStage.PRE_TRANSACTION)
 public class Paging extends FlagJob {
 
   public static final int DEFAULT_LENGTH = 100;
diff --git a/src/main/java/org/caosdb/server/jobs/core/PickUp.java b/src/main/java/org/caosdb/server/jobs/core/PickUp.java
index b91fccf72d54022c6f6d19ba26c2b0974a54829f..94a80836c863ea226a88e035201a2e2fb9fd1039 100644
--- a/src/main/java/org/caosdb/server/jobs/core/PickUp.java
+++ b/src/main/java/org/caosdb/server/jobs/core/PickUp.java
@@ -30,12 +30,12 @@ import org.caosdb.server.entity.FileProperties;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
 import org.caosdb.server.utils.Observer;
 
-@JobAnnotation(time = JobExecutionTime.INIT)
+@JobAnnotation(stage = TransactionStage.INIT)
 public class PickUp extends EntityJob implements Observer {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java b/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java
index e7e8f0e4d6aab3f3d0b3e9392d408a88aaa0abcc..1cc12c6102f66dda224360554e4ac5c515ae418d 100644
--- a/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java
+++ b/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java
@@ -35,7 +35,7 @@ import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
@@ -44,7 +44,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
-@JobAnnotation(time = JobExecutionTime.PRE_TRANSACTION)
+@JobAnnotation(stage = TransactionStage.PRE_TRANSACTION)
 public class ProcessNameProperties extends EntityJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/RetrieveACL.java b/src/main/java/org/caosdb/server/jobs/core/RetrieveACL.java
index f59b2c1ec94de846714575537516c41d18bf4629..4664c01b88d361485c342a8fcddca2ab082f0dee 100644
--- a/src/main/java/org/caosdb/server/jobs/core/RetrieveACL.java
+++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveACL.java
@@ -27,7 +27,7 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.transaction.Retrieve;
 import org.caosdb.server.utils.EntityStatus;
@@ -35,7 +35,7 @@ import org.caosdb.server.utils.ServerMessages;
 import org.jdom2.Element;
 
 @JobAnnotation(
-    time = JobExecutionTime.POST_TRANSACTION,
+    stage = TransactionStage.POST_TRANSACTION,
     flag = "ACL",
     description = "Adds an XML representation of the EntityACL to the output of this entity.")
 public class RetrieveACL extends FlagJob {
diff --git a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java
index 603d861ad24b659fab262291d888420c02f6bcf5..7cd7791e0b27f1ec1d5e67e047e7f1cbe177948c 100644
--- a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java
+++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java
@@ -26,9 +26,9 @@ import org.caosdb.server.database.backend.transaction.RetrieveAll;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 
-@JobAnnotation(flag = "all", time = JobExecutionTime.INIT)
+@JobAnnotation(flag = "all", stage = TransactionStage.INIT)
 public class RetrieveAllJob extends FlagJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/RetrieveIdOnlyFlag.java b/src/main/java/org/caosdb/server/jobs/core/RetrieveIdOnlyFlag.java
index 1712950dff9403df54f26457d0b6a763d7ce4de8..844468c5473b4d2dff6e59777f925f2701505749 100644
--- a/src/main/java/org/caosdb/server/jobs/core/RetrieveIdOnlyFlag.java
+++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveIdOnlyFlag.java
@@ -25,10 +25,10 @@ package org.caosdb.server.jobs.core;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.utils.EntityStatus;
 
-@JobAnnotation(flag = "IdOnly", time = JobExecutionTime.PRE_TRANSACTION)
+@JobAnnotation(flag = "IdOnly", stage = TransactionStage.PRE_TRANSACTION)
 public class RetrieveIdOnlyFlag extends FlagJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/RetrieveOwner.java b/src/main/java/org/caosdb/server/jobs/core/RetrieveOwner.java
index 7377805805ec55e2c3c3928cc42b0c053d8a9c9d..8b6412cee5d6e800a78509741d4f3680403008ce 100644
--- a/src/main/java/org/caosdb/server/jobs/core/RetrieveOwner.java
+++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveOwner.java
@@ -28,7 +28,7 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.permissions.ResponsibleAgent;
 import org.caosdb.server.transaction.Retrieve;
@@ -37,7 +37,7 @@ import org.caosdb.server.utils.ServerMessages;
 import org.jdom2.Element;
 
 @JobAnnotation(
-    time = JobExecutionTime.POST_TRANSACTION,
+    stage = TransactionStage.POST_TRANSACTION,
     flag = "owner",
     description = "Adds an XML representation of the owner(s) to the output of this entity.")
 public class RetrieveOwner extends FlagJob {
diff --git a/src/main/java/org/caosdb/server/jobs/core/RetriveAllNames.java b/src/main/java/org/caosdb/server/jobs/core/RetriveAllNames.java
index dfd69a5cb5a40a6e6be35e345f17e38f3dbb7e2e..b5787246cbafba0df31056d2da15adb330ef0c50 100644
--- a/src/main/java/org/caosdb/server/jobs/core/RetriveAllNames.java
+++ b/src/main/java/org/caosdb/server/jobs/core/RetriveAllNames.java
@@ -4,7 +4,7 @@ import org.caosdb.server.database.backend.transaction.GetAllNames;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 
@@ -15,7 +15,7 @@ import org.caosdb.server.utils.EntityStatus;
  * <p>The entites' status are set to VALID because the entities parents and properties do not have
  * to be retrieved.
  */
-@JobAnnotation(flag = "names", time = JobExecutionTime.INIT)
+@JobAnnotation(flag = "names", stage = TransactionStage.INIT)
 public class RetriveAllNames extends FlagJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/Strict.java b/src/main/java/org/caosdb/server/jobs/core/Strict.java
index aebbecf86a89476339dbdef8892b074aebc8196d..bb69063c116e5c14aa9fad2b0bbf881d2481b394 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Strict.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Strict.java
@@ -27,11 +27,11 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.jobs.ContainerJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
-@JobAnnotation(time = JobExecutionTime.POST_CHECK)
+@JobAnnotation(stage = TransactionStage.POST_CHECK)
 public class Strict extends ContainerJob {
   // TODO load strict job via unified flag interface
 
diff --git a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java
index 300e32bf1662e4990f52bda0570a65c05e5d60b8..2836ccf742ebbd1bbda5a8ed3ae95ac0d69c2fbb 100644
--- a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java
+++ b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java
@@ -27,10 +27,11 @@ import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
-@JobAnnotation(flag = "uniquename")
+@JobAnnotation(flag = "uniquename", stage = TransactionStage.PRE_CHECK)
 public class UniqueName extends FlagJob {
 
   private void doCheck(final EntityInterface entity) {
diff --git a/src/main/java/org/caosdb/server/jobs/core/UpdateUnitConverters.java b/src/main/java/org/caosdb/server/jobs/core/UpdateUnitConverters.java
index b3ef80adb64fcf3c1007a33a64bde691c9ff9c1d..0b71d2102c98188d875d52d3bbc3e0f79a23043b 100644
--- a/src/main/java/org/caosdb/server/jobs/core/UpdateUnitConverters.java
+++ b/src/main/java/org/caosdb/server/jobs/core/UpdateUnitConverters.java
@@ -25,12 +25,12 @@ package org.caosdb.server.jobs.core;
 import org.caosdb.server.database.backend.transaction.InsertLinCon;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.unit.Converter;
 import org.caosdb.unit.LinearConverter;
 import org.caosdb.unit.Unit;
 
-@JobAnnotation(time = JobExecutionTime.PRE_TRANSACTION)
+@JobAnnotation(stage = TransactionStage.PRE_TRANSACTION)
 public class UpdateUnitConverters extends EntityJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java b/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java
index c8cd93e4a1d12125235819b4a2ba69fa12bd0256..d5d818fd42b6a9b65af07df8bab627be2aeb1551 100644
--- a/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java
+++ b/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.permissions;
 
diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4
index 71b41d480bc171aa02af3b2e61eadd16345b3c6d..a091027c40abafe60b307367929f1736b62ac046 100644
--- a/src/main/java/org/caosdb/server/query/CQLLexer.g4
+++ b/src/main/java/org/caosdb/server/query/CQLLexer.g4
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,40 +18,38 @@
  *
  * 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
  */
 lexer grammar CQLLexer;
 
 AS_A:
-	[Aa][Ss] (WHITE_SPACE_f? A)? WHITE_SPACE_f?
+    [Aa][Ss] (WHITE_SPACE_f? A)? WHITE_SPACE_f?
 ;
 
 IS_REFERENCED:
-	(IS_f WHITE_SPACE_f?)? [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee][Dd] WHITE_SPACE_f?
+    (IS_f WHITE_SPACE_f?)? [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee][Dd] WHITE_SPACE_f?
 ;
 
 BY:
-	[Bb][Yy] WHITE_SPACE_f?
+    [Bb][Yy] WHITE_SPACE_f?
 ;
 
 fragment
 OF_f:
-	[Oo][Ff]
+    [Oo][Ff]
 ;
 
 fragment
 ANY_f:
-	[Aa][Nn][Yy]
+    [Aa][Nn][Yy]
 ;
 
 fragment
 VERSION_f:
-	[Vv][Ee][Rr][Ss][Ii][Oo][Nn]
+    [Vv][Ee][Rr][Ss][Ii][Oo][Nn]
 ;
 
 ANY_VERSION_OF:
-	(ANY_f WHITE_SPACE_f VERSION_f WHITE_SPACE_f OF_f) WHITE_SPACE_f?
+    (ANY_f WHITE_SPACE_f VERSION_f WHITE_SPACE_f OF_f) WHITE_SPACE_f?
 ;
 
 SELECT:
@@ -58,27 +57,27 @@ SELECT:
 ;
 
 INSERTED:
-	[Ii][Nn][Ss][Ee][Rr][Tt][Ee][Dd] WHITE_SPACE_f?
+    [Ii][Nn][Ss][Ee][Rr][Tt][Ee][Dd] WHITE_SPACE_f?
 ;
 
 CREATED:
-	[Cc][Rr][Ee][Aa][Tt][Ee][Dd] WHITE_SPACE_f?
+    [Cc][Rr][Ee][Aa][Tt][Ee][Dd] WHITE_SPACE_f?
 ;
 
 UPDATED:
-	[Uu][Pp][Dd][Aa][Tt][Ee][Dd] WHITE_SPACE_f?
+    [Uu][Pp][Dd][Aa][Tt][Ee][Dd] WHITE_SPACE_f?
 ;
 
 ON:
-	[Oo][Nn] WHITE_SPACE_f?
+    [Oo][Nn] WHITE_SPACE_f?
 ;
 
 IN:
-	[Ii][Nn] WHITE_SPACE_f?
+    [Ii][Nn] WHITE_SPACE_f?
 ;
 
 AFTER:
-	[Aa][Ff][Tt][Ee][Rr] WHITE_SPACE_f?
+    [Aa][Ff][Tt][Ee][Rr] WHITE_SPACE_f?
 ;
 
 BEFORE:
@@ -90,258 +89,258 @@ UNTIL:
 ;
 
 SINCE:
-	[Ss][Ii][Nn][Cc][Ee] WHITE_SPACE_f?
+    [Ss][Ii][Nn][Cc][Ee] WHITE_SPACE_f?
 ;
 
 IS_STORED_AT:
-	(IS_f WHITE_SPACE_f?)? [Ss][Tt][Oo][Rr][Ee][Dd] (WHITE_SPACE_f? AT)? WHITE_SPACE_f?
+    (IS_f WHITE_SPACE_f?)? [Ss][Tt][Oo][Rr][Ee][Dd] (WHITE_SPACE_f? AT)? WHITE_SPACE_f?
 ;
 
 AT:
-	[Aa][Tt] WHITE_SPACE_f?
+    [Aa][Tt] WHITE_SPACE_f?
 ;
 
 FIND:
-	[Ff][Ii][Nn][Dd] WHITE_SPACE_f?
+    [Ff][Ii][Nn][Dd] WHITE_SPACE_f?
 ;
 
 COUNT:
-	[Cc][Oo][Uu][Nn][Tt] WHITE_SPACE_f?
+    [Cc][Oo][Uu][Nn][Tt] WHITE_SPACE_f?
 ;
-	
+
 AND:
-	(
-		(
-			[Aa][Nn][Dd]
-		)
-		| '&'
-	) WHITE_SPACE_f?
+    (
+        (
+            [Aa][Nn][Dd]
+        )
+        | '&'
+    ) WHITE_SPACE_f?
 ;
 
 OR:
-	(
-		(
-			[Oo][Rr]
-		)
-		| '|'
-	) WHITE_SPACE_f?
+    (
+        (
+            [Oo][Rr]
+        )
+        | '|'
+    ) WHITE_SPACE_f?
 ;
 
 LPAREN:
-	'(' WHITE_SPACE_f?
+    '(' WHITE_SPACE_f?
 ;
 
 RPAREN:
-	')' WHITE_SPACE_f?
+    ')' WHITE_SPACE_f?
 ;
 
 SINGLE_QUOTE_START:
-	'\'' -> pushMode(SINGLE_QUOTE_MODE)
+    '\'' -> pushMode(SINGLE_QUOTE_MODE)
 ;
 
 DOUBLE_QUOTE_START:
-	'"' -> pushMode(DOUBLE_QUOTE_MODE)
+    '"' -> pushMode(DOUBLE_QUOTE_MODE)
 ;
 
 OPERATOR:
-	'='
-	| '<'
-	| '<='
-	| '>='
-	| '>'
-	| '!='
-	| '->'
-	| [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee]([Ss]|WHITE_SPACE_f? [Tt][Oo]) (WHITE_SPACE_f? A {_input.LA(1) == ' '}?)? {setText("->");}
+    '='
+    | '<'
+    | '<='
+    | '>='
+    | '>'
+    | '!='
+    | '->'
+    | [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee]([Ss]|WHITE_SPACE_f? [Tt][Oo]) (WHITE_SPACE_f? A {_input.LA(1) == ' '}?)? {setText("->");}
 ;
 
 LIKE:
-	[Ll][Ii][Kk][Ee] WHITE_SPACE_f?
+    [Ll][Ii][Kk][Ee] WHITE_SPACE_f?
 ;
 
 IS_NULL:
-	IS_f WHITE_SPACE_f NULL_f WHITE_SPACE_f?
+    IS_f WHITE_SPACE_f NULL_f WHITE_SPACE_f?
 ;
 
 IS_NOT_NULL:
-	IS_f WHITE_SPACE_f NOT_f WHITE_SPACE_f NULL_f WHITE_SPACE_f?
+    IS_f WHITE_SPACE_f NOT_f WHITE_SPACE_f NULL_f WHITE_SPACE_f?
 ;
 
 fragment
 NULL_f:
-	[Nn][Uu][Ll][Ll]
+    [Nn][Uu][Ll][Ll]
 ;
 
 fragment
 DOES_f:
-	[Dd][Oo][Ee][Ss]
+    [Dd][Oo][Ee][Ss]
 ;
 
 fragment
 NOT_f:
-	[Nn][Oo][Tt]
+    [Nn][Oo][Tt]
 ;
 
 fragment
 DOESNT_f:
-	DOES_f WHITE_SPACE_f? NOT_f
-	| DOES_f [Nn] SINGLE_QUOTE [Tt]
+    DOES_f WHITE_SPACE_f? NOT_f
+    | DOES_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 ISNT_f:
-	IS_f [Nn] SINGLE_QUOTE [Tt]
+    IS_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 WERE_f:
-	[Ww][Ee][Rr][Ee]
+    [Ww][Ee][Rr][Ee]
 ;
 
 fragment
 WERENT_f:
-	WERE_f [Nn] SINGLE_QUOTE [Tt]
+    WERE_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 HAVENT_f:
-	HAVE_f [Nn] SINGLE_QUOTE [Tt]
+    HAVE_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 HADNT_f:
-	HAD_f [Nn] SINGLE_QUOTE [Tt]
+    HAD_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 HAD_f:
-	[Hh][Aa][Dd]
+    [Hh][Aa][Dd]
 ;
-	
+
 fragment
 HAVE_f:
-	[Hh][Aa][Vv][Ee]
+    [Hh][Aa][Vv][Ee]
 ;
 
 fragment
 HAS_f:
-	[Hh][Aa][Ss]
+    [Hh][Aa][Ss]
 ;
 
 fragment
 HASNT_f:
-	HAS_f [Nn] SINGLE_QUOTE [Tt]
+    HAS_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 BEEN_f:
-	[Bb][Ee][Ee][Nn]
+    [Bb][Ee][Ee][Nn]
 ;
 
 fragment
 HAVE_A_f:
-	HAVE_f (WHITE_SPACE? A)? 
+    HAVE_f (WHITE_SPACE? A)?
 ;
 
 fragment
 DO_f:
-	[Dd][Oo]
+    [Dd][Oo]
 ;
 
 fragment
 DONT_f:
-	DO_f NOT_f
-	| DO_f [Nn] SINGLE_QUOTE [Tt]
+    DO_f NOT_f
+    | DO_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 fragment
 WAS_f:
-	[Ww][Aa][Ss]
+    [Ww][Aa][Ss]
 ;
 
 fragment
 WASNT_f:
-	WAS_f [Nn] SINGLE_QUOTE [Tt]
+    WAS_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
 NEGATION:
-	(
-		'!'
-		| DOESNT_f (WHITE_SPACE? HAVE_A_f)?
-		| DONT_f (WHITE_SPACE? HAVE_A_f)?
-		| HASNT_f (WHITE_SPACE? BEEN_f)?
-		| ISNT_f (WHITE_SPACE? BEEN_f)?
-		| NOT_f (WHITE_SPACE? BEEN_f)?
-		| WERENT_f (WHITE_SPACE? BEEN_f)?
-		| WASNT_f (WHITE_SPACE? BEEN_f)?
-		| HAVENT_f (WHITE_SPACE? BEEN_f)?
-		| HADNT_f (WHITE_SPACE? BEEN_f)?
-	) WHITE_SPACE_f?
-;	
+    (
+        '!'
+        | DOESNT_f (WHITE_SPACE? HAVE_A_f)?
+        | DONT_f (WHITE_SPACE? HAVE_A_f)?
+        | HASNT_f (WHITE_SPACE? BEEN_f)?
+        | ISNT_f (WHITE_SPACE? BEEN_f)?
+        | NOT_f (WHITE_SPACE? BEEN_f)?
+        | WERENT_f (WHITE_SPACE? BEEN_f)?
+        | WASNT_f (WHITE_SPACE? BEEN_f)?
+        | HAVENT_f (WHITE_SPACE? BEEN_f)?
+        | HADNT_f (WHITE_SPACE? BEEN_f)?
+    ) WHITE_SPACE_f?
+;
 
 WITH:
-	[Ww][Ii][Tt][Hh] WHITE_SPACE_f?
+    [Ww][Ii][Tt][Hh] WHITE_SPACE_f?
 ;
 
-THE: 
-	[Tt][Hh][Ee] WHITE_SPACE_f?
+THE:
+    [Tt][Hh][Ee] WHITE_SPACE_f?
 ;
 
 GREATEST:
-	[Gg][Rr][Ee][Aa][Tt][Ee][Ss][Tt] WHITE_SPACE_f?
+    [Gg][Rr][Ee][Aa][Tt][Ee][Ss][Tt] WHITE_SPACE_f?
 ;
 
 SMALLEST:
-	[Ss][Mm][Aa][Ll][Ll][Ee][Ss][Tt] WHITE_SPACE_f?
+    [Ss][Mm][Aa][Ll][Ll][Ee][Ss][Tt] WHITE_SPACE_f?
 ;
 
 A:
-	[Aa][Nn]? WHITE_SPACE_f?
+    [Aa][Nn]? WHITE_SPACE_f?
 ;
 
 ME:
-	[Mm][Ee] WHITE_SPACE_f?
+    [Mm][Ee] WHITE_SPACE_f?
 ;
 
 SOMEONE:
-	[Ss][Oo][Mm][Ee][Oo][Nn][Ee] WHITE_SPACE_f?
+    [Ss][Oo][Mm][Ee][Oo][Nn][Ee] WHITE_SPACE_f?
 ;
 
 ELSE:
-	[Ee][Ll][Ss][Ee] WHITE_SPACE_f?
+    [Ee][Ll][Ss][Ee] WHITE_SPACE_f?
 ;
 
 WHERE:
-	[Ww][Hh][Ee][Rr][Ee] WHITE_SPACE_f?
+    [Ww][Hh][Ee][Rr][Ee] WHITE_SPACE_f?
 ;
 
 WHICH:
-	[Ww][Hh][Ii][Cc][Hh] WHITE_SPACE_f?
+    [Ww][Hh][Ii][Cc][Hh] WHITE_SPACE_f?
 ;
 
 HAS_A:
-	(
-		(HAS_f | HAD_f | HAVE_f | WERE_f | WAS_f | IS_f)
-		(
-			(WHITE_SPACE_f? A)
-			| (WHITE_SPACE_f? BEEN_f)
-		)?
-	) WHITE_SPACE_f?
+    (
+        (HAS_f | HAD_f | HAVE_f | WERE_f | WAS_f | IS_f)
+        (
+            (WHITE_SPACE_f? A)
+            | (WHITE_SPACE_f? BEEN_f)
+        )?
+    ) WHITE_SPACE_f?
 ;
 
 PROPERTY:
-	[Pp][Rr][Oo][Pp][Ee][Rr][Tt]([Yy]|[Ii][Ee][Ss]) WHITE_SPACE_f?
+    [Pp][Rr][Oo][Pp][Ee][Rr][Tt]([Yy]|[Ii][Ee][Ss]) WHITE_SPACE_f?
 ;
 
-RECORDTYPE:	
-	[Rr][Ee][Cc][Oo][Rr][Dd][Tt][Yy][Pp][Ee]([Ss])? WHITE_SPACE_f?
+RECORDTYPE:
+    [Rr][Ee][Cc][Oo][Rr][Dd][Tt][Yy][Pp][Ee]([Ss])? WHITE_SPACE_f?
 ;
 
 RECORD:
-	[Rr][Ee][Cc][Oo][Rr][Dd]([Ss])? WHITE_SPACE_f?
+    [Rr][Ee][Cc][Oo][Rr][Dd]([Ss])? WHITE_SPACE_f?
 ;
 
 FILE:
-	[Ff][Ii][Ll][Ee]([Ss])? WHITE_SPACE_f?
+    [Ff][Ii][Ll][Ee]([Ss])? WHITE_SPACE_f?
 ;
 
 ENTITY:
@@ -349,99 +348,99 @@ ENTITY:
 ;
 
 QUERYTEMPLATE:
-	[Qq][Uu][Ee][Rr][yY][Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee] WHITE_SPACE_f?
+    [Qq][Uu][Ee][Rr][yY][Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee] WHITE_SPACE_f?
 ;
 
 fragment
 IS_f:
-	[Ii][Ss]
+    [Ii][Ss]
 ;
 
 fragment
 WHITE_SPACE_f:
-	[ \t\n\r]+
+    [ \t\n\r]+
 ;
 
 WHITE_SPACE:
-	[ \t\n\r]+
+    [ \t\n\r]+
 ;
 
 fragment
 DOUBLE_QUOTE:
-	'"'
+    '"'
 ;
 
 fragment
 SINGLE_QUOTE:
-	'\''
+    '\''
 ;
 
 REGEXP_MARKER:
-	'#'
+    '#'
 ;
 
 REGEXP_BEGIN:
-	'<<'
+    '<<'
 ;
 
 REGEXP_END:
-	'>>'
+    '>>'
 ;
 
-ID: 
-	[Ii][Dd] WHITE_SPACE_f?
+ID:
+    [Ii][Dd] WHITE_SPACE_f?
 ;
 
 SLASH:
-	'/'
+    '/'
 ;
 
 STAR:
-	'*'
+    '*'
 ;
 
 DOT:
-	'.'
+    '.'
 ;
 
 QMARK:
-	'?' WHITE_SPACE_f?
+    '?' WHITE_SPACE_f?
 ;
 
 BUT:
-	[Bb][Uu][Tt] WHITE_SPACE_f?
+    [Bb][Uu][Tt] WHITE_SPACE_f?
 ;
 
 ESC_REGEXP_END:
-	ESC_MARKER
-	'>>' WHITE_SPACE_f?
+    ESC_MARKER
+    '>>' WHITE_SPACE_f?
 ;
 
 ESC_STAR:
-	ESC_MARKER
-	'*' WHITE_SPACE_f?
+    ESC_MARKER
+    '*' WHITE_SPACE_f?
 ;
 
 ESC_BS:
-	ESC_MARKER
-	'\\' WHITE_SPACE_f?
+    ESC_MARKER
+    '\\' WHITE_SPACE_f?
 ;
 
-fragment 
+fragment
 ESC_MARKER:
-	'\\'
+    '\\'
 ;
 
 TODAY:
-	[Tt][Oo][Dd][Aa][Yy] WHITE_SPACE_f?
+    [Tt][Oo][Dd][Aa][Yy] WHITE_SPACE_f?
 ;
 
 HYPHEN:
-	'-'
+    '-'
 ;
 
 COLON:
-	':'
+    ':'
 ;
 
 NUM:
@@ -451,53 +450,53 @@ NUM:
 ;
 
 NUM_f:
-	('0'..'9')+
+    ('0'..'9')+
 ;
 
 TXT:
-	('a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' {_input.LA(1) != '>'}? | '+' | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+
+    ('a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' {_input.LA(1) != '>'}? | '+' | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+
 ;
 
 UNKNOWN_CHAR: . ;
 
 mode SINGLE_QUOTE_MODE;
 
-	SINGLE_QUOTE_ESCAPED_CHAR:
-		ESC_MARKER
-		( '\'' | '\\' | '*' )
-	;
-
-	SINGLE_QUOTE_END:
-		'\'' -> mode(DEFAULT_MODE)
-	;
-	
-	SINGLE_QUOTE_STAR:
-		'*'
-	;
-
-	SINGLE_QUOTE_ANY_CHAR:
-		~('\''|'\\'|'*')+
-	;
-	
+    SINGLE_QUOTE_ESCAPED_CHAR:
+        ESC_MARKER
+        ( '\'' | '\\' | '*' )
+    ;
+
+    SINGLE_QUOTE_END:
+        '\'' -> mode(DEFAULT_MODE)
+    ;
+
+    SINGLE_QUOTE_STAR:
+        '*'
+    ;
+
+    SINGLE_QUOTE_ANY_CHAR:
+        ~('\''|'\\'|'*')+
+    ;
+
 mode DOUBLE_QUOTE_MODE;
 
-	DOUBLE_QUOTE_ESCAPED_CHAR:
-		ESC_MARKER
-		( '"' | '\\' | '*' )
-	;
+    DOUBLE_QUOTE_ESCAPED_CHAR:
+        ESC_MARKER
+        ( '"' | '\\' | '*' )
+    ;
+
+    DOUBLE_QUOTE_END:
+        '"' -> mode(DEFAULT_MODE)
+    ;
+
+    DOUBLE_QUOTE_STAR:
+        '*'
+    ;
 
-	DOUBLE_QUOTE_END:
-		'"' -> mode(DEFAULT_MODE)
-	;
-	
-	DOUBLE_QUOTE_STAR:
-		'*'
-	;
+    DOUBLE_QUOTE_ANY_CHAR:
+        ~('"'|'\\'|'*')+
+    ;
 
-	DOUBLE_QUOTE_ANY_CHAR:
-		~('"'|'\\'|'*')+
-	;
-	
 
 mode SELECT_MODE;
 
@@ -508,14 +507,11 @@ mode SELECT_MODE;
     SELECT_DOT:
         '.'
     ;
-    
+
     SELECT_COMMA:
         ','
     ;
-    
+
     SELECTOR_TXT:
-        . 
+        .
     ;
-    
-    
-    
diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4
index 6aea8c55f006cd9127d68f0c09e1654d9f9842a9..c4311ac1cbe03f490b79456e091cc0e73b94c506 100644
--- a/src/main/java/org/caosdb/server/query/CQLParser.g4
+++ b/src/main/java/org/caosdb/server/query/CQLParser.g4
@@ -1,10 +1,10 @@
 /*
- * ** 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) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com>
  * Copyright (C) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -19,8 +19,6 @@
  *
  * 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
  */
 parser grammar CQLParser;
 
@@ -32,38 +30,38 @@ options { tokenVocab = CQLLexer; }
 }
 
 cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r, EntityFilterInterface filter, VersionFilter v]
-	@init{
-	    $s = null;
-		$e = null;
-		$r = null;
-		$v = VersionFilter.UNVERSIONED;
-		$filter = null;
-	}
+    @init{
+        $s = null;
+        $e = null;
+        $r = null;
+        $v = VersionFilter.UNVERSIONED;
+        $filter = null;
+    }
 :
 
-	(
-	   SELECT prop_sel {$s = $prop_sel.s;} FROM {$t = Query.Type.FIND;}
-	   | FIND {$t = Query.Type.FIND;}
-	   | COUNT {$t = Query.Type.COUNT;})
-	(version {$v = $version.v;})?
-	( role {$r = $role.r;} )?
-	(
-		entity_filter {$filter = $entity_filter.filter;}
-		|
-		entity WHITE_SPACE?{$e = $entity.ep;}
-		(
-			entity_filter {$filter = $entity_filter.filter;}
-		)?
-	)?
-	EOF
+    (
+       SELECT prop_sel {$s = $prop_sel.s;} FROM {$t = Query.Type.FIND;}
+       | FIND {$t = Query.Type.FIND;}
+       | COUNT {$t = Query.Type.COUNT;})
+    (version {$v = $version.v;})?
+    ( role {$r = $role.r;} )?
+    (
+        entity_filter {$filter = $entity_filter.filter;}
+        |
+        entity WHITE_SPACE?{$e = $entity.ep;}
+        (
+            entity_filter {$filter = $entity_filter.filter;}
+        )?
+    )?
+    EOF
 ;
 
 version returns [VersionFilter v]
-	@init{
-		$v = null;
-	}
+    @init{
+        $v = null;
+    }
 :
-	ANY_VERSION_OF {$v = VersionFilter.ANY_VERSION;}
+    ANY_VERSION_OF {$v = VersionFilter.ANY_VERSION;}
 ;
 
 prop_sel returns [List<Query.Selection> s]
@@ -83,110 +81,110 @@ selector_txt:
     SELECTOR_TXT+
 ;
 
-role returns [Query.Role r]: 
-	RECORDTYPE {$r = Query.Role.RECORDTYPE;}
-	| RECORD {$r = Query.Role.RECORD;}
-	| PROPERTY {$r = Query.Role.PROPERTY;}
-	| FILE {$r = Query.Role.FILE;}
-	| QUERYTEMPLATE {$r = Query.Role.QUERYTEMPLATE;}
-	| ENTITY {$r = Query.Role.ENTITY;}
+role returns [Query.Role r]:
+    RECORDTYPE {$r = Query.Role.RECORDTYPE;}
+    | RECORD {$r = Query.Role.RECORD;}
+    | PROPERTY {$r = Query.Role.PROPERTY;}
+    | FILE {$r = Query.Role.FILE;}
+    | QUERYTEMPLATE {$r = Query.Role.QUERYTEMPLATE;}
+    | ENTITY {$r = Query.Role.ENTITY;}
 ;
 
 entity_filter returns [EntityFilterInterface filter]
-	@init{
-		$filter = null;
-	}
+    @init{
+        $filter = null;
+    }
 :
-	which_exp
-	(
-		(
-			LPAREN WHITE_SPACE?
-			( 
-				filter_expression {$filter = $filter_expression.efi;}
-				| conjunction {$filter = $conjunction.c;} 
-				| disjunction {$filter = $disjunction.d;}
-			)
-			RPAREN
-		) | (
-			filter_expression {$filter = $filter_expression.efi;}
-			| conjunction {$filter = $conjunction.c;}
-			| disjunction {$filter = $disjunction.d;}
-		)
-	)?
+    which_exp
+    (
+        (
+            LPAREN WHITE_SPACE?
+            (
+                filter_expression {$filter = $filter_expression.efi;}
+                | conjunction {$filter = $conjunction.c;}
+                | disjunction {$filter = $disjunction.d;}
+            )
+            RPAREN
+        ) | (
+            filter_expression {$filter = $filter_expression.efi;}
+            | conjunction {$filter = $conjunction.c;}
+            | disjunction {$filter = $disjunction.d;}
+        )
+    )?
 ;
 
 which_exp:
-	WHICH (HAS_A (PROPERTY)?)?
-	| HAS_A (PROPERTY)?
-	| WITH (A (PROPERTY)?)?
-	| WHERE
-	| DOT WHITE_SPACE?
+    WHICH (HAS_A (PROPERTY)?)?
+    | HAS_A (PROPERTY)?
+    | WITH (A (PROPERTY)?)?
+    | WHERE
+    | DOT WHITE_SPACE?
 ;
 
 filter_expression returns [EntityFilterInterface efi]
 :
-	backreference (subproperty {((Backreference) $backreference.ref).setSubProperty($subproperty.subp);})? {$efi = $backreference.ref;}
-	| idfilter {$efi=$idfilter.filter;}
-	| storedat {$efi=$storedat.filter;}
-	| transaction {$efi=$transaction.filter;}
-	| ( pov (subproperty {((POV) $pov.filter).setSubProperty($subproperty.subp);})? {$efi = $pov.filter;} )
-	| subproperty {$efi=$subproperty.subp;}
-	| negation {$efi=$negation.n;}
+    backreference (subproperty {((Backreference) $backreference.ref).setSubProperty($subproperty.subp);})? {$efi = $backreference.ref;}
+    | idfilter {$efi=$idfilter.filter;}
+    | storedat {$efi=$storedat.filter;}
+    | transaction {$efi=$transaction.filter;}
+    | ( pov (subproperty {((POV) $pov.filter).setSubProperty($subproperty.subp);})? {$efi = $pov.filter;} )
+    | subproperty {$efi=$subproperty.subp;}
+    | negation {$efi=$negation.n;}
 ;
 
 idfilter returns [IDFilter filter] locals [String o, String v, String a]
 @init{
-	$a = null;
-	$o = null;
-	$v = null;
-} 
+    $a = null;
+    $o = null;
+    $v = null;
+}
 @after{
-	$filter = new IDFilter($o,$v,$a);
+    $filter = new IDFilter($o,$v,$a);
 }
 :
-	(minmax {$a=$minmax.agg;})??
-	ID 
-	(
-		OPERATOR {$o = $OPERATOR.text;}
-		WHITE_SPACE?
-		value {$v = $value.str;}
-	)?
+    (minmax {$a=$minmax.agg;})??
+    ID
+    (
+        OPERATOR {$o = $OPERATOR.text;}
+        WHITE_SPACE?
+        value {$v = $value.str;}
+    )?
 ;
 
 transaction returns [TransactionFilter filter] locals [String type, TransactionFilter.Transactor user, String time, String time_op]
 @init{
-	$time = null;
-	$user = null;
-	$type = null;
-	$time_op = null;
+    $time = null;
+    $user = null;
+    $type = null;
+    $time_op = null;
 }
 @after{
-	$filter = new TransactionFilter($type,$user,$time,$time_op);
+    $filter = new TransactionFilter($type,$user,$time,$time_op);
 }
 :
-	(
-		( INSERTED | CREATED ) {$type = TransactionFilter.INSERTION;}
-		| ( UPDATED ) {$type = TransactionFilter.UPDATE;}
-	)
-	
-	(
-		transactor (transaction_time {$time = $transaction_time.tqp; $time_op = $transaction_time.op;})? {$user = $transactor.t;}
-		| transaction_time (transactor {$user = $transactor.t;})? {$time = $transaction_time.tqp; $time_op = $transaction_time.op;}
-	)
+    (
+        ( INSERTED | CREATED ) {$type = TransactionFilter.INSERTION;}
+        | ( UPDATED ) {$type = TransactionFilter.UPDATE;}
+    )
+
+    (
+        transactor (transaction_time {$time = $transaction_time.tqp; $time_op = $transaction_time.op;})? {$user = $transactor.t;}
+        | transaction_time (transactor {$user = $transactor.t;})? {$time = $transaction_time.tqp; $time_op = $transaction_time.op;}
+    )
 ;
 
 transactor returns [TransactionFilter.Transactor t]
 :
-	BY 
-	( 
-		SOMEONE ELSE BUT ME {$t = TransactionFilter.neq_currentTransactor();}
-		| SOMEONE ELSE BUT entity {$t = TransactionFilter.neq_foreignTransactor($entity.ep);}
-		| SOMEONE ELSE BUT username {$t = TransactionFilter.neq_foreignTransactor($username.ep);}
-		| SOMEONE ELSE {$t = TransactionFilter.neq_currentTransactor();}
-		| ME {$t = TransactionFilter.eq_currentTransactor();}
-		| entity {$t = TransactionFilter.eq_foreignTransactor($entity.ep);}
-		| username {$t = TransactionFilter.eq_foreignTransactor($username.ep);}
-	)
+    BY
+    (
+        SOMEONE ELSE BUT ME {$t = TransactionFilter.neq_currentTransactor();}
+        | SOMEONE ELSE BUT entity {$t = TransactionFilter.neq_foreignTransactor($entity.ep);}
+        | SOMEONE ELSE BUT username {$t = TransactionFilter.neq_foreignTransactor($username.ep);}
+        | SOMEONE ELSE {$t = TransactionFilter.neq_currentTransactor();}
+        | ME {$t = TransactionFilter.eq_currentTransactor();}
+        | entity {$t = TransactionFilter.eq_foreignTransactor($entity.ep);}
+        | username {$t = TransactionFilter.eq_foreignTransactor($username.ep);}
+    )
 ;
 
 username returns [Query.Pattern ep] locals [int type]
@@ -205,14 +203,14 @@ transaction_time returns [String tqp, String op]
      $op = "(";
 }
 :
-	(
-	    AT  {$op = "=";}
+    (
+        AT  {$op = "=";}
         | (ON | IN)
         | (
-        	BEFORE {$op = "<";}
-          	| UNTIL {$op = "<=";}
-          	| AFTER {$op = ">";}
-          	| SINCE {$op = ">=";}
+            BEFORE {$op = "<";}
+              | UNTIL {$op = "<=";}
+              | AFTER {$op = ">";}
+              | SINCE {$op = ">=";}
         )
     )?
     (
@@ -222,283 +220,283 @@ transaction_time returns [String tqp, String op]
 ;
 
 /*
-* not fully compliant with iso 8601 (TODO) 
+* not fully compliant with iso 8601 (TODO)
 */
 datetime
 :
-	NUM // year 
-	(
-		HYPHEN NUM // mon
-		( 
-			HYPHEN NUM // day of mon
-			(
-				(m=TXT {$m.text.equals("T")}?)?// compliance with iso datetime
-				NUM // hour
-				(
-					COLON NUM // minut 
-					(
-						COLON NUM // sec 
-						(
-							DOT NUM // millisec
-						)?
-					)?
-				)?
-			)?
-		)?
-	)?	
+    NUM // year
+    (
+        HYPHEN NUM // mon
+        (
+            HYPHEN NUM // day of mon
+            (
+                (m=TXT {$m.text.equals("T")}?)?// compliance with iso datetime
+                NUM // hour
+                (
+                    COLON NUM // minut
+                    (
+                        COLON NUM // sec
+                        (
+                            DOT NUM // millisec
+                        )?
+                    )?
+                )?
+            )?
+        )?
+    )?
 ;
 
 pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a]
-	@init{
-		$p = null;
-		$o = null;
-		$v = null;
-		$a = null;
-	}
-	@after{
-		$filter = new POV($p,$o,$v,$a);
-	}
+    @init{
+        $p = null;
+        $o = null;
+        $v = null;
+        $a = null;
+    }
+    @after{
+        $filter = new POV($p,$o,$v,$a);
+    }
 :
-	(
-		property {$p = $property.pp; $a=$property.agg;} 
-		(
-			( 
-			  LIKE {$o = $LIKE.text.trim();}
-			  ( like_pattern {$v = $like_pattern.ep.toString();}
-			    | value {$v = $value.str;} )
-			  | OPERATOR {$o = $OPERATOR.text;} WHITE_SPACE? value {$v = $value.str;}
-			)
-			| IS_NULL {$o = "0";}
-			| IS_NOT_NULL {$o = "!0";}
-			| IN value {$o = "("; $v=$value.str;}
-			| NEGATION IN value {$o = "!("; $v=$value.str;}
-		)?
-	) 
-	| 
-	(
-		( LIKE {$o = $LIKE.text;}
-		  ( like_pattern {$v = $like_pattern.ep.toString();}
-		    | value {$v = $value.str;} )
-		)
-		| ( OPERATOR {$o = $OPERATOR.text;} WHITE_SPACE? value {$v = $value.str;}
-		  ( AS_A
-			property {$p = $property.pp;} )?
-		)
-	)
-	WHITE_SPACE?
+    (
+        property {$p = $property.pp; $a=$property.agg;}
+        (
+            (
+              LIKE {$o = $LIKE.text.trim();}
+              ( like_pattern {$v = $like_pattern.ep.toString();}
+                | value {$v = $value.str;} )
+              | OPERATOR {$o = $OPERATOR.text;} WHITE_SPACE? value {$v = $value.str;}
+            )
+            | IS_NULL {$o = "0";}
+            | IS_NOT_NULL {$o = "!0";}
+            | IN value {$o = "("; $v=$value.str;}
+            | NEGATION IN value {$o = "!("; $v=$value.str;}
+        )?
+    )
+    |
+    (
+        ( LIKE {$o = $LIKE.text;}
+          ( like_pattern {$v = $like_pattern.ep.toString();}
+            | value {$v = $value.str;} )
+        )
+        | ( OPERATOR {$o = $OPERATOR.text;} WHITE_SPACE? value {$v = $value.str;}
+          ( AS_A
+            property {$p = $property.pp;} )?
+        )
+    )
+    WHITE_SPACE?
 ;
 
 
 subproperty returns [SubProperty subp] locals [String p]
 @init{
-	$p = null;
-	$subp = null;
+    $p = null;
+    $subp = null;
 }
 :
-	entity_filter {$subp = new SubProperty($entity_filter.filter);}
+    entity_filter {$subp = new SubProperty($entity_filter.filter);}
 ;
 
 backreference returns [Backreference ref] locals [Query.Pattern e, Query.Pattern p]
-	@init{
-		$e = null;
-		$p = null;
-	}
-	@after{
-		$ref = new Backreference($e, $p); 
-	}
+    @init{
+        $e = null;
+        $p = null;
+    }
+    @after{
+        $ref = new Backreference($e, $p);
+    }
 :
-	IS_REFERENCED
-	(BY A? entity {$e=$entity.ep;})?
-	( 
-		WHITE_SPACE?
-		AS_A
-		property {$p=$property.pp;}
-	)?
-	WHITE_SPACE?
+    IS_REFERENCED
+    (BY A? entity {$e=$entity.ep;})?
+    (
+        WHITE_SPACE?
+        AS_A
+        property {$p=$property.pp;}
+    )?
+    WHITE_SPACE?
 ;
 
 storedat returns [StoredAt filter] locals [String loc]
-	@init{
-		$loc = null;
-	}
-	@after{
-		$filter = new StoredAt($loc);
-	}
+    @init{
+        $loc = null;
+    }
+    @after{
+        $filter = new StoredAt($loc);
+    }
 :
-	IS_STORED_AT
-	location {$loc = $location.str;}
-	WHITE_SPACE?
+    IS_STORED_AT
+    location {$loc = $location.str;}
+    WHITE_SPACE?
 ;
 
 conjunction returns [Conjunction c] locals [Conjunction dummy]
-	@init{
-		$c = new Conjunction();
-	}
+    @init{
+        $c = new Conjunction();
+    }
 :
-	(
-		f1 = filter_expression {$c.add($f1.efi);}
-		|
-		LPAREN WHITE_SPACE?
-		(
-			f4 = filter_expression {$c.add($f4.efi);}
-			| disjunction {$c.add($disjunction.d);}
-			| c3=conjunction {$c.addAll($c3.c);}
-		)
-		RPAREN
-	) 
-	( 
-		WHITE_SPACE?
-		AND
-		(
-			( which_exp | A (PROPERTY)?? )
-		)? 
-		( 
-			f2 = filter_expression {$c.add($f2.efi);}
-			| (
-				LPAREN WHITE_SPACE?
-				( 
-					f3 = filter_expression {$c.add($f3.efi);}
-					| disjunction {$c.add($disjunction.d);}
-					| c2=conjunction {$c.addAll($c2.c);}
-				)
-				RPAREN
-			)
-		)
-	)+
+    (
+        f1 = filter_expression {$c.add($f1.efi);}
+        |
+        LPAREN WHITE_SPACE?
+        (
+            f4 = filter_expression {$c.add($f4.efi);}
+            | disjunction {$c.add($disjunction.d);}
+            | c3=conjunction {$c.addAll($c3.c);}
+        )
+        RPAREN
+    )
+    (
+        WHITE_SPACE?
+        AND
+        (
+            ( which_exp | A (PROPERTY)?? )
+        )?
+        (
+            f2 = filter_expression {$c.add($f2.efi);}
+            | (
+                LPAREN WHITE_SPACE?
+                (
+                    f3 = filter_expression {$c.add($f3.efi);}
+                    | disjunction {$c.add($disjunction.d);}
+                    | c2=conjunction {$c.addAll($c2.c);}
+                )
+                RPAREN
+            )
+        )
+    )+
 ;
 
 disjunction returns [Disjunction d]
-	@init{
-		$d = new Disjunction();
-	}
+    @init{
+        $d = new Disjunction();
+    }
 :
-	(
-		f1 = filter_expression {$d.add($f1.efi);}
-		|
-		LPAREN WHITE_SPACE?
-		(
-			f4 = filter_expression {$d.add($f4.efi);}
-			| conjunction {$d.add($conjunction.c);}
-			| d3 = disjunction {$d.addAll($d3.d);} 
-		)
-		RPAREN
-	)
-	(
-		OR
-		(
-			( which_exp | A (PROPERTY)? )
-		)?
-		(
-			f2 = filter_expression {$d.add($f2.efi);}
-			| (
-				LPAREN WHITE_SPACE?
-				( 
-					f3 = filter_expression {$d.add($f3.efi);}
-					| conjunction {$d.add($conjunction.c);}
-					| d2 = disjunction {$d.addAll($d2.d);} 
-				)
-				RPAREN
-			)
-		)
-	)+
+    (
+        f1 = filter_expression {$d.add($f1.efi);}
+        |
+        LPAREN WHITE_SPACE?
+        (
+            f4 = filter_expression {$d.add($f4.efi);}
+            | conjunction {$d.add($conjunction.c);}
+            | d3 = disjunction {$d.addAll($d3.d);}
+        )
+        RPAREN
+    )
+    (
+        OR
+        (
+            ( which_exp | A (PROPERTY)? )
+        )?
+        (
+            f2 = filter_expression {$d.add($f2.efi);}
+            | (
+                LPAREN WHITE_SPACE?
+                (
+                    f3 = filter_expression {$d.add($f3.efi);}
+                    | conjunction {$d.add($conjunction.c);}
+                    | d2 = disjunction {$d.addAll($d2.d);}
+                )
+                RPAREN
+            )
+        )
+    )+
 ;
 
 negation returns [Negation n]
-	@init{
-	}
-:	
-	NEGATION
-	(
-		f1 = filter_expression {$n = new Negation($f1.efi);}
-		| ( 
-			LPAREN WHITE_SPACE?
-			(	
-				f2 = filter_expression {$n = new Negation($f2.efi);}
-				| disjunction {$n = new Negation($disjunction.d);}
-				| conjunction {$n = new Negation($conjunction.c);}
-			)
-			RPAREN
-		)
-	)
+    @init{
+    }
+:
+    NEGATION
+    (
+        f1 = filter_expression {$n = new Negation($f1.efi);}
+        | (
+            LPAREN WHITE_SPACE?
+            (
+                f2 = filter_expression {$n = new Negation($f2.efi);}
+                | disjunction {$n = new Negation($disjunction.d);}
+                | conjunction {$n = new Negation($conjunction.c);}
+            )
+            RPAREN
+        )
+    )
 ;
 
 entity returns [Query.Pattern ep]
-: 
-	regexp_pattern {$ep = $regexp_pattern.ep;}
-	| like_pattern {$ep = $like_pattern.ep;}
-	| ( double_quoted {$ep = $double_quoted.ep;} )
-	| ( single_quoted {$ep = $single_quoted.ep;} )
-	| ENTITY {$ep = new Query.Pattern((String) $text.trim(), Query.Pattern.TYPE_NORMAL);}
-	| ( ~(ENTITY |WHITE_SPACE | DOT) )+ {$ep = new Query.Pattern((String) $text.trim(), Query.Pattern.TYPE_NORMAL);}
+:
+    regexp_pattern {$ep = $regexp_pattern.ep;}
+    | like_pattern {$ep = $like_pattern.ep;}
+    | ( double_quoted {$ep = $double_quoted.ep;} )
+    | ( single_quoted {$ep = $single_quoted.ep;} )
+    | ENTITY {$ep = new Query.Pattern((String) $text.trim(), Query.Pattern.TYPE_NORMAL);}
+    | ( ~(ENTITY |WHITE_SPACE | DOT) )+ {$ep = new Query.Pattern((String) $text.trim(), Query.Pattern.TYPE_NORMAL);}
 ;
 
 regexp_pattern returns [Query.Pattern ep] locals [StringBuffer sb]
-	@init{
-		$sb = new StringBuffer();
-	}
+    @init{
+        $sb = new StringBuffer();
+    }
 :
-	REGEXP_BEGIN  
-	(ESC_REGEXP_END {$sb.append(">>");} |m=. {$sb.append($m.text);})*? 
-	REGEXP_END {$ep = new Query.Pattern((String) $sb.toString(), Query.Pattern.TYPE_REGEXP);}
+    REGEXP_BEGIN
+    (ESC_REGEXP_END {$sb.append(">>");} |m=. {$sb.append($m.text);})*?
+    REGEXP_END {$ep = new Query.Pattern((String) $sb.toString(), Query.Pattern.TYPE_REGEXP);}
 ;
 
 like_pattern returns [Query.Pattern ep] locals [StringBuffer sb]
-	@init{
-		$sb = new StringBuffer();
-	}
+    @init{
+        $sb = new StringBuffer();
+    }
 :
-	~(WHITE_SPACE|DOT|LIKE|OPERATOR|AS_A|AND|OR|IS_STORED_AT|IS_REFERENCED)*?
-	STAR
-	~(WHITE_SPACE|DOT|STAR)*?
-	{$ep = new Query.Pattern((String) $text, Query.Pattern.TYPE_LIKE);}
+    ~(WHITE_SPACE|DOT|LIKE|OPERATOR|AS_A|AND|OR|IS_STORED_AT|IS_REFERENCED)*?
+    STAR
+    ~(WHITE_SPACE|DOT|STAR)*?
+    {$ep = new Query.Pattern((String) $text, Query.Pattern.TYPE_LIKE);}
 ;
 
 property returns [Query.Pattern pp, String agg]locals [StringBuffer sb]
-	@init{
-		$sb = new StringBuffer();
-		$agg = null;
-	}
+    @init{
+        $sb = new StringBuffer();
+        $agg = null;
+    }
 :
-	(minmax {$agg=$minmax.agg;})??
-	(
+    (minmax {$agg=$minmax.agg;})??
+    (
         regexp_pattern {$pp = $regexp_pattern.ep;}
         | like_pattern {$pp = $like_pattern.ep;}
         | ( double_quoted {$pp = $double_quoted.ep;} )
         | ( single_quoted {$pp = $single_quoted.ep;} )
         | ((m=TXT | m=NUM | m=REGEXP_MARKER | m=ENTITY){$sb.append($m.text);})+  {$pp = new Query.Pattern($sb.toString(), Query.Pattern.TYPE_NORMAL);}
-	)
-	WHITE_SPACE?
+    )
+    WHITE_SPACE?
 ;
 
 minmax returns [String agg]
 :
-	(THE?? (
-		GREATEST {$agg="max";} 
-		| SMALLEST {$agg="min";} 
-	))
+    (THE?? (
+        GREATEST {$agg="max";}
+        | SMALLEST {$agg="min";}
+    ))
 ;
 
 value returns [String str]
-: 
-	number_with_unit {$str = $number_with_unit.text;}
-	| datetime {$str = $datetime.text;}
-	| atom {$str = $atom.ep.toString();}
-	WHITE_SPACE?
+:
+    number_with_unit {$str = $number_with_unit.text;}
+    | datetime {$str = $datetime.text;}
+    | atom {$str = $atom.ep.toString();}
+    WHITE_SPACE?
 ;
 
 number_with_unit
 :
-	HYPHEN??
-	NUM
-	(WHITE_SPACE?? unit)?
+    HYPHEN??
+    NUM
+    (WHITE_SPACE?? unit)?
 ;
 
 unit
 :
-	(~(WHITE_SPACE | WHICH | HAS_A | WITH | WHERE | DOT | AND | OR ))
-	(~(WHITE_SPACE))*
-	|
-	NUM SLASH (~(WHITE_SPACE))+
+    (~(WHITE_SPACE | WHICH | HAS_A | WITH | WHERE | DOT | AND | OR ))
+    (~(WHITE_SPACE))*
+    |
+    NUM SLASH (~(WHITE_SPACE))+
 ;
 
 location returns [String str]
@@ -511,46 +509,46 @@ location returns [String str]
 atom returns [Query.Pattern ep]
 :
     double_quoted {$ep = $double_quoted.ep;}
-	| single_quoted {$ep = $single_quoted.ep;}
-	| (~(WHITE_SPACE | DOT ))+  {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);}
+    | single_quoted {$ep = $single_quoted.ep;}
+    | (~(WHITE_SPACE | DOT ))+  {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);}
 ;
 
 single_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType]
-	@init{
-		$sb = new StringBuffer();
-		$patternType = Query.Pattern.TYPE_NORMAL;
-	}
-	@after{
-		$ep = new Query.Pattern($sb.toString(),$patternType);
-	}
+    @init{
+        $sb = new StringBuffer();
+        $patternType = Query.Pattern.TYPE_NORMAL;
+    }
+    @after{
+        $ep = new Query.Pattern($sb.toString(),$patternType);
+    }
 :
-	SINGLE_QUOTE_START
-	(
-		t = SINGLE_QUOTE_ESCAPED_CHAR {$sb.append($t.text.substring(1,$t.text.length()));}
-	|
-		r = SINGLE_QUOTE_STAR {$sb.append($r.text); $patternType = Query.Pattern.TYPE_LIKE;}
-	|
-		s = ~SINGLE_QUOTE_END {$sb.append($s.text);}
-	)*?
-	SINGLE_QUOTE_END
+    SINGLE_QUOTE_START
+    (
+        t = SINGLE_QUOTE_ESCAPED_CHAR {$sb.append($t.text.substring(1,$t.text.length()));}
+    |
+        r = SINGLE_QUOTE_STAR {$sb.append($r.text); $patternType = Query.Pattern.TYPE_LIKE;}
+    |
+        s = ~SINGLE_QUOTE_END {$sb.append($s.text);}
+    )*?
+    SINGLE_QUOTE_END
 ;
 
 double_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType]
-	@init{
-		$sb = new StringBuffer();
-		$patternType = Query.Pattern.TYPE_NORMAL;
-	}
-	@after{
-		$ep = new Query.Pattern($sb.toString(),$patternType);
-	}
+    @init{
+        $sb = new StringBuffer();
+        $patternType = Query.Pattern.TYPE_NORMAL;
+    }
+    @after{
+        $ep = new Query.Pattern($sb.toString(),$patternType);
+    }
 :
-	DOUBLE_QUOTE_START
-	(
-		t = DOUBLE_QUOTE_ESCAPED_CHAR {$sb.append($t.text.substring(1,$t.text.length()));}
-	|
-		r = DOUBLE_QUOTE_STAR {$sb.append($r.text); $patternType = Query.Pattern.TYPE_LIKE;}
-	|
-		s = ~DOUBLE_QUOTE_END {$sb.append($s.text);}
-	)*?
-	DOUBLE_QUOTE_END
+    DOUBLE_QUOTE_START
+    (
+        t = DOUBLE_QUOTE_ESCAPED_CHAR {$sb.append($t.text.substring(1,$t.text.length()));}
+    |
+        r = DOUBLE_QUOTE_STAR {$sb.append($r.text); $patternType = Query.Pattern.TYPE_LIKE;}
+    |
+        s = ~DOUBLE_QUOTE_END {$sb.append($s.text);}
+    )*?
+    DOUBLE_QUOTE_END
 ;
diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java
index 058fc619e021b96e1ac3d83750d7ed84c5acc2c0..dca63841df3ee1a5efa4148d24e3e8eb68cd2f50 100644
--- a/src/main/java/org/caosdb/server/query/Query.java
+++ b/src/main/java/org/caosdb/server/query/Query.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.query;
 
diff --git a/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java b/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java
index 704fcfb9aa92e56ad963365d763f70fa7feb470e..7003dfc1d9657def48da4295a189e72730ef9fa8 100644
--- a/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java
+++ b/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.resource.transaction;
 
diff --git a/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java b/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java
index 5eb9db72f69f8f054dbb6f02b970d09e09ff366c..578eca4c855ed067aae7a9f2e18463808558b94d 100644
--- a/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java
+++ b/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java
@@ -1,5 +1,4 @@
 /*
- * ** header v3.0
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.resource.transaction;
 
diff --git a/src/main/java/org/caosdb/server/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java
index 19f840e92a1a6812e2ae2610ed3ace23b7e3cee7..250df0423869ae97a766da2fc54131e04d5257ca 100644
--- a/src/main/java/org/caosdb/server/transaction/Retrieve.java
+++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java
@@ -31,7 +31,7 @@ import org.caosdb.server.entity.xml.SetFieldStrategy;
 import org.caosdb.server.entity.xml.ToElementStrategy;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.jobs.ScheduledJob;
-import org.caosdb.server.jobs.core.Mode;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
 import org.caosdb.server.jobs.core.RemoveDuplicates;
 import org.caosdb.server.jobs.core.ResolveNames;
 import org.caosdb.server.permissions.EntityPermission;
@@ -53,14 +53,14 @@ public class Retrieve extends Transaction<RetrieveContainer> {
     // resolve names
     {
       final ResolveNames r = new ResolveNames();
-      r.init(Mode.SHOULD, null, this);
+      r.init(JobFailureSeverity.WARN, null, this);
       ScheduledJob scheduledJob = getSchedule().add(r);
       getSchedule().runJob(scheduledJob);
     }
 
     {
       final RemoveDuplicates job = new RemoveDuplicates();
-      job.init(Mode.MUST, null, this);
+      job.init(JobFailureSeverity.ERROR, null, this);
       ScheduledJob scheduledJob = getSchedule().add(job);
       getSchedule().runJob(scheduledJob);
     }
diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java
index 97ed4954b93c55830d1bed5f74bad6e157e70776..fe0b63f333e0151aed161ce3fa3914794e5f3d52 100644
--- a/src/main/java/org/caosdb/server/transaction/Transaction.java
+++ b/src/main/java/org/caosdb/server/transaction/Transaction.java
@@ -1,11 +1,10 @@
 /*
- * ** 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) 2020 Indiscale GmbH <info@indiscale.com>
- * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.transaction;
 
@@ -39,14 +36,15 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Message.MessageType;
 import org.caosdb.server.entity.container.TransactionContainer;
 import org.caosdb.server.jobs.Job;
-import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.jobs.Schedule;
 import org.caosdb.server.jobs.ScheduledJob;
+import org.caosdb.server.jobs.TransactionStage;
 import org.caosdb.server.jobs.core.AccessControl;
 import org.caosdb.server.jobs.core.CheckDatatypePresent;
 import org.caosdb.server.jobs.core.CheckEntityACLRoles;
-import org.caosdb.server.jobs.core.Mode;
+import org.caosdb.server.jobs.core.JobFailureSeverity;
 import org.caosdb.server.jobs.core.PickUp;
+import org.caosdb.server.permissions.EntityACL;
 import org.caosdb.server.utils.AbstractObservable;
 import org.caosdb.server.utils.Info;
 import org.caosdb.server.utils.Observer;
@@ -73,9 +71,11 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     this(container, Info.getInstance());
   }
 
-  protected Transaction(C container, Observer o) {
+  protected Transaction(final C container, final Observer o) {
     this.container = container;
-    if (o != null) acceptObserver(o);
+    if (o != null) {
+      acceptObserver(o);
+    }
   }
 
   public static DatabaseAccessManager getAccessManager() {
@@ -86,15 +86,24 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     return this.container;
   }
 
+  /**
+   * Implementation note: Not called in this class, but may be used by subclasses.
+   *
+   * <p>E.g. in {@link Retrieve} and {@link WriteTransaction}.
+   */
   protected void makeSchedule() throws Exception {
     // load flag jobs
-    final Job loadContainerFlags = Job.getJob("LoadContainerFlagJobs", Mode.MUST, null, this);
-    ScheduledJob scheduledJob = this.schedule.add(loadContainerFlags);
+    final Job loadContainerFlags =
+        Job.getJob("LoadContainerFlagJobs", JobFailureSeverity.ERROR, null, this);
+    final ScheduledJob scheduledJob = this.schedule.add(loadContainerFlags);
     this.schedule.runJob(scheduledJob);
 
     // AccessControl
-    this.schedule.add(Job.getJob(AccessControl.class.getSimpleName(), Mode.MUST, null, this));
-    this.schedule.add(Job.getJob(CheckEntityACLRoles.class.getSimpleName(), Mode.MUST, null, this));
+    this.schedule.add(
+        Job.getJob(AccessControl.class.getSimpleName(), JobFailureSeverity.ERROR, null, this));
+    this.schedule.add(
+        Job.getJob(
+            CheckEntityACLRoles.class.getSimpleName(), JobFailureSeverity.ERROR, null, this));
 
     // load permanent container jobs
     this.schedule.addAll(Job.loadPermanentContainerJobs(this));
@@ -105,22 +114,52 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
 
       // additionally load datatype job
       if (e.hasValue()) {
-        this.schedule.add(new CheckDatatypePresent().init(Mode.MUST, e, this));
+        this.schedule.add(new CheckDatatypePresent().init(JobFailureSeverity.ERROR, e, this));
       }
 
       // load pickup job if necessary
       if (e.hasFileProperties() && e.getFileProperties().isPickupable()) {
-        this.schedule.add(new PickUp().init(Mode.MUST, e, this));
+        this.schedule.add(new PickUp().init(JobFailureSeverity.ERROR, e, this));
       }
     }
   }
 
+  /**
+   * The main transaction execution method.
+   *
+   * <p>This method calls the following other internal methods and scheduled jobs stored in the
+   * {@link getSchedule() internal Schedule object}:
+   *
+   * <ol>
+   *   <li>{@link init} - Make {@link Schedule}, resolve names to ids, aquire read access.
+   *   <li>{@link Schedule.runJobs(INIT)} - See {@link TransactionStage#INIT}.
+   *   <li>{@link preCheck} - Load/generate {@link EntityACL}s, check if any updates are to be
+   *       processed.
+   *   <li>{@link Schedule.runJobs(PRE_CHECK)} - See {@link TransactionStage#PRE_CHECK}.
+   *   <li>{@link check} - only run the jobs in the CHECK stage, see {@link TransactionStage#CHECK}.
+   *   <li>{@link Schedule.runJobs(POST_CHECK)} - See {@link TransactionStage#POST_CHECK}.
+   *   <li>{@link postCheck} - currently, nothing happens here (just there for consistency).
+   *   <li>{@link preTransaction} - acquire write access (if necessary)
+   *   <li>{@link Schedule.runJobs(PRE_TRANSACTION)} - See {@link TransactionStage#PRE_TRANSACTION}.
+   *   <li>{@link transaction}: This is typically the main method of a Transaction.
+   *   <li>{@link Schedule.runJobs(POST_TRANSACTION)} - See {@link
+   *       TransactionStage#POST_TRANSACTION}.
+   *   <li>{@link postTransaction} - Add success messages
+   *   <li>{@link writeHistory} - write the transaction history logs
+   *   <li>{@link commit} - commit the changes
+   *   <li>{@link rollBack}: Only in the case of errors - rollback any changes (also file-system
+   *       changes).
+   *   <li>{@link cleanUp}: Always - cleanup the transaction (e.g. remove temporary files).
+   *   <li>{@link notifyObservers(CLEAN_UP)}: Also always - for any jobs that do their own clean-up.
+   *
+   * @see {@link TransactionStage}.
+   */
   @Override
   public final void execute() throws Exception {
     long t1 = System.currentTimeMillis();
     try {
       init();
-      this.schedule.runJobs(JobExecutionTime.INIT);
+      this.schedule.runJobs(TransactionStage.INIT);
       getContainer()
           .getTransactionBenchmark()
           .addMeasurement(
@@ -128,7 +167,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
 
       t1 = System.currentTimeMillis();
       preCheck();
-      this.schedule.runJobs(JobExecutionTime.PRE_CHECK);
+      this.schedule.runJobs(TransactionStage.PRE_CHECK);
       getContainer()
           .getTransactionBenchmark()
           .addMeasurement(
@@ -142,7 +181,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
               this.getClass().getSimpleName() + ".check", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
-      this.schedule.runJobs(JobExecutionTime.POST_CHECK);
+      this.schedule.runJobs(TransactionStage.POST_CHECK);
       postCheck();
       getContainer()
           .getTransactionBenchmark()
@@ -151,7 +190,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
 
       t1 = System.currentTimeMillis();
       preTransaction();
-      this.schedule.runJobs(JobExecutionTime.PRE_TRANSACTION);
+      this.schedule.runJobs(TransactionStage.PRE_TRANSACTION);
       getContainer()
           .getTransactionBenchmark()
           .addMeasurement(
@@ -166,7 +205,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
               this.getClass().getSimpleName() + ".transaction", System.currentTimeMillis() - t1);
 
       t1 = System.currentTimeMillis();
-      this.schedule.runJobs(JobExecutionTime.POST_TRANSACTION);
+      this.schedule.runJobs(TransactionStage.POST_TRANSACTION);
       postTransaction();
       getContainer()
           .getTransactionBenchmark()
@@ -202,6 +241,12 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     }
   }
 
+  /**
+   * Return the internal {@link Schedule} object.
+   *
+   * <p>The Schedule stores jobs which are also triggered by this transaction (see {@link execute()}
+   * for details).
+   */
   public Schedule getSchedule() {
     return this.schedule;
   }
@@ -210,6 +255,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     return getContainer().getOwner();
   }
 
+  /** Return true iff this transaction should be logged in the transaction history logs. */
   public abstract boolean logHistory();
 
   public UTCDateTime getTimestamp() {
@@ -219,36 +265,46 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
   // TODO move to post-transaction job
   private void writeHistory() throws TransactionException, Message {
     if (logHistory()) {
-      String realm = ((Principal) getTransactor().getPrincipal()).getRealm();
-      String username = ((Principal) getTransactor().getPrincipal()).getUsername();
+      final String realm = ((Principal) getTransactor().getPrincipal()).getRealm();
+      final String username = ((Principal) getTransactor().getPrincipal()).getUsername();
       execute(
           new InsertTransactionHistory(getContainer(), realm, username, getTimestamp()),
           getAccess());
     }
   }
 
+  /** @see {@link #execute()} */
   protected void rollBack() {
-    this.schedule.runJobs(JobExecutionTime.ROLL_BACK);
+    this.schedule.runJobs(TransactionStage.ROLL_BACK);
   }
 
+  /** @see {@link #execute()} */
   protected abstract void init() throws Exception;
 
+  /** @see {@link #execute()} */
   protected abstract void preCheck() throws InterruptedException, Exception;
 
+  /** @see {@link #execute()} */
   protected final void check() {
-    this.schedule.runJobs(JobExecutionTime.CHECK);
+    this.schedule.runJobs(TransactionStage.CHECK);
   }
 
+  /** @see {@link #execute()} */
   protected abstract void postCheck();
 
+  /** @see {@link #execute()} */
   protected abstract void preTransaction() throws InterruptedException;
 
+  /** @see {@link #execute()} */
   protected abstract void transaction() throws Exception;
 
+  /** @see {@link #execute()} */
   protected abstract void postTransaction() throws Exception;
 
+  /** @see {@link #execute()} */
   protected abstract void cleanUp();
 
+  /** @see {@link #execute()} */
   protected void commit() throws Exception {}
 
   public boolean useCache() {
diff --git a/src/main/java/org/caosdb/server/transaction/TransactionInterface.java b/src/main/java/org/caosdb/server/transaction/TransactionInterface.java
index 1aa41b2bbc31063b2978aa9c141e75c98799f124..d56407ca854c756a956fe11d9bc98a72f05a4b50 100644
--- a/src/main/java/org/caosdb/server/transaction/TransactionInterface.java
+++ b/src/main/java/org/caosdb/server/transaction/TransactionInterface.java
@@ -35,6 +35,10 @@ public interface TransactionInterface {
     return TransactionBenchmark.getRootInstance().getBenchmark(getClass());
   }
 
+  /**
+   * Append the BackendTransaction t to a RollBackHandler before basically calling {@code
+   * t.execute()}. Except for benchmarking, this method does not interact directly with this object.
+   */
   public default <K extends BackendTransaction> K execute(K t, Access access) {
     final RollBackHandler handler = (RollBackHandler) access.getHelper("RollBack");
     handler.append(t);
diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
index 570fce2c2b001699fdb48c1379e807ae05d62996..62a9beea1921f34dd3646bcc78ed9086d05e6098 100644
--- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
@@ -1,11 +1,10 @@
 /*
- * ** 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) 2021 IndiScale GmbH <info@indiscale.com>
- * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -19,8 +18,6 @@
  *
  * 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 org.caosdb.server.transaction;
 
@@ -76,8 +73,7 @@ public class WriteTransaction extends Transaction<WritableContainer>
 
   @Override
   protected final void preTransaction() throws InterruptedException {
-    // acquire strong access. No other thread can have access until
-    // it this strong access is released.
+    // Acquire strong access. No other thread can have access until this strong access is released.
     setAccess(getAccessManager().acquireWriteAccess(this));
   }
 
diff --git a/src/main/java/org/caosdb/server/utils/ConfigurationException.java b/src/main/java/org/caosdb/server/utils/ConfigurationException.java
index 517c11677c73d9624b93df18fb5196e2f4578024..df04ac169f3c8113443a69795840a50774bef0b1 100644
--- a/src/main/java/org/caosdb/server/utils/ConfigurationException.java
+++ b/src/main/java/org/caosdb/server/utils/ConfigurationException.java
@@ -26,7 +26,11 @@ public class ConfigurationException extends RuntimeException {
 
   private static final long serialVersionUID = -8973694831724941007L;
 
-  public ConfigurationException(String reason) {
+  public ConfigurationException(final String reason) {
     super(reason);
   }
+
+  public ConfigurationException(final String reason, final Throwable cause) {
+    super(reason, cause);
+  }
 }
diff --git a/src/main/java/org/caosdb/server/utils/FlagInfo.java b/src/main/java/org/caosdb/server/utils/FlagInfo.java
index 164c6aabe9c4fafff26f93db30d791fa91389eee..60697cb90df0bcf0da0588c580c49664ec0b0f52 100644
--- a/src/main/java/org/caosdb/server/utils/FlagInfo.java
+++ b/src/main/java/org/caosdb/server/utils/FlagInfo.java
@@ -55,7 +55,7 @@ public class FlagInfo {
     for (final JobAnnotation a : as) {
       final Element aElem = new Element(a.flag());
       aElem.setAttribute("description", a.description());
-      aElem.setAttribute("time", a.time().toString());
+      aElem.setAttribute("stage", a.stage().toString());
       if (!a.transaction().equals(TransactionInterface.class)) {
         aElem.setAttribute("transaction", a.transaction().getSimpleName());
       }
diff --git a/src/test/docker/Dockerfile b/src/test/docker/Dockerfile
index 08a2a0884d1f154bb941388075b2bdd915125f99..f3c8b98fdab34400cccfc11f0c9211eeca37f845 100644
--- a/src/test/docker/Dockerfile
+++ b/src/test/docker/Dockerfile
@@ -2,10 +2,11 @@ FROM debian:buster
 RUN apt-get update && \
   apt-get install -y \
     git make mariadb-server maven openjdk-11-jdk-headless \
+    plantuml \
     python3-pip screen libpam0g-dev unzip curl shunit2 \
     python3-sphinx \
   && \
-  pip3 install javasphinx recommonmark sphinx-rtd-theme
+  pip3 install javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml
 
 # Alternative, if javasphinx fails because python3-sphinx is too recent:
 # (`_l` not found):
diff --git a/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java b/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
index 0ba085b1fd050578b50e443613f82167890ea61f..1e8f6cc707299f5b0102ad774d127630dee51c08 100644
--- a/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
+++ b/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.database;
 
diff --git a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java
deleted file mode 100644
index 93402bf0d06c487451d9219b4487f301eb855f49..0000000000000000000000000000000000000000
--- a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.caosdb.server.database.backend.transaction;
-
-import static org.junit.Assert.assertEquals;
-
-import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.entity.DeleteEntity;
-import org.caosdb.server.entity.InsertEntity;
-import org.caosdb.server.entity.RetrieveEntity;
-import org.caosdb.server.entity.Role;
-import org.caosdb.server.entity.UpdateEntity;
-import org.jdom2.Element;
-import org.junit.Test;
-
-public class BackendTransactionTest {
-
-  @Test
-  public void testGetTransactionType() {
-    BackendTransaction transaction = new InsertTransactionHistory(null, null, null, null);
-    assertEquals("Retrieve", transaction.getTransactionType(new RetrieveEntity("test")));
-    assertEquals("Insert", transaction.getTransactionType(new InsertEntity("test", Role.Record)));
-    assertEquals("Delete", transaction.getTransactionType(new DeleteEntity(1234)));
-    assertEquals("Update", transaction.getTransactionType(new UpdateEntity(new Element("Record"))));
-  }
-}
diff --git a/src/test/java/org/caosdb/server/jobs/JobConfigTest.java b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..70917b53553c6b94b283ed1e39b0ee81db9d466c
--- /dev/null
+++ b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java
@@ -0,0 +1,31 @@
+package org.caosdb.server.jobs;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.entity.DeleteEntity;
+import org.caosdb.server.entity.InsertEntity;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.Role;
+import org.caosdb.server.entity.UpdateEntity;
+import org.jdom2.Element;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JobConfigTest {
+
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
+  @Test
+  public void testGetTransactionType() {
+    final JobConfig jobConfig = JobConfig.getInstance();
+    assertEquals("Retrieve", jobConfig.getTransactionType(new RetrieveEntity("test")));
+    assertEquals("Insert", jobConfig.getTransactionType(new InsertEntity("test", Role.Record)));
+    assertEquals("Delete", jobConfig.getTransactionType(new DeleteEntity(1234)));
+    assertEquals("Update", jobConfig.getTransactionType(new UpdateEntity(new Element("Record"))));
+  }
+}
diff --git a/src/test/java/org/caosdb/server/jobs/ScheduleTest.java b/src/test/java/org/caosdb/server/jobs/ScheduleTest.java
index 7ad1d6112e8b7bd3a29042d5f6ca82709ae4100a..9aa8e6f66788911f2c4314c44549d543277398d2 100644
--- a/src/test/java/org/caosdb/server/jobs/ScheduleTest.java
+++ b/src/test/java/org/caosdb/server/jobs/ScheduleTest.java
@@ -24,10 +24,18 @@ package org.caosdb.server.jobs;
 
 import static org.junit.Assert.fail;
 
+import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class ScheduleTest {
 
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
   /** The problem of ticket #297 was that a CHECK job was executed during the ROLL_BACK stage. */
   @Test
   public void testTicket297() {
@@ -46,6 +54,6 @@ public class ScheduleTest {
           }
         });
 
-    schedule.runJobs(JobExecutionTime.ROLL_BACK);
+    schedule.runJobs(TransactionStage.ROLL_BACK);
   }
 }
diff --git a/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java b/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java
index ae5df853ba5d29e681cd0fb0e5ec8eee6ec3462c..5d81ac368f65c559eb666f003f34da522f191f21 100644
--- a/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java
+++ b/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java
@@ -4,16 +4,23 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class TestInsertFilesInDir {
 
+  @BeforeClass
+  public static void setup() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
   @Test
   public void testExclude() throws IOException {
-    InsertFilesInDir job = new InsertFilesInDir();
+    final InsertFilesInDir job = new InsertFilesInDir();
     job.init(null, null, null);
     job.parseValue("-e ^.*test.*$ test");
-    File testFile = new File("test.dat");
+    final File testFile = new File("test.dat");
     assertTrue(job.isExcluded(testFile));
   }
 }
diff --git a/src/test/java/org/caosdb/server/query/QueryTest.java b/src/test/java/org/caosdb/server/query/QueryTest.java
index 26e43ec6c65414808c72b9e04b95c1d67011fc9a..8f3d4946c36d795e5e7bd9048a66f86cc0290a0b 100644
--- a/src/test/java/org/caosdb/server/query/QueryTest.java
+++ b/src/test/java/org/caosdb/server/query/QueryTest.java
@@ -1,3 +1,22 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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/>.
+ */
 package org.caosdb.server.query;
 
 import static org.junit.Assert.assertEquals;
diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java
index 154977a909a9d34ac2034c1ef352b81e7b2a3cb7..cf54bf71b69ea5f539744bd4bbe30fd1c37edcf4 100644
--- a/src/test/java/org/caosdb/server/query/TestCQL.java
+++ b/src/test/java/org/caosdb/server/query/TestCQL.java
@@ -1,9 +1,10 @@
 /*
- * ** 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-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 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
@@ -17,8 +18,6 @@
  *
  * 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 org.caosdb.server.query;