diff --git a/CHANGELOG.md b/CHANGELOG.md
index e51cc4f004662b95d12abdb411043da60192b2de..bca214af664914277377d179c61ce2716f066e12 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   by setting the server property `EXT_ENTITY_STATE=ENABLED`. See
   [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more
   information.
+* Basic caching for queries. The caching is enabled by default and can be
+  controlled by the usual "cache" flag.
+
+### Changed
+
+### Deprecated
+
+### Removed
+
+### Fixed
+
+* #120 - Editing entities that were created with a no longer existing user
+  leads to a server error.
+* #31 - Queries with keywords in the path (e.g. `... STORED AT 0in.txt`)
+* #116 - Queries `FIND [ANY VERSION OF] *` and `FIND [ANY VERSION OF] ENTITY`.
+
+### Security
+
+## [0.3.0] - 2021-02-10
+
+### Added
+
 * New version history feature. The "H" container flag retrieves the full
   version history during a transaction (e.g. during Retrievals) and constructs
   a tree of successors and predecessors of the requested entity version.
@@ -27,6 +49,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+* Select queries would originally only select the returned properties by their
+  names and would not check if a property is a subtype of a selected property. This
+  has changed now and select queries will also return subtypes of selected
+  properties.
+
 ### Deprecated
 
 * `SERVER_SIDE_SCRIPTING_BIN_DIR` property is deprecated.
@@ -35,8 +62,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Removed
 
+* Text user interface (CaosDBTerminal).
+
 ### Fixed
 
+* Bug: When the user password is updated the user is deactivated.
 * Semi-fixed a bug which occurs when retrieving old versions of entities which
   reference entities which have been deleted in the mean time. The current fix
   adds a warning message to the reference property in question and sets the
@@ -50,7 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Security
 
-## [0.2] - 2020-09-02
+## [0.2.0] - 2020-09-02
 
 ### Added
 
@@ -93,6 +123,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+* Missing handling of list of reference properties in SELECT queries.
 * #51 - name queries (e.g. `FIND ENTITY WITH name = ...`)
 - #27 - star matches slashes (e.g. for `FIND ... STORED AT /*.dat`).
 - #30 - file path cannot be in quotes
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
new file mode 100644
index 0000000000000000000000000000000000000000..3744c7b7d654c5f2379b396c5894f8c054907ee4
--- /dev/null
+++ b/DEPENDENCIES.md
@@ -0,0 +1,4 @@
+* caosdb-mysqlbackend == 4.0.0
+* Java 11
+* Apache Maven >= 3.6.0
+* make >= 4.2.0
diff --git a/README_SETUP.md b/README_SETUP.md
index 5cbd85ab73bc200fe8534508be1f4538ac1dad88..f47f5a08624c20de194829b3534d72c2e06b461c 100644
--- a/README_SETUP.md
+++ b/README_SETUP.md
@@ -119,6 +119,8 @@ server:
     * Maybe set another `SESSION_TIMEOUT_MS`.
     * See also [README_CONFIGURATION.md](README_CONFIGURATION.md)
 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.
     * 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
diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md
index 234ff24f40e9c281bfeb23703864c39369aaea05..5d89ceec00bbd8ba228bb497964f0eeb437891f7 100644
--- a/RELEASE_GUIDELINES.md
+++ b/RELEASE_GUIDELINES.md
@@ -18,6 +18,9 @@ guidelines of the CaosDB Project
 
 2. Check all general prerequisites.
 
+3. Update the version property in [pom.xml](./pom.xml) (probably this means to
+   remove the `-SNAPSHOT`) and in `src/doc/conf.py`.
+
 4. Merge the release branch into the master branch.
 
 5. Tag the latest commit of the master branch with `v<VERSION>`.
@@ -25,3 +28,6 @@ guidelines of the CaosDB Project
 6. Delete the release branch.
 
 7. Merge the master branch back into the dev branch.
+
+8. Update the version property in [pom.xml](./pom.xml) for the next
+   developlement round (with a `-SNAPSHOT` suffix).
diff --git a/caosdb-webui b/caosdb-webui
index 82315e1199bd5adcf584f61bfde167bd05624172..8c59cc861d646cbdba0ec749ba052656f67fd58d 160000
--- a/caosdb-webui
+++ b/caosdb-webui
@@ -1 +1 @@
-Subproject commit 82315e1199bd5adcf584f61bfde167bd05624172
+Subproject commit 8c59cc861d646cbdba0ec749ba052656f67fd58d
diff --git a/conf/core/cache.ccf b/conf/core/cache.ccf
index b6a50bee08fdd8598dac3c9f3d7aa70f43190127..9cd12694874dfd5e7739f7b23098602155436c8e 100644
--- a/conf/core/cache.ccf
+++ b/conf/core/cache.ccf
@@ -36,6 +36,9 @@ jcs.region.BACKEND_SparseEntities.cacheattributes.MaxObjects=1002
 jcs.region.BACKEND_RetrieveVersionHistory
 jcs.region.BACKEND_RetrieveVersionHistory.cacheattributes.MaxObjects=1006
 
+jcs.region.HIGH_LEVEL_QUERY_CACHE
+jcs.region.HIGH_LEVEL_QUERY_CACHE.cacheattributes.MaxObjects=1000
+
 # PAM UserSource Caching: Cached Items expire after 60 seconds if they are not requested (idle) and after 600 seconds max.
 # PAM_UnixUserGroups
 jcs.region.PAM_UnixUserGroups
diff --git a/conf/core/server.conf b/conf/core/server.conf
index 2c9a50ced2600d837e933742e715624eeada6a4b..65c8b57e3ad4f69a1a5c8d2120b6fdc6fdc7989d 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-rc2
+MYSQL_SCHEMA_VERSION=v4.0.0
 
 
 # --------------------------------------------------
@@ -112,8 +112,8 @@ CERTIFICATES_KEY_STORE_PASSWORD=
 # --------------------------------------------------
 
 # The session timeout after which the cookie expires.
-# 10 min
-SESSION_TIMEOUT_MS=600000
+# 60 min
+SESSION_TIMEOUT_MS=3600000
 
 # Time after which one-time tokens expire.
 # This is only a default value. The actual timeout of tokens can be
diff --git a/conf/core/usersources.ini.template b/conf/core/usersources.ini.template
index 9053093eba21af0adcc5846112932a8a84b8250c..2e0fe2490a65b53022d8d0edff6495f3b92ec10a 100644
--- a/conf/core/usersources.ini.template
+++ b/conf/core/usersources.ini.template
@@ -22,14 +22,19 @@
 # ** end header
 #
 
-# `realms` is a comma and/or space separated list of realms which users can use for authentication
+# This file configures external authentication providers. The CaosDB realm is
+# always available (without being defined here).
+
+# `realms` is a comma and/or space separated list of realms which users can 
+# use for authentication
+# Currently available: PAM
 realms = PAM
 
 # This is the default realm, to be used when no other realms is specified
 defaultRealm = PAM
 
-# Each realm has one section with specific options.  The options for a specific realm can be looked
-# up in that realm's documentation.
+# Each realm has one section with specific options.  The options for a specific 
+# realm can be looked up in that realm's documentation.
 #
 # Hint: Realms are implemented by classes which are typically in the
 # org.caosdb.server.accessControl.Pam package and implement the UserSource interface.
diff --git a/pom.xml b/pom.xml
index 7b229e5178141e8cd90f85328f77cf4e5b3b0531..4c4435934c97633ecadf7bc1ca2d2b6917198bd6 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.3-SNAPSHOT</version>
+  <version>0.4.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>CaosDB Server</name>
   <properties>
@@ -117,11 +117,6 @@
       <artifactId>reflections</artifactId>
       <version>0.9.11</version>
     </dependency>
-    <dependency>
-      <groupId>com.googlecode.lanterna</groupId>
-      <artifactId>lanterna</artifactId>
-      <version>2.1.9</version>
-    </dependency>
     <dependency>
       <groupId>org.antlr</groupId>
       <artifactId>antlr4</artifactId>
diff --git a/src/doc/CaosDB-Query-Language.md b/src/doc/CaosDB-Query-Language.md
index 47a890d716564eb72c70de4f9d39d1cd00d92157..07fbdbc310b8d22de4642f041918710e6e707488 100644
--- a/src/doc/CaosDB-Query-Language.md
+++ b/src/doc/CaosDB-Query-Language.md
@@ -351,6 +351,33 @@ Any result set can be filtered by logically combining POV filters or back refere
 *  REFERENCE:: This one is tricky: `REFERENCE TO` expresses a the state of _having_ a reference property. `REFERENCED BY` expresses the state of _being_ referenced by another entity.
 *  COUNT:: `COUNT` works like `FIND` but doesn't return the entities.
 
+
+## Select Queries
+
+In contrast to `FIND` queries, which always return the complete entity, there are `SELECT` queries which only return the entity with only those properties which are specified in the query. The syntax is very similar to `FIND` queries - just replace the `FIND` by `SELECT <comma separated list of selectors> FROM`:
+
+`SELECT p1, p2, p3 FROM Record ename`
+
+However, the `SELECT` query can also return properties of referenced entities and thereby are a means of joining entities together and return a custom view or projection:
+
+`SELECT Conductor.Last Name FROM Experiment`
+
+would return the conductor's last name, when `Conductor` is a reference property of `Experiment` and `Last Name` is a property of the `Conductor` records.
+
+### Selectors
+
+Selectors are strings of entity names which are separated by `.` (dot). E.g. `Conductor.Last Name` or `Conductor.Address` or even `Experiment.Conductor.Last name`. Selectors in a `SELECT` queries are separated by `,` (comma). E.g. `Conductor.First Name, Conductor.Last Name`.
+
+### Evaluation of Selectors
+
+The query will return all those properties which have the same name as
+specified by the selector (case-insensitive). However, `SELECT` queries are
+also capable of subtyping in the selectors:
+
+`SELECT Person FROM Experiment` would return all `Person` properties but all `Conductors` as well, if `Conductor` is a child of `Person`.
+
+Note: When a property `responsible` with data type `Person` exists, the above `SELECT` statement would not include records that use this property (since `responsible` is not a child of Person).
+
 ## Versioning
 
 Since Caosdb 0.2 entities are optionally version controlled. The query language will be extended to include versioning in the future. A current minimal implementation introduces the `ANY VERSION OF` modifier which can be used to return all matching versions in the results of `COUNT`, `FIND`, and `SELECT` queries.
diff --git a/src/doc/Data-Model.md b/src/doc/Data-Model.md
new file mode 100644
index 0000000000000000000000000000000000000000..3fa64ec2fdab693552b742572d05b1e3be88d11b
--- /dev/null
+++ b/src/doc/Data-Model.md
@@ -0,0 +1,40 @@
+
+# CaosDB Data Model
+
+The data structure is build 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.
diff --git a/src/doc/Glossary.md b/src/doc/Glossary.md
new file mode 100644
index 0000000000000000000000000000000000000000..080c2aea4ff393d349553f7d7211d35518ff7c11
--- /dev/null
+++ b/src/doc/Glossary.md
@@ -0,0 +1,17 @@
+# Glossary
+
+## Valid Unique Existing Name
+
+A name of an exiting entity which is unique among the names of all existing entities.
+
+## Valid Unique Prospective Name
+
+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/Permissions.rst b/src/doc/Permissions.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c624092aff4f479bc8ed48f439109530cc5295aa
--- /dev/null
+++ b/src/doc/Permissions.rst
@@ -0,0 +1,96 @@
+#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
+this is the ``anonymous`` role. The permissions for an action which
+involves one or more objects are set either manually or via default
+permissions which can be configured. For more detailed information,
+there is separate
+:doc:``documentation of the permission system<permissions>``.
+
+Permissions are needed to perform particular elementary *actions* during
+any interaction with the the server. E.g. retrieving an Entity requires
+the requesting user to have the ``RETRIEVE:ENTITY`` permission for that
+entity, and ``DELETE:ENTITY`` to delete the entity.
+
+The permissions of every user are calculated from a set of *Permission
+Rules*. These rules have several sources. Some are global defaults, some
+are defined by administrators or the owners of an entity.
+
+As a side note on the implementation: The server uses `Apache
+Shiro <https://shiro.apache.org/documentation.html>`__ for its
+permission system.
+
+What is a Permission Rule?
+--------------------------
+
+A Permission Rule consists of:
+
+-  A type: Permission Rules can be of ``Grant`` or ``Deny`` type, either
+   granting or denying specific permissions.
+-  A `role <manuals/general/roles>`__ (or user): For which users the
+   permission shall be granted or denied.
+-  A permission action: Which action shall be permitted or forbidden,
+   for example all retrieval, or modifications of a specific entity.
+-  An optional priority: May be ``true`` or ``false``. Permissions with
+   priority = ``true`` override those without, see the calculation rules
+   below.
+
+Permission calculation
+----------------------
+
+For each action a user tries to perform, the server tests, in the
+following order, which rules apply:
+
+1. *Grant* rules, without priority.
+2. *Deny* rules, without priority.
+3. *Grant* rules, with priority.
+4. *Deny* rules, with priority.
+
+If at the end the user’s permission is *granted*, the action may take
+place. Otherwise (the result is *denied* or the permission still
+undefined), the action can not take place. In other words, if you have
+not been given the permission explicitly at some point, you don’t have
+it.
+
+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>`__.
+
+Typical permissions are:
+
+-  ``RETRIEVE:ENTITY`` :: To retrieve the full entity (name,
+   description, data type, …) with all parents and properties (unless
+   prohibited by another rule on the property level).
+-  ``RETRIEVE:ACL`` :: To retrieve the full and final ACL of this
+   entity.
+-  ``RETRIEVE:ENTITY:1234`` :: To retrieve the entity ``1234``.
+-  ``RETRIEVE:*:1234`` :: For all “retrieve” actions concerning the
+   entity ``1234``.
+
+How to set permissions
+----------------------
+
+-  Config file :: Some default permissions are typically set in the
+   ``global_entity_permissions.xml`` file, see also the `documentation
+   for that
+   file </manuals/server/conf/global_entity_permissions.xml>`__.
+-  API :: The REST API allows to set the permissions. (*to be documented
+   here*)
+-  The Python library :: The permissions can also conveniently be set
+   via the Python library. Currently the best documentation is inside
+   various files which use the permission API:
+
+   -  The `example
+      file <https://gitlab.com/caosdb/caosdb-pylib/blob/HEAD/examples/set_permissions.py>`__
+   -  The ```caosdb_admin.py`` utility
+      script <https://gitlab.com/caosdb/caosdb-pylib/blob/HEAD/src/caosdb/utils/caosdb_admin.py>`__
+   -  The `integration
+      tests <https://gitlab.com/caosdb/caosdb-pyinttest/blob/HEAD/tests/test_permissions.py>`__
+      also cover quite a bit of the permission API.
+
+-  WebUI :: Not implemented (or documented?) yet.
diff --git a/src/doc/administration.rst b/src/doc/administration.rst
new file mode 100644
index 0000000000000000000000000000000000000000..819ce5a59fcfbeccc5e145cf773d807197a42d0f
--- /dev/null
+++ b/src/doc/administration.rst
@@ -0,0 +1,10 @@
+Administration
+==============
+
+.. toctree::
+   :maxdepth: 1
+   :glob:
+
+   administration/*
+
+
diff --git a/src/doc/administration/configuration.rst b/src/doc/administration/configuration.rst
index 4170fd0ed31c7a0ec5c1b4e589ff919267cf7de9..bbafcf22bd8576e141cb9d7e8388212ccf24d934 100644
--- a/src/doc/administration/configuration.rst
+++ b/src/doc/administration/configuration.rst
@@ -23,6 +23,7 @@ In each of these directories, the server looks for the following files:
 
 ``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>`_.
 
 ``usersources.ini``
    This file defines possible sources which are checked when a user tries to authenticate.  Each
diff --git a/src/doc/administration/curl-access.rst b/src/doc/administration/curl-access.rst
new file mode 100644
index 0000000000000000000000000000000000000000..79b10a712056959bf03a5ee6a41671b9cb3c3119
--- /dev/null
+++ b/src/doc/administration/curl-access.rst
@@ -0,0 +1,265 @@
+Curl Access for CaosDB
+======================
+
+As the API that is used to communicate with the CaosDB server is XML
+over HTTP also simple HTTP clients, such as
+`cURL <https://curl.haxx.se/>`__ can be used. cURL is an old and
+established command line program for transferring files over networks
+that implements various protocols including HTTP/HTTPS. It is installed
+by default on many Linux distributions and can therefore be very useful
+for testing and debugging of CaosDB.
+
+This small manual also gives some practical insights about the CaosDB
+protocol itself.
+
+Doing a simple retrieve
+-----------------------
+
+So, let’s start right away with a few basic examples.
+
+Let’s do a query on our demo instance:
+
+.. code:: bash
+
+   curl "https://demo.indiscale.com/Entity/?query=FIND%20Experiment"
+
+By default cURL sends an HTTP GET request which is needed for doing
+queries for CaosDB. CaosDB requires sending them to ``/Entity``. The
+query itself is specified after the HTTP query string ``?query=``.
+``%20`` is specific to URL encoding and corresponds to a space (see
+https://en.wikipedia.org/wiki/Percent-encoding for details). So the
+actual query we were doing was ``FIND Experiment`` which should return
+all entities with name or with parent ``Experiment``.
+
+The response should look like:
+
+.. code:: xml
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <?xml-stylesheet type="text/xsl" href="https://demo.indiscale.com/webinterface/webcaosdb.xsl" ?>
+   <Response srid="b56e3d1a442460c46dde924a54e8afba" timestamp="1561453363382" baseuri="https://demo.indiscale.com" count="3">
+     <UserInfo>
+       <Roles>
+         <Role>anonymous</Role>
+       </Roles>
+     </UserInfo>
+     <Query string="FIND Experiment" results="3">
+       <ParseTree>(cq FIND (entity Experiment) &lt;EOF&gt;)</ParseTree>
+       <Role />
+       <Entity>Experiment</Entity>
+       <TransactionBenchmark since="Tue Jun 25 11:02:43 CEST 2019" />
+     </Query>
+     <RecordType id="212" name="Experiment">
+       <Permissions />
+     </RecordType>
+     <RecordType id="215" name="UnicornExperiment">
+       <Permissions />
+       <Parent id="212" name="Experiment" />
+     </RecordType>
+     <Record id="230">
+       <Permissions />
+       <Parent id="215" name="UnicornExperiment" />
+       <Property id="216" name="date" datatype="DATETIME" importance="FIX">
+         2025-10-11
+         <Permissions />
+       </Property>
+       <Property id="217" name="species" datatype="TEXT" importance="FIX">
+         Unicorn
+         <Permissions />
+       </Property>
+       <Property id="214" name="Conductor" datatype="TEXT" importance="FIX">
+         Anton Beta
+         <Permissions />
+       </Property>
+       <Property id="221" name="Photo" datatype="Photo" importance="FIX">
+         226
+         <Permissions />
+       </Property>
+       <Property id="220" name="LabNotes" datatype="LabNotes" importance="FIX">
+         227
+         <Permissions />
+       </Property>
+       <Property id="218" name="UnicornVideo" datatype="UnicornVideo" importance="FIX">
+         228
+         <Permissions />
+       </Property>
+       <Property id="219" name="UnicornECG" datatype="UnicornECG" importance="FIX">
+         229
+         <Permissions />
+       </Property>
+     </Record>
+   </Response>
+
+We can see, that in fact two ``RecordTypes`` and one ``Record`` are
+returned. Furthermore the response contains some additional information:
+
+-  Attributes in the ``Response`` tag:
+
+   -  ``srid="b56e3d1a442460c46dde924a54e8afba"`` A unique identifier
+      for this request.
+   -  ``timestamp="1561453363382"`` The `UNIX
+      timestamp <https://en.wikipedia.org/wiki/Unix_time>`__ for this
+      request.
+   -  ``baseuri="https://demo.indiscale.com"`` The base URI of the
+      instance of CaosDB that performed the request.
+   -  ``count="3"`` The number of results (2 ``RecordTypes`` and 1
+      ``Record``)
+
+-  Information about the user in ``UserInfo``. In this case we have not
+   logged in, so we are anonymous.
+-  Detailled information about the query. This includes for example the
+   parse tree and can be used for debugging and testing. Depending on
+   the settings of the server instance, this tag includes more or less
+   detail, for example a more detailled transaction benchmark.
+
+More details about the retrieve
+-------------------------------
+
+The cURL statement used in the previous section made use of a lot of
+default settings for cURL. Let’s have a closer look behind the options.
+(I assigned the above URL to a shell variable, to make the statement
+more readable.)
+
+.. code:: bash
+
+   URL="https://demo.indiscale.com/Entity/?query=FIND%20Experiment"
+
+   curl -X GET -b cookie.txt -D head.txt $URL
+
+This command specifies three more options:
+
+-  ``-X GET`` Do a GET request. This can of course be replaced by POST,
+   PUT, DELETE or any other HTTP operation.
+-  ``-b cookie.txt`` This instructs cURL to use cookies from the file
+   ``cookie.txt`` (which we don’t have yet, see below)
+-  ``-D head.txt`` Tell cURL to store the received header in the file
+   ``head.txt``.
+
+Running this command will give us a similar response than in the
+previous section, but additionally a file ``head.txt``:
+
+::
+
+   HTTP/1.1 200 OK
+   Content-Type: text/xml; charset=UTF-8
+   Date: Tue, 25 Jun 2019 09:17:23 GMT
+   Accept-Ranges: bytes
+   Server: Restlet-Framework/2.3.12
+   Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept
+   Transfer-Encoding: chunked
+
+There is nothing special in that header. Most importantly the request
+has lead to a response without HTTP error.
+
+Logging in
+----------
+
+You might have asked yourself, what we need the cookie for. The simple
+answer is: authentication. For many operations done on the CaosDB server
+we have to log in first. The demo instance is configured to allow for
+anonymous read access by default. But depending on the instance you are
+accessing, even this might be disallowed.
+
+You can log in to the server using cURL with the following command:
+
+.. code:: bash
+
+   URL="https://demo.indiscale.com/login"
+
+   curl -X POST -c cookie.txt -D head.txt -H "Content-Type: application/x-www-form-urlencoded" -d username=salexan -d password=$PW $URL
+
+So here we are doing a ``POST`` request instead of a ``GET``. We are
+instructing cURL to use the ``cookie.txt`` file to **store** the cookies
+it recieves. This is done using the ``-c`` option (instead of the ``-b``
+option above). This time we are explicitely specifying the content type
+of the content we are sending (using the ``POST`` request) with the
+``-H`` option. The actual content sent are the two fields specified
+using the ``-d`` options. This boils down to the two key-value-pairs
+“username=” and “password=”.
+
+This time we are sending the information to a different context
+indentified by ``/login``.
+
+If you don’t want to supply your passowrd in plain text you can for
+example use a password manager (like I do), in my case
+`pass <https://www.passwordstore.org/>`__ as follows to store your
+password in the variable ``$PW``:
+
+.. code:: bash
+
+   PW=$(pass your/passowrd/identifier)
+
+Custom Certificates
+-------------------
+
+If you are running your own CaosDB instance it might be necessary to use
+a custom SSL certificate.
+
+You can specify this using the option ``--cacert``, e.g.:
+
+::
+
+   --cacert "/path/to/certificate/root.cert.pem"
+
+Uploading files
+---------------
+
+According to the specification, a file upload is a POST with multipart
+form data. This can be achieved using CURL with the following simple
+command line:
+
+.. code:: bash
+
+   curl -b cookie.txt \
+        -F "FileRepresentation=<file.xml" \
+        -F "testfile.bla=@testfile.bla" \
+        $URL
+
+Here I am using a previously stored cookie from cookie.txt.
+
+There are two prerequisites to executing the above command: - The file
+representation ``file.xml`` which is in this case stored in a file. -
+The actual file to be uploaded with name ``testfile.bla``. The left hand
+side of the assignment is very important as the identifier given here
+(which in this case is ``testfile.bla``) will be used in the XML for
+identifying the file that is described in the corresponding XML tag.
+
+Let’s have a look at the contents of file.xml:
+
+.. code:: xml
+
+   <Post>
+     <File upload="testfile.bla" destination="/test/testfile.bla" description="bla" 
+   checksum="672f8ff4ae8530de295f9dd963724947841e6277edec3b21820b5e44d0a64baef90fb04e22048028453d715f79357acc5bd2d566fe6ede65f981ba3dda06bae4"
+   size="3"/>
+   </Post>
+
+The attributes have the following meaning: - ``upload=testfile.bla`` The
+filename given here is actually no filename, but an identifier to find
+the multipart data chunk that contains the file. I called it
+``testfile.bla`` for simplicity here. -
+``destination="/test/testfile.bla"`` The destination path on the CaosDB
+server file system.
+
+Before looking at the other attributes let’s have a look at the file
+``testfile.bla`` itself:
+
+::
+
+   ok
+
+The file has size “3” which can be verified on linux using a:
+
+.. code:: bash
+
+   stat testfile.bla
+
+It’s hashsum is important for checking the integrity after the transfer
+to the server. It can be computed on linux using:
+
+.. code:: bash
+
+   sha512sum testfile.bla
+
+These information has to be supplied as the remaining attributes to the
+XML.
diff --git a/src/doc/administration/maintenance.rst b/src/doc/administration/maintenance.rst
index 8a3397614b409a4ad498d0d8898c79cd0eb69bd6..67d8475bf469957416a6f42e495db07b1533f151 100644
--- a/src/doc/administration/maintenance.rst
+++ b/src/doc/administration/maintenance.rst
@@ -57,3 +57,11 @@ If you want to restore the entities exported to XML, you can do::
          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).
+
diff --git a/src/doc/administration/server_side_scripting.rst b/src/doc/administration/server_side_scripting.rst
index 59bce98682c6d1190fc90681fc8e14fd483e891e..b187d1d566fdcac004649506d036807276cbc4ae 100644
--- a/src/doc/administration/server_side_scripting.rst
+++ b/src/doc/administration/server_side_scripting.rst
@@ -81,5 +81,5 @@ An invocation via a button in javascript could look like:
       return scripting_form[0];
     }
 
-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>`
 
diff --git a/src/doc/concepts.rst b/src/doc/concepts.rst
index ec93719548a7d7c577ac62c6615b3fcf24111b42..0db6186302302767c49735bcdfa81364685573f9 100644
--- a/src/doc/concepts.rst
+++ b/src/doc/concepts.rst
@@ -1,29 +1,15 @@
-===================================
+
 Basic concepts of the CaosDB server
 ===================================
 
+.. toctree::
+   :hidden:
+   :glob:
+
+   Data Model <Data-Model>
+   Permissions
+   roles
+
 The CaosDB server provides the HTTP API resources to users and client libraries.  It uses a plain
 MariaDB/MySQL database as backend for data storage, raw files are stored separately on the file
 system.
-
-
-Configuration
--------------
-
-Administrators may configure the server through :doc:`configuration
-files<administration/configuration>`.  Additionally, configurations may be set via the API if the
-server is in debug mode.
-
-
-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 this is the ``anonymous`` role.  The
-permissions for an action which involves one or more objects are set either manually or via default
-permissions which can be configured.  For more detailed information, there is separate
-:doc:`documentation of the permission system<permissions>`.
-
-
-
-
diff --git a/src/doc/conf.py b/src/doc/conf.py
index 84e676394d19e8ec35f241f1cf3cc202dafa0d4d..fb3d6264cc4add018b83d08378813aab9d10964d 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.2'
+version = '0.3'
 # The full version, including alpha/beta/rc tags
-release = '0.2'
+release = '0.3'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/src/doc/entities.png b/src/doc/entities.png
new file mode 100644
index 0000000000000000000000000000000000000000..ba3e111a78ddec275636ff0b7e8b324edb13f7be
Binary files /dev/null and b/src/doc/entities.png differ
diff --git a/src/doc/index.rst b/src/doc/index.rst
index e41d318a2dbf7323e5cb327be1c7a3fe0b5d61ad..6a9013c51720f13b4f473d9213bb64b8d4d27eec 100644
--- a/src/doc/index.rst
+++ b/src/doc/index.rst
@@ -11,8 +11,10 @@ Welcome to caosdb-server's documentation!
    Getting started <README_SETUP>
    Concepts <concepts>
    Query Language <CaosDB-Query-Language>
-   administration/*
+   administration
    development/*
+   specification/index.rst
+   Glossary
    API documentation<_apidoc/packages>
 
 Welcome to the CaosDB, the flexible semantic data management toolkit!
diff --git a/src/doc/roles.md b/src/doc/roles.md
new file mode 100644
index 0000000000000000000000000000000000000000..80729fb11f8113c64da7e9bf26b04de68fdd6681
--- /dev/null
+++ b/src/doc/roles.md
@@ -0,0 +1,35 @@
+# Roles #
+
+## Users and roles ##
+
+Interaction with CaosDB happens either as an authenticated *user* or without
+authentication.  In CaosDB, users can have zero, one or more *roles*, several
+users may have the same role, and there may be roles without any users.
+
+## What are users and roles good for? ##
+
+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](manuals/general/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
+of an entity is seen as defining the ownership of an object: Being able to
+change the permissions is equivalent to being the owner.
+
+## Special roles ##
+
+There are some special roles, which are automatically assigned to users:
+
+- `anonymous` :: If requests are sent to the server without authentication, so
+  that no user is defined, the request always has the role `anonymous`.
+- *User names* :: An authenticated user implicitly has a role with the same name
+  as the user name.
+- `?OWNER?` :: If a user has the permission to edit the permissions of an
+  entity, the user automatically has the `?OWNER?` roler for that entity.
+- `?OTHER?` :: The `?OTHER?` role is the contrary to the `?OWNER?` role: A user
+  is either the owner of an entity, or has the role `?OTHER?`.
+
+Except for the `anonymous` role, these special roles are not returned by the
+server, but can nevertheless be used to define
+[permissions](manuals/general/permissions).
diff --git a/src/doc/specification/AbstractProperty.md b/src/doc/specification/AbstractProperty.md
new file mode 100644
index 0000000000000000000000000000000000000000..d594f81967cacd708699ceaeccd9188685a7184d
--- /dev/null
+++ b/src/doc/specification/AbstractProperty.md
@@ -0,0 +1,331 @@
+# AbstractProperty Specification
+
+## Introduction
+An `AbstractProperty` is one of the basal [objects of HeartDB](./HeartDBObject).
+An `AbstractProperty` MUST have the following _qualities_ (shortcut in brackets):
+* a persistent id (`id`)
+* an unique name (`name`)
+* a description (`description`)
+* a type (`type`)
+* a generator `generator`)
+* a creator (`creator`) 
+* a timestamp of it's date of creation (`created`) 
+* a set of owners and owning groups (`owner`)
+* a set rules which controls the access to that `AbstractProperty` (`permission`)
+Depending on the `AbstractProperty's` type it MUST have one of the following _qualities_: 
+* a unit (`unit`)
+* a RecordType which is referenced by any instantiating ConcreteProperty. (`reference`)
+This is described below. 
+
+## Property Types
+An `AbstractProperty` MUST have one of the following 7 types:
+* `text`
+* `integer`
+* `double`
+* `datetime`
+* `reference`
+* `file`
+
+An `AbstractProperty` of type `text`, `datetime`, or `file` MUST NOT have any unit or referenced RecordType. A `double`, `integer` AbstractProperty MUST have a unit but MUST NOT have a referenced RecordType. A `reference` `AbstractProperty` MUST have referenced RecordType und MUST NOT have a unit.
+
+## XML Representation of AbstractProperty Objects
+
+An `AbstractProperty` is represented in xml by a `<Property/>` tag. It's _qualities_ except for `owner` and `permission` are represented by an xml attribute of the same name. E.g `<Property name="length"/> denotes an AbstractProperty which {{{name` is _length_. The `owner` and `permission` qualities are not in use, yet.
+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:
+#### text
+
+        <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="text" />
+#### integer
+
+        <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="integer" unit="$unit" />
+#### double
+
+        <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="double" unit="$unit" />
+#### datetime
+
+        <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="datetime" />
+#### reference
+
+        <Property id="$id" name="$name" description="$description" generator="$generator" creator="$creator" created="$created" type="reference" reference="$reference" />
+#### file
+
+        <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).
+
+### 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:
+#### text
+
+        <Property name="$name" description="$description" generator="$generator" type="text" />
+#### integer
+
+        <Property name="$name" description="$description" generator="$generator" type="integer" unit="$unit" />
+#### double
+
+        <Property name="$name" description="$description" generator="$generator" type="double" unit="$unit" />
+#### datetime
+
+        <Property name="$name" description="$description" generator="$generator" type="datetime" />
+#### reference
+
+        <Property name="$name" description="$description" generator="$generator" type="reference" reference="$reference" />
+#### file
+
+        <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.
+* 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).
+
+----
+## Examples
+### GET Requests
+#### Single-Get
+*Request:*
+
+        GET http://localhost:8122/mpidsserver/AbstractProperty/1
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property id="1" name="explanation" type="text" description="explains the thing" generator="Timm (manually)"/>
+        </Response>
+----
+*Request:*
+
+        GET http://localhost:8122/mpidsserver/AbstractProperty/explanation
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property id="1" name="explanation" type="text" description="explains the thing" />
+        </Response>
+----
+#### Multi-Get
+*Request:*
+
+        GET http://localhost:8122/mpidsserver/AbstractProperty/explanation&2&3
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property id="1" name="explanation" type="text" description="explains the thing" />
+          <Property id="2" name="comment" type="text" description="A comment" generator="Timm (manually)"/>
+          <Property id="3" name="name" type="text" description="Name" generator="Timm (manually)" />
+        </Response>
+----
+#### Get all
+*Request:*
+
+        GET http://localhost:8122/mpidsserver/AbstractProperty/
+        GET http://localhost:8122/mpidsserver/AbstractProperty
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response />
+[View Ticket #2](http://dev.bmp.ds.mpg.de/heartdb/ticket/2)
+#### Erroneous Requests
+##### Non-existing
+*Request:*
+
+        GET http://localhost:8122/mpidsserver/AbstractProperty/123456
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property id="123456">
+            <Error code="201" description="Property does not exist. " />
+          </Property>
+        </Response>
+----
+### POST Requests
+
+*Request is to be sent to:*
+
+        POST http://localhost:8122/mpidsserver/AbstractProperty/
+        POST http://localhost:8122/mpidsserver/AbstractProperty
+----
+#### Single-Post
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty1" type="integer" description="This is TimmsIntegerProperty1" unit="kg" generator="Timm (manually)"/>
+        </Post>
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property id="39" name="TimmsIntegerProperty1" type="integer" unit="kg" description="This is TimmsIntegerProperty1" generator="Timm (manually)"/>
+        </Response>
+----
+*Request body (with a reference to Property 12345):*
+
+        <Post>
+          <Property name="Timms reference property 1" type="reference" reference="12345" description="This is Timms reference property 1"/>
+        </Post>
+----
+#### Multi-Post
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty7" type="integer" description="This is TimmsIntegerProperty7" unit="kg" generator="Timm (manually)"/>
+          <Property name="TimmsIntegerProperty8" type="integer" description="This is TimmsIntegerProperty8" unit="kg" generator="Timm (manually)"/>
+          <Property name="TimmsIntegerProperty9" type="integer" description="This is TimmsIntegerProperty9" unit="kg" generator="Timm (manually)"/>
+        </Post>
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          ...
+          <Property id="41" name="TimmsIntegerProperty9" type="integer" unit="kg" description="This is TimmsIntegerProperty9" generator="Timm (manually)"/>
+        </Response>
+
+----
+#### Erroneous Requests
+##### No Description
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty3" type="integer" unit="kg" generator="Timm (manually)"/>
+        </Post>
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property name="TimmsIntegerProperty3" type="integer" unit="kg">
+            <Error code="202" description="Property has no Description. " />
+          </Property>
+        </Response>
+----
+##### No generator
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty3" type="integer" unit="kg" description="This is TimmsIntegerProperty4" />
+        </Post>
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property name="TimmsIntegerProperty3" type="integer" unit="kg" >
+            <Error code="202" description="Property has no generator. " />
+          </Property>
+        </Response>
+----
+##### No Type
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty4" description="This is TimmsIntegerProperty4" unit="kg" generator="Timm (manually)"/>
+        </Post>
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property name="TimmsIntegerProperty4" unit="kg" description="This is TimmsIntegerProperty4" generator="Timm (manually)" >
+            <Error code="252" description="Property has no PropertyType" />
+          </Property>
+        </Response>
+----
+##### No Unit
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty5" type="integer" description="This is TimmsIntegerProperty5" generator="Timm (manually)"/>
+        </Post>
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property name="TimmsIntegerProperty5" type="integer" description="This is TimmsIntegerProperty5" generator="Timm (manually)">
+            <Error code="202" description="Property has no Unit. " />
+          </Property>
+        </Response>
+----
+##### No Name
+*Request body:*
+
+        <Post>
+          <Property type="integer" description="This is TimmsIntegerProperty6" unit="kg" generator="Timm (manually)"/>
+        </Post>
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property type="integer" unit="kg" description="This is TimmsIntegerProperty6" generator="Timm (manually)">
+            <Error code="202" description="Property has no Name. " />
+          </Property>
+        </Response>
+----
+##### Invalid Name
+*Request body:*
+
+        <Post>
+          <Property id="39" name="TimmsIntegerProperty7" type="integer" description="This is TimmsIntegerProperty7" unit="kg" generator="Timm (manually)"/>
+        </Post>
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property id="39" name="TimmsIntegerProperty7" type="integer" unit="kg" description="This is TimmsIntegerProperty7" generator="Timm (manually)">
+            <Error code="204" description="Property has an invalid Name. " />
+          </Property>
+        </Response>
+----
+##### Mixed
+*Request body:*
+
+        <Post>
+          <Property name="TimmsIntegerProperty2" type="integer" description="This is TimmsIntegerProperty2" unit="kg" generator="Timm (manually)"/>
+          <Property name="TimmsIntegerProperty3" type="integer" unit="kg" generator="Timm (manually)"/>
+          <Property name="TimmsIntegerProperty4" description="This is TimmsIntegerProperty4" unit="kg" generator="Timm (manually)"/>
+          <Property name="TimmsIntegerProperty5" type="integer" description="This is TimmsIntegerProperty5" generator="Timm (manually)"/>
+          <Property type="integer" description="This is TimmsIntegerProperty6" unit="kg" generator="Timm (manually)"/>
+          <Property id="39" name="TimmsIntegerProperty7" type="integer" description="This is TimmsIntegerProperty7" unit="kg" generator="Timm (manually)"/>
+        </Post>
+
+*Response:*
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response>
+          <Property type="integer" unit="kg" description="This is TimmsIntegerProperty6" generator="Timm (manually)">
+            <Error code="202" description="Property has no Name. " />
+          </Property>
+          <Property id="39" name="TimmsIntegerProperty7" type="integer" unit="kg" description="This is TimmsIntegerProperty7" generator="Timm (manually)">
+            <Error code="204" description="Property has an invalid Name. " />
+          </Property>
+        ...
+        </Response>
+
+= DELETE AbstractProperties = 
+
+HTTP-DELETE-requests are to be send to `http://${host}:${port}/mpidsserver/AbstractProperty/...`. Default port is 8123.
+
+*Example*
+{{{ 
+DELETE http://${host}:${port}/mpidsserver/AbstractProperty/1&2&3&4
+}}}
+
+
+## TODO
+### UPDATE AbstractProperties
+Notes:
+* ids are persistent. They cannot be changed.
+* Maybe we should take the PropertyTypes as persistent, too or just allow a few changes: Double <-> Integer?
+Roadmap:
+1) Specify and implement changing the name, description and unit of AbstractProperties.
+2) Specify and implement changing the reference of an abstract reference property.
+3) Anything else...
diff --git a/src/doc/specification/Authentication.md b/src/doc/specification/Authentication.md
new file mode 100644
index 0000000000000000000000000000000000000000..9ec5bcd74ffb68ee33084f739d6d547a3e7a9bb8
--- /dev/null
+++ b/src/doc/specification/Authentication.md
@@ -0,0 +1,82 @@
+# 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:
+
+| `username:` | `$username` | 
+|-------------|-------------|-
+| `password:` | `$SHA512ed_password` |
+
+
+## Sessions
+
+### Login
+
+#### Request Challenge
+
+ * `GET http://host:port/mpidsserver/login?username=$username`
+ * `GET http://host:port/mpidsserver/login` with `username` header
+
+*no password required to be sent over http*
+
+The request returns an AuthToken with a login challenge as a cookie. The AuthToken is a dictionary of the following form:
+
+
+        {scope=$scope;
+        mode=LOGIN;
+        offerer=$offerer;
+        auth=$auth
+        expires=$expires;
+        date=$date;
+        hash=$hash;
+        session=$session;
+        }
+
+ $scope:: A uri pattern string. Example: ` {**/*} `
+ $mode:: `ONETIME`, `SESSION`, or `LOGIN`
+ $offerer:: A valid username
+ $auth:: A valid username
+ $expires:: A `YYYY-MM-DD HH:mm:ss[.nnnn]` date string
+ $date:: A `YYYY-MM-DD HH:mm:ss[.nnnn]` date string
+ $hash:: A string
+ $session:: A string
+
+The challenge is solved by concatenating the `$hash` string and the user's `$password` string and calculating the sha512 hash of both. Pseudo code:
+
+
+        $solution = sha512($hash + sha512($password))
+
+#### Send Solution
+
+The old $hash string in the cookie has to be replaces by $solution and the cookie is to be send with the next request:
+
+`PUT http://host:port/mpidsserver/login`
+
+The server will return the user's entity in the HTTP body, e.g.
+
+
+        <Response ...>
+          <User name="$username" ...>
+            ...
+          </User>
+        </Response>
+
+and a new AuthToken with `$mode=SESSION` and a new expiration date and so on. This AuthToken cookie is to be send with every request.
+
+### Logout
+
+Send 
+
+`PUT http://host:port/mpidsserver/logout`
+
+with a valid AuthToken cookie. No new AuthToken will be returned and no AuthToken with that `$session` will be accepted anymore.
+
+### Commandline solution with `curl` ##
+
+To use curl for talking with the server, first save your password into a variable:
+`PW=$(cat)`
+
+The create a cookie in `cookie.txt` like this (note that this makes your password visible for a short time to everyone on your system:
+`curl -X POST -c cookie.txt -D head.txt -H "Content-Type: application/x-www-form-urlencoded" -d username=<USERNAME> -d password="$PW" --insecure "https://<SERVER>/login`
+
+To use the cookie, pass it on with later requests:
+`curl -X GET -b cookie.txt --insecure "https://<SERVER>/Entity/12345"`
+
diff --git a/src/doc/specification/Datatype.md b/src/doc/specification/Datatype.md
new file mode 100644
index 0000000000000000000000000000000000000000..ffc9794ea36cfe748e8c11cee2bcd32adc8ebe0a
--- /dev/null
+++ b/src/doc/specification/Datatype.md
@@ -0,0 +1,95 @@
+# Datatype
+
+## TEXT
+* Description: TEXT stores stores any text values.
+* Range: Any [utf-8](https://en.wikipedia.org/wiki/UTF-8) encodable sequence of characters with maximal 65,535 bytes. (Simply put: In most cases, any text with less than 65,535 letters and spaces will work. But if you use special characters like `à`, `€` or non-latin letters then the number of bytes, which are needed to store it, increases. Then the effective maximal length is smaller than 65,535. A bad case scenario would be a text in Chinese. Chinese characters need about three times the space of letters from the latin alphabet. Therefore, only 21845 Chinese characters can be stored within this datatype. Which is still quite a lot I guess :D)
+* Examples: 
+  * `Am Faßberg 17, D-37077 Göttingen, Germany`
+  * `Experiment went well until the problem with the voltmeter occured. Don't use the results after that.`
+  * `someone@email.org`
+  * `Abstract: bla bla bla ...`
+  * `Head of Group`
+  * `http://www.bmp.ds.mpg.de`
+  * 
+
+        A. Schlemmer, S. Berg, TK Shajahan, S. Luther, U. Parlitz,
+           Quantifying Spatiotemporal Complexity of Cardiac Dynamics using Ordinal Patterns,
+           37th Annual International Conference of the IEEE Engineering in Medicine and Biology Society (EMBC), 2015, doi: 10.1109/EMBC.2015.7319283
+
+----
+
+## BOOLEAN
+* Description: BOOLEAN stores boolean `TRUE` or `FALSE`. It is therefore suitable for any variable that represents that something is the case or not.
+* Accepted Values: `TRUE` or `FALSE`, case insensitive (i.e. it doesn't matter if you use capitals or small letters).
+* Note: You could also use a TEXT datatype to represent booleans (or even INTEGER or DOUBLE). But it makes a lot of sense to use this special datatype as it ensures that only the two possible values, `TRUE` or `FALSE` are inserted into the database. Every other input would be rejected. This helps to keep the database understandable and to avoid mistakes.
+
+----
+
+## INTEGER
+* Description: INTEGER stores integer numbers. If you need floating point variables, take a look at DOUBLE.
+* Range: `-2147483648` to `2147483647`, `-0` is interpreted and stored as `0`.
+* Note: This rather limited range is just provisional. It can be extended with low effort as soon as requested.
+
+----
+
+## DOUBLE
+* Description: DOUBLE stores floating point numbers with a double precision as defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
+* Range: 
+  * From `2.2250738585072014E-308` to `1.7976931348623157E308` (negative and positive) with a precision of 15 decimals. 
+  * Any other decimal number _might work_ but it is not guaranteed.
+  * `-0`, `0`, `NaN`, `-inf` and `inf`
+* Note: The server generates a warning when the precision of the submitted DOUBLE value is to high to be preserved.
+
+----
+
+## DATETIME
+The DateTime data type exists in (currently) three flavors which are dynamically chosen during parsing on the the serverside. The flavors have different ranges, support of time zones and intended use cases. Only the first two flavors are actually implemented for storage and queries. The third one is implemented for queries exclusively.
+
+### UTCDateTime
+* Description: This DATETIME flavor stores values which represent a single point of time according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) with the format specified by [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) (Combined date and time). It does support [UTC Leap Seconds](https://en.wikipedia.org/wiki/Leap_second) and time zones.
+* Range: From `-9999-01-01T00:00:00.0UTC` to `9999-12-31T23:59:59.999999999UTC` with nanosecond precision.
+* Examples:
+  * `2016-01-01T13:23:00.0CEST` which means _January 1, 2016,  1:23 PM, Central European Summer Time_.
+  * `-800-01-01T13:23:00.0` which means _January 1, 800 BC,  1:23 PM, UTC_.
+* Note:
+  * It is allowed to ommit the nanosecond part of a UTCDateTime (`2016-01-01T13:23:00CEST`). This indicates a precision of seconds for a UTCDateTime value.
+
+### Date
+ Description:: This DATETIME flavor stores values which represent a single date, month or year according to the [gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_Calendar). A month/year is conceived as a single date with the presion of a month/year. This concept is useful if you try to understand the query semantics which are explained [elsewhere](./QueryLanguage#POVDateTime).
+ Format:: `Y[YYY][-MM[-dd]]` (where square brackets mean that the expression is optional).
+ Range:: Any valid date according to the gregorian calendar from `-9999-01-01` to `9999-12-31` (and respective dates with lower precision. E.g. the year `-9999`). There is no year `0`.
+* Note: Date is a specialization of [#SemiCompleteDateTime]. 
+
+### SemiCompleteDateTime
+* Description: A generalization of the _Date_ and _UTCDateTime_ flavors. In general, there is no time zone support. Although this flavor is not yet storable in general, it is implemented for search queries yet. I.e. you might search for `FIND ... date>2015-04-03T20:15` yet.
+* Format: `Y[YYY]['-MM[-dd[Thh:[mm[:ss[.ns]]]]]]]`. 
+* Special Properties: For every SemiCompleteDateTime _d_ there exists a _Inclusive Lower Bound_ (`d.ILB`) and a _Exclusive Upper Bound_ (`d.EUB`). That means, a SemiCompleteDateTime can be interpreted as an interval of time. E.g. `2015-01` is the half-open interval `[2015-01-01T00:00:00.0, 2016-01-01T00:00:00.0)`. ILB and EUB are UTCDateTimes respectively. These properties are important for the semantics of the the query language, especialy the [operators](./QueryLanguage#POVDateTime). 
+
+### Future Flavors
+Please file a new feature request as soon as you need them.
+* Time:: For a time of the day (without the date). Supports time zones.
+* FragmentaryDateTime:: For any fragmentary DateTime. That is an arbitrary combination of year, month, day of week, day of month, day of year, hour of day, minute, seconds (and nanoseconds). This flavor is useful for recurrent events like a bus schedule (_Saturday, 7:30_) or the time of a standing order for money transfer (_third day of the month_).
+
+----
+
+## REFERENCE
+* Description: REFERENCE values store the [Valid ID](./Glossary#valid-id) of an existing entity. The are useful to establish links between two entities. 
+* Accepted Values: Any [Valid ID](./Glossary#valid-id) or [Valid Unique Existing Name](./Glossary#valid-unique-existing-name) or [Valid Unique Temporary ID](./Glossary#valid-unique-temporary-id) or [Valid Unique Prospective Name](./Glossary#valid-unique-prospective-pame).
+* Note:
+  * After beeing processed successfully by the server the REFERENCE value is normalized to a [Valid ID](./Glossary#valid-id). I.e. it is guaranteed that a REFERENCE value of a valid property is a positive integer.
+
+### FILE
+* Description: A FILE is a special REFERENCE. It only allows entity IDS which belong to a File.
+
+### RecordType as a data type
+* Furthermore, any RecordType can be used as a data type. This is a variant of the REFERENCE data type where any entity is a valid value which is a child of the RecordType in question.
+* Example:
+  * Let `Person` be a RecordType, `Bertrand Russel` be a child of `Person`. Then `Bertrand Russel` is a valid value for a property with a `Person` data type.
+
+## LIST
+* Description: A LIST is always a list of something which has another data type. E.g. A LIST of TEXT values, a LIST of REFERENCES value, etc. Here we call TEXT resp. REFERENCE the **Element Data Type**. The LIST data type allows you to store an arbitrary empty or non-empty ordered set (with duplicates) of values of the *same* data type into one property. Each value must be a valid value of the Element Data Type.
+* Example:
+  * LIST of INTEGER: ```[0, 2, 4, 5, 8, 2, 3, 6, 7]```
+  * LIST of Person, while `Person` is a RecordType: ```['Bertrand Russel', 'Mahatma Ghandi', 'Mother Therese']```
+
+
diff --git a/src/doc/specification/Fileserver.md b/src/doc/specification/Fileserver.md
new file mode 100644
index 0000000000000000000000000000000000000000..fda2ec183c909e494a407f74eb1f4d03a5a23625
--- /dev/null
+++ b/src/doc/specification/Fileserver.md
@@ -0,0 +1,142 @@
+# 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. 
+
+## 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. 
+
+Now, the user may send a pick up request to `POST http://host:port/mpidsserver/FilesDropOff` with a similar body:
+
+        <Post>
+          <File pickup="$path_dropoffbox" destination="$path_filesystem" description="$description" generator="$generator"/>
+          ...
+        </Post>
+
+where
+* $path_dropoffbox is the actual relative path of the dropped file or folder in the DropOffBox,
+* $path_filesystem is the designated relative path of that object in the internal file system,
+* $description is a description of the file to be uploaded,
+* $generator is the tool or client used for pushing this file.
+  
+After a successful pick up the server will return:
+
+        <Response>
+          <File description="$description" path="$path" id="$id" checksum="$checksum" size="$size" />
+          ...
+        </Response>
+
+where
+* $id is the new generated id of that file and 
+* $path is the path of the submitted file or folder relative to the file system's root.
+
+### 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).
+
+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. 
+
+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:
+* `Content-type: text/plain; charset=UTF-8`
+* `Content-disposition: form-data; name="FileRepresentation"`
+
+If the content type of the first part is not `text/plain; charset=UTF-8` the server will return error 418. If the body is not actually encoded in UTF-8 the servers behaviour is not defined. If the field name of the first part is not `FileRepresentation` the server will return error 419.
+
+The body of that first part is to be an xml document of the following form: 
+
+
+        <Post>
+          <File upload="$temporary_identifier" destination="$path_filesystem" description="$description" checksum="$checksum" size="$size"/>
+          ...
+        </Post>
+
+where
+* $temporary_identifier is simply a arbitrary name, which will be used to identify this `<File>` tag with a uploaded file in the other form-data parts.
+* $path_filesystem is the designated relative path of that object in the internal file system,
+* $description is a description of the file to be uploaded,
+* $size is the files size in bytes,
+* $checksum is a SHA-512 Hash of the file.
+
+The other parts (which must be at least one) may have any appropriate media type. `application/octet-stream` is a good choice for it is the default for any upload file according to [rfc1867](http://www.ietf.org/rfc/rfc1867.txt). Their field name may be any name meeting the requirements of [rfc1867](http://www.ietf.org/rfc/rfc1867.txt) (most notably they must be unique within this POST). But in order to identify the corresponding xml file representation of each file the `filename` parameter of the content-disposition header has to be set to the proper $temporary_identifier. The Content-disposition type must be `form-data`:
+* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier"`
+
+Finally the body of these parts have to contain the file encoded in the proper `Content-Transfer-Encoding`.
+
+
+If a file part has a `filename` parameter which doesn't occur in the xml file representation the server will return error 420. The file will not be stored anywhere. If an xml file representation has no corresponding file to be uploaded (i.e. there is no part with the same `filename`) the server will return error 421. Some other error might occur if the checksum, the size, the destination etc. are somehow corrupted. 
+
+#### Folders
+
+Uploading folders works in a similar way. The first part of the `multipart/form-data` document is to be the representation of the folders:
+
+
+        <Post>
+          <File upload="$temporary_identifier" destination="$path_filesystem" description="$description" checksum="$checksum" size="$size"/>
+          ...
+        </Post>
+
+The root folder is represented by a part which has a header of the form:
+* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier/"`
+The slash at the end of the `filename` indicates that this is a folder, not a file. Consequently, the body of this part will be ignored and should be empty.
+Any file with the name `$filename` in the root folder is represented by a part which has a header of the form:
+* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier/$filename"`
+Any sub folder with the name `$subfolder` is represented by a part which has a header of the form: 
+* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier/$subfolder/"`
+
+Likewise, a complete directory tree can be transfered by appending the structure to the `filename` header field.
+
+**Example**:
+Given the structure
+
+        rootfolder/
+        rootfolder/file1
+        rootfolder/subfolder/
+        rootfolder/subfolder/file2
+
+an upload document would have the following form:
+
+        ... (HTTP Header)
+        Content-type: multipart/form-data, boundary=AaB03x
+        
+        --AaB03x
+        content-disposition: form-data; name="FileRepresentation"
+        
+        <Post>
+          <File upload="tmp1234" destination="$path_filesystem" description="$description" checksum="$checksum" size="$size"/>
+        </Post>
+        
+        --AaB03x
+        content-disposition: form-data; name="random_name1"; filename="temp1234/"
+        
+        --AaB03x
+        content-disposition: form-data; name="random_name1"; filename="temp1234/file1"
+        
+        Hello, world! This is file1.
+        
+        --AaB03x
+        content-disposition: form-data; name="random_name1"; filename="temp1234/subfolder/"
+        
+        --AaB03x
+        content-disposition: form-data; name="random_name1"; filename="temp1234/subfolder/file2"
+        
+        Hello, world! This is file2.
+        
+        --AaB03x--
+
+
+(**Timm 2014-06-17: to be continued**)
+
+## Consistency checks
+To start a consistency check on either the complete file system or a subdirectory, add the `fileStorageConsistency` flag to a retrieve query.  In a `GET` request, simply add `...?fileStorageConsistency=<OPTIONS>` to the URL.  Possible options are (currently, only one of them?):
+
+- `-t <TIMEOUT>` :: The timeout for the query (in seconds?)
+- `-c <TESTCASE>` :: To trigger internal test cases.
+- `<PATH>` :: The path in the file system where searching files should start.  If omitted or `\`, the full file system will be checked.
+
+One example, using curl and an existing cookie:
+`curl -X GET -G -b cookie.txt -d "fileStorageConsistency=Analysis/VideoAnalysis/masks/" --insecure "https://<SERVER>/Entity/12345"`
+
+
diff --git a/src/doc/specification/Paging.md b/src/doc/specification/Paging.md
new file mode 100644
index 0000000000000000000000000000000000000000..fb994639204704618af2b1c7a8ccb32301013af3
--- /dev/null
+++ b/src/doc/specification/Paging.md
@@ -0,0 +1,25 @@
+# Paging
+The Paging flag splits the retrieval of a (possibly huge) number entities into pages.
+
+## Syntax
+
+
+          flag   = name, [":", value];
+          name   = "P";
+          value  = [ index ], ["L", length]];
+          index  =  ? any positive integer ?;
+          length =  ? any positive integer ?;
+
+## Semantics
+
+The `index` (starting with zero) denotes the index of the first entity to be retrieved. The `length` is the number of entities on that page. If `length` is omitted, the default number of entities is returned (as configured by a server contant called ...). If only the `name` is given the paging behaves as if the `index` has been zero.
+
+## Examples
+
+`http://localhost:8123/mpidsserver/Entities/all?flags=P:24L50` returns 50 entities starting with the 25th entity which would be retrieved without paging.
+
+`http://localhost:8123/mpidsserver/Entities/all?flags=P:24` returns the default number of entities starting with the 25th entity which would be retrieved without paging.
+
+`http://localhost:8123/mpidsserver/Entities/all?flags=P:L50` returns 50 entities starting with the first entity which would be retrieved without paging.
+
+`http://localhost:8123/mpidsserver/Entities/all?flags=P` returns the default number of entities starting with the first entity which would be retrieved without paging.
diff --git a/src/doc/specification/Record.md b/src/doc/specification/Record.md
new file mode 100644
index 0000000000000000000000000000000000000000..1e43e372f754d884da2ff15cfb7b71f6813f1785
--- /dev/null
+++ b/src/doc/specification/Record.md
@@ -0,0 +1,367 @@
+# Record
+
+
+= GET records = 
+
+HTTP-GET-requests are to be send to `http://${host}:${port}/mpidsserver/Record/...`. Default port is 8123.
+
+### Get single record
+
+{{{ 
+GET http://${host}:${port}/mpidsserver/Record/1
+}}}
+
+returns in case of success:
+
+
+        <Response id="1353783822304" generated="2012-11-23 11:10:50.507" items="1">
+          <Record id="1" generator="X23" timestamp="2012-09-22 00:56:03.0" recordtypename="Experiment" recordtypeid="1">
+            <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended">100111327</Property>  
+            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory">Here comes the explanation</Property>
+            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory">2010-01-20 20:15:00.0</Property>
+          </Record>
+        </Response>
+
+
+If a requested record didn't exist, it returns:
+
+
+        <Response id="1354256823059" generated="2012-11-23 11:17:06.754" items="1">
+          <Record id="1">
+            <Error code="101" description="Record does not exist. "/>
+          </Record>
+        </Response>
+
+### Get multiple records
+
+{{{ 
+GET http://${host}:${port}/mpidsserver/Record/1&2
+}}}
+
+returns in case of success:
+
+
+        <Response id="1354570488723" generated="2012-11-23 11:12:33.666" items="2">
+          <Record id="1" generator="X23" timestamp="2012-09-22 00:56:03.0" recordtypename="Experiment" recordtypeid="1">
+            <Property id="4" name="id" type="integer" description="identifier" exponent="0" importance="recommended">100111327</Property>
+            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory">Here comes the explanation</Property>
+            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory">2010-01-20 20:15:00.0</Property>
+          </Record>
+          <Record id="2" generator="G22" timestamp="2012-09-22 00:56:03.0" recordtypename="Heart" recordtypeid="6">
+            <Property id="9" name="weightg" type="double" unit="g" description="weight in g" exponent="0" importance="obligatory">1000.0</Property>
+          </Record>
+        </Response>
+
+Same behavior as above if one of these didn't exist.
+
+## POST records
+
+The POST-requests are to be send to `http://${host}:${port}/mpidsserver/Record`. Default port is 8123.
+
+### Post single record
+
+*HTTP body:*
+
+
+        <Post>
+          <Record generator="Timm" recordtypeid="1">
+            <Property id="6">2012-12-24 18:00:00</Property>
+            <Property id="1">We investigate if Santa Clause does fit down the chimney</Property>
+          </Record>
+        </Post>
+
+| Remarks |
+|---------|
+* The obligatory root tag is `<Post>`
+* A record has to have a `generator="..."` attribute and a _recordtype_ (i.e. a `recordtypeid="..."` or `recordtypename="..."` attribute).
+* Depending on the _recordtype_ of the posted _record_ there are several _concrete properties_ that must be definded (they are _obligatory_).
+
+In case of success the response will look like:
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1353708657159" generated="2012-11-23 11:55:41.223" items="1">
+          <Record id="272" generator="Timm" timestamp="2012-11-23 10:55:43.0" recordtypename="Experiment" recordtypeid="1">
+            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory">2012-12-24 18:00:00.0</Property>
+            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory">We investigate if Santa Clause does fit down the chimney</Property>
+          </Record>
+        </Response>
+
+| Remarks |
+|---------|
+* The responces contains the whole record as it lies in the database now. That entails it's final `id`, a timestamp (`generated` attribute) and a full description of the properties with name, type, descpription and so on.
+* For further specifications of the _concrete properties_ see [wiki:ConcreteProperty].
+
+#### Erroneous properies
+
+Now, there are a couple of errors that can occur (cf. [wiki:errorcodes]). The errors that occur due to corrupt concrete properies are described in [wiki:ConcreteProperty]. If any Errors occur on the level of properties, the whole record is handled as unqualified to be posted. Suppose the post body reads
+
+
+        <Post>
+          <Record generator="Timm" recordtypeid="1">
+            <Property id="6">Christmas Eve</Property>
+            <Property id="1">Does Santa Clause fit down the chimney</Property>
+          </Record>
+        </Post>
+
+The first property cannot be parsed to a datetime format. Therefore, the response will be
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1354575364020" generated="2012-11-23 12:37:20.045" items="1">
+          <Record generator="Timm" recordtypename="Experiment" recordtypeid="1">
+            <Error code="103" description="Record has unqualified Properties. " />
+            ...
+          </Record>
+        </Response>
+
+| Remarks |
+|---------|
+* The error "Record has unqualified Properties" occures whenever the posted concrete properties of the record are corrupt (i.e. any `<Error>` tag is child of a `<Property>` tag).
+* Erroneous records are not stored to the database.
+
+#### Erroneous record tags
+
+1) If the recordtype hasn't been indicated, the server returns
+
+
+        ...
+        <Error code="105" description="Record has no RecordType. " />
+        ...
+
+2) The indicated recordtype does not exist:
+
+
+        ...
+        <Error code="105" description="Record's RecordType does not exist. " />
+        ...
+view Tickets #16 #17
+
+3) The generator has not been indicated:
+
+
+        ...
+        <Error code="105" description="Record has no Generator. " />
+        ...
+
+4) The record's _recordtype_ demands for a property that isn't present:
+
+
+        <Post>
+          <Record generator="Timm" recordtypeid="1">
+            <Property id="6">2012-12-24 18:00:00</Property>
+          </Record>
+        </Post>
+
+returns:
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1354701618532" generated="2012-11-23 13:25:41.59" items="1">
+          <Record generator="Timm" recordtypename="Experiment" recordtypeid="1">
+            <Error code="102" description="Obligatory Property 1 (explanation) is missing. " />
+            <Property id="6" name="startDate" type="datetime" description="start" importance="obligatory">2012-12-24 18:00:00.0</Property>
+          </Record></Response>
+
+
+5) Record does'nt entail any properties:
+
+
+        ...
+        <Error code="105" description="Record has no Properties. " />
+        ...
+
+6) The indicated ID and name of a _recordtype_ are mismatching (at least one of them exists but they don't correspond to each other).
+
+
+        ...
+        <Error code="105" description="Record has invalid RecordType (Name/ID mismatch). " />
+        ...
+
+
+### Post multiple records
+
+It is possible to post several records in one go. Here we go through a few scenarios with independent sets of records and closely intertwined ones. 
+
+#### Independent records
+
+*HTTP body:*
+
+
+        <Post>
+          <Record generator="Timm" recordtypeid="1">
+            ... (properties) ...
+          </Record>
+          <Record generator="Timm" recordtypeid="1">
+            ... (properties) ...
+          </Record>
+          ... (more records) ...
+        </Post>
+
+returns:
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1356209136586" generated="2012-11-30 11:33:39.897" items="2">
+          <Record id="274" generator="Timm" timestamp="2012-11-30 10:33:41.0" recordtypename="Experiment" recordtypeid="1">
+            ... (properties) ...
+          </Record>
+          <Record id="275" generator="Timm" timestamp="2012-11-30 10:33:41.0" recordtypename="Experiment" recordtypeid="1">
+            ... (properties) ...
+          </Record>
+          ... (more records) ...
+        </Response>
+
+#### Non-circular interdependent records:
+
+*HTTP body:*
+
+
+        <Post>
+          <Record generator="Timm" recordtypeid="1">
+            <Property id="10">-1</Property>
+            ... (more properties) ...
+          </Record>
+          <Record id="-1" generator="Timm" recordtypeid="1">
+            ... (properties) ...
+          </Record>
+          ... (more records) ...
+        </Post>
+
+returns:
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1354608915619" generated="2012-11-30 11:40:35.369" items="2">
+          <Record id="277" generator="Timm" timestamp="2012-11-30 10:40:37.0" recordtypename="Experiment" recordtypeid="1">
+            <Property id="10" name="experiment" type="reference" reference="1" description="reference to experiment" importance="suggested">276</Property>
+            ... (more properties) ...
+          </Record>
+          <Record id="276" generator="Timm" timestamp="2012-11-30 10:40:37.0" recordtypename="Experiment" recordtypeid="1">
+            ... (properties) ...
+          </Record>
+          ... (more records) ...
+        </Response>
+
+| Remarks |
+|---------|
+* The record that is to be referenced by another gets an id less than zero (id="-1"). This id is provisional and will be replaced by the final id, which is the one the record is stored with in the database, in the response (id="276"). 
+
+#### Circular dependent records
+
+*HTTP body:*
+
+
+        <Post>
+          <Record id="-2" generator="Timm" recordtypeid="1">
+            <Property id="10">-1</Property>
+            ... (more properties) ...
+          </Record>
+          <Record id="-1" generator="Timm" recordtypeid="1">
+            <Property id="10">-2</Property>
+            ... (more properties) ...
+          </Record>
+          ... (more records) ...
+        </Post>
+
+returns:
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1354698071005" generated="2012-11-30 11:52:28.789" items="2">
+          <Record id="278" generator="Timm" timestamp="2012-11-30 10:52:30.0" recordtypename="Experiment" recordtypeid="1">
+            <Property id="10" name="experiment" type="reference" reference="1" description="reference to experiment" importance="suggested">279</Property>
+            ... (more properties) ...
+          </Record><Record id="279" generator="Timm" timestamp="2012-11-30 10:52:30.0" recordtypename="Experiment" recordtypeid="1">
+            <Property id="10" name="experiment" type="reference" reference="1" description="reference to experiment" importance="suggested">278</Property>
+            ... (more properties) ...
+          </Record>
+          ... (more records) ...
+        </Response>
+
+| Remarks |
+|---------|
+* Likewise,records that are to be referenced by each other get ids less than zero (id="-1" and id="-2"). These are provisional and will be replaced by the final ids in the response (id="278" and id="279"). 
+
+
+#### Erroneous multi-posts
+
+1) If any error occurs in a single record, all correct records are nevertheless stored (In future this behavior should be controlled better by an _atomic flag_. See #18). 
+
+2) If a referenced record is corrupt, the referencing record is marked with an error tag equally.
+
+*HTTP body:*
+
+
+        <Post>
+          <Record generator="Timm" recordtypeid="1">
+            <Property id="10">-1</Property>
+             ... (more properties) ...
+          </Record>
+          <Record id="-1" generator="Timm" recordtypeid="1">
+            <Property id="1">Explanation2</Property>
+            <!-- obligatory property 6 is missing -->
+          </Record>
+          ... (more records) ...
+        </Post>
+
+returns:
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1356334296695" generated="2012-11-30 12:14:37.246" items="2">
+          <Record id="-2" generator="Timm" recordtypename="Experiment" recordtypeid="1">
+            <Error code="103" description="Record has unqualified Properties. " />
+            <Property id="10" name="experiment" type="reference" reference="1" description="reference to experiment" importance="suggested">
+              <Error code="204" description="Referenced Record is unqualified. " />
+              -1
+            </Property>
+            ... (more properties) ...
+          </Record>
+          <Record id="-1" generator="Timm" recordtypename="Experiment" recordtypeid="1">
+            <Error code="102" description="Obligatory Property 6 (startDate) is missing. " />
+            <Property id="1" name="explanation" type="text" description="explains the thing" importance="obligatory">Explanation2</Property>
+          </Record>
+          ... (more records) ...
+        </Response>
+
+3) Self-referencing is not allowed. A self-referencing record will return
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1355236745833" generated="2012-11-30 12:18:45.066" items="1">
+          <Record id="-2" generator="Timm" recordtypename="Experiment" recordtypeid="1">
+            <Error code="104" description="Self-referencing is not allowed. " />
+            <Property id="10" name="experiment" type="reference" reference="1" description="reference to experiment" importance="suggested">-2</Property>
+            ... (more properties) ...
+          </Record>
+          ... (more records) ...
+        </Response>
+
+4) Referencing a non-existing provisional id will return (cf. #19):
+
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <Response id="1354990914009" generated="2012-11-30 13:07:00.45" items="1">
+          <Record generator="Timm" recordtypename="Experiment" recordtypeid="1">
+            <Error code="104" description="Referenced record does not exist. " />
+            <Property id="10" name="experiment" type="reference" reference="1" description="reference to experiment" importance="suggested">-2</Property>
+            ... (more properties) ...
+          </Record>
+          ... (more records) ...
+        </Response>
+
+= DELETE records = 
+
+HTTP-DELETE-requests are to be send to `http://${host}:${port}/mpidsserver/Record/...`. Default port is 8123.
+
+*Example*
+{{{ 
+DELETE http://${host}:${port}/mpidsserver/Record/1&2&3&4
+}}}
+
+
+
+
+
+
diff --git a/src/doc/specification/RecordType.md b/src/doc/specification/RecordType.md
new file mode 100644
index 0000000000000000000000000000000000000000..068c433d8c5390882176c0ff8783576127a2da2e
--- /dev/null
+++ b/src/doc/specification/RecordType.md
@@ -0,0 +1,164 @@
+# 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/specs/Server-side-scripting.md b/src/doc/specification/Server-side-scripting.md
similarity index 86%
rename from src/doc/specs/Server-side-scripting.md
rename to src/doc/specification/Server-side-scripting.md
index 49f56af66a6b65855e36de12144955b2925a4f51..18dfe9eab0201fdd322bb2f61cbc87370ced99d5 100644
--- a/src/doc/specs/Server-side-scripting.md
+++ b/src/doc/specification/Server-side-scripting.md
@@ -1,21 +1,24 @@
 # Server-Side Scripting API (v0.1)
 
-The CaosDB Server can execute scripts (bash/python/perl) and compiled executables. The scripts can be invoked by a remote-procedure-call (RPC) protocol. Both, the requirements for the scripts and the RPC are described in this document.
+The CaosDB Server can execute scripts (bash/python/perl) and 
+compiled executables. The scripts can be invoked by a remote-procedure-call 
+(RPC) protocol. Both, the requirements for the scripts and the 
+RPC are described in this document.
 
 ## Configuration of the Server
 
-* The CaosDB Server has two relevant properties:
-    1. `SERVER_SIDE_SCRIPTING_BIN_DIR` is the directory where executable scripts are to be placed. The server will not execute scripts which are out side of this directory. This directory must be readable and executable for the server. But it should not be readable or executable for anyone else.
-    2. `SERVER_SIDE_SCRIPTING_WORKING_DIR` is the directory under which the server creates temporary working directories. The server needs writing, reading and executing permissions. The temporary working directories are deleted after the scripts have finished and the server has collected the results of the scripts.
+The CaosDB Server has two relevant properties:
 
-## Installing a Server-Side Script (SSS)
+### `SERVER_SIDE_SCRIPTING_BIN_DIR` 
+is the directory where executable scripts are to be placed. The server will not execute scripts which are out side of this directory. This directory must be readable and executable for the server. But it should not be readable or executable for anyone else.
+Executable files in this directory or its subdirectories are called `SSS`.
 
-* Put your script into the `SERVER_SIDE_SCRIPTING_BIN_DIR` or in any subdirectory and make it executable. Executable files below this directory are called `SSS`.
-* All other files in that directory MUST be ignored by the server, i.e. the server will never call them directly.
+All other files in that directory MUST be ignored by the server, i.e. the server will never call them directly.
   However, they MAY contain additional data, different implementations, libraries etc.
-* A symlink pointing to an executable MUST be treated as SSS, too.
 
-### Example SERVER\_SIDE\_SCRIPTING\_BIN\_DIR
+A symlink pointing to an executable MUST be treated as SSS, too.
+
+#### Example SERVER\_SIDE\_SCRIPTING\_BIN\_DIR
 
 ```
 /
@@ -34,6 +37,10 @@ The files `scripts1.py`, `scripts2.sh`, `scripts3.pl` and `another_script.py` ar
 Also the `script4` can be called which would result in calling `another_script.py`
 The script5 points to an executable which is not stored in the `SSD_DIR`.
 
+### `SERVER_SIDE_SCRIPTING_WORKING_DIR` 
+is the directory under which the server creates temporary working directories. The server needs writing, reading and executing permissions. The temporary working directories are deleted after the scripts have finished and the server has collected the results of the scripts.
+
+
 ## Calling a Server-Side Script Via Remote Procedure Call
 
 * Users can invoke scripts via HTTP under the uri `https://$HOST:$HTTPS_PORT/$CONTEXT_ROOT/scripting`. The server accepts POST requests with content types `application/x-www-form-urlencoded` and `multipart/form-data`.
@@ -54,7 +61,7 @@ The script5 points to an executable which is not stored in the `SSD_DIR`.
 * If a file form field has a field name which begins with either `-p` or `-O` the file name with prefix `.upload_files/` is passed as value of an option or as a positional argument to the script. Thus it is possible to distinguish several uploaded files from one another.
 * If there is a "auth-token" field present, another command line options `--auth-token=...` is appended. The value is either the string which was submitted with the POST request, or, if the value was "generate", a refreshed, valid AuthToken which authenticates as the user of the request (why? see below).
 
-### Example HTML Form
+## Example HTML Form
 
 ```html
 <form action="/scripting" method="post" enctype="multipart/form-data">
@@ -73,6 +80,7 @@ where the user uploads `my.conf` as `-Oconfig-file` and `my.input.tsv` as `-p1`,
 ```
 $SERVER_SIDE_SCRIPTING_BIN_DIR/my/script.py --config-file=.upload_files/my.conf --algorithm=fast analyze .upload_files/my.input.tsv
 ```
+
 ## CaosDB Server Response
 
 The CaosDB Server responds with an xml document. The root element is the usual `/Response`. If no errors occurred (which would be represented with `/Response/Error` elements) the result of the script execution is represented as a `/Response/script/` element.
diff --git a/src/doc/specification/Specification-of-the-Entity-API.md b/src/doc/specification/Specification-of-the-Entity-API.md
new file mode 100644
index 0000000000000000000000000000000000000000..93b39f5333b9f80c9f4485fc00c2f827ad60792a
--- /dev/null
+++ b/src/doc/specification/Specification-of-the-Entity-API.md
@@ -0,0 +1,191 @@
+# Specification of the Entity API
+Version: 0.1.0r1
+
+Author: Timm Fitschen
+
+Email: timm.fitschen@ds.mpg.de
+
+Date: 2017-12-17
+
+## Introduction
+
+CaosDB is a database management system that stores it's data into `Entities`. An `Entity` can be thought of as the equivalent to tables, rows, columns and the tuples that fill the tables of a traditional RDBMS. Entities are not only used to store the data they also define the structure of the data.
+
+## Formal Definition
+
+An `Entity` may have 
+
+* a `domain`
+* an `id`
+* a `role`
+* a `name`
+* a `data type`
+* a `Set of Values`
+* a `Set of Properties`
+* a `Set of Parents`
+
+A `domain` contains an `Entity`.
+
+An `id` is an arbitrary string. 
+
+A `role` is an arbitrary string. Especially, it may be one of the following strings:
+
+* `RecordType`
+* `Record`
+* `Relation`
+* `Property`
+* `File`
+* `QueryTemplate`
+* `Domain`
+* `Unit`
+* `Rule`
+* `DataType`
+* `Remote`
+
+A `name` is an arbitrary string.
+
+A `data type` contains an `Entity`. Note: this is not necessarily a `Data Type`.
+
+### Set of Values
+
+A `Set of Values` is a mapping from a `indices` to a finite set of `Values`.
+
+An `index` is an interval of non-negative integers starting with zero.
+
+#### Value
+
+A `Value` may have a `data type` and/or a `unit`.
+
+A `data type` is an `Entity`. Note: this is not necessarily a `Data Type`.
+
+A `unit` is an arbitrary string.
+
+### Data Type
+
+A `Data Type` is an `Entity` with role `DataType`.
+
+#### Reference Data Type
+
+A `Reference Data Type` is a `Data Type`. It may have a `scope`. 
+
+A `scope` contains an `Entity`.
+
+#### Collection Data Type
+
+A `Collection Data Type` is a `Data Type`. It may have an ordered set of `elements`.
+
+### Record Type
+
+A `Record Type` is an `Entity` with role `RecordType`.
+
+### Record
+
+A `Record` is an `Entity` with role `Record`.
+
+### Relation
+
+A `Relation` is an `Entity` with role `Relation`.
+
+### Property
+
+A `Property` is an `Entity` with role `Property`. It is also refered to as `Abstract Property`.
+
+### File
+
+A `File` is an `Entity` with role `File`.
+
+A `File` may have 
+
+* a `path`
+* a `size`
+* a `checksum`
+
+A `path` is an arbitrary string.
+
+A `size` is a non-negative integer.
+
+A `checksum` is an ordered pair (`method`,`result`).
+
+A `method` is an arbitrary string. 
+
+A `result` is an arbitrary string.
+
+### QueryTemplate
+
+A `QueryTemplate` is an `Entity` with role `QueryTemplate`.
+
+### Domain
+
+A `Domain` is an `Entity` with role `Domain`.
+
+### Unit
+
+A `Unit` is an `Entity` with role `Unit`.
+
+### Rule
+
+A `Rule` is an `Entity` with role `Rule`.
+
+### Remote
+
+A `Remote` is an `Entity` with role `Remote`.
+
+### Set of Parents
+
+A `Set of Parents` is a set of `Parents`.
+
+#### Parent
+
+A `Parent` may contain another `Entity`. 
+
+A `Parent` may have an `affiliation`.
+
+An `affiliation` may contain of the following strings:
+
+* `subtyping`
+* `instantiation`
+* `membership`
+* `parthood`
+* `realization`
+
+### Set of Properties
+
+A `Set of Properties` is a tripple (`index`, set of `Implemented Properties`, `Phrases`).
+
+An `index` is a bijective mapping from an interval of non-negative integer numbers starting with zero to the set of `Implemented Properties`.
+
+#### Implemented Property
+
+An `Implemented Property` contains another `Entity`.
+
+An `Implemented Property` may have an `importance`.
+
+An `Implemented Property` may have a `maximum cardinality`.
+
+An `Implemented Property` may have a `minimum cardinality`.
+
+An `Implemented Property` may have an `import`.
+
+An `importance` is an arbitrary string. It may contain of the following strings:
+
+* `obligatory`
+* `recommended`
+* `suggested`
+* `fix`
+
+A `maximum cardinality` is a non-negative integer.
+
+A `minimum cardinality` is a non-negative integer.
+
+An `import` is an arbitrary string. It may contain of the following strings:
+
+* `fix`
+* `none`
+
+#### Phrases
+
+`Phrases` are a mapping from the cartesian product of the `index` with itself to a `predicate`.
+
+A `predicate` is an arbitrary string.
+
+
diff --git a/src/doc/specification/Specification-of-the-Message-API.md b/src/doc/specification/Specification-of-the-Message-API.md
new file mode 100644
index 0000000000000000000000000000000000000000..fc08b343f67ca8c052395cb0979b02ab455e98fa
--- /dev/null
+++ b/src/doc/specification/Specification-of-the-Message-API.md
@@ -0,0 +1,167 @@
+# Specification of the Message API
+## Introduction
+
+API Version 0.1.0
+
+A Message is a way of communication between the server and a client. The main purpose is to inform the clients about errors which occured during transactions, issue warnings when entities have a certain state or just explicitly confirm that a transaction was successful. Messages represents information that is not persistent or just the reproducible outcome of a transaction. Messages are not stored aside from logging.
+
+## Message Classes And Their Properties
+
+### Message (generic super class)
+
+A `Message` must be either a `Server Message` or a `Client Message`. 
+
+A `Message` must have a `description`. A `description` is a string and a human-readable explanation of the meaning and/or purpose of the message. The description must not have leading or trailing whitespaces. The description should be kept in English. For the time being there is no mechanism to indicate that the description is written in other languages. This could be changed in later versions of this API. 
+
+### Server Message
+
+A `Server Message` is a Message issued by the server. It must not be issued by clients. 
+
+A `Server Message` may be either a `Standard Server Message` or a `Non-Standard Server Message`
+
+#### Standard Server Message
+
+A `Standard Server Message` is one of a set of predefined messages with a certain meaning. The set of these `Standard Server Messages` is maintained and documented in the Java code of the server. There should be a server resource for these definitions in order to have a always up-to-date documentation of the messages on every server.
+
+A `Standard Server Message` must have an `id`. An `id` is a non-empty string that uniquely identifies a standard server message. An id should consist only of ASCII compliant upper-case Latin alphabetic letters from `A` to `Z` and the underscore character `_`.
+An `id` of a `Standard Server Message` must not start with the string `NSSM_`.
+
+A `Standard Server Message` must have a `type`. A `type` is one these strings: `Info`, `Warning`, `Error`, or `Success`.
+
+##### Error Message
+
+A `Server Message` with type `Error` is also called `Error Message` and sometimes just `Error`. An `Error Message` indicates that a request has *failed*. It informs about the reasons for that failure or the nature of the problems which occurred. The description of each error message should explain the error and indicate if and how the client can remedy the problems with her request.
+
+##### Warning Message
+
+A `Server Message` with type `Error` is also called `Warning Message` and sometime just `Warning`. A `Warning Message` indicates that certain *irregularities* occurred during the processing of the request or that the client requested something that is *not recommended but not strictly forbidden*.
+
+##### Info Message
+
+A `Server Message` with type `Info` is also called `Info Message` and sometimes just `Info`. An `Info Message` is a means to inform the client about *arbitrary events* which occurred during the processing of the request and which are *not* to be considered *erroneous* or *non-recommended*. These info messages are primarily intended to make the processing of the request more understandable for the client. Info messages are not meant to be used for debugging.
+
+##### Success Message
+
+A `Server Message` with type `Success` is also called a `Success Message`. A `Success Message` indicates the successful *state change* due to portions of a request or the whole request. A success message must not be issued if the request fails.
+
+#### Non-Standard Server Message
+
+A `Non-Standard Server Message` may be issued by any non-standard server plugin or extension. It is a placeholder for extensions to the Message API. 
+
+A `Non-Standard Server Message` may have an `id`. An `id` is a non-empty string. It should consist only of ASCII compliant upper-case Latin alphabetic letters from `A` to `Z` and the underscore character `_`. However, the id should not be equal to any id from the set of predefined standard server messages. Furthermore, the id of a non-standard server message should start with the string `NSSM_`.
+
+A `Non-Standard Server Message` may have a `type`. A `type` is a non-empty string. It should consist only of ASCII compliant upper-case or lower-case Latin alphabetic letters from `a` to `z`, from `A` to `Z`, and the underscore character `_`. If the type is equal to one of the above-mentioned types, it must have the same meaning and the same effects on the request as the respective type from above. Especially, a message with type `Error` must not be issued unless the request actually fails. Likewise a `Success` must not be issued unless the request actually caused a *state change* of the server.
+
+### Client Message
+
+A `Client Message` may have an `ignore` flag. The `ignore` flag can have one of these values: `no`, `yes`, `warn`, `silent`
+
+A `Client Message` is a message issued by a client. It should not be issued by the server. A `Client Message` may be completely ignored by clients. A client message must not be ignored by the server. A `Client Message` which cannot be understood by the server must result in an error, unless the `ignore` flag states otherwise. 
+
+#### Ignore Flag
+
+If the `ignore` flag is set to `no` the server must not ignore the client message. If the server cannot understand the client message an error must be issued. This will cause the transaction to fail.
+
+If the `ignore` flag is set to `yes` the server must ignore the client message.
+
+If the `ignore` flag is set to `warn` the server should not ignore the message. If the server cannot understand the client message, a warning must be issued. The transaction will not fail due to this warning.
+
+### Message Parameters
+
+A `Message` may have zero or more parameters. A `Message Parameter` is a a triple of a `key`, a `value`. It is intended to facilitate the processing and representation of messages by clients and the server. For example, consider an `Error Message` which states that a certain server state cannot be reached and the reason be that there is an entity with certain features. Then it is useful to refer to the entity via the
+parameters. A client can now resolve the entity and just show it or generate a URI for this entity.
+
+A `key` is a non-empty string which should consist only of ASCII compliant lower-case Latin alphabetic letters from `a` to `z` and the minus character `-`. A `key` must be unique among the keys of the message parameters. 
+
+A `value` is a possibly empty, arbitrary string which must not have leading or trailing white spaces.
+
+A `Message Parameter` may have a `type`. The `type` of a `Message Parameter` is also called a `Message Parameter Type`. A `Message Parameter Type` is a non-empty string which should consist only of ASCII compliant lower-case Latin alphabetic letters from `a` to `z` and the minus character `-`. A message parameter type may be one these string: `entity-id`, `entity-name`, `entity-cuid`, `property-index`, `parent-id`, `parent-name`.
+
+A `Message Parameter` with a type which begins with `entity-` is also called an `Entity Message Parameter`. The value of an `Entity Message Parameter` must refer to an entity—via its id, name, or cuid, respectively.
+
+A `Message Parameter` with a type which begins with `property-` is also called a `Property Message Parameter`. The value of such a parameter must refer to an entity's property. In the case of the `property-index` type the value refers to a property via a zero-based index (among the list of properties of that entity). The list of properties in question must belong to the `Message Bearer` which must in turn be an `Entity`.
+
+A `Message Parameter` with a type which begins with `parent-` is also called a `Parent Message Parameter`. The value of such a parameter must refer to an entity's parent via its id or name, respectively.
+
+### Message Bearer
+
+A `Message` must have a single `Message Bearer`, or, equivalently, a `Message` `belongs to` a single `Message Bearer`. The message is usually considered to carry information about the message bearer if not stated otherwise. The message's subject should be the message bearer itself, so to speak. Although, possibly indicated by a `Message Parameter` the message may be additionally or solely concerned with other things than the message bearer. Please note: The message bearer may also indicate the context of evaluation of the message parameters, e.g. when the type of the message parameter is `property-index`. 
+
+A `Message Bearer` may be an `Entity`, a `Property`, a `Container`, a `Request`, a `Response`, or a `Transaction`.
+
+## Representation and Serialization
+
+Messages can be serialized, deserialized by the means of XML.
+
+### XML Representation
+
+A `Message` is serialized into a single XML Element Node (hereafter the *root element* with zero or more Child Nodes. 
+
+##### Root Element Tag
+
+The root element's tag of a `Server Message` must be equal to its `type` if and only if the type is equal to one of the allowed types of a `Standard Server Message` (even if it is a Non-Standard Server Message). Otherwise the root tag is just 'ServerMessage'.
+
+```xml
+<Error/><!--an Error Message-->
+<Warning/><!--a Warning Message-->
+<Info/><!--an Info Message-->
+<Success/><!--a Success Message-->
+<ServerMessage/> <!--a Non-Standard Server Message with a non-standard type-->
+```
+
+The root element's tag of a `Client Message` must be 'ClientMessage'. E.g.
+```xml
+<ClientMessage/><!--a Client Message-->
+```
+
+##### Root Element Attributes
+
+The root element must have the attributes nodes `id`, and/or `ignore` if and only if the messages have corresponding properties. The root element must have a 'type' attribute only if the message has a type property and if the type is not equal to the root element's tag. The values of the attributes must equal the corresponding properties. E.g.
+
+```xml
+<Error id="ENTITY_DOES_NOT_EXIST" type="Error"/><!--this and the next element are equivalent-->
+<Error id="ENTITY_DOES_NOT_EXIST"/>
+<ServerMessage type="CustomType"/><!--has no id-->
+<ServerMessage id="NSSM_MY_ID"/><!--has no type-->
+```
+
+or
+
+```xml
+<ClientMessage id="CM_MY_ID" ignore="warn"/>
+```
+
+All other Attributes should be ignored.
+
+##### Description Element
+
+The root element must have exactly one Child Element Node with tag 'Description' if and only if the message has a `description` property. The string value of the message's description must be the first Child Text Node of the 'Description' Element. E.g.
+
+```xml
+<ServerMessage>
+  <Description>This is a description.</Description>
+</ServerMessage>
+```
+
+Please note: Any leading or trailing whitespaces of the Text Node must be stripped during the deserialization.
+
+All other Attributes and Child Nodes should be ignored.
+
+##### Parameters Element
+
+The root element must have exactly one Child Element Node with tag 'Parameters' if the message has at least one `parameter`. The 'Parameters' Element in turn must have a single Child Element Node for each parameter which are called `Parameter Elements`. 
+
+A `Parameter Element` must have a tag equal to the `key` of the parameter.
+It must have a `type` attribute equal to the `type` property of the parameter if and only if the parameter has a type. And it must have a first Child Text Node which is equal to the parameter's `value`. E.g.
+
+```xml
+<ClientMessage>
+  <Parameters>
+    <param-one type="entity-name">Experiment</param-one><!--One parameter with key="param-one", value="Experiment", and type="entity-name"-->
+  </Parameters>
+</ClientMessage>
+```
+
+Please note: Any leading or trailing whitespaces of the Text Node must be stripped during the deserialization. 
+
+All other Attributes and Child Nodes below the 'Parameters' Element should be ignored.
diff --git a/src/doc/specification/index.rst b/src/doc/specification/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e9683072f555725ee8b74a0ae468c6a407e244b2
--- /dev/null
+++ b/src/doc/specification/index.rst
@@ -0,0 +1,22 @@
+                                                                                
+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>
+
diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java
index 996e1480a8158dd27e62e3af072a725698a3e5f3..4a69f24d4d905f5d8fea360c291dfc1522b414fc 100644
--- a/src/main/java/org/caosdb/server/CaosDBServer.java
+++ b/src/main/java/org/caosdb/server/CaosDBServer.java
@@ -78,13 +78,9 @@ import org.caosdb.server.resource.Webinterface;
 import org.caosdb.server.resource.WebinterfaceBuildNumber;
 import org.caosdb.server.resource.transaction.EntityNamesResource;
 import org.caosdb.server.resource.transaction.EntityResource;
-import org.caosdb.server.terminal.CaosDBTerminal;
-import org.caosdb.server.terminal.StatsPanel;
-import org.caosdb.server.terminal.SystemErrPanel;
 import org.caosdb.server.transaction.ChecksumUpdater;
 import org.caosdb.server.utils.FileUtils;
 import org.caosdb.server.utils.Initialization;
-import org.caosdb.server.utils.NullPrintStream;
 import org.quartz.JobDetail;
 import org.quartz.Scheduler;
 import org.quartz.SchedulerException;
@@ -116,7 +112,6 @@ import org.slf4j.LoggerFactory;
 public class CaosDBServer extends Application {
 
   private static Logger logger = LoggerFactory.getLogger(CaosDBServer.class);
-  private static boolean START_GUI = true;
   private static Properties SERVER_PROPERTIES = null;
   private static Component component = null;
   private static ArrayList<Runnable> postShutdownHooks = new ArrayList<Runnable>();
@@ -146,7 +141,7 @@ public class CaosDBServer extends Application {
   public static void main(final String[] args)
       throws SecurityException, FileNotFoundException, IOException {
     try {
-      init(args);
+      parseArguments(args);
       initScheduler();
       initServerProperties();
       initTimeZone();
@@ -161,16 +156,22 @@ public class CaosDBServer extends Application {
     }
   }
 
-  private static void init(final String[] args) {
-    // Important change:
-    // Make silent the default option
-    START_GUI = false;
+  /**
+   * Parse the command line arguments.
+   *
+   * <ul>
+   *   <li>"nobackend": flag to run caosdb without any backend (for testing purposes)
+   *   <li>"insecure": flag to start only a http server (no https server)
+   * </ul>
+   *
+   * <p>Both flags are only available in the debug mode which is controlled by the `caosdb.debug`
+   * JVM Property.
+   *
+   * @param args
+   */
+  private static void parseArguments(final String[] args) {
     for (final String s : args) {
-      if (s.equals("silent")) {
-        START_GUI = false;
-      } else if (s.equals("gui")) {
-        START_GUI = true;
-      } else if (s.equals("nobackend")) {
+      if (s.equals("nobackend")) {
         START_BACKEND = false;
       } else if (s.equals("insecure")) {
         INSECURE = true;
@@ -354,32 +355,6 @@ public class CaosDBServer extends Application {
     }
   }
 
-  public static void initGUI() throws InterruptedException {
-    if (START_GUI) {
-      final CaosDBTerminal caosDBTerminal = new CaosDBTerminal();
-      caosDBTerminal.setName("CaosDBTerminal");
-      caosDBTerminal.start();
-
-      addPreShutdownHook(
-          new Runnable() {
-
-            @Override
-            public void run() {
-              caosDBTerminal.shutDown();
-              SystemErrPanel.close();
-            }
-          });
-      // wait until the terminal is initialized.
-      Thread.sleep(1000);
-
-      // add Benchmark
-      StatsPanel.addStat("TransactionBenchmark", TransactionBenchmark.getRootInstance());
-    } else {
-      logger.info("NO GUI");
-      System.setOut(new NullPrintStream());
-    }
-  }
-
   private static void initDatatypes(final Access access) throws Exception {
     final RetrieveDatatypes t = new RetrieveDatatypes();
     t.setAccess(access);
diff --git a/src/main/java/org/caosdb/server/accessControl/UserSources.java b/src/main/java/org/caosdb/server/accessControl/UserSources.java
index 8f69c8c5c86faea903a29049c3a604121b8951ad..a7abd1405f3d566bb672befc86b853dcf2a17357 100644
--- a/src/main/java/org/caosdb/server/accessControl/UserSources.java
+++ b/src/main/java/org/caosdb/server/accessControl/UserSources.java
@@ -79,8 +79,18 @@ public class UserSources extends HashMap<String, UserSource> {
 
   private static UserSources instance = new UserSources();
 
+  /**
+   * Check whether a user exists.
+   *
+   * @param principal - principal of the user.
+   * @return true iff the user identified by the given {@link Principal} exists.
+   */
   public static boolean isUserExisting(final Principal principal) {
-    return instance.get(principal.getRealm()).isUserExisting(principal.getUsername());
+    UserSource userSource = instance.get(principal.getRealm());
+    if (userSource != null) {
+      return userSource.isUserExisting(principal.getUsername());
+    }
+    return false;
   }
 
   private UserSources() {
diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java
index 5bf978343d72272309d4f4ab0f039770419c87b8..61aebf42a2dfd65fb9ed1c84459064e3aa274c06 100644
--- a/src/main/java/org/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java
@@ -129,6 +129,8 @@ 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;
 
@@ -295,4 +297,9 @@ public abstract class BackendTransaction implements Undoable {
       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/DatabaseAccessManager.java b/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..196a9fb2b4418d81ced4b174482f8f6a5dce8ce8
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java
@@ -0,0 +1,331 @@
+/*
+ * ** 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) 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/>.
+ *
+ * ** end header
+ */
+package org.caosdb.server.database;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.locks.ReentrantLock;
+import org.caosdb.server.database.access.Access;
+import org.caosdb.server.database.access.AccessControlAccess;
+import org.caosdb.server.database.access.InfoAccess;
+import org.caosdb.server.database.access.InitAccess;
+import org.caosdb.server.database.access.TransactionAccess;
+import org.caosdb.server.transaction.AccessControlTransaction;
+import org.caosdb.server.transaction.TransactionInterface;
+import org.caosdb.server.transaction.WriteTransactionInterface;
+import org.caosdb.server.utils.Info;
+import org.caosdb.server.utils.Initialization;
+import org.caosdb.server.utils.Releasable;
+
+/**
+ * Acquire and release read access. {@link DatabaseAccessManager} uses this class for managing
+ * access to the back-end during processing updates, inserts, deletions and retrievals. Read access
+ * will be granted immediately for every thread that requests it, unless a thread requests that read
+ * access be blocked (usually by requesting write access). If this happens, all threads that already
+ * have read access will proceed and release their read access as usual but no NEW read permits will
+ * be granted.
+ *
+ * <p>This is a blockable {@link Semaphore}. Any number of threads may acquire a permit unless it is
+ * blocked. The blocking thread (requesting a {@link WriteAccessLock}) waits until all threads have
+ * released their permits.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+class ReadAccessSemaphore extends Semaphore implements Releasable {
+
+  private static final long serialVersionUID = 4384921156838881337L;
+  private int acquired = 0; // how many threads have read access
+  Semaphore writersBlock =
+      new Semaphore(1, true); // This semaphore is blocked as long as there are any
+  // unreleased read permits.
+
+  public ReadAccessSemaphore() {
+    // this is a fair semaphore with no initial permit.
+    super(1, true);
+  }
+
+  /**
+   * Acquire a read access permit if and only if it has not been blocked via block(). If read access
+   * is currently blocked, the thread waits until the unblock() method is invoked by any thread.
+   */
+  @Override
+  public void acquire() throws InterruptedException {
+    super.acquire(); // Protect the next few lines
+    if (this.acquired == 0) {
+      this.writersBlock.acquire();
+    }
+    this.acquired++;
+    super.release();
+  }
+
+  /**
+   * Release a read access permit.
+   *
+   * <p>If this is the last remaining acquired permit, also release the general writersBlock.
+   */
+  @Override
+  public void release() {
+    this.acquired--;
+    if (this.acquired <= 0) { // Last permit: release
+      this.acquired = 0;
+      if (this.writersBlock.availablePermits() <= 0) {
+        this.writersBlock.release();
+      }
+    }
+  }
+
+  /**
+   * Acquire the permit to block further read access, if no thread currently has acquired read
+   * access and no thread currently has a block.
+   *
+   * <p>Consequences of calling this method are:
+   *
+   * <ul>
+   *   <li>Further read access permits are blocked for any thread.
+   *   <li>Every thread that has a read access already can proceed. The current thread waits until
+   *       all threads have released their read access.
+   * </ul>
+   *
+   * <p>If another thread has invoked this method before, the current thread waits until the
+   * unblock() method is called.
+   *
+   * @throws InterruptedException
+   */
+  public void block() throws InterruptedException {
+    super.reducePermits(1);
+    this.writersBlock.acquire();
+  }
+
+  /**
+   * Unblock read access.
+   *
+   * <p>This method releases the writersBlock introduced by calling the block() method.
+   */
+  public void unblock() {
+    if (this.writersBlock.availablePermits() <= 0) {
+      this.writersBlock.release();
+    }
+    super.release();
+  }
+
+  public int waitingAquireAccess() {
+    return getQueueLength();
+  }
+}
+
+/**
+ * Acquire and release write access. DatabaseAccessManager uses this class for managing access to
+ * entities while processing updates, inserts, deletions and retrievals. Write access will be
+ * granted to one and only one thread if no other thread already holds read or write access permits.
+ * The write access seat has to be reserved before acquiring the lock.
+ *
+ * <p>The flow is as follows:
+ *
+ * <p>
+ *
+ * <pre>
+ *                                        No new read
+ *                                      access possible,
+ *    Read access      Read access    wait for running read
+ *      possible        possible       threads to fininish
+ *
+ *      +------+       +----------+       +----------+
+ * +--> |  no  | ----> | reserved | ----> | acquired | --+
+ * |    | Lock |       +----------+       +----------+   |
+ * |    +------+                 \          /            |
+ * |                           (1 seat together)         |
+ * +-----------------------------------------------------+
+ *             release()
+ * </pre>
+ *
+ * @author Timm Fitschen
+ */
+class WriteAccessLock extends ReentrantLock implements Releasable {
+
+  private static final long serialVersionUID = 833147084787201103L;
+  private ReadAccessSemaphore readSem = null;
+  private Thread reservedBy = null;
+
+  public WriteAccessLock(final ReadAccessSemaphore readSem) {
+    super();
+    this.readSem = readSem;
+  }
+
+  /**
+   * Reserve the seat for the next write access. While a write access is reserved but not yet
+   * acquired, all read access may still be granted. When a write access has been already granted or
+   * another reservation is active, the thread waits until the write access has been released.
+   *
+   * @throws InterruptedException
+   */
+  public void reserve() throws InterruptedException {
+    super.lock();
+    this.reservedBy = Thread.currentThread();
+  }
+
+  /**
+   * Lock the write access seat. This method returns once all current read permits have been
+   * released.
+   */
+  @Override
+  public void lockInterruptibly() throws InterruptedException {
+    if (!super.isHeldByCurrentThread()) {
+      super.lock();
+    }
+    this.readSem.block(); // Wait until all current read permits have been released.
+  }
+
+  @Override
+  public void unlock() {
+    if (super.isHeldByCurrentThread()) {
+      this.readSem.unblock();
+      this.reservedBy = null;
+      super.unlock();
+    }
+  }
+
+  @Override
+  public void release() {
+    unlock();
+  }
+
+  public Thread whoHasReservedAccess() {
+    return this.reservedBy;
+  }
+}
+
+/**
+ * Manages the read and write access to the database.
+ *
+ * @author tf
+ */
+public class DatabaseAccessManager {
+
+  private DatabaseAccessManager() {}
+
+  private static final DatabaseAccessManager instance = new DatabaseAccessManager();
+  private final ReadAccessSemaphore readAccess = new ReadAccessSemaphore();
+  private final WriteAccessLock writeAccess = new WriteAccessLock(this.readAccess);
+
+  public static DatabaseAccessManager getInstance() {
+    return instance;
+  }
+
+  /**
+   * Return the thread which has successfully reserved write access. This is the thread which will
+   * be the next to actually acquire write access.
+   *
+   * @return
+   */
+  public static Thread whoHasReservedWriteAccess() {
+    return instance.writeAccess.whoHasReservedAccess();
+  }
+
+  /**
+   * Acquire read access. This method returns the Access object as soon as there are no active write
+   * permits.
+   *
+   * <p>The returned Access object can be used to read in the data base back-end.
+   *
+   * <p>Read access can be acquired parallel to other threads having read access or while another
+   * thread has <em>reserved</em> write access. As soon as any thread has requested to
+   * <em>acquire</em> write access, all other threads have to wait.
+   *
+   * @param t the {@link TransactionInterface} which requests the read access
+   * @return {@link Access} object which holds and abstract away all connection details.
+   * @throws InterruptedException
+   */
+  public Access acquireReadAccess(final TransactionInterface t) throws InterruptedException {
+    this.readAccess.acquire();
+    return new TransactionAccess(t, this.readAccess);
+  }
+
+  /**
+   * Reserve write access. This method returns the Access object as soon as there is no other
+   * reserved or acquired write access.
+   *
+   * <p>The returned Access object can be used to read in the data base back-end.
+   *
+   * <p>The reservation has no effect on granting of read access permits, but only one thread may at
+   * any time reserve or acquire write access.
+   *
+   * @param wt - the {@link WriteTransactionInterface} which request the reservation of the write
+   *     access.
+   * @return {@link Access} object which holds and abstract away all connection details.
+   * @throws InterruptedException
+   */
+  public Access reserveWriteAccess(final WriteTransactionInterface wt) throws InterruptedException {
+    this.writeAccess.reserve();
+    return new TransactionAccess(wt, this.writeAccess);
+  }
+
+  /**
+   * Acquire write access. This method returns the Access object as soon as all already acquired
+   * read access permits have been released. When the write access is acquired, no other access can
+   * be acquired (read or write).
+   *
+   * <p>The returned Access object can be used to read and write in the data base back-end.
+   *
+   * @param wt - the {@link WriteTransactionInterface} which request the acquisition of the write
+   *     access.
+   * @return {@link Access} object which holds and abstract away all connection details.
+   * @throws InterruptedException
+   */
+  public Access acquireWriteAccess(final WriteTransactionInterface wt) throws InterruptedException {
+    this.writeAccess.lockInterruptibly();
+    return wt.getAccess();
+  }
+
+  /**
+   * Special access to be used by {@link Info}.
+   *
+   * @param i
+   * @return
+   */
+  public static Access getInfoAccess(final Info i) {
+    return new InfoAccess(i);
+  }
+
+  /**
+   * Special access to be used during the {@link Initialization} of the caosdb server.
+   *
+   * @param initialization
+   * @return
+   */
+  public static Access getInitAccess(final Initialization initialization) {
+    return new InitAccess(initialization);
+  }
+
+  /**
+   * Special access to be used for {@link AccessControlTransaction}s, mainly authentication and
+   * authorization.
+   *
+   * @param t
+   * @return
+   */
+  public static Access getAccountAccess(final AccessControlTransaction t) {
+    return new AccessControlAccess(t);
+  }
+}
diff --git a/src/main/java/org/caosdb/server/database/DatabaseMonitor.java b/src/main/java/org/caosdb/server/database/DatabaseMonitor.java
deleted file mode 100644
index ed98d1d6d88e3e797ce022c0880640d9a9128a0d..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/DatabaseMonitor.java
+++ /dev/null
@@ -1,330 +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;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.locks.ReentrantLock;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.access.AccessControlAccess;
-import org.caosdb.server.database.access.InfoAccess;
-import org.caosdb.server.database.access.InitAccess;
-import org.caosdb.server.database.access.TransactionAccess;
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.transaction.AccessControlTransaction;
-import org.caosdb.server.transaction.TransactionInterface;
-import org.caosdb.server.transaction.WriteTransaction;
-import org.caosdb.server.utils.Info;
-import org.caosdb.server.utils.Initialization;
-import org.caosdb.server.utils.Observable;
-import org.caosdb.server.utils.Observer;
-import org.caosdb.server.utils.Releasable;
-
-/**
- * Acquire and release weak access. DatabaseMonitor uses this class for managing access to entities
- * during processing updates, inserts, deletions and retrievals. Weak access will be granted
- * immediately for every thread that requests it, unless one or more threads requested for blocking
- * the weak access (usually due to requesting for strong access). If this happens, all threads that
- * already have weak access will proceed and release their weak access as usual but no NEW permits
- * will be granted.
- *
- * @author Timm Fitschen
- */
-class WeakAccessSemaphore extends Semaphore implements Observable, Releasable {
-
-  private static final long serialVersionUID = -262226321839837533L;
-  private int acquired = 0; // how many thread have weak access
-  Semaphore block = new Semaphore(1, true);
-
-  public WeakAccessSemaphore() {
-    // this is a fair semaphore with no initial permit.
-    super(1, true);
-  }
-
-  /**
-   * Acquires a weak access permit if and only if it has not been blocked via block(). If the
-   * WeakAccess is currently blocked, the thread wait until the unblock() method is invoked by any
-   * thread.
-   */
-  @Override
-  public void acquire() throws InterruptedException {
-    super.acquire();
-    if (this.acquired == 0) {
-      this.block.acquire();
-    }
-    this.acquired++;
-    notifyObservers(null);
-    super.release();
-  }
-
-  /** Releases a weak access permit. */
-  @Override
-  public void release() {
-    this.acquired--;
-    notifyObservers(null);
-    if (this.acquired <= 0) {
-      this.acquired = 0;
-      if (this.block.availablePermits() <= 0) {
-        this.block.release();
-      }
-    }
-  }
-
-  /**
-   * Acquires the permit of a block of WeakAccess if no thread currently has a WeakAccess and no
-   * thread currently has a block. I.e. it blocks the further permission of weak access for any
-   * thread. Every thread that has a weak access yet can proceed. The current thread waits until any
-   * thread has released its weak access. If another thread has invoked this method yet the current
-   * thread waits until the unblock() method is called.
-   *
-   * @throws InterruptedException
-   */
-  public void block() throws InterruptedException {
-    super.reducePermits(1);
-    this.block.acquire();
-  }
-
-  /**
-   * Unblock WeakAccess.
-   *
-   * @throws InterruptedException
-   */
-  public void unblock() {
-    if (this.block.availablePermits() <= 0) {
-      this.block.release();
-    }
-    super.release();
-  }
-
-  public int waitingAquireAccess() {
-    return getQueueLength();
-  }
-
-  public int acquiredAccess() {
-    return this.acquired;
-  }
-
-  LinkedList<Observer> observers = new LinkedList<Observer>();
-
-  @Override
-  public boolean acceptObserver(final Observer o) {
-    return this.observers.add(o);
-  }
-
-  @Override
-  public void notifyObservers(final String e) {
-    final Iterator<Observer> it = this.observers.iterator();
-    while (it.hasNext()) {
-      final Observer o = it.next();
-      if (!o.notifyObserver(e, this)) {
-        it.remove();
-      }
-    }
-  }
-}
-
-/**
- * Acquire and release strong access. DatabaseMonitor uses this class for managing access to
- * entities during processing updates, inserts, deletions and retrievals. Strong access will be
- * granted to one and only one thread if no other thread yet holds weak or strong access permits.
- * The strong access has to be allocated before requesting it to be permitted. See below.
- *
- * @author Timm Fitschen
- */
-class StrongAccessLock extends ReentrantLock implements Observable, Releasable {
-
-  private static final long serialVersionUID = -262226321839837533L;
-  private WeakAccessSemaphore wa = null;
-  private Thread allocator = null;
-  private Thread acquirer = null;
-
-  public StrongAccessLock(final WeakAccessSemaphore wa) {
-    super();
-    this.wa = wa;
-  }
-
-  /**
-   * Allocates the strong access permits. While a strong access is allocated but not yet acquired
-   * any weak access may still be granted. When a strong access is yet granted or another allocation
-   * is still active, the thread waits until the strong access has been released.
-   *
-   * @throws InterruptedException
-   */
-  public void allocate() throws InterruptedException {
-    super.lock();
-    this.allocator = Thread.currentThread();
-    notifyObservers(null);
-  }
-
-  @Override
-  public void lockInterruptibly() throws InterruptedException {
-    if (!super.isHeldByCurrentThread()) {
-      super.lock();
-    }
-    this.acquirer = Thread.currentThread();
-    notifyObservers(null);
-    this.wa.block();
-  }
-
-  @Override
-  public void unlock() {
-    if (super.isHeldByCurrentThread()) {
-      this.wa.unblock();
-      this.allocator = null;
-      this.acquirer = null;
-      notifyObservers(null);
-      super.unlock();
-    }
-  }
-
-  @Override
-  public void release() {
-    unlock();
-  }
-
-  LinkedList<Observer> observers = new LinkedList<Observer>();
-
-  @Override
-  public boolean acceptObserver(final Observer o) {
-    return this.observers.add(o);
-  }
-
-  @Override
-  public void notifyObservers(final String e) {
-    final Iterator<Observer> it = this.observers.iterator();
-    while (it.hasNext()) {
-      final Observer o = it.next();
-      if (!o.notifyObserver(e, this)) {
-        it.remove();
-      }
-    }
-  }
-
-  public Thread whoHasAllocatedAccess() {
-    return this.allocator;
-  }
-
-  public Thread whoHasAcquiredAccess() {
-    return this.acquirer;
-  }
-
-  public int waitingAllocateAccess() {
-    return getQueueLength();
-  }
-}
-
-/**
- * Manages the read and write access to the database.
- *
- * @author tf
- */
-public class DatabaseMonitor {
-
-  private DatabaseMonitor() {}
-
-  private static final DatabaseMonitor instance = new DatabaseMonitor();
-  private final WeakAccessSemaphore wa = new WeakAccessSemaphore();
-  private final StrongAccessLock sa = new StrongAccessLock(this.wa);
-
-  public static DatabaseMonitor getInstance() {
-    return instance;
-  }
-
-  public void acquireWeakAccess() throws InterruptedException {
-    this.wa.acquire();
-  }
-
-  public void releaseWeakAccess() {
-    this.wa.release();
-  }
-
-  public void allocateStrongAccess() throws InterruptedException {
-    this.sa.allocate();
-  }
-
-  public void acquireStrongAccess() throws InterruptedException {
-    this.sa.lockInterruptibly();
-  }
-
-  public void releaseStrongAccess() {
-    this.sa.unlock();
-  }
-
-  public static final void acceptWeakAccessObserver(final Observer o) {
-    instance.wa.acceptObserver(o);
-  }
-
-  public static final void acceptStrongAccessObserver(final Observer o) {
-    instance.sa.acceptObserver(o);
-  }
-
-  public static int waitingAllocateStrongAccess() {
-    return instance.sa.waitingAllocateAccess();
-  }
-
-  public static int waitingAcquireWeakAccess() {
-    return instance.wa.waitingAquireAccess();
-  }
-
-  public static int acquiredWeakAccess() {
-    return instance.wa.acquiredAccess();
-  }
-
-  public static Thread whoHasAllocatedStrongAccess() {
-    return instance.sa.whoHasAllocatedAccess();
-  }
-
-  public static Thread whoHasAcquiredStrongAccess() {
-    return instance.sa.whoHasAcquiredAccess();
-  }
-
-  public Access acquiredWeakAccess(final TransactionInterface t) {
-    acquiredWeakAccess();
-    return new TransactionAccess(t, this.wa);
-  }
-
-  public Access allocateStrongAccess(final WriteTransaction<? extends TransactionContainer> wt)
-      throws InterruptedException {
-    allocateStrongAccess();
-    return new TransactionAccess(wt, this.sa);
-  }
-
-  public Access acquireStrongAccess(final WriteTransaction<? extends TransactionContainer> wt)
-      throws InterruptedException {
-    acquireStrongAccess();
-    return wt.getAccess();
-  }
-
-  public static Access getInfoAccess(final Info i) {
-    return new InfoAccess(i);
-  }
-
-  public static Access getInitAccess(final Initialization initialization) {
-    return new InitAccess(initialization);
-  }
-
-  public static Access getAccountAccess(final AccessControlTransaction t) {
-    return new AccessControlAccess(t);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java
index 2ad36235bd16e1cecde9908d334f643ebc127e54..b1f445e2b8ad2d79b756e1dd662296d8e6c9a49c 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java
@@ -55,7 +55,7 @@ public class MySQLHelper implements DBHelper {
    *
    * <p>In the database, this adds a row to the transaction table with SRID, user and timestamp.
    */
-  public void initTransaction(Connection connection, WriteTransaction<?> transaction)
+  public void initTransaction(Connection connection, WriteTransaction transaction)
       throws SQLException {
     try (CallableStatement call = connection.prepareCall("CALL set_transaction(?,?,?,?,?)")) {
 
@@ -89,7 +89,7 @@ public class MySQLHelper implements DBHelper {
     } else if (transaction instanceof WriteTransaction) {
       connection.setReadOnly(false);
       connection.setAutoCommit(false);
-      initTransaction(connection, (WriteTransaction<?>) transaction);
+      initTransaction(connection, (WriteTransaction) transaction);
     } else {
       connection.setReadOnly(false);
       connection.setAutoCommit(true);
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/DeleteEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/DeleteEntityTransaction.java
similarity index 92%
rename from src/main/java/org/caosdb/server/database/backend/transaction/DeleteEntity.java
rename to src/main/java/org/caosdb/server/database/backend/transaction/DeleteEntityTransaction.java
index 7f3d3435e1d4913d99ea0c9b7b064da85e12ac02..f8ebcd2af6ff73dd11f1b845d7e5aef58ff925f3 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/DeleteEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/DeleteEntityTransaction.java
@@ -27,11 +27,11 @@ import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.container.TransactionContainer;
 
-public class DeleteEntity extends BackendTransaction {
+public class DeleteEntityTransaction extends BackendTransaction {
 
   private final TransactionContainer container;
 
-  public DeleteEntity(final TransactionContainer container) {
+  public DeleteEntityTransaction(final TransactionContainer container) {
     this.container = container;
   }
 
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
similarity index 94%
rename from src/main/java/org/caosdb/server/database/backend/transaction/InsertEntity.java
rename to src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
index 42439aa54e06e2556cb7b814f1c9056afb35f9d7..9c86ba33bf47b8051bc365bb32a6f36d0342ff92 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java
@@ -27,11 +27,11 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.container.TransactionContainer;
 import org.caosdb.server.utils.EntityStatus;
 
-public class InsertEntity extends BackendTransaction {
+public class InsertEntityTransaction extends BackendTransaction {
 
   private final TransactionContainer container;
 
-  public InsertEntity(final TransactionContainer container) {
+  public InsertEntityTransaction(final TransactionContainer container) {
     this.container = container;
   }
 
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 db7393b04f0afe3c15622dfb750cf31d42050521..9b798de4279de8034168c4ecfbc1738c70370a65 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
@@ -30,22 +30,24 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.container.TransactionContainer;
 import org.caosdb.server.utils.EntityStatus;
 
+/**
+ * Record the current transaction in the entities transaction history.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
 public class InsertTransactionHistory extends BackendTransaction {
 
   private final TransactionContainer container;
-  private final String transaction;
   private final UTCDateTime datetime;
   private final String user;
   private final String realm;
 
   public InsertTransactionHistory(
       final TransactionContainer container,
-      final String transaction,
       final String realm,
       final String user,
       final UTCDateTime timestamp) {
     this.container = container;
-    this.transaction = transaction;
     this.user = user;
     this.datetime = timestamp;
     this.realm = realm;
@@ -60,7 +62,7 @@ public class InsertTransactionHistory extends BackendTransaction {
           || e.getEntityStatus() == EntityStatus.VALID) {
 
         t.execute(
-            this.transaction,
+            getTransactionType(e),
             this.realm,
             this.user,
             this.datetime.getUTCSeconds(),
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/IsSubType.java b/src/main/java/org/caosdb/server/database/backend/transaction/IsSubType.java
index d75fb2c2453613c50915678d81f53f696589afd4..683903f396a186c8243b6bba20c0d19e29a61688 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/IsSubType.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/IsSubType.java
@@ -29,19 +29,36 @@ import org.caosdb.server.database.exceptions.TransactionException;
 public class IsSubType extends BackendTransaction {
 
   private final Integer child;
-  private final Integer parent;
+  private Integer parent = null;
+  private String parentName = null;
 
   public IsSubType(final Integer child, final Integer parent) {
     this.child = child;
     this.parent = parent;
   }
 
-  private boolean isSubType;
+  public IsSubType(Integer child, String parent) {
+    this.parentName = parent;
+    this.child = child;
+  }
+
+  private Boolean isSubType = null;
 
   @Override
   public void execute() throws TransactionException {
     final IsSubTypeImpl t = getImplementation(IsSubTypeImpl.class);
-    this.isSubType = t.execute(this.child, this.parent);
+
+    if (this.parent == null) {
+      this.isSubType = false;
+      for (Integer parent : execute(new GetIDByName(parentName, false)).getList()) {
+        this.isSubType = t.execute(this.child, parent);
+        if (this.isSubType) {
+          return;
+        }
+      }
+    } else {
+      this.isSubType = t.execute(this.child, this.parent);
+    }
   }
 
   public boolean isSubType() {
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java
similarity index 51%
rename from src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntity.java
rename to src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java
index 38cd7073613e6477d013f8923aaecfc188a3c234..6bac0ff187fe2a08d2d0c1c7450ac954bc023d5e 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java
@@ -3,9 +3,9 @@
  * 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 Timm Fitschen <t.fitschen@indiscale.com>
- * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ *   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
@@ -27,7 +27,9 @@ package org.caosdb.server.database.backend.transaction;
 import java.util.LinkedList;
 import java.util.List;
 import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.datatype.ReferenceDatatype;
+import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
+import org.caosdb.server.datatype.CollectionValue;
+import org.caosdb.server.datatype.IndexedSingleValue;
 import org.caosdb.server.datatype.ReferenceValue;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
@@ -51,21 +53,21 @@ import org.caosdb.server.utils.EntityStatus;
  *
  * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
-public class RetrieveFullEntity extends BackendTransaction {
+public class RetrieveFullEntityTransaction extends BackendTransaction {
 
   private final Container<? extends EntityInterface> container;
 
-  public RetrieveFullEntity(final EntityInterface entity) {
+  public RetrieveFullEntityTransaction(final EntityInterface entity) {
     final Container<EntityInterface> c = new Container<>();
     c.add(entity);
     this.container = c;
   }
 
-  public RetrieveFullEntity(final Container<? extends EntityInterface> container) {
+  public RetrieveFullEntityTransaction(final Container<? extends EntityInterface> container) {
     this.container = container;
   }
 
-  public RetrieveFullEntity(Integer id) {
+  public RetrieveFullEntityTransaction(Integer id) {
     this(new RetrieveEntity(id));
   }
 
@@ -116,6 +118,53 @@ public class RetrieveFullEntity extends BackendTransaction {
     }
   }
 
+  /**
+   * Recursively resolve the reference values of the list of reference property `p` (but only the
+   * selected sub-properties).
+   */
+  private void resolveReferenceListProperty(
+      Property p, List<Selection> selections, String propertyName) {
+    try {
+      p.parseValue();
+    } catch (Message m) {
+      p.addError(m);
+    }
+
+    CollectionValue values = (CollectionValue) p.getValue();
+    for (IndexedSingleValue sv : values) {
+      resolveReferenceValue((ReferenceValue) sv.getWrapped(), selections, propertyName);
+    }
+  }
+
+  /**
+   * Recursively resolve the reference values of the reference property `p` (but only the selected
+   * sub-properties).
+   */
+  private void resolveReferenceProperty(
+      Property p, List<Selection> selections, String propertyName) {
+    try {
+      p.parseValue();
+    } catch (Message m) {
+      p.addError(m);
+    }
+
+    resolveReferenceValue((ReferenceValue) p.getValue(), selections, propertyName);
+  }
+
+  /**
+   * Recursively resolve the reference value.
+   *
+   * <p>To be called by {@link #resolveReferenceListProperty(Property, List, String)} and {@link
+   * #resolveReferenceProperty(Property, List, String)}.
+   */
+  private void resolveReferenceValue(
+      ReferenceValue value, List<Selection> selections, String propertyName) {
+    RetrieveEntity ref = new RetrieveEntity(value.getId());
+    // recursion! (Only for the matching selections)
+    retrieveFullEntity(ref, getSubSelects(selections, propertyName));
+    value.setEntity(ref, true);
+  }
+
   /**
    * Retrieve the Entities which match the selections and are referenced by the Entity 'e'.
    *
@@ -124,27 +173,55 @@ public class RetrieveFullEntity extends BackendTransaction {
    */
   public void retrieveSubEntities(EntityInterface e, List<Selection> selections) {
     for (final Selection s : selections) {
-      if (s.getSubselection() != null) {
-
-        String propertyName = s.getSelector();
-
-        // Find matching (i.e. referencing) Properties
-        for (Property p : e.getProperties()) {
-          // get reference properties by name.
-          if (propertyName.equalsIgnoreCase(p.getName())
-              && p.getDatatype() instanceof ReferenceDatatype) {
-            if (p.getValue() != null) {
+      String propertyName = s.getSelector();
+      for (Property p : e.getProperties()) {
+        if (s.getSubselection() != null) {
+          // The presence of sub-selections means that the properties are
+          // expected to be references (list or plain).
+          if (p.getValue() != null) {
+            if (propertyName.equalsIgnoreCase(p.getName())) {
+              if (p.isReference()) {
+                // handle (single) reference properties with matching name...
+                resolveReferenceProperty(p, selections, propertyName);
+                continue;
+              } else if (p.isReferenceList()) {
+                // handle (list) reference properties with matching name...
+                resolveReferenceListProperty(p, selections, propertyName);
+                continue;
+              }
+            } else {
               try {
-                p.parseValue();
-              } catch (Message m) {
-                p.addError(m);
+                boolean isSubtype = execute(new IsSubType(p.getId(), propertyName)).isSubType();
+                if (isSubtype) {
+                  // ... handle reference properties that are a subtype of `propertyName`.
+                  if (p.getValue() != null) {
+                    if (p.isReference()) {
+                      resolveReferenceProperty(p, selections, propertyName);
+                    } else if (p.isReferenceList()) {
+                      resolveReferenceListProperty(p, selections, propertyName);
+                    }
+                  }
+
+                  // the name is set the the super-types name! Otherwise, clients
+                  // wouldn't know what this property is supposed to be.
+                  p.setName(propertyName);
+                }
+              } catch (EntityDoesNotExistException exc) {
+                // unknown parent name.
               }
-
-              ReferenceValue value = (ReferenceValue) p.getValue();
-              RetrieveEntity ref = new RetrieveEntity(value.getId());
-              // recursion!  (Only for the matching selections)
-              retrieveFullEntity(ref, getSubSelects(selections, propertyName));
-              value.setEntity(ref, true);
+            }
+          }
+        } else {
+          // no subselections - no need to resolve any references...
+          if (!propertyName.equalsIgnoreCase(p.getName())) {
+            // ... we only need to cover property sub-typing
+            try {
+              boolean isSubtype = execute(new IsSubType(p.getId(), propertyName)).isSubType();
+              if (isSubtype) {
+                p.setName(propertyName);
+              }
+            } catch (EntityDoesNotExistException exc) {
+              // unknown parent name.
             }
           }
         }
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
index 2bc6a92a2449a8ba2d42119c0ba743129bcee2ac..c73023bd4429db19ca0a968aa99b657f0d0cbea4 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java
@@ -33,9 +33,16 @@ 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 =
@@ -45,6 +52,7 @@ public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Ru
   private final Integer entity;
   private final Integer domain;
   private ArrayList<Job> jobs;
+  private String transactionType;
 
   public RuleLoader(
       final Integer domain,
@@ -55,18 +63,17 @@ public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Ru
     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.transaction.getClass().getSimpleName()
-        + ">";
+    return "<" + this.domain + "," + this.entity + "," + this.transactionType + ">";
   }
 
   public ArrayList<Job> getJobs() {
@@ -76,7 +83,7 @@ public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Ru
   @Override
   public ArrayList<Rule> executeNoCache() throws TransactionException {
     final RuleLoaderImpl t = getImplementation(RuleLoaderImpl.class);
-    return t.executeNoCache(this.domain, this.entity, this.transaction.getClass().getSimpleName());
+    return t.executeNoCache(this.domain, this.entity, this.transactionType);
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java
similarity index 93%
rename from src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java
rename to src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java
index 2bf965575884f29640a80ec3d3ab284b7779c8f2..275947abd9b42a2424141bc28111889859fb1475 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntity.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/UpdateEntityTransaction.java
@@ -28,11 +28,11 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.container.TransactionContainer;
 import org.caosdb.server.utils.EntityStatus;
 
-public class UpdateEntity extends BackendTransaction {
+public class UpdateEntityTransaction extends BackendTransaction {
 
   private final TransactionContainer container;
 
-  public UpdateEntity(final TransactionContainer container) {
+  public UpdateEntityTransaction(final TransactionContainer container) {
     this.container = container;
   }
 
diff --git a/src/main/java/org/caosdb/server/datatype/SQLiteDatatype.java b/src/main/java/org/caosdb/server/datatype/SQLiteDatatype.java
deleted file mode 100644
index ffd4341013176992e802e8e0c8c4bc2d58b78ad3..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/datatype/SQLiteDatatype.java
+++ /dev/null
@@ -1,26 +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.datatype;
-
-@DatatypeDefinition(name = "SQLite")
-public class SQLiteDatatype extends ReferenceDatatype {}
diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java
index 2a62f6390e95cccd831ce5328db07abc47feab8c..e331ce84517bf00df05ea052382aeaab8395946a 100644
--- a/src/main/java/org/caosdb/server/entity/Entity.java
+++ b/src/main/java/org/caosdb/server/entity/Entity.java
@@ -41,6 +41,7 @@ import org.caosdb.server.datatype.AbstractCollectionDatatype;
 import org.caosdb.server.datatype.AbstractDatatype;
 import org.caosdb.server.datatype.CollectionValue;
 import org.caosdb.server.datatype.GenericValue;
+import org.caosdb.server.datatype.ReferenceDatatype;
 import org.caosdb.server.datatype.Value;
 import org.caosdb.server.entity.Message.MessageType;
 import org.caosdb.server.entity.container.ParentContainer;
@@ -1132,4 +1133,16 @@ public class Entity extends AbstractObservable implements EntityInterface {
     }
     return getId().toString();
   }
+
+  @Override
+  public boolean isReference() {
+    return this.hasDatatype() && this.getDatatype() instanceof ReferenceDatatype;
+  }
+
+  @Override
+  public boolean isReferenceList() {
+    return this.hasDatatype()
+        && this.getDatatype() instanceof AbstractCollectionDatatype
+        && ((AbstractCollectionDatatype) getDatatype()).getDatatype() instanceof ReferenceDatatype;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/entity/EntityInterface.java b/src/main/java/org/caosdb/server/entity/EntityInterface.java
index ef97630808536f367869851c5be06d05e6247ac2..954bcc47ca5b6117d3164ba3c9c585dbd373d4f8 100644
--- a/src/main/java/org/caosdb/server/entity/EntityInterface.java
+++ b/src/main/java/org/caosdb/server/entity/EntityInterface.java
@@ -194,4 +194,13 @@ public interface EntityInterface
   public abstract void setVersion(Version version);
 
   public abstract void addToElement(Element element, SetFieldStrategy strategy);
+
+  /** Return true iff the data type is present and is an instance of ReferenceDatatype. */
+  public abstract boolean isReference();
+
+  /**
+   * Return true iff the data type is present, an instance of AbstractCollectionDatatype and the
+   * AbstractCollectionDatatype's elements' data type is an instance of ReferenceDatatype.
+   */
+  public abstract boolean isReferenceList();
 }
diff --git a/src/main/java/org/caosdb/server/entity/FileProperties.java b/src/main/java/org/caosdb/server/entity/FileProperties.java
index 0c10288e0cbaa6a4f55a2959f72ac09db9d6f394..c2c2c10fae44d4d84af56a33a052f5ed589debed 100644
--- a/src/main/java/org/caosdb/server/entity/FileProperties.java
+++ b/src/main/java/org/caosdb/server/entity/FileProperties.java
@@ -289,4 +289,8 @@ public class FileProperties {
   public void removeOnCleanUp(final String tempPath) {
     this.tempPath = tempPath;
   }
+
+  public boolean hasTmpIdentifier() {
+    return this.tmpIdentifyer != null;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/entity/InsertEntity.java b/src/main/java/org/caosdb/server/entity/InsertEntity.java
index 56eeb12b1c2e7b9d5259a4d418e87c79e8068f51..4f7c33015e6ba70fb3d062ff4b32c27ebd80b8a8 100644
--- a/src/main/java/org/caosdb/server/entity/InsertEntity.java
+++ b/src/main/java/org/caosdb/server/entity/InsertEntity.java
@@ -29,4 +29,8 @@ public class InsertEntity extends WritableEntity {
   public InsertEntity(final Element element) {
     super(element);
   }
+
+  public InsertEntity(String name, Role role) {
+    super(name, role);
+  }
 }
diff --git a/src/main/java/org/caosdb/server/entity/ValidEntity.java b/src/main/java/org/caosdb/server/entity/ValidEntity.java
deleted file mode 100644
index 6378e044e65afe4b7340cefd04e0b09153e5ca94..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/entity/ValidEntity.java
+++ /dev/null
@@ -1,30 +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.entity;
-
-public class ValidEntity extends Entity {
-
-  public ValidEntity(final int id) {
-    super(id);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/entity/WritableEntity.java b/src/main/java/org/caosdb/server/entity/WritableEntity.java
index d9dc3edc4fc1fa886370bafb5c40ce0e732b3e4b..ab8ccbb995430e267e84d1f2a235c0d928e6762d 100644
--- a/src/main/java/org/caosdb/server/entity/WritableEntity.java
+++ b/src/main/java/org/caosdb/server/entity/WritableEntity.java
@@ -29,4 +29,8 @@ public class WritableEntity extends Entity {
   public WritableEntity(final Element element) {
     super(element);
   }
+
+  public WritableEntity(String name, Role role) {
+    super(name, role);
+  }
 }
diff --git a/src/main/java/org/caosdb/server/entity/container/DeleteContainer.java b/src/main/java/org/caosdb/server/entity/container/DeleteContainer.java
deleted file mode 100644
index b0c2f21ff3b0e0dd96e24f0ef042a29e843c54f0..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/entity/container/DeleteContainer.java
+++ /dev/null
@@ -1,50 +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.entity.container;
-
-import java.util.HashMap;
-import org.apache.shiro.subject.Subject;
-import org.caosdb.server.entity.DeleteEntity;
-
-public class DeleteContainer extends EntityByIdContainer {
-
-  private static final long serialVersionUID = -1279198934417710461L;
-
-  public DeleteContainer(
-      final Subject user,
-      final Long timestamp,
-      final String srid,
-      final HashMap<String, String> flags) {
-    super(user, timestamp, srid, flags);
-  }
-
-  @Override
-  public void add(final int id) {
-    add(new DeleteEntity(id));
-  }
-
-  @Override
-  public void add(int id, String version) {
-    add(new DeleteEntity(id, version));
-  }
-}
diff --git a/src/main/java/org/caosdb/server/entity/container/InsertContainer.java b/src/main/java/org/caosdb/server/entity/container/InsertContainer.java
deleted file mode 100644
index 99128a8a56758e67ce1529785de0151b1fe624d4..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/entity/container/InsertContainer.java
+++ /dev/null
@@ -1,46 +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.entity.container;
-
-import java.util.HashMap;
-import org.apache.shiro.subject.Subject;
-import org.caosdb.server.entity.InsertEntity;
-import org.jdom2.Element;
-
-public class InsertContainer extends WritableContainer {
-
-  public InsertContainer(
-      final Subject user,
-      final Long timestamp,
-      final String srid,
-      final HashMap<String, String> flags) {
-    super(user, timestamp, srid, flags);
-  }
-
-  private static final long serialVersionUID = 8896630069350477274L;
-
-  @Override
-  public void add(final Element entity) {
-    add(new InsertEntity(entity));
-  }
-}
diff --git a/src/main/java/org/caosdb/server/entity/container/UpdateContainer.java b/src/main/java/org/caosdb/server/entity/container/UpdateContainer.java
deleted file mode 100644
index 1c2a807b7759ff680d30ed0f1f9f14d5952e98f8..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/entity/container/UpdateContainer.java
+++ /dev/null
@@ -1,46 +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.entity.container;
-
-import java.util.HashMap;
-import org.apache.shiro.subject.Subject;
-import org.caosdb.server.entity.UpdateEntity;
-import org.jdom2.Element;
-
-public class UpdateContainer extends WritableContainer {
-
-  private static final long serialVersionUID = 8489287672264669883L;
-
-  public UpdateContainer(
-      final Subject user,
-      final Long timestamp,
-      final String srid,
-      final HashMap<String, String> flags) {
-    super(user, timestamp, srid, flags);
-  }
-
-  @Override
-  public void add(final Element entity) {
-    add(new UpdateEntity(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 2f9148651c890a0e7188acb035bf8fc55609834b..5fdb8c39ec7f01280278bec0a1f2928bfed7f5c2 100644
--- a/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
+++ b/src/main/java/org/caosdb/server/entity/container/WritableContainer.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
@@ -23,12 +25,12 @@
 package org.caosdb.server.entity.container;
 
 import java.util.HashMap;
+import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.entity.FileProperties;
-import org.jdom2.Element;
 
-public abstract class WritableContainer extends TransactionContainer {
-  private static final long serialVersionUID = 3011242254959617091L;
+public class WritableContainer extends TransactionContainer {
+  private static final long serialVersionUID = -4097777313518959519L;
 
   public WritableContainer(
       final Subject user,
@@ -38,7 +40,9 @@ public abstract class WritableContainer extends TransactionContainer {
     super(user, timestamp, srid, flags);
   }
 
-  public abstract void add(final Element entity);
+  public WritableContainer() {
+    this(SecurityUtils.getSubject(), System.currentTimeMillis(), null, null);
+  }
 
   @Override
   public void addFile(final String uploadId, final FileProperties fileProperties) {
diff --git a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java
index e042c2326cc43e5d98cc3a27b61c21301bf9e90d..c83cb67883f5a616109268fb6d62bae40cd73e6b 100644
--- a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java
+++ b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java
@@ -580,4 +580,14 @@ public class EntityWrapper implements EntityInterface {
   public void addToElement(Element element, SetFieldStrategy strategy) {
     this.entity.addToElement(element, strategy);
   }
+
+  @Override
+  public boolean isReference() {
+    return this.entity.isReference();
+  }
+
+  @Override
+  public boolean isReferenceList() {
+    return this.entity.isReferenceList();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java
index e6dc2b21d088ceeff9e0e7a9b60547b288893f18..89420c596894da9be35e33a507821c96d10f5b7d 100644
--- a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java
+++ b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java
@@ -4,8 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
- * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020-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
@@ -25,6 +25,8 @@
 package org.caosdb.server.entity.xml;
 
 import org.apache.shiro.SecurityUtils;
+import org.caosdb.server.datatype.CollectionValue;
+import org.caosdb.server.datatype.IndexedSingleValue;
 import org.caosdb.server.datatype.ReferenceValue;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
@@ -87,7 +89,7 @@ public class EntityToElementStrategy implements ToElementStrategy {
     if (setFieldStrategy.isToBeSet("id") && entity.hasId()) {
       element.setAttribute("id", Integer.toString(entity.getId()));
     }
-    if (setFieldStrategy.isToBeSet("version") && entity.hasVersion()) {
+    if (entity.hasVersion()) {
       Element v = new VersionXMLSerializer().toElement(entity.getVersion());
       element.addContent(v);
     }
@@ -137,8 +139,7 @@ public class EntityToElementStrategy implements ToElementStrategy {
         // CheckValueParsable job.
       }
 
-      if (entity.getValue() instanceof ReferenceValue
-          && setFieldStrategy.isToBeSet("_referenced")) {
+      if (entity.isReference() && setFieldStrategy.isToBeSet("_referenced")) {
         // Append the complete entity. This needs to be done when we are
         // processing SELECT Queries.
         EntityInterface ref = ((ReferenceValue) entity.getValue()).getEntity();
@@ -151,6 +152,26 @@ public class EntityToElementStrategy implements ToElementStrategy {
           // adding the reference id as well.
           return;
         }
+      } else if (entity.isReferenceList() && setFieldStrategy.isToBeSet("_referenced")) {
+        // Append the all referenced entities. This needs to be done when we are
+        // processing SELECT Queries.
+        boolean skipValue = false;
+        for (IndexedSingleValue sv : ((CollectionValue) entity.getValue())) {
+          EntityInterface ref = ((ReferenceValue) sv.getWrapped()).getEntity();
+          if (ref != null) {
+            if (entity.hasDatatype()) {
+              setDatatype(entity, element);
+            }
+            Element valueElem = new Element("Value");
+            ref.addToElement(valueElem, setFieldStrategy);
+            element.addContent(valueElem);
+            skipValue = true;
+          }
+        }
+        if (skipValue)
+          // the referenced entity has been appended. Return here to suppress
+          // adding the reference id as well.
+          return;
       }
 
       if (setFieldStrategy.isToBeSet("value")) {
diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java
index 4d744383897a121032e14bd97a3d859f950feae9..86ba49fabeedf04833f4e82214876be698aaa8cd 100644
--- a/src/main/java/org/caosdb/server/jobs/Job.java
+++ b/src/main/java/org/caosdb/server/jobs/Job.java
@@ -37,7 +37,7 @@ import org.caosdb.server.CaosDBException;
 import org.caosdb.server.database.BackendTransaction;
 import org.caosdb.server.database.backend.transaction.GetIDByName;
 import org.caosdb.server.database.backend.transaction.IsSubType;
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntity;
+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;
@@ -54,12 +54,10 @@ import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 import org.reflections.Reflections;
 
-
 /**
  * This is a Job.
  *
  * @todo Describe me.
- *
  */
 public abstract class Job {
   private Transaction<? extends TransactionContainer> transaction = null;
@@ -213,7 +211,7 @@ public abstract class Job {
   }
 
   protected final EntityInterface retrieveValidEntity(Integer id) {
-    return execute(new RetrieveFullEntity(id)).getContainer().get(0);
+    return execute(new RetrieveFullEntityTransaction(id)).getContainer().get(0);
   }
 
   protected final Integer retrieveValidIDByName(final String name) {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckEntityACLRoles.java b/src/main/java/org/caosdb/server/jobs/core/CheckEntityACLRoles.java
index e2dd63b1d733d737e09a6b4e86f3fe4ae4bd0e30..037b38b90c94827265d47876e722686d5d030cc0 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckEntityACLRoles.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckEntityACLRoles.java
@@ -26,10 +26,10 @@ import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.ServerProperties;
 import org.caosdb.server.accessControl.AuthenticationUtils;
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.InsertEntity;
+import org.caosdb.server.entity.UpdateEntity;
 import org.caosdb.server.jobs.ContainerJob;
 import org.caosdb.server.permissions.EntityACI;
-import org.caosdb.server.transaction.Insert;
-import org.caosdb.server.transaction.Update;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
@@ -37,12 +37,9 @@ public class CheckEntityACLRoles extends ContainerJob {
 
   @Override
   protected void run() {
-    if (!(getTransaction() instanceof Update || getTransaction() instanceof Insert)) {
-      return;
-    }
-
     for (final EntityInterface entity : getContainer()) {
-      if (entity.getEntityACL() != null) {
+      if ((entity instanceof UpdateEntity || entity instanceof InsertEntity)
+          && entity.getEntityACL() != null) {
         for (final EntityACI aci : entity.getEntityACL().getRules()) {
           if (!AuthenticationUtils.isResponsibleAgentExistent(aci.getResponsibleAgent())) {
             if (CaosDBServer.getServerProperty(ServerProperties.KEY_CHECK_ENTITY_ACL_ROLES_MODE)
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 c592eca81394e4a92582fd3d2286b5ac0507dcce..7aca2bac3dd3709006497f9ce8f556f267c4ca81 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.jobs.core;
 
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntity;
+import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.StatementStatus;
 import org.caosdb.server.jobs.EntityJob;
@@ -48,7 +48,7 @@ public class CheckParOblPropPresent extends EntityJob {
     runJobFromSchedule(getEntity(), CheckParValid.class);
 
     if (getEntity().hasParents()) {
-      execute(new RetrieveFullEntity(getEntity().getParents()));
+      execute(new RetrieveFullEntityTransaction(getEntity().getParents()));
     }
 
     // inherit properties
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
index 38320291d0d882e6eb8eb2eb54f9ba0e31c64dda..8563ea716072602684dc1a78870392d573dc3a22 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
@@ -4,8 +4,8 @@
  *
  * 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
@@ -26,10 +26,8 @@ package org.caosdb.server.jobs.core;
 
 import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
 import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
-import org.caosdb.server.datatype.AbstractCollectionDatatype;
 import org.caosdb.server.datatype.CollectionValue;
 import org.caosdb.server.datatype.IndexedSingleValue;
-import org.caosdb.server.datatype.ReferenceDatatype;
 import org.caosdb.server.datatype.ReferenceValue;
 import org.caosdb.server.entity.Entity;
 import org.caosdb.server.entity.EntityInterface;
@@ -50,7 +48,7 @@ public class CheckRefidValid extends EntityJob implements Observer {
   @Override
   public final void run() {
     try {
-      if (isReference(getEntity())) {
+      if (assureReference(getEntity())) {
         if (getEntity().hasValue()) {
 
           // parse referenced id
@@ -59,11 +57,9 @@ public class CheckRefidValid extends EntityJob implements Observer {
             return;
           }
 
-          if (getEntity().getDatatype() instanceof ReferenceDatatype) {
+          if (getEntity().isReference()) {
             checkRefValue((ReferenceValue) getEntity().getValue());
-          } else if (getEntity().getDatatype() instanceof AbstractCollectionDatatype
-              && ((AbstractCollectionDatatype) getEntity().getDatatype()).getDatatype()
-                  instanceof ReferenceDatatype) {
+          } else if (getEntity().isReferenceList()) {
             final CollectionValue vals = (CollectionValue) getEntity().getValue();
             for (final IndexedSingleValue v : vals) {
               if (v != null && v.getWrapped() != null) {
@@ -176,14 +172,15 @@ public class CheckRefidValid extends EntityJob implements Observer {
     return true;
   }
 
-  private final boolean isReference(final EntityInterface entity) {
+  /**
+   * Return true if this is a reference or a list of reference property.
+   *
+   * <p>If the data type is not present (yet), append a data type listener which calls this job
+   * again when the data type is present.
+   */
+  private final boolean assureReference(final EntityInterface entity) {
     if (entity.hasDatatype()) {
-      if (entity.getDatatype() instanceof ReferenceDatatype) {
-        return true;
-      } else if (entity.getDatatype() instanceof AbstractCollectionDatatype) {
-        return ((AbstractCollectionDatatype) entity.getDatatype()).getDatatype()
-            instanceof ReferenceDatatype;
-      }
+      return entity.isReference() || entity.isReferenceList();
     } else {
       entity.acceptObserver(this);
     }
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 b3f91338883f16dcc1fd00b1fecbbae775f787b9..107c5768a5c15fbb05ea6d68b0cb956cbb235687 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
@@ -24,17 +24,17 @@ package org.caosdb.server.jobs.core;
 
 import java.util.ArrayList;
 import java.util.List;
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntity;
+import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.entity.Entity;
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.InsertEntity;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Message.MessageType;
 import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.UpdateEntity;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.ScheduledJob;
-import org.caosdb.server.transaction.Insert;
-import org.caosdb.server.transaction.Update;
 import org.caosdb.server.utils.EntityStatus;
 
 /**
@@ -64,7 +64,7 @@ public class Inheritance extends EntityJob {
 
   @Override
   protected void run() {
-    if (getTransaction() instanceof Insert || getTransaction() instanceof Update) {
+    if (getEntity() instanceof InsertEntity || getEntity() instanceof UpdateEntity) {
       if (getEntity().hasParents()) {
         final ArrayList<Property> transfer = new ArrayList<>();
         parentLoop:
@@ -88,7 +88,7 @@ public class Inheritance extends EntityJob {
             EntityInterface foreign = getEntityByName(parent.getName());
             if (foreign == null) {
               // was not in container -> retrieve from database.
-              execute(new RetrieveFullEntity(parent));
+              execute(new RetrieveFullEntityTransaction(parent));
               foreign = parent;
             }
 
@@ -147,7 +147,7 @@ public class Inheritance extends EntityJob {
                 }
               }
             } else {
-              execute(new RetrieveFullEntity(validProperty));
+              execute(new RetrieveFullEntityTransaction(validProperty));
             }
             if (validProperty.getEntityStatus() == EntityStatus.VALID) {
               collectInheritedProperties(transfer, validProperty, inheritance);
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 73d7ee3f5be0b8847ce322f6774719b7f5a768c2..35ce49297291accd41a7c293fc909cc0a27f9317 100644
--- a/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java
+++ b/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.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
@@ -34,16 +36,19 @@ import org.caosdb.server.FileSystem;
 import org.caosdb.server.ServerProperties;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.misc.RollBackHandler;
-import org.caosdb.server.entity.Entity;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.FileProperties;
+import org.caosdb.server.entity.InsertEntity;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Message.MessageType;
+import org.caosdb.server.entity.RetrieveEntity;
 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.transaction.Retrieve;
+import org.caosdb.server.transaction.WriteTransactionInterface;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.FileUtils;
 import org.caosdb.server.utils.Undoable;
@@ -208,7 +213,7 @@ public class InsertFilesInDir extends FlagJob {
         } else {
           i++;
           final String targetPath = root + sub.getName();
-          final Entity newFileEntity = new Entity(sub.getName(), Role.File);
+          final EntityInterface newFileEntity = createInsertFileEntity(sub.getName());
           final long size = sub.length();
           final FileProperties fp = new FileProperties(null, targetPath, size);
           newFileEntity.setFileProperties(fp);
@@ -227,7 +232,7 @@ public class InsertFilesInDir extends FlagJob {
               continue;
             }
 
-            // add create symlink and file record to this
+            // add create symlink and add file record to this
             // container if the target
             // path is allowed
             if (FileSystem.checkTarget(
@@ -253,6 +258,22 @@ public class InsertFilesInDir extends FlagJob {
     return i;
   }
 
+  /**
+   * Create a new InsertEntity (if this is an actual run) or a new RetrieveEntity (in dry-run mode)
+   * with {@link Role.File}.
+   *
+   * @param name the file name
+   * @return new File entity
+   */
+  private EntityInterface createInsertFileEntity(String name) {
+    if (getTransaction() instanceof WriteTransactionInterface) {
+      return new InsertEntity(name, Role.File);
+    }
+    EntityInterface result = new RetrieveEntity(name);
+    result.setRole(Role.File);
+    return result;
+  }
+
   boolean isExcluded(File f) throws IOException {
     return this.exclude != null && this.exclude.matcher(f.getCanonicalPath()).find();
   }
@@ -317,7 +338,7 @@ public class InsertFilesInDir extends FlagJob {
     return true;
   }
 
-  private void loadJobs(final Entity e) {
+  private void loadJobs(final EntityInterface e) {
     final List<Job> loadJobs = loadJobs(e, getTransaction());
     getTransaction().getSchedule().addAll(loadJobs);
   }
diff --git a/src/main/java/org/caosdb/server/query/Backreference.java b/src/main/java/org/caosdb/server/query/Backreference.java
index b9114ad55147ca92163a55cf374d3b13093ead98..fec64b19b761418a4da148ca4adff9d9145960af 100644
--- a/src/main/java/org/caosdb/server/query/Backreference.java
+++ b/src/main/java/org/caosdb/server/query/Backreference.java
@@ -130,6 +130,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface {
         callApplyBackRef.setNull(2, VARCHAR);
       }
       if (this.propertiesTable != null) { // propertiesTable
+        getQuery().filterEntitiesWithoutRetrievePermission(this.propertiesTable);
         callApplyBackRef.setString(3, this.propertiesTable);
         this.statistics.put("propertiesTable", this.propertiesTable);
         this.statistics.put(
@@ -139,6 +140,7 @@ public class Backreference implements EntityFilterInterface, QueryInterface {
         callApplyBackRef.setNull(3, VARCHAR);
       }
       if (this.entitiesTable != null) { // entitiesTable
+        getQuery().filterEntitiesWithoutRetrievePermission(this.entitiesTable);
         callApplyBackRef.setString(4, this.entitiesTable);
         this.statistics.put("entitiesTable", this.entitiesTable);
         this.statistics.put(
@@ -330,4 +332,12 @@ public class Backreference implements EntityFilterInterface, QueryInterface {
   public boolean isVersioned() {
     return this.query.isVersioned();
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(toString());
+    if (this.hasSubProperty()) sb.append(getSubProperty().getCacheKey());
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4
index ec363be59893ad72cf412dbdc9d1f6d6644da5be..85061d30cacb222dd8c866d84a7abfb525a592a9 100644
--- a/src/main/java/org/caosdb/server/query/CQLLexer.g4
+++ b/src/main/java/org/caosdb/server/query/CQLLexer.g4
@@ -23,15 +23,15 @@
 lexer grammar CQLLexer;
 
 AS_A:
-	[Aa][Ss] (EMPTY_SPACE? A)?
+	[Aa][Ss] (WHITE_SPACE_f? A)? WHITE_SPACE_f?
 ;
 
 IS_REFERENCED:
-	(IS_f EMPTY_SPACE?)? [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee][Dd]
+	(IS_f WHITE_SPACE_f?)? [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee][Dd] WHITE_SPACE_f?
 ;
 
 BY:
-	[Bb][Yy]
+	[Bb][Yy] WHITE_SPACE_f?
 ;
 
 fragment
@@ -50,69 +50,73 @@ VERSION_f:
 ;
 
 ANY_VERSION_OF:
-	(ANY_f EMPTY_SPACE VERSION_f EMPTY_SPACE OF_f)
+	(ANY_f WHITE_SPACE_f VERSION_f WHITE_SPACE_f OF_f) WHITE_SPACE_f?
 ;
 
 SELECT:
-    [Ss][Ee][Ll][Ee][Cc][Tt] -> pushMode(SELECT_MODE)
+    [Ss][Ee][Ll][Ee][Cc][Tt] WHITE_SPACE_f? -> pushMode(SELECT_MODE)
 ;
 
 INSERTED:
-	[Ii][Nn][Ss][Ee][Rr][Tt][Ee][Dd]
+	[Ii][Nn][Ss][Ee][Rr][Tt][Ee][Dd] WHITE_SPACE_f?
 ;
 
 CREATED:
-	[Cc][Rr][Ee][Aa][Tt][Ee][Dd]
+	[Cc][Rr][Ee][Aa][Tt][Ee][Dd] WHITE_SPACE_f?
 ;
 
 UPDATED:
-	[Uu][Pp][Dd][Aa][Tt][Ee][Dd]
+	[Uu][Pp][Dd][Aa][Tt][Ee][Dd] WHITE_SPACE_f?
 ;
 
 ON:
-	[Oo][Nn]
+	[Oo][Nn] WHITE_SPACE_f?
 ;
 
 IN:
-	[Ii][Nn]
+	[Ii][Nn] WHITE_SPACE_f?
 ;
 
 IS_STORED_AT:
-	(IS_f EMPTY_SPACE?)? [Ss][Tt][Oo][Rr][Ee][Dd] (EMPTY_SPACE? AT)?
+	(IS_f WHITE_SPACE_f?)? [Ss][Tt][Oo][Rr][Ee][Dd] (WHITE_SPACE_f? AT)? WHITE_SPACE_f?
 ;
 
 AT:
-	[Aa][Tt]
+	[Aa][Tt] WHITE_SPACE_f?
 ;
 
 FIND:
-	[Ff][Ii][Nn][Dd]
+	[Ff][Ii][Nn][Dd] WHITE_SPACE_f?
 ;
 
 COUNT:
-	[Cc][Oo][Uu][Nn][Tt]
+	[Cc][Oo][Uu][Nn][Tt] WHITE_SPACE_f?
 ;
 	
 AND:
-	( 
-		[Aa][Nn][Dd] 
-	)
-	| '&'
+	(
+		(
+			[Aa][Nn][Dd]
+		)
+		| '&'
+	) WHITE_SPACE_f?
 ;
 
 OR:
-	( 
-		[Oo][Rr] 
-	)
-	| '|'
+	(
+		(
+			[Oo][Rr]
+		)
+		| '|'
+	) WHITE_SPACE_f?
 ;
 
 LPAREN:
-	'('
+	'(' WHITE_SPACE_f?
 ;
 
 RPAREN:
-	')'
+	')' WHITE_SPACE_f?
 ;
 
 SINGLE_QUOTE_START:
@@ -131,19 +135,19 @@ OPERATOR:
 	| '>'
 	| '!='
 	| '->'
-	| [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee]([Ss]|EMPTY_SPACE? [Tt][Oo]) (EMPTY_SPACE? 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]
+	[Ll][Ii][Kk][Ee] WHITE_SPACE_f?
 ;
 
 IS_NULL:
-	IS_f EMPTY_SPACE NULL_f
+	IS_f WHITE_SPACE_f NULL_f WHITE_SPACE_f?
 ;
 
 IS_NOT_NULL:
-	IS_f EMPTY_SPACE NOT_f EMPTY_SPACE NULL_f
+	IS_f WHITE_SPACE_f NOT_f WHITE_SPACE_f NULL_f WHITE_SPACE_f?
 ;
 
 fragment
@@ -163,7 +167,7 @@ NOT_f:
 
 fragment
 DOESNT_f:
-	DOES_f EMPTY_SPACE? NOT_f
+	DOES_f WHITE_SPACE_f? NOT_f
 	| DOES_f [Nn] SINGLE_QUOTE [Tt]
 ;
 
@@ -244,88 +248,92 @@ WASNT_f:
 ;
 
 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)?
+	(
+		'!'
+		| 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]
+	[Ww][Ii][Tt][Hh] WHITE_SPACE_f?
 ;
 
 THE: 
-	[Tt][Hh][Ee]
+	[Tt][Hh][Ee] WHITE_SPACE_f?
 ;
 
 GREATEST:
-	[Gg][Rr][Ee][Aa][Tt][Ee][Ss][Tt]
+	[Gg][Rr][Ee][Aa][Tt][Ee][Ss][Tt] WHITE_SPACE_f?
 ;
 
 SMALLEST:
-	[Ss][Mm][Aa][Ll][Ll][Ee][Ss][Tt]
+	[Ss][Mm][Aa][Ll][Ll][Ee][Ss][Tt] WHITE_SPACE_f?
 ;
 
 A:
-	[Aa][Nn]?
+	[Aa][Nn]? WHITE_SPACE_f?
 ;
 
 ME:
-	[Mm][Ee]
+	[Mm][Ee] WHITE_SPACE_f?
 ;
 
 SOMEONE:
-	[Ss][Oo][Mm][Ee][Oo][Nn][Ee]
+	[Ss][Oo][Mm][Ee][Oo][Nn][Ee] WHITE_SPACE_f?
 ;
 
 ELSE:
-	[Ee][Ll][Ss][Ee]
+	[Ee][Ll][Ss][Ee] WHITE_SPACE_f?
 ;
 
 WHERE:
-	[Ww][Hh][Ee][Rr][Ee]
+	[Ww][Hh][Ee][Rr][Ee] WHITE_SPACE_f?
 ;
 
 WHICH:
-	[Ww][Hh][Ii][Cc][Hh]
+	[Ww][Hh][Ii][Cc][Hh] WHITE_SPACE_f?
 ;
 
 HAS_A:
-	(HAS_f | HAD_f | HAVE_f | WERE_f | WAS_f | IS_f)
 	(
-		(EMPTY_SPACE? A)
-		| (EMPTY_SPACE? BEEN_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])
+	[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])?
+	[Rr][Ee][Cc][Oo][Rr][Dd][Tt][Yy][Pp][Ee]([Ss])? WHITE_SPACE_f?
 ;
 
 RECORD:
-	[Rr][Ee][Cc][Oo][Rr][Dd]([Ss])?
+	[Rr][Ee][Cc][Oo][Rr][Dd]([Ss])? WHITE_SPACE_f?
 ;
 
 FILE:
-	[Ff][Ii][Ll][Ee]([Ss])?
+	[Ff][Ii][Ll][Ee]([Ss])? WHITE_SPACE_f?
 ;
 
 ENTITY:
-    [Ee][Nn][Tt][Ii][Tt]([Yy]|[Ii][Ee][Ss])
+    [Ee][Nn][Tt][Ii][Tt]([Yy]|[Ii][Ee][Ss]) WHITE_SPACE_f?
 ;
 
 QUERYTEMPLATE:
-	[Qq][Uu][Ee][Rr][yY][Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]
+	[Qq][Uu][Ee][Rr][yY][Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee] WHITE_SPACE_f?
 ;
 
 fragment
@@ -334,12 +342,12 @@ IS_f:
 ;
 
 fragment
-EMPTY_SPACE:
+WHITE_SPACE_f:
 	[ \t\n\r]+
 ;
 
 WHITE_SPACE:
-	[ \t\n\r]+ -> channel(HIDDEN)
+	[ \t\n\r]+
 ;
 
 fragment
@@ -365,12 +373,11 @@ REGEXP_END:
 ;
 
 ID: 
-	[Ii][Dd]
+	[Ii][Dd] WHITE_SPACE_f?
 ;
 
-// Multiple slashes should be allowed in paths instead of a single slash.
-SLASHES:
-	'/'+
+SLASH:
+	'/'
 ;
 
 STAR:
@@ -382,26 +389,26 @@ DOT:
 ;
 
 QMARK:
-	'?'
+	'?' WHITE_SPACE_f?
 ;
 
 BUT:
-	[Bb][Uu][Tt]
+	[Bb][Uu][Tt] WHITE_SPACE_f?
 ;
 
 ESC_REGEXP_END:
 	ESC_MARKER
-	'>>'
+	'>>' WHITE_SPACE_f?
 ;
 
 ESC_STAR:
 	ESC_MARKER
-	'*'
+	'*' WHITE_SPACE_f?
 ;
 
 ESC_BS:
 	ESC_MARKER
-	'\\'
+	'\\' WHITE_SPACE_f?
 ;
 
 fragment 
@@ -410,7 +417,7 @@ ESC_MARKER:
 ;
 
 TODAY:
-	[Tt][Oo][Dd][Aa][Yy]
+	[Tt][Oo][Dd][Aa][Yy] WHITE_SPACE_f?
 ;
 
 HYPHEN:
@@ -422,11 +429,17 @@ COLON:
 ;
 
 NUM:
+    NUM_f
+    | DOT NUM_f
+    | NUM_f DOT NUM_f
+;
+
+NUM_f:
 	('0'..'9')+
 ;
 
 TXT:
-	('a'..'z' | 'A'..'Z' | '_' | '-' {_input.LA(1) != '>'}? | '+' | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+
+	('a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' {_input.LA(1) != '>'}? | '+' | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+
 ;
 
 UNKNOWN_CHAR: . ;
@@ -473,7 +486,7 @@ mode DOUBLE_QUOTE_MODE;
 mode SELECT_MODE;
 
     FROM:
-         [Ff][Rr][Oo][Mm] -> mode(DEFAULT_MODE)
+         [Ff][Rr][Oo][Mm]([ \t\n\r])? -> mode(DEFAULT_MODE)
     ;
 
     SELECT_DOT:
diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4
index 0daeff47a95ff38391ce3f9b8004bec642b0ccb2..452321c670d4101bc603e657023143025f12c22f 100644
--- a/src/main/java/org/caosdb/server/query/CQLParser.g4
+++ b/src/main/java/org/caosdb/server/query/CQLParser.g4
@@ -46,15 +46,15 @@ cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r
 	   | FIND {$t = Query.Type.FIND;}
 	   | COUNT {$t = Query.Type.COUNT;})
 	(version {$v = $version.v;})?
-	(
-		(
-			role {$r = $role.r;}
-			(entity {$e = $entity.ep;})?
-		) | entity {$e = $entity.ep;}
-	)
+	( role {$r = $role.r;} )?
 	(
 		entity_filter {$filter = $entity_filter.filter;}
-	)??
+		|
+		entity WHITE_SPACE?{$e = $entity.ep;}
+		(
+			entity_filter {$filter = $entity_filter.filter;}
+		)?
+	)?
 	EOF
 ;
 
@@ -100,7 +100,7 @@ entity_filter returns [EntityFilterInterface filter]
 	which_exp
 	(
 		(
-			LPAREN
+			LPAREN WHITE_SPACE?
 			( 
 				filter_expression {$filter = $filter_expression.efi;}
 				| conjunction {$filter = $conjunction.c;} 
@@ -120,7 +120,7 @@ which_exp:
 	| HAS_A (PROPERTY)?
 	| WITH (A (PROPERTY)?)?
 	| WHERE
-	| DOT
+	| DOT WHITE_SPACE?
 ;
 
 filter_expression returns [EntityFilterInterface efi]
@@ -148,6 +148,7 @@ idfilter returns [IDFilter filter] locals [String o, String v, String a]
 	ID 
 	(
 		OPERATOR {$o = $OPERATOR.text;}
+		WHITE_SPACE?
 		value {$v = $value.str;}
 	)?
 ;
@@ -195,15 +196,14 @@ username returns [Query.Pattern ep] locals [int type]
     $ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);
 }
 :
-    ( STAR {$type = Query.Pattern.TYPE_LIKE;} | ESC_STAR | TXT | DOT | ESC_REGEXP_END | COLON  )+
+    ( STAR {$type = Query.Pattern.TYPE_LIKE;} | ~(STAR | WHITE_SPACE) )+
 ;
 
 transaction_time returns [String tqp]
 :
     (
         (ON | IN)
-        (value {$tqp = $value.text;}
-        | entity {$tqp = $entity.ep.toString();})
+        (value {$tqp = $value.text;})
     ) | TODAY {$tqp = TransactionFilter.TODAY;}
 ;
 
@@ -249,10 +249,10 @@ pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a]
 		property {$p = $property.pp; $a=$property.agg;} 
 		(
 			( 
-			  LIKE {$o = $LIKE.text;}
+			  LIKE {$o = $LIKE.text.trim();}
 			  ( like_pattern {$v = $like_pattern.ep.toString();}
 			    | value {$v = $value.str;} )
-			  | OPERATOR {$o = $OPERATOR.text;} value {$v = $value.str;}
+			  | OPERATOR {$o = $OPERATOR.text;} WHITE_SPACE? value {$v = $value.str;}
 			)
 			| IS_NULL {$o = "0";}
 			| IS_NOT_NULL {$o = "!0";}
@@ -266,12 +266,12 @@ pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a]
 		  ( like_pattern {$v = $like_pattern.ep.toString();}
 		    | value {$v = $value.str;} )
 		)
-		| ( OPERATOR {$o = $OPERATOR.text;} value {$v = $value.str;}
+		| ( OPERATOR {$o = $OPERATOR.text;} WHITE_SPACE? value {$v = $value.str;}
 		  ( AS_A
 			property {$p = $property.pp;} )?
 		)
 	)
-	
+	WHITE_SPACE?
 ;
 
 
@@ -296,9 +296,11 @@ backreference returns [Backreference ref] locals [Query.Pattern e, Query.Pattern
 	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]
@@ -311,6 +313,7 @@ storedat returns [StoredAt filter] locals [String loc]
 :
 	IS_STORED_AT
 	location {$loc = $location.str;}
+	WHITE_SPACE?
 ;
 
 conjunction returns [Conjunction c] locals [Conjunction dummy]
@@ -321,7 +324,7 @@ conjunction returns [Conjunction c] locals [Conjunction dummy]
 	(
 		f1 = filter_expression {$c.add($f1.efi);}
 		|
-		LPAREN
+		LPAREN WHITE_SPACE?
 		(
 			f4 = filter_expression {$c.add($f4.efi);}
 			| disjunction {$c.add($disjunction.d);}
@@ -330,6 +333,7 @@ conjunction returns [Conjunction c] locals [Conjunction dummy]
 		RPAREN
 	) 
 	( 
+		WHITE_SPACE?
 		AND
 		(
 			( which_exp | A (PROPERTY)?? )
@@ -337,7 +341,7 @@ conjunction returns [Conjunction c] locals [Conjunction dummy]
 		( 
 			f2 = filter_expression {$c.add($f2.efi);}
 			| (
-				LPAREN
+				LPAREN WHITE_SPACE?
 				( 
 					f3 = filter_expression {$c.add($f3.efi);}
 					| disjunction {$c.add($disjunction.d);}
@@ -357,7 +361,7 @@ disjunction returns [Disjunction d]
 	(
 		f1 = filter_expression {$d.add($f1.efi);}
 		|
-		LPAREN
+		LPAREN WHITE_SPACE?
 		(
 			f4 = filter_expression {$d.add($f4.efi);}
 			| conjunction {$d.add($conjunction.c);}
@@ -373,7 +377,7 @@ disjunction returns [Disjunction d]
 		(
 			f2 = filter_expression {$d.add($f2.efi);}
 			| (
-				LPAREN
+				LPAREN WHITE_SPACE?
 				( 
 					f3 = filter_expression {$d.add($f3.efi);}
 					| conjunction {$d.add($conjunction.c);}
@@ -393,7 +397,7 @@ negation returns [Negation n]
 	(
 		f1 = filter_expression {$n = new Negation($f1.efi);}
 		| ( 
-			LPAREN
+			LPAREN WHITE_SPACE?
 			(	
 				f2 = filter_expression {$n = new Negation($f2.efi);}
 				| disjunction {$n = new Negation($disjunction.d);}
@@ -408,9 +412,10 @@ entity returns [Query.Pattern ep]
 : 
 	regexp_pattern {$ep = $regexp_pattern.ep;}
 	| like_pattern {$ep = $like_pattern.ep;}
-	| ( double_quoted {$ep = $double_quoted.ep;} ) 
+	| ( double_quoted {$ep = $double_quoted.ep;} )
 	| ( single_quoted {$ep = $single_quoted.ep;} )
-	| (~(SINGLE_QUOTE_START|DOUBLE_QUOTE_START|WHITE_SPACE|WHICH|DOT|WHERE|HAS_A|WITH|STAR|AND|OR))+? {$ep = new Query.Pattern((String) $text, Query.Pattern.TYPE_NORMAL);}
+	| 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]
@@ -428,12 +433,10 @@ like_pattern returns [Query.Pattern ep] locals [StringBuffer sb]
 		$sb = new StringBuffer();
 	}
 :
-	
-	(m=~(WHITE_SPACE|WHICH|DOT|WHERE|HAS_A|WITH|STAR|LIKE|OPERATOR|AS_A|AND|OR|IS_STORED_AT|IS_REFERENCED) {$sb.append($m.text);})*?
-	(
-		STAR {$sb.append('*');}
-		(m=~(WHITE_SPACE|WHICH|DOT|WHERE|HAS_A|WITH|STAR|LIKE|OPERATOR|AS_A|AND|OR) {$sb.append($m.text);})*?
-	)+? {$ep = new Query.Pattern((String) $sb.toString(), 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]
@@ -446,10 +449,11 @@ property returns [Query.Pattern pp, String agg]locals [StringBuffer sb]
 	(
         regexp_pattern {$pp = $regexp_pattern.ep;}
         | like_pattern {$pp = $like_pattern.ep;}
-        | ( double_quoted {$pp = $double_quoted.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?
 ;
 
 minmax returns [String agg]
@@ -462,29 +466,37 @@ minmax returns [String agg]
 
 value returns [String str]
 : 
-	number {$str = $text;}
+	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)?
 ;
 
-number
+unit
 :
-	HYPHEN?? NUM* (DOT NUM+)?
+	TXT
+	| NUM SLASH TXT
 ;
 
 location returns [String str]
 :
     atom {$str = $atom.ep.str;}
     |
-	SLASHES ?
-	((WHICH | WITH)+ SLASHES |( TXT | COLON | HYPHEN | NUM | DOT | ESC_STAR | ESC_BS | ESC_REGEXP_END | STAR )+ SLASHES ?)* {$str = $text; }
+    (~WHITE_SPACE)+ {$str = $text; }
 ;
 
 atom returns [Query.Pattern ep]
 :
     double_quoted {$ep = $double_quoted.ep;}
 	| single_quoted {$ep = $single_quoted.ep;}
-	| (TXT | NUM | REGEXP_MARKER | STAR | A )+  {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);}
+	| (~(WHITE_SPACE | DOT ))+  {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);}
 ;
 
 single_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType]
diff --git a/src/main/java/org/caosdb/server/query/Conjunction.java b/src/main/java/org/caosdb/server/query/Conjunction.java
index 2e44aea4f64a43ebb2a1c1edb153c6f68c172f8e..03b331242e239a26f108973ca7a37624ab80ea64 100644
--- a/src/main/java/org/caosdb/server/query/Conjunction.java
+++ b/src/main/java/org/caosdb/server/query/Conjunction.java
@@ -34,7 +34,8 @@ import org.caosdb.server.database.access.Access;
 import org.caosdb.server.query.Query.QueryException;
 import org.jdom2.Element;
 
-public class Conjunction extends EntityFilterContainer implements QueryInterface {
+public class Conjunction extends EntityFilterContainer
+    implements QueryInterface, EntityFilterInterface {
 
   private String sourceSet = null;
   private String targetSet = null;
@@ -155,4 +156,15 @@ public class Conjunction extends EntityFilterContainer implements QueryInterface
   public boolean isVersioned() {
     return this.query.isVersioned();
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Conj(");
+    for (EntityFilterInterface filter : getFilters()) {
+      sb.append(filter.getCacheKey());
+    }
+    sb.append(")");
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/Disjunction.java b/src/main/java/org/caosdb/server/query/Disjunction.java
index 31288f2f169d4ebce1cf3d4da5b2ab369fcd5e1f..edd2e6daf243cd25ea112fdbfb946f2b80ee80b1 100644
--- a/src/main/java/org/caosdb/server/query/Disjunction.java
+++ b/src/main/java/org/caosdb/server/query/Disjunction.java
@@ -34,7 +34,8 @@ import org.caosdb.server.database.access.Access;
 import org.caosdb.server.query.Query.QueryException;
 import org.jdom2.Element;
 
-public class Disjunction extends EntityFilterContainer implements QueryInterface {
+public class Disjunction extends EntityFilterContainer
+    implements QueryInterface, EntityFilterInterface {
 
   private String targetSet = null;
   private int targetSetCount = -1;
@@ -143,4 +144,15 @@ public class Disjunction extends EntityFilterContainer implements QueryInterface
   public boolean isVersioned() {
     return this.query.isVersioned();
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Disj(");
+    for (EntityFilterInterface filter : getFilters()) {
+      sb.append(filter.getCacheKey());
+    }
+    sb.append(")");
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/EntityFilterContainer.java b/src/main/java/org/caosdb/server/query/EntityFilterContainer.java
index c2409f90ecc43cfb318a17cc8a9f3ba6df60beb0..1b270e9b120f2c336381db16c88769862b68a725 100644
--- a/src/main/java/org/caosdb/server/query/EntityFilterContainer.java
+++ b/src/main/java/org/caosdb/server/query/EntityFilterContainer.java
@@ -24,7 +24,7 @@ package org.caosdb.server.query;
 
 import java.util.LinkedList;
 
-public abstract class EntityFilterContainer implements EntityFilterInterface {
+public abstract class EntityFilterContainer {
   private final LinkedList<EntityFilterInterface> filters = new LinkedList<EntityFilterInterface>();
 
   public void add(final EntityFilterInterface filter) {
diff --git a/src/main/java/org/caosdb/server/query/EntityFilterInterface.java b/src/main/java/org/caosdb/server/query/EntityFilterInterface.java
index 3bf113edbfa6aeb3bbe1c4ead7c2bbca0aca0d03..be8484c2f122ae301ea78a014c771e116b0fc012 100644
--- a/src/main/java/org/caosdb/server/query/EntityFilterInterface.java
+++ b/src/main/java/org/caosdb/server/query/EntityFilterInterface.java
@@ -30,4 +30,12 @@ public interface EntityFilterInterface {
   void apply(QueryInterface query) throws QueryException;
 
   public Element toElement();
+
+  /**
+   * Return a string which can serve as a cache key. The string must describe all relevant
+   * parameters of the filter.
+   *
+   * @return a cache key.
+   */
+  public String getCacheKey();
 }
diff --git a/src/main/java/org/caosdb/server/query/IDFilter.java b/src/main/java/org/caosdb/server/query/IDFilter.java
index 12f53b44472d5b897c3d8b2a065388969c1edcbc..ce01f5f242f914c6f6f812f0875332f7ca004c07 100644
--- a/src/main/java/org/caosdb/server/query/IDFilter.java
+++ b/src/main/java/org/caosdb/server/query/IDFilter.java
@@ -125,4 +125,17 @@ public class IDFilter implements EntityFilterInterface {
   public String getAggregate() {
     return this.aggregate;
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("ID(");
+    if (this.aggregate != null) sb.append(this.aggregate);
+    sb.append(",");
+    if (this.operator != null) sb.append(this.operator);
+    sb.append(",");
+    if (this.value != null) sb.append(this.value);
+    sb.append(")");
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/Negation.java b/src/main/java/org/caosdb/server/query/Negation.java
index 981b4c8f2ca19a1a6ec47df9e85f108ab7bb01ac..cfdfd05e286030f525b8d4f3c291e2bdfb27b100 100644
--- a/src/main/java/org/caosdb/server/query/Negation.java
+++ b/src/main/java/org/caosdb/server/query/Negation.java
@@ -55,14 +55,6 @@ public class Negation implements EntityFilterInterface, QueryInterface {
       }
       return this.neg;
     }
-
-    @Override
-    public void apply(final QueryInterface query) {}
-
-    @Override
-    public Element toElement() {
-      return null;
-    }
   }
 
   private String targetSet = null;
@@ -175,4 +167,13 @@ public class Negation implements EntityFilterInterface, QueryInterface {
   public boolean isVersioned() {
     return this.query.isVersioned();
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("NEG(");
+    if (this.filter != null) sb.append(this.filter.getCacheKey());
+    sb.append(")");
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/POV.java b/src/main/java/org/caosdb/server/query/POV.java
index fcc719e6ff24299d0b5a62a241442e08fa33962f..a1008bd6a67a8712baf2c9b52ab164f619236263 100644
--- a/src/main/java/org/caosdb/server/query/POV.java
+++ b/src/main/java/org/caosdb/server/query/POV.java
@@ -512,4 +512,15 @@ public class POV implements EntityFilterInterface {
   private String measurement(String m) {
     return String.join("", prefix) + m;
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    if (this.getAggregate() != null) sb.append(this.aggregate);
+    sb.append(toString());
+    if (this.hasSubProperty()) {
+      sb.append(getSubProperty().getCacheKey());
+    }
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java
index 695a295da5f64ec788c4dfbb3332734ead44b289..0a877fc83e2f680565181655ed92c3a61cda393b 100644
--- a/src/main/java/org/caosdb/server/query/Query.java
+++ b/src/main/java/org/caosdb/server/query/Query.java
@@ -24,6 +24,7 @@ package org.caosdb.server.query;
 
 import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8;
 
+import java.io.Serializable;
 import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
@@ -40,9 +41,11 @@ import java.util.Map;
 import java.util.Map.Entry;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.commons.jcs.access.behavior.ICacheAccess;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.ServerProperties;
+import org.caosdb.server.caching.Cache;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLHelper;
@@ -63,6 +66,7 @@ import org.caosdb.server.query.CQLParser.CqContext;
 import org.caosdb.server.query.CQLParsingErrorListener.ParsingError;
 import org.caosdb.server.transaction.TransactionInterface;
 import org.jdom2.Element;
+import org.slf4j.Logger;
 
 public class Query implements QueryInterface, ToElementable, TransactionInterface {
 
@@ -177,6 +181,28 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
     public Integer id;
     public String version;
+
+    @Override
+    public String toString() {
+      if (version == null) {
+        return Integer.toString(id);
+      }
+      return Integer.toString(id) + "@" + version;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj instanceof IdVersionPair) {
+        IdVersionPair that = (IdVersionPair) obj;
+        return this.id == that.id && this.version == that.version;
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return toString().hashCode();
+    }
   }
 
   private static boolean filterEntitiesWithoutRetrievePermisions =
@@ -184,6 +210,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
               ServerProperties.KEY_QUERY_FILTER_ENTITIES_WITHOUT_RETRIEVE_PERMISSIONS)
           .equalsIgnoreCase("FALSE");
 
+  private Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
   List<IdVersionPair> resultSet = null;
   private final String query;
   private Pattern entity = null;
@@ -200,6 +227,16 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
   private final ArrayList<ToElementable> messages = new ArrayList<>();
   private Access access;
   private boolean versioned = false;
+  private static ICacheAccess<String, Serializable> cache =
+      Cache.getCache("HIGH_LEVEL_QUERY_CACHE");
+  /** Cached=true means that the results of this query have actually been pulled from the cache. */
+  private boolean cached = false;
+  /**
+   * Cachable=false means that the results of this query cannot be used for the cache, because the
+   * query evaluation contains complex permission checking which could only be cached on a per-user
+   * basis (maybe in the future).
+   */
+  private boolean cachable = true;
 
   public Type getType() {
     return this.type;
@@ -430,7 +467,13 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     }
   }
 
-  public void parse() throws ParsingException {
+  /**
+   * Parse the query and run optimize() if the parameter `optimize` is true.
+   *
+   * @param optimize whether to run optimize() immediately.
+   * @throws ParsingException
+   */
+  public void parse(boolean optimize) throws ParsingException {
     final long t1 = System.currentTimeMillis();
     CQLLexer lexer;
     lexer = new CQLLexer(CharStreams.fromString(this.query));
@@ -456,29 +499,96 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     if (t2 - t1 > 1000) {
       addBenchmark("LONG_PARSING: " + this.query, t2 - t1);
     }
+
+    if (optimize) {
+      optimize();
+    }
+  }
+
+  /**
+   * Parse the query and run optimize() immediately.
+   *
+   * @throws ParsingException
+   */
+  public void parse() throws ParsingException {
+    parse(true);
+  }
+
+  /**
+   * Optimize the query after parsing. The optimization is purely based on formal rules.
+   *
+   * <p>Implemented rules:
+   *
+   * <ol>
+   *   <li>FIND * -> FIND ENTITY (which basically prevents to copy the complete entity table just to
+   *       read out the ids immediately).
+   * </ol>
+   */
+  public void optimize() {
+    // basic optimization
+    if (this.entity != null
+        && this.entity.type == Pattern.TYPE_LIKE
+        && this.entity.str.equals("*")) {
+      this.entity = null;
+      if (this.role == null) {
+        this.role = Role.ENTITY;
+      }
+    }
   }
 
   private String executeStrategy(boolean versioned) throws QueryException {
     if (this.entity != null) {
       return sourceStrategy(initQuery(versioned));
+    } else if (this.role == Role.ENTITY && this.filter == null) {
+      return "entities";
     } else {
       return targetStrategy(initQuery(versioned));
     }
   }
 
+  /**
+   * Generate a SQL statement which reads out the resulting ids (and version ids if `versioned` is
+   * true).
+   *
+   * <p>If the parameter `resultSetTableName` is "entities" actually the entity_version table is
+   * used to fetch all ids.
+   *
+   * @param resultSetTableName name of the table with all the resulting entities
+   * @param versioned whether the query was versioned
+   * @return an SQL statement
+   * @throws QueryException
+   */
+  private String generateSelectStatementForResultSet(
+      final String resultSetTableName, boolean versioned) {
+    if (resultSetTableName.equals("entities")) {
+      return "SELECT entity_id AS id"
+          + (versioned ? ", version AS version" : "")
+          + " FROM entity_version"
+          + (versioned ? "" : " WHERE `_iversion` = 1");
+    }
+    return "SELECT results.id AS id"
+        + (versioned ? ", ev.version AS version" : "")
+        + " FROM `"
+        + resultSetTableName
+        + "` AS results"
+        + (versioned
+            ? " JOIN entity_version AS ev ON (results.id = ev.entity_id AND results._iversion = ev._iversion)"
+            : "");
+  }
+
+  /**
+   * Return a list of all resulting entities (versions of entities if `versioned` is true).
+   *
+   * @param resultSetTableName name of the table with all the resulting entities
+   * @param versioned whether the query was versioned
+   * @return list of results of this query.
+   * @throws QueryException
+   */
   private List<IdVersionPair> getResultSet(final String resultSetTableName, boolean versioned)
       throws QueryException {
     ResultSet finishResultSet = null;
     try {
-      final String sql =
-          "Select results.id AS id"
-              + (versioned ? ", ev.version AS version" : "")
-              + " from `"
-              + resultSetTableName
-              + "` AS results"
-              + (versioned
-                  ? " JOIN entity_version AS ev ON (results.id = ev.entity_id AND results._iversion = ev._iversion)"
-                  : "");
+      final String sql = generateSelectStatementForResultSet(resultSetTableName, versioned);
       final PreparedStatement finish = getConnection().prepareStatement(sql);
       finishResultSet = finish.executeQuery();
       final List<IdVersionPair> rs = new LinkedList<>();
@@ -500,31 +610,95 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     }
   }
 
+  /**
+   * Whether the transaction allows this query instance to use the query cache. This is controlled
+   * by the "cache" flag.
+   *
+   * @see {@link NoCache}
+   * @return true if caching is encouraged.
+   */
+  private boolean useCache() {
+    return getAccess().useCache();
+  }
+
+  /**
+   * Execute the query.
+   *
+   * <p>First try the cache and only then use the back-end.
+   *
+   * @param access
+   * @return
+   * @throws ParsingException
+   */
   public Query execute(final Access access) throws ParsingException {
-    setAccess(access);
     parse();
+    setAccess(access);
+    if (useCache()) {
+      this.resultSet = getCached(getCacheKey());
+    }
 
-    try {
-
-      this.resultSet = getResultSet(executeStrategy(this.versioned), this.versioned);
+    if (this.resultSet == null) {
+      executeNoCache(access);
+      if (this.cachable) {
+        setCache(getCacheKey(), this.resultSet);
+      }
+      this.logger.debug("Uncached query {}", this.query);
+    } else {
+      this.logger.debug("Using cached result for {}", this.query);
+      this.cached = true;
+    }
 
-      filterEntitiesWithoutRetrievePermission(this.resultSet);
+    this.resultSet = filterEntitiesWithoutRetrievePermission(this.resultSet);
 
-      // Fill resulting entities into container
-      if (this.container != null && this.type == Type.FIND) {
-        for (final IdVersionPair p : this.resultSet) {
+    // Fill resulting entities into container
+    if (this.container != null && this.type == Type.FIND) {
+      for (final IdVersionPair p : this.resultSet) {
 
-          final Entity e = new RetrieveEntity(p.id, p.version);
+        final Entity e = new RetrieveEntity(p.id, p.version);
 
-          // if query has select-clause:
-          if (this.selections != null && !this.selections.isEmpty()) {
-            e.addSelections(this.selections);
-          }
-          this.container.add(e);
+        // if query has select-clause:
+        if (this.selections != null && !this.selections.isEmpty()) {
+          e.addSelections(this.selections);
         }
+        this.container.add(e);
       }
-      return this;
+    }
+    return this;
+  }
+
+  /** Remove all cached queries from the cache. */
+  public static void clearCache() {
+    cache.clear();
+  }
+
+  /**
+   * Cache a query result.
+   *
+   * @param key
+   * @param resultSet
+   */
+  private void setCache(String key, List<IdVersionPair> resultSet) {
+    if (resultSet instanceof Serializable) {
+      cache.put(key, (Serializable) resultSet);
+    } else {
+      cache.put(key, new ArrayList<>(resultSet));
+    }
+  }
 
+  /**
+   * Retrieve a result set of entity ids (and the version) from the cache.
+   *
+   * @param key
+   * @return
+   */
+  @SuppressWarnings("unchecked")
+  private List<IdVersionPair> getCached(String key) {
+    return (List<IdVersionPair>) cache.get(key);
+  }
+
+  protected void executeNoCache(Access access) {
+    try {
+      this.resultSet = getResultSet(executeStrategy(this.versioned), this.versioned);
     } finally {
       cleanUp();
     }
@@ -564,31 +738,31 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    * Filter out all entities which may not be retrieved by this user due to a missing RETRIEVE
    * permission. This one is also designed for filtering of intermediate results.
    *
-   * @param query
    * @param resultSet
    * @throws SQLException
    * @throws TransactionException
    */
-  public void filterEntitiesWithoutRetrievePermission(
-      final QueryInterface query, final String resultSet)
+  public void filterEntitiesWithoutRetrievePermission(final String resultSet)
       throws SQLException, TransactionException {
     if (!filterEntitiesWithoutRetrievePermisions) {
       return;
     }
-    try (final Statement stmt = query.getConnection().createStatement()) {
+    cachable = false;
+    try (final Statement stmt = this.getConnection().createStatement()) {
       final ResultSet rs = stmt.executeQuery("SELECT id from `" + resultSet + "`");
       final List<Integer> toBeDeleted = new LinkedList<Integer>();
       while (rs.next()) {
         final long t1 = System.currentTimeMillis();
         final Integer id = rs.getInt("id");
-        if (!execute(new RetrieveSparseEntity(id, null), query.getAccess())
-            .getEntity()
-            .getEntityACL()
-            .isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
+        if (id > 99
+            && !execute(new RetrieveSparseEntity(id, null), this.getAccess())
+                .getEntity()
+                .getEntityACL()
+                .isPermitted(this.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
           toBeDeleted.add(id);
         }
         final long t2 = System.currentTimeMillis();
-        query.addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
+        this.addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
       }
       rs.close();
       for (final Integer id : toBeDeleted) {
@@ -604,25 +778,30 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    *
    * @param entities
    * @throws TransactionException
+   * @return the filtered list.
    */
-  private void filterEntitiesWithoutRetrievePermission(final List<IdVersionPair> entities)
-      throws TransactionException {
+  private List<IdVersionPair> filterEntitiesWithoutRetrievePermission(
+      final List<IdVersionPair> entities) throws TransactionException {
     if (!filterEntitiesWithoutRetrievePermisions) {
-      return;
+      return entities;
     }
+
+    List<IdVersionPair> result = new ArrayList<>();
     final Iterator<IdVersionPair> iterator = entities.iterator();
     while (iterator.hasNext()) {
       final long t1 = System.currentTimeMillis();
       final IdVersionPair next = iterator.next();
-      if (!execute(new RetrieveSparseEntity(next.id, next.version), getAccess())
-          .getEntity()
-          .getEntityACL()
-          .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) {
-        iterator.remove();
+      if (next.id > 99
+          && execute(new RetrieveSparseEntity(next.id, next.version), getAccess())
+              .getEntity()
+              .getEntityACL()
+              .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) {
+        result.add(next);
       }
       final long t2 = System.currentTimeMillis();
       addBenchmark("filterEntitiesWithoutRetrievePermission", t2 - t1);
     }
+    return result;
   }
 
   @Override
@@ -679,6 +858,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     } else {
       ret.setAttribute("results", "0");
     }
+    ret.setAttribute("cached", Boolean.toString(this.cached));
 
     final Element parseTreeElem = new Element("ParseTree");
     if (this.el.hasErrors()) {
@@ -769,4 +949,27 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
   public boolean isVersioned() {
     return this.versioned;
   }
+
+  /**
+   * Return a key for the query cache. The key should describe the query with all the filters but
+   * without the FIND, COUNT and SELECT ... FROM parts.
+   *
+   * @return A Cache key.
+   */
+  String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    if (this.versioned) sb.append("versioned");
+    if (this.role != null) sb.append(this.role.toString());
+    if (this.entity != null) sb.append(this.entity.toString());
+    if (this.filter != null) sb.append(this.filter.getCacheKey());
+    return sb.toString();
+  }
+
+  public Pattern getEntity() {
+    return this.entity;
+  }
+
+  public Role getRole() {
+    return this.role;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/RoleFilter.java b/src/main/java/org/caosdb/server/query/RoleFilter.java
index 48c8372014c6e702be8b88113102da030dbc6046..97d1b1eab3bcc9e21adab4fa5e2a5204411e58fb 100644
--- a/src/main/java/org/caosdb/server/query/RoleFilter.java
+++ b/src/main/java/org/caosdb/server/query/RoleFilter.java
@@ -148,4 +148,10 @@ public class RoleFilter implements EntityFilterInterface {
   private String getOperator() {
     return this.operator;
   }
+
+  @Override
+  public String getCacheKey() {
+    // unused
+    return null;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/StoredAt.java b/src/main/java/org/caosdb/server/query/StoredAt.java
index 84a12d2c6b5b7ca3c3b0644eef4ea72a200cc328..90adc188b61e375a30721b605cb91b93fe76a88b 100644
--- a/src/main/java/org/caosdb/server/query/StoredAt.java
+++ b/src/main/java/org/caosdb/server/query/StoredAt.java
@@ -201,4 +201,9 @@ public class StoredAt implements EntityFilterInterface {
   public String toString() {
     return "SAT(" + (this.pattern_matching ? this.likeLocation : this.location) + ")";
   }
+
+  @Override
+  public String getCacheKey() {
+    return toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/SubProperty.java b/src/main/java/org/caosdb/server/query/SubProperty.java
index 4668935e56f0057d3d0fe927aa0a729669e7e0f6..f3be825ccf28b7df2ba646306a61f006664ae5a6 100644
--- a/src/main/java/org/caosdb/server/query/SubProperty.java
+++ b/src/main/java/org/caosdb/server/query/SubProperty.java
@@ -87,7 +87,7 @@ public class SubProperty implements QueryInterface, EntityFilterInterface {
 
         this.filter.apply(this);
 
-        getQuery().filterEntitiesWithoutRetrievePermission(this, this.sourceSet);
+        getQuery().filterEntitiesWithoutRetrievePermission(this.sourceSet);
 
         final CallableStatement callFinishSubProperty =
             getConnection().prepareCall("call finishSubProperty(?,?,?,?)");
@@ -167,4 +167,12 @@ public class SubProperty implements QueryInterface, EntityFilterInterface {
   public boolean isVersioned() {
     return this.query.isVersioned();
   }
+
+  @Override
+  public String getCacheKey() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("SUB(");
+    if (this.filter != null) sb.append(this.filter.getCacheKey());
+    return sb.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/query/TransactionFilter.java b/src/main/java/org/caosdb/server/query/TransactionFilter.java
index 55176594f5d17fdb726b82f37e5d3eb83944e611..fed4a7156048f1bd43f7e4000df80f29a38187a0 100644
--- a/src/main/java/org/caosdb/server/query/TransactionFilter.java
+++ b/src/main/java/org/caosdb/server/query/TransactionFilter.java
@@ -256,4 +256,9 @@ public class TransactionFilter implements EntityFilterInterface {
         + this.transactor
         + ")";
   }
+
+  @Override
+  public String getCacheKey() {
+    return toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
index c7ecd4deb76117474adc188c3f8a7d0f6064a3b8..876b43a160f673b96a7220a746c9a7dcd4951388 100644
--- a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
+++ b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
@@ -117,7 +117,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
       getRequest().setEntity(r);
     }
 
-    this.utils = WebinterfaceUtils.getInstance(getHostRef());
+    this.utils = WebinterfaceUtils.getInstance(getRequest());
 
     this.timestamp = getRequest().getDate().getTime();
 
@@ -177,7 +177,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
       retRoot.setAttribute("crid", this.getCRID());
     }
     retRoot.setAttribute("timestamp", getTimestamp().toString());
-    retRoot.setAttribute("baseuri", getRootRef().toString());
+    retRoot.setAttribute("baseuri", getUtils().getServerRootURI());
     return retRoot;
   }
 
diff --git a/src/main/java/org/caosdb/server/resource/EntityOwnerResource.java b/src/main/java/org/caosdb/server/resource/EntityOwnerResource.java
index f20a4ab042bd996ef7e7d004e9246935cf034b64..41f92a5631d9cabb04ee5498373848b516885424 100644
--- a/src/main/java/org/caosdb/server/resource/EntityOwnerResource.java
+++ b/src/main/java/org/caosdb/server/resource/EntityOwnerResource.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 Timm Fitschen <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
@@ -22,20 +24,26 @@
  */
 package org.caosdb.server.resource;
 
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-import org.caosdb.server.resource.transaction.handlers.RequestHandler;
-import org.caosdb.server.resource.transaction.handlers.RetriveOwnerRequestHandler;
+import org.caosdb.server.permissions.EntityPermission;
+import org.caosdb.server.resource.transaction.RetrieveEntityResource;
 
-public class EntityOwnerResource extends EntityResource {
-
-  public EntityOwnerResource() {
-    // only retrieval is allowed
-    super(true, false, false, false);
-  }
+/**
+ * Resource which returns the entity with the owner information attached to it.
+ *
+ * <p>Note: 'Owners' are all roles with the {@link EntityPermission#EDIT_ACL}
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class EntityOwnerResource extends RetrieveEntityResource {
 
   @Override
-  protected RequestHandler<RetrieveContainer> getGetRequestHandler() {
-    return new RetriveOwnerRequestHandler();
+  protected void handleRetrieveContainer(RetrieveContainer container) {
+    super.handleRetrieveContainer(container);
+    for (final EntityInterface e : container) {
+      // with this flag, the owner info will be loaded as well.
+      e.setFlag("owner", null);
+    }
   }
 }
diff --git a/src/main/java/org/caosdb/server/resource/EntityPermissionsResource.java b/src/main/java/org/caosdb/server/resource/EntityPermissionsResource.java
index ffce6fe18378ec2586276cd69124cfaeaf8f29fc..1f5ee5f7d8a28a235de02bbc33f826da32af0615 100644
--- a/src/main/java/org/caosdb/server/resource/EntityPermissionsResource.java
+++ b/src/main/java/org/caosdb/server/resource/EntityPermissionsResource.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 Timm Fitschen <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
@@ -23,19 +25,20 @@
 package org.caosdb.server.resource;
 
 import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-import org.caosdb.server.resource.transaction.handlers.RequestHandler;
-import org.caosdb.server.resource.transaction.handlers.RetrieveGlobalEntityPermissionsHandler;
+import org.caosdb.server.permissions.EntityPermission;
+import org.caosdb.server.resource.transaction.RetrieveEntityResource;
 
-public class EntityPermissionsResource extends EntityResource {
-
-  public EntityPermissionsResource() {
-    // only retrieval is allowed
-    super(true, false, false, false);
-  }
+/**
+ * Resource which appends the entity permissions (esp. the global ones) to the normal entity
+ * response.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class EntityPermissionsResource extends RetrieveEntityResource {
 
   @Override
-  protected RequestHandler<RetrieveContainer> getGetRequestHandler() {
-    return new RetrieveGlobalEntityPermissionsHandler();
+  protected void handleRetrieveContainer(RetrieveContainer container) {
+    super.handleRetrieveContainer(container);
+    container.addMessage(EntityPermission.getAllEntityPermissions());
   }
 }
diff --git a/src/main/java/org/caosdb/server/resource/UserResource.java b/src/main/java/org/caosdb/server/resource/UserResource.java
index 8ac0df1d8f8f018a07251e35a118ae944224dcc3..2c463e0e4bf7a640adc2a8368f74d798f69d0fd5 100644
--- a/src/main/java/org/caosdb/server/resource/UserResource.java
+++ b/src/main/java/org/caosdb/server/resource/UserResource.java
@@ -99,13 +99,11 @@ public class UserResource extends AbstractCaosDBServerResource {
               : UserSources.guessRealm(username));
       final String password = form.getFirstValue("password");
       final String email = form.getFirstValue("email");
+
       final UserStatus status =
-          UserStatus.valueOf(
-              form.getFirstValue(
-                      "status",
-                      CaosDBServer.getServerProperty(
-                          ServerProperties.KEY_NEW_USER_DEFAULT_ACTIVITY))
-                  .toUpperCase());
+          form.getFirstValue("status") != null
+              ? UserStatus.valueOf(form.getFirstValue("status").toUpperCase())
+              : null;
       Integer userEntity = null;
       if (form.getFirst("entity") != null) {
         if (form.getFirstValue("entity").isEmpty()) {
diff --git a/src/main/java/org/caosdb/server/resource/Webinterface.java b/src/main/java/org/caosdb/server/resource/Webinterface.java
index bba1fb09c7f2c0a14b95b7314832b6ff174cef6b..adba714a96e5bbb4bc3ea13b8d72ec69a88cb33b 100644
--- a/src/main/java/org/caosdb/server/resource/Webinterface.java
+++ b/src/main/java/org/caosdb/server/resource/Webinterface.java
@@ -47,7 +47,7 @@ public class Webinterface extends ServerResource {
 
   @Override
   protected void doInit() throws ResourceException {
-    this.utils = WebinterfaceUtils.getInstance(getHostRef());
+    this.utils = WebinterfaceUtils.getInstance(getRequest());
     super.doInit();
   }
 
diff --git a/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java b/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java
index 0fe186a0426002071e0b6c6a5ebe0b3f234e9b2a..22f1a46e4fd314b0dcaebde7abdcc1d41a9f50f0 100644
--- a/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java
+++ b/src/main/java/org/caosdb/server/resource/WebinterfaceBuildNumber.java
@@ -39,7 +39,7 @@ public class WebinterfaceBuildNumber extends ServerResource {
   @Override
   protected void doInit() throws ResourceException {
     super.doInit();
-    this.utils = WebinterfaceUtils.getInstance(getHostRef());
+    this.utils = WebinterfaceUtils.getInstance(getRequest());
   }
 
   /**
diff --git a/src/main/java/org/caosdb/server/resource/transaction/EntityNamesResource.java b/src/main/java/org/caosdb/server/resource/transaction/EntityNamesResource.java
index 0b3f8b7aa4f4a7724b9b6646e7ecb72330293980..0c66485ea3d62286d633d9580c1362ba33435095 100644
--- a/src/main/java/org/caosdb/server/resource/transaction/EntityNamesResource.java
+++ b/src/main/java/org/caosdb/server/resource/transaction/EntityNamesResource.java
@@ -1,18 +1,41 @@
+/*
+ * ** 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 Timm Fitschen <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/>.
+ *
+ * ** end header
+ */
 package org.caosdb.server.resource.transaction;
 
 import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.resource.transaction.handlers.GetNamesRequestHandler;
-import org.caosdb.server.resource.transaction.handlers.RequestHandler;
 
-public class EntityNamesResource extends EntityResource {
-
-  public EntityNamesResource() {
-    // only get is allowed
-    super(true, false, false, false);
-  }
+/**
+ * Resource which returns only the names of entities.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class EntityNamesResource extends RetrieveEntityResource {
 
   @Override
-  protected RequestHandler<RetrieveContainer> getGetRequestHandler() {
-    return new GetNamesRequestHandler();
+  protected void handleRetrieveContainer(RetrieveContainer container) {
+    super.handleRetrieveContainer(container);
+    container.getFlags().put("names", null);
   }
 }
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 12659362e0a9a82e8cbb95e73e4d6ac6c2d27c6c..704fcfb9aa92e56ad963365d763f70fa7feb470e 100644
--- a/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java
+++ b/src/main/java/org/caosdb/server/resource/transaction/EntityResource.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 Timm Fitschen <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
@@ -24,182 +26,167 @@ package org.caosdb.server.resource.transaction;
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
-import java.sql.SQLException;
+import java.util.List;
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
 import org.caosdb.server.CaosDBException;
+import org.caosdb.server.FileSystem;
 import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException;
-import org.caosdb.server.entity.container.DeleteContainer;
-import org.caosdb.server.entity.container.InsertContainer;
-import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.entity.container.UpdateContainer;
-import org.caosdb.server.resource.AbstractCaosDBServerResource;
-import org.caosdb.server.resource.transaction.handlers.FileUploadHandler;
-import org.caosdb.server.resource.transaction.handlers.RequestHandler;
-import org.caosdb.server.resource.transaction.handlers.SimpleDeleteRequestHandler;
-import org.caosdb.server.resource.transaction.handlers.SimpleGetRequestHandler;
-import org.caosdb.server.resource.transaction.handlers.SimpleWriteHandler;
-import org.caosdb.server.transaction.Delete;
-import org.caosdb.server.transaction.Insert;
-import org.caosdb.server.transaction.Retrieve;
-import org.caosdb.server.transaction.Update;
+import org.caosdb.server.entity.DeleteEntity;
+import org.caosdb.server.entity.FileProperties;
+import org.caosdb.server.entity.InsertEntity;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.UpdateEntity;
+import org.caosdb.server.entity.container.WritableContainer;
+import org.caosdb.server.transaction.WriteTransaction;
+import org.caosdb.server.transaction.WriteTransactionInterface;
+import org.caosdb.server.utils.ServerMessages;
 import org.jdom2.Document;
 import org.jdom2.Element;
 import org.jdom2.JDOMException;
 import org.restlet.data.MediaType;
-import org.restlet.data.Status;
+import org.restlet.ext.fileupload.RestletFileUpload;
 import org.restlet.representation.Representation;
 
-public class EntityResource extends AbstractCaosDBServerResource {
-
-  private final boolean get;
-  private final boolean post;
-  private final boolean put;
-  private final boolean delete;
-
-  public EntityResource(
-      final boolean get, final boolean post, final boolean put, final boolean delete) {
-    this.get = get;
-    this.post = post;
-    this.put = put;
-    this.delete = delete;
-  }
-
-  public EntityResource() {
-    this(true, true, true, true);
-  }
-
-  protected RequestHandler<RetrieveContainer> getGetRequestHandler() {
-    return new SimpleGetRequestHandler();
-  }
-
-  protected RequestHandler<DeleteContainer> getDeleteRequestHandler() {
-    return new SimpleDeleteRequestHandler();
-  }
-
-  protected RequestHandler<InsertContainer> getPostRequestHandler() {
-    if (getRequest().getEntity().getMediaType() != null
-        && getRequest().getEntity().getMediaType().equals(MediaType.MULTIPART_FORM_DATA, true)) {
-      return new FileUploadHandler<InsertContainer>();
-    } else {
-      return new SimpleWriteHandler<InsertContainer>();
-    }
-  }
-
-  protected RequestHandler<UpdateContainer> getPutRequestHandler() {
-    if (getRequest().getEntity().getMediaType() != null
-        && getRequest().getEntity().getMediaType().equals(MediaType.MULTIPART_FORM_DATA, true)) {
-      return new FileUploadHandler<UpdateContainer>();
-    } else {
-      return new SimpleWriteHandler<UpdateContainer>();
-    }
-  }
-
-  @Override
-  protected final Representation httpGetInChildClass()
-      throws ConnectionException, IOException, SQLException, CaosDBException,
-          NoSuchAlgorithmException, Exception {
-
-    final long t1 = System.currentTimeMillis();
-    if (!this.get) {
-      getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
-      return null;
-    }
-    final RetrieveContainer entityContainer =
-        new RetrieveContainer(getUser(), getTimestamp(), getSRID(), getFlags());
-    final Document doc = new Document();
-
-    getGetRequestHandler().handle(this, entityContainer);
-
-    final long t2 = System.currentTimeMillis();
-    entityContainer
-        .getTransactionBenchmark()
-        .addMeasurement(getClass().getSimpleName() + ".httpGetInChildClass#handle", t2 - t1);
-
-    final Retrieve retrieve = new Retrieve(entityContainer);
-    retrieve.execute();
-
-    final long t3 = System.currentTimeMillis();
-    entityContainer
-        .getTransactionBenchmark()
-        .addMeasurement(
-            getClass().getSimpleName() + ".httpGetInChildClass#retrieve.execute", t3 - t2);
-
-    final Element rootElem = generateRootElement();
-    entityContainer.addToElement(rootElem);
-    doc.setRootElement(rootElem);
-
-    final long t4 = System.currentTimeMillis();
-    entityContainer
-        .getTransactionBenchmark()
-        .addMeasurement(
-            getClass().getSimpleName() + ".httpGetInChildClass#element_handling", t4 - t3);
-
-    return ok(doc);
-  }
+/**
+ * Handles POST, PUT, and DELETE requests for Entities.
+ *
+ * <p>The GET requests (Retrieval) is handled in the superclass {@link RetrieveEntityResource}.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class EntityResource extends RetrieveEntityResource {
 
+  /** Handle entity deletions (DELETE requests). */
   @Override
   protected final Representation httpDeleteInChildClass() throws Exception {
 
-    if (!this.delete) {
-      getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
-      return null;
-    }
-    final DeleteContainer entityContainer =
-        new DeleteContainer(getUser(), getTimestamp(), getSRID(), getFlags());
+    final WritableContainer container =
+        new WritableContainer(getUser(), getTimestamp(), getSRID(), getFlags());
     final Document doc = new Document();
 
-    getDeleteRequestHandler().handle(this, entityContainer);
+    for (final String item : getRequestedItems()) {
+      String[] elem = item.split("@", 1);
+      Integer id = null;
+      String version = null;
+      try {
+        id = Integer.parseInt(elem[0]);
+      } catch (NumberFormatException e) {
+        // pass
+      }
+      if (elem.length > 1) {
+        version = elem[1];
+      }
+
+      if (id != null) {
+        container.add(new DeleteEntity(id, version));
+      }
+    }
 
-    final Delete delete = new Delete(entityContainer);
+    final WriteTransactionInterface delete = new WriteTransaction(container);
     delete.execute();
 
     final Element rootElem = generateRootElement();
-    entityContainer.addToElement(rootElem);
+    container.addToElement(rootElem);
     doc.setRootElement(rootElem);
 
     return ok(doc);
   }
 
+  /** Handle entity insertions (POST requests). */
   @Override
   protected final Representation httpPostInChildClass(final Representation entity)
       throws xmlNotWellFormedException, Exception {
 
-    if (!this.post) {
-      getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
-      return null;
-    }
+    final WritableContainer entityContainer =
+        new WritableContainer(getUser(), getTimestamp(), getSRID(), getFlags());
 
-    final InsertContainer entityContainer =
-        new InsertContainer(getUser(), getTimestamp(), getSRID(), getFlags());
-    final Document doc = new Document();
+    List<Element> insertEntities;
+    if (entity.getMediaType() != null
+        && entity.getMediaType().equals(MediaType.MULTIPART_FORM_DATA, true)) {
+      insertEntities = parseMultipartEntity(entityContainer).getChildren();
+    } else {
+      insertEntities = parseEntity(entity).getRootElement().getChildren();
+    }
 
-    getPostRequestHandler().handle(this, entityContainer);
+    for (final Element e : insertEntities) {
+      entityContainer.add(new InsertEntity(e));
+    }
 
-    final Insert insert = new Insert(entityContainer);
+    final WriteTransactionInterface insert = new WriteTransaction(entityContainer);
     insert.execute();
 
     final Element rootElem = generateRootElement();
     entityContainer.addToElement(rootElem);
+    final Document doc = new Document();
     doc.setRootElement(rootElem);
 
     return ok(doc);
   }
 
+  /**
+   * Parse the body of requests with content type "multipart/form-data". This is specific for
+   * requests which also contain file blobs.
+   */
+  private Element parseMultipartEntity(WritableContainer container)
+      throws FileUploadException, IOException, Message, xmlNotWellFormedException,
+          NoSuchAlgorithmException, CaosDBException {
+    final DiskFileItemFactory factory = new DiskFileItemFactory();
+    factory.setSizeThreshold(1000240);
+    final RestletFileUpload upload = new RestletFileUpload(factory);
+    final FileItemIterator iter = upload.getItemIterator(getRequest().getEntity());
+
+    FileItemStream item = iter.next();
+
+    final Element element;
+    // First part of the multi-part form data entity must be the xml
+    // representation of the files.
+    // Name of the form field: FileRepresentation
+    if (item.isFormField()) {
+      if (item.getFieldName().equals("FileRepresentation")) {
+        element = parseEntity(item.openStream()).getRootElement();
+      } else {
+        throw ServerMessages.NO_FILE_REPRESENTATION_SUBMITTED;
+      }
+    } else {
+      throw ServerMessages.FORM_CONTAINS_UNSUPPORTED_CONTENT;
+    }
+
+    // Get files, store to tmp dir.
+    while (iter.hasNext()) {
+      item = iter.next();
+      final FileProperties file = FileSystem.upload(item, getSRID());
+      container.addFile(item.getName(), file);
+    }
+
+    // transform xml elements to entities and add them to the container.
+    return element;
+  }
+
+  /** Handle entity updates (PUT requests). */
   @Override
   protected final Representation httpPutInChildClass(final Representation entity)
       throws ConnectionException, JDOMException, Exception, xmlNotWellFormedException {
 
-    if (!this.put) {
-      getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
-      return null;
-    }
-
-    final UpdateContainer entityContainer =
-        new UpdateContainer(getUser(), getTimestamp(), getSRID(), getFlags());
+    final WritableContainer entityContainer =
+        new WritableContainer(getUser(), getTimestamp(), getSRID(), getFlags());
     final Document doc = new Document();
 
-    getPutRequestHandler().handle(this, entityContainer);
+    List<Element> updateEntities;
+    if (getRequest().getEntity().getMediaType() != null
+        && getRequest().getEntity().getMediaType().equals(MediaType.MULTIPART_FORM_DATA, true)) {
+      updateEntities = parseMultipartEntity(entityContainer).getChildren();
+    } else {
+      updateEntities = parseEntity(getRequest().getEntity()).getRootElement().getChildren();
+    }
+
+    for (final Element e : updateEntities) {
+      entityContainer.add(new UpdateEntity(e));
+    }
 
-    final Update update = new Update(entityContainer);
+    final WriteTransactionInterface update = new WriteTransaction(entityContainer);
     update.execute();
 
     final Element rootElem = generateRootElement();
diff --git a/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java b/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..5eb9db72f69f8f054dbb6f02b970d09e09ff366c
--- /dev/null
+++ b/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java
@@ -0,0 +1,110 @@
+/*
+ * ** 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 Timm Fitschen <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/>.
+ *
+ * ** end header
+ */
+package org.caosdb.server.resource.transaction;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.sql.SQLException;
+import org.caosdb.server.CaosDBException;
+import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException;
+import org.caosdb.server.entity.container.RetrieveContainer;
+import org.caosdb.server.resource.AbstractCaosDBServerResource;
+import org.caosdb.server.transaction.Retrieve;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.restlet.representation.Representation;
+
+/**
+ * Handles GET requests for different subclasses which all have in common that they retrieve
+ * Entities (plus other information in some cases).
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public abstract class RetrieveEntityResource extends AbstractCaosDBServerResource {
+
+  /**
+   * Parse the segment which specifies the entities which are to be retrieved
+   *
+   * @param container
+   */
+  protected void handleRetrieveContainer(RetrieveContainer container) {
+
+    for (final String item : getRequestedItems()) {
+      String[] elem = item.split("@", 2);
+      Integer id = null;
+      String name = null;
+      String version = null;
+      try {
+        id = Integer.parseInt(elem[0]);
+      } catch (NumberFormatException e) {
+        name = elem[0];
+      }
+      if (elem.length > 1) {
+        version = elem[1];
+      }
+
+      if (id != null) {
+        container.add(id, version);
+      } else {
+        container.add(name);
+      }
+    }
+  }
+
+  /** Handle the GET request. */
+  @Override
+  protected final Representation httpGetInChildClass()
+      throws ConnectionException, IOException, SQLException, CaosDBException,
+          NoSuchAlgorithmException, Exception {
+
+    final RetrieveContainer entityContainer =
+        new RetrieveContainer(getUser(), getTimestamp(), getSRID(), getFlags());
+
+    handleRetrieveContainer(entityContainer);
+
+    final long t2 = System.currentTimeMillis();
+    final Retrieve retrieve = new Retrieve(entityContainer);
+    retrieve.execute();
+
+    final long t3 = System.currentTimeMillis();
+    entityContainer
+        .getTransactionBenchmark()
+        .addMeasurement(
+            getClass().getSimpleName() + ".httpGetInChildClass#retrieve.execute", t3 - t2);
+
+    final Element rootElem = generateRootElement();
+    entityContainer.addToElement(rootElem);
+    final Document doc = new Document();
+    doc.setRootElement(rootElem);
+
+    final long t4 = System.currentTimeMillis();
+    entityContainer
+        .getTransactionBenchmark()
+        .addMeasurement(
+            getClass().getSimpleName() + ".httpGetInChildClass#element_handling", t4 - t3);
+
+    return ok(doc);
+  }
+}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/FileUploadHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/FileUploadHandler.java
deleted file mode 100644
index ec5c5bc4d144af9ce99f7b27915a52420c331bb5..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/FileUploadHandler.java
+++ /dev/null
@@ -1,81 +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.resource.transaction.handlers;
-
-import java.io.IOException;
-import org.apache.commons.fileupload.FileItemIterator;
-import org.apache.commons.fileupload.FileItemStream;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.caosdb.server.FileSystem;
-import org.caosdb.server.entity.FileProperties;
-import org.caosdb.server.entity.container.WritableContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-import org.caosdb.server.utils.ServerMessages;
-import org.jdom2.Element;
-import org.restlet.data.MediaType;
-import org.restlet.ext.fileupload.RestletFileUpload;
-
-public class FileUploadHandler<T extends WritableContainer> extends SimpleWriteHandler<T> {
-
-  @Override
-  public void handle(final EntityResource t, final T container) throws Exception {
-    if (!t.getRequest().getEntity().getMediaType().equals(MediaType.MULTIPART_FORM_DATA, true)) {
-      throw new Exception("Wrong MEDIATYPE");
-    }
-    final DiskFileItemFactory factory = new DiskFileItemFactory();
-    factory.setSizeThreshold(1000240);
-    final RestletFileUpload upload = new RestletFileUpload(factory);
-    final FileItemIterator iter = upload.getItemIterator(t.getRequest().getEntity());
-
-    FileItemStream item = iter.next();
-
-    final Element element;
-    // First part of the multi-part form data entity must be the xml
-    // representation of the files.
-    // Name of the form field: FileRepresentation
-    if (item.isFormField()) {
-      if (item.getFieldName().equals("FileRepresentation")) {
-        element = t.parseEntity(item.openStream()).getRootElement();
-      } else {
-        throw ServerMessages.NO_FILE_REPRESENTATION_SUBMITTED;
-      }
-    } else {
-      throw ServerMessages.FORM_CONTAINS_UNSUPPORTED_CONTENT;
-    }
-
-    // Get files, store to tmp dir.
-    while (iter.hasNext()) {
-      item = iter.next();
-      try {
-        final FileProperties file = FileSystem.upload(item, t.getSRID());
-        container.addFile(item.getName(), file);
-      } catch (final IOException e) {
-        throw new FileUploadException();
-      }
-    }
-
-    // transform xml elements to entities and add them to the container.
-    processEntities(container, element);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/GetNamesRequestHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/GetNamesRequestHandler.java
deleted file mode 100644
index c327c6833d281c07148c59be689ddfac25b33f4d..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/GetNamesRequestHandler.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.caosdb.server.resource.transaction.handlers;
-
-import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-
-public class GetNamesRequestHandler extends RequestHandler<RetrieveContainer> {
-
-  /** Adds the `names` flag to the RetrieveContainer which triggers the RetrieveAllNames job. */
-  @Override
-  public void handle(EntityResource t, RetrieveContainer container) throws Exception {
-    container.getFlags().put("names", null);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/RequestHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/RequestHandler.java
deleted file mode 100644
index fc49cc518db12e1f750716372f94b808dcb3874b..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/RequestHandler.java
+++ /dev/null
@@ -1,31 +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.resource.transaction.handlers;
-
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-
-public abstract class RequestHandler<T extends TransactionContainer> {
-
-  public abstract void handle(final EntityResource t, T container) throws Exception;
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/RetrieveGlobalEntityPermissionsHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/RetrieveGlobalEntityPermissionsHandler.java
deleted file mode 100644
index 6801e804a8bcb721e2c7170ad857126579cbfb46..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/RetrieveGlobalEntityPermissionsHandler.java
+++ /dev/null
@@ -1,36 +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.resource.transaction.handlers;
-
-import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.permissions.EntityPermission;
-import org.caosdb.server.resource.transaction.EntityResource;
-
-public class RetrieveGlobalEntityPermissionsHandler extends SimpleGetRequestHandler {
-
-  @Override
-  public void handle(final EntityResource t, final RetrieveContainer container) throws Exception {
-    super.handle(t, container);
-    container.addMessage(EntityPermission.getAllEntityPermissions());
-  }
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/RetriveOwnerRequestHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/RetriveOwnerRequestHandler.java
deleted file mode 100644
index 7b2d698cb4673ba7e4ed30fc81fc249e89632734..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/RetriveOwnerRequestHandler.java
+++ /dev/null
@@ -1,38 +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.resource.transaction.handlers;
-
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-
-public class RetriveOwnerRequestHandler extends SimpleGetRequestHandler {
-
-  @Override
-  public void handle(final EntityResource t, final RetrieveContainer container) throws Exception {
-    super.handle(t, container);
-    for (final EntityInterface e : container) {
-      e.setFlag("owner", null);
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java
deleted file mode 100644
index 34c1a3836290f4e90fde5a1ec10a2818b7b63fae..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java
+++ /dev/null
@@ -1,55 +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) 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/>.
- *
- * ** end header
- */
-package org.caosdb.server.resource.transaction.handlers;
-
-import org.caosdb.server.entity.container.DeleteContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-
-public class SimpleDeleteRequestHandler extends RequestHandler<DeleteContainer> {
-
-  @Override
-  public void handle(final EntityResource t, final DeleteContainer container) throws Exception {
-    // TODO a lot of code duplication, see SimpleGetRequestHandle#handle.
-    // However, this is about to be changed again when string ids are
-    // introduced, anyways. So we just leave it.
-    for (final String item : t.getRequestedItems()) {
-      String[] elem = item.split("@", 1);
-      Integer id = null;
-      String version = null;
-      try {
-        id = Integer.parseInt(elem[0]);
-      } catch (NumberFormatException e) {
-        // pass
-      }
-      if (elem.length > 1) {
-        version = elem[1];
-      }
-
-      if (id != null) {
-        container.add(id, version);
-      }
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java
deleted file mode 100644
index b80d9c219e2cbda1e012c9c73993b5ea427c6ccf..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java
+++ /dev/null
@@ -1,53 +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.resource.transaction.handlers;
-
-import org.caosdb.server.entity.container.RetrieveContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-
-public class SimpleGetRequestHandler extends RequestHandler<RetrieveContainer> {
-
-  @Override
-  public void handle(final EntityResource t, final RetrieveContainer container) throws Exception {
-    for (final String item : t.getRequestedItems()) {
-      String[] elem = item.split("@", 2);
-      Integer id = null;
-      String name = null;
-      String version = null;
-      try {
-        id = Integer.parseInt(elem[0]);
-      } catch (NumberFormatException e) {
-        name = elem[0];
-      }
-      if (elem.length > 1) {
-        version = elem[1];
-      }
-
-      if (id != null) {
-        container.add(id, version);
-      } else {
-        container.add(name);
-      }
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleWriteHandler.java b/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleWriteHandler.java
deleted file mode 100644
index feb784da2f1ab9dcc09942a108bcce6bd073e128..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/resource/transaction/handlers/SimpleWriteHandler.java
+++ /dev/null
@@ -1,58 +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.resource.transaction.handlers;
-
-import java.util.List;
-import org.caosdb.server.entity.container.WritableContainer;
-import org.caosdb.server.resource.transaction.EntityResource;
-import org.jdom2.Element;
-
-public class SimpleWriteHandler<T extends WritableContainer> extends RequestHandler<T> {
-
-  /**
-   * Transform child elements of parameter 'element' to entities and add them to the container. Iff
-   * parameter 'tag' is not null, only elements with that tag name will be transformed into
-   * entities. The others will be ignored.
-   *
-   * @param container
-   * @param element
-   * @param tag
-   * @throws Exception
-   */
-  protected void processEntities(final WritableContainer container, final Element element)
-      throws Exception {
-
-    final List<Element> insertEntities;
-    insertEntities = element.getChildren();
-
-    for (final Element e : insertEntities) {
-      container.add(e);
-    }
-  }
-
-  @Override
-  public void handle(final EntityResource t, final T container) throws Exception {
-    final Element element = t.parseEntity(t.getRequest().getEntity()).getRootElement();
-    processEntities(container, element);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/CaosDBTerminal.java b/src/main/java/org/caosdb/server/terminal/CaosDBTerminal.java
deleted file mode 100644
index 96bd6315b7e32c03273a8b65bd489bfc061e0ef1..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/CaosDBTerminal.java
+++ /dev/null
@@ -1,72 +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.terminal;
-
-import com.googlecode.lanterna.TerminalFacade;
-import com.googlecode.lanterna.gui.GUIScreen;
-import com.googlecode.lanterna.gui.GUIScreen.Position;
-import com.googlecode.lanterna.screen.Screen;
-import com.googlecode.lanterna.terminal.Terminal;
-import com.googlecode.lanterna.terminal.text.UnixTerminal;
-import java.nio.charset.Charset;
-
-/**
- * @deprecated Soon to be removed
- * @author Timm Fitschen (t.fitschen@indiscale.com)
- */
-@Deprecated
-public class CaosDBTerminal extends Thread {
-
-  public CaosDBTerminal() {
-    if ((this.terminal =
-            TerminalFacade.createTerminal(System.in, System.out, Charset.forName("UTF8")))
-        instanceof UnixTerminal) {
-      this.terminal =
-          new UnixTerminal(
-              System.in,
-              System.out,
-              Charset.forName("UTF8"),
-              null,
-              UnixTerminal.Behaviour.CTRL_C_KILLS_APPLICATION);
-    }
-    this.screen = new Screen(this.terminal);
-    this.guiScreen = new GUIScreen(this.screen);
-  }
-
-  private Terminal terminal = null;
-  private Screen screen = null;
-  private GUIScreen guiScreen = null;
-
-  @Override
-  public void run() {
-    this.guiScreen.getScreen().startScreen();
-    final MainWindow m = new MainWindow();
-
-    this.guiScreen.showWindow(m, Position.FULL_SCREEN);
-    this.guiScreen.getScreen().stopScreen();
-  }
-
-  public void shutDown() {
-    this.guiScreen.getActiveWindow().close();
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/DatabaseAccessPanel.java b/src/main/java/org/caosdb/server/terminal/DatabaseAccessPanel.java
deleted file mode 100644
index 6877049605888682bd003f8382a931661e79602c..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/DatabaseAccessPanel.java
+++ /dev/null
@@ -1,84 +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.terminal;
-
-import com.googlecode.lanterna.gui.component.Label;
-import com.googlecode.lanterna.gui.component.Panel;
-import com.googlecode.lanterna.gui.component.Table;
-import org.caosdb.server.database.DatabaseMonitor;
-import org.caosdb.server.utils.Observable;
-import org.caosdb.server.utils.Observer;
-
-public final class DatabaseAccessPanel extends Panel implements Observer {
-
-  private static final DatabaseAccessPanel instance = new DatabaseAccessPanel();
-  private final Table table = new Table(2);
-  private final Label label1 = new Label();
-  private final Label label2 = new Label();
-  private final Label label3 = new Label();
-  private final Label label4 = new Label();
-  private final Label label5 = new Label();
-
-  public static final DatabaseAccessPanel getInstance() {
-    return instance;
-  }
-
-  private DatabaseAccessPanel() {
-    super();
-    DatabaseMonitor.acceptWeakAccessObserver(this);
-    DatabaseMonitor.acceptStrongAccessObserver(this);
-
-    this.table.addRow(new Label("Weak access, acquired:"), this.label1);
-    this.table.addRow(new Label("Weak access, waiting:"), this.label2);
-    this.table.addRow(new Label("Strong access, allocated:"), this.label3);
-    this.table.addRow(new Label("Strong access, acquired:"), this.label4);
-    this.table.addRow(new Label("Strong access, waiting:"), this.label5);
-    addComponent(this.table);
-
-    notifyObserver(null, null);
-  }
-
-  @Override
-  public boolean notifyObserver(final String e, final Observable o) {
-    try {
-      synchronized (this) {
-        if (getParent() != null) {
-          this.label1.setText(Integer.toString(DatabaseMonitor.acquiredWeakAccess()));
-          this.label2.setText(Integer.toString(DatabaseMonitor.waitingAcquireWeakAccess()));
-          this.label3.setText(
-              DatabaseMonitor.whoHasAllocatedStrongAccess() != null
-                  ? DatabaseMonitor.whoHasAllocatedStrongAccess().getName()
-                  : "None");
-          this.label4.setText(
-              DatabaseMonitor.whoHasAcquiredStrongAccess() != null
-                  ? DatabaseMonitor.whoHasAcquiredStrongAccess().getName()
-                  : "None");
-          this.label5.setText(Integer.toString(DatabaseMonitor.waitingAllocateStrongAccess()));
-        }
-      }
-    } catch (final Exception exc) {
-      exc.printStackTrace();
-    }
-    return true;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/EntitiesPanel.java b/src/main/java/org/caosdb/server/terminal/EntitiesPanel.java
deleted file mode 100644
index b2f7489de66eeae9771e911b45e7d4546cb4f5b9..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/EntitiesPanel.java
+++ /dev/null
@@ -1,85 +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.terminal;
-
-import com.googlecode.lanterna.gui.component.Label;
-import com.googlecode.lanterna.gui.component.Panel;
-import com.googlecode.lanterna.gui.component.Table;
-import org.caosdb.server.utils.Info;
-
-public final class EntitiesPanel extends Panel implements StatComponent {
-
-  private static final EntitiesPanel instance = new EntitiesPanel();
-  private boolean active = false;
-  private final Table table = new Table(2);
-  private final Label label1 = new Label();
-  private final Label label2 = new Label();
-  private final Label label3 = new Label();
-  private final Label label4 = new Label();
-
-  public static final EntitiesPanel getInstance() {
-    return instance;
-  }
-
-  private EntitiesPanel() {
-    super();
-
-    this.table.addRow(new Label("RecordTypes:"), this.label2);
-    this.table.addRow(new Label("Properties:"), this.label3);
-    this.table.addRow(new Label("Records:"), this.label4);
-    this.table.addRow(new Label("Files:"), this.label1);
-    addComponent(this.table);
-
-    // init INFO
-    Info.getInstance().notifyObserver(null, null);
-
-    // adds itself to StatsPanel
-    StatsPanel.addStat("Entities", this);
-    start();
-  }
-
-  @Override
-  public void start() {
-    this.active = true;
-    update();
-  }
-
-  @Override
-  public void stop() {
-    this.active = false;
-  }
-
-  @Override
-  public void update() {
-    if (this.active) {
-      try {
-        this.label1.setText(Info.getFilesCount().toString());
-        this.label2.setText(Info.getRecordTypesCount().toString());
-        this.label3.setText(Info.getPropertiesCount().toString());
-        this.label4.setText(Info.getRecordsCount().toString());
-      } catch (final Exception e) {
-        e.printStackTrace();
-      }
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/MainWindow.java b/src/main/java/org/caosdb/server/terminal/MainWindow.java
deleted file mode 100644
index aa22f12b23bed00dfb46db8c7cd140fb05551eec..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/MainWindow.java
+++ /dev/null
@@ -1,174 +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.terminal;
-
-import com.googlecode.lanterna.gui.Action;
-import com.googlecode.lanterna.gui.Border;
-import com.googlecode.lanterna.gui.Window;
-import com.googlecode.lanterna.gui.component.Button;
-import com.googlecode.lanterna.gui.component.Label;
-import com.googlecode.lanterna.gui.component.Panel;
-import com.googlecode.lanterna.gui.dialog.DialogButtons;
-import com.googlecode.lanterna.gui.dialog.DialogResult;
-import com.googlecode.lanterna.gui.dialog.MessageBox;
-import com.googlecode.lanterna.gui.layout.LinearLayout;
-import com.googlecode.lanterna.input.Key;
-import com.googlecode.lanterna.input.Key.Kind;
-import org.caosdb.server.utils.Observer;
-
-public class MainWindow extends Window {
-
-  // A panel at the top of the window which contains buttons to switch
-  // between several data panels which are to be shown in the dataPanel.
-  final Panel dataSelectorPanel = new Panel(new Border.Invisible(), Panel.Orientation.HORISONTAL);
-
-  // A panel right below the dataSelectorPanel. Here the interesting
-  // data is to be show. The contained panels can be made visible or
-  // invisible via the buttons in the dataSelectorPanel.
-  final Panel dataPanel = new Panel(new Border.Bevel(true), Panel.Orientation.HORISONTAL);
-
-  public final synchronized void setVisibleDataPanel(final Panel visibleDataPanel) {
-    this.dataPanel.removeAllComponents();
-    this.dataPanel.addComponent(visibleDataPanel);
-  }
-
-  public MainWindow() {
-    super("Caos DB");
-    addComponent(this.dataSelectorPanel, LinearLayout.MAXIMIZES_HORIZONTALLY);
-    addComponent(
-        this.dataPanel, LinearLayout.MAXIMIZES_HORIZONTALLY, LinearLayout.MAXIMIZES_VERTICALLY);
-
-    // initializing stream output panels. These pipe the output of
-    // System.out and System.err to a TextArea.
-    final SystemOutPanel systemOutPanel = SystemOutPanel.getInstance();
-    final Thread systemOutThread = new Thread(systemOutPanel);
-    systemOutThread.setName("systemOutPanel");
-
-    final SystemErrPanel systemErrPanel = SystemErrPanel.getInstance();
-    final Thread systemErrThread = new Thread(systemErrPanel);
-    systemErrThread.setName("systemErrPanel");
-
-    systemOutThread.start();
-    systemErrThread.start();
-
-    // initializing DatabaseAccessPanel that shows which thread is accessing
-    // the database and how many threads are waiting for access.
-    final DatabaseAccessPanel databaseAccessPanel = DatabaseAccessPanel.getInstance();
-
-    // init StatsPanel
-    StatsPanel.getPanel();
-
-    // init entities stats
-    EntitiesPanel.getInstance();
-
-    // add all initialized panels to the main window
-    addDataPanel("System.out", systemOutPanel);
-    addDataPanel("System.err", systemErrPanel);
-    addDataPanel("DB Access", databaseAccessPanel);
-    addDataPanel("Misc", StatsPanel.getPanel());
-
-    // add a welcome panel
-    this.dataPanel.addComponent(
-        new Label("Welcome. This is CaosDB - An Open Scientific DataBase."));
-
-    // add shutdown button
-    this.dataSelectorPanel.addComponent(new ShutdownButton());
-
-    // set focus on the dataSelectorPanel
-    setFocus(this.dataSelectorPanel.nextFocus(null));
-  }
-
-  /**
-   * Add a panel to the dataPanel. A button is created within the dataSelectorPanel which shows the
-   * name of the panel
-   *
-   * @param name
-   * @param dataPanel
-   */
-  private void addDataPanel(final String name, final Panel dataPanel) {
-    this.dataSelectorPanel.addComponent(new DataPanelSelectorButton(name, dataPanel));
-  }
-
-  /**
-   * A Button that causes the dataPanel to show a certain panel.
-   *
-   * @author tf
-   */
-  class DataPanelSelectorButton extends Button {
-    public DataPanelSelectorButton(final String name, final Panel panel) {
-      super(
-          name,
-          new Action() {
-
-            @Override
-            public void doAction() {
-              // show panel in dataPanel
-              setVisibleDataPanel(panel);
-
-              // update panel if necessary
-              if (panel instanceof Observer) {
-                ((Observer) panel).notifyObserver(null, null);
-              }
-
-              // move focus to panel
-              setFocus(panel.nextFocus(null));
-            }
-          });
-    }
-  }
-
-  @Override
-  public void onKeyPressed(final Key key) {
-    if (key.getKind() == Kind.Escape) {
-      setFocus(this.dataSelectorPanel.nextFocus(null));
-    } else {
-      super.onKeyPressed(key);
-    }
-  }
-
-  /**
-   * A button that causes the server to shut down when pressed.
-   *
-   * @author tf
-   */
-  class ShutdownButton extends Button {
-    public ShutdownButton() {
-      super(
-          "Shutdown Server",
-          new Action() {
-            @Override
-            public void doAction() {
-              final DialogResult result =
-                  MessageBox.showMessageBox(
-                      getOwner(),
-                      "Server shutdown",
-                      "Select [OK] to shut down the server now.",
-                      DialogButtons.OK_CANCEL);
-              if (result == DialogResult.OK) {
-                System.exit(0);
-              }
-            }
-          });
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/OutputStreamPanel.java b/src/main/java/org/caosdb/server/terminal/OutputStreamPanel.java
deleted file mode 100644
index 50284f8a88e7d30596cc901be24108f3ea201d0a..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/OutputStreamPanel.java
+++ /dev/null
@@ -1,151 +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.terminal;
-
-import com.googlecode.lanterna.gui.Action;
-import com.googlecode.lanterna.gui.component.Button;
-import com.googlecode.lanterna.gui.component.Panel;
-import com.googlecode.lanterna.gui.component.TextArea;
-import com.googlecode.lanterna.gui.layout.LinearLayout;
-import com.googlecode.lanterna.input.Key;
-import com.googlecode.lanterna.input.Key.Kind;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.io.PrintStream;
-
-class PipedPrintStream extends PrintStream {
-  private final TextArea out;
-  int lines = 0;
-
-  public PipedPrintStream(final TextArea out) {
-    super(new NullOutputStream());
-    this.out = out;
-  }
-
-  @Override
-  public void println(final String x) {
-    if (open) {
-      synchronized (this) {
-        out.appendLine(x);
-        if (lines >= 4000) {
-          out.removeLine(0);
-        } else {
-          lines++;
-        }
-      }
-    }
-  }
-
-  @Override
-  public void print(final String x) {}
-
-  private final boolean open = false;
-}
-
-class NullOutputStream extends OutputStream {
-
-  @Override
-  public void write(final int b) throws IOException {}
-}
-
-public abstract class OutputStreamPanel extends Panel implements Runnable {
-
-  private final TextArea logArea = new TextArea();
-  private BufferedReader reader = null;
-  protected final PipedOutputStream panelOutputStream = new PipedOutputStream();
-
-  protected final PipedOutputStream getPanelOutputStream() {
-    return panelOutputStream;
-  }
-
-  Button toogle =
-      new Button(
-          "on/off",
-          new Action() {
-
-            @Override
-            public void doAction() {
-              toogle();
-            }
-          });
-
-  private boolean on = true;
-
-  void toogle() {
-    on = !on;
-  }
-
-  public OutputStreamPanel() {
-    super();
-    // addComponent(toogle);
-    addComponent(logArea, LinearLayout.MAXIMIZES_HORIZONTALLY, LinearLayout.MAXIMIZES_VERTICALLY);
-  }
-
-  protected void init() throws IOException {
-    final PipedInputStream pIn = new PipedInputStream(panelOutputStream);
-    reader = new BufferedReader(new InputStreamReader(pIn));
-  }
-
-  @Override
-  public final void run() {
-    int lines = 0;
-    while (true) {
-      while (on) {
-        try {
-          if (reader.ready()) {
-            logArea.appendLine(reader.readLine());
-            if (lines >= 4000) {
-              logArea.removeLine(0);
-            } else {
-              lines++;
-            }
-            try {
-              logArea.keyboardInteraction(new Key(Kind.End));
-            } catch (final NullPointerException e) {
-              // this usually happens when the logArea is updated
-              // but
-              // not painted (while not being an active panel)
-            }
-          } else {
-            Thread.sleep(1000);
-          }
-
-        } catch (final Exception e2) {
-          e2.printStackTrace();
-        }
-      }
-    }
-  }
-
-  @Override
-  protected void finalize() throws Throwable {
-    if (reader != null) {
-      reader.close();
-    }
-    super.finalize();
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/StatComponent.java b/src/main/java/org/caosdb/server/terminal/StatComponent.java
deleted file mode 100644
index 90a9ac1125425bd23415abf3e4186f00695b9b2f..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/StatComponent.java
+++ /dev/null
@@ -1,34 +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.terminal;
-
-import com.googlecode.lanterna.gui.Component;
-
-public interface StatComponent extends Component {
-
-  public void start();
-
-  public void stop();
-
-  public void update();
-}
diff --git a/src/main/java/org/caosdb/server/terminal/StatLabel.java b/src/main/java/org/caosdb/server/terminal/StatLabel.java
deleted file mode 100644
index 73a03ac31ed8874fc936807393701995130eaf7f..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/StatLabel.java
+++ /dev/null
@@ -1,71 +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.terminal;
-
-import com.googlecode.lanterna.gui.component.Label;
-import org.caosdb.server.utils.Observable;
-import org.caosdb.server.utils.Observer;
-
-public class StatLabel extends Label implements Observer, StatComponent {
-
-  boolean active = false;
-  Object obj;
-  String name;
-
-  @Override
-  public boolean notifyObserver(final String e, final Observable o) {
-    update();
-    return true;
-  }
-
-  public StatLabel(final Object obj) {
-    this(null, obj);
-  }
-
-  public StatLabel(final String name, final Object obj) {
-    this.name = name;
-    this.obj = obj;
-    if (obj instanceof Observable) {
-      ((Observable) obj).acceptObserver(this);
-    }
-    start();
-    update();
-  }
-
-  @Override
-  public void stop() {
-    this.active = false;
-  }
-
-  @Override
-  public void start() {
-    this.active = true;
-  }
-
-  @Override
-  public void update() {
-    if (this.active) {
-      setText((this.name == null ? "" : this.name + ": ") + this.obj.toString());
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/StatTable.java b/src/main/java/org/caosdb/server/terminal/StatTable.java
deleted file mode 100644
index e7234cdb8000a7c4dcc98463c3e9ca6cc8c9f19c..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/StatTable.java
+++ /dev/null
@@ -1,56 +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.terminal;
-
-import com.googlecode.lanterna.gui.Component;
-import com.googlecode.lanterna.gui.component.Panel;
-
-public class StatTable extends Panel implements StatComponent {
-
-  @Override
-  public void start() {
-    for (final Component c : components()) {
-      if (c instanceof StatComponent) {
-        ((StatComponent) c).start();
-      }
-    }
-  }
-
-  @Override
-  public void stop() {
-    for (final Component c : components()) {
-      if (c instanceof StatComponent) {
-        ((StatComponent) c).stop();
-      }
-    }
-  }
-
-  @Override
-  public void update() {
-    for (final Component c : components()) {
-      if (c instanceof StatComponent) {
-        ((StatComponent) c).update();
-      }
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/StatsPanel.java b/src/main/java/org/caosdb/server/terminal/StatsPanel.java
deleted file mode 100644
index 3b41ac6a9c4ae7366e85cffedbd71f83b30c6438..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/StatsPanel.java
+++ /dev/null
@@ -1,106 +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.terminal;
-
-import com.googlecode.lanterna.gui.Action;
-import com.googlecode.lanterna.gui.Border;
-import com.googlecode.lanterna.gui.component.Button;
-import com.googlecode.lanterna.gui.component.Panel;
-import com.googlecode.lanterna.gui.layout.LinearLayout;
-import java.util.HashMap;
-
-public class StatsPanel extends Panel {
-
-  private static StatsPanel instance = null;
-
-  HashMap<String, StatTable> stats = new HashMap<String, StatTable>();
-  Panel dataPanel = new Panel("Data", new Border.Bevel(true), Panel.Orientation.VERTICAL);
-  Panel selectorPanel = new Panel("Selector", new Border.Bevel(true), Panel.Orientation.VERTICAL);
-  StatComponent current = null;
-
-  private StatsPanel() {
-    super(Panel.Orientation.HORISONTAL);
-    addComponent(this.selectorPanel, LinearLayout.MAXIMIZES_VERTICALLY);
-    addComponent(
-        this.dataPanel, LinearLayout.MAXIMIZES_VERTICALLY, LinearLayout.MAXIMIZES_HORIZONTALLY);
-  }
-
-  public static void addStat(final String name, final Object obj) {
-    if (instance != null) {
-      instance.addStatComponent(name, new StatLabel(obj));
-    }
-  }
-
-  public static void addStat(final String name, final StatComponent statComponent) {
-    if (instance != null) {
-      instance.addStatComponent(name, statComponent);
-    }
-  }
-
-  private void addStatComponent(final String name, final StatComponent statComponent) {
-    final StatTable c = this.stats.get(name);
-    if (c != null) {
-      c.addComponent(statComponent);
-    } else {
-      final StatTable table = new StatTable();
-      table.addComponent(statComponent);
-      this.selectorPanel.addComponent(new SelectorButton(name, table));
-      this.stats.put(name, table);
-    }
-  }
-
-  private void setVisibleDataPanel(final StatComponent panel) {
-    this.dataPanel.removeAllComponents();
-
-    if (this.current != null) {
-      this.current.stop();
-    }
-    this.current = panel;
-    this.current.start();
-    this.current.update();
-
-    this.dataPanel.addComponent(this.current);
-  }
-
-  class SelectorButton extends Button {
-    public SelectorButton(final String name, final StatComponent panel) {
-      super(
-          name,
-          new Action() {
-
-            @Override
-            public void doAction() {
-              // show panel in dataPanel
-              setVisibleDataPanel(panel);
-            }
-          });
-    }
-  }
-
-  public static Panel getPanel() {
-    if (instance == null) {
-      instance = new StatsPanel();
-    }
-    return instance;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/SystemErrPanel.java b/src/main/java/org/caosdb/server/terminal/SystemErrPanel.java
deleted file mode 100644
index 9bf800fdb3c755cd8ea2b2a55119cf4e093fde09..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/SystemErrPanel.java
+++ /dev/null
@@ -1,53 +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.terminal;
-
-import com.googlecode.lanterna.gui.component.Label;
-import java.io.IOException;
-import java.io.PrintStream;
-
-public final class SystemErrPanel extends OutputStreamPanel {
-
-  private static final SystemErrPanel instance = new SystemErrPanel();
-  private PrintStream err;
-
-  public static final SystemErrPanel getInstance() {
-    return instance;
-  }
-
-  private SystemErrPanel() {
-    super();
-    try {
-      init();
-      this.err = System.err;
-      final PrintStream newErr = new PrintStream(getPanelOutputStream());
-      System.setErr(newErr);
-    } catch (final IOException e) {
-      addComponent(new Label("Sorry, could not initialize this SystemErrPanel"));
-    }
-  }
-
-  public static void close() {
-    System.setErr(instance.err);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/terminal/SystemOutPanel.java b/src/main/java/org/caosdb/server/terminal/SystemOutPanel.java
deleted file mode 100644
index b34cdaf97d8088a97ea66932d9abc96d6e1589be..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/terminal/SystemOutPanel.java
+++ /dev/null
@@ -1,46 +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.terminal;
-
-import com.googlecode.lanterna.gui.component.Label;
-import java.io.IOException;
-import java.io.PrintStream;
-
-public final class SystemOutPanel extends OutputStreamPanel {
-
-  private static final SystemOutPanel instance = new SystemOutPanel();
-
-  public static final SystemOutPanel getInstance() {
-    return instance;
-  }
-
-  private SystemOutPanel() {
-    super();
-    try {
-      init();
-      System.setOut(new PrintStream(getPanelOutputStream()));
-    } catch (final IOException e) {
-      addComponent(new Label("Sorry, could not initialize this SystemOutPanel"));
-    }
-  }
-}
diff --git a/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java b/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java
index 0a2d1363427d9e21504b421c88e7605af59634ac..4897cd65620577e36a98f2db5e5530a42b3f43d0 100644
--- a/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/AccessControlTransaction.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.transaction;
 
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.misc.RollBackHandler;
 import org.caosdb.server.entity.Message;
@@ -33,7 +33,7 @@ public abstract class AccessControlTransaction implements TransactionInterface {
 
   @Override
   public final void execute() throws Exception {
-    this.access = DatabaseMonitor.getAccountAccess(this);
+    this.access = DatabaseAccessManager.getAccountAccess(this);
 
     try {
       transaction();
diff --git a/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java b/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java
index b9d350719f13b5bd26e44f7d2b959a428adf5793..56fc07da22d9fac5bfbe713d1a9cd36b2d25a9ba 100644
--- a/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java
+++ b/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java
@@ -25,7 +25,7 @@ package org.caosdb.server.transaction;
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 import org.caosdb.server.CaosDBException;
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.transaction.GetUpdateableChecksums;
 import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity;
@@ -34,7 +34,7 @@ import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.FileProperties;
 import org.caosdb.server.entity.Message;
-import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.entity.container.WritableContainer;
 import org.caosdb.server.utils.FileUtils;
 
 /**
@@ -43,14 +43,15 @@ import org.caosdb.server.utils.FileUtils;
  *
  * @author tf
  */
-public class ChecksumUpdater extends WriteTransaction<TransactionContainer> implements Runnable {
+public class ChecksumUpdater extends WriteTransaction
+    implements Runnable, WriteTransactionInterface {
 
   private Boolean running = false;
 
   private static final ChecksumUpdater instance = new ChecksumUpdater();
 
   private ChecksumUpdater() {
-    super(new TransactionContainer());
+    super(new WritableContainer());
   }
 
   /** Retrieves all file without a checksum, calculates one and stores it to the database */
@@ -76,14 +77,14 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
         Access strongAccess = null;
 
         try {
-          strongAccess = DatabaseMonitor.getInstance().allocateStrongAccess(this);
+          strongAccess = DatabaseAccessManager.getInstance().reserveWriteAccess(this);
           if (wasChangedInTheMeantime(fileEntity, strongAccess, lastModified)) {
             continue;
           }
 
           fileEntity.getFileProperties().setChecksum(checksum);
 
-          DatabaseMonitor.getInstance().acquireStrongAccess(this);
+          DatabaseAccessManager.getInstance().acquireWriteAccess(this);
 
           // update
           execute(new SetFileChecksum(fileEntity), strongAccess);
@@ -130,8 +131,8 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
     return null;
   }
 
-  private EntityInterface getNextUpdateableFileEntity() {
-    final Access weakAccess = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
+  private EntityInterface getNextUpdateableFileEntity() throws InterruptedException {
+    final Access weakAccess = DatabaseAccessManager.getInstance().acquireReadAccess(this);
     try {
       synchronized (instance.running) {
 
diff --git a/src/main/java/org/caosdb/server/transaction/Delete.java b/src/main/java/org/caosdb/server/transaction/Delete.java
deleted file mode 100644
index 44e68b3afe6225f286ab76329b699f0cf0f10670..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/transaction/Delete.java
+++ /dev/null
@@ -1,116 +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.transaction;
-
-import org.apache.shiro.authz.AuthorizationException;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.transaction.DeleteEntity;
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntity;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.container.DeleteContainer;
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.jobs.JobExecutionTime;
-import org.caosdb.server.permissions.EntityPermission;
-import org.caosdb.server.utils.EntityStatus;
-import org.caosdb.server.utils.ServerMessages;
-
-public class Delete extends WriteTransaction<DeleteContainer> {
-
-  public Delete(final DeleteContainer container) {
-    super(container);
-  }
-
-  @Override
-  protected void init() throws Exception {}
-
-  @Override
-  protected void preCheck() throws InterruptedException, Exception {
-    // allocate strong (write) access. Only one thread can do this
-    // at a time. But weak access can still be acquired by other
-    // thread until the allocated strong access is actually
-    // acquired.
-    setAccess(getMonitor().allocateStrongAccess(this));
-
-    // retrieve all entities which are to be deleted.
-    execute(new RetrieveFullEntity(getContainer()), getAccess());
-
-    for (final EntityInterface e : getContainer()) {
-      if (e.getEntityStatus() == EntityStatus.NONEXISTENT) {
-        e.addError(ServerMessages.ENTITY_DOES_NOT_EXIST);
-        continue;
-      }
-
-      // check permissions
-      try {
-        e.checkPermission(EntityPermission.DELETE);
-      } catch (final AuthorizationException exc) {
-        e.setEntityStatus(EntityStatus.UNQUALIFIED);
-        e.addError(ServerMessages.AUTHORIZATION_ERROR);
-        e.addInfo(exc.getMessage());
-      }
-
-      // no standard entities are to be deleted.
-      if (e.hasId() && e.getId() < 100) {
-        e.setEntityStatus(EntityStatus.UNQUALIFIED);
-        e.addInfo("This entity cannot be deleted");
-      }
-    }
-
-    // make schedule of existing entities which are to be deleted.
-    makeSchedule();
-    getSchedule().runJobs(JobExecutionTime.INIT);
-  }
-
-  @Override
-  protected void postCheck() {}
-
-  @Override
-  protected void postTransaction() {
-    // set entityStatus to DELETED and add deletion info message
-    if (getContainer().getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      for (final EntityInterface entity : getContainer()) {
-        if (entity.getEntityStatus() == EntityStatus.VALID) {
-          entity.setEntityStatus(EntityStatus.DELETED);
-          entity.addInfo(ServerMessages.ENTITY_HAS_BEEN_DELETED_SUCCESSFULLY);
-        }
-      }
-    }
-  }
-
-  @Override
-  public void transaction() throws Exception {
-    // delete entities from database
-    delete(getContainer(), getAccess());
-  }
-
-  private void delete(final TransactionContainer container, final Access access) throws Exception {
-    if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      execute(new DeleteEntity(container), access);
-    }
-  }
-
-  @Override
-  public boolean logHistory() {
-    return true;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java b/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java
index 5d2a04b1e9f45360f47a93889f638eab23fbd512..ed93812d22eaad6bf448fa1dede2d92cea1d50a6 100644
--- a/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java
+++ b/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java
@@ -29,7 +29,7 @@ import java.util.List;
 import java.util.Map.Entry;
 import java.util.TimeZone;
 import org.caosdb.datetime.UTCDateTime;
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.transaction.FileConsistencyCheck;
 import org.caosdb.server.database.backend.transaction.GetFileIterator;
@@ -64,8 +64,8 @@ public class FileStorageConsistencyCheck extends Thread
 
   @Override
   public void run() {
-    this.access = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
     try {
+      this.access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
 
       // test all files in file system.
       final Iterator<String> iterator =
@@ -73,12 +73,12 @@ public class FileStorageConsistencyCheck extends Thread
 
       this.ts = System.currentTimeMillis();
       while (iterator != null && iterator.hasNext()) {
-        if (DatabaseMonitor.whoHasAllocatedStrongAccess() != null) {
+        if (DatabaseAccessManager.whoHasReservedWriteAccess() != null) {
           // there is a thread waiting to write. pause this one and
           // apply for a new weak access which will be granted when
           // the write thread is ready.
           this.access.release();
-          this.access = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
+          this.access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
         }
 
         final String path = iterator.next();
diff --git a/src/main/java/org/caosdb/server/transaction/Insert.java b/src/main/java/org/caosdb/server/transaction/Insert.java
deleted file mode 100644
index d37f7ce516f36252466555ea18fe249f2730fea1..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/transaction/Insert.java
+++ /dev/null
@@ -1,113 +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.transaction;
-
-import org.apache.shiro.SecurityUtils;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.transaction.InsertEntity;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.FileProperties;
-import org.caosdb.server.entity.container.InsertContainer;
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.permissions.EntityACL;
-import org.caosdb.server.utils.EntityStatus;
-import org.caosdb.server.utils.ServerMessages;
-
-public class Insert extends WriteTransaction<InsertContainer> {
-
-  public Insert(final InsertContainer container) {
-    super(container);
-  }
-
-  @Override
-  protected void init() throws Exception {
-    // allocate strong (write) access. Only one thread can do this
-    // at a time. But weak access can still be acquired by other
-    // thread until the allocated strong access is actually
-    // acquired.
-    setAccess(getMonitor().allocateStrongAccess(this));
-
-    // make schedule for all parsed entities;
-    makeSchedule();
-
-    // dereference files (file upload only)
-    for (final EntityInterface entity : getContainer()) {
-      if (entity.hasFileProperties() && !entity.getFileProperties().isPickupable()) {
-        if (entity.getFileProperties().getTmpIdentifyer() != null) {
-          final FileProperties f =
-              getContainer().getFiles().get(entity.getFileProperties().getTmpIdentifyer());
-          if (f != null) {
-            entity.getFileProperties().setFile(f.getFile());
-            if (f.getThumbnail() != null) {
-              entity.getFileProperties().setThumbnail(f.getThumbnail());
-            } else {
-              final FileProperties thumbnail =
-                  getContainer()
-                      .getFiles()
-                      .get(entity.getFileProperties().getTmpIdentifyer() + ".thumbnail");
-              if (thumbnail != null) {
-                entity.getFileProperties().setThumbnail(thumbnail.getFile());
-              }
-            }
-          } else {
-            entity.addError(ServerMessages.FILE_HAS_NOT_BEEN_UPLOAED);
-            entity.setEntityStatus(EntityStatus.UNQUALIFIED);
-          }
-        }
-      }
-    }
-  }
-
-  @Override
-  protected void preCheck() throws InterruptedException {
-    for (final EntityInterface entity : getContainer()) {
-      // set default EntityACL if none present
-      if (entity.getEntityACL() == null) {
-        entity.setEntityACL(EntityACL.getOwnerACLFor(SecurityUtils.getSubject()));
-      }
-    }
-  }
-
-  @Override
-  protected void postCheck() {}
-
-  @Override
-  protected void postTransaction() {}
-
-  @Override
-  public void transaction() throws Exception {
-    // write new entities to database
-    insert(getContainer(), getAccess());
-  }
-
-  public void insert(final TransactionContainer container, final Access access) throws Exception {
-    if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      execute(new InsertEntity(container), access);
-    }
-  }
-
-  @Override
-  public boolean logHistory() {
-    return true;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java b/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java
index be01d93086e65694313c0d97c6b1603aef6a6bc7..c1128135b8620ebfe08328cd4901e9c0e6062053 100644
--- a/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java
@@ -24,7 +24,7 @@ package org.caosdb.server.transaction;
 
 import java.util.List;
 import java.util.logging.LogRecord;
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.transaction.InsertLogRecord;
 
@@ -38,7 +38,7 @@ public class InsertLogRecordTransaction implements TransactionInterface {
 
   @Override
   public void execute() throws Exception {
-    final Access access = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
+    final Access access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
     try {
       execute(new InsertLogRecord(this.toBeFlushed), access);
     } finally {
diff --git a/src/main/java/org/caosdb/server/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java
index 087bc5a3d8b3268f308adda85b26253b25d8e4dc..19f840e92a1a6812e2ae2610ed3ace23b7e3cee7 100644
--- a/src/main/java/org/caosdb/server/transaction/Retrieve.java
+++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java
@@ -24,7 +24,7 @@ package org.caosdb.server.transaction;
 
 import org.apache.shiro.authz.AuthorizationException;
 import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntity;
+import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.container.RetrieveContainer;
 import org.caosdb.server.entity.xml.SetFieldStrategy;
@@ -48,7 +48,7 @@ public class Retrieve extends Transaction<RetrieveContainer> {
   @Override
   protected void init() throws Exception {
     // acquire weak access
-    setAccess(getMonitor().acquiredWeakAccess(this));
+    setAccess(getAccessManager().acquireReadAccess(this));
 
     // resolve names
     {
@@ -142,7 +142,7 @@ public class Retrieve extends Transaction<RetrieveContainer> {
 
   private void retrieveFullEntities(final RetrieveContainer container, final Access access)
       throws Exception {
-    execute(new RetrieveFullEntity(container), access);
+    execute(new RetrieveFullEntityTransaction(container), access);
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java b/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java
index 49d850dbcb672767da33a7c855063b04e1099f29..7e6c527865566795c397377409b1fc63020ca1dd 100644
--- a/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java
@@ -27,7 +27,7 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import org.apache.shiro.SecurityUtils;
 import org.caosdb.server.accessControl.ACMPermissions;
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.transaction.RetrieveLogRecord;
 
@@ -60,7 +60,7 @@ public class RetrieveLogRecordTransaction implements TransactionInterface {
   @Override
   public void execute() throws Exception {
     SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_SERVERLOGS);
-    final Access access = DatabaseMonitor.getInstance().acquiredWeakAccess(this);
+    final Access access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
     try {
       this.logRecords =
           execute(new RetrieveLogRecord(this.logger, this.level, this.message), access)
diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveSparseEntityByPath.java b/src/main/java/org/caosdb/server/transaction/RetrieveSparseEntityByPath.java
index d82985fe8060b3919d48c17a6bbd2994fc92c5bf..520787105388d8f1408c2d3b05090c761c8f687c 100644
--- a/src/main/java/org/caosdb/server/transaction/RetrieveSparseEntityByPath.java
+++ b/src/main/java/org/caosdb/server/transaction/RetrieveSparseEntityByPath.java
@@ -41,7 +41,7 @@ public class RetrieveSparseEntityByPath extends Transaction<TransactionContainer
   @Override
   protected void init() throws Exception {
     // acquire weak access
-    setAccess(getMonitor().acquiredWeakAccess(this));
+    setAccess(getAccessManager().acquireReadAccess(this));
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java
index b0f8710575375f82f0a7290c04ae8a7392a40af0..97ed4954b93c55830d1bed5f74bad6e157e70776 100644
--- a/src/main/java/org/caosdb/server/transaction/Transaction.java
+++ b/src/main/java/org/caosdb/server/transaction/Transaction.java
@@ -29,7 +29,7 @@ import java.util.List;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.datetime.UTCDateTime;
 import org.caosdb.server.accessControl.Principal;
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.transaction.InsertTransactionHistory;
 import org.caosdb.server.database.exceptions.TransactionException;
@@ -62,7 +62,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     return getContainer().getTransactionBenchmark();
   }
 
-  private static final DatabaseMonitor monitor = DatabaseMonitor.getInstance();
+  private static final DatabaseAccessManager monitor = DatabaseAccessManager.getInstance();
 
   public static final String CLEAN_UP = "TransactionCleanUp";
   private final C container;
@@ -78,7 +78,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
     if (o != null) acceptObserver(o);
   }
 
-  public static DatabaseMonitor getMonitor() {
+  public static DatabaseAccessManager getAccessManager() {
     return monitor;
   }
 
@@ -222,8 +222,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
       String realm = ((Principal) getTransactor().getPrincipal()).getRealm();
       String username = ((Principal) getTransactor().getPrincipal()).getUsername();
       execute(
-          new InsertTransactionHistory(
-              getContainer(), this.getClass().getSimpleName(), realm, username, getTimestamp()),
+          new InsertTransactionHistory(getContainer(), realm, username, getTimestamp()),
           getAccess());
     }
   }
diff --git a/src/main/java/org/caosdb/server/transaction/Update.java b/src/main/java/org/caosdb/server/transaction/Update.java
deleted file mode 100644
index 894728ccb7ed6c1e3eb72d40d6ea821e9e92ee68..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/transaction/Update.java
+++ /dev/null
@@ -1,407 +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) 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/>.
- *
- * ** end header
- */
-package org.caosdb.server.transaction;
-
-import com.google.common.base.Objects;
-import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashSet;
-import java.util.Set;
-import org.apache.shiro.authz.AuthorizationException;
-import org.caosdb.server.CaosDBException;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.transaction.RetrieveFullEntity;
-import org.caosdb.server.database.backend.transaction.UpdateEntity;
-import org.caosdb.server.entity.Entity;
-import org.caosdb.server.entity.EntityInterface;
-import org.caosdb.server.entity.FileProperties;
-import org.caosdb.server.entity.RetrieveEntity;
-import org.caosdb.server.entity.container.TransactionContainer;
-import org.caosdb.server.entity.container.UpdateContainer;
-import org.caosdb.server.entity.wrapper.Parent;
-import org.caosdb.server.entity.wrapper.Property;
-import org.caosdb.server.permissions.EntityPermission;
-import org.caosdb.server.permissions.Permission;
-import org.caosdb.server.utils.EntityStatus;
-import org.caosdb.server.utils.ServerMessages;
-
-public class Update extends WriteTransaction<UpdateContainer> {
-
-  public Update(final UpdateContainer container) {
-    super(container);
-  }
-
-  @Override
-  protected void preCheck() throws Exception {
-    for (final EntityInterface e : getContainer()) {
-      if (e.getEntityStatus() == EntityStatus.QUALIFIED) {
-        org.caosdb.server.entity.UpdateEntity newEntity = (org.caosdb.server.entity.UpdateEntity) e;
-        try {
-          checkPermissions(newEntity, deriveUpdate(newEntity, newEntity.getOriginal()));
-        } catch (final AuthorizationException exc) {
-          newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
-          newEntity.addError(ServerMessages.AUTHORIZATION_ERROR);
-          newEntity.addInfo(exc.getMessage());
-        }
-      }
-    }
-  }
-
-  @Override
-  protected void init() throws Exception {
-    // collect all ids of the entities which are to be updated.
-    final TransactionContainer oldContainer = new TransactionContainer();
-    for (final EntityInterface entity : getContainer()) {
-      // entity has no id -> it cannot be updated.
-      if (!entity.hasId()) {
-        entity.addError(ServerMessages.ENTITY_HAS_NO_ID);
-        entity.setEntityStatus(EntityStatus.UNQUALIFIED);
-        continue;
-      }
-
-      // add entity with this id to the updateContainer
-      if (entity.getEntityStatus() == EntityStatus.QUALIFIED) {
-        final Entity oldEntity = new RetrieveEntity(entity.getId());
-        oldContainer.add(oldEntity);
-      }
-    }
-
-    // allocate strong (write) access. Only one thread can do this
-    // at a time. But weak access can still be acquired by other
-    // thread until the allocated strong access is actually
-    // acquired.
-    setAccess(getMonitor().allocateStrongAccess(this));
-
-    // retrieve a container which contains all id of those entities
-    // which are to be updated.
-    execute(new RetrieveFullEntity(oldContainer), getAccess());
-
-    // Check if any updates are to be processed.
-    for (final EntityInterface e : getContainer()) {
-      org.caosdb.server.entity.UpdateEntity newEntity = (org.caosdb.server.entity.UpdateEntity) e;
-      if (newEntity.getEntityStatus() == EntityStatus.QUALIFIED) {
-        innerLoop:
-        for (final EntityInterface oldEntity : oldContainer) {
-          if (oldEntity.getId().equals(newEntity.getId())) {
-            if (oldEntity.getEntityStatus() == EntityStatus.NONEXISTENT) {
-              newEntity.addError(ServerMessages.ENTITY_DOES_NOT_EXIST);
-              newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
-            } else {
-              // dereference files (upload only)
-              if (newEntity.hasFileProperties() && !newEntity.getFileProperties().isPickupable()) {
-
-                if (newEntity.getFileProperties().getTmpIdentifyer() != null) {
-                  // get file by tmpIdentifier
-                  final FileProperties f =
-                      getContainer()
-                          .getFiles()
-                          .get(newEntity.getFileProperties().getTmpIdentifyer());
-
-                  // is it there?
-                  if (f != null) {
-                    newEntity.getFileProperties().setFile(f.getFile());
-
-                    // has it a thumbnail?
-                    if (f.getThumbnail() != null) {
-                      newEntity.getFileProperties().setThumbnail(f.getThumbnail());
-                    } else {
-                      final FileProperties thumbnail =
-                          getContainer()
-                              .getFiles()
-                              .get(newEntity.getFileProperties().getTmpIdentifyer() + ".thumbnail");
-                      if (thumbnail != null) {
-                        newEntity.getFileProperties().setThumbnail(thumbnail.getFile());
-                      } else {
-                        newEntity.addWarning(ServerMessages.THUMBNAIL_HAS_NOT_BEEN_UPLOAED);
-                      }
-                    }
-                  } else {
-                    newEntity.addError(ServerMessages.FILE_HAS_NOT_BEEN_UPLOAED);
-                    newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
-                  }
-
-                } else {
-                  // in case no file has been uploaded,
-                  // the file is expected to stay
-                  // unchanged. Therefore we let the file
-                  // object point to the original file in
-                  // the file system.
-                  newEntity
-                      .getFileProperties()
-                      .setFile(oldEntity.getFileProperties().retrieveFromFileSystem());
-                }
-              }
-
-              newEntity.setOriginal(oldEntity);
-            }
-            break innerLoop;
-          }
-        }
-      }
-    }
-
-    // make schedule of those entities which are to be updated.
-    makeSchedule();
-  }
-
-  @Override
-  protected void postCheck() {}
-
-  @Override
-  protected void postTransaction() {}
-
-  @Override
-  public void transaction() throws Exception {
-    // write new entities to database
-    update(getContainer(), getAccess());
-  }
-
-  private void update(final TransactionContainer container, final Access access) throws Exception {
-    if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
-      execute(new UpdateEntity(container), access);
-    }
-  }
-
-  /** Check if the user has all permissions */
-  public void checkPermissions(final EntityInterface entity, final Set<Permission> permissions) {
-    for (final Permission p : permissions) {
-      entity.checkPermission(p);
-    }
-  }
-
-  /**
-   * The entity is set to VALID iff there are no updates to be processed. The entity is set to
-   * QUALIFIED otherwise.
-   *
-   * @param newEntity
-   * @param oldEntity
-   * @throws CaosDBException
-   * @throws IOException
-   * @throws NoSuchAlgorithmException
-   */
-  public static HashSet<Permission> deriveUpdate(
-      final EntityInterface newEntity, final EntityInterface oldEntity)
-      throws NoSuchAlgorithmException, IOException, CaosDBException {
-    final HashSet<Permission> needPermissions = new HashSet<>();
-    boolean updatetable = false;
-
-    // new acl?
-    if (newEntity.hasEntityACL() && !newEntity.getEntityACL().equals(oldEntity.getEntityACL())) {
-      oldEntity.checkPermission(EntityPermission.EDIT_ACL);
-      if (!newEntity
-          .getEntityACL()
-          .getPriorityEntityACL()
-          .equals(oldEntity.getEntityACL().getPriorityEntityACL())) {
-        // priority acl is to be changed?
-        oldEntity.checkPermission(Permission.EDIT_PRIORITY_ACL);
-      }
-      updatetable = true;
-    } else if (!newEntity.hasEntityACL()) {
-      newEntity.setEntityACL(oldEntity.getEntityACL());
-    }
-
-    // new query template definition?
-    if (!Objects.equal(
-        newEntity.getQueryTemplateDefinition(), oldEntity.getQueryTemplateDefinition())) {
-      needPermissions.add(EntityPermission.UPDATE_QUERY_TEMPLATE_DEFINITION);
-      updatetable = true;
-    }
-
-    // new datatype?
-    if (newEntity.hasDatatype()
-            && oldEntity.hasDatatype()
-            && !newEntity.getDatatype().equals(oldEntity.getDatatype())
-        || newEntity.hasDatatype() ^ oldEntity.hasDatatype()) {
-      needPermissions.add(EntityPermission.UPDATE_DATA_TYPE);
-      updatetable = true;
-    }
-
-    // entity role
-    if (newEntity.hasRole()
-            && oldEntity.hasRole()
-            && !newEntity.getRole().equals(oldEntity.getRole())
-        || newEntity.hasRole() ^ oldEntity.hasRole()) {
-      needPermissions.add(EntityPermission.UPDATE_ROLE);
-      updatetable = true;
-    }
-
-    // entity value
-    if (newEntity.hasValue()
-            && oldEntity.hasValue()
-            && !newEntity.getValue().equals(oldEntity.getValue())
-        || newEntity.hasValue() ^ oldEntity.hasValue()) {
-      needPermissions.add(EntityPermission.UPDATE_VALUE);
-      updatetable = true;
-    }
-
-    // entity name
-    if (newEntity.hasName()
-            && oldEntity.hasName()
-            && !newEntity.getName().equals(oldEntity.getName())
-        || newEntity.hasName() ^ oldEntity.hasName()) {
-      needPermissions.add(EntityPermission.UPDATE_NAME);
-      updatetable = true;
-    }
-
-    // entity description
-    if (newEntity.hasDescription()
-            && oldEntity.hasDescription()
-            && !newEntity.getDescription().equals(oldEntity.getDescription())
-        || newEntity.hasDescription() ^ oldEntity.hasDescription()) {
-      needPermissions.add(EntityPermission.UPDATE_DESCRIPTION);
-      updatetable = true;
-    }
-
-    // file properties
-    if (newEntity.hasFileProperties() || oldEntity.hasFileProperties()) {
-      if (newEntity.hasFileProperties() && !oldEntity.hasFileProperties()) {
-        // add a file
-        needPermissions.add(EntityPermission.UPDATE_ADD_FILE);
-        updatetable = true;
-      } else if (!newEntity.hasFileProperties() && oldEntity.hasFileProperties()) {
-        // remove a file
-        needPermissions.add(EntityPermission.UPDATE_REMOVE_FILE);
-        updatetable = true;
-      } else {
-        // change file
-        final FileProperties newFile = newEntity.getFileProperties();
-        final FileProperties oldFile = oldEntity.getFileProperties();
-
-        // file path
-        if (newFile.hasPath() && oldFile.hasPath() && !newFile.getPath().equals(oldFile.getPath())
-            || newFile.hasPath() ^ oldFile.hasPath()) {
-          // this means, the location of the file is to be changed
-          needPermissions.add(EntityPermission.UPDATE_MOVE_FILE);
-          updatetable = true;
-        }
-
-        // change actual file (different byte code!)
-        if (!oldFile.retrieveFromFileSystem().equals(newFile.getFile())) {
-          // CHANGE A FILE is like REMOVE AND ADD
-          needPermissions.add(EntityPermission.UPDATE_REMOVE_FILE);
-          needPermissions.add(EntityPermission.UPDATE_ADD_FILE);
-          updatetable = true;
-        }
-      }
-    }
-
-    // properties
-    outerLoop:
-    for (final EntityInterface newProperty : newEntity.getProperties()) {
-
-      // find corresponding oldProperty for this new property and make a
-      // diff.
-      if (newProperty.hasId()) {
-        for (final EntityInterface oldProperty : oldEntity.getProperties()) {
-          if (newProperty.getId().equals(oldProperty.getId())) {
-            // do not check again.
-            oldEntity.getProperties().remove(oldProperty);
-
-            if (((Property) oldProperty).getPIdx() != ((Property) newProperty).getPIdx()) {
-              // change order of properties
-              needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY);
-              needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY);
-              updatetable = true;
-            }
-
-            deriveUpdate(newProperty, oldProperty);
-            if (newProperty.getEntityStatus() == EntityStatus.QUALIFIED) {
-              needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY);
-              needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY);
-              updatetable = true;
-            }
-
-            continue outerLoop;
-          }
-        }
-      } else {
-        newProperty.setEntityStatus(EntityStatus.UNQUALIFIED);
-        newProperty.addError(ServerMessages.ENTITY_HAS_NO_ID);
-        newProperty.addInfo("On updates, allways specify the id not just the name.");
-        newEntity.addError(ServerMessages.ENTITY_HAS_UNQUALIFIED_PROPERTIES);
-        newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
-        return needPermissions;
-      }
-
-      // no corresponding property found -> this property is new.
-      needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY);
-      updatetable = true;
-    }
-
-    // some old properties left (and not matched with new ones) -> there are
-    // properties to be deleted.
-    if (!oldEntity.getProperties().isEmpty()) {
-      needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY);
-      updatetable = true;
-    }
-
-    // update parents
-    outerLoop:
-    for (final Parent newParent : newEntity.getParents()) {
-
-      // find corresponding oldParent
-      if (newParent.hasId()) {
-        for (final Parent oldParent : oldEntity.getParents()) {
-          if (oldParent.getId().equals(newParent.getId())) {
-            // still there! do not check this one again
-            oldEntity.getParents().remove(oldParent);
-            continue outerLoop;
-          }
-        }
-      } else {
-        newParent.setEntityStatus(EntityStatus.UNQUALIFIED);
-        newParent.addError(ServerMessages.ENTITY_HAS_NO_ID);
-        newParent.addInfo("On updates, allways specify the id not just the name.");
-        newEntity.addError(ServerMessages.ENTITY_HAS_UNQUALIFIED_PROPERTIES);
-        newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
-        return needPermissions;
-      }
-
-      // no corresponding parent found -> this parent is new.
-      needPermissions.add(EntityPermission.UPDATE_ADD_PARENT);
-      updatetable = true;
-    }
-
-    // some old parents left (and not matched with new ones) -> there are
-    // parents to be deleted.
-    if (!oldEntity.getParents().isEmpty()) {
-      needPermissions.add(EntityPermission.UPDATE_REMOVE_PARENT);
-      updatetable = true;
-    }
-
-    // nothing to be updated
-    if (!updatetable) {
-      newEntity.setEntityStatus(EntityStatus.VALID);
-      newEntity.addInfo("Nothing to be updated.");
-    }
-
-    return needPermissions;
-  }
-
-  @Override
-  public boolean logHistory() {
-    return true;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
index 04c3834caad3264d6c1b8e5c99cf5b17a513e403..abda78b6feeb0de7104de5365877b28d474058fe 100644
--- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.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,13 +24,53 @@
  */
 package org.caosdb.server.transaction;
 
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.AuthorizationException;
+import org.caosdb.server.CaosDBException;
+import org.caosdb.server.database.BackendTransaction;
+import org.caosdb.server.database.access.Access;
+import org.caosdb.server.database.backend.transaction.DeleteEntityTransaction;
+import org.caosdb.server.database.backend.transaction.InsertEntityTransaction;
+import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction;
+import org.caosdb.server.database.backend.transaction.UpdateEntityTransaction;
 import org.caosdb.server.database.misc.RollBackHandler;
+import org.caosdb.server.entity.DeleteEntity;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.FileProperties;
+import org.caosdb.server.entity.InsertEntity;
+import org.caosdb.server.entity.RetrieveEntity;
+import org.caosdb.server.entity.UpdateEntity;
 import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.entity.container.WritableContainer;
+import org.caosdb.server.entity.wrapper.Parent;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.jobs.Schedule;
+import org.caosdb.server.permissions.EntityACL;
+import org.caosdb.server.permissions.EntityPermission;
+import org.caosdb.server.permissions.Permission;
+import org.caosdb.server.query.Query;
+import org.caosdb.server.utils.EntityStatus;
+import org.caosdb.server.utils.ServerMessages;
 
-public abstract class WriteTransaction<C extends TransactionContainer> extends Transaction<C> {
+/**
+ * This class is responsible for inserting, updating and deleting entities which are held in the
+ * {@link TransactionContainer}.
+ *
+ * <p>This class initializes and runs the {@link Schedule} of {@link Job}s, calls the {@link
+ * BackendTransaction}s for each entity, and handles exceptions, roll-back (if necessary) and clean
+ * up afterwards.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public class WriteTransaction extends Transaction<WritableContainer>
+    implements WriteTransactionInterface {
 
-  protected WriteTransaction(final C container) {
+  public WriteTransaction(final WritableContainer container) {
     super(container);
   }
 
@@ -36,12 +78,13 @@ public abstract class WriteTransaction<C extends TransactionContainer> extends T
   protected final void preTransaction() throws InterruptedException {
     // acquire strong access. No other thread can have access until
     // it this strong access is released.
-    setAccess(getMonitor().acquireStrongAccess(this));
+    setAccess(getAccessManager().acquireWriteAccess(this));
   }
 
   @Override
   protected void commit() throws Exception {
     getAccess().commit();
+    Query.clearCache();
   }
 
   @Override
@@ -51,6 +94,18 @@ public abstract class WriteTransaction<C extends TransactionContainer> extends T
     super.rollBack();
   }
 
+  private void update(final TransactionContainer container, final Access access) throws Exception {
+    execute(new UpdateEntityTransaction(container), access);
+  }
+
+  private void insert(final TransactionContainer container, final Access access) throws Exception {
+    execute(new InsertEntityTransaction(container), access);
+  }
+
+  private void delete(final TransactionContainer container, final Access access) throws Exception {
+    execute(new DeleteEntityTransaction(container), access);
+  }
+
   @Override
   protected final void cleanUp() {
     // release strong access. Other threads can acquire or allocate
@@ -65,6 +120,437 @@ public abstract class WriteTransaction<C extends TransactionContainer> extends T
     }
   }
 
+  @Override
+  public boolean logHistory() {
+    return true;
+  }
+
+  @Override
+  protected void init() throws Exception {
+    // collect all ids of the entities which are to be updated.
+    final TransactionContainer oldContainer = new TransactionContainer();
+    // collect all ids of the entities which are to be deleted.
+    final TransactionContainer deleteContainer = new TransactionContainer();
+    for (final EntityInterface entity : getContainer()) {
+      if (entity instanceof UpdateEntity) {
+        // entity has no id -> it cannot be updated.
+        if (!entity.hasId()) {
+          entity.addError(ServerMessages.ENTITY_HAS_NO_ID);
+          entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+          continue;
+        }
+
+        // add entity with this id to the updateContainer
+        if (entity.getEntityStatus() == EntityStatus.QUALIFIED) {
+          final EntityInterface oldEntity = new RetrieveEntity(entity.getId());
+          oldContainer.add(oldEntity);
+        }
+      } else if (entity instanceof DeleteEntity) {
+        deleteContainer.add(entity);
+      }
+    }
+
+    // Reserve write access. Only one thread can do this at a time. But read access can still be
+    // acquired by other threads until the reserved write access is actually acquired.
+    setAccess(getAccessManager().reserveWriteAccess(this));
+
+    // Retrieve a container which contains all IDs of those entities
+    // which are to be updated.
+    execute(new RetrieveFullEntityTransaction(oldContainer), getAccess());
+
+    // Retrieve all entities which are to be deleted.
+    execute(new RetrieveFullEntityTransaction(deleteContainer), getAccess());
+
+    // Check if any updates are to be processed.
+    for (final EntityInterface entity : getContainer()) {
+      if (entity instanceof UpdateEntity && entity.getEntityStatus() == EntityStatus.QUALIFIED) {
+        innerLoop:
+        for (final EntityInterface oldEntity : oldContainer) {
+          if (oldEntity.getId().equals(entity.getId())) {
+            if (oldEntity.getEntityStatus() == EntityStatus.NONEXISTENT) {
+              entity.addError(ServerMessages.ENTITY_DOES_NOT_EXIST);
+              entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+            } else {
+              // dereference files (upload only)
+              if (entity.hasFileProperties()
+                  && entity.getFileProperties().hasTmpIdentifier()
+                  && !entity.getFileProperties().isPickupable()) {
+
+                // get file by tmpIdentifier
+                final FileProperties f =
+                    getContainer().getFiles().get(entity.getFileProperties().getTmpIdentifyer());
+
+                // is it there?
+                if (f != null) {
+                  entity.getFileProperties().setFile(f.getFile());
+
+                  // has it a thumbnail?
+                  if (f.getThumbnail() != null) {
+                    entity.getFileProperties().setThumbnail(f.getThumbnail());
+                  } else {
+                    final FileProperties thumbnail =
+                        getContainer()
+                            .getFiles()
+                            .get(entity.getFileProperties().getTmpIdentifyer() + ".thumbnail");
+                    if (thumbnail != null) {
+                      entity.getFileProperties().setThumbnail(thumbnail.getFile());
+                    } else {
+                      entity.addWarning(ServerMessages.THUMBNAIL_HAS_NOT_BEEN_UPLOAED);
+                    }
+                  }
+                } else {
+                  entity.addError(ServerMessages.FILE_HAS_NOT_BEEN_UPLOAED);
+                  entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+                }
+
+              } else if (entity.hasFileProperties()
+                  && !entity.getFileProperties().hasTmpIdentifier()) {
+                // in case no file has been uploaded,
+                // the file is expected to stay
+                // unchanged. Therefore we let the file
+                // object point to the original file in
+                // the file system.
+                entity
+                    .getFileProperties()
+                    .setFile(oldEntity.getFileProperties().retrieveFromFileSystem());
+              }
+
+              try {
+                checkPermissions(entity, deriveUpdate(entity, oldEntity));
+              } catch (final AuthorizationException exc) {
+                entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+                entity.addError(ServerMessages.AUTHORIZATION_ERROR);
+                entity.addInfo(exc.getMessage());
+              }
+            }
+            break innerLoop;
+          }
+        }
+      } else if (entity instanceof DeleteEntity) {
+        if (entity.getEntityStatus() == EntityStatus.NONEXISTENT) {
+          entity.addError(ServerMessages.ENTITY_DOES_NOT_EXIST);
+          continue;
+        }
+
+        // check permissions
+        try {
+          entity.checkPermission(EntityPermission.DELETE);
+        } catch (final AuthorizationException exc) {
+          entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+          entity.addError(ServerMessages.AUTHORIZATION_ERROR);
+          entity.addInfo(exc.getMessage());
+        }
+
+        // no standard entities are to be deleted.
+        if (entity.hasId() && entity.getId() < 100) {
+          entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+          entity.addInfo("This entity cannot be deleted");
+        }
+
+      } else if (entity instanceof InsertEntity
+          && entity.hasFileProperties()
+          && entity.getFileProperties().hasTmpIdentifier()
+          && !entity.getFileProperties().isPickupable()) {
+        // dereference files (file upload only)
+        final FileProperties f =
+            getContainer().getFiles().get(entity.getFileProperties().getTmpIdentifyer());
+        if (f != null) {
+          entity.getFileProperties().setFile(f.getFile());
+          if (f.getThumbnail() != null) {
+            entity.getFileProperties().setThumbnail(f.getThumbnail());
+          } else {
+            final FileProperties thumbnail =
+                getContainer()
+                    .getFiles()
+                    .get(entity.getFileProperties().getTmpIdentifyer() + ".thumbnail");
+            if (thumbnail != null) {
+              entity.getFileProperties().setThumbnail(thumbnail.getFile());
+            } else {
+              entity.addWarning(ServerMessages.THUMBNAIL_HAS_NOT_BEEN_UPLOAED);
+            }
+          }
+        } else {
+          entity.addError(ServerMessages.FILE_HAS_NOT_BEEN_UPLOAED);
+          entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+        }
+      }
+    }
+
+    makeSchedule();
+  }
+
+  /** Check if the user has all permissions */
+  public static void checkPermissions(
+      final EntityInterface entity, final Set<Permission> permissions) {
+    for (final Permission p : permissions) {
+      entity.checkPermission(p);
+    }
+  }
+
+  @Override
+  protected void preCheck() throws InterruptedException, Exception {
+    for (final EntityInterface entity : getContainer()) {
+      // set default EntityACL if none present
+      if (entity.getEntityACL() == null) {
+        entity.setEntityACL(EntityACL.getOwnerACLFor(SecurityUtils.getSubject()));
+      }
+    }
+  }
+
+  @Override
+  protected void postCheck() {}
+
+  @Override
+  protected void transaction() throws Exception {
+    if (getContainer().getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
+
+      // split up the TransactionContainer into three containers, one for each
+      // type of writing transaction.
+      TransactionContainer inserts = new TransactionContainer();
+      TransactionContainer updates = new TransactionContainer();
+      TransactionContainer deletes = new TransactionContainer();
+      for (EntityInterface entity : getContainer()) {
+        if (entity instanceof InsertEntity) {
+          inserts.add(entity);
+        } else if (entity instanceof UpdateEntity) {
+          updates.add(entity);
+        } else if (entity instanceof DeleteEntity) {
+          deletes.add(entity);
+        }
+      }
+
+      // The order is crucial: 1) Update-entities may reference new entities which
+      // have to be inserted first. 2) Update-Entities which (originally)
+      // reference old entities which are to be deleted, have be updated before
+      // the others can be deleted.
+      insert(inserts, getAccess());
+      update(updates, getAccess());
+      delete(deletes, getAccess());
+    }
+  }
+
+  @Override
+  protected void postTransaction() throws Exception {
+    // set entityStatus to DELETED and add deletion info message for deleted entities.
+    if (getContainer().getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
+      for (final EntityInterface entity : getContainer()) {
+        if (entity instanceof DeleteEntity && entity.getEntityStatus() == EntityStatus.VALID) {
+          entity.setEntityStatus(EntityStatus.DELETED);
+          entity.addInfo(ServerMessages.ENTITY_HAS_BEEN_DELETED_SUCCESSFULLY);
+        }
+      }
+    }
+  }
+
+  /**
+   * The entity is set to VALID iff there are no updates to be processed. The entity is set to
+   * QUALIFIED otherwise.
+   *
+   * @param newEntity
+   * @param oldEntity
+   * @throws CaosDBException
+   * @throws IOException
+   * @throws NoSuchAlgorithmException
+   */
+  public static HashSet<Permission> deriveUpdate(
+      final EntityInterface newEntity, final EntityInterface oldEntity)
+      throws NoSuchAlgorithmException, IOException, CaosDBException {
+    final HashSet<Permission> needPermissions = new HashSet<>();
+    boolean updatetable = false;
+
+    // new acl?
+    if (newEntity.hasEntityACL() && !newEntity.getEntityACL().equals(oldEntity.getEntityACL())) {
+      oldEntity.checkPermission(EntityPermission.EDIT_ACL);
+      if (!newEntity
+          .getEntityACL()
+          .getPriorityEntityACL()
+          .equals(oldEntity.getEntityACL().getPriorityEntityACL())) {
+        // priority acl is to be changed?
+        oldEntity.checkPermission(Permission.EDIT_PRIORITY_ACL);
+      }
+      updatetable = true;
+    } else if (!newEntity.hasEntityACL()) {
+      newEntity.setEntityACL(oldEntity.getEntityACL());
+    }
+
+    // new query template definition?
+    if (!Objects.equals(
+        newEntity.getQueryTemplateDefinition(), oldEntity.getQueryTemplateDefinition())) {
+      needPermissions.add(EntityPermission.UPDATE_QUERY_TEMPLATE_DEFINITION);
+      updatetable = true;
+    }
+
+    // new datatype?
+    if (newEntity.hasDatatype()
+            && oldEntity.hasDatatype()
+            && !newEntity.getDatatype().equals(oldEntity.getDatatype())
+        || newEntity.hasDatatype() ^ oldEntity.hasDatatype()) {
+      needPermissions.add(EntityPermission.UPDATE_DATA_TYPE);
+      updatetable = true;
+    }
+
+    // entity role
+    if (newEntity.hasRole()
+            && oldEntity.hasRole()
+            && !newEntity.getRole().equals(oldEntity.getRole())
+        || newEntity.hasRole() ^ oldEntity.hasRole()) {
+      needPermissions.add(EntityPermission.UPDATE_ROLE);
+      updatetable = true;
+    }
+
+    // entity value
+    if (newEntity.hasValue()
+            && oldEntity.hasValue()
+            && !newEntity.getValue().equals(oldEntity.getValue())
+        || newEntity.hasValue() ^ oldEntity.hasValue()) {
+      needPermissions.add(EntityPermission.UPDATE_VALUE);
+      updatetable = true;
+    }
+
+    // entity name
+    if (newEntity.hasName()
+            && oldEntity.hasName()
+            && !newEntity.getName().equals(oldEntity.getName())
+        || newEntity.hasName() ^ oldEntity.hasName()) {
+      needPermissions.add(EntityPermission.UPDATE_NAME);
+      updatetable = true;
+    }
+
+    // entity description
+    if (newEntity.hasDescription()
+            && oldEntity.hasDescription()
+            && !newEntity.getDescription().equals(oldEntity.getDescription())
+        || newEntity.hasDescription() ^ oldEntity.hasDescription()) {
+      needPermissions.add(EntityPermission.UPDATE_DESCRIPTION);
+      updatetable = true;
+    }
+
+    // file properties
+    if (newEntity.hasFileProperties() || oldEntity.hasFileProperties()) {
+      if (newEntity.hasFileProperties() && !oldEntity.hasFileProperties()) {
+        // add a file
+        needPermissions.add(EntityPermission.UPDATE_ADD_FILE);
+        updatetable = true;
+      } else if (!newEntity.hasFileProperties() && oldEntity.hasFileProperties()) {
+        // remove a file
+        needPermissions.add(EntityPermission.UPDATE_REMOVE_FILE);
+        updatetable = true;
+      } else {
+        // change file
+        final FileProperties newFile = newEntity.getFileProperties();
+        final FileProperties oldFile = oldEntity.getFileProperties();
+
+        // file path
+        if (newFile.hasPath() && oldFile.hasPath() && !newFile.getPath().equals(oldFile.getPath())
+            || newFile.hasPath() ^ oldFile.hasPath()) {
+          // this means, the location of the file is to be changed
+          needPermissions.add(EntityPermission.UPDATE_MOVE_FILE);
+          updatetable = true;
+        }
+
+        // change actual file (different byte code!)
+        if (!oldFile.retrieveFromFileSystem().equals(newFile.getFile())) {
+          // CHANGE A FILE is like REMOVE AND ADD
+          needPermissions.add(EntityPermission.UPDATE_REMOVE_FILE);
+          needPermissions.add(EntityPermission.UPDATE_ADD_FILE);
+          updatetable = true;
+        }
+      }
+    }
+
+    // properties
+    outerLoop:
+    for (final EntityInterface newProperty : newEntity.getProperties()) {
+
+      // find corresponding oldProperty for this new property and make a
+      // diff.
+      if (newProperty.hasId()) {
+        for (final EntityInterface oldProperty : oldEntity.getProperties()) {
+          if (newProperty.getId().equals(oldProperty.getId())) {
+            // do not check again.
+            oldEntity.getProperties().remove(oldProperty);
+
+            if (((Property) oldProperty).getPIdx() != ((Property) newProperty).getPIdx()) {
+              // change order of properties
+              needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY);
+              needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY);
+              updatetable = true;
+            }
+
+            deriveUpdate(newProperty, oldProperty);
+            if (newProperty.getEntityStatus() == EntityStatus.QUALIFIED) {
+              needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY);
+              needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY);
+              updatetable = true;
+            }
+
+            continue outerLoop;
+          }
+        }
+      } else {
+        newProperty.setEntityStatus(EntityStatus.UNQUALIFIED);
+        newProperty.addError(ServerMessages.ENTITY_HAS_NO_ID);
+        newProperty.addInfo("On updates, allways specify the id not just the name.");
+        newEntity.addError(ServerMessages.ENTITY_HAS_UNQUALIFIED_PROPERTIES);
+        newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
+        return needPermissions;
+      }
+
+      // no corresponding property found -> this property is new.
+      needPermissions.add(EntityPermission.UPDATE_ADD_PROPERTY);
+      updatetable = true;
+    }
+
+    // some old properties left (and not matched with new ones) -> there are
+    // properties to be deleted.
+    if (!oldEntity.getProperties().isEmpty()) {
+      needPermissions.add(EntityPermission.UPDATE_REMOVE_PROPERTY);
+      updatetable = true;
+    }
+
+    // update parents
+    outerLoop:
+    for (final Parent newParent : newEntity.getParents()) {
+
+      // find corresponding oldParent
+      if (newParent.hasId()) {
+        for (final Parent oldParent : oldEntity.getParents()) {
+          if (oldParent.getId().equals(newParent.getId())) {
+            // still there! do not check this one again
+            oldEntity.getParents().remove(oldParent);
+            continue outerLoop;
+          }
+        }
+      } else {
+        newParent.setEntityStatus(EntityStatus.UNQUALIFIED);
+        newParent.addError(ServerMessages.ENTITY_HAS_NO_ID);
+        newParent.addInfo("On updates, allways specify the id not just the name.");
+        newEntity.addError(ServerMessages.ENTITY_HAS_UNQUALIFIED_PROPERTIES);
+        newEntity.setEntityStatus(EntityStatus.UNQUALIFIED);
+        return needPermissions;
+      }
+
+      // no corresponding parent found -> this parent is new.
+      needPermissions.add(EntityPermission.UPDATE_ADD_PARENT);
+      updatetable = true;
+    }
+
+    // some old parents left (and not matched with new ones) -> there are
+    // parents to be deleted.
+    if (!oldEntity.getParents().isEmpty()) {
+      needPermissions.add(EntityPermission.UPDATE_REMOVE_PARENT);
+      updatetable = true;
+    }
+
+    // nothing to be updated
+    if (!updatetable) {
+      newEntity.setEntityStatus(EntityStatus.VALID);
+      newEntity.addInfo("Nothing to be updated.");
+    }
+
+    return needPermissions;
+  }
+
   public String getSRID() {
     return getContainer().getRequestId();
   }
diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java b/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..165acb408776a720c6887d31b550cf57e3d6fa0c
--- /dev/null
+++ b/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java
@@ -0,0 +1,8 @@
+package org.caosdb.server.transaction;
+
+import org.caosdb.server.database.access.Access;
+
+public interface WriteTransactionInterface extends TransactionInterface {
+
+  public Access getAccess();
+}
diff --git a/src/main/java/org/caosdb/server/utils/EntityStatus.java b/src/main/java/org/caosdb/server/utils/EntityStatus.java
index ef8bdc101c61e216c9aa448d667b59dfa5107672..d6658ef12535bca15a6af7b1d16a4d3d8c0b5e58 100644
--- a/src/main/java/org/caosdb/server/utils/EntityStatus.java
+++ b/src/main/java/org/caosdb/server/utils/EntityStatus.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 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
@@ -23,17 +25,24 @@
 package org.caosdb.server.utils;
 
 /**
- * IGNORE - This instance must be ignored during further processing. <br>
- * VALID - This instance has a final ID and has been synchronized with the database. <br>
- * WRITTEN - This instance has been written successfully to the database. It has a final ID. <br>
- * QUALIFIED - This instance has a provisional or final ID, represents a well-formed entity, and any
- * referenced entities (by a reference property) have status QUALIFIED or VALID. QUALIFIED - This
- * instance has been checked and is not qualified to be processed further (i.e. to be inserted,
- * updated, deleted) DELETED - This instance has been deleted recently. CORRUPT - This instance has
- * been retrieved from the database, but though something turned out to be wrong with it.
- * NONEXISTENT - This instance has been called (via id or something) but it doesn't exist.
- *
- * @author Timm Fitschen
+ * The order of the EntityStatus values matters, in that everything later than "QUALIFIED" also
+ * counts as qualified.
+ *
+ * <p>
+ *
+ * <ul>
+ *   <li>IGNORE - This instance must be ignored during further processing.
+ *   <li>UNQUALIFIED - This instance has been checked and is not qualified to be processed further
+ *       (i.e. to be inserted, updated, deleted)
+ *   <li>DELETED - This instance has been deleted recently.
+ *   <li>NONEXISTENT - This instance has been called (via id or something) but it doesn't exist.
+ *   <li>QUALIFIED - This instance has a provisional or final ID, represents a well-formed entity,
+ *       and any referenced entities (by a reference property) have status QUALIFIED or VALID. Every
+ *       status after this one also counts as QUALIFIED.
+ *   <li>VALID - This instance has a final ID and has been synchronized with the database.
+ * </ul>
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
  */
 public enum EntityStatus {
   IGNORE,
diff --git a/src/main/java/org/caosdb/server/utils/Info.java b/src/main/java/org/caosdb/server/utils/Info.java
index 19f792cf625765924f02a7b734f2e57849584ffc..18ee09828006c0394dec315ca77483f8298ba86b 100644
--- a/src/main/java/org/caosdb/server/utils/Info.java
+++ b/src/main/java/org/caosdb/server/utils/Info.java
@@ -30,7 +30,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.FileSystem;
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.transaction.GetInfo;
 import org.caosdb.server.database.backend.transaction.SyncStats;
@@ -58,7 +58,7 @@ public class Info extends AbstractObservable implements Observer, TransactionInt
   }
 
   private Info() {
-    this.access = DatabaseMonitor.getInfoAccess(this);
+    this.access = DatabaseAccessManager.getInfoAccess(this);
     try {
       syncDatabase();
     } catch (final Exception exc) {
diff --git a/src/main/java/org/caosdb/server/utils/Initialization.java b/src/main/java/org/caosdb/server/utils/Initialization.java
index baae53804f57433aa58da1ae1b9c58060c3db160..cb1a36307928a72b9da95b48737196e569c43346 100644
--- a/src/main/java/org/caosdb/server/utils/Initialization.java
+++ b/src/main/java/org/caosdb/server/utils/Initialization.java
@@ -24,7 +24,7 @@
  */
 package org.caosdb.server.utils;
 
-import org.caosdb.server.database.DatabaseMonitor;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.transaction.TransactionInterface;
 
@@ -34,7 +34,7 @@ public final class Initialization implements TransactionInterface, AutoCloseable
   private static final Initialization instance = new Initialization();
 
   private Initialization() {
-    this.access = DatabaseMonitor.getInitAccess(this);
+    this.access = DatabaseAccessManager.getInitAccess(this);
   }
 
   public static final Initialization setUp() {
diff --git a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java
index 1aff4de329bf3f32ba599a449c101b7fbe30423f..375d6b5e7bbced84cec5bdcdee26a9e436f0215a 100644
--- a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java
+++ b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java
@@ -33,6 +33,7 @@ import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.ServerProperties;
 import org.caosdb.server.resource.AbstractCaosDBServerResource;
 import org.caosdb.server.resource.Webinterface;
+import org.restlet.Request;
 import org.restlet.data.Reference;
 
 /**
@@ -61,14 +62,19 @@ public class WebinterfaceUtils {
   private static final Map<String, WebinterfaceUtils> instances = new HashMap<>();
 
   /**
-   * Retrieve an instance of {@link WebinterfaceUtils} for the host. The instance can be shared with
-   * other callers.
+   * Retrieve an instance of {@link WebinterfaceUtils} for the request. The instance can be shared
+   * with other callers.
    *
-   * @param host
+   * @param request
    * @return a shared instance of {@link WebinterfaceUtils}.
    */
-  public static WebinterfaceUtils getInstance(Reference host) {
-    return getInstance(host.getHostIdentifier());
+  public static WebinterfaceUtils getInstance(Request request) {
+    String hostStr = request.getHostRef().getHostIdentifier();
+    String scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true);
+    if (scheme != null) {
+      hostStr = hostStr.replaceFirst("^" + request.getHostRef().getScheme(), scheme);
+    }
+    return getInstance(hostStr);
   }
 
   /**
diff --git a/src/test/java/org/caosdb/server/database/DatabaseMonitorTest.java b/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
similarity index 52%
rename from src/test/java/org/caosdb/server/database/DatabaseMonitorTest.java
rename to src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
index 554baec079ed17d332dff47f0afb57405913dc33..8e08131f3b18d9e4a495301ee570683facb4d9c5 100644
--- a/src/test/java/org/caosdb/server/database/DatabaseMonitorTest.java
+++ b/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
@@ -25,61 +25,61 @@ package org.caosdb.server.database;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class DatabaseMonitorTest {
-  public static final WeakAccessSemaphore wa = new WeakAccessSemaphore();
-  public static final StrongAccessLock sa = new StrongAccessLock(wa);
+public class DatabaseAccessManagerTest {
+  public static final ReadAccessSemaphore readAccess = new ReadAccessSemaphore();
+  public static final WriteAccessLock writeAccess = new WriteAccessLock(readAccess);
 
   /**
-   * Two read-, two write-threads. The read-threads request weak access, the write-threads request
-   * strong access.<br>
-   * The read-thread rt1 is started and gets weak access. Then the write-thread wt1 starts and
-   * allocates strong access.<br>
-   * A second read-thread rt2 starts and also gets weak access. A second write thread wt2 starts and
-   * requests allocation of strong access. It has to wait until wt2 releases it. <br>
-   * wt1 acquires strong access as soon as both read-threads released their weak-accesss.<br>
+   * Two read-, two write-threads. The read-threads request read access, the write-threads request
+   * write access.<br>
+   * The read-thread rt1 is started and gets read access. Then the write-thread wt1 starts and
+   * reserves write access.<br>
+   * A second read-thread rt2 starts and also gets read access. A second write thread wt2 starts and
+   * requests allocation of write access. It has to wait until wt2 releases it. <br>
+   * wt1 acquires write access as soon as both read-threads released their read-accesss.<br>
    *
    * @throws InterruptedException
    */
   @Test
   public void test1() throws InterruptedException {
     // first invoke a read thread
-    final ReadThread rt1 = new ReadThread();
-    rt1.start(); // paused, has acquired weak access now
+    final ReadThread rt1 = new ReadThread("rt1");
+    rt1.start(); // paused, has acquired read access now
 
     // invoke a write thread
-    final WriteThread wt1 = new WriteThread();
-    wt1.start(); // paused, has allocated strong access now
+    final WriteThread wt1 = new WriteThread("wt1");
+    wt1.start(); // paused, has reserved write access now
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
     // waiting means any processing
     Assert.assertEquals(wt1.getState(), Thread.State.WAITING);
 
-    final ReadThread rt2 = new ReadThread();
-    rt2.start(); // paused, has acquired a second weak access now
+    final ReadThread rt2 = new ReadThread("rt2");
+    rt2.start(); // paused, has acquired a second read access now
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
     synchronized (rt2) {
       rt2.notify();
     }
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
 
-    // rt2 was processed while wt1 has allocated but not yet acquired strong
-    // access. rt2 terminated after releasing its weak access.
+    // rt2 was processed while wt1 has reserved but not yet acquired write
+    // access. rt2 terminated after releasing its read access.
     Assert.assertEquals(rt2.getState(), Thread.State.TERMINATED);
 
-    final WriteThread wt2 = new WriteThread();
-    wt2.start(); // wt2 immediatelly allocates strong access and is block
+    final WriteThread wt2 = new WriteThread("wt2");
+    wt2.start(); // wt2 immediatelly reserves write access and is block
     // since wt1 already yields it.
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
-    // Assert.assertEquals(wt2.getState(), Thread.State.BLOCKED);
+    Assert.assertEquals(wt2.getState(), Thread.State.WAITING);
 
-    // wt1 request strong access.
+    // wt1 request write access.
     synchronized (wt1) {
       wt1.notify();
     }
@@ -92,10 +92,10 @@ public class DatabaseMonitorTest {
       rt1.notify();
     }
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
-    // rt1 was notified an terminated, releasing the weak acccess.
-    // so wt1 acquires strong access and pauses the second time.
+    // rt1 was notified an terminated, releasing the read acccess.
+    // so wt1 acquires write access and pauses the second time.
     Assert.assertEquals(rt1.getState(), Thread.State.TERMINATED);
 
     synchronized (wt1) {
@@ -104,16 +104,16 @@ public class DatabaseMonitorTest {
     synchronized (this) {
       this.wait(1000);
     }
-    // wt2 allocates strong access as wt1 released it now.
+    // wt2 reserves write access as wt1 released it now.
     Assert.assertEquals(wt1.getState(), Thread.State.TERMINATED);
     Assert.assertEquals(wt2.getState(), Thread.State.WAITING);
 
-    // while wt2 has not yet acquired strong access, rt3 acquires weak
+    // while wt2 has not yet acquired write access, rt3 acquires read
     // access
-    final ReadThread rt3 = new ReadThread();
+    final ReadThread rt3 = new ReadThread("rt3");
     rt3.start();
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
     Assert.assertEquals(rt3.getState(), Thread.State.WAITING);
 
@@ -121,24 +121,23 @@ public class DatabaseMonitorTest {
       wt2.notify();
     }
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
-    // Assert.assertEquals(wt2.getState(), Thread.State.BLOCKED);
+    Assert.assertEquals(wt2.getState(), Thread.State.WAITING);
 
     synchronized (rt3) {
       rt3.notify();
     }
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
     Assert.assertEquals(rt3.getState(), Thread.State.TERMINATED);
-    Assert.assertEquals(wt2.getState(), Thread.State.WAITING);
 
     synchronized (wt2) {
       wt2.notify();
     }
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
     Assert.assertEquals(wt2.getState(), Thread.State.TERMINATED);
   }
@@ -147,34 +146,35 @@ public class DatabaseMonitorTest {
   public void test2() throws InterruptedException {
 
     // start a write-thread
-    final WriteThread wt1 = new WriteThread();
+    final WriteThread wt1 = new WriteThread("wt1");
     wt1.start();
     // start another write-thread. It is blocked until wt1 releases the
-    // strong access.
-    final WriteThread wt2 = new WriteThread();
+    // write access.
+    final WriteThread wt2 = new WriteThread("wt2");
     wt2.start();
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
 
-    // and interrupt wt1 after allocating, but before acquiring the strong
+    // and interrupt wt1 after allocating, but before acquiring the write
     // access.
     wt1.interrupt();
 
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
     synchronized (wt2) {
       wt2.notify();
     }
 
     // read access should still be blocked.
-    final ReadThread rt1 = new ReadThread();
+    final ReadThread rt1 = new ReadThread("rt1");
     rt1.start();
 
     synchronized (this) {
-      this.wait(1000);
+      this.wait(500);
     }
+    Assert.assertEquals(rt1.getState(), Thread.State.WAITING);
     synchronized (wt2) {
       wt2.notify();
     }
@@ -184,23 +184,29 @@ public class DatabaseMonitorTest {
     @Override
     public void run() {
       try {
-        System.out.println("T" + currentThread().getId() + " request allocation sa");
-        sa.allocate();
-        System.out.println("T" + currentThread().getId() + " allocates sa");
-        pause();
-        sa.lockInterruptibly();
-        System.out.println("T" + currentThread().getId() + " acquires sa");
-        pause();
-        sa.unlock();
-        System.out.println("T" + currentThread().getId() + " releases sa");
+        System.out.println(currentThread().getName() + " request to reserve write access");
+        writeAccess.reserve();
+        System.out.println(currentThread().getName() + " has reserved write access ");
+        process("reserved write access");
+        writeAccess.lockInterruptibly();
+        System.out.println(currentThread().getName() + " acquires write access");
+        process("acquired write access");
+        writeAccess.unlock();
+        System.out.println(currentThread().getName() + " releases write access");
       } catch (final InterruptedException e) {
-        System.out.println("T" + currentThread().getId() + " was interrupted");
-        sa.unlock();
+        System.out.println(currentThread().getName() + " was interrupted");
+        writeAccess.unlock();
       }
     }
 
-    private synchronized void pause() throws InterruptedException {
+    private synchronized void process(String access) throws InterruptedException {
+      System.out.println(currentThread().getName() + " processes with " + access);
       this.wait();
+      System.out.println(currentThread().getName() + " is ready with " + access);
+    }
+
+    public WriteThread(String name) {
+      super(name);
     }
   }
 
@@ -208,19 +214,25 @@ public class DatabaseMonitorTest {
     @Override
     public void run() {
       try {
-        System.out.println("T" + currentThread().getId() + " requests wa");
-        wa.acquire();
-        System.out.println("T" + currentThread().getId() + " acquires wa");
+        System.out.println(currentThread().getName() + " requests read access");
+        readAccess.acquire();
+        System.out.println(currentThread().getName() + " acquires read access");
         pause();
-        wa.release();
-        System.out.println("T" + currentThread().getId() + " releases wa");
+        readAccess.release();
+        System.out.println(currentThread().getName() + " releases read access");
       } catch (final InterruptedException e) {
         e.printStackTrace();
       }
     }
 
     private synchronized void pause() throws InterruptedException {
+      System.out.println(currentThread().getName() + " waits ");
       this.wait();
+      System.out.println(currentThread().getName() + " goes on");
+    }
+
+    public ReadThread(String name) {
+      super(name);
     }
   }
 }
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
new file mode 100644
index 0000000000000000000000000000000000000000..93402bf0d06c487451d9219b4487f301eb855f49
--- /dev/null
+++ b/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java
@@ -0,0 +1,24 @@
+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/database/backend/transaction/RetrieveFullEntityTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java
index 52b4c951d939f097258ec40ed16b6fd99784f9bd..4b6429b73946110b0fd173de9c53a3b30fd83e18 100644
--- a/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java
+++ b/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java
@@ -40,8 +40,8 @@ public class RetrieveFullEntityTest {
 
   @Test
   public void testRetrieveSubEntities() {
-    RetrieveFullEntity r =
-        new RetrieveFullEntity(0) {
+    RetrieveFullEntityTransaction r =
+        new RetrieveFullEntityTransaction(0) {
 
           /** Mock-up */
           @Override
diff --git a/src/test/java/org/caosdb/server/entity/EntityTest.java b/src/test/java/org/caosdb/server/entity/EntityTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3bc85dfbf81ba865e2ebb543a28b72c5e468a485
--- /dev/null
+++ b/src/test/java/org/caosdb/server/entity/EntityTest.java
@@ -0,0 +1,59 @@
+/*
+ * ** header v3.0
+ * 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/>.
+ *
+ * ** end header
+ */
+package org.caosdb.server.entity;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.caosdb.server.datatype.IntegerDatatype;
+import org.junit.Test;
+
+public class EntityTest {
+
+  @Test
+  public void testIsReference() {
+    EntityInterface entity = new RetrieveEntity("test");
+    assertFalse(entity.isReference());
+
+    entity.setDatatype(new IntegerDatatype());
+    assertFalse(entity.isReference());
+
+    entity.setDatatype("Person");
+    assertTrue(entity.isReference());
+  }
+
+  @Test
+  public void testIsReferenceList() {
+    EntityInterface entity = new RetrieveEntity("test");
+    assertFalse(entity.isReferenceList());
+
+    entity.setDatatype(new IntegerDatatype());
+    assertFalse(entity.isReferenceList());
+
+    entity.setDatatype("Person");
+    assertFalse(entity.isReferenceList());
+
+    entity.setDatatype("LIST<Person>");
+    assertTrue(entity.isReferenceList());
+  }
+}
diff --git a/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java b/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java
index 99cf2706937dce10b8db3a1305053356587e139a..436278ad640663ba42f71b824924b2f1293a5132 100644
--- a/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java
+++ b/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java
@@ -77,6 +77,7 @@ public class PropertyToElementStrategyTest {
     house.addProperty(houseHeight);
     windowProperty = new Property(2345);
     windowProperty.setName("window");
+    windowProperty.setDatatype("window");
     windowProperty.setValue(new ReferenceValue(window.getId()));
     house.addProperty(windowProperty);
 
diff --git a/src/test/java/org/caosdb/server/query/QueryTest.java b/src/test/java/org/caosdb/server/query/QueryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..edbefab78fdfb5846c59a058fdfb67b55a6707cb
--- /dev/null
+++ b/src/test/java/org/caosdb/server/query/QueryTest.java
@@ -0,0 +1,64 @@
+package org.caosdb.server.query;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import org.caosdb.server.CaosDBServer;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class QueryTest {
+
+  @BeforeClass
+  public static void initServerProperties() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
+  String getCacheKey(String query) {
+    Query q = new Query(query);
+    q.parse();
+    return q.getCacheKey();
+  }
+
+  @Test
+  public void testGetKey() {
+    assertEquals("enamePOV(pname,=,val1)", getCacheKey("FIND ename WITH pname = val1"));
+    assertEquals("enamePOV(pname,=,val1)", getCacheKey("COUNT ename WITH pname = val1"));
+    assertEquals("enamePOV(pname,=,val1)", getCacheKey("SELECT bla FROM ename WITH pname = val1"));
+    assertEquals("enamePOV(pname,null,null)", getCacheKey("SELECT bla FROM ename WITH pname"));
+    assertEquals(
+        "enamemaxPOV(pname,null,null)",
+        getCacheKey("SELECT bla FROM ename WITH THE GREATEST pname"));
+
+    assertEquals(
+        "RECORDenamePOV(pname,=,val1)", getCacheKey("FIND RECORD ename WITH pname = val1"));
+    assertEquals("ENTITYPOV(pname,=,val1)", getCacheKey("COUNT ENTITY WITH pname = val1"));
+    assertEquals(
+        "enameConj(POV(pname,=,val1)POV(ename2,=,val2))",
+        getCacheKey("SELECT bla FROM ename WITH pname = val1 AND ename2 = val2"));
+
+    assertEquals("versionedENTITYID(,>,2)", getCacheKey("FIND ANY VERSION OF ENTITY WITH ID > 2"));
+    assertEquals("ENTITYID(min,,)", getCacheKey("FIND ENTITY WITH THE SMALLEST ID"));
+    assertEquals("ENTITYSAT(asdf/%%)", getCacheKey("FIND ENTITY WHICH IS STORED AT /asdf/*"));
+    assertEquals("ENTITYSAT(asdf/asdf)", getCacheKey("FIND ENTITY WHICH IS STORED AT asdf/asdf"));
+    assertEquals(
+        "enamePOV(ref1,null,null)SUB(POV(pname,>,val1)",
+        getCacheKey("FIND ename WITH ref1 WITH pname > val1 "));
+    assertEquals(
+        "ename@(ref1,null)SUB(POV(pname,>,val1)",
+        getCacheKey("FIND ename WHICH IS REFERENCED BY ref1 WITH pname > val1 "));
+  }
+
+  @Test
+  public void testOptimizationOfFindStar() {
+    Query q = new Query("FIND *");
+    q.parse(false);
+    assertEquals("*", q.getEntity().str);
+    assertNull(q.getRole());
+
+    q.optimize();
+    assertNull(q.getEntity());
+    assertEquals(Query.Role.ENTITY, q.getRole());
+  }
+}
diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java
index 2f6651cc81a2614b63f1314f35052fe2096758b0..2c28d7f59c018f90ce77e9f7691a362027655466 100644
--- a/src/test/java/org/caosdb/server/query/TestCQL.java
+++ b/src/test/java/org/caosdb/server/query/TestCQL.java
@@ -65,9 +65,8 @@ public class TestCQL {
   String query1e = "FIND ename . pname1";
   String query2 = "FIND ename.pname1=val1 OR pname2=val2";
   String query3 = "FIND ename.pname1=val1 AND pname2=val2 AND pname3=val3";
-  // String query4 =
-  // "FIND ename WHICH HAS A PROPERTY pname1=val1 AND pname1 HAS A PROPERTY
-  // pname2=val2";
+  String query4 =
+      "FIND ename WHICH HAS A PROPERTY pname1=val1 AND pname1 HAS A PROPERTY pname2=val2";
   String query5 = "FIND ename WHICH HAS A PROPERTY pname1=val1 AND (pname2=val2 OR pname3=val3)";
   String query6 = "FIND ename1 WHICH HAS A pname REFERENCE TO ename2";
   String query6a = "FIND ename1 WHICH REFERENCES ename2 AS A pname";
@@ -148,8 +147,13 @@ public class TestCQL {
   String query27b = "FIND 'Some name with spaces and 1234 numbers and \"'";
   String query27c = "FIND 'Some name with spaces and 1234 numbers and \\*'";
   String query27d = "FIND 'Some name with spaces and 1234 numbers and *'";
-  String query28 = "FIND ename . pname=2.0";
+  String query28 = "FIND ename . pname=2.02";
   String query28a = "FIND ename . pname=2.0prop=test";
+  String query28b = "FIND ename . pname = 1.02m";
+  String query28c = "FIND ename . pname = .02";
+  String query28d = "FIND ename . pname =.02m";
+  String query28e = "FIND ename . pname =.02 1/m^2";
+
   String ticket148 =
       "FIND RECORD FrequencyMeasurement WHICH HAS A PROPERTY ( BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016') AND A PROPERTY MinPeakHeight = '0.5'";
   String ticket148a =
@@ -213,13 +217,26 @@ public class TestCQL {
   String query54b = "SELECT field with spaces FROM RECORD ename";
   String query54c = "SELECT field1, field2, field3 FROM RECORD ename";
   String query54d = "SELECT field1.subfield1, field1.subfield2, field2.*, field3 FROM RECORD ename";
+  String query54e = "SELECT id FROM ename";
   String query55a = "FIND FILE WHICH IS STORED AT /dir/with/date/2016-05-15";
   String query55b = "FIND FILE WHICH IS STORED AT /dir/with/date/2016-05-15/**";
   String query56a = "FIND RECORD WHICH REFERENCES anna";
   String query56b = "FIND RECORD WHICH REFERENCES AN ename2";
   String query56c = "FIND RECORD WHICH REFERENCES atom";
   String query56d = "FIND RECORD WHICH REFERENCES A tom";
+  String query57a = "FIND ENTITY";
+  String query57b = "FIND ENTITY WITH ID";
+  String query57c = "FIND ENTITY WITH ID = 123";
+
+  // strange names and values
+  String query58a = "FIND ENTITY WITH endswith";
+  String query58b = "FIND ENTITY WITH endswith = val1";
+  String query58c = "FIND ENTITY WITH 0with = val1";
+  String query58d = "FIND ENTITY.withdrawn=TRUE";
+  String query58e = "FIND ENTITY WITH pname=with";
+
   String queryIssue31 = "FIND FILE WHICH IS STORED AT /data/in0.foo";
+  String queryIssue116 = "FIND *";
 
   // File paths ///////////////////////////////////////////////////////////////
   String filepath_verb01 = "/foo/";
@@ -259,7 +276,7 @@ public class TestCQL {
 
     // 4 children: FIND, EMPTY_SPACE, entity, entity_filter
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
@@ -306,19 +323,19 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals("WITHpname1=val1", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals("WITH pname1=val1", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WITH", entity_filter.getChild(0).getText());
+    assertEquals("WITH ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname1=val1", entity_filter.getChild(1).getText());
@@ -353,19 +370,19 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals("WHICHHAS APROPERTYpname1=val1", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals("WHICH HAS A PROPERTY pname1=val1", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP,conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", entity_filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname1=val1", entity_filter.getChild(1).getText());
@@ -401,19 +418,19 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals("WHICHHAS Apname1=val1", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals("WHICH HAS A pname1=val1", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname1=val1", entity_filter.getChild(1).getText());
@@ -454,21 +471,21 @@ public class TestCQL {
     }
     System.out.println("query1d: " + sfq.toStringTree(parser));
 
-    // 5 children: FIND, role, entity, filter, EOF
+    // 6 children: FIND, role, entity, WHITE_SPACE, filter, EOF
     // entity_filter
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("ename", sfq.getChild(2).getText());
-    assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals("WHICHHAS Apname1=val1", sfq.getChild(3).getText());
-    final ParseTree entity_filter = sfq.getChild(3);
+    assertEquals("WHICH HAS A pname1=val1", sfq.getChild(4).getText());
+    assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
+    final ParseTree entity_filter = sfq.getChild(4);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname1=val1", entity_filter.getChild(1).getText());
@@ -512,20 +529,19 @@ public class TestCQL {
     }
     System.out.println("query1e: " + sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, entity, EOF
-    // entity_filter
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals(".pname1", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals(". pname1", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals(".", entity_filter.getChild(0).getText());
+    assertEquals(". ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname1", entity_filter.getChild(1).getText());
@@ -563,12 +579,12 @@ public class TestCQL {
 
     // 4 children: FIND, entity, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals(".pname1=val1ORpname2=val2", sfq.getChild(2).getText());
+    assertEquals(".pname1=val1 OR pname2=val2", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, disjunction
@@ -576,13 +592,13 @@ public class TestCQL {
     assertEquals(".", entity_filter.getChild(0).getText());
 
     // disjunction
-    assertEquals("pname1=val1ORpname2=val2", entity_filter.getChild(1).getText());
+    assertEquals("pname1=val1 OR pname2=val2", entity_filter.getChild(1).getText());
     final ParseTree disjunction = entity_filter.getChild(1);
 
     // 3 children: pov, OR, pov
     assertEquals(3, disjunction.getChildCount());
-    assertEquals("pname1=val1", disjunction.getChild(0).getText());
-    assertEquals("OR", disjunction.getChild(1).getText());
+    assertEquals("pname1=val1 ", disjunction.getChild(0).getText());
+    assertEquals("OR ", disjunction.getChild(1).getText());
     assertEquals("pname2=val2", disjunction.getChild(2).getText());
 
     // pov
@@ -600,7 +616,7 @@ public class TestCQL {
     assertEquals("=", pov1.getChild(1).getText());
     assertEquals("=", pov2.getChild(1).getText());
 
-    assertEquals("val1", pov1.getChild(2).getText());
+    assertEquals("val1 ", pov1.getChild(2).getText());
     assertEquals("val2", pov2.getChild(2).getText());
 
     assertEquals("ename", sfq.e.toString());
@@ -628,14 +644,14 @@ public class TestCQL {
 
     // 4 children: FIND, entity, entity_filter EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
 
     // entity
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity filter
-    assertEquals(".pname1=val1ANDpname2=val2ANDpname3=val3", sfq.getChild(2).getText());
+    assertEquals(".pname1=val1 AND pname2=val2 AND pname3=val3", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
@@ -643,15 +659,16 @@ public class TestCQL {
     assertEquals(".", entity_filter.getChild(0).getText());
 
     // conjunction
-    assertEquals("pname1=val1ANDpname2=val2ANDpname3=val3", entity_filter.getChild(1).getText());
+    assertEquals(
+        "pname1=val1 AND pname2=val2 AND pname3=val3", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 9 children: pov, AND, pov, AND, pov
     assertEquals(5, conjunction.getChildCount());
-    assertEquals("pname1=val1", conjunction.getChild(0).getText());
-    assertEquals("AND", conjunction.getChild(1).getText());
-    assertEquals("pname2=val2", conjunction.getChild(2).getText());
-    assertEquals("AND", conjunction.getChild(3).getText());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(1).getText());
+    assertEquals("pname2=val2 ", conjunction.getChild(2).getText());
+    assertEquals("AND ", conjunction.getChild(3).getText());
     assertEquals("pname3=val3", conjunction.getChild(4).getText());
 
     // pov
@@ -674,8 +691,8 @@ public class TestCQL {
     assertEquals("=", pov2.getChild(1).getText());
     assertEquals("=", pov3.getChild(1).getText());
 
-    assertEquals("val1", pov1.getChild(2).getText());
-    assertEquals("val2", pov2.getChild(2).getText());
+    assertEquals("val1 ", pov1.getChild(2).getText());
+    assertEquals("val2 ", pov2.getChild(2).getText());
     assertEquals("val3", pov3.getChild(2).getText());
 
     assertEquals("ename", sfq.e.toString());
@@ -692,124 +709,82 @@ public class TestCQL {
     }
   }
 
-  // @Test
-  // public void testQuery4() {
-  // CQLLexer lexer;
-  // lexer = new CQLLexer(CharStreams.fromString(query4));
-  // final CommonTokenStream tokens = new CommonTokenStream(lexer);
-  //
-  // final CQLParser parser = new CQLParser(tokens);
-  // final CqContext sfq = parser.cq();
-  //
-  // System.out.println("query4: " + sfq.toStringTree(parser));
-  //
-  // // 5 children: FIND, EMPTY_SPACE, entity, EMPTY_SPACE, entity_filter
-  // assertEquals(5, sfq.getChildCount());
-  // assertEquals("FIND", sfq.getChild(0).getText());
-  // assertEquals(" ", sfq.getChild(1).getText());
-  // assertEquals("ename", sfq.getChild(2).getText());
-  // assertEquals(" ", sfq.getChild(3).getText());
-  //
-  // // entity_filter
-  // assertEquals("WHICH HAS A PROPERTY pname1=val1 AND pname1 HAS A PROPERTY
-  // pname2=val2",
-  // sfq
-  // .getChild(4).getText());
-  // final ParseTree entity_filter1 = sfq.getChild(4);
-  //
-  // // 3 children: WHICH_EXP, conjunction
-  // assertEquals(3, entity_filter1.getChildCount());
-  // assertEquals("WHICH HAS A PROPERTY",
-  // entity_filter1.getChild(0).getText());
-  // assertEquals(" ", entity_filter1.getChild(1).getText());
-  //
-  // // conjunction
-  // assertEquals("pname1=val1 AND pname1 HAS A PROPERTY pname2=val2",
-  // entity_filter1
-  // .getChild(2).getText());
-  // final ParseTree conjunction = entity_filter1.getChild(2);
-  //
-  // // 5 children: pov, AND, subproperty
-  // assertEquals(5, conjunction.getChildCount());
-  // assertEquals("pname1=val1", conjunction.getChild(0).getText());
-  // assertEquals(" ", conjunction.getChild(1).getText());
-  // assertEquals("AND", conjunction.getChild(2).getText());
-  // assertEquals(" ", conjunction.getChild(3).getText());
-  // assertEquals("pname1 HAS A PROPERTY pname2=val2",
-  // conjunction.getChild(4).getText());
-  //
-  // // pov
-  // final ParseTree pov1 = conjunction.getChild(0);
-  //
-  // // 3 children: property, operator, value
-  // assertEquals(3, pov1.getChildCount());
-  // assertEquals("pname1", pov1.getChild(0).getText());
-  // assertEquals("=", pov1.getChild(1).getText());
-  // assertEquals("val1", pov1.getChild(2).getText());
-  //
-  // // subproperty
-  // final ParseTree subproperty = conjunction.getChild(4);
-  //
-  // // 3 children: property, entity_filter
-  // assertEquals(3, subproperty.getChildCount());
-  // assertEquals("pname1", subproperty.getChild(0).getText());
-  // assertEquals(" ", subproperty.getChild(1).getText());
-  // assertEquals("HAS A PROPERTY pname2=val2",
-  // subproperty.getChild(2).getText());
-  //
-  // // entity_filter
-  // final ParseTree entity_filter2 = subproperty.getChild(2);
-  //
-  // // 3 children: WHICH_EXP pov
-  // assertEquals(3, entity_filter2.getChildCount());
-  // assertEquals("HAS A PROPERTY", entity_filter2.getChild(0).getText());
-  // assertEquals(" ", entity_filter2.getChild(1).getText());
-  // assertEquals("pname2=val2", entity_filter2.getChild(2).getText());
-  //
-  // // conjunction2
-  // final ParseTree conjunction2 = entity_filter2.getChild(2);
-  // assertEquals(1, conjunction2.getChildCount());
-  // assertEquals("pname2=val2", conjunction2.getChild(0).getText());
-  //
-  // // pov2
-  // final ParseTree pov2 = conjunction2.getChild(0);
-  //
-  // // 3 children: property, operator, value
-  // assertEquals(3, pov2.getChildCount());
-  // assertEquals("pname2", pov2.getChild(0).getText());
-  // assertEquals("=", pov2.getChild(1).getText());
-  // assertEquals("val2", pov2.getChild(2).getText());
-  //
-  // assertEquals("ename", sfq.e.toString());
-  // assertNotNull(sfq.filter);
-  // assertEquals(Conjunction.class.getName(),
-  // sfq.filter.getClass().getName());
-  // assertNotNull(sfq.filter.getFilters());
-  // assertFalse(sfq.filter.getFilters().isEmpty());
-  //
-  // Integer i = 0;
-  // EntityFilterInterface f = sfq.filter.getFilters().get(i);
-  // assertNotNull(f);
-  // assertEquals(POV.class.getName(), f.getClass().getName());
-  // assertEquals("POV(pname1,=,val1)", f.toString());
-  //
-  // i++;
-  // f = sfq.filter.getFilters().get(i);
-  // assertNotNull(f);
-  // assertEquals(SubProperty.class.getName(), f.getClass().getName());
-  //
-  // assertEquals("pname1", ((SubProperty) f).getProperty());
-  // assertEquals(Conjunction.class.getName(), ((SubProperty)
-  // f).getFilter().getClass()
-  // .getName());
-  // assertNotNull(((SubProperty) f).getFilter().getFilters());
-  // assertFalse(((SubProperty) f).getFilter().getFilters().isEmpty());
-  // f = ((SubProperty) f).getFilter().getFilters().getFirst();
-  // assertNotNull(f);
-  // assertEquals(POV.class.getName(), f.getClass().getName());
-  // assertEquals("POV(pname2,=,val2)", f.toString());
-  //
-  // }
+  @Test
+  public void testQuery4() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(query4));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println("query4: " + sfq.toStringTree(parser));
+
+    // 5 children: FIND, entity, EMPTY_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("ename", sfq.getChild(1).getText());
+    // entity_filter
+    assertEquals(
+        "WHICH HAS A PROPERTY pname1=val1 AND pname1 HAS A PROPERTY pname2=val2",
+        sfq.getChild(3).getText());
+    final ParseTree entity_filter1 = sfq.getChild(3);
+
+    // 2 children: WHICH_EXP, conjunction
+    assertEquals(2, entity_filter1.getChildCount());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter1.getChild(0).getText());
+
+    // conjunction
+    assertEquals(
+        "pname1=val1 AND pname1 HAS A PROPERTY pname2=val2", entity_filter1.getChild(1).getText());
+    final ParseTree conjunction = entity_filter1.getChild(1);
+
+    // 3 children: pov, AND, pov
+    assertEquals(3, conjunction.getChildCount());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(1).getText());
+    assertEquals("pname1 HAS A PROPERTY pname2=val2", conjunction.getChild(2).getText());
+
+    // subproperty
+    final ParseTree subproperty = conjunction.getChild(2);
+
+    // 3 children: property, entity_filter
+    assertEquals(2, subproperty.getChildCount());
+    assertEquals("pname1 ", subproperty.getChild(0).getText());
+    assertEquals("HAS A PROPERTY pname2=val2", subproperty.getChild(1).getText());
+
+    // entity_filter
+    final ParseTree entity_filter2 = subproperty.getChild(1).getChild(0);
+
+    // 3 children: WHICH_EXP pov
+    assertEquals(2, entity_filter2.getChildCount());
+    assertEquals("HAS A PROPERTY ", entity_filter2.getChild(0).getText());
+    assertEquals("pname2=val2", entity_filter2.getChild(1).getText());
+
+    final ParseTree pov2 = entity_filter2.getChild(1).getChild(0);
+
+    // 3 children: property, operator, value
+    assertEquals(3, pov2.getChildCount());
+    assertEquals("pname2", pov2.getChild(0).getText());
+    assertEquals("=", pov2.getChild(1).getText());
+    assertEquals("val2", pov2.getChild(2).getText());
+
+    assertEquals("ename", sfq.e.toString());
+    assertNotNull(sfq.filter);
+    assertEquals(Conjunction.class.getName(), sfq.filter.getClass().getName());
+    assertNotNull(sfq.filter);
+
+    EntityFilterInterface f = sfq.filter;
+    assertNotNull(f);
+    assertEquals(Conjunction.class.getName(), f.getClass().getName());
+    assertEquals("POV(pname1,=,val1)", ((Conjunction) f).getFilters().get(0).toString());
+
+    f = ((Conjunction) sfq.filter).getFilters().get(1);
+    assertEquals("POV(pname1,null,null)", f.toString());
+
+    f = ((POV) f).getSubProperty().getFilter();
+    assertEquals("POV(pname2,=,val2)", f.toString());
+  }
 
   @Test
   public void testQuery5() {
@@ -823,30 +798,32 @@ public class TestCQL {
     System.out.println(sfq.toStringTree(parser));
 
     // r children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    //    assertEquals(4, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
     assertEquals(
-        "WHICHHAS APROPERTYpname1=val1AND(pname2=val2ORpname3=val3)", sfq.getChild(2).getText());
-    final ParseTree entity_filter1 = sfq.getChild(2);
+        "WHICH HAS A PROPERTY pname1=val1 AND (pname2=val2 OR pname3=val3)",
+        sfq.getChild(3).getText());
+    final ParseTree entity_filter1 = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter1.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", entity_filter1.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter1.getChild(0).getText());
 
     // conjunction
-    assertEquals("pname1=val1AND(pname2=val2ORpname3=val3)", entity_filter1.getChild(1).getText());
+    assertEquals(
+        "pname1=val1 AND (pname2=val2 OR pname3=val3)", entity_filter1.getChild(1).getText());
     final ParseTree conjunction = entity_filter1.getChild(1);
 
     // 5 children: pov, AND, LPAREN, disjunction, RPAREN
     assertEquals(5, conjunction.getChildCount());
-    assertEquals("pname1=val1", conjunction.getChild(0).getText());
-    assertEquals("AND", conjunction.getChild(1).getText());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(1).getText());
     assertEquals("(", conjunction.getChild(2).getText());
-    assertEquals("pname2=val2ORpname3=val3", conjunction.getChild(3).getText());
+    assertEquals("pname2=val2 OR pname3=val3", conjunction.getChild(3).getText());
     assertEquals(")", conjunction.getChild(4).getText());
 
     // pov
@@ -856,15 +833,15 @@ public class TestCQL {
     assertEquals(3, pov1.getChildCount());
     assertEquals("pname1", pov1.getChild(0).getText());
     assertEquals("=", pov1.getChild(1).getText());
-    assertEquals("val1", pov1.getChild(2).getText());
+    assertEquals("val1 ", pov1.getChild(2).getText());
 
     // disjunction
     final ParseTree disjunction = conjunction.getChild(3);
 
     // 3 children: pov, OR, pov
     assertEquals(3, disjunction.getChildCount());
-    assertEquals("pname2=val2", disjunction.getChild(0).getText());
-    assertEquals("OR", disjunction.getChild(1).getText());
+    assertEquals("pname2=val2 ", disjunction.getChild(0).getText());
+    assertEquals("OR ", disjunction.getChild(1).getText());
     assertEquals("pname3=val3", disjunction.getChild(2).getText());
 
     // pov
@@ -877,7 +854,7 @@ public class TestCQL {
 
     assertEquals("pname2", pov2.getChild(0).getText());
     assertEquals("=", pov2.getChild(1).getText());
-    assertEquals("val2", pov2.getChild(2).getText());
+    assertEquals("val2 ", pov2.getChild(2).getText());
 
     assertEquals("pname3", pov3.getChild(0).getText());
     assertEquals("=", pov3.getChild(1).getText());
@@ -924,34 +901,34 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 4 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals("WHICHHAS Apname->ename2", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals("WHICH HAS A pname -> ename2", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
-    assertEquals("pname->ename2", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
+    assertEquals("pname -> ename2", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: pov
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("pname->ename2", conjunction.getChild(0).getText());
+    assertEquals("pname -> ename2", conjunction.getChild(0).getText());
 
     // pov
     final ParseTree pov1 = conjunction.getChild(0);
 
-    // 3 chidren: property, operator, value
-    assertEquals(3, pov1.getChildCount());
-    assertEquals("pname", pov1.getChild(0).getText());
+    // 4 chidren: property, operator, WHITE_SPACE, value
+    assertEquals(4, pov1.getChildCount());
+    assertEquals("pname ", pov1.getChild(0).getText());
     assertEquals("->", pov1.getChild(1).getText());
-    assertEquals("ename2", pov1.getChild(2).getText());
+    assertEquals("ename2", pov1.getChild(3).getText());
 
     assertEquals("ename1", sfq.e.toString());
     assertNotNull(sfq.filter);
@@ -975,35 +952,35 @@ public class TestCQL {
     }
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals("WHICH->ename2AS Apname", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals("WHICH -> ename2 AS A pname", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("->ename2AS Apname", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("-> ename2 AS A pname", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: pov
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("->ename2AS Apname", conjunction.getChild(0).getText());
+    assertEquals("-> ename2 AS A pname", conjunction.getChild(0).getText());
 
     // pov
     final ParseTree pov1 = conjunction.getChild(0);
 
-    // 4 chidren: value operator, AS_A, property
-    assertEquals(4, pov1.getChildCount());
+    // 5 children: operator, WHITE_SPACE, entity, AS_A, property
+    assertEquals(5, pov1.getChildCount());
     assertEquals("->", pov1.getChild(0).getText());
-    assertEquals("ename2", pov1.getChild(1).getText());
-    assertEquals("AS A", pov1.getChild(2).getText());
-    assertEquals("pname", pov1.getChild(3).getText());
+    assertEquals("ename2 ", pov1.getChild(2).getText());
+    assertEquals("AS A ", pov1.getChild(3).getText());
+    assertEquals("pname", pov1.getChild(4).getText());
 
     assertEquals("ename1", sfq.e.toString());
     assertNotNull(sfq.filter);
@@ -1022,33 +999,34 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
+    assertEquals(" ", sfq.getChild(2).getText());
 
     // entity_filter
-    assertEquals("WHICHHAS A->ename2", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals("WHICH HAS A -> ename2", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
+    assertEquals(5, sfq.getChildCount());
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
-    assertEquals("->ename2", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
+    assertEquals("-> ename2", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: pov
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("->ename2", conjunction.getChild(0).getText());
+    assertEquals("-> ename2", conjunction.getChild(0).getText());
 
     // pov
     final ParseTree pov1 = conjunction.getChild(0);
 
-    // 2 children: operator, value
-    assertEquals(2, pov1.getChildCount());
+    // 3 children: operator, WHITE_SPACE, value
+    assertEquals(3, pov1.getChildCount());
     assertEquals("->", pov1.getChild(0).getText());
-    assertEquals("ename2", pov1.getChild(1).getText());
+    assertEquals("ename2", pov1.getChild(2).getText());
 
     assertEquals("ename1", sfq.e.toString());
     assertNotNull(sfq.filter);
@@ -1068,33 +1046,33 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals("WHICHIS REFERENCEDBYename2", sfq.getChild(2).getText());
+    assertEquals("WHICH IS REFERENCED BY ename2", sfq.getChild(3).getText());
 
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS REFERENCEDBYename2", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS REFERENCED BY ename2", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: backreference
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS REFERENCEDBYename2", conjunction.getChild(0).getText());
+    assertEquals("IS REFERENCED BY ename2", conjunction.getChild(0).getText());
 
     // backreference
     final ParseTree backreference = conjunction.getChild(0);
 
     // 3 children: IS_REFERENCED, BY, entity
     assertEquals(3, backreference.getChildCount());
-    assertEquals("IS REFERENCED", backreference.getChild(0).getText());
-    assertEquals("BY", backreference.getChild(1).getText());
+    assertEquals("IS REFERENCED ", backreference.getChild(0).getText());
+    assertEquals("BY ", backreference.getChild(1).getText());
     assertEquals("ename2", backreference.getChild(2).getText());
 
     assertEquals("ename1", sfq.e.toString());
@@ -1116,25 +1094,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals("WHICHIS REFERENCEDBYename2AS Apname1", sfq.getChild(2).getText());
+    assertEquals("WHICH IS REFERENCED BY ename2 AS A pname1", sfq.getChild(3).getText());
 
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS REFERENCEDBYename2AS Apname1", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS REFERENCED BY ename2 AS A pname1", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: backreference
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS REFERENCEDBYename2AS Apname1", conjunction.getChild(0).getText());
+    assertEquals("IS REFERENCED BY ename2 AS A pname1", conjunction.getChild(0).getText());
 
     // backreference
     final ParseTree backreference = conjunction.getChild(0);
@@ -1144,13 +1122,13 @@ public class TestCQL {
     assertNotNull(sfq.filter);
     assertEquals("@(ename2,pname1)", sfq.filter.toString());
 
-    // 5 children: IS_REFERENCED, BY, entity, AS_A, property
-    assertEquals(5, backreference.getChildCount());
-    assertEquals("IS REFERENCED", backreference.getChild(0).getText());
-    assertEquals("BY", backreference.getChild(1).getText());
+    // 6 children: IS_REFERENCED, BY, entity, WHITE_SPACE, AS_A, property
+    assertEquals(6, backreference.getChildCount());
+    assertEquals("IS REFERENCED ", backreference.getChild(0).getText());
+    assertEquals("BY ", backreference.getChild(1).getText());
     assertEquals("ename2", backreference.getChild(2).getText());
-    assertEquals("AS A", backreference.getChild(3).getText());
-    assertEquals("pname1", backreference.getChild(4).getText());
+    assertEquals("AS A ", backreference.getChild(4).getText());
+    assertEquals("pname1", backreference.getChild(5).getText());
     assertEquals("ename1", sfq.e.toString());
     assertNotNull(sfq.filter);
   }
@@ -1164,42 +1142,42 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    assertEquals(Backreference.class.getName(), sfq.filter.getClass().getName());
-    assertEquals("@(ename2,pname1)", sfq.filter.toString());
-
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals("WHICHIS REFERENCEDBYANename2AS Apname1", sfq.getChild(2).getText());
+    assertEquals("WHICH IS REFERENCED BY AN ename2 AS A pname1", sfq.getChild(3).getText());
 
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS REFERENCEDBYANename2AS Apname1", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS REFERENCED BY AN ename2 AS A pname1", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: backreference
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS REFERENCEDBYANename2AS Apname1", conjunction.getChild(0).getText());
+    assertEquals("IS REFERENCED BY AN ename2 AS A pname1", conjunction.getChild(0).getText());
 
     // backreference
     final ParseTree backreference = conjunction.getChild(0);
 
-    // 6 children: IS_REFERENCED, BY, AN, entity, AS_A, property
-    assertEquals(6, backreference.getChildCount());
-    assertEquals("IS REFERENCED", backreference.getChild(0).getText());
-    assertEquals("BY", backreference.getChild(1).getText());
-    assertEquals("AN", backreference.getChild(2).getText());
+    // 6 children: IS_REFERENCED, BY, AN, entity, WHITE_SPACE, AS_A, property
+    assertEquals(7, backreference.getChildCount());
+    assertEquals("IS REFERENCED ", backreference.getChild(0).getText());
+    assertEquals("BY ", backreference.getChild(1).getText());
+    assertEquals("AN ", backreference.getChild(2).getText());
     assertEquals("ename2", backreference.getChild(3).getText());
-    assertEquals("AS A", backreference.getChild(4).getText());
-    assertEquals("pname1", backreference.getChild(5).getText());
+    assertEquals("AS A ", backreference.getChild(5).getText());
+    assertEquals("pname1", backreference.getChild(6).getText());
     assertEquals("ename1", sfq.e.toString());
     assertNotNull(sfq.filter);
+
+    assertEquals(Backreference.class.getName(), sfq.filter.getClass().getName());
+    assertEquals("@(ename2,pname1)", sfq.filter.toString());
   }
 
   @Test
@@ -1211,32 +1189,32 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals("WITHNOTpname1=val1", sfq.getChild(2).getText());
+    assertEquals("WITH NOT pname1=val1", sfq.getChild(3).getText());
 
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WITH", entity_filter.getChild(0).getText());
+    assertEquals("WITH ", entity_filter.getChild(0).getText());
 
     // conjunction
-    assertEquals("NOTpname1=val1", entity_filter.getChild(1).getText());
+    assertEquals("NOT pname1=val1", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: negation
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("NOTpname1=val1", conjunction.getChild(0).getText());
+    assertEquals("NOT pname1=val1", conjunction.getChild(0).getText());
     final ParseTree negation = conjunction.getChild(0);
 
     // 2 children: NOT, pov
     assertEquals(2, negation.getChildCount());
-    assertEquals("NOT", negation.getChild(0).getText());
+    assertEquals("NOT ", negation.getChild(0).getText());
     assertEquals("pname1=val1", negation.getChild(1).getText());
     final ParseTree pov1 = negation.getChild(1).getChild(0);
 
@@ -1263,32 +1241,32 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals("WHICHDOES NOT HAVE Apname1!=val1", sfq.getChild(2).getText());
+    assertEquals("WHICH DOES NOT HAVE A pname1!=val1", sfq.getChild(3).getText());
 
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
 
     // conjunction
-    assertEquals("DOES NOT HAVE Apname1!=val1", entity_filter.getChild(1).getText());
+    assertEquals("DOES NOT HAVE A pname1!=val1", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: negation
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("DOES NOT HAVE Apname1!=val1", conjunction.getChild(0).getText());
+    assertEquals("DOES NOT HAVE A pname1!=val1", conjunction.getChild(0).getText());
     final ParseTree negation = conjunction.getChild(0);
 
     // 2 children: DOESN'T, pov
     assertEquals(2, negation.getChildCount());
-    assertEquals("DOES NOT HAVE A", negation.getChild(0).getText());
+    assertEquals("DOES NOT HAVE A ", negation.getChild(0).getText());
     assertEquals("pname1!=val1", negation.getChild(1).getText());
     final ParseTree pov1 = negation.getChild(1).getChild(0);
 
@@ -1316,11 +1294,11 @@ public class TestCQL {
     final CqContext sfq = parser.cq();
 
     // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(".!pname1=val1", sfq.getChild(2).getText());
+    assertEquals(4, sfq.getChildCount());
 
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
@@ -1369,12 +1347,12 @@ public class TestCQL {
 
     // 4 children: FIND, entity, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
-    assertEquals(".pname1=val1AND.pname2=val2", sfq.getChild(2).getText());
+    assertEquals(".pname1=val1 AND .pname2=val2", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
@@ -1382,13 +1360,13 @@ public class TestCQL {
     assertEquals(".", entity_filter.getChild(0).getText());
 
     // conjunction
-    assertEquals("pname1=val1AND.pname2=val2", entity_filter.getChild(1).getText());
+    assertEquals("pname1=val1 AND .pname2=val2", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 4 child: pov1, AND, which_exp, pov2
     assertEquals(4, conjunction.getChildCount());
-    assertEquals("pname1=val1", conjunction.getChild(0).getText());
-    assertEquals("AND", conjunction.getChild(1).getText());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(1).getText());
     assertEquals(".", conjunction.getChild(2).getText());
     assertEquals("pname2=val2", conjunction.getChild(3).getText());
     final ParseTree pov1 = conjunction.getChild(0).getChild(0);
@@ -1398,7 +1376,7 @@ public class TestCQL {
     assertEquals(3, pov1.getChildCount());
     assertEquals("pname1", pov1.getChild(0).getText());
     assertEquals("=", pov1.getChild(1).getText());
-    assertEquals("val1", pov1.getChild(2).getText());
+    assertEquals("val1 ", pov1.getChild(2).getText());
 
     assertEquals(3, pov2.getChildCount());
     assertEquals("pname2", pov2.getChild(0).getText());
@@ -1426,46 +1404,47 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
     // entity_filter
     assertEquals(
-        "WHICHDOESN'T HAVE Apname1=val1ANDWHICHHAS Apname2=val2", sfq.getChild(2).getText());
-    final ParseTree entity_filter = sfq.getChild(2);
+        "WHICH DOESN'T HAVE A pname1=val1 AND WHICH HAS A pname2=val2", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals(
-        "DOESN'T HAVE Apname1=val1ANDWHICHHAS Apname2=val2", entity_filter.getChild(1).getText());
+        "DOESN'T HAVE A pname1=val1 AND WHICH HAS A pname2=val2",
+        entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 4 children: pov1, AND, which_exp, pov2
     assertEquals(4, conjunction.getChildCount());
-    assertEquals("DOESN'T HAVE Apname1=val1", conjunction.getChild(0).getText());
-    assertEquals("AND", conjunction.getChild(1).getText());
-    assertEquals("WHICHHAS A", conjunction.getChild(2).getText());
+    assertEquals("DOESN'T HAVE A pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(1).getText());
+    assertEquals("WHICH HAS A ", conjunction.getChild(2).getText());
     assertEquals("pname2=val2", conjunction.getChild(3).getText());
     final ParseTree pov2 = conjunction.getChild(3).getChild(0);
 
     final ParseTree negation = conjunction.getChild(0).getChild(0);
     // 2 children: NOT, pov
     assertEquals(2, negation.getChildCount());
-    assertEquals("DOESN'T HAVE A", negation.getChild(0).getText());
-    assertEquals("pname1=val1", negation.getChild(1).getText());
+    assertEquals("DOESN'T HAVE A ", negation.getChild(0).getText());
+    assertEquals("pname1=val1 ", negation.getChild(1).getText());
     final ParseTree pov1 = negation.getChild(1).getChild(0);
 
     // 3 children: property, operator, value
     assertEquals(3, pov1.getChildCount());
     assertEquals("pname1", pov1.getChild(0).getText());
     assertEquals("=", pov1.getChild(1).getText());
-    assertEquals("val1", pov1.getChild(2).getText());
+    assertEquals("val1 ", pov1.getChild(2).getText());
 
     assertEquals(3, pov2.getChildCount());
     assertEquals("pname2", pov2.getChild(0).getText());
@@ -1502,7 +1481,7 @@ public class TestCQL {
 
     // 3 children: FIND, role, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("FILE", sfq.getChild(1).getText());
     assertEquals(null, sfq.e);
     assertEquals(Query.Role.FILE, sfq.r);
@@ -1525,27 +1504,27 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("File", sfq.getChild(1).getText());
-    assertEquals("WHICHISNOTREFERENCED", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("File ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS NOT REFERENCED", sfq.getChild(2).getText());
 
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHIS", entity_filter.getChild(0).getText());
-    assertEquals("NOTREFERENCED", entity_filter.getChild(1).getText());
+    assertEquals("WHICH IS ", entity_filter.getChild(0).getText());
+    assertEquals("NOT REFERENCED", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: negation
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("NOTREFERENCED", conjunction.getChild(0).getText());
+    assertEquals("NOT REFERENCED", conjunction.getChild(0).getText());
     final ParseTree negation = conjunction.getChild(0);
 
     // 2 children: NOT, backreference
     assertEquals(2, negation.getChildCount());
-    assertEquals("NOT", negation.getChild(0).getText());
+    assertEquals("NOT ", negation.getChild(0).getText());
     assertEquals("REFERENCED", negation.getChild(1).getText());
 
     // backreference
@@ -1582,26 +1561,26 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT\"/bla/bla/bla\"", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT \"/bla/bla/bla\"", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT\"/bla/bla/bla\"", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT \"/bla/bla/bla\"", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT\"/bla/bla/bla\"", conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT \"/bla/bla/bla\"", conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("\"/bla/bla/bla\"", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1627,27 +1606,27 @@ public class TestCQL {
     assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName());
 
     // 4 children: FIND, role, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/bla/bla/bla", sfq.getChild(2).getText());
+    //    assertEquals(4, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /bla/bla/bla", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT/bla/bla/bla", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla/bla", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT/bla/bla/bla", conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla/bla", conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("/bla/bla/bla", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1674,26 +1653,26 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/bla/bla/bla/", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /bla/bla/bla/", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT/bla/bla/bla/", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla/bla/", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT/bla/bla/bla/", conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla/bla/", conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("/bla/bla/bla/", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1723,27 +1702,27 @@ public class TestCQL {
     assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName());
 
     // 4 children: FIND, role, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/bla/bla/bla.html", sfq.getChild(2).getText());
+    //    assertEquals(4, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /bla/bla/bla.html", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT/bla/bla/bla.html", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla/bla.html", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT/bla/bla/bla.html", conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla/bla.html", conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("/bla/bla/bla.html", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1775,26 +1754,26 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT//bla///bla.html", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT //bla///bla.html", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT//bla///bla.html", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT //bla///bla.html", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT//bla///bla.html", conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT //bla///bla.html", conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("//bla///bla.html", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1826,26 +1805,26 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/bla/bla_bla.html", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /bla/bla_bla.html", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT/bla/bla_bla.html", entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla_bla.html", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT/bla/bla_bla.html", conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT /bla/bla_bla.html", conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("/bla/bla_bla.html", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1879,26 +1858,26 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT" + origPath, sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT " + origPath, sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
-    assertEquals("IS STORED AT" + origPath, entity_filter.getChild(1).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
+    assertEquals("IS STORED AT " + origPath, entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: storedAt
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("IS STORED AT" + origPath, conjunction.getChild(0).getText());
+    assertEquals("IS STORED AT " + origPath, conjunction.getChild(0).getText());
     final ParseTree storedat = conjunction.getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals(origPath, storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1929,30 +1908,31 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT\"/bla/bla/bla\"ORHAS Apname2=val2", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals(
+        "WHICH IS STORED AT \"/bla/bla/bla\" OR HAS A pname2=val2", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
     assertEquals(
-        "IS STORED AT\"/bla/bla/bla\"ORHAS Apname2=val2", entity_filter.getChild(1).getText());
+        "IS STORED AT \"/bla/bla/bla\" OR HAS A pname2=val2", entity_filter.getChild(1).getText());
     final ParseTree disjunction = entity_filter.getChild(1);
 
     // 4 child: storedAt, OR, HAS_A, POV
     assertEquals(4, disjunction.getChildCount());
-    assertEquals("IS STORED AT\"/bla/bla/bla\"", disjunction.getChild(0).getText());
-    assertEquals("OR", disjunction.getChild(1).getText());
-    assertEquals("HAS A", disjunction.getChild(2).getText());
+    assertEquals("IS STORED AT \"/bla/bla/bla\" ", disjunction.getChild(0).getText());
+    assertEquals("OR ", disjunction.getChild(1).getText());
+    assertEquals("HAS A ", disjunction.getChild(2).getText());
     assertEquals("pname2=val2", disjunction.getChild(3).getText());
     final ParseTree storedat = disjunction.getChild(0).getChild(0);
 
-    // 2 children: IS_STORED_AT, loc
-    assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    // 3 children: IS_STORED_AT, loc, WHITE_SPACE
+    assertEquals(3, storedat.getChildCount());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("\"/bla/bla/bla\"", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -1990,36 +1970,38 @@ public class TestCQL {
 
     // 4 children: FIND, role, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHHAS Apname2=val2ORIS STORED AT\"/bla/bla/bla\"", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals(
+        "WHICH HAS A pname2=val2 OR IS STORED AT \"/bla/bla/bla\"", sfq.getChild(2).getText());
     // entity_filter
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
-    assertEquals("pname2=val2ORIS STORED AT\"/bla/bla/bla\"", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
+    assertEquals(
+        "pname2=val2 OR IS STORED AT \"/bla/bla/bla\"", entity_filter.getChild(1).getText());
     final ParseTree disjunction = entity_filter.getChild(1);
 
     // 3 children: POV, OR, storedAt
     assertEquals(3, disjunction.getChildCount());
-    assertEquals("pname2=val2", disjunction.getChild(0).getText());
-    assertEquals("OR", disjunction.getChild(1).getText());
-    assertEquals("IS STORED AT\"/bla/bla/bla\"", disjunction.getChild(2).getText());
+    assertEquals("pname2=val2 ", disjunction.getChild(0).getText());
+    assertEquals("OR ", disjunction.getChild(1).getText());
+    assertEquals("IS STORED AT \"/bla/bla/bla\"", disjunction.getChild(2).getText());
     final ParseTree pov = disjunction.getChild(0).getChild(0);
 
     // 3 children: p, o, v
     assertEquals(3, pov.getChildCount());
     assertEquals("pname2", pov.getChild(0).getText());
     assertEquals("=", pov.getChild(1).getText());
-    assertEquals("val2", pov.getChild(2).getText());
+    assertEquals("val2 ", pov.getChild(2).getText());
 
     final ParseTree storedat = disjunction.getChild(2).getChild(0);
 
     // 2 children: IS_STORED_AT, loc
     assertEquals(2, storedat.getChildCount());
-    assertEquals("IS STORED AT", storedat.getChild(0).getText());
+    assertEquals("IS STORED AT ", storedat.getChild(0).getText());
     assertEquals("\"/bla/bla/bla\"", storedat.getChild(1).getText());
 
     assertEquals(null, sfq.e);
@@ -2056,48 +2038,50 @@ public class TestCQL {
     // assertEquals(Conjunction.class.getName(),
     // sfq.filter.getClass().getName());
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(
-        "WHICHHAS APROPERTY(pname1=val1WHICHHAS APROPERTYpname2=val2)", sfq.getChild(2).getText());
+        "WHICH HAS A PROPERTY ( pname1=val1 WHICH HAS A PROPERTY pname2=val2 )",
+        sfq.getChild(3).getText());
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 4 children: WHICH_EXP, (, conjunction, )
     assertEquals(4, entity_filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", entity_filter.getChild(0).getText());
-    assertEquals("(", entity_filter.getChild(1).getText());
-    assertEquals("pname1=val1WHICHHAS APROPERTYpname2=val2", entity_filter.getChild(2).getText());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter.getChild(0).getText());
+    assertEquals("( ", entity_filter.getChild(1).getText());
+    assertEquals(
+        "pname1=val1 WHICH HAS A PROPERTY pname2=val2 ", entity_filter.getChild(2).getText());
     assertEquals(")", entity_filter.getChild(3).getText());
     final ParseTree conjunction = entity_filter.getChild(2);
 
     // 2 children: pov subp
     assertEquals(2, conjunction.getChildCount());
-    assertEquals("pname1=val1", conjunction.getChild(0).getText());
-    assertEquals("WHICHHAS APROPERTYpname2=val2", conjunction.getChild(1).getText());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY pname2=val2 ", conjunction.getChild(1).getText());
     final ParseTree pov = conjunction.getChild(0);
 
     // 3 children: p, o, v
     assertEquals(3, pov.getChildCount());
     assertEquals("pname1", pov.getChild(0).getText());
     assertEquals("=", pov.getChild(1).getText());
-    assertEquals("val1", pov.getChild(2).getText());
+    assertEquals("val1 ", pov.getChild(2).getText());
 
     final ParseTree subproperty = conjunction.getChild(1);
 
     // 1 child: entity_filter
     assertEquals(1, subproperty.getChildCount());
-    assertEquals("WHICHHAS APROPERTYpname2=val2", subproperty.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY pname2=val2 ", subproperty.getChild(0).getText());
 
     final ParseTree subEntityFilter = subproperty.getChild(0);
 
     // 2 children: WHICH_EXP, pov
     assertEquals(2, subEntityFilter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", subEntityFilter.getChild(0).getText());
-    assertEquals("pname2=val2", subEntityFilter.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY ", subEntityFilter.getChild(0).getText());
+    assertEquals("pname2=val2 ", subEntityFilter.getChild(1).getText());
 
     assertEquals("ename", sfq.e.toString());
     assertEquals(null, sfq.r);
@@ -2128,45 +2112,47 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(
-        "WHICHHAS APROPERTYpname1=val1WHICHHAS APROPERTYpname2=val2", sfq.getChild(2).getText());
+        "WHICH HAS A PROPERTY pname1=val1 WHICH HAS A PROPERTY pname2=val2",
+        sfq.getChild(3).getText());
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", entity_filter.getChild(0).getText());
-    assertEquals("pname1=val1WHICHHAS APROPERTYpname2=val2", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter.getChild(0).getText());
+    assertEquals(
+        "pname1=val1 WHICH HAS A PROPERTY pname2=val2", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 2 children: pov, subp
     assertEquals(2, conjunction.getChildCount());
-    assertEquals("pname1=val1", conjunction.getChild(0).getText());
-    assertEquals("WHICHHAS APROPERTYpname2=val2", conjunction.getChild(1).getText());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY pname2=val2", conjunction.getChild(1).getText());
     final ParseTree pov = conjunction.getChild(0);
 
     // 3 children: p, o, v
     assertEquals(3, pov.getChildCount());
     assertEquals("pname1", pov.getChild(0).getText());
     assertEquals("=", pov.getChild(1).getText());
-    assertEquals("val1", pov.getChild(2).getText());
+    assertEquals("val1 ", pov.getChild(2).getText());
 
     final ParseTree subproperty = conjunction.getChild(1);
 
     // 1 child: entity_filter
     assertEquals(1, subproperty.getChildCount());
-    assertEquals("WHICHHAS APROPERTYpname2=val2", subproperty.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY pname2=val2", subproperty.getChild(0).getText());
 
     final ParseTree subEntityFilter = subproperty.getChild(0);
 
     // 2 children: WHICH_EXP, pov
     assertEquals(2, subEntityFilter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", subEntityFilter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", subEntityFilter.getChild(0).getText());
     assertEquals("pname2=val2", subEntityFilter.getChild(1).getText());
 
     assertEquals("ename", sfq.e.toString());
@@ -2197,14 +2183,14 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals(".pname1=val1.pname2=val2", sfq.getChild(2).getText());
+    assertEquals(".pname1=val1.pname2=val2", sfq.getChild(3).getText());
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
@@ -2266,14 +2252,14 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals(".(pname1=val1.pname2=val2)", sfq.getChild(2).getText());
+    assertEquals(".(pname1=val1.pname2=val2)", sfq.getChild(3).getText());
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 4 children: WHICH_EXP, (, conjunction, )
     assertEquals(4, entity_filter.getChildCount());
@@ -2339,44 +2325,44 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    assertEquals("WHICHHAS Apname1=val1WITHpname2=val2", sfq.getChild(2).getText());
+    assertEquals("WHICH HAS A pname1=val1 WITH pname2=val2", sfq.getChild(3).getText());
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
-    assertEquals("pname1=val1WITHpname2=val2", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
+    assertEquals("pname1=val1 WITH pname2=val2", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 2 children: pov, subp
     assertEquals(2, conjunction.getChildCount());
-    assertEquals("pname1=val1", conjunction.getChild(0).getText());
-    assertEquals("WITHpname2=val2", conjunction.getChild(1).getText());
+    assertEquals("pname1=val1 ", conjunction.getChild(0).getText());
+    assertEquals("WITH pname2=val2", conjunction.getChild(1).getText());
     final ParseTree pov = conjunction.getChild(0);
 
     // 3 children: p, o, v
     assertEquals(3, pov.getChildCount());
     assertEquals("pname1", pov.getChild(0).getText());
     assertEquals("=", pov.getChild(1).getText());
-    assertEquals("val1", pov.getChild(2).getText());
+    assertEquals("val1 ", pov.getChild(2).getText());
 
     final ParseTree subproperty = conjunction.getChild(1);
 
     // 1 child: entity_filter
     assertEquals(1, subproperty.getChildCount());
-    assertEquals("WITHpname2=val2", subproperty.getChild(0).getText());
+    assertEquals("WITH pname2=val2", subproperty.getChild(0).getText());
 
     final ParseTree subEntityFilter = subproperty.getChild(0);
 
     // 2 children: WHICH_EXP, pov
     assertEquals(2, subEntityFilter.getChildCount());
-    assertEquals("WITH", subEntityFilter.getChild(0).getText());
+    assertEquals("WITH ", subEntityFilter.getChild(0).getText());
     assertEquals("pname2=val2", subEntityFilter.getChild(1).getText());
 
     assertEquals("ename", sfq.e.toString());
@@ -2408,50 +2394,53 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(
-        "WHICHHAS A->ename2WHICHHAS A->ename3WHICHHAS APROPERTYpname1=val1",
-        sfq.getChild(2).getText());
+        "WHICH HAS A -> ename2 WHICH HAS A -> ename3 WHICH HAS A PROPERTY pname1=val1",
+        sfq.getChild(3).getText());
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICHHAS A", entity_filter.getChild(0).getText());
+    assertEquals("WHICH HAS A ", entity_filter.getChild(0).getText());
     assertEquals(
-        "->ename2WHICHHAS A->ename3WHICHHAS APROPERTYpname1=val1",
+        "-> ename2 WHICH HAS A -> ename3 WHICH HAS A PROPERTY pname1=val1",
         entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 2 children: pov, subp
     assertEquals(2, conjunction.getChildCount());
-    assertEquals("->ename2", conjunction.getChild(0).getText());
+    assertEquals("-> ename2 ", conjunction.getChild(0).getText());
     assertEquals(
-        "WHICHHAS A->ename3WHICHHAS APROPERTYpname1=val1", conjunction.getChild(1).getText());
+        "WHICH HAS A -> ename3 WHICH HAS A PROPERTY pname1=val1",
+        conjunction.getChild(1).getText());
     final ParseTree pov = conjunction.getChild(0);
 
-    // 2 children: ->, ?
-    assertEquals(2, pov.getChildCount());
+    // 3 children: ->, WHITE_SPACE, ename2
+    assertEquals(3, pov.getChildCount());
     assertEquals("->", pov.getChild(0).getText());
-    assertEquals("ename2", pov.getChild(1).getText());
+    assertEquals("ename2 ", pov.getChild(2).getText());
 
     final ParseTree subproperty = conjunction.getChild(1);
 
     // 1 child: entity_filter
     assertEquals(1, subproperty.getChildCount());
     assertEquals(
-        "WHICHHAS A->ename3WHICHHAS APROPERTYpname1=val1", subproperty.getChild(0).getText());
+        "WHICH HAS A -> ename3 WHICH HAS A PROPERTY pname1=val1",
+        subproperty.getChild(0).getText());
 
     final ParseTree subEntityFilter = subproperty.getChild(0);
 
     // 2 children: WHICH_EXP, subp
     assertEquals(2, subEntityFilter.getChildCount());
-    assertEquals("WHICHHAS A", subEntityFilter.getChild(0).getText());
-    assertEquals("->ename3WHICHHAS APROPERTYpname1=val1", subEntityFilter.getChild(1).getText());
+    assertEquals("WHICH HAS A ", subEntityFilter.getChild(0).getText());
+    assertEquals(
+        "-> ename3 WHICH HAS A PROPERTY pname1=val1", subEntityFilter.getChild(1).getText());
 
     assertEquals("ename1", sfq.e.toString());
     assertEquals(null, sfq.r);
@@ -2489,7 +2478,7 @@ public class TestCQL {
 
     // 4 children: FIND, ename, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(".ename2.ename3.pname1=val1", sfq.getChild(2).getText());
@@ -2556,40 +2545,37 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
-    // assertEquals("WHICH HAS A PROPERTY ( ename2 WHICH HAS A PROPERTY
-    // pname1=val1)",
-    // sfq
-    // .getChild(4).getText());
-    // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    assertEquals(
+        "WHICH HAS A PROPERTY ( WHICH HAS A PROPERTY pname1=val1)", sfq.getChild(3).getText());
+    final ParseTree entity_filter = sfq.getChild(3);
 
-    // 4 children: WHICH_EXP, (, conjunction, )
+    // 4 children: WHICH_EXP, (, subproperty, )
     assertEquals(4, entity_filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", entity_filter.getChild(0).getText());
-    assertEquals("(", entity_filter.getChild(1).getText());
-    assertEquals("WHICHHAS APROPERTYpname1=val1", entity_filter.getChild(2).getText());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter.getChild(0).getText());
+    assertEquals("( ", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY pname1=val1", entity_filter.getChild(2).getText());
     assertEquals(")", entity_filter.getChild(3).getText());
-    final ParseTree conjunction = entity_filter.getChild(2);
+    final ParseTree subproperty1 = entity_filter.getChild(2);
 
-    // 1 children
-    assertEquals(1, conjunction.getChildCount());
-    assertEquals("WHICHHAS APROPERTYpname1=val1", conjunction.getChild(0).getText());
-    final ParseTree subp = conjunction.getChild(0);
+    // 1 child: filter
+    assertEquals(1, subproperty1.getChildCount());
+    assertEquals("WHICH HAS A PROPERTY pname1=val1", subproperty1.getChild(0).getText());
+    final ParseTree filter = subproperty1.getChild(0);
 
     // 1 children: filter
-    assertEquals(1, subp.getChildCount());
-    assertEquals("WHICHHAS APROPERTYpname1=val1", subp.getChild(0).getText());
+    assertEquals(1, filter.getChildCount());
+    assertEquals("WHICH HAS A PROPERTY pname1=val1", filter.getChild(0).getText());
 
-    final ParseTree subproperty = subp.getChild(0);
+    final ParseTree subproperty = filter.getChild(0);
 
-    // 2 children: entity_filter
+    // 2 children: WHICH entity_filter
     assertEquals(2, subproperty.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", subproperty.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", subproperty.getChild(0).getText());
     assertEquals("pname1=val1", subproperty.getChild(1).getText());
 
     assertEquals("ename1", sfq.e.toString());
@@ -2616,41 +2602,44 @@ public class TestCQL {
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
     assertEquals(null, sfq.r);
 
-    // 4 children: FIND, ename, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename1", sfq.getChild(1).getText());
 
     // entity_filter
-    final ParseTree entity_filter = sfq.getChild(2);
+    final ParseTree entity_filter = sfq.getChild(3);
 
     // 4 children: WHICH_EXP, (, conjunction, )
     assertEquals(4, entity_filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", entity_filter.getChild(0).getText());
-    assertEquals("(", entity_filter.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY ", entity_filter.getChild(0).getText());
+    assertEquals("( ", entity_filter.getChild(1).getText());
     assertEquals(
-        "WHICHHAS APROPERTY(WHICHHAS APROPERTY(pname1=val1))", entity_filter.getChild(2).getText());
+        "WHICH HAS A PROPERTY ( WHICH HAS A PROPERTY (pname1=val1) ) ",
+        entity_filter.getChild(2).getText());
     assertEquals(")", entity_filter.getChild(3).getText());
     final ParseTree conjunction = entity_filter.getChild(2);
 
     // 1 children
     assertEquals(1, conjunction.getChildCount());
     assertEquals(
-        "WHICHHAS APROPERTY(WHICHHAS APROPERTY(pname1=val1))", conjunction.getChild(0).getText());
+        "WHICH HAS A PROPERTY ( WHICH HAS A PROPERTY (pname1=val1) ) ",
+        conjunction.getChild(0).getText());
     final ParseTree subp = conjunction.getChild(0);
 
     // 1 children: filter
     assertEquals(1, subp.getChildCount());
-    assertEquals("WHICHHAS APROPERTY(WHICHHAS APROPERTY(pname1=val1))", subp.getChild(0).getText());
+    assertEquals(
+        "WHICH HAS A PROPERTY ( WHICH HAS A PROPERTY (pname1=val1) ) ", subp.getChild(0).getText());
 
     final ParseTree subproperty = subp.getChild(0);
 
     // 4 children: which, (, conjunction, )
     assertEquals(4, subproperty.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", subproperty.getChild(0).getText());
-    assertEquals("(", subproperty.getChild(1).getText());
-    assertEquals("WHICHHAS APROPERTY(pname1=val1)", subproperty.getChild(2).getText());
-    assertEquals(")", subproperty.getChild(3).getText());
+    assertEquals("WHICH HAS A PROPERTY ", subproperty.getChild(0).getText());
+    assertEquals("( ", subproperty.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY (pname1=val1) ", subproperty.getChild(2).getText());
+    assertEquals(") ", subproperty.getChild(3).getText());
 
     assertEquals("ename1", sfq.e.toString());
     assertEquals(null, sfq.r);
@@ -2684,13 +2673,13 @@ public class TestCQL {
 
     // 4 children: FIND, ename, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals(".", entity_filter.getChild(0).getText());
+    assertEquals(". ", entity_filter.getChild(0).getText());
     assertEquals("pname->ename", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
@@ -2742,7 +2731,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename#", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
   }
@@ -2759,7 +2748,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("#ename", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
   }
@@ -2778,7 +2767,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("<<ename-regexp>>", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_REGEXP, sfq.e.type);
     assertEquals("ename-regexp", sfq.e.toString());
@@ -2801,7 +2790,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("<<ename\\>>regexp>>", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_REGEXP, sfq.e.type);
     assertEquals("ename>>regexp", sfq.e.toString());
@@ -2825,7 +2814,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("*ename", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_LIKE, sfq.e.type);
     assertEquals("%ename", sfq.e.toString());
@@ -2848,7 +2837,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("en*ame", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_LIKE, sfq.e.type);
     assertEquals("en%ame", sfq.e.toString());
@@ -2871,7 +2860,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("SimpleD*Property", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_LIKE, sfq.e.type);
     assertEquals("SimpleD%Property", sfq.e.toString());
@@ -2894,7 +2883,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename*", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_LIKE, sfq.e.type);
     assertEquals("ename%", sfq.e.toString());
@@ -2917,7 +2906,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("<<SimpleD.*Property>>", sfq.getChild(1).getText());
     assertEquals(Query.Pattern.TYPE_REGEXP, sfq.e.type);
     assertEquals("SimpleD.*Property", sfq.e.toString());
@@ -2938,11 +2927,11 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals(".THEGREATESTpname", sfq.getChild(2).getText());
+    assertEquals(". THE GREATEST pname", sfq.getChild(3).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
   }
 
@@ -2961,11 +2950,11 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals(".THESMALLESTpname", sfq.getChild(2).getText());
+    assertEquals(". THE SMALLEST pname", sfq.getChild(3).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
   }
 
@@ -2985,17 +2974,17 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("SimpleRecordType", sfq.getChild(1).getText());
-    assertEquals("WITHTHEGREATESTSimpleDoubleProperty>0", sfq.getChild(2).getText());
+    assertEquals("WITH THE GREATEST SimpleDoubleProperty>0", sfq.getChild(3).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    assertEquals(2, sfq.getChild(2).getChildCount());
-    final ParseTree filter = sfq.getChild(2);
-    assertEquals("WITH", filter.getChild(0).getText());
-    assertEquals("THEGREATESTSimpleDoubleProperty>0", filter.getChild(1).getText());
+    assertEquals(2, sfq.getChild(3).getChildCount());
+    final ParseTree filter = sfq.getChild(3);
+    assertEquals("WITH ", filter.getChild(0).getText());
+    assertEquals("THE GREATEST SimpleDoubleProperty>0", filter.getChild(1).getText());
 
     assertEquals(1, filter.getChild(1).getChildCount());
     assertEquals(3, filter.getChild(1).getChild(0).getChildCount());
@@ -3024,13 +3013,13 @@ public class TestCQL {
     System.out.println(sfq.toStringTree(parser));
 
     // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'ANDAPROPERTY(BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016')",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND A PROPERTY ( BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016')",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
     assertNotNull(sfq.filter);
@@ -3069,24 +3058,24 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHHAS APROPERTYpname=val1AND(pname=val2)", sfq.getChild(2).getText());
+    assertEquals("WHICH HAS A PROPERTY pname=val1 AND (pname=val2)", sfq.getChild(3).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(2);
+    final ParseTree filter = sfq.getChild(3);
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
-    assertEquals("pname=val1AND(pname=val2)", filter.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
+    assertEquals("pname=val1 AND (pname=val2)", filter.getChild(1).getText());
 
     final ParseTree conjunction1 = filter.getChild(1);
 
     // 5 children: pov, AND, (, pov, )
     assertEquals(5, conjunction1.getChildCount());
-    assertEquals("pname=val1", conjunction1.getChild(0).getText());
-    assertEquals("AND", conjunction1.getChild(1).getText());
+    assertEquals("pname=val1 ", conjunction1.getChild(0).getText());
+    assertEquals("AND ", conjunction1.getChild(1).getText());
     assertEquals("(", conjunction1.getChild(2).getText());
     assertEquals("pname=val2", conjunction1.getChild(3).getText());
     assertEquals(")", conjunction1.getChild(4).getText());
@@ -3123,41 +3112,41 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'AND(BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016')",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND ( BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016')",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
+    final ParseTree filter = sfq.getChild(4);
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
     assertEquals(
-        "ObstacleRadius='2.0'AND(BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016')",
+        "ObstacleRadius = '2.0' AND ( BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016')",
         filter.getChild(1).getText());
 
     final ParseTree conjunction1 = filter.getChild(1);
     assertEquals(5, conjunction1.getChildCount());
-    assertEquals("ObstacleRadius='2.0'", conjunction1.getChild(0).getText());
-    assertEquals("AND", conjunction1.getChild(1).getText());
-    assertEquals("(", conjunction1.getChild(2).getText());
+    assertEquals("ObstacleRadius = '2.0' ", conjunction1.getChild(0).getText());
+    assertEquals("AND ", conjunction1.getChild(1).getText());
+    assertEquals("( ", conjunction1.getChild(2).getText());
     assertEquals(
-        "BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
+        "BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
         conjunction1.getChild(3).getText());
     assertEquals(")", conjunction1.getChild(4).getText());
 
     final ParseTree sub = conjunction1.getChild(3);
     assertEquals(2, sub.getChildCount());
-    assertEquals("BarkleyModelSimulation", sub.getChild(0).getText());
-    assertEquals("WHICHHAS APROPERTYTimeStep='0.0016'", sub.getChild(1).getText());
+    assertEquals("BarkleyModelSimulation ", sub.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY TimeStep='0.0016'", sub.getChild(1).getText());
 
     final ParseTree con2 = sub.getChild(1).getChild(0);
     assertEquals(2, con2.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", con2.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", con2.getChild(0).getText());
     assertEquals("TimeStep='0.0016'", con2.getChild(1).getText());
 
     assertNotNull(sfq.filter);
@@ -3197,42 +3186,42 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'ANDBarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
+    final ParseTree filter = sfq.getChild(4);
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
     assertEquals(
-        "ObstacleRadius='2.0'ANDBarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
+        "ObstacleRadius = '2.0' AND BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
         filter.getChild(1).getText());
 
     final ParseTree conjunction1 = filter.getChild(1);
     assertEquals(3, conjunction1.getChildCount());
-    assertEquals("ObstacleRadius='2.0'", conjunction1.getChild(0).getText());
-    assertEquals("AND", conjunction1.getChild(1).getText());
+    assertEquals("ObstacleRadius = '2.0' ", conjunction1.getChild(0).getText());
+    assertEquals("AND ", conjunction1.getChild(1).getText());
     assertEquals(
-        "BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
+        "BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
         conjunction1.getChild(2).getText());
     final ParseTree filter_exp = conjunction1.getChild(2);
     assertEquals(2, filter_exp.getChildCount());
-    assertEquals("BarkleyModelSimulation", filter_exp.getChild(0).getText());
-    assertEquals("WHICHHAS APROPERTYTimeStep='0.0016'", filter_exp.getChild(1).getText());
+    assertEquals("BarkleyModelSimulation ", filter_exp.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY TimeStep='0.0016'", filter_exp.getChild(1).getText());
 
     final ParseTree sub = filter_exp.getChild(1);
     assertEquals(1, sub.getChildCount());
-    assertEquals("WHICHHAS APROPERTYTimeStep='0.0016'", sub.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY TimeStep='0.0016'", sub.getChild(0).getText());
 
     final ParseTree sub1 = sub.getChild(0);
     assertEquals(2, sub1.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", sub1.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", sub1.getChild(0).getText());
     assertEquals("TimeStep='0.0016'", sub1.getChild(1).getText());
 
     assertNotNull(sfq.filter);
@@ -3274,43 +3263,43 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'ANDAPROPERTYBarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND A PROPERTY BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
+    final ParseTree filter = sfq.getChild(4);
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
     assertEquals(
-        "ObstacleRadius='2.0'ANDAPROPERTYBarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
+        "ObstacleRadius = '2.0' AND A PROPERTY BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
         filter.getChild(1).getText());
 
     final ParseTree conjunction1 = filter.getChild(1);
     assertEquals(5, conjunction1.getChildCount());
-    assertEquals("ObstacleRadius='2.0'", conjunction1.getChild(0).getText());
-    assertEquals("AND", conjunction1.getChild(1).getText());
-    assertEquals("A", conjunction1.getChild(2).getText());
-    assertEquals("PROPERTY", conjunction1.getChild(3).getText());
+    assertEquals("ObstacleRadius = '2.0' ", conjunction1.getChild(0).getText());
+    assertEquals("AND ", conjunction1.getChild(1).getText());
+    assertEquals("A ", conjunction1.getChild(2).getText());
+    assertEquals("PROPERTY ", conjunction1.getChild(3).getText());
     assertEquals(
-        "BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
+        "BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
         conjunction1.getChild(4).getText());
     final ParseTree filter_exp = conjunction1.getChild(4);
-    assertEquals("BarkleyModelSimulation", filter_exp.getChild(0).getText());
-    assertEquals("WHICHHAS APROPERTYTimeStep='0.0016'", filter_exp.getChild(1).getText());
+    assertEquals("BarkleyModelSimulation ", filter_exp.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY TimeStep='0.0016'", filter_exp.getChild(1).getText());
 
     final ParseTree sub = filter_exp.getChild(1);
     assertEquals(1, sub.getChildCount());
-    assertEquals("WHICHHAS APROPERTYTimeStep='0.0016'", sub.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY TimeStep='0.0016'", sub.getChild(0).getText());
 
     final ParseTree sub1 = sub.getChild(0);
     assertEquals(2, sub1.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", sub1.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", sub1.getChild(0).getText());
     assertEquals("TimeStep='0.0016'", sub1.getChild(1).getText());
 
     assertNotNull(sfq.filter);
@@ -3352,30 +3341,31 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'ANDAPROPERTY(BarkleyModelSimulation)",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND A PROPERTY ( BarkleyModelSimulation )",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
+    final ParseTree filter = sfq.getChild(4);
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
     assertEquals(
-        "ObstacleRadius='2.0'ANDAPROPERTY(BarkleyModelSimulation)", filter.getChild(1).getText());
+        "ObstacleRadius = '2.0' AND A PROPERTY ( BarkleyModelSimulation )",
+        filter.getChild(1).getText());
 
     final ParseTree conjunction1 = filter.getChild(1);
     assertEquals(7, conjunction1.getChildCount());
-    assertEquals("ObstacleRadius='2.0'", conjunction1.getChild(0).getText());
-    assertEquals("AND", conjunction1.getChild(1).getText());
-    assertEquals("A", conjunction1.getChild(2).getText());
-    assertEquals("PROPERTY", conjunction1.getChild(3).getText());
-    assertEquals("(", conjunction1.getChild(4).getText());
-    assertEquals("BarkleyModelSimulation", conjunction1.getChild(5).getText());
+    assertEquals("ObstacleRadius = '2.0' ", conjunction1.getChild(0).getText());
+    assertEquals("AND ", conjunction1.getChild(1).getText());
+    assertEquals("A ", conjunction1.getChild(2).getText());
+    assertEquals("PROPERTY ", conjunction1.getChild(3).getText());
+    assertEquals("( ", conjunction1.getChild(4).getText());
+    assertEquals("BarkleyModelSimulation ", conjunction1.getChild(5).getText());
     assertEquals(")", conjunction1.getChild(6).getText());
 
     assertNotNull(sfq.filter);
@@ -3412,33 +3402,33 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, entity_filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHHAS APROPERTYpname1=val1AND(pname2)", sfq.getChild(2).getText());
+    assertEquals("WHICH HAS A PROPERTY pname1=val1 AND ( pname2 )", sfq.getChild(3).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(2);
+    final ParseTree filter = sfq.getChild(3);
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
-    assertEquals("pname1=val1AND(pname2)", filter.getChild(1).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
+    assertEquals("pname1=val1 AND ( pname2 )", filter.getChild(1).getText());
 
     final ParseTree conjunction1 = filter.getChild(1);
     assertEquals(5, conjunction1.getChildCount());
-    assertEquals("pname1=val1", conjunction1.getChild(0).getText());
-    assertEquals("AND", conjunction1.getChild(1).getText());
-    assertEquals("(", conjunction1.getChild(2).getText());
-    assertEquals("pname2", conjunction1.getChild(3).getText());
+    assertEquals("pname1=val1 ", conjunction1.getChild(0).getText());
+    assertEquals("AND ", conjunction1.getChild(1).getText());
+    assertEquals("( ", conjunction1.getChild(2).getText());
+    assertEquals("pname2 ", conjunction1.getChild(3).getText());
     assertEquals(")", conjunction1.getChild(4).getText());
 
     final ParseTree sub = conjunction1.getChild(3);
     assertEquals(1, sub.getChildCount());
-    assertEquals("pname2", sub.getChild(0).getText());
+    assertEquals("pname2 ", sub.getChild(0).getText());
 
     final ParseTree sub1 = sub.getChild(0);
     assertEquals(1, sub1.getChildCount());
-    assertEquals("pname2", sub1.getChild(0).getText());
+    assertEquals("pname2 ", sub1.getChild(0).getText());
 
     assertNotNull(sfq.filter);
     assertEquals(Conjunction.class.getName(), sfq.filter.getClass().getName());
@@ -3469,14 +3459,14 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'ANDAPROPERTY(BarkleyModelSimulationWHICHHAS ATimeStep='0.0016')",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND A PROPERTY ( BarkleyModelSimulation WHICH HAS A TimeStep='0.0016')",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
 
     assertNotNull(sfq.filter);
@@ -3512,14 +3502,14 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYObstacleRadius='2.0'ANDAPROPERTY(BarkleyModelSimulationWHICHHASTimeStep='0.0016')",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ObstacleRadius = '2.0' AND A PROPERTY ( BarkleyModelSimulation WHICH HAS TimeStep='0.0016')",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
   }
 
@@ -3576,14 +3566,14 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, RECORD, entity, entity_filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, RECORD, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("ticket147_FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYticket147_ObstacleRadius='2.0'ANDAPROPERTY(ticket147_BarkleyModelSimulationWHICHHASticket147_TimeStep='0.0016')",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ticket147_ObstacleRadius = '2.0' AND A PROPERTY ( ticket147_BarkleyModelSimulation WHICH HAS ticket147_TimeStep='0.0016')",
+        sfq.getChild(4).getText());
     assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
   }
 
@@ -3604,7 +3594,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("'Some name with spaces and 1234 numbers'", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
@@ -3629,7 +3619,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("\"Some name with spaces and 1234 numbers\"", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
@@ -3654,7 +3644,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("'Some name with spaces and 1234 numbers and \"'", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
@@ -3680,7 +3670,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("'Some name with spaces and 1234 numbers and \\*'", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
@@ -3705,7 +3695,7 @@ public class TestCQL {
 
     // 3 children: FIND, entity, EOF
     assertEquals(3, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("'Some name with spaces and 1234 numbers and *'", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_LIKE, sfq.e.type);
 
@@ -3714,7 +3704,7 @@ public class TestCQL {
   }
 
   /*
-   * String query28 = "FIND ename . pname=2.0";
+   * String query28 = "FIND ename . pname=2.02";
    */
   @Test
   public void testQuery28()
@@ -3727,20 +3717,21 @@ public class TestCQL {
     final CqContext sfq = parser.cq();
 
     System.out.println(sfq.toStringTree(parser));
+    assertEquals("POV(pname,=,2.02)", sfq.filter.toString());
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals(".pname=2.0", sfq.getChild(2).getText());
+    assertEquals(". pname=2.02", sfq.getChild(3).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
-    assertEquals(2, sfq.getChild(2).getChildCount());
-    assertEquals(".", sfq.getChild(2).getChild(0).getText());
-    assertEquals("pname=2.0", sfq.getChild(2).getChild(1).getText());
-    assertEquals(1, sfq.getChild(2).getChild(1).getChildCount());
-    assertEquals("pname=2.0", sfq.getChild(2).getChild(1).getChild(0).getText());
-    assertEquals("2.0", sfq.getChild(2).getChild(1).getChild(0).getChild(2).getText());
+    assertEquals(2, sfq.getChild(3).getChildCount());
+    assertEquals(". ", sfq.getChild(3).getChild(0).getText());
+    assertEquals("pname=2.02", sfq.getChild(3).getChild(1).getText());
+    assertEquals(1, sfq.getChild(3).getChild(1).getChildCount());
+    assertEquals("pname=2.02", sfq.getChild(3).getChild(1).getChild(0).getText());
+    assertEquals("2.02", sfq.getChild(3).getChild(1).getChild(0).getChild(2).getText());
   }
 
   /*
@@ -3757,58 +3748,88 @@ public class TestCQL {
     final CqContext sfq = parser.cq();
 
     System.out.println(sfq.toStringTree(parser));
+    assertEquals("POV(pname,=,2.0prop=test)", sfq.filter.toString());
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals(".pname=2.0prop=test", sfq.getChild(2).getText());
+    assertEquals(". pname=2.0prop=test", sfq.getChild(3).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
+  }
 
-    assertEquals(2, sfq.getChild(2).getChildCount());
-    assertEquals(".", sfq.getChild(2).getChild(0).getText());
-    assertEquals("pname=2.0prop=test", sfq.getChild(2).getChild(1).getText());
-    assertEquals(2, sfq.getChild(2).getChild(1).getChildCount());
-    assertEquals("pname=2", sfq.getChild(2).getChild(1).getChild(0).getText());
-    assertEquals(".0prop=test", sfq.getChild(2).getChild(1).getChild(1).getText());
-    assertEquals("2", sfq.getChild(2).getChild(1).getChild(0).getChild(2).getText());
-    assertEquals(1, sfq.getChild(2).getChild(1).getChild(1).getChildCount());
-    assertEquals(".0prop=test", sfq.getChild(2).getChild(1).getChild(1).getChild(0).getText());
-    assertEquals(2, sfq.getChild(2).getChild(1).getChild(1).getChild(0).getChildCount());
-    assertEquals(".", sfq.getChild(2).getChild(1).getChild(1).getChild(0).getChild(0).getText());
-    assertEquals(
-        "0prop=test",
-        sfq.getChild(2).getChild(1).getChild(1).getChild(0).getChild(1).getChild(0).getText());
-    assertEquals(
-        "0prop",
-        sfq.getChild(2)
-            .getChild(1)
-            .getChild(1)
-            .getChild(0)
-            .getChild(1)
-            .getChild(0)
-            .getChild(0)
-            .getText());
-    assertEquals(
-        "=",
-        sfq.getChild(2)
-            .getChild(1)
-            .getChild(1)
-            .getChild(0)
-            .getChild(1)
-            .getChild(0)
-            .getChild(1)
-            .getText());
-    assertEquals(
-        "test",
-        sfq.getChild(2)
-            .getChild(1)
-            .getChild(1)
-            .getChild(0)
-            .getChild(1)
-            .getChild(0)
-            .getChild(2)
-            .getText());
+  /** String query28b = "FIND ename . pname = 1.02m"; */
+  @Test
+  public void testQuery28b()
+      throws InterruptedException, SQLException, ConnectionException, QueryException {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query28b));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+    assertEquals("POV(pname,=,1.02m)", sfq.filter.toString());
+
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("ename", sfq.getChild(1).getText());
+    assertEquals(". pname = 1.02m", sfq.getChild(3).getText());
+    assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
+
+    assertEquals(2, sfq.getChild(3).getChildCount());
+    assertEquals(". ", sfq.getChild(3).getChild(0).getText());
+    assertEquals("pname = 1.02m", sfq.getChild(3).getChild(1).getText());
+    assertEquals(1, sfq.getChild(3).getChild(1).getChildCount());
+    assertEquals("pname = 1.02m", sfq.getChild(3).getChild(1).getChild(0).getText());
+    assertEquals("1.02m", sfq.getChild(3).getChild(1).getChild(0).getChild(3).getText());
+  }
+
+  /** String query28c = "FIND ename . pname = .02"; */
+  @Test
+  public void testQuery28c()
+      throws InterruptedException, SQLException, ConnectionException, QueryException {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query28c));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+    assertEquals("POV(pname,=,.02)", sfq.filter.toString());
+  }
+
+  /** String query28d = "FIND ename . pname =.02m"; */
+  @Test
+  public void testQuery28d()
+      throws InterruptedException, SQLException, ConnectionException, QueryException {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query28d));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+    assertEquals("POV(pname,=,.02m)", sfq.filter.toString());
+  }
+
+  /** String query28e = "FIND ename . pname =.02 1/m^2"; */
+  @Test
+  public void testQuery28e()
+      throws InterruptedException, SQLException, ConnectionException, QueryException {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query28e));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+    assertEquals("POV(pname,=,.02 1/m^2)", sfq.filter.toString());
   }
 
   /*
@@ -3828,45 +3849,45 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTY(BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016')ANDAPROPERTYMinPeakHeight='0.5'",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY ( BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016') AND A PROPERTY MinPeakHeight = '0.5'",
+        sfq.getChild(4).getText());
 
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
-    // 2 children: WHICH, conjunction
+    final ParseTree filter = sfq.getChild(4);
+    // 2 children: WHICH , conjunction
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
     assertEquals(
-        "(BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016')ANDAPROPERTYMinPeakHeight='0.5'",
+        "( BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016') AND A PROPERTY MinPeakHeight = '0.5'",
         filter.getChild(1).getText());
 
     final ParseTree conjunction = filter.getChild(1);
     // 7 children: (, filter, ), AND, A, PROPERTY, filter
     assertEquals(7, conjunction.getChildCount());
-    assertEquals("(", conjunction.getChild(0).getText());
+    assertEquals("( ", conjunction.getChild(0).getText());
     assertEquals(
-        "BarkleyModelSimulationWHICHHAS APROPERTYTimeStep='0.0016'",
+        "BarkleyModelSimulation WHICH HAS A PROPERTY TimeStep='0.0016'",
         conjunction.getChild(1).getText());
-    assertEquals(")", conjunction.getChild(2).getText());
-    assertEquals("AND", conjunction.getChild(3).getText());
-    assertEquals("A", conjunction.getChild(4).getText());
-    assertEquals("PROPERTY", conjunction.getChild(5).getText());
-    assertEquals("MinPeakHeight='0.5'", conjunction.getChild(6).getText());
+    assertEquals(") ", conjunction.getChild(2).getText());
+    assertEquals("AND ", conjunction.getChild(3).getText());
+    assertEquals("A ", conjunction.getChild(4).getText());
+    assertEquals("PROPERTY ", conjunction.getChild(5).getText());
+    assertEquals("MinPeakHeight = '0.5'", conjunction.getChild(6).getText());
 
     final ParseTree pov1 = conjunction.getChild(1).getChild(0);
-    assertEquals("BarkleyModelSimulation", pov1.getText());
+    assertEquals("BarkleyModelSimulation ", pov1.getText());
     final ParseTree subp = conjunction.getChild(1).getChild(1);
-    assertEquals("WHICHHAS APROPERTYTimeStep='0.0016'", subp.getText());
+    assertEquals("WHICH HAS A PROPERTY TimeStep='0.0016'", subp.getText());
     final ParseTree pov2 = subp.getChild(0).getChild(1);
     assertEquals("TimeStep='0.0016'", pov2.getText());
     final ParseTree pov3 = conjunction.getChild(6).getChild(0);
-    assertEquals("MinPeakHeight='0.5'", pov3.getText());
+    assertEquals("MinPeakHeight = '0.5'", pov3.getText());
 
     assertEquals(sfq.e.toString(), "FrequencyMeasurement");
     assertEquals(sfq.r, Query.Role.RECORD);
@@ -3901,44 +3922,44 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, role, entity, filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, role, entity, WHITE_SPACE, filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        ".(BarkleyModelSimulation.TimeStep='0.0016')ANDAPROPERTYMinPeakHeight='0.5'",
-        sfq.getChild(3).getText());
+        ". ( BarkleyModelSimulation . TimeStep='0.0016') AND A PROPERTY MinPeakHeight = '0.5'",
+        sfq.getChild(4).getText());
 
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
-    // 2 children: WHICH, conjunction
+    final ParseTree filter = sfq.getChild(4);
+    // 2 children: WHICH , conjunction
     assertEquals(2, filter.getChildCount());
-    assertEquals(".", filter.getChild(0).getText());
+    assertEquals(". ", filter.getChild(0).getText());
     assertEquals(
-        "(BarkleyModelSimulation.TimeStep='0.0016')ANDAPROPERTYMinPeakHeight='0.5'",
+        "( BarkleyModelSimulation . TimeStep='0.0016') AND A PROPERTY MinPeakHeight = '0.5'",
         filter.getChild(1).getText());
 
     final ParseTree conjunction = filter.getChild(1);
     // 7 children: (, filter, ), AND, A, PROPERTY, filter
     assertEquals(7, conjunction.getChildCount());
-    assertEquals("(", conjunction.getChild(0).getText());
-    assertEquals("BarkleyModelSimulation.TimeStep='0.0016'", conjunction.getChild(1).getText());
-    assertEquals(")", conjunction.getChild(2).getText());
-    assertEquals("AND", conjunction.getChild(3).getText());
-    assertEquals("A", conjunction.getChild(4).getText());
-    assertEquals("PROPERTY", conjunction.getChild(5).getText());
-    assertEquals("MinPeakHeight='0.5'", conjunction.getChild(6).getText());
+    assertEquals("( ", conjunction.getChild(0).getText());
+    assertEquals("BarkleyModelSimulation . TimeStep='0.0016'", conjunction.getChild(1).getText());
+    assertEquals(") ", conjunction.getChild(2).getText());
+    assertEquals("AND ", conjunction.getChild(3).getText());
+    assertEquals("A ", conjunction.getChild(4).getText());
+    assertEquals("PROPERTY ", conjunction.getChild(5).getText());
+    assertEquals("MinPeakHeight = '0.5'", conjunction.getChild(6).getText());
 
     final ParseTree pov1 = conjunction.getChild(1).getChild(0);
-    assertEquals("BarkleyModelSimulation", pov1.getText());
+    assertEquals("BarkleyModelSimulation ", pov1.getText());
     final ParseTree subp = conjunction.getChild(1).getChild(1);
-    assertEquals(".TimeStep='0.0016'", subp.getText());
+    assertEquals(". TimeStep='0.0016'", subp.getText());
     final ParseTree pov2 = subp.getChild(0).getChild(1);
     assertEquals("TimeStep='0.0016'", pov2.getText());
     final ParseTree pov3 = conjunction.getChild(6).getChild(0);
-    assertEquals("MinPeakHeight='0.5'", pov3.getText());
+    assertEquals("MinPeakHeight = '0.5'", pov3.getText());
 
     assertEquals(sfq.e.toString(), "FrequencyMeasurement");
     assertEquals(sfq.r, Query.Role.RECORD);
@@ -3972,47 +3993,47 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 5 children: FIND, role, entity, filter, EOF
-    assertEquals(5, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    // 6 children: FIND, role, entity, WHITE_SPACE, filter, EOF
+    assertEquals(6, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
     assertEquals("FrequencyMeasurement", sfq.getChild(2).getText());
     assertEquals(
-        "WHICHHAS APROPERTYMinPeakHeight='0.5'ANDAPROPERTY(BarkleyModelSimulation.TimeStep='0.0016')",
-        sfq.getChild(3).getText());
+        "WHICH HAS A PROPERTY MinPeakHeight = '0.5' AND A PROPERTY ( BarkleyModelSimulation . TimeStep='0.0016')",
+        sfq.getChild(4).getText());
 
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(3);
-    // 2 children: WHICH, conjunction,
+    final ParseTree filter = sfq.getChild(4);
+    // 2 children: WHICH , conjunction,
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICHHAS APROPERTY", filter.getChild(0).getText());
+    assertEquals("WHICH HAS A PROPERTY ", filter.getChild(0).getText());
     assertEquals(
-        "MinPeakHeight='0.5'ANDAPROPERTY(BarkleyModelSimulation.TimeStep='0.0016')",
+        "MinPeakHeight = '0.5' AND A PROPERTY ( BarkleyModelSimulation . TimeStep='0.0016')",
         filter.getChild(1).getText());
 
     final ParseTree conjunction = filter.getChild(1);
 
     // 7 children: filter_exp, AND, A, PROPERTY, (, filter_exp, )
     assertEquals(7, conjunction.getChildCount());
-    assertEquals("MinPeakHeight='0.5'", conjunction.getChild(0).getText());
-    assertEquals("AND", conjunction.getChild(1).getText());
-    assertEquals("A", conjunction.getChild(2).getText());
-    assertEquals("PROPERTY", conjunction.getChild(3).getText());
-    assertEquals("(", conjunction.getChild(4).getText());
-    assertEquals("BarkleyModelSimulation.TimeStep='0.0016'", conjunction.getChild(5).getText());
+    assertEquals("MinPeakHeight = '0.5' ", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(1).getText());
+    assertEquals("A ", conjunction.getChild(2).getText());
+    assertEquals("PROPERTY ", conjunction.getChild(3).getText());
+    assertEquals("( ", conjunction.getChild(4).getText());
+    assertEquals("BarkleyModelSimulation . TimeStep='0.0016'", conjunction.getChild(5).getText());
     assertEquals(")", conjunction.getChild(6).getText());
 
     final ParseTree pov1 = conjunction.getChild(0).getChild(0);
-    assertEquals(3, pov1.getChildCount());
-    assertEquals("MinPeakHeight", pov1.getChild(0).getText());
+    assertEquals(4, pov1.getChildCount());
+    assertEquals("MinPeakHeight ", pov1.getChild(0).getText());
     assertEquals("=", pov1.getChild(1).getText());
-    assertEquals("'0.5'", pov1.getChild(2).getText());
+    assertEquals("'0.5' ", pov1.getChild(3).getText());
 
     final ParseTree ref = conjunction.getChild(5);
     assertEquals(2, ref.getChildCount());
-    assertEquals("BarkleyModelSimulation", ref.getChild(0).getText());
-    assertEquals(".TimeStep='0.0016'", ref.getChild(1).getText());
+    assertEquals("BarkleyModelSimulation ", ref.getChild(0).getText());
+    assertEquals(". TimeStep='0.0016'", ref.getChild(1).getText());
 
     // this is a pov
     final ParseTree subp = ref.getChild(1).getChild(0).getChild(1).getChild(0);
@@ -4034,25 +4055,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHpnameIS NULL", sfq.getChild(2).getText());
+    assertEquals("WHICH pname IS NULL", sfq.getChild(3).getText());
 
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(2);
-    // 2 children: WHICH, pov,
+    final ParseTree filter = sfq.getChild(3);
+    // 2 children: WHICH , pov,
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICH", filter.getChild(0).getText());
-    assertEquals("pnameIS NULL", filter.getChild(1).getText());
+    assertEquals("WHICH ", filter.getChild(0).getText());
+    assertEquals("pname IS NULL", filter.getChild(1).getText());
 
     final ParseTree conjunction = filter.getChild(1).getChild(0);
 
     // 2 children: pname, IS_NOT_NULL
     assertEquals(2, conjunction.getChildCount());
-    assertEquals("pname", conjunction.getChild(0).getText());
+    assertEquals("pname ", conjunction.getChild(0).getText());
     assertEquals("IS NULL", conjunction.getChild(1).getText());
     assertEquals("POV(pname,0,null)", sfq.filter.toString());
   }
@@ -4069,25 +4090,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHpnameIS NOT NULL", sfq.getChild(2).getText());
+    assertEquals("WHICH pname IS NOT NULL", sfq.getChild(3).getText());
 
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
-    final ParseTree filter = sfq.getChild(2);
-    // 2 children: WHICH, pov,
+    final ParseTree filter = sfq.getChild(3);
+    // 2 children: WHICH , pov,
     assertEquals(2, filter.getChildCount());
-    assertEquals("WHICH", filter.getChild(0).getText());
-    assertEquals("pnameIS NOT NULL", filter.getChild(1).getText());
+    assertEquals("WHICH ", filter.getChild(0).getText());
+    assertEquals("pname IS NOT NULL", filter.getChild(1).getText());
 
     final ParseTree conjunction = filter.getChild(1).getChild(0);
 
     // 2 children: pname, IS_NOT_NULL
     assertEquals(2, conjunction.getChildCount());
-    assertEquals("pname", conjunction.getChild(0).getText());
+    assertEquals("pname ", conjunction.getChild(0).getText());
     assertEquals("IS NOT NULL", conjunction.getChild(1).getText());
     assertEquals("POV(pname,!0,null)", sfq.filter.toString());
   }
@@ -4104,12 +4125,6 @@ public class TestCQL {
       final String on,
       final String date) {
 
-    if (neg && !sugar.contains("N'T")) {
-      sugar = sugar.replaceFirst("\\s", "");
-    }
-    if (someone_else != null) {
-      someone_else = someone_else.replace(" ", "");
-    }
     CQLLexer lexer;
     lexer = new CQLLexer(CharStreams.fromString(query));
     final CommonTokenStream tokens = new CommonTokenStream(lexer);
@@ -4117,20 +4132,22 @@ public class TestCQL {
     final CQLParser parser = new CQLParser(tokens);
     final CqContext sfq = parser.cq();
 
-    // 4 children: FIND, ename, FILTER, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, FILTER, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(
-        "WHICH"
+        "WHICH "
             + sugar
+            + " "
             + transaction
+            + " "
             + (by != null ? by : "")
-            + (someone_else != null ? someone_else : "")
-            + (but != null ? but : "")
-            + (person != null ? person : "")
-            + (on != null ? on + date : ""),
-        sfq.getChild(2).getText());
+            + (someone_else != null ? " " + someone_else : "")
+            + (but != null ? " " + but : "")
+            + (person != null ? " " + person : "")
+            + (on != null ? " " + on + " " + date : ""),
+        sfq.getChild(3).getText());
   }
 
   @Test
@@ -4298,26 +4315,27 @@ public class TestCQL {
     System.out.println(sfq.toStringTree(parser));
 
     // 4 children: FIND, entity, filter, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("RECORD", sfq.getChild(1).getText());
-    assertEquals(".CREATEDBY'henrik.tomwoerden'ANDTHEGREATESTID", sfq.getChild(2).getText());
+    assertEquals(".CREATED BY 'henrik.tomwoerden' AND THE GREATEST ID", sfq.getChild(2).getText());
 
     assertNull(sfq.e);
+    assertEquals(4, sfq.getChildCount());
 
     final ParseTree filter = sfq.getChild(2);
-    // 2 children: WHICH, conjunction,
+    // 2 children: WHICH , conjunction,
     assertEquals(2, filter.getChildCount());
     assertEquals(".", filter.getChild(0).getText());
-    assertEquals("CREATEDBY'henrik.tomwoerden'ANDTHEGREATESTID", filter.getChild(1).getText());
+    assertEquals(
+        "CREATED BY 'henrik.tomwoerden' AND THE GREATEST ID", filter.getChild(1).getText());
 
     final ParseTree conjunction = filter.getChild(1);
 
-    // 3 children: transaction, AND, id_filter
-    assertEquals(3, conjunction.getChildCount());
-    assertEquals("CREATEDBY'henrik.tomwoerden'", conjunction.getChild(0).getText());
-    assertEquals("AND", conjunction.getChild(1).getText());
-    assertEquals("THEGREATESTID", conjunction.getChild(2).getText());
+    // 4 children: transaction, WHITE_SPACE, AND, id_filter
+    assertEquals(4, conjunction.getChildCount());
+    assertEquals("CREATED BY 'henrik.tomwoerden'", conjunction.getChild(0).getText());
+    assertEquals("AND ", conjunction.getChild(2).getText());
+    assertEquals("THE GREATEST ID", conjunction.getChild(3).getText());
   }
 
   /** String query31 = "FIND PROPERTIES WHICH ARE INSERTED TODAY"; */
@@ -4334,22 +4352,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("PROPERTIES", sfq.getChild(1).getText());
-    assertEquals("WHICHWEREINSERTEDTODAY", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("PROPERTIES ", sfq.getChild(1).getText());
+    assertEquals("WHICH WERE INSERTED TODAY", sfq.getChild(2).getText());
     assertEquals(null, sfq.e);
     assertEquals(Query.Role.PROPERTY, sfq.r);
     assertEquals("TransactionFilter", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, transaction
+    // 2 children: WHICH , transaction
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICHWERE", whichclause.getChild(0).getText());
-    assertEquals("INSERTEDTODAY", whichclause.getChild(1).getText());
+    assertEquals("WHICH WERE ", whichclause.getChild(0).getText());
+    assertEquals("INSERTED TODAY", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("INSERTED", transactionFilter.getChild(0).getText());
+    assertEquals("INSERTED ", transactionFilter.getChild(0).getText());
     assertEquals("TODAY", transactionFilter.getChild(1).getText());
   }
 
@@ -4368,7 +4386,7 @@ public class TestCQL {
 
     // 4 children: FIND, EMPTY_SPACE, entity, entity_filter
     assertEquals(4, sfq.getChildCount());
-    assertEquals("COUNT", sfq.getChild(0).getText());
+    assertEquals("COUNT ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
     assertEquals(Pattern.TYPE_NORMAL, sfq.e.type);
 
@@ -4421,16 +4439,16 @@ public class TestCQL {
 
     // 4 children: FIND, EMPTY_SPACE, entity, entity_filter
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("Entity", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("Entity ", sfq.getChild(1).getText());
 
     // entity_filter
-    assertEquals(".pname=2.0", sfq.getChild(2).getText());
+    assertEquals(". pname=2.0", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals(".", entity_filter.getChild(0).getText());
+    assertEquals(". ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname=2.0", entity_filter.getChild(1).getText());
@@ -4475,35 +4493,36 @@ public class TestCQL {
 
     // 4 children: FIND, RECORD, entity_filter, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("Record", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("Record ", sfq.getChild(1).getText());
 
     // entity_filter
     assertEquals(
-        "WHICHIS REFERENCEDBYannotationWITHcomment='blablabla'", sfq.getChild(2).getText());
+        "WHICH IS REFERENCED BY annotation WITH comment='blablabla'", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, filter
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
 
     // filter
     assertEquals(
-        "IS REFERENCEDBYannotationWITHcomment='blablabla'", entity_filter.getChild(1).getText());
+        "IS REFERENCED BY annotation WITH comment='blablabla'",
+        entity_filter.getChild(1).getText());
     final ParseTree filter = entity_filter.getChild(1);
 
     // 2 children: backref, subproperty
     assertEquals(2, filter.getChildCount());
-    assertEquals("IS REFERENCEDBYannotation", filter.getChild(0).getText());
-    assertEquals("WITHcomment='blablabla'", filter.getChild(1).getText());
+    assertEquals("IS REFERENCED BY annotation ", filter.getChild(0).getText());
+    assertEquals("WITH comment='blablabla'", filter.getChild(1).getText());
 
     // backref
     final ParseTree backref = filter.getChild(0);
 
     // 3 children: IS_REFERENCED, BY, entity
-    assertEquals(3, backref.getChildCount());
-    assertEquals("IS REFERENCED", backref.getChild(0).getText());
-    assertEquals("BY", backref.getChild(1).getText());
+    assertEquals(4, backref.getChildCount());
+    assertEquals("IS REFERENCED ", backref.getChild(0).getText());
+    assertEquals("BY ", backref.getChild(1).getText());
     assertEquals("annotation", backref.getChild(2).getText());
 
     // subproperty
@@ -4511,14 +4530,14 @@ public class TestCQL {
 
     // 1 children: subquery
     assertEquals(1, subp.getChildCount());
-    assertEquals("WITHcomment='blablabla'", subp.getChild(0).getText());
+    assertEquals("WITH comment='blablabla'", subp.getChild(0).getText());
 
     // subquery
     final ParseTree subquery = subp.getChild(0);
 
     // 2 children: WHICH_EXP filter
     assertEquals(2, subquery.getChildCount());
-    assertEquals("WITH", subquery.getChild(0).getText());
+    assertEquals("WITH ", subquery.getChild(0).getText());
     assertEquals("comment='blablabla'", subquery.getChild(1).getText());
 
     assertEquals(Query.Role.RECORD, sfq.r);
@@ -4547,16 +4566,16 @@ public class TestCQL {
 
     // 4 children: FIND, EMPTY_SPACE, entity, entity_filter
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("Entity", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("Entity ", sfq.getChild(1).getText());
 
     // entity_filter
-    assertEquals("WITHpname=-1", sfq.getChild(2).getText());
+    assertEquals("WITH pname=-1", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WITH", entity_filter.getChild(0).getText());
+    assertEquals("WITH ", entity_filter.getChild(0).getText());
 
     // conjunction
     assertEquals("pname=-1", entity_filter.getChild(1).getText());
@@ -4600,32 +4619,32 @@ public class TestCQL {
 
     // 4 children: FIND, EMPTY_SPACE, entity, entity_filter
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("RECORD", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("RECORD ", sfq.getChild(1).getText());
 
     // entity_filter
-    assertEquals("WHICH->10594", sfq.getChild(2).getText());
+    assertEquals("WHICH -> 10594", sfq.getChild(2).getText());
     final ParseTree entity_filter = sfq.getChild(2);
 
     // 2 children: WHICH_EXP, conjunction
     assertEquals(2, entity_filter.getChildCount());
-    assertEquals("WHICH", entity_filter.getChild(0).getText());
+    assertEquals("WHICH ", entity_filter.getChild(0).getText());
 
     // conjunction
-    assertEquals("->10594", entity_filter.getChild(1).getText());
+    assertEquals("-> 10594", entity_filter.getChild(1).getText());
     final ParseTree conjunction = entity_filter.getChild(1);
 
     // 1 child: pov
     assertEquals(1, conjunction.getChildCount());
-    assertEquals("->10594", conjunction.getChild(0).getText());
+    assertEquals("-> 10594", conjunction.getChild(0).getText());
 
     // pov
     final ParseTree pov1 = conjunction.getChild(0);
 
-    // 2 children: (no property), operator, value
-    assertEquals(2, pov1.getChildCount());
+    // 3 children: (no property), operator, WHITE_SPACE, value
+    assertEquals(3, pov1.getChildCount());
     assertEquals("->", pov1.getChild(0).getText());
-    assertEquals("10594", pov1.getChild(1).getText());
+    assertEquals("10594", pov1.getChild(2).getText());
 
     assertEquals(Query.Role.RECORD, sfq.r);
     assertNotNull(sfq.filter);
@@ -4650,25 +4669,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WITHadateIN2015", sfq.getChild(2).getText());
+    assertEquals("WITH a date IN 2015", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, transaction
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , transaction
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WITHa", whichclause.getChild(0).getText());
-    assertEquals("dateIN2015", whichclause.getChild(1).getText());
+    assertEquals("WITH a ", whichclause.getChild(0).getText());
+    assertEquals("date IN 2015", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("date", transactionFilter.getChild(0).getText());
-    assertEquals("IN", transactionFilter.getChild(1).getText());
+    assertEquals("date ", transactionFilter.getChild(0).getText());
+    assertEquals("IN ", transactionFilter.getChild(1).getText());
     assertEquals("2015", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4689,25 +4708,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, role, WHITE_SPACE, WHICHCLAUSE, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WITHadateIN\"2015\"", sfq.getChild(2).getText());
+    assertEquals("WITH a date IN \"2015\"", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children; WHICH, transaction
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children; WHICH , transaction
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WITHa", whichclause.getChild(0).getText());
-    assertEquals("dateIN\"2015\"", whichclause.getChild(1).getText());
+    assertEquals("WITH a ", whichclause.getChild(0).getText());
+    assertEquals("date IN \"2015\"", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("date", transactionFilter.getChild(0).getText());
-    assertEquals("IN", transactionFilter.getChild(1).getText());
+    assertEquals("date ", transactionFilter.getChild(0).getText());
+    assertEquals("IN ", transactionFilter.getChild(1).getText());
     assertEquals("\"2015\"", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4728,26 +4747,26 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WITHadateNOTIN2015", sfq.getChild(2).getText());
+    assertEquals("WITH a date NOT IN 2015", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WITHa", whichclause.getChild(0).getText());
-    assertEquals("dateNOTIN2015", whichclause.getChild(1).getText());
+    assertEquals("WITH a ", whichclause.getChild(0).getText());
+    assertEquals("date NOT IN 2015", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(4, transactionFilter.getChildCount());
-    assertEquals("date", transactionFilter.getChild(0).getText());
-    assertEquals("NOT", transactionFilter.getChild(1).getText());
-    assertEquals("IN", transactionFilter.getChild(2).getText());
+    assertEquals("date ", transactionFilter.getChild(0).getText());
+    assertEquals("NOT ", transactionFilter.getChild(1).getText());
+    assertEquals("IN ", transactionFilter.getChild(2).getText());
     assertEquals("2015", transactionFilter.getChild(3).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4768,26 +4787,26 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, role, WHITE_SPACE, WHICHCLAUSE, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WITHadateNOTIN\"2015\"", sfq.getChild(2).getText());
+    assertEquals("WITH a date NOT IN \"2015\"", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WITHa", whichclause.getChild(0).getText());
-    assertEquals("dateNOTIN\"2015\"", whichclause.getChild(1).getText());
+    assertEquals("WITH a ", whichclause.getChild(0).getText());
+    assertEquals("date NOT IN \"2015\"", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(4, transactionFilter.getChildCount());
-    assertEquals("date", transactionFilter.getChild(0).getText());
-    assertEquals("NOT", transactionFilter.getChild(1).getText());
-    assertEquals("IN", transactionFilter.getChild(2).getText());
+    assertEquals("date ", transactionFilter.getChild(0).getText());
+    assertEquals("NOT ", transactionFilter.getChild(1).getText());
+    assertEquals("IN ", transactionFilter.getChild(2).getText());
     assertEquals("\"2015\"", transactionFilter.getChild(3).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4808,25 +4827,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WITHadateIN2015-01-01", sfq.getChild(2).getText());
+    assertEquals("WITH a date IN 2015-01-01", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WITHa", whichclause.getChild(0).getText());
-    assertEquals("dateIN2015-01-01", whichclause.getChild(1).getText());
+    assertEquals("WITH a ", whichclause.getChild(0).getText());
+    assertEquals("date IN 2015-01-01", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("date", transactionFilter.getChild(0).getText());
-    assertEquals("IN", transactionFilter.getChild(1).getText());
+    assertEquals("date ", transactionFilter.getChild(0).getText());
+    assertEquals("IN ", transactionFilter.getChild(1).getText());
     assertEquals("2015-01-01", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4847,25 +4866,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, role, WHITE_SPACE, WHICHCLAUSE, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WITHadateIN\"2015-01-01\"", sfq.getChild(2).getText());
+    assertEquals("WITH a date IN \"2015-01-01\"", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WITHa", whichclause.getChild(0).getText());
-    assertEquals("dateIN\"2015-01-01\"", whichclause.getChild(1).getText());
+    assertEquals("WITH a ", whichclause.getChild(0).getText());
+    assertEquals("date IN \"2015-01-01\"", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("date", transactionFilter.getChild(0).getText());
-    assertEquals("IN", transactionFilter.getChild(1).getText());
+    assertEquals("date ", transactionFilter.getChild(0).getText());
+    assertEquals("IN ", transactionFilter.getChild(1).getText());
     assertEquals("\"2015-01-01\"", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4888,23 +4907,23 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals(".pnameLIKE\"wil*card\"", sfq.getChild(2).getText());
+    assertEquals(".pname LIKE \"wil*card\"", sfq.getChild(2).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
     assertEquals(".", whichclause.getChild(0).getText());
-    assertEquals("pnameLIKE\"wil*card\"", whichclause.getChild(1).getText());
+    assertEquals("pname LIKE \"wil*card\"", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("pname", transactionFilter.getChild(0).getText());
-    assertEquals("LIKE", transactionFilter.getChild(1).getText());
+    assertEquals("pname ", transactionFilter.getChild(0).getText());
+    assertEquals("LIKE ", transactionFilter.getChild(1).getText());
     assertEquals("\"wil*card\"", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4927,23 +4946,23 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals(".pnameLIKEwil*card", sfq.getChild(2).getText());
+    assertEquals(".pname LIKE wil*card", sfq.getChild(2).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
     assertEquals(".", whichclause.getChild(0).getText());
-    assertEquals("pnameLIKEwil*card", whichclause.getChild(1).getText());
+    assertEquals("pname LIKE wil*card", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("pname", transactionFilter.getChild(0).getText());
-    assertEquals("LIKE", transactionFilter.getChild(1).getText());
+    assertEquals("pname ", transactionFilter.getChild(0).getText());
+    assertEquals("LIKE ", transactionFilter.getChild(1).getText());
     assertEquals("wil*card", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -4964,25 +4983,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, ename, WHITE_SPACE, WHICHCLAUSE, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHHAS ApnameLIKE\"wil*\"", sfq.getChild(2).getText());
+    assertEquals("WHICH HAS A pname LIKE \"wil*\"", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICHHAS A", whichclause.getChild(0).getText());
-    assertEquals("pnameLIKE\"wil*\"", whichclause.getChild(1).getText());
+    assertEquals("WHICH HAS A ", whichclause.getChild(0).getText());
+    assertEquals("pname LIKE \"wil*\"", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("pname", transactionFilter.getChild(0).getText());
-    assertEquals("LIKE", transactionFilter.getChild(1).getText());
+    assertEquals("pname ", transactionFilter.getChild(0).getText());
+    assertEquals("LIKE ", transactionFilter.getChild(1).getText());
     assertEquals("\"wil*\"", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -5003,25 +5022,25 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHHAS ApnameLIKEwil*", sfq.getChild(2).getText());
+    assertEquals("WHICH HAS A pname LIKE wil*", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICHHAS A", whichclause.getChild(0).getText());
-    assertEquals("pnameLIKEwil*", whichclause.getChild(1).getText());
+    assertEquals("WHICH HAS A ", whichclause.getChild(0).getText());
+    assertEquals("pname LIKE wil*", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("pname", transactionFilter.getChild(0).getText());
-    assertEquals("LIKE", transactionFilter.getChild(1).getText());
+    assertEquals("pname ", transactionFilter.getChild(0).getText());
+    assertEquals("LIKE ", transactionFilter.getChild(1).getText());
     assertEquals("wil*", transactionFilter.getChild(2).getText());
 
     assertTrue(sfq.filter instanceof POV);
@@ -5042,26 +5061,26 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, role, WHICHCLAUSE, EOF
-    assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
+    // 5 children: FIND, entity, WHITE_SPACE, entity_filter, EOF
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("FIND ", sfq.getChild(0).getText());
     assertEquals("ename", sfq.getChild(1).getText());
-    assertEquals("WHICHHAS Apname=wil*", sfq.getChild(2).getText());
+    assertEquals("WHICH HAS A pname = wil*", sfq.getChild(3).getText());
     assertEquals("ename", sfq.e.toString());
     assertNull(sfq.r);
     assertEquals("POV", sfq.filter.getClass().getSimpleName());
 
-    final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    final ParseTree whichclause = sfq.getChild(3);
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICHHAS A", whichclause.getChild(0).getText());
-    assertEquals("pname=wil*", whichclause.getChild(1).getText());
+    assertEquals("WHICH HAS A ", whichclause.getChild(0).getText());
+    assertEquals("pname = wil*", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
-    assertEquals(3, transactionFilter.getChildCount());
-    assertEquals("pname", transactionFilter.getChild(0).getText());
+    assertEquals(4, transactionFilter.getChildCount());
+    assertEquals("pname ", transactionFilter.getChild(0).getText());
     assertEquals("=", transactionFilter.getChild(1).getText());
-    assertEquals("wil*", transactionFilter.getChild(2).getText());
+    assertEquals("wil*", transactionFilter.getChild(3).getText());
 
     assertTrue(sfq.filter instanceof POV);
     final POV pov = (POV) sfq.filter;
@@ -5083,22 +5102,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/data/bla.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /data/bla.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/data/bla.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /data/bla.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/data/bla.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5120,22 +5139,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/*", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /*", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/*", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /*", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/*", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5158,22 +5177,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/*/", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /*/", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/*/", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /*/", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/*/", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5196,22 +5215,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/**/", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /**/", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/**/", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /**/", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/**/", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5233,22 +5252,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/**/*", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /**/*", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/**/*", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /**/*", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/**/*", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5270,22 +5289,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/*/*", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /*/*", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/*/*", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /*/*", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/*/*", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5307,22 +5326,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/*/*.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /*/*.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/*/*.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /*/*.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/*/*.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5344,22 +5363,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/**/*.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /**/*.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/**/*.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /**/*.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/**/*.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5381,22 +5400,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/*.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /*.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/*.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /*.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/*.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5418,22 +5437,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT*.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT *.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT*.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT *.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("*.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5455,22 +5474,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT*", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT *", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT*", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT *", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("*", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5600,22 +5619,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT*%.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT *%.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT*%.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT *%.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("*%.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5637,22 +5656,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT*\\*.acq", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT *\\*.acq", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT*\\*.acq", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT *\\*.acq", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("*\\*.acq", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -5688,8 +5707,8 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    assertTrue(sfq.filter instanceof TransactionFilter);
     assertEquals("TRANS(Insert,null,Transactor(some.user,=))", sfq.filter.toString());
+    assertTrue(sfq.filter instanceof TransactionFilter);
   }
 
   /** String ticket262a = "COUNT FILE which is not referenced"; */
@@ -5874,6 +5893,21 @@ public class TestCQL {
     final CqContext sfq = parser.cq();
 
     System.out.println(sfq.toStringTree(parser));
+    assertEquals(4, sfq.getChildCount());
+    assertEquals(
+        "WHICH IS NOT REFERENCED BY AN entity WHICH WAS created by me", sfq.getChild(2).getText());
+
+    ParseTree filter = sfq.getChild(2).getChild(1);
+    assertEquals(1, filter.getChildCount());
+    ParseTree negation = filter.getChild(0);
+    assertEquals(2, negation.getChildCount());
+    assertEquals("NOT ", negation.getChild(0).getText());
+    assertEquals("REFERENCED BY AN entity WHICH WAS created by me", negation.getChild(1).getText());
+    ParseTree refereced = negation.getChild(1).getChild(0);
+    assertEquals("REFERENCED ", refereced.getChild(0).getText());
+    assertEquals("BY ", refereced.getChild(1).getText());
+    assertEquals("AN ", refereced.getChild(2).getText());
+    assertEquals("entity ", refereced.getChild(3).getText());
 
     assertTrue(sfq.filter instanceof Negation);
     final EntityFilterInterface backref = ((Negation) sfq.filter).getFilter();
@@ -6127,22 +6161,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/dir/with/date/2016-05-15", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /dir/with/date/2016-05-15", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/dir/with/date/2016-05-15", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /dir/with/date/2016-05-15", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/dir/with/date/2016-05-15", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -6162,22 +6196,22 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
-    assertEquals("WHICHIS STORED AT/dir/with/date/2016-05-15/**", sfq.getChild(2).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
+    assertEquals("WHICH IS STORED AT /dir/with/date/2016-05-15/**", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
-    assertEquals("IS STORED AT/dir/with/date/2016-05-15/**", whichclause.getChild(1).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
+    assertEquals("IS STORED AT /dir/with/date/2016-05-15/**", whichclause.getChild(1).getText());
 
     final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, transactionFilter.getChildCount());
-    assertEquals("IS STORED AT", transactionFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", transactionFilter.getChild(0).getText());
     assertEquals("/dir/with/date/2016-05-15/**", transactionFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -6320,25 +6354,26 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
     assertEquals(
-        "WHICHIS STORED AT'/SimulationData/2016_single/2018-01-10/**'", sfq.getChild(2).getText());
+        "WHICH IS STORED AT '/SimulationData/2016_single/2018-01-10/**'",
+        sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
     assertEquals(
-        "IS STORED AT'/SimulationData/2016_single/2018-01-10/**'",
+        "IS STORED AT '/SimulationData/2016_single/2018-01-10/**'",
         whichclause.getChild(1).getText());
 
     final ParseTree satFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, satFilter.getChildCount());
-    assertEquals("IS STORED AT", satFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", satFilter.getChild(0).getText());
     assertEquals("'/SimulationData/2016_single/2018-01-10/**'", satFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -6357,24 +6392,25 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("FIND", sfq.getChild(0).getText());
-    assertEquals("FILE", sfq.getChild(1).getText());
+    assertEquals("FIND ", sfq.getChild(0).getText());
+    assertEquals("FILE ", sfq.getChild(1).getText());
     assertEquals(
-        "WHICHIS STORED AT/SimulationData/2016_single/2018-01-10/**", sfq.getChild(2).getText());
+        "WHICH IS STORED AT /SimulationData/2016_single/2018-01-10/**", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
 
     final ParseTree whichclause = sfq.getChild(2);
-    // 2 children: WHICH, POV
+    // 2 children: WHICH , POV
     assertEquals(2, whichclause.getChildCount());
-    assertEquals("WHICH", whichclause.getChild(0).getText());
+    assertEquals("WHICH ", whichclause.getChild(0).getText());
     assertEquals(
-        "IS STORED AT/SimulationData/2016_single/2018-01-10/**", whichclause.getChild(1).getText());
+        "IS STORED AT /SimulationData/2016_single/2018-01-10/**",
+        whichclause.getChild(1).getText());
 
     final ParseTree satFilter = whichclause.getChild(1).getChild(0);
     assertEquals(2, satFilter.getChildCount());
-    assertEquals("IS STORED AT", satFilter.getChild(0).getText());
+    assertEquals("IS STORED AT ", satFilter.getChild(0).getText());
     assertEquals("/SimulationData/2016_single/2018-01-10/**", satFilter.getChild(1).getText());
 
     assertTrue(sfq.filter instanceof StoredAt);
@@ -6396,8 +6432,7 @@ public class TestCQL {
   }
 
   /** String queryIssue31 = "FIND FILE WHICH IS STORED AT /data/in0.foo"; */
-  // FIXME Remove "expected" annotation.
-  @Test(expected = AssertionError.class)
+  @Test
   public void testIssue31() {
     CQLLexer lexer;
     lexer = new CQLLexer(CharStreams.fromString(this.queryIssue31));
@@ -6410,7 +6445,7 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("WHICHIS STORED AT/data/in0.foo", sfq.getChild(2).getText());
+    assertEquals("WHICH IS STORED AT /data/in0.foo", sfq.getChild(2).getText());
     assertEquals("FILE", sfq.r.toString());
     assertNull(sfq.e);
     assertEquals("StoredAt", sfq.filter.getClass().getSimpleName());
@@ -6441,14 +6476,14 @@ public class TestCQL {
 
     // 4 children: FIND, role, WHICHCLAUSE, EOF
     assertEquals(4, sfq.getChildCount());
-    assertEquals("WITH((p0=v0ORp1=v1)ANDp2=v2)", sfq.getChild(2).getText());
+    assertEquals("WITH ((p0 = v0 OR p1=v1) AND p2=v2)", sfq.getChild(2).getText());
     assertEquals("ENTITY", sfq.r.toString());
     assertEquals("Conjunction", sfq.filter.getClass().getSimpleName());
     final ParseTree whichclause = sfq.getChild(2);
     final ParseTree conjunction = whichclause.getChild(2);
-    assertEquals("(p0=v0ORp1=v1)ANDp2=v2", conjunction.getText());
+    assertEquals("(p0 = v0 OR p1=v1) AND p2=v2", conjunction.getText());
     final ParseTree disjunction = conjunction.getChild(1);
-    assertEquals("p0=v0ORp1=v1", disjunction.getText());
+    assertEquals("p0 = v0 OR p1=v1", disjunction.getText());
     final ParseTree pov = conjunction.getChild(4);
     assertEquals("p2=v2", pov.getText());
   }
@@ -6465,10 +6500,190 @@ public class TestCQL {
 
     System.out.println(sfq.toStringTree(parser));
 
-    // 4 children: FIND, version, role, entity, EOF
+    // 5 children: FIND, version, role, entity,  EOF
     assertEquals(5, sfq.getChildCount());
     assertEquals(VersionFilter.ANY_VERSION, sfq.v);
     assertEquals(Query.Role.ENTITY, sfq.r);
     assertEquals("e1", sfq.e.toString());
   }
+
+  /** String query57a = "FIND ENTITY"; */
+  @Test
+  public void testQuery57a() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query57a));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 3 children: FIND, ENTITY, EOF
+    assertEquals(3, sfq.getChildCount());
+    assertEquals(Query.Role.ENTITY, sfq.r);
+    assertNull(sfq.e);
+  }
+
+  /** String query57b = "FIND ENTITY WITH ID"; */
+  @Test
+  public void testQuery57b() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query57b));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 4 children: FIND, ENTITY, WHICHCLAUSE, EOF
+    assertEquals(4, sfq.getChildCount());
+    assertEquals(Query.Role.ENTITY, sfq.r);
+    assertNull(sfq.e);
+    assertEquals("WITH ID", sfq.getChild(2).getText());
+  }
+
+  /** String query57c = "FIND ENTITY WITH ID = 123"; */
+  @Test
+  public void testQuery57c() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query57c));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 4 children: FIND, ENTITY, WHICHCLAUSE, EOF
+    assertEquals(4, sfq.getChildCount());
+    assertEquals(Query.Role.ENTITY, sfq.r);
+    assertNull(sfq.e);
+    assertEquals("WITH ID = 123", sfq.getChild(2).getText());
+  }
+
+  /** String queryIssue116 = "FIND *"; */
+  @Test
+  public void testIssue116() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.queryIssue116));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 3 children: FIND, ENTITY, EOF
+    assertEquals(3, sfq.getChildCount());
+    assertNull(sfq.r);
+    assertTrue(sfq.e.type == Query.Pattern.TYPE_LIKE);
+    assertEquals("%", sfq.e.toString());
+  }
+
+  /** String query54e = "SELECT id FROM ename"; */
+  @Test
+  public void testQuery54e() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query54e));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    assertEquals(5, sfq.getChildCount());
+    assertEquals("SELECT ", sfq.getChild(0).getText());
+    assertEquals("id ", sfq.getChild(1).getText());
+    assertEquals("FROM ", sfq.getChild(2).getText());
+    assertEquals("ename", sfq.getChild(3).getText());
+    assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type);
+    assertEquals("ename", sfq.e.toString());
+    assertNull(sfq.r);
+    assertEquals(Query.Type.FIND, sfq.t);
+    assertNotNull(sfq.s);
+    assertFalse(sfq.s.isEmpty());
+    assertEquals(1, sfq.s.size());
+    assertEquals("id", sfq.s.get(0).toString());
+  }
+
+  /** String query58a = "FIND ENTITY WITH endswith"; */
+  @Test
+  public void testQuery58a() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query58a));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    assertEquals("POV(endswith,null,null)", sfq.filter.toString());
+  }
+
+  /** String query58b = "FIND ENTITY WITH endswith = val1"; */
+  @Test
+  public void testQuery58b() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query58b));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    assertEquals("POV(endswith,=,val1)", sfq.filter.toString());
+  }
+
+  /** String query58c = "FIND ENTITY WITH 0with = val1"; */
+  @Test
+  public void testQuery58c() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query58c));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    assertNull(((POV) sfq.filter).getSubProperty());
+    assertEquals("POV(0with,=,val1)", sfq.filter.toString());
+  }
+
+  /** String query58d = "FIND ENTITY WITH WITH"; */
+  @Test
+  public void testQuery58d() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query58d));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    assertEquals("POV(withdrawn,=,TRUE)", sfq.filter.toString());
+    assertNull(((POV) sfq.filter).getSubProperty());
+  }
+
+  /** String query58e = "FIND ENTITY WITH pname=with"; */
+  @Test
+  public void testQuery58e() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query58e));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    assertEquals("POV(pname,=,with)", sfq.filter.toString());
+    assertNull(((POV) sfq.filter).getSubProperty());
+  }
 }
diff --git a/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java b/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java
index 1dcf71d8f330ab6807e65bd23022138e941fca1c..5d81ca738d507bcfd75dbe137756135cede3989b 100644
--- a/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java
+++ b/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java
@@ -26,6 +26,7 @@ import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.misc.TransactionBenchmark;
 import org.caosdb.server.permissions.PermissionRule;
+import org.caosdb.server.utils.WebinterfaceUtils;
 import org.jdom2.Element;
 import org.junit.BeforeClass;
 import org.junit.Rule;
@@ -125,6 +126,11 @@ public class TestAbstractCaosDBServerResource {
             // TODO Auto-generated method stub
             return user;
           }
+
+          @Override
+          public WebinterfaceUtils getUtils() {
+            return WebinterfaceUtils.getInstance(getRootRef().toString());
+          }
         };
     provideUserSourcesFile();
     Element response = s.generateRootElement();
diff --git a/src/test/java/org/caosdb/server/transaction/UpdateTest.java b/src/test/java/org/caosdb/server/transaction/UpdateTest.java
index 6b0e7b3f988f18142c6ebdb535212cd7454233f0..d22fbea8982b1f8d88c9ccdf069a0c8a816add25 100644
--- a/src/test/java/org/caosdb/server/transaction/UpdateTest.java
+++ b/src/test/java/org/caosdb/server/transaction/UpdateTest.java
@@ -44,7 +44,7 @@ public class UpdateTest {
       throws NoSuchAlgorithmException, IOException, CaosDBException {
     final Entity newEntity = new Entity("Name");
     final Entity oldEntity = new Entity("Name");
-    Update.deriveUpdate(newEntity, oldEntity);
+    WriteTransaction.deriveUpdate(newEntity, oldEntity);
     assertEquals(newEntity.getEntityStatus(), EntityStatus.VALID);
   }
 
@@ -53,7 +53,7 @@ public class UpdateTest {
       throws NoSuchAlgorithmException, IOException, CaosDBException {
     final Entity newEntity = new Entity("NewName");
     final Entity oldEntity = new Entity("OldName");
-    Update.deriveUpdate(newEntity, oldEntity);
+    WriteTransaction.deriveUpdate(newEntity, oldEntity);
     assertEquals(newEntity.getEntityStatus(), EntityStatus.QUALIFIED);
   }
 
@@ -67,7 +67,7 @@ public class UpdateTest {
     final Entity oldEntity = new Entity();
     oldEntity.addProperty(oldProperty);
 
-    Update.deriveUpdate(newEntity, oldEntity);
+    WriteTransaction.deriveUpdate(newEntity, oldEntity);
     assertEquals(newEntity.getEntityStatus(), VALID);
   }
 
@@ -83,7 +83,7 @@ public class UpdateTest {
     final Entity oldEntity = new Entity();
     oldEntity.addProperty(oldProperty);
 
-    Update.deriveUpdate(newEntity, oldEntity);
+    WriteTransaction.deriveUpdate(newEntity, oldEntity);
     assertEquals(newEntity.getEntityStatus(), QUALIFIED);
     assertEquals(newProperty.getEntityStatus(), VALID);
     assertEquals(newProperty2.getEntityStatus(), QUALIFIED);
@@ -124,7 +124,7 @@ public class UpdateTest {
 
     oldEntity.addProperty(oldProperty);
 
-    Update.deriveUpdate(newEntity, oldEntity);
+    WriteTransaction.deriveUpdate(newEntity, oldEntity);
     assertEquals(newUnit.getEntityStatus(), VALID);
     assertEquals(newProperty.getEntityStatus(), VALID);
     assertEquals(newEntity.getEntityStatus(), VALID);
@@ -165,7 +165,7 @@ public class UpdateTest {
 
     oldEntity.addProperty(oldProperty);
 
-    Update.deriveUpdate(newEntity, oldEntity);
+    WriteTransaction.deriveUpdate(newEntity, oldEntity);
     assertEquals(newEntity.getEntityStatus(), QUALIFIED);
     assertEquals(newProperty.getEntityStatus(), QUALIFIED);
   }