diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0cf07b5cc6c376d27df7f1956af2e5ec8639be10..0852bbaa5e63ed6ceae1fc8ddad79db80d9fc16a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,7 +24,7 @@
 
 variables:
   DEPLOY_REF: dev
-  CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-server/caosdb-server-testenv:latest
+  CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/src/caosdb-server/caosdb-server-testenv:latest
 
 image: $CI_REGISTRY_IMAGE
 stages:
@@ -42,14 +42,11 @@ build-testenv:
     - schedules
   script:
     - cd src/test/docker
-    - time docker load < /image-cache/caosdb-server-testenv.tar || true
     - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
       # use here general latest or specific branch latest...
     - docker build
       --pull
       -t $CI_REGISTRY_IMAGE .
-    - docker save $CI_REGISTRY_IMAGE > image.tar;
-          mv image.tar /image-cache/caosdb-server-testenv.tar;
     - docker push $CI_REGISTRY_IMAGE
 
 # Test: run unit tests of the server
@@ -70,7 +67,7 @@ trigger_build:
   stage: deploy
   script:
     - /usr/bin/curl -X POST
-      -F token=$DEPLOY_TRIGGER_TOKEN
+      -F token=$CI_JOB_TOKEN
       -F "variables[F_BRANCH]=$CI_COMMIT_REF_NAME"
       -F "variables[SERVER]=$CI_COMMIT_REF_NAME"
       -F "variables[TriggerdBy]=SERVER"
@@ -78,15 +75,17 @@ trigger_build:
       -F ref=$DEPLOY_REF https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline
 
 # Build the sphinx documentation and make it ready for deployment by Gitlab Pages
-# documentation:
-#   stage: deploy
-
 # Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages
 pages:
   tags: [ cached-dind ]
   stage: deploy
   only:
-    - dev
+    refs:
+      - /^release-.*$/i
+      - master
+    variables:
+      # run pages only on gitlab.com
+      - $CI_SERVER_HOST == "gitlab.com"
   script:
     - echo "Deploying"
     - make doc
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8077b4075f814ed34577e68cba4de2fdeed0b192..64c6a1a3b21b5b7f796f892a63a419178d848fb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+* New EntityState plug-in. The plug-in disabled by default and can be enabled
+  by setting the server property `EXT_ENTITY_STATE=ENABLED`. See
+  [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more
+  information.
+* `ETag` property for the query. The `ETag` is assigned to the query cache
+  each time the cache is cleared (currently whenever the server state is being
+  updated, i.e. the stored entities change).
+  This can be used to debug the query cache and also allows a client
+  to determine whether the server's state has changed between queries.
 * Basic caching for queries. The caching is enabled by default and can be
   controlled by the usual "cache" flag.
 
@@ -20,6 +29,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+* #130 - Error during `FIND ENTITY` when
+  `QUERY_FILTER_ENTITIES_WITHOUT_RETRIEVE_PERMISSIONS=False`.
+* #125 - `bend_symlinks` script did not allow whitespace in filename.
+* #122 - Dead-lock due to error in the DatabaseAccessManager.
+* #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
@@ -40,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+* Server can be started without TLS even when not in debug mode.
 * 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
diff --git a/Makefile b/Makefile
index d73f140f065ca0b3db62651e40162194f4ffb4eb..1543286b67c6c7be20772a3b6932b9e0dcec27d5 100644
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,7 @@
 #
 
 CAOSDB_SERVER_VERSION ?= $(shell mvn org.apache.maven.plugins:maven-help-plugin:3.1.0:evaluate -Dexpression=project.version -q -DforceStdout)
+CAOSDB_COMMAND_LINE_OPTIONS ?=
 SHELL:=/bin/bash
 JPDA_PORT ?= 9000
 JMX_PORT ?= 9090
@@ -41,13 +42,14 @@ run: compile
 	mvn exec:java@run
 
 run-debug: jar
-	java -Xrunjdwp:transport=dt_socket,address=0.0.0.0:$(JPDA_PORT),server=y,suspend=n -Dcaosdb.debug=true -jar target/caosdb-server.jar
+	java -Xrunjdwp:transport=dt_socket,address=0.0.0.0:$(JPDA_PORT),server=y,suspend=n -Dcaosdb.debug=true -jar target/caosdb-server.jar $(CAOSDB_COMMAND_LINE_OPTIONS)
+
 
 run-debug-single:
-	java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$(JMX_PORT) -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Xrunjdwp:transport=dt_socket,address=0.0.0.0:$(JPDA_PORT),server=y,suspend=n -Dcaosdb.debug=true -jar target/caosdb-server.jar
+	java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$(JMX_PORT) -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Xrunjdwp:transport=dt_socket,address=0.0.0.0:$(JPDA_PORT),server=y,suspend=n -Dcaosdb.debug=true -jar target/caosdb-server.jar $(CAOSDB_COMMAND_LINE_OPTIONS)
 
 run-single:
-	java -jar target/caosdb-server.jar
+	java -jar target/caosdb-server.jar $(CAOSDB_COMMAND_LINE_OPTIONS)
 
 formatting:
 	mvn fmt:format
@@ -64,7 +66,7 @@ antlr:
 	mvn antlr4:antlr4
 
 test: print-version easy-units
-	MAVEN_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Dcaosdb.debug=true -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=0.0.0.0:9000"
+	MAVEN_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Dcaosdb.debug=true -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=0.0.0.0:$(JPDA_PORT)"
 	mvn test -X
 
 test_misc:
diff --git a/README.md b/README.md
index 8b68da0a79f9d66901ddb0176415c3ae4f2ca465..21d5400e5383d4c2571f8a409f50cd72f926187a 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,47 @@
-<!--THIS FILE HAS BEEN GENERATED BY A SCRIPT. PLEASE DON'T CHANGE IT MANUALLY.-->
 
-# Welcome
+# README
 
-This is the **CaosDB Server** repository and a part of the CaosDB project.
+## Welcome
 
-# Setup
+This is the **CaosDB Java Server** repository and a part of the
+CaosDB project.
+
+## Setup
 
 Please read the [README_SETUP.md](README_SETUP.md) for instructions on how to
 setup this code.
 
 
-# Further Reading
+## Further Reading
+
+Please refer to the [official documentation](https://docs.indiscale.com/caosdb-server/) for more information.
+
+## Contributing
+
+Thank you very much to all contributers—[past, present](https://gitlab.com/caosdb/caosdb/-/blob/dev/HUMANS.md), and prospective ones.
 
-Please refer to the [official gitlab repository of the CaosDB
-project](https://gitlab.com/caosdb/caosdb) for more information.
+### Code of Conduct
 
-# License
+By participating, you are expected to uphold our [Code of Conduct](https://gitlab.com/caosdb/caosdb/-/blob/dev/CODE_OF_CONDUCT.md).
 
-Copyright (C) 2018 Research Group Biomedical Physics, Max Planck Institute for
-Dynamics and Self-Organization Göttingen.
+### How to Contribute
+
+* You found a bug, have a question, or want to request a feature? Please
+[create an issue](https://gitlab.com/caosdb/caosdb-server/-/issues).
+* You want to contribute code? Please fork the repository and create a merge
+request in GitLab and choose this repository as target. Make sure to select
+"Allow commits from members who can merge the target branch" under Contribution
+when creating the merge request. This allows our team to work with you on your request.
+- If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-server/),
+the preferred way is also a merge request as describe above (the documentation resides in `src/doc`).
+However, you can also create an issue for it.
+- You can also contact us at **info (AT) caosdb.de**.
+
+## License
+
+* Copyright (C) 2018 Research Group Biomedical Physics, Max Planck Institute
+  for Dynamics and Self-Organization Göttingen.
+* Copyright (C) 2020-2021 Indiscale GmbH <info@indiscale.com>
 
 All files in this repository are licensed under a [GNU Affero General Public
 License](LICENCE.md) (version 3 or later).
-
diff --git a/README_SETUP.md b/README_SETUP.md
index f47f5a08624c20de194829b3534d72c2e06b461c..60fb212a62f99fdf7b9fe97b1895c39336359e23 100644
--- a/README_SETUP.md
+++ b/README_SETUP.md
@@ -79,7 +79,7 @@ server:
      Replace `localhost` by your host name, if you want.
    - `keytool -importkeystore -srckeystore caosdb.jks -destkeystore caosdb.p12 -deststoretype PKCS12 -srcalias selfsigned`
    - Export the public part only: `openssl pkcs12 -in caosdb.p12 -nokeys -out cert.pem`.
-	 The resulting ``cert.pem` can safely be given to users to allow ssl verification.
+	 The resulting `cert.pem` can safely be given to users to allow ssl verification.
    - You can check the content of the certificate with `openssl x509 -in cert.pem -text`
 
    Alternatively, you can create a keystore from certificate files that you already have:
diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md
index 5d89ceec00bbd8ba228bb497964f0eeb437891f7..5c1fd68d462212e9b81dbc803fc590006df36a3d 100644
--- a/RELEASE_GUIDELINES.md
+++ b/RELEASE_GUIDELINES.md
@@ -21,13 +21,13 @@ guidelines of the CaosDB Project
 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.
+4. Merge the release branch into the main branch.
 
-5. Tag the latest commit of the master branch with `v<VERSION>`.
+5. Tag the latest commit of the main branch with `v<VERSION>`.
 
 6. Delete the release branch.
 
-7. Merge the master branch back into the dev branch.
+7. Merge the main 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 8c59cc861d646cbdba0ec749ba052656f67fd58d..5dfe879722bd01acc5209c581b60bf0ac49635b6 160000
--- a/caosdb-webui
+++ b/caosdb-webui
@@ -1 +1 @@
-Subproject commit 8c59cc861d646cbdba0ec749ba052656f67fd58d
+Subproject commit 5dfe879722bd01acc5209c581b60bf0ac49635b6
diff --git a/conf/core/server.conf b/conf/core/server.conf
index 8922d19c8a41a0d54de3357e4ac3eecffdcbf482..76ed6030523f24f977f41a510c703fe075f30042 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -75,7 +75,7 @@ MYSQL_SCHEMA_VERSION=v4.0.0
 # Server options
 # --------------------------------------------------
 # The context root is a prefix which allows running multiple instances of CaosDB using the same
-# hostname and port.
+# hostname and port. Must start with "/".
 CONTEXT_ROOT=
 # HTTPS port of this server instance.
 SERVER_PORT_HTTPS=443
@@ -188,3 +188,11 @@ GLOBAL_ENTITY_PERMISSIONS_FILE=./conf/core/global_entity_permissions.xml
 
 # If set to true, versioning of entities' history is enabled.
 ENTITY_VERSIONING_ENABLED=true
+
+
+# --------------------------------------------------
+# Extension settings
+# --------------------------------------------------
+
+# Enabling the state machine extension
+# EXT_STATE_ENTITY=ENABLE
diff --git a/doc/devel/Benchmarking.md b/doc/devel/Benchmarking.md
index e07f6f8973784bfe3d77191f6730662d261f73fb..5fc3f75fe01114558f325662048cde904480c910 100644
--- a/doc/devel/Benchmarking.md
+++ b/doc/devel/Benchmarking.md
@@ -1,18 +1,153 @@
-# Profiling #
 
-If the server is started with the `run-debug-single` make target, it will expose
-the JMX interface, by default on port 9090.  Using a profiler such as VisualVM,
-one can then connect to the CaosDB server and profile execution times.
 
-## Example settings for VisualVM ##
+# Benchmarking CaosDB #
 
-In the sampler settings, you may want to add these expressions to the blocked
-packages: `org.restlet.**, com.mysql.**`.  Branches on the call tree which are
-entirely inside the blacklist, will become leaves.  Alternatively, specify a
-whitelist, for example with `org.caosdb.server.database.backend.implementation.**`,
-if you only want to see the time spent for certain MySQL calls.
+Benchmarking CaosDB may encompass several distinct areas: How much time is spent in the server's
+Java code, how much time is spent inside the SQL backend, are the same costly methods called more
+than once?  This documentation tries to answer some questions connected with these benchmarking
+aspects and give you the tools to answer your own questions.
+
+
+## Before you start ##
+In order to obtain meaningful results, you should disable caching.
+
+### MariaDB
+Set the corresponding variable to 0: `SET GLOBAL query_cache_type = 0;`
+
+### Java Server
+In the config:
+```conf
+CACHE_DISABLE=true
+```
+
+
+## Tools for the benchmarking ##
+
+For averaging over many runs of comparable requests and for putting the database into a
+representative state, Python scripts are used.  The scripts can be found in the `caosdb-dev-tools`
+repository, located at [https://gitlab.indiscale.com/caosdb/src/caosdb-dev-tools](https://gitlab.indiscale.com/caosdb/src/caosdb-dev-tools) in the folder
+`benchmarking`:
+
+### Python Script `fill_database.py` ###
 
-# Manual Java-side benchmarking #
+This commandline script is meant for filling the database with enough data to represeny an actual
+real-life case, it can easily create hundreds of thousands of Entities.
+
+The script inserts predefined amounts of randomized Entities into the database, RecordTypes,
+Properties and Records.  Each Record has a random (but with defined average) number of Properties,
+some of which may be references to other Records which have been inserted before.  Actual insertion
+of the Entities into CaosDB is done in chunks of a defined size.
+
+Users can tell the script to store times needed for the insertion of each chunk into a tsv file.
+
+### Python Script  `measure_execution_time.py` ###
+
+A somewhat outdated script which executes a given query a number of times and then save statistics
+about the `TransactionBenchmark` readings (see below for more information about the transaction
+benchmarks) delivered by the server.
+
+
+### Python Script  `sql_routine_measurement.py` 
+
+
+
+Simply call `./sql_routine_measurement.py` in the scripts directory. An sql
+file is automatically executed which enables the correct `performance_schema`
+tables. However, the performance_schema of mariadb needs to be enabled. Add
+`performance_schema=ON` to the configuration file of mariadb as it needs to be
+enabled on start up.
+This script expects the MariaDB server to be accessible on 127.0.0.1 with the default caosdb user
+and password (caosdb;random1234).
+
+
+The performance schema must be enabled (see below).
+
+### MariaDB General Query Log ###
+
+MariaDB and MySQL have a feature to enable the logging of SQL queries' times.  This logging must be
+turned on on the SQL server as described in the [upstream documentation](https://mariadb.com/kb/en/general-query-log/):
+Add to the mysql configuration:
+```
+log_output=TABLE
+general_log
+```
+or calling
+```sql
+SET GLOBAL log_output = 'TABLE';
+SET GLOBAL general_log = 'ON';
+```
+
+In the Docker environment LinkAhead, this can conveniently be 
+done with `linkahead mysqllog {on,off,store}`.
+
+### MariaDB Slow Query Log ###
+See [slow query log docs](https://mariadb.com/kb/en/slow-query-log-overview/)
+
+### MariaDB Performance Schema ###
+The most detailed information on execution times can be acquired using the performance schema.
+
+To use it, the `performance_schema` setting in the MariaDB server must be enabled([docs](https://mariadb.com/kb/en/performance-schema-overview/#enabling-the-performance-schema), for example by setting
+this in the config files:
+```
+[mysqld]
+
+performance_schema=ON
+```
+
+The performance schema provides many different tables in the `performance_schema`. You can instruct MariaDB to create
+those tables by setting the appropriate `instrument` and `consumer` variables. E.g. 
+```SQL
+update performance_schema.setup_instruments set enabled='YES', timed='YES' WHERE NAME LIKE '%statement%';
+update performance_schema.setup_consumers set enabled='YES' WHERE NAME LIKE '%statement%';
+```
+This can also be done via the configuration. 
+```
+[mysqld]
+
+performance_schema=ON
+performance-schema-instrument='statement/%=ON'
+performance-schema-consumer-events-statements-history=ON                        
+performance-schema-consumer-events-statements-history-long=ON
+```
+You may want to look at the result of the following commands:
+```sql
+
+select * from performance_schema.setup_consumers;
+select * from performance_schema.setup_instruments;
+```
+
+Note, that the `base_settings.sql` enables appropriate instruments and consumers.
+
+Before you start a measurement, you will want to empty the tables. E.g.:
+```sql
+truncate table  performance_schema.events_statements_history_long ;
+```
+The procedure `reset_stats` in `base_settings.sql` clears the typically used ones.
+
+The tables contain many columns. An example to get an informative view is
+```sql
+select left(sql_text,50), left(digest_text,50), ms(timer_wait) from performance_schema.events_statements_history_long order by ms(timer_wait);
+```
+where the function `ms` is defined in `base_settings.sql`.
+Or a very useful one:
+```sql
+select  left(digest_text,100) as digest,ms(sum_timer_wait) as time_ms, count_star from performance_schema.events_statements_summary_by_digest order by time_ms;
+```
+
+### Useful SQL configuration with docker
+In order to allow easy testing and debugging the following is useful when using docker.
+Change the docker-compose file to include the following for the mariadb service:
+```
+    networks:
+      # available on port 3306, host name 'sqldb'
+      - caosnet
+    ports:
+      - 3306:3306
+```
+Check it with `mysql -ucaosdb -prandom1234 -h127.0.0.1 caosdb`
+Add the appropriate changes (e.g. `performance_schema=ON`) to `profiles/empty/custom/mariadb.conf.d/mariadb.cnf` (or in the profile folder that you use).
+
+### Manual Java-side benchmarking #
 
 Benchmarking can be done using the `TransactionBenchmark` class (in package
 `org.caosdb.server.database.misc`).
@@ -26,9 +161,95 @@ Benchmarking can be done using the `TransactionBenchmark` class (in package
   - `Container.getTransactionBenchmark().addBenchmark()`
   - `Query.addBenchmark()`
 
-# Miscellaneous notes #
 
-Notes to self, details, etc.
+To enable transaction benchmarks and disable caching in the server, set these
+server settings:
+```conf
+TRANSACTION_BENCHMARK_ENABLED=true
+CACHE_DISABLE=true
+```
+Additionally, the server should be started via `make run-debug` (instead of
+`make run-single`), otherwise the benchmarking will not be active.
+
+#### Notable benchmarks and where to find them ##
+
+| Name                                 | Where measured                               | What measured                 |
+|--------------------------------------|----------------------------------------------|-------------------------------|
+| `Retrieve.init`                      | transaction/Transaction.java#135             | transaction/Retrieve.java#48  |
+| `Retrieve.transaction`               | transaction/Transaction.java#174             | transaction/Retrieve.java#133 |
+| `Retrieve.post_transaction`          | transaction/Transaction.java#182             | transaction/Retrieve.java#77  |
+| `EntityResource.httpGetInChildClass` | resource/transaction/EntityResource.java#118 | all except XML generation     |
+| `ExecuteQuery`                       | ?                                            | ?                             |
+|                                      |                                              |                               |
+
+### External JVM profilers ###
+
+Additionally to the transaction benchmarks, it is possible to benchmark the server execution via
+external Java profilers.  For example, [VisualVM](https://visualvm.github.io/) can connect to JVMs running locally or remotely
+(e.g. in a Docker container).  To enable this in LinkAhead's Docker environment, set
+
+```yaml
+devel:
+  profiler: true
+```
+Alternatively, start the server (without docker) with the `run-debug-single` make target, it will expose
+the JMX interface, by default on port 9090.
+
+Most profilers, like as VisualVM, only gather cumulative data for call trees, they do not provide
+complete call graphs (as callgrind/kcachegrind would do).  They also do not differentiate between
+calls with different query strings, as long as the Java process flow is the same (for example, `FIND
+Record 1234` and `FIND Record A WHICH HAS A Property B WHICH HAS A Property C>100` would be handled
+equally).
+
+
+#### Example settings for VisualVM 
+
+In the sampler settings, you may want to add these expressions to the blocked
+packages: `org.restlet.**, com.mysql.**`.  Branches on the call tree which are
+entirely inside the blacklist, will become leaves.  Alternatively, specify a
+whitelist, for example with `org.caosdb.server.database.backend.implementation.**`,
+if you only want to see the time spent for certain MySQL calls.
+
+
+## How to set up a representative database ##
+For reproducible results, it makes sense to start off with an empty database and fill it using the
+`fill_database.py` script, for example like this:
+
+```sh
+./fill_database.py -t 500 -p 700 -r 10000 -s 100 --clean
+```
+
+The `--clean` argument is not strictly necessary when the database was empty before, but it may make
+sense when there have been previous runs of the command.  This example would create 500 RecordTypes,
+700 Properties and 10000 Records with randomized properties, everything is inserted in chunks of 100
+Entities.
+
+## How to measure request times ##
+
+If the execution of the Java components is of interest, the VisualVM profiler should be started and
+connected to the server before any requests to the server are started.
+
+When doing performance tests which are used for detailed analysis, it is important that
+
+1. CaosDB is in a reproducible state, which should be documented
+2. all measurements are repeated several times to account for inevitable variance in access (for
+   example file system caching, network variablity etc.)
+
+### Filling the database ###
+
+By simply adding the option `-T logfile.tsv` to the `fill_database.py` command above, the times for
+inserting the records are stored in a tsv file and can be analyzed later.
+
+### Obtain statistics about a query ###
+
+To repeat single queries a number of times, `measure_execution_time.py` can be used, for example:
+
+```sh
+./measure_execution_time.py -n 120 -q "FIND MusicalInstrument WHICH IS REFERENCED BY Analysis"
+```
+
+This command executes the query 120 times, additional arguments could even plot the
+TransactionBenchmark results directly.
 
 ## On method calling order and benchmarked events ##
 
@@ -56,29 +277,37 @@ Notes to self, details, etc.
   - Executing the SQL statement
   - Java-side caching
 
-## Server settings ##
+## What is measured ##
 
-- To enable the SQL general logs, log into the SQL server and do:
-  ```sql
-SET GLOBAL log_output = 'TABLE';
-SET GLOBAL general_log = 'ON';
-```
-- To enable transaction benchmarks and disable caching in the server, set these
-  server settings:
-```conf
-TRANSACTION_BENCHMARK_ENABLED=true
-CACHE_DISABLE=true
-```
-- Additionally, the server should be started via `make run-debug` (instead of
-  `make run-single`), otherwise the benchmarking will not be active.
+For a consistent interpretation, the exact definitions of the measured times are as follows:
 
-## Notable benchmarks and where to find them ##
+### SQL logs ###
 
-| Name                                 | Where measured                               | What measured                 |
-|--------------------------------------|----------------------------------------------|-------------------------------|
-| `Retrieve.init`                      | transaction/Transaction.java#135             | transaction/Retrieve.java#48  |
-| `Retrieve.transaction`               | transaction/Transaction.java#174             | transaction/Retrieve.java#133 |
-| `Retrieve.post_transaction`          | transaction/Transaction.java#182             | transaction/Retrieve.java#77  |
-| `EntityResource.httpGetInChildClass` | resource/transaction/EntityResource.java#118 | all except XML generation     |
-| `ExecuteQuery`                       | ?                                            | ?                             |
-|                                      |                                              |                               |
+As per https://mariadb.com/kb/en/general-query-log, the logs store only the time at which the SQL
+server received a query, not the duration of the query.
+
+#### Possible future enhancements ####
+
+- The `query_response_time` plugin may be additionally used in the future, see
+  https://mariadb.com/kb/en/query-response-time-plugin
+
+### Transaction benchmarks ###
+
+Transaction benchmarking manually collects timing information for each transaction.  At defined
+points, different measurements can be made, accumulated and will finally be returned to the client.
+Benchmark objects may consist of sub benchmarks and have a number of measurement objects, which
+contain the actual statistics.
+
+Because transaction benchmarks must be manually added to the server code, they only monitor those
+code paths where they are added.  On the other hand, their manual nature allows for a more
+abstracted analysis of performance bottlenecks.
+
+### Java profiler ###
+
+VisualVM records for each thread the call tree, specifically which methods were called how often and
+how much time was spent inside these methods.
+
+### Global requests ###
+
+Python scripts may measure the global time needed for the execution of each request.
+`fill_database.py` obtains its numbers this way.
diff --git a/misc/bend_symlinks/src/main.sh b/misc/bend_symlinks/src/main.sh
index c2a6a94766d437e41619c2602d1c62417e09ee42..1d148df30533529855f65141ba5705d76b0cb687 100644
--- a/misc/bend_symlinks/src/main.sh
+++ b/misc/bend_symlinks/src/main.sh
@@ -66,8 +66,10 @@ if [ $IS_MOVE -eq 1 ] ; then
     REPLACEMENT=$(new_dir "$REPLACEMENT")
 fi
 
+
 set -o noglob
-for syml in $(find -P $(realpath $FILE_SYSTEM_ROOT) -type l) ; do
+find -P $(realpath $FILE_SYSTEM_ROOT) -type l -print0 |
+        while ISF= read -r -d '' syml; do
   OLD_TARGET=$(realpath -m "$syml" | sed -n -r "/$REGEX_OLD/p")
   if [ -z "$OLD_TARGET" ] ; then
     # filter non matching
diff --git a/misc/bend_symlinks/src/utils.sh b/misc/bend_symlinks/src/utils.sh
index 0e10fe9acc0c1c27fa3f2f58add8e7daf845e24c..2ddda289639a9a38e13540da2caad32dc439aa4a 100644
--- a/misc/bend_symlinks/src/utils.sh
+++ b/misc/bend_symlinks/src/utils.sh
@@ -37,6 +37,8 @@ function escape_simple_path () {
     SPATH=$(echo "$SPATH" | sed -r "s/\(/\\\\(/g")
     # {
     SPATH=$(echo "$SPATH" | sed -r "s/\{/\\\\{/g")
+    # white space
+    SPATH=$(echo "$SPATH" | sed -r "s/ /\\ /g")
     echo "$SPATH"
 }
 
diff --git a/misc/bend_symlinks/test/test_suite.sh b/misc/bend_symlinks/test/test_suite.sh
index 9ffa6b1fffe9ec095ebbc3a25bdfa5ee767e1a69..2def1fda1450ad9c3b412f033a6085bddd840d8f 100755
--- a/misc/bend_symlinks/test/test_suite.sh
+++ b/misc/bend_symlinks/test/test_suite.sh
@@ -23,8 +23,11 @@ tearDown () {
 
 _make_test_file () {
     touch "$DATA_DIR/$1"
-    ln -s $(realpath "$DATA_DIR/$1") "$FILE_SYSTEM_ROOT/$1"
-    assertEquals "initial target $1" $(realpath "$FILE_SYSTEM_ROOT/$1") $(realpath "$DATA_DIR/$1")
+    TARGET=$(realpath "$DATA_DIR/$1")
+    LINK=$FILE_SYSTEM_ROOT/$1
+    ln -s "$TARGET" "$LINK"
+    LINKED=$(realpath "$LINK")
+    assertEquals "initial target $1" "$LINKED" "$TARGET"
 }
 
 _break_link_move_file () {
@@ -35,7 +38,8 @@ _break_link_move_file () {
     NEW_PATH_REAL=$(realpath "$NEW_PATH")
     LINK="$FILE_SYSTEM_ROOT/$1"
     mv "$OLD_PATH_REAL" "$NEW_PATH_REAL"
-    assertEquals "still target $OLD_PATH_REAL" $(realpath "$LINK") "$OLD_PATH_REAL"
+    LINKED=$(realpath "$LINK")
+    assertEquals "still target $OLD_PATH_REAL" "$LINKED" "$OLD_PATH_REAL"
     assertFalse "$LINK link is broken" "[ -f '$LINK' ]"
     assertFalse "$OLD_PATH_REAL was moved" "[ -f '$OLD_PATH_REAL' ]"
     assertTrue "$NEW_PATH_REAL is there" "[ -f '$NEW_PATH_REAL' ]"
@@ -51,7 +55,7 @@ assertLinkOk () {
     LINK=$(realpath "$FILE_SYSTEM_ROOT/$1")
     TARGET=$(realpath "$DATA_DIR/$2")
     assertTrue "target exists $LINK" "[ -f '$LINK' ]"
-    assertEquals "target matches $TARGET" $TARGET "$LINK"
+    assertEquals "target matches $TARGET" "$TARGET" "$LINK"
     set +o noglob
 }
 
@@ -135,6 +139,7 @@ testFullPathWithStrangeChars () {
     _testFullPathWithStrageChars "{"
     _testFullPathWithStrageChars "]"
     _testFullPathWithStrageChars "[.]"
+    _testFullPathWithStrageChars " "
 }
 
 testRegex () {
diff --git a/src/doc/Permissions.rst b/src/doc/Permissions.rst
index c624092aff4f479bc8ed48f439109530cc5295aa..a5fae0bbfaf8d021691f7f8af93c3db9b5995497 100644
--- a/src/doc/Permissions.rst
+++ b/src/doc/Permissions.rst
@@ -28,7 +28,7 @@ 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
+-  A :doc:`role <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.
diff --git a/src/doc/administration/server_side_scripting.rst b/src/doc/administration/server_side_scripting.rst
index b187d1d566fdcac004649506d036807276cbc4ae..1f2b9a1d80e11abed48ae89502ab6f80c099507e 100644
--- a/src/doc/administration/server_side_scripting.rst
+++ b/src/doc/administration/server_side_scripting.rst
@@ -50,7 +50,7 @@ script invocation from a skeleton directory, located in the server directory, in
 - `readme.md` :: A small text file describing the purpose of the directory.
 
 Users of CaosDB are invited to populate the directory with whatever their
-scripts need.
+scripts need (for example a `.pycaosdb.ini` file).
 
 Invocation
 ------------
diff --git a/src/doc/conf.py b/src/doc/conf.py
index fb3d6264cc4add018b83d08378813aab9d10964d..7b6ac359abf8d88d3c60cb37001bc43e00b97872 100644
--- a/src/doc/conf.py
+++ b/src/doc/conf.py
@@ -55,8 +55,8 @@ templates_path = ['_templates']
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
 #
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ['.rst', '.md']
+# source_suffix = '.rst'
 
 # The master toctree document.
 master_doc = 'index'
diff --git a/src/doc/development/benchmarking.md b/src/doc/development/benchmarking.md
index f2d663f6e69799c7a87a28fbdf32f15fab892ac1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/src/doc/development/benchmarking.md
+++ b/src/doc/development/benchmarking.md
@@ -1,130 +0,0 @@
-
-# Benchmarking CaosDB #
-
-Benchmarking CaosDB may encompass several distinct areas: How much time is spent in the server's
-Java code, how much time is spent inside the SQL backend, are the same costly methods clalled more
-than once?  This documentation tries to answer some questions connected with these benchmarking
-aspects and give you the tools to answer your own questions.
-
-## Tools for the benchmarking ##
-
-For averaging over many runs of comparable requests and for putting the database into a
-representative state, Python scripts are used.  The scripts can be found in the `caosdb-dev-tools`
-repository, located at [https://gitlab.indiscale.com/caosdb/src/caosdb-dev-tools](https://gitlab.indiscale.com/caosdb/src/caosdb-dev-tools) in the folder
-`benchmarking`:
-
-### `fill_database.py` ###
-
-This commandline script is meant for filling the database with enough data to represeny an actual
-real-life case, it can easily create hundreds of thousands of Entities.
-
-The script inserts predefined amounts of randomized Entities into the database, RecordTypes,
-Properties and Records.  Each Record has a random (but with defined average) number of Properties,
-some of which may be references to other Records which have been inserted before.  Actual insertion
-of the Entities into CaosDB is done in chunks of a defined size.
-
-Users can tell the script to store times needed for the insertion of each chunk into a tsv file.
-
-### `measure_execution_time.py` ###
-
-A somewhat outdated script which executes a given query a number of times and then save statistics
-about the `TransactionBenchmark` readings (see below for more information about the transaction
-benchmarks) delivered by the server.
-
-### Benchmarking SQL commands ###
-
-MariaDB and MySQL have a feature to enable the logging of SQL queries' times.  This logging must be
-turned on on the SQL server as described in the [upstream documentation](https://mariadb.com/kb/en/general-query-log/).  For the Docker
-environment LinkAhead, this can conveniently be done with `linkahead mysqllog {on,off,store}`.
-
-### External JVM profilers ###
-
-Additionally to the transaction benchmarks, it is possible to benchmark the server execution via
-external Java profilers.  For example, [VisualVM](https://visualvm.github.io/) can connect to JVMs running locally or remotely
-(e.g. in a Docker container).  To enable this in LinkAhead's Docker environment, set
-
-```yaml
-devel:
-  profiler: true
-```
-
-Most profilers, like as VisualVM, only gather cumulative data for call trees, they do not provide
-complete call graphs (as callgrind/kcachegrind would do).  They also do not differentiate between
-calls with different query strings, as long as the Java process flow is the same (for example, `FIND
-Record 1234` and `FIND Record A WHICH HAS A Property B WHICH HAS A Property C>100` would be handled
-equally).
-
-## How to set up a representative database ##
-For reproducible results, it makes sense to start off with an empty database and fill it using the
-`fill_database.py` script, for example like this:
-
-```sh
-./fill_database.py -t 500 -p 700 -r 10000 -s 100 --clean
-```
-
-The `--clean` argument is not strictly necessary when the database was empty before, but it may make
-sense when there have been previous runs of the command.  This example would create 500 RecordTypes,
-700 Properties and 10000 Records with randomized properties, everything is inserted in chunks of 100
-Entities.
-
-## How to measure request times ##
-
-If the execution of the Java components is of interest, the VisualVM profiler should be started and
-connected to the server before any requests to the server are started.
-
-When doing performance tests which are used for detailed analysis, it is important that
-
-1. CaosDB is in a reproducible state, which should be documented
-2. all measurements are repeated several times to account for inevitable variance in access (for
-   example file system caching, network variablity etc.)
-
-### Filling the database ###
-
-By simply adding the option `-T logfile.tsv` to the `fill_database.py` command above, the times for
-inserting the records are stored in a tsv file and can be analyzed later.
-
-### Obtain statistics about a query ###
-
-To repeat single queries a number of times, `measure_execution_time.py` can be used, for example:
-
-```sh
-./measure_execution_time.py -n 120 -q "FIND MusicalInstrument WHICH IS REFERENCED BY Analysis"
-```
-
-This command executes the query 120 times, additional arguments could even plot the
-TransactionBenchmark results directly.
-
-## What is measured ##
-
-For a consistent interpretation, the exact definitions of the measured times are as follows:
-
-### SQL logs ###
-
-As per https://mariadb.com/kb/en/general-query-log, the logs store only the time at which the SQL
-server received a query, not the duration of the query.
-
-#### Possible future enhancements ####
-
-- The `query_response_time` plugin may be additionally used in the future, see
-  https://mariadb.com/kb/en/query-response-time-plugin
-
-### Transaction benchmarks ###
-
-Transaction benchmarking manually collects timing information for each transaction.  At defined
-points, different measurements can be made, accumulated and will finally be returned to the client.
-Benchmark objects may consist of sub benchmarks and have a number of measurement objects, which
-contain the actual statistics.
-
-Because transaction benchmarks must be manually added to the server code, they only monitor those
-code paths where they are added.  On the other hand, their manual nature allows for a more
-abstracted analysis of performance bottlenecks.
-
-### Java profiler ###
-
-VisualVM records for each thread the call tree, specifically which methods were called how often and
-how much time was spent inside these methods.
-
-### Global requests ###
-
-Python scripts may measure the global time needed for the execution of each request.
-`fill_database.py` obtains its numbers this way.
diff --git a/src/doc/index.rst b/src/doc/index.rst
index 6a9013c51720f13b4f473d9213bb64b8d4d27eec..870db0251049dfaaff6a6c3a2f2c38b9e95aff97 100644
--- a/src/doc/index.rst
+++ b/src/doc/index.rst
@@ -27,5 +27,3 @@ Indices and tables
 ==================
 
 * :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
diff --git a/src/doc/roles.md b/src/doc/roles.md
index 80729fb11f8113c64da7e9bf26b04de68fdd6681..b70f54506e8adb3ad3caf46af94479af49370409 100644
--- a/src/doc/roles.md
+++ b/src/doc/roles.md
@@ -10,7 +10,7 @@ users may have the same role, and there may be roles without any users.
 
 The user and their roles are always returned by the server in answers to requests
 and can thus be interpreted and used by clients.  The most important use though
-is [permission](manuals/general/permissions) checking in the server: Access and
+is [permission](permissions) checking in the server: Access and
 modification of
 entities can be controlled via roles, so that users of a given role are allowed
 or denied certain actions.  Incidentally, the permission to edit the permissions
@@ -32,4 +32,4 @@ There are some special roles, which are automatically assigned to users:
 
 Except for the `anonymous` role, these special roles are not returned by the
 server, but can nevertheless be used to define
-[permissions](manuals/general/permissions).
+[permissions](permissions.rst).
diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java
index a4b8d2982d12383e1f4df9c86077cc751c4fdcfb..a81c2f3c0f7ffb55a6a26518220cbdcd06be97d5 100644
--- a/src/main/java/org/caosdb/server/CaosDBServer.java
+++ b/src/main/java/org/caosdb/server/CaosDBServer.java
@@ -118,7 +118,7 @@ public class CaosDBServer extends Application {
   private static ArrayList<Runnable> postShutdownHooks = new ArrayList<Runnable>();
   private static ArrayList<Runnable> preShutdownHooks = new ArrayList<Runnable>();
   private static boolean START_BACKEND = true;
-  private static boolean INSECURE = false;
+  private static boolean NO_TLS = false;
   public static final String REQUEST_TIME_LOGGER = "REQUEST_TIME_LOGGER";
   public static final String REQUEST_ERRORS_LOGGER = "REQUEST_ERRORS_LOGGER";
   private static Scheduler SCHEDULER;
@@ -161,24 +161,23 @@ public class CaosDBServer extends Application {
    * 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)
+   *   <li>"--no-backend": flag to run caosdb without any backend (for testing purposes)
+   *   <li>"--no-tls": 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.
+   * <p>The --no-backend flag is 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("nobackend")) {
+      if (s.equals("--no-backend")) {
         START_BACKEND = false;
-      } else if (s.equals("insecure")) {
-        INSECURE = true;
+      } else if (s.equals("--no-tls")) {
+        NO_TLS = true;
       }
     }
-    INSECURE = INSECURE && isDebugMode(); // only allow insecure in debug mode
     START_BACKEND = START_BACKEND || !isDebugMode(); // always start backend if not in debug mode
   }
 
@@ -348,7 +347,7 @@ public class CaosDBServer extends Application {
     final int maxTotalConnections =
         Integer.parseInt(getServerProperty(ServerProperties.KEY_MAX_CONNECTIONS));
 
-    if (INSECURE) {
+    if (NO_TLS) {
       runHTTPServer(port_http, initialConnections, maxTotalConnections);
     } else {
       runHTTPSServer(
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/DatabaseAccessManager.java b/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java
index 196a9fb2b4418d81ced4b174482f8f6a5dce8ce8..f12c78e31bf28799b8c8ead1c9ff311a65bb01ef 100644
--- a/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java
+++ b/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java
@@ -26,6 +26,7 @@
 package org.caosdb.server.database;
 
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.access.AccessControlAccess;
@@ -56,7 +57,7 @@ import org.caosdb.server.utils.Releasable;
 class ReadAccessSemaphore extends Semaphore implements Releasable {
 
   private static final long serialVersionUID = 4384921156838881337L;
-  private int acquired = 0; // how many threads have read access
+  private AtomicInteger acquired = new AtomicInteger(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.
@@ -73,10 +74,9 @@ class ReadAccessSemaphore extends Semaphore implements Releasable {
   @Override
   public void acquire() throws InterruptedException {
     super.acquire(); // Protect the next few lines
-    if (this.acquired == 0) {
+    if (this.acquired.getAndIncrement() == 0) {
       this.writersBlock.acquire();
     }
-    this.acquired++;
     super.release();
   }
 
@@ -87,9 +87,7 @@ class ReadAccessSemaphore extends Semaphore implements Releasable {
    */
   @Override
   public void release() {
-    this.acquired--;
-    if (this.acquired <= 0) { // Last permit: release
-      this.acquired = 0;
+    if (this.acquired.decrementAndGet() == 0) { // Last permit: release
       if (this.writersBlock.availablePermits() <= 0) {
         this.writersBlock.release();
       }
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
index ec0c0c1b0a067d529ecb72309d7fe83a8b37f819..7af4022595f64b909231c72c83ae1e151a9d06c1 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java
@@ -27,10 +27,14 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.shiro.SecurityUtils;
+import org.caosdb.server.database.DatabaseUtils;
 import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.RetrieveAllImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.entity.Role;
+import org.caosdb.server.permissions.EntityACL;
+import org.caosdb.server.permissions.EntityPermission;
 
 public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImpl {
 
@@ -38,17 +42,20 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp
     super(access);
   }
 
-  public static final String STMT_GET_ALL_HEAD = "Select id from entities where id > 99";
+  public static final String STMT_GET_ALL_HEAD =
+      "SELECT e.id AS ID, a.acl AS ACL FROM entities AS e JOIN entity_acl AS a ON (e.acl = a.id) WHERE e.id > 99";
   public static final String STMT_ENTITY_WHERE_CLAUSE =
-      " AND ( role=? OR role='"
+      " AND ( e.role='"
+          + Role.Record
+          + "' OR e.role='"
           + Role.RecordType
-          + "' OR role='"
+          + "' OR e.role='"
           + Role.Property
-          + "' OR role='"
+          + "' OR e.role='"
           + Role.File
           + "'"
           + " )";
-  public static final String STMT_OTHER_ROLES = " AND role=?";
+  public static final String STMT_OTHER_ROLES = " AND e.role=?";
 
   @Override
   public List<Integer> execute(final String role) throws TransactionException {
@@ -58,10 +65,7 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp
               + (role.equalsIgnoreCase("ENTITY") ? STMT_ENTITY_WHERE_CLAUSE : STMT_OTHER_ROLES);
       final PreparedStatement stmt = prepareStatement(STMT_GET_ALL);
 
-      if (role.equalsIgnoreCase("ENTITY")) {
-        stmt.setString(1, Role.Record.toString());
-
-      } else {
+      if (!role.equalsIgnoreCase("ENTITY")) {
         stmt.setString(1, role);
       }
 
@@ -69,7 +73,11 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp
       try {
         final ArrayList<Integer> ret = new ArrayList<Integer>();
         while (rs.next()) {
-          ret.add(rs.getInt(1));
+          String acl = DatabaseUtils.bytes2UTF8(rs.getBytes("ACL"));
+          if (EntityACL.deserialize(acl)
+              .isPermitted(SecurityUtils.getSubject(), EntityPermission.RETRIEVE_ENTITY)) {
+            ret.add(rs.getInt("ID"));
+          }
         }
         return ret;
       } finally {
diff --git a/src/main/java/org/caosdb/server/entity/ClientMessage.java b/src/main/java/org/caosdb/server/entity/ClientMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6b996770b1d1d88992a56551acc0d5aaa5db1f0
--- /dev/null
+++ b/src/main/java/org/caosdb/server/entity/ClientMessage.java
@@ -0,0 +1,90 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.jdom2.Attribute;
+import org.jdom2.Element;
+
+/**
+ * Class which represents client messages. Client messages is a way to extend the Entity API with
+ * special properties which may be used by plug-ins.
+ *
+ * <p>If no plug-in handles the client message, it is printed back to the response unaltered.
+ *
+ * <p>Client message can have arbitrary key-value (string-string typed) tuples {@link #properties}.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class ClientMessage extends Message {
+
+  private static final long serialVersionUID = 1L;
+  private Map<String, String> properties = new HashMap<>();
+
+  public ClientMessage(String type, String body) {
+    super(type, null, null, null);
+  }
+
+  @Override
+  public Element toElement() {
+    final Element e = new Element(this.type);
+    for (Entry<String, String> a : this.properties.entrySet()) {
+      e.setAttribute(a.getKey(), a.getValue());
+    }
+    return e;
+  }
+
+  @Override
+  public void addToElement(final Element parent) {
+    final Element e = toElement();
+    parent.addContent(e);
+  }
+
+  /** NB: This is the only place where properties are set in this class. */
+  public static ClientMessage fromXML(Element pe) {
+    ClientMessage result = new ClientMessage(pe.getName(), pe.getText());
+    for (Attribute a : pe.getAttributes()) {
+      result.properties.put(a.getName(), a.getValue());
+    }
+    return result;
+  }
+
+  public String getProperty(String key) {
+    return properties.get(key);
+  }
+
+  @Override
+  public String toString() {
+    return this.type + " - " + this.properties.toString();
+  }
+
+  @Override
+  public int hashCode() {
+    return type.hashCode()
+        + (this.getBody() == null ? 0 : this.getBody().hashCode())
+        + this.properties.hashCode();
+  }
+}
diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java
index aa4c96127cfc97b03f308a50cb587dc12949c105..0a60f9af59f731098a0e194e4848b578094d4e68 100644
--- a/src/main/java/org/caosdb/server/entity/Entity.java
+++ b/src/main/java/org/caosdb/server/entity/Entity.java
@@ -35,6 +35,7 @@ import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.CaosDBException;
+import org.caosdb.server.accessControl.Principal;
 import org.caosdb.server.database.proto.SparseEntity;
 import org.caosdb.server.database.proto.VerySparseEntity;
 import org.caosdb.server.datatype.AbstractCollectionDatatype;
@@ -104,10 +105,12 @@ public class Entity extends AbstractObservable implements EntityInterface {
   public void checkPermission(final Subject subject, final Permission permission) {
     try {
       if (!this.hasPermission(subject, permission)) {
+        String user = "The current user ";
+        if (subject.getPrincipal() instanceof Principal) {
+          user = ((Principal) subject.getPrincipal()).getUsername();
+        }
         throw new AuthorizationException(
-            subject.getPrincipal().toString()
-                + " doesn't have permission "
-                + permission.toString());
+            user + " doesn't have permission " + permission.toString());
       }
     } catch (final NullPointerException e) {
       throw new AuthorizationException("This entity doesn't have an ACL!");
@@ -848,34 +851,7 @@ public class Entity extends AbstractObservable implements EntityInterface {
       } else if (getRole() == Role.QueryTemplate && pe.getName().equalsIgnoreCase("Query")) {
         setQueryTemplateDefinition(pe.getTextNormalize());
       } else {
-        final String type = pe.getName();
-        Integer code = null;
-        String localDescription = null;
-        String body = null;
-
-        // Parse MESSAGE CODE.
-        if (pe.getAttribute("code") != null && !pe.getAttributeValue("code").equals("")) {
-          try {
-            code = Integer.parseInt(pe.getAttributeValue("code"));
-          } catch (final NumberFormatException e) {
-            addInfo("Message code was " + pe.getAttributeValue("code") + ".");
-            addError(ServerMessages.PARSING_FAILED);
-            setEntityStatus(EntityStatus.UNQUALIFIED);
-          }
-        }
-
-        // Parse MESSAGE DESCRIPTION.
-        if (pe.getAttribute("description") != null
-            && !pe.getAttributeValue("description").equals("")) {
-          localDescription = pe.getAttributeValue("description");
-        }
-
-        // Parse MESSAGE BODY.
-        if (pe.getTextTrim() != null && !pe.getTextTrim().equals("")) {
-          body = pe.getTextTrim();
-        }
-
-        addMessage(new Message(type, code, localDescription, body));
+        addMessage(ClientMessage.fromXML(pe));
       }
     }
 
diff --git a/src/main/java/org/caosdb/server/entity/Message.java b/src/main/java/org/caosdb/server/entity/Message.java
index 743a2b62fa87570e0dbca05a1f279e6764920f0b..3e469b2dc84b07e3ac3de378f8990cc520989cc0 100644
--- a/src/main/java/org/caosdb/server/entity/Message.java
+++ b/src/main/java/org/caosdb/server/entity/Message.java
@@ -27,7 +27,7 @@ import org.jdom2.Element;
 
 public class Message extends Exception implements Comparable<Message>, ToElementable {
 
-  private final String type;
+  protected final String type;
   private final Integer code;
   private final String description;
   private final String body;
@@ -127,7 +127,7 @@ public class Message extends Exception implements Comparable<Message>, ToElement
     return this.code;
   }
 
-  public final Element toElement() {
+  public Element toElement() {
     final Element e = new Element(this.type);
     if (this.code != null) {
       e.setAttribute("code", Integer.toString(this.code));
@@ -142,7 +142,7 @@ public class Message extends Exception implements Comparable<Message>, ToElement
   }
 
   @Override
-  public final void addToElement(final Element parent) {
+  public void addToElement(final Element parent) {
     final Element e = toElement();
     parent.addContent(e);
   }
diff --git a/src/main/java/org/caosdb/server/entity/UpdateEntity.java b/src/main/java/org/caosdb/server/entity/UpdateEntity.java
index aa6d591602df66cc9317351cf6a1ea980bc53ea3..884632b5ed3f92d740f2eea69ed49ef77511fd1b 100644
--- a/src/main/java/org/caosdb/server/entity/UpdateEntity.java
+++ b/src/main/java/org/caosdb/server/entity/UpdateEntity.java
@@ -22,11 +22,21 @@
  */
 package org.caosdb.server.entity;
 
+import org.caosdb.server.transaction.WriteTransaction;
 import org.caosdb.server.utils.EntityStatus;
 import org.jdom2.Element;
 
+/**
+ * UpdateEntity class represents entities which are to be updated. The previous version is appeded
+ * during the {@link WriteTransaction} transactions initialization.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
 public class UpdateEntity extends WritableEntity {
 
+  /** The previous version of this entity. */
+  private EntityInterface original = null;
+
   public UpdateEntity(final Element element) {
     super(element);
   }
@@ -35,4 +45,12 @@ public class UpdateEntity extends WritableEntity {
   public boolean skipJob() {
     return getEntityStatus() != EntityStatus.QUALIFIED;
   }
+
+  public void setOriginal(EntityInterface original) {
+    this.original = original;
+  }
+
+  public EntityInterface getOriginal() {
+    return this.original;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java
index 28dc512b669eb6f1df0cfd24e32e2051f734b697..86ba49fabeedf04833f4e82214876be698aaa8cd 100644
--- a/src/main/java/org/caosdb/server/jobs/Job.java
+++ b/src/main/java/org/caosdb/server/jobs/Job.java
@@ -50,10 +50,7 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.container.TransactionContainer;
 import org.caosdb.server.jobs.core.Mode;
 import org.caosdb.server.transaction.Transaction;
-import org.caosdb.server.utils.AbstractObservable;
 import org.caosdb.server.utils.EntityStatus;
-import org.caosdb.server.utils.Observable;
-import org.caosdb.server.utils.Observer;
 import org.caosdb.server.utils.ServerMessages;
 import org.reflections.Reflections;
 
@@ -62,7 +59,7 @@ import org.reflections.Reflections;
  *
  * @todo Describe me.
  */
-public abstract class Job extends AbstractObservable implements Observer {
+public abstract class Job {
   private Transaction<? extends TransactionContainer> transaction = null;
   private Mode mode = null;
 
@@ -139,10 +136,6 @@ public abstract class Job extends AbstractObservable implements Observer {
     getTransaction().getSchedule().runJob(entity, jobclass);
   }
 
-  protected void runJobFromSchedule(ScheduledJob job) {
-    getTransaction().getSchedule().runJob(job);
-  }
-
   public EntityInterface getEntity() {
     return this.entity;
   }
@@ -237,14 +230,6 @@ public abstract class Job extends AbstractObservable implements Observer {
     }
   }
 
-  @Override
-  public boolean notifyObserver(final String e, final Observable o) {
-    if (getEntity().getEntityStatus() != EntityStatus.UNQUALIFIED) {
-      getTransaction().getSchedule().runJob(this);
-    }
-    return true;
-  }
-
   static HashMap<String, Class<? extends Job>> allClasses = null;
   private static List<Class<? extends Job>> loadAlways;
 
@@ -463,10 +448,6 @@ public abstract class Job extends AbstractObservable implements Observer {
         + "]";
   }
 
-  public void finish() {
-    super.removeAllObservers();
-  }
-
   public void print() {
     System.out.println(toString());
   }
diff --git a/src/main/java/org/caosdb/server/jobs/Schedule.java b/src/main/java/org/caosdb/server/jobs/Schedule.java
index 7f35106bcdda8ddc00512197091fa096a6bc157e..77bc57c9e3f4183bf572b6a95dfe16fc79b6e003 100644
--- a/src/main/java/org/caosdb/server/jobs/Schedule.java
+++ b/src/main/java/org/caosdb/server/jobs/Schedule.java
@@ -22,94 +22,48 @@
  */
 package org.caosdb.server.jobs;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import org.caosdb.server.entity.EntityInterface;
 
-class ScheduledJob {
-
-  long runtime = 0;
-  final Job job;
-  private long startTime = -1;
-
-  public ScheduledJob(final Job j) {
-    this.job = j;
-  }
-
-  public void run() {
-    if (!hasStarted()) {
-      start();
-      this.job.run();
-      finish();
-
-      this.job.notifyObservers(null);
-    }
-  }
-
-  private void start() {
-    this.startTime = System.currentTimeMillis();
-  }
-
-  private void finish() {
-    this.runtime += System.currentTimeMillis() - this.startTime;
-    this.job
-        .getContainer()
-        .getTransactionBenchmark()
-        .addMeasurement(this.job.getClass().getSimpleName(), this.runtime);
-  }
-
-  void pause() {
-    this.runtime += System.currentTimeMillis() - this.startTime;
-  }
-
-  void unpause() {
-    start();
-  }
-
-  private boolean hasStarted() {
-    return this.startTime != -1;
-  }
-
-  public JobExecutionTime getExecutionTime() {
-    return this.job.getExecutionTime();
-  }
-
-  public boolean skip() {
-    return this.job.getTarget().skipJob();
-  }
-}
-
 public class Schedule {
 
-  private final CopyOnWriteArrayList<ScheduledJob> jobs = new CopyOnWriteArrayList<ScheduledJob>();
+  private final Map<Integer, List<ScheduledJob>> jobLists = new HashMap<>();
   private ScheduledJob running = null;
 
-  public void addAll(final Collection<Job> jobs) {
+  public List<ScheduledJob> addAll(final Collection<Job> jobs) {
+    List<ScheduledJob> result = new ArrayList<ScheduledJob>(jobs.size());
     for (final Job j : jobs) {
-      add(j);
+      result.add(add(j));
     }
+    return result;
   }
 
   public ScheduledJob add(final Job j) {
     ScheduledJob ret = new ScheduledJob(j);
-    this.jobs.add(ret);
+    List<ScheduledJob> jobs = jobLists.get(ret.getExecutionTime().ordinal());
+    if (jobs == null) {
+      jobs = new CopyOnWriteArrayList<ScheduledJob>();
+      jobLists.put(ret.getExecutionTime().ordinal(), jobs);
+    }
+    jobs.add(ret);
     return ret;
   }
 
   public void runJobs(final JobExecutionTime time) {
-    for (final ScheduledJob scheduledJob : this.jobs) {
-      if (scheduledJob.getExecutionTime().ordinal() == time.ordinal()
-          || (time.ordinal() <= JobExecutionTime.POST_CHECK.ordinal()
-              && scheduledJob.getExecutionTime().ordinal() < time.ordinal())) {
+    List<ScheduledJob> jobs = this.jobLists.get(time.ordinal());
+    if (jobs != null) {
+      for (final ScheduledJob scheduledJob : jobs) {
         runJob(scheduledJob);
       }
     }
   }
 
-  protected void runJob(final ScheduledJob scheduledJob) {
-    if (!this.jobs.contains(scheduledJob)) {
-      throw new RuntimeException("Job was not in schedule.");
-    }
+  public void runJob(final ScheduledJob scheduledJob) {
     if (scheduledJob.skip()) {
       return;
     }
@@ -127,18 +81,12 @@ public class Schedule {
     }
   }
 
-  public void runJob(final Job j) {
-    for (final ScheduledJob scheduledJob : this.jobs) {
-      if (scheduledJob.job == j) {
-        scheduledJob.run();
-        return;
-      }
-    }
-    throw new RuntimeException("Job was not in schedule.");
-  }
-
   public void runJob(final EntityInterface entity, final Class<? extends Job> jobclass) {
-    for (final ScheduledJob scheduledJob : this.jobs) {
+    List<ScheduledJob> jobs =
+        jobclass.isAnnotationPresent(JobAnnotation.class)
+            ? this.jobLists.get(jobclass.getAnnotation(JobAnnotation.class).time().ordinal())
+            : this.jobLists.get(JobExecutionTime.CHECK.ordinal());
+    for (final ScheduledJob scheduledJob : jobs) {
       if (jobclass.isInstance(scheduledJob.job)) {
         if (scheduledJob.job.getEntity() == entity) {
           runJob(scheduledJob);
diff --git a/src/main/java/org/caosdb/server/jobs/ScheduledJob.java b/src/main/java/org/caosdb/server/jobs/ScheduledJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee0c29805d7500987f5823f2c7d3eaec8f6ed50b
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/ScheduledJob.java
@@ -0,0 +1,88 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.jobs;
+
+/**
+ * ScheduledJob is a wrapper class for jobs held by the Scheduler.
+ *
+ * <p>It is mainly a means to have simplified interface for the Scheduler which also measures the
+ * execution time of the job "from outside".
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public class ScheduledJob {
+
+  long runtime = 0;
+  final Job job;
+  private long startTime = -1;
+
+  ScheduledJob(final Job j) {
+    this.job = j;
+  }
+
+  void run() {
+    if (!hasStarted()) {
+      start();
+      this.job.run();
+      finish();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return this.job.toString();
+  }
+
+  private void start() {
+    this.startTime = System.currentTimeMillis();
+  }
+
+  private void finish() {
+    this.runtime += System.currentTimeMillis() - this.startTime;
+    this.job
+        .getContainer()
+        .getTransactionBenchmark()
+        .addMeasurement(this.job.getClass().getSimpleName(), this.runtime);
+  }
+
+  void pause() {
+    this.runtime += System.currentTimeMillis() - this.startTime;
+  }
+
+  void unpause() {
+    start();
+  }
+
+  private boolean hasStarted() {
+    return this.startTime != -1;
+  }
+
+  public JobExecutionTime getExecutionTime() {
+    return this.job.getExecutionTime();
+  }
+
+  public boolean skip() {
+    return this.job.getTarget().skipJob();
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/Atomic.java b/src/main/java/org/caosdb/server/jobs/core/Atomic.java
index 509f87fc8515ca08daa1a5cb8ef6571dfe6fa441..45b5de8e4d5835bd835a797290e2d3c875687e44 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Atomic.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Atomic.java
@@ -30,13 +30,14 @@ import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.transaction.WriteTransaction;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
+import org.caosdb.server.utils.Observer;
 import org.caosdb.server.utils.ServerMessages;
 
 @JobAnnotation(
-    time = JobExecutionTime.POST_CHECK,
+    time = JobExecutionTime.PRE_TRANSACTION,
     transaction = WriteTransaction.class,
     loadAlways = true)
-public class Atomic extends ContainerJob {
+public class Atomic extends ContainerJob implements Observer {
 
   private boolean doCheck() {
     if (getContainer().getStatus() == EntityStatus.QUALIFIED) {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
index ce9943ecbefb4d669003a097e74bf9baff078167..f46013d278f268b95b1b105130e1eabd74f3a9f4 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
@@ -33,6 +33,7 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Role;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.jobs.Job;
+import org.caosdb.server.jobs.ScheduledJob;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -81,8 +82,8 @@ public final class CheckDatatypePresent extends EntityJob {
 
         // run jobsreturn this.entities;
         final List<Job> datatypeJobs = loadDataTypeSpecificJobs();
-        getTransaction().getSchedule().addAll(datatypeJobs);
-        for (final Job job : datatypeJobs) {
+        List<ScheduledJob> scheduledJobs = getTransaction().getSchedule().addAll(datatypeJobs);
+        for (final ScheduledJob job : scheduledJobs) {
           getTransaction().getSchedule().runJob(job);
         }
       } else {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
index c6d10a8f4f7cd0656f571a4cbf70385ab9cb7725..02cb96fc1214712c7a8ef1bb98327ba9fb39e9d0 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
@@ -32,6 +32,8 @@ import org.caosdb.server.entity.Role;
 import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -41,6 +43,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
+@JobAnnotation(time = JobExecutionTime.PRE_CHECK)
 public class CheckParValid extends EntityJob {
   @Override
   public final void run() {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
index 10ce6295a5b4910084d769278ca6348287e6223a..f95104bd2f2552a642e7e4bcd2fc13f7fd2788d4 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
@@ -31,6 +31,8 @@ import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
@@ -40,6 +42,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
+@JobAnnotation(time = JobExecutionTime.PRE_CHECK)
 public class CheckPropValid extends EntityJob {
   @Override
   public final void run() {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckRefidIsaParRefid.java b/src/main/java/org/caosdb/server/jobs/core/CheckRefidIsaParRefid.java
index 59fcfe1d877ca61c98674ee85919f03b7bd4278e..b9f804f519948b605a9fa8820c91495618463fba 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidIsaParRefid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidIsaParRefid.java
@@ -37,6 +37,7 @@ import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
+import org.caosdb.server.utils.Observer;
 import org.caosdb.server.utils.ServerMessages;
 
 /**
@@ -45,7 +46,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
-public class CheckRefidIsaParRefid extends EntityJob {
+public class CheckRefidIsaParRefid extends EntityJob implements Observer {
 
   private void doJob() {
     try {
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 c9d06cb5487ff62a276ff1028c22864afa850562..8563ea716072602684dc1a78870392d573dc3a22 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
@@ -36,6 +36,7 @@ import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.permissions.EntityPermission;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
+import org.caosdb.server.utils.Observer;
 import org.caosdb.server.utils.ServerMessages;
 
 /**
@@ -43,7 +44,7 @@ import org.caosdb.server.utils.ServerMessages;
  *
  * @author tf
  */
-public class CheckRefidValid extends EntityJob {
+public class CheckRefidValid extends EntityJob implements Observer {
   @Override
   public final void run() {
     try {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd187cf17ad72c5431fcd3f2eb0b95890941faf9
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckStateTransition.java
@@ -0,0 +1,200 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.jobs.core;
+
+import java.util.Map;
+import org.apache.shiro.authz.AuthorizationException;
+import org.caosdb.server.entity.DeleteEntity;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.Message.MessageType;
+import org.caosdb.server.entity.UpdateEntity;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.transaction.WriteTransaction;
+import org.caosdb.server.utils.ServerMessages;
+
+/**
+ * Check if the attempted state transition is allowed.
+ *
+ * <p>This job checks if the attempted state transition is in compliance with the state model. This
+ * job runs during the CHECK phase and should do all necessary consistency and permission checks.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+@JobAnnotation(time = JobExecutionTime.POST_CHECK, transaction = WriteTransaction.class)
+public class CheckStateTransition extends EntityStateJob {
+
+  private static final String PERMISSION_STATE_FORCE_FINAL = "STATE:FORCE:FINAL";
+  private static final String PERMISSION_STATE_UNASSIGN = "STATE:UNASSIGN:";
+  private static final String PERMISSION_STATE_ASSIGN = "STATE:ASSIGN:";
+  private static final Message TRANSITION_NOT_ALLOWED =
+      new Message(MessageType.Error, "Transition not allowed.");
+  private static final Message INITIAL_STATE_NOT_ALLOWED =
+      new Message(MessageType.Error, "Initial state not allowed.");
+  private static final Message FINAL_STATE_NOT_ALLOWED =
+      new Message(MessageType.Error, "Final state not allowed.");
+
+  /**
+   * The forceFinalState flag is especially useful if you want to delete entities in the middle of
+   * the state machine's usual state cycle.
+   */
+  private static final String FLAG_FORCE_FINAL_STATE = "forceFinalState";
+
+  @Override
+  protected void run() {
+    try {
+      State newState = getState();
+      if (newState != null) {
+        checkStateValid(newState);
+      }
+      if (getEntity() instanceof UpdateEntity) {
+        State oldState = getState(((UpdateEntity) getEntity()).getOriginal());
+        checkStateTransition(oldState, newState);
+      } else if (getEntity() instanceof DeleteEntity) {
+        if (newState != null) checkFinalState(newState);
+      } else { // fresh Entity
+        if (newState != null) checkInitialState(newState);
+      }
+    } catch (Message m) {
+      getEntity().addError(m);
+    } catch (AuthorizationException e) {
+      getEntity().addError(ServerMessages.AUTHORIZATION_ERROR);
+      getEntity().addInfo(e.getMessage());
+    }
+  }
+
+  /**
+   * Check if the state belongs to the state model.
+   *
+   * <p>In practical terms, throw a Message if the state is invalid.
+   *
+   * @param state
+   * @throws Message
+   */
+  private void checkStateValid(State state) throws Message {
+    if (state.isFinal() || state.isInitial() || state.getStateModel().getStates().contains(state)) {
+      return;
+    }
+    throw STATE_NOT_IN_STATE_MODEL;
+  }
+
+  /**
+   * Check if state is valid and transition is allowed.
+   *
+   * <p>Especially, transitions between {@code null} states are allowed, non-trivial transitions
+   * from or to {@code null} must be initial or final states, respectively ({@link
+   * FORCE_FINAL_STATE} exception applies).
+   *
+   * @param oldState
+   * @param newState
+   * @throws Message if not
+   */
+  private void checkStateTransition(State oldState, State newState) throws Message {
+    if (oldState == null && newState == null) {
+      return;
+    } else if (oldState == null && newState != null) {
+      checkInitialState(newState);
+      return;
+    } else if (newState == null && oldState != null) {
+      checkFinalState(oldState);
+      return;
+    }
+
+    StateModel stateModel = findMatchingStateModel(oldState, newState);
+    if (stateModel == null) {
+      // change from one stateModel to another
+      checkInitialState(newState);
+      checkFinalState(oldState);
+      return;
+    }
+
+    boolean transition_defined = false;
+    for (Transition t : stateModel.getTransitions()) {
+      if (t.isFromState(oldState) && t.isToState(newState)) {
+        transition_defined = true;
+        if (t.isPermitted(getUser())) {
+          return;
+        }
+      }
+    }
+    if (transition_defined) {
+      throw new AuthorizationException(
+          getUser().getPrincipal().toString()
+              + " doesn't have permission to perform this transition.");
+    }
+    throw TRANSITION_NOT_ALLOWED;
+  }
+
+  /**
+   * Return the two State's common StateModel, or {@code null} if they don't have one in common.
+   *
+   * @param oldState
+   * @param newState
+   * @return the state model which contains both of the states.
+   * @throws Message if the state model of one of the states cannot be constructed.
+   */
+  private StateModel findMatchingStateModel(State oldState, State newState) throws Message {
+    if (oldState.getStateModel().equals(newState.getStateModel())) {
+      return oldState.getStateModel();
+    }
+    return null;
+  }
+
+  /**
+   * Check if the old state is final or if the {@link FORCE_FINAL_STATE} flag is true.
+   *
+   * @param oldState
+   * @throws Message if the state is not final.
+   */
+  private void checkFinalState(State oldState) throws Message {
+    if (!oldState.isFinal()) {
+      if (isForceFinal()) {
+        getUser().checkPermission(PERMISSION_STATE_FORCE_FINAL);
+      } else {
+        throw FINAL_STATE_NOT_ALLOWED;
+      }
+    }
+    getUser().checkPermission(PERMISSION_STATE_UNASSIGN + oldState.getStateModelName());
+  }
+
+  /**
+   * Check if the new state is an initial state.
+   *
+   * @param newState
+   * @throws Message if not
+   */
+  private void checkInitialState(State newState) throws Message {
+    if (!newState.isInitial()) {
+      throw INITIAL_STATE_NOT_ALLOWED;
+    }
+    getUser().checkPermission(PERMISSION_STATE_ASSIGN + newState.getStateModelName());
+  }
+
+  private boolean isForceFinal() {
+    Map<String, String> containerFlags = getTransaction().getContainer().getFlags();
+    return (containerFlags != null
+            && "true".equalsIgnoreCase(containerFlags.get(FLAG_FORCE_FINAL_STATE)))
+        || "true".equalsIgnoreCase(getEntity().getFlag(FLAG_FORCE_FINAL_STATE));
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckValueParsable.java b/src/main/java/org/caosdb/server/jobs/core/CheckValueParsable.java
index 34e1960961f72118567653a77e4f48fe357dfedc..1dd457143f8cc23c0136f2c51cb40edf8552ba15 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckValueParsable.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckValueParsable.java
@@ -28,6 +28,7 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.jobs.EntityJob;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
+import org.caosdb.server.utils.Observer;
 
 /**
  * Check whether the value of an entity is parsable according to the entity's data type. This job
@@ -37,7 +38,7 @@ import org.caosdb.server.utils.Observable;
  *
  * @author tf
  */
-public class CheckValueParsable extends EntityJob {
+public class CheckValueParsable extends EntityJob implements Observer {
   @Override
   public final void run() {
 
@@ -96,7 +97,6 @@ public class CheckValueParsable extends EntityJob {
       // Therefore, not the whole test has to be run again.
       if (getEntity().hasDatatype()) {
         parseValue();
-        super.notifyObservers(e);
         return false;
       }
     }
diff --git a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..c20b18b6e4950c07967d2cfcdd776c95c25f4a53
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java
@@ -0,0 +1,857 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.jobs.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.shiro.subject.Subject;
+import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
+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.ReferenceDatatype2;
+import org.caosdb.server.datatype.ReferenceValue;
+import org.caosdb.server.datatype.TextDatatype;
+import org.caosdb.server.entity.ClientMessage;
+import org.caosdb.server.entity.DeleteEntity;
+import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.Message.MessageType;
+import org.caosdb.server.entity.StatementStatus;
+import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.entity.xml.ToElementable;
+import org.caosdb.server.jobs.EntityJob;
+import org.caosdb.server.permissions.EntityACI;
+import org.caosdb.server.permissions.EntityACL;
+import org.caosdb.server.query.Query;
+import org.caosdb.server.utils.EntityStatus;
+import org.jdom2.Element;
+
+/**
+ * The EntityStateJob is the abstract base class for four EntityJobs:
+ *
+ * <p>1. The {@link InitEntityState} job reads ClientMessages or StateProperties with tag state and
+ * converts them into instances of State. This job runs during WriteTransactions. This job runs
+ * during the INIT Phase and does not perform any checks other than those necessary for the
+ * conversion.
+ *
+ * <p>2. The {@link CheckStateTransition} job checks if the attempted state transition is in
+ * compliance with the state model. This job runs during the CHECK phase and should do all necessary
+ * consistency and permission checks.
+ *
+ * <p>3. The {@link MakeStateProperty} job constructs an ordinary Property from the State right
+ * before the entity is being written to the back-end and after any checks run.
+ *
+ * <p>4. The {@link MakeStateMessage} job converts a state property (back) into State messages and
+ * appends them to the entity.
+ *
+ * <p>Only the 4th job ({@link MakeStateMessage}) runs during Retrieve transitions. During
+ * WriteTransactions all four jobs do run.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+public abstract class EntityStateJob extends EntityJob {
+
+  protected static final String SERVER_PROPERTY_EXT_ENTITY_STATE = "EXT_ENTITY_STATE";
+
+  public static final String TO_STATE_PROPERTY_NAME = "to";
+  public static final String FROM_STATE_PROPERTY_NAME = "from";
+  public static final String FINAL_STATE_PROPERTY_NAME = "final";
+  public static final String INITIAL_STATE_PROPERTY_NAME = "initial";
+  public static final String STATE_RECORD_TYPE_NAME = "State";
+  public static final String STATE_MODEL_RECORD_TYPE_NAME = "StateModel";
+  public static final String TRANSITION_RECORD_TYPE_NAME = "Transition";
+  public static final String TRANSITION_XML_TAG = "Transition";
+  public static final String TRANSITION_ATTRIBUTE_NAME = "name";
+  public static final String TRANSITION_ATTRIBUTE_DESCRIPTION = "description";
+  public static final String TO_XML_TAG = "ToState";
+  public static final String FROM_XML_TAG = "FromState";
+  public static final String STATE_XML_TAG = "State";
+  public static final String STATE_ATTRIBUTE_MODEL = "model";
+  public static final String STATE_ATTRIBUTE_NAME = "name";
+  public static final String STATE_ATTRIBUTE_DESCRIPTION = "description";
+  public static final String STATE_ATTRIBUTE_ID = "id";
+  public static final String ENTITY_STATE_ROLE_MARKER = "?STATE?";
+  public static final String PERMISSION_STATE_TRANSION = "STATE:TRANSITION:";
+
+  public static final Message STATE_MODEL_NOT_FOUND =
+      new Message(MessageType.Error, "StateModel not found.");
+  public static final Message STATE_NOT_IN_STATE_MODEL =
+      new Message(MessageType.Error, "State does not exist in this StateModel.");
+  public static final Message COULD_NOT_CONSTRUCT_STATE_MESSAGE =
+      new Message(MessageType.Error, "Could not construct the state message.");
+  public static final Message COULD_NOT_CONSTRUCT_TRANSITIONS =
+      new Message(MessageType.Error, "Could not construct the transitions.");
+  public static final Message STATE_MODEL_NOT_SPECIFIED =
+      new Message(MessageType.Error, "State model not specified.");
+  public static final Message STATE_NOT_SPECIFIED =
+      new Message(MessageType.Error, "State not specified.");
+
+  /**
+   * Represents a Transition which is identified by a name and the two States from and to which an
+   * entity is being transitioned.
+   *
+   * <p>Currently, only exactly one toState and one fromState can be defined. However, it might be
+   * allowed in the future to have multiple states here.
+   *
+   * @author Timm Fitschen (t.fitschen@indiscale.com)
+   */
+  public class Transition {
+
+    private String name;
+    private String description;
+    private State fromState;
+    private State toState;
+    private Map<String, String> transitionProperties;
+
+    /**
+     * @param transition The transition Entity, from which the Transition is created. Relevant
+     *     Properties are "to" and "from"
+     */
+    public Transition(EntityInterface transition) throws Message {
+      this.name = transition.getName();
+      this.description = transition.getDescription();
+      this.fromState = getFromState(transition);
+      this.toState = getToState(transition);
+      this.transitionProperties = getTransitionProperties(transition);
+    }
+
+    private Map<String, String> getTransitionProperties(EntityInterface transition) {
+      Map<String, String> result = new LinkedHashMap<>();
+      for (Property p : transition.getProperties()) {
+        if (p.getDatatype() instanceof TextDatatype) {
+          result.put(p.getName(), p.getValue().toString());
+        }
+      }
+      return result;
+    }
+
+    private State getToState(EntityInterface transition) throws Message {
+      for (Property p : transition.getProperties()) {
+        if (p.getName().equals(TO_STATE_PROPERTY_NAME)) {
+          return createState(p);
+        }
+      }
+      return null;
+    }
+
+    private State getFromState(EntityInterface transition) throws Message {
+      for (Property p : transition.getProperties()) {
+        if (p.getName().equals(FROM_STATE_PROPERTY_NAME)) {
+          return createState(p);
+        }
+      }
+      return null;
+    }
+
+    /**
+     * @param previousState
+     * @return true iff the previous state is a fromState of this transition.
+     */
+    public boolean isFromState(State previousState) {
+      return this.fromState.equals(previousState);
+    }
+
+    /**
+     * @param nextState
+     * @return true iff the next state is a toState of this transition.
+     */
+    public boolean isToState(State nextState) {
+      return this.toState.equals(nextState);
+    }
+
+    public State getToState() {
+      return this.toState;
+    }
+
+    public State getFromState() {
+      return this.fromState;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj instanceof Transition) {
+        Transition that = (Transition) obj;
+        return Objects.equals(this.getName(), that.getName())
+            && Objects.equals(this.getFromState(), that.getFromState())
+            && Objects.equals(this.getToState(), that.getToState());
+      }
+      return false;
+    }
+
+    public String getName() {
+      return this.name;
+    }
+
+    public String getDescription() {
+      return this.description;
+    }
+
+    @Override
+    public String toString() {
+      return "Transition (name="
+          + getName()
+          + ", from="
+          + getFromState().getStateName()
+          + ", to="
+          + getToState().getStateName()
+          + ")";
+    }
+
+    public Element toElement() {
+      Element result = new Element(TRANSITION_XML_TAG);
+      if (this.transitionProperties != null) {
+        this.transitionProperties.forEach(
+            (String key, String value) -> {
+              result.setAttribute(key, value);
+            });
+      }
+      if (this.name != null) {
+        result.setAttribute(TRANSITION_ATTRIBUTE_NAME, this.name);
+      }
+      if (this.description != null) {
+        result.setAttribute(TRANSITION_ATTRIBUTE_DESCRIPTION, this.description);
+      }
+      Element to = new Element(TO_XML_TAG);
+      to.setAttribute(STATE_ATTRIBUTE_NAME, this.toState.stateName);
+      if (this.toState.stateDescription != null) {
+        to.setAttribute(STATE_ATTRIBUTE_DESCRIPTION, this.toState.stateDescription);
+      }
+      Element from = new Element(FROM_XML_TAG);
+      from.setAttribute(STATE_ATTRIBUTE_NAME, this.fromState.stateName);
+      return result.addContent(from).addContent(to);
+    }
+
+    public boolean isPermitted(Subject user) {
+      return user.isPermitted(PERMISSION_STATE_TRANSION + this.name);
+    }
+  }
+
+  /**
+   * The State instance represents a single entity state. This class is used for concrete states
+   * (the state of a stateful entity, say a Record) and abstract states (states which are part of a
+   * {@link StateModel}).
+   *
+   * <p>States are identified via their name and the name of the model to which they belong.
+   *
+   * <p>States are represented by Records with the state's name as the Record name. They belong to a
+   * StateModel iff the StateModel RecordType references the State Record. Each State should only
+   * belong to one StateModel.
+   *
+   * <p>Furthermore, States are the start or end point of {@link Transition Transitions} which
+   * belong to the same StateModel. Each State can be part of several transitions at the same time.
+   *
+   * <p>Note: The purpose of this should not be confused with {@link EntityStatus} which is purely
+   * for internal use.
+   *
+   * @author Timm Fitschen (t.fitschen@indiscale.com)
+   */
+  public class State implements ToElementable {
+
+    private String stateModelName = null;
+    private String stateName = null;
+    private EntityInterface stateEntity = null;
+    private EntityInterface stateModelEntity = null;
+    private StateModel stateModel;
+    private String stateDescription = null;
+    private Integer stateId = null;
+    private EntityACL stateACL = null;
+    private Map<String, String> stateProperties;
+
+    public State(EntityInterface stateEntity, EntityInterface stateModelEntity) throws Message {
+      this.stateEntity = stateEntity;
+      this.stateDescription = stateEntity.getDescription();
+      this.stateId = stateEntity.getId();
+      this.stateName = stateEntity.getName();
+      this.stateModelEntity = stateModelEntity;
+      this.stateModelName = stateModelEntity.getName();
+      this.stateACL = createStateACL(stateEntity.getEntityACL());
+      this.stateProperties = createStateProperties(stateEntity);
+    }
+
+    private Map<String, String> createStateProperties(EntityInterface stateEntity) {
+      Map<String, String> result = new LinkedHashMap<>();
+      for (Property p : stateEntity.getProperties()) {
+        if (p.getDatatype() instanceof TextDatatype) {
+          result.put(p.getName(), p.getValue().toString());
+        }
+      }
+      return result;
+    }
+
+    private EntityACL createStateACL(EntityACL entityACL) {
+      LinkedList<EntityACI> rules = new LinkedList<>();
+      for (EntityACI aci : entityACL.getRules()) {
+        if (aci.getResponsibleAgent().toString().startsWith(ENTITY_STATE_ROLE_MARKER)) {
+          int end = aci.getResponsibleAgent().toString().length() - 1;
+          String role = aci.getResponsibleAgent().toString().substring(7, end);
+          rules.add(
+              new EntityACI(org.caosdb.server.permissions.Role.create(role), aci.getBitSet()));
+        }
+      }
+      return new EntityACL(rules);
+    }
+
+    public EntityACL getStateACL() {
+      return this.stateACL;
+    }
+
+    public String getStateDescription() throws Message {
+      return this.stateDescription;
+    }
+
+    public Integer getStateId() throws Message {
+      return this.stateId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj instanceof State) {
+        State that = (State) obj;
+        return Objects.equals(that.getStateName(), this.getStateName())
+            && Objects.equals(that.getStateModelName(), this.getStateModelName());
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return 21364234 + this.getStateName().hashCode() + this.getStateModelName().hashCode();
+    }
+
+    /**
+     * Serialize this State into XML.
+     *
+     * <p>The result looks approximately like this: {@code <State name="My name" model="Model's
+     * name"/>}
+     */
+    @Override
+    public void addToElement(Element ret) {
+      Element e = new Element(STATE_XML_TAG);
+      if (this.stateProperties != null) {
+        this.stateProperties.forEach(
+            (String key, String value) -> {
+              e.setAttribute(key, value);
+            });
+      }
+      if (this.stateModelName != null) {
+        e.setAttribute(STATE_ATTRIBUTE_MODEL, this.stateModelName);
+      }
+      if (this.stateName != null) {
+        e.setAttribute(STATE_ATTRIBUTE_NAME, this.stateName);
+      }
+      if (this.stateDescription != null) {
+        e.setAttribute(STATE_ATTRIBUTE_DESCRIPTION, this.stateDescription);
+      }
+      if (this.stateId != null) {
+        e.setAttribute(STATE_ATTRIBUTE_ID, Integer.toString(this.stateId));
+      }
+      if (this.stateModel != null) {
+        this.stateModel.transitions.forEach(
+            (Transition t) -> {
+              if (t.isFromState(this) && t.isPermitted(getUser())) {
+                e.addContent(t.toElement());
+              }
+            });
+      }
+      ret.addContent(e);
+    }
+
+    public String getStateModelName() {
+      return this.stateModelName;
+    }
+
+    private String getStateName() {
+      return this.stateName;
+    }
+
+    public StateModel getStateModel() throws Message {
+      if (this.stateModel == null) {
+        this.stateModel = new StateModel(this.stateModelEntity);
+      }
+      return this.stateModel;
+    }
+
+    /**
+     * @return true iff this state is an initial state of its StateModel.
+     * @throws Message
+     */
+    public boolean isInitial() throws Message {
+      return Objects.equals(this, getStateModel().initialState);
+    }
+
+    /**
+     * @return true iff this state is a final state of its StateModel.
+     * @throws Message
+     */
+    public boolean isFinal() throws Message {
+      return Objects.equals(this, getStateModel().finalState);
+    }
+
+    /**
+     * Create a Property which represents the current entity state of a stateful entity.
+     *
+     * @return stateProperty
+     * @throws Message
+     */
+    public Property createStateProperty() throws Message {
+      EntityInterface stateRecordType = getStateRecordType();
+      Property stateProperty = new Property(stateRecordType);
+      stateProperty.setDatatype(new ReferenceDatatype2(stateRecordType));
+      stateProperty.setValue(new ReferenceValue(getStateEntity(), false));
+      stateProperty.setStatementStatus(StatementStatus.FIX);
+      return stateProperty;
+    }
+
+    public EntityInterface getStateEntity() {
+      return this.stateEntity;
+    }
+
+    public EntityInterface getStateModelEntity() {
+      return this.stateModelEntity;
+    }
+
+    @Override
+    public String toString() {
+      String isInitial = null;
+      String isFinal = null;
+      try {
+        isInitial = String.valueOf(isInitial());
+      } catch (Message e) {
+        isInitial = "null";
+      }
+      try {
+        isFinal = String.valueOf(isFinal());
+      } catch (Message e) {
+        isFinal = "null";
+      }
+      return "State (name="
+          + getStateName()
+          + ", model="
+          + getStateModelName()
+          + ", initial="
+          + isInitial
+          + ", final="
+          + isFinal
+          + ")";
+    }
+  }
+
+  /**
+   * A StateModel is an abstract definition of a Finite State Machine for entities.
+   *
+   * <p>It consists of a set of States, a set of transitions, a initial state and a final state.
+   *
+   * <p>If the StateModel has no initial state, it cannot be initialized (no entity will ever be in
+   * any of the StateModel's states) without using the forceInitialState flag.
+   *
+   * <p>If the StateModel has not final state, an entity with any of the states from this StateModel
+   * cannot leave this StateModel (and cannot be deleted either) without using the forceFinalState
+   * flag.
+   *
+   * @author Timm Fitschen (t.fitschen@indiscale.com)
+   */
+  public class StateModel {
+
+    private String name;
+    private Set<State> states;
+    private Set<Transition> transitions;
+    private State initialState;
+    private State finalState;
+
+    public StateModel(EntityInterface stateModelEntity) throws Message {
+      this.name = stateModelEntity.getName();
+      this.transitions = getTransitions(stateModelEntity);
+      this.states = getStates(transitions, this);
+      this.finalState = getFinalState(stateModelEntity);
+      this.initialState = getInitialState(stateModelEntity);
+    }
+
+    private State getInitialState(EntityInterface stateModelEntity) throws Message {
+      // TODO maybe check if there is more than one "initial" Property?
+      for (Property p : stateModelEntity.getProperties()) {
+        if (p.getName().equals(INITIAL_STATE_PROPERTY_NAME)) {
+          return createState(p);
+        }
+      }
+      return null;
+    }
+
+    private State getFinalState(EntityInterface stateModelEntity) throws Message {
+      // TODO maybe check if there is more than one "final" Property?
+      for (Property p : stateModelEntity.getProperties()) {
+        if (p.getName().equals(FINAL_STATE_PROPERTY_NAME)) {
+          return createState(p);
+        }
+      }
+      return null;
+    }
+
+    /** Transitions are taken from list Property with name="Transition". */
+    private Set<Transition> getTransitions(EntityInterface stateModelEntity) throws Message {
+      for (Property p : stateModelEntity.getProperties()) {
+        if (p.getName().equals(TRANSITION_RECORD_TYPE_NAME)) {
+          return createTransitions(p);
+        }
+      }
+      return null;
+    }
+
+    /**
+     * Read out the "Transition" property and create Transition instances.
+     *
+     * @param p the transition property
+     * @return a set of transitions
+     * @throws Message if the transitions could ne be created.
+     */
+    private Set<Transition> createTransitions(Property p) throws Message {
+      Set<Transition> result = new LinkedHashSet<>();
+      try {
+        if (!(p.getDatatype() instanceof AbstractCollectionDatatype)) {
+          // FIXME raise an exception instead?
+          return result;
+        }
+        p.parseValue();
+        CollectionValue vals = (CollectionValue) p.getValue();
+        for (IndexedSingleValue val : vals) {
+          if (val.getWrapped() instanceof ReferenceValue) {
+            Integer refid = ((ReferenceValue) val.getWrapped()).getId();
+
+            String key = "transition" + Integer.toString(refid);
+            EntityInterface transition = getCached(key);
+            if (transition == null) {
+              transition = retrieveValidEntity(refid);
+              putCache(key, transition);
+            }
+            result.add(new Transition(transition));
+          }
+        }
+      } catch (Exception e) {
+        throw COULD_NOT_CONSTRUCT_TRANSITIONS;
+      }
+      return result;
+    }
+
+    /**
+     * Collect all possible states from the set of transitions.
+     *
+     * <p>This function does not perform any consistency checks. It only add all toStates and
+     * fromStates of the transitions to the result.
+     *
+     * @param transitions
+     * @param stateModel
+     * @return set of states.
+     * @throws Message
+     */
+    private Set<State> getStates(Set<Transition> transitions, StateModel stateModel)
+        throws Message {
+      // TODO Move outside of this class
+      Iterator<Transition> it = transitions.iterator();
+      Set<State> result = new LinkedHashSet<>();
+      while (it.hasNext()) {
+        Transition t = it.next();
+        result.add(t.getFromState());
+        result.add(t.getToState());
+      }
+      return result;
+    }
+
+    public String getName() {
+      return this.name;
+    }
+
+    public Set<State> getStates() {
+      return this.states;
+    }
+
+    public Set<Transition> getTransitions() {
+      return this.transitions;
+    }
+
+    public State getFinalState() {
+      return this.finalState;
+    }
+
+    public State getInitialState() {
+      return this.initialState;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj instanceof StateModel) {
+        return ((StateModel) obj).getName().equals(this.getName());
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("StateModel (name=");
+      sb.append(this.getName());
+      sb.append(", initial=");
+      sb.append(this.getInitialState().stateName);
+      sb.append(", final=");
+      sb.append(this.getFinalState().stateName);
+      sb.append(", transitions=[");
+      Iterator<Transition> iterator = this.transitions.iterator();
+      while (iterator.hasNext()) {
+        sb.append(iterator.next().name);
+        sb.append(" -> ");
+        sb.append(iterator.next().name);
+        sb.append(", ");
+      }
+      sb.append("])");
+      return sb.toString();
+    }
+  }
+
+  private EntityInterface retrieveStateEntity(String stateName) throws Message {
+    try {
+      return retrieveValidEntity(retrieveValidIDByName(stateName));
+    } catch (EntityDoesNotExistException e) {
+      throw STATE_NOT_IN_STATE_MODEL;
+    }
+  }
+
+  private EntityInterface retrieveStateModelEntity(String stateModel) throws Message {
+    try {
+      return retrieveValidEntity(retrieveValidIDByName(stateModel));
+    } catch (EntityDoesNotExistException e) {
+      throw STATE_MODEL_NOT_FOUND;
+    }
+  }
+
+  protected EntityInterface getStateRecordType() throws Message {
+    EntityInterface stateRecordType = getCached(STATE_RECORD_TYPE_NAME);
+    if (stateRecordType == null) {
+      stateRecordType = retrieveValidSparseEntityByName(STATE_RECORD_TYPE_NAME);
+      putCache(STATE_RECORD_TYPE_NAME, stateRecordType);
+    }
+    return stateRecordType;
+  }
+
+  protected State getState() {
+    return getState(false);
+  }
+
+  protected State getState(EntityInterface entity) {
+    return getState(entity, false);
+  }
+
+  protected State getState(EntityInterface entity, boolean remove) {
+    Iterator<ToElementable> messages = entity.getMessages().iterator();
+    while (messages.hasNext()) {
+      ToElementable s = messages.next();
+      if (s instanceof State) {
+        if (remove) {
+          messages.remove();
+        }
+        return (State) s;
+      }
+    }
+    return null;
+  }
+
+  protected State getState(boolean remove) {
+    return getState(getEntity(), remove);
+  }
+
+  /** Return (and possibly remove) the States Properties of `entity`. */
+  protected List<Property> getStateProperties(EntityInterface entity, boolean remove) {
+    Iterator<Property> it = entity.getProperties().iterator();
+    List<Property> result = new ArrayList<>();
+    while (it.hasNext()) {
+      Property p = it.next();
+      if (Objects.equals(p.getName(), STATE_RECORD_TYPE_NAME)) {
+        if (!(p.getDatatype() instanceof ReferenceDatatype)) {
+          continue;
+        }
+        if (remove) {
+          it.remove();
+        }
+        result.add(p);
+      }
+    }
+    return result;
+  }
+
+  protected List<Property> getStateProperties(boolean remove) {
+    return getStateProperties(getEntity(), remove);
+  }
+
+  /** Get the {@code ClientMessage}s which denote a state. */
+  protected List<ClientMessage> getStateClientMessages(EntityInterface entity, boolean remove) {
+    Iterator<ToElementable> stateMessages = entity.getMessages().iterator();
+    List<ClientMessage> result = new ArrayList<>();
+    while (stateMessages.hasNext()) {
+      ToElementable s = stateMessages.next();
+      if (s instanceof ClientMessage && STATE_XML_TAG.equals(((ClientMessage) s).getType())) {
+        if (remove) {
+          stateMessages.remove();
+        }
+        result.add((ClientMessage) s);
+      }
+    }
+    return result;
+  }
+
+  protected List<ClientMessage> getStateClientMessages(boolean remove) {
+    return getStateClientMessages(getEntity(), remove);
+  }
+
+  protected State createState(ClientMessage s) throws Message {
+    String stateModel = s.getProperty(STATE_ATTRIBUTE_MODEL);
+    if (stateModel == null) {
+      throw STATE_MODEL_NOT_SPECIFIED;
+    }
+    String stateName = s.getProperty(STATE_ATTRIBUTE_NAME);
+    if (stateName == null) {
+      throw STATE_NOT_SPECIFIED;
+    }
+    String stateModelKey = "statemodel:" + stateModel;
+
+    EntityInterface stateModelEntity = getCached(stateModelKey);
+    if (stateModelEntity == null) {
+      stateModelEntity = retrieveStateModelEntity(stateModel);
+      putCache(stateModelKey, stateModelEntity);
+    }
+
+    String stateKey = "namestate:" + stateName;
+
+    EntityInterface stateEntity = getCached(stateKey);
+    if (stateEntity == null) {
+      stateEntity = retrieveStateEntity(stateName);
+      putCache(stateKey, stateEntity);
+    }
+    return new State(stateEntity, stateModelEntity);
+  }
+
+  /**
+   * Create a State instance from the value of the state property.
+   *
+   * <p>This method also retrieves the state entity from the back-end. The StateModel is deduced
+   * from finding an appropriately referencing StateModel Record.
+   *
+   * @param p the entity's state property
+   * @return The state of the entity
+   * @throws Message
+   */
+  protected State createState(Property p) throws Message {
+    try {
+      p.parseValue();
+      ReferenceValue refid = (ReferenceValue) p.getValue();
+      String key = "idstate" + Integer.toString(refid.getId());
+
+      EntityInterface stateEntity = getCached(key);
+      if (stateEntity == null) {
+        stateEntity = retrieveValidEntity(refid.getId());
+        putCache(key, stateEntity);
+      }
+
+      EntityInterface stateModelEntity = findStateModel(stateEntity);
+      return new State(stateEntity, stateModelEntity);
+    } catch (Message e) {
+      throw e;
+    } catch (Exception e) {
+      throw COULD_NOT_CONSTRUCT_STATE_MESSAGE;
+    }
+  }
+
+  private static final Map<String, EntityInterface> cache = new HashMap<>();
+  private static final Set<Integer> id_in_cache = new HashSet<>();
+
+  EntityInterface findStateModel(EntityInterface stateEntity) throws Exception {
+    boolean cached = true;
+    String key = "modelof" + Integer.toString(stateEntity.getId());
+
+    EntityInterface result = getCached(key);
+    if (result != null && cached) {
+      return result;
+    }
+    // TODO This should throw a meaningful Exception if no matching StateModel can be found.
+    TransactionContainer c = new TransactionContainer();
+    Query query =
+        new Query(
+            "FIND RECORD "
+                + STATE_MODEL_RECORD_TYPE_NAME
+                + " WHICH REFERENCES "
+                + TRANSITION_RECORD_TYPE_NAME
+                + " WHICH REFERENCES "
+                + Integer.toString(stateEntity.getId()),
+            getUser(),
+            c);
+    query.execute(getTransaction().getAccess());
+    result = retrieveValidEntity(c.get(0).getId());
+    putCache(key, result);
+    return result;
+  }
+
+  private EntityInterface getCached(String key) {
+    EntityInterface result;
+    synchronized (cache) {
+      result = cache.get(key);
+    }
+    return result;
+  }
+
+  private void putCache(String key, EntityInterface value) {
+    synchronized (cache) {
+      if (value instanceof DeleteEntity) {
+        throw new RuntimeException("Delete entity in cache. This is an implementation error.");
+      }
+      id_in_cache.add(value.getId());
+      cache.put(key, value);
+    }
+  }
+
+  protected void removeCached(EntityInterface entity) {
+    synchronized (cache) {
+      if (id_in_cache.contains(entity.getId())) {
+        id_in_cache.remove(entity.getId());
+
+        List<String> remove = new LinkedList<>();
+        for (Entry<String, EntityInterface> entry : cache.entrySet()) {
+          if (entry.getValue().getId().equals(entity.getId())) {
+            remove.add(entry.getKey());
+          }
+        }
+        for (String key : remove) {
+          cache.remove(key);
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java
index b6583f024071e3c1151cf974b387ffe72d1579b7..7f5444b36104526101ebf9b3f2aa547b3d91cbc9 100644
--- a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java
+++ b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java
@@ -22,6 +22,7 @@
  */
 package org.caosdb.server.jobs.core;
 
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
@@ -51,5 +52,8 @@ public class ExecuteQuery extends FlagJob {
       getContainer().addMessage(new Message(e.getMessage()));
     }
     getContainer().addMessage(queryInstance);
+    for (EntityInterface entity : getContainer()) {
+      getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction()));
+    }
   }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
new file mode 100644
index 0000000000000000000000000000000000000000..bda4b8ebe885aa49df13ecf36ab14663c7ead270
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java
@@ -0,0 +1,36 @@
+package org.caosdb.server.jobs.core;
+
+import java.util.List;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.wrapper.Parent;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
+
+@JobAnnotation(time = JobExecutionTime.CHECK)
+public class InheritInitialState extends EntityStateJob {
+
+  @Override
+  protected void run() {
+    try {
+      State parentState = getFirstParentState();
+      if (parentState != null) {
+        getEntity().addMessage(parentState);
+        parentState.getStateEntity();
+        getEntity().setEntityACL(parentState.getStateACL());
+      }
+    } catch (Message e) {
+      getEntity().addError(e);
+    }
+  }
+
+  private State getFirstParentState() throws Message {
+    for (Parent par : getEntity().getParents()) {
+      List<Property> stateProperties = getStateProperties(par, false);
+      if (stateProperties.size() > 0) {
+        return createState(stateProperties.get(0));
+      }
+    }
+    return null;
+  }
+}
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 d49bec09c909c5e884eae80619a5911a50b1f333..107c5768a5c15fbb05ea6d68b0cb956cbb235687 100644
--- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
+++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java
@@ -34,6 +34,7 @@ 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.utils.EntityStatus;
 
 /**
@@ -65,7 +66,7 @@ public class Inheritance extends EntityJob {
   protected void run() {
     if (getEntity() instanceof InsertEntity || getEntity() instanceof UpdateEntity) {
       if (getEntity().hasParents()) {
-        final ArrayList<EntityInterface> transfer = new ArrayList<EntityInterface>();
+        final ArrayList<Property> transfer = new ArrayList<>();
         parentLoop:
         for (final EntityInterface parent : getEntity().getParents()) {
           try {
@@ -100,23 +101,25 @@ public class Inheritance extends EntityJob {
 
         // transfer properties if they are not implemented yet
         outerLoop:
-        for (final EntityInterface prop : transfer) {
-          for (final EntityInterface eprop : getEntity().getProperties()) {
+        for (final Property prop : transfer) {
+          for (final Property eprop : getEntity().getProperties()) {
             if (prop.hasId() && eprop.hasId() && prop.getId().equals(eprop.getId())) {
               continue outerLoop;
             }
           }
           // prop's Datatype might need to be resolved.
-          this.appendJob(prop, CheckDatatypePresent.class);
-          getEntity().addProperty(new Property(prop));
+          ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
+          getTransaction().getSchedule().runJob(job);
+
+          getEntity().addProperty(new Property(prop.getWrapped()));
         }
       }
 
       // implement properties
       if (getEntity().hasProperties()) {
         propertyLoop:
-        for (final EntityInterface property : getEntity().getProperties()) {
-          final ArrayList<EntityInterface> transfer = new ArrayList<EntityInterface>();
+        for (final Property property : getEntity().getProperties()) {
+          final ArrayList<Property> transfer = new ArrayList<>();
           try {
             if (property.getFlags().get("inheritance") == null) {
               break propertyLoop;
@@ -156,15 +159,17 @@ public class Inheritance extends EntityJob {
 
           // transfer properties if they are not implemented yet
           outerLoop:
-          for (final EntityInterface prop : transfer) {
-            for (final EntityInterface eprop : property.getProperties()) {
+          for (final Property prop : transfer) {
+            for (final Property eprop : property.getProperties()) {
               if (prop.hasId() && eprop.hasId() && prop.getId() == eprop.getId()) {
                 continue outerLoop;
               }
             }
             // prop's Datatype might need to be resolved.
-            this.appendJob(prop, CheckDatatypePresent.class);
-            property.addProperty(new Property(prop));
+            ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class);
+            getTransaction().getSchedule().runJob(job);
+
+            property.addProperty(new Property(prop.getWrapped()));
           }
         }
       }
@@ -182,9 +187,9 @@ public class Inheritance extends EntityJob {
    * @param inheritance
    */
   private void collectInheritedProperties(
-      List<EntityInterface> transfer, EntityInterface from, INHERITANCE_MODE inheritance) {
+      List<Property> transfer, EntityInterface from, INHERITANCE_MODE inheritance) {
     if (from.hasProperties()) {
-      for (final EntityInterface propProperty : from.getProperties()) {
+      for (final Property propProperty : from.getProperties()) {
         switch (inheritance) {
             // the following cases are ordered according to their importance level and use a
             // fall-through.
diff --git a/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java
new file mode 100644
index 0000000000000000000000000000000000000000..7dbff695a342e5dcd2a19a2d72116c14fba78a6c
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/InitEntityStateJobs.java
@@ -0,0 +1,200 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.jobs.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.entity.ClientMessage;
+import org.caosdb.server.entity.DeleteEntity;
+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.Role;
+import org.caosdb.server.entity.UpdateEntity;
+import org.caosdb.server.entity.WritableEntity;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.transaction.WriteTransaction;
+import org.caosdb.server.utils.EntityStatus;
+import org.caosdb.server.utils.Observable;
+import org.caosdb.server.utils.Observer;
+
+/**
+ * Initialize the other entity jobs by converting the client message with type "State" or
+ * StateProperties into {@link State} instances.
+ *
+ * <p>This job also needs to initialize the other jobs even if the current entity version does not
+ * have a state anymore but the previous version had, because it has to be checked if the stateModel
+ * allows to leave in this state.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+@JobAnnotation(
+    loadAlways = true,
+    time = JobExecutionTime.INIT,
+    transaction = WriteTransaction.class)
+public class InitEntityStateJobs extends EntityStateJob implements Observer {
+
+  @Override
+  protected void run() {
+    if ("ENABLED".equals(CaosDBServer.getServerProperty(SERVER_PROPERTY_EXT_ENTITY_STATE))) {
+      State newState = handleNewState();
+      State oldState = handleOldState(newState);
+      if (newState != null || oldState != null) {
+        if (!(getEntity() instanceof DeleteEntity)) {
+
+          appendJob(MakeStateProperty.class);
+        }
+        appendJob(CheckStateTransition.class);
+        appendJob(MakeStateMessage.class);
+      } else if (newState == null
+          && getEntity().getRole() == Role.Record
+          && getEntity() instanceof InsertEntity) {
+        appendJob(InheritInitialState.class);
+        appendJob(CheckStateTransition.class);
+        appendJob(MakeStateProperty.class);
+        appendJob(MakeStateMessage.class);
+      }
+      if (getEntity() instanceof WritableEntity || getEntity() instanceof DeleteEntity) {
+        removeCached(getEntity());
+      }
+    }
+  }
+
+  /**
+   * Converts the state property of the original entity into a state message (only needed for
+   * updates).
+   *
+   * <p>Also, this method adds an observer to the entity state which handles a corner case where the
+   * entity changes the state, but no other property changes. In this case Update.deriveUpdate
+   * cannot detect any changes and will mark this entity as "to-be-skipped". The observer waits for
+   * that to happen and changes the {@EntityStatus} back to normal.
+   *
+   * @param newState
+   * @return The old state or null.
+   */
+  private State handleOldState(State newState) {
+    State oldState = null;
+    try {
+      if (getEntity() instanceof UpdateEntity) {
+        List<State> states = initStateMessage(((UpdateEntity) getEntity()).getOriginal());
+        oldState = null;
+        if (states.size() == 1) {
+          oldState = states.get(0);
+          if (newState != null) {
+            ((UpdateEntity) getEntity()).getOriginal().setEntityACL(getEntity().getEntityACL());
+          }
+        }
+        if (!Objects.equals(newState, oldState)) {
+          getEntity().acceptObserver(this);
+        }
+      }
+    } catch (Message m) {
+      getEntity().addWarning(STATE_ERROR_IN_ORIGINAL_ENTITY(m));
+    }
+    return oldState;
+  }
+
+  /**
+   * Converts the state property of this entity into a state message.
+   *
+   * @return The new state or null.
+   */
+  private State handleNewState() {
+    State newState = null;
+    try {
+      List<State> states = initStateMessage(getEntity());
+      if (states.size() > 1) {
+        throw new Message(
+            MessageType.Error, "Currently, each entity can only have one state at a time.");
+      } else if (states.size() == 1) {
+        newState = states.get(0);
+        if (getEntity().getRole() == Role.Record) {
+          transferEntityACL(getEntity(), newState);
+        }
+      }
+    } catch (Message m) {
+      getEntity().addError(m);
+    }
+
+    return newState;
+  }
+
+  private void transferEntityACL(EntityInterface entity, State newState) throws Message {
+    newState.getStateEntity();
+    entity.setEntityACL(newState.getStateACL());
+  }
+
+  private static final Message STATE_ERROR_IN_ORIGINAL_ENTITY(Message m) {
+    return new Message(
+        MessageType.Warning, "State error in previous entity version\n" + m.getDescription());
+  }
+
+  /**
+   * Return a list of states from their representations as properties or client messages in the
+   * entity.
+   *
+   * @param entity
+   * @return list of state instances for the entity.
+   * @throws Message
+   */
+  private List<State> initStateMessage(EntityInterface entity) throws Message {
+    List<ClientMessage> stateClientMessages = getStateClientMessages(entity, true);
+    List<State> result = new ArrayList<>();
+    if (stateClientMessages != null) {
+      for (ClientMessage s : stateClientMessages) {
+        State stateMessage = createState(s);
+        entity.addMessage(stateMessage);
+        result.add(stateMessage);
+      }
+    }
+    List<Property> stateProperties = getStateProperties(entity, true);
+    if (stateProperties != null) {
+      for (Property p : stateProperties) {
+        State stateMessage = createState(p);
+        entity.addMessage(stateMessage);
+        result.add(stateMessage);
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public boolean notifyObserver(String e, Observable o) {
+    if (e == Entity.ENTITY_STATUS_CHANGED_EVENT) {
+      if (o == getEntity() && getEntity().getEntityStatus() == EntityStatus.VALID) {
+        // The Update.deriveUpdate method didn't recognize that the state is changing and set the
+        // entity to "VALID"
+        getEntity().setEntityStatus(EntityStatus.QUALIFIED);
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e98704a5f6d73199993f2ed531e6d6277934e64
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateMessage.java
@@ -0,0 +1,108 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.jobs.core;
+
+import java.util.List;
+import java.util.Map;
+import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.wrapper.Property;
+import org.caosdb.server.entity.xml.ToElementable;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.transaction.Retrieve;
+import org.jdom2.Element;
+
+/**
+ * Remove the state property from the entity and, iff necessary, convert it into a State instance
+ * which is then being appended to the entity's messages.
+ *
+ * <p>If this job belongs to a Write transaction there is already a State instance present and the
+ * conversion is not necessary.
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+@JobAnnotation(
+    loadAlways = true,
+    transaction = Retrieve.class,
+    time = JobExecutionTime.POST_TRANSACTION)
+public class MakeStateMessage extends EntityStateJob {
+
+  public static final String SPARSE_FLAG = "sparseState";
+
+  @Override
+  protected void run() {
+
+    if ("ENABLED".equals(CaosDBServer.getServerProperty(SERVER_PROPERTY_EXT_ENTITY_STATE))) {
+      try {
+        // fetch all state properties and remove them from the entity (indicated by "true")
+        List<Property> stateProperties = getStateProperties(true);
+        State stateMessage = getState(false);
+
+        if (stateMessage != null) {
+          // trigger retrieval of state model because when the XML Writer calls the addToElement
+          // method, it is to late.
+          stateMessage.getStateModel();
+        } else if (stateProperties != null && stateProperties.size() > 0) {
+          for (Property s : stateProperties) {
+            getEntity().addMessage(getMessage(s, isSparse()));
+          }
+        }
+      } catch (Message e) {
+        getEntity().addError(e);
+      }
+    }
+  }
+
+  private ToElementable getMessage(Property s, boolean sparse) throws Message {
+    if (sparse) {
+      return getSparseStateMessage(s);
+    }
+    State stateMessage = createState(s);
+
+    // trigger retrieval of state model because when the XML Writer calls the addToElement method,
+    // it is to late.
+    stateMessage.getStateModel();
+    return stateMessage;
+  }
+
+  private ToElementable getSparseStateMessage(Property s) {
+    return new ToElementable() {
+      @Override
+      public void addToElement(Element ret) {
+        Element state = new Element(STATE_XML_TAG);
+        state.setAttribute(STATE_ATTRIBUTE_ID, s.getValue().toString());
+        ret.addContent(state);
+      }
+    };
+  }
+
+  private boolean isSparse() {
+    Map<String, String> flags = getTransaction().getContainer().getFlags();
+    if (flags != null) {
+      return "true".equals(flags.getOrDefault(SPARSE_FLAG, "false"));
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java b/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java
new file mode 100644
index 0000000000000000000000000000000000000000..56682921563d77f462903567a2c128fcd5d92555
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/MakeStateProperty.java
@@ -0,0 +1,53 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+package org.caosdb.server.jobs.core;
+
+import org.caosdb.server.entity.Message;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
+import org.caosdb.server.transaction.WriteTransaction;
+
+/**
+ * This job constructs an ordinary Property from the State right before the entity is being written
+ * to the back-end and after any checks run.
+ */
+@JobAnnotation(transaction = WriteTransaction.class, time = JobExecutionTime.PRE_TRANSACTION)
+public class MakeStateProperty extends EntityStateJob {
+
+  @Override
+  protected void run() {
+    State s = getState();
+    if (s != null) {
+      try {
+        addStateProperty(s);
+      } catch (Message e) {
+        getEntity().addError(e);
+      }
+    }
+  }
+
+  private void addStateProperty(State stateEntity) throws Message {
+    getEntity().addProperty(stateEntity.createStateProperty());
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/PickUp.java b/src/main/java/org/caosdb/server/jobs/core/PickUp.java
index dff574a4ad0aa5199fe3ccdfdd9c72d2a7b2bb67..b91fccf72d54022c6f6d19ba26c2b0974a54829f 100644
--- a/src/main/java/org/caosdb/server/jobs/core/PickUp.java
+++ b/src/main/java/org/caosdb/server/jobs/core/PickUp.java
@@ -33,9 +33,10 @@ import org.caosdb.server.jobs.JobAnnotation;
 import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.Observable;
+import org.caosdb.server.utils.Observer;
 
 @JobAnnotation(time = JobExecutionTime.INIT)
-public class PickUp extends EntityJob {
+public class PickUp extends EntityJob implements Observer {
 
   @Override
   protected void run() {
diff --git a/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java b/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java
index 950c61d36255b68f52e3066616b1dee5d9f259c3..e7e8f0e4d6aab3f3d0b3e9392d408a88aaa0abcc 100644
--- a/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java
+++ b/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java
@@ -34,14 +34,17 @@ import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.wrapper.Parent;
 import org.caosdb.server.entity.wrapper.Property;
 import org.caosdb.server.jobs.EntityJob;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.utils.EntityStatus;
 import org.caosdb.server.utils.ServerMessages;
 
 /**
- * To be called after CheckPropValid.
+ * To be called after CheckPropValid and Inheritance.
  *
  * @author tf
  */
+@JobAnnotation(time = JobExecutionTime.PRE_TRANSACTION)
 public class ProcessNameProperties extends EntityJob {
 
   @Override
diff --git a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java
index 1ca43cec45413d80c307df2f4d09c6b8d34cfb49..603d861ad24b659fab262291d888420c02f6bcf5 100644
--- a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java
+++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java
@@ -23,6 +23,7 @@
 package org.caosdb.server.jobs.core;
 
 import org.caosdb.server.database.backend.transaction.RetrieveAll;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
 import org.caosdb.server.jobs.JobExecutionTime;
@@ -37,6 +38,9 @@ public class RetrieveAllJob extends FlagJob {
         value = "ENTITY";
       }
       execute(new RetrieveAll(getContainer(), value));
+      for (EntityInterface entity : getContainer()) {
+        getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction()));
+      }
     }
   }
 }
diff --git a/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java b/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java
index 3a3d03d18bc7b0913a522e2edd6c235f9a7d39a7..c8cd93e4a1d12125235819b4a2ba69fa12bd0256 100644
--- a/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java
+++ b/src/main/java/org/caosdb/server/permissions/AbstractEntityACLFactory.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -168,32 +169,75 @@ public abstract class AbstractEntityACLFactory<T extends EntityACL> {
     return create(acis);
   }
 
+  /**
+   * Normalize the permission rules.
+   *
+   * <p>This means that rules which are overriden by other rules are removed. E.g. a granting rule
+   * for the permission X and the agent P would be removed if there is a denial of X (for P) with
+   * the same or a higher priority. Likewise, A denial of Y for agent Q would be removed if there is
+   * a granting rule of Y (for Q) with a higher priority.
+   */
   private void normalize() {
-    for (final Entry<ResponsibleAgent, Long> set : this.priorityDenials.entrySet()) {
-      if (this.priorityGrants.containsKey(set.getKey())) {
-        this.priorityGrants.put(
-            set.getKey(), this.priorityGrants.get(set.getKey()) & ~set.getValue());
+    // 1. run through all prioritized denials and remove overriden rules
+    // (priority grants, normal grants and normal denials)
+    Iterator<Entry<ResponsibleAgent, Long>> iterator = this.priorityDenials.entrySet().iterator();
+    while (iterator.hasNext()) {
+      Entry<ResponsibleAgent, Long> next = iterator.next();
+      final ResponsibleAgent agent = next.getKey();
+      long bitset = next.getValue();
+      if (bitset == 0L) {
+        iterator.remove();
+        continue;
       }
-      if (this.normalDenials.containsKey(set.getKey())) {
-        this.normalDenials.put(
-            set.getKey(), this.normalDenials.get(set.getKey()) & ~set.getValue());
+      if (this.priorityGrants.containsKey(agent)) {
+        this.priorityGrants.put(agent, this.priorityGrants.get(agent) & ~bitset);
       }
-      if (this.normalGrants.containsKey(set.getKey())) {
-        this.normalGrants.put(set.getKey(), this.normalGrants.get(set.getKey()) & ~set.getValue());
+      if (this.normalDenials.containsKey(agent)) {
+        this.normalDenials.put(agent, this.normalDenials.get(agent) & ~bitset);
+      }
+      if (this.normalGrants.containsKey(agent)) {
+        this.normalGrants.put(agent, this.normalGrants.get(agent) & ~bitset);
+      }
+    }
+    // 2. run through all prioritized grants and remove overriden rules (normal
+    // denials and grants)
+    iterator = this.priorityGrants.entrySet().iterator();
+    while (iterator.hasNext()) {
+      Entry<ResponsibleAgent, Long> next = iterator.next();
+      final ResponsibleAgent agent = next.getKey();
+      long bitset = next.getValue();
+      if (bitset == 0L) {
+        iterator.remove();
+        continue;
+      }
+      if (this.normalDenials.containsKey(agent)) {
+        this.normalDenials.put(agent, this.normalDenials.get(agent) & ~bitset);
+      }
+      if (this.normalGrants.containsKey(agent)) {
+        this.normalGrants.put(agent, this.normalGrants.get(agent) & ~bitset);
       }
     }
-    for (final Entry<ResponsibleAgent, Long> set : this.priorityGrants.entrySet()) {
-      if (this.normalDenials.containsKey(set.getKey())) {
-        this.normalDenials.put(
-            set.getKey(), this.normalDenials.get(set.getKey()) & ~set.getValue());
+    // 3. run through all normal denials and remove overriden rules (normal grants)
+    iterator = this.normalDenials.entrySet().iterator();
+    while (iterator.hasNext()) {
+      Entry<ResponsibleAgent, Long> next = iterator.next();
+      final ResponsibleAgent agent = next.getKey();
+      long bitset = next.getValue();
+      if (bitset == 0L) {
+        iterator.remove();
+        continue;
       }
-      if (this.normalGrants.containsKey(set.getKey())) {
-        this.normalGrants.put(set.getKey(), this.normalGrants.get(set.getKey()) & ~set.getValue());
+      if (this.normalGrants.containsKey(agent)) {
+        this.normalGrants.put(agent, this.normalGrants.get(agent) & ~bitset);
       }
     }
-    for (final Entry<ResponsibleAgent, Long> set : this.normalDenials.entrySet()) {
-      if (this.normalGrants.containsKey(set.getKey())) {
-        this.normalGrants.put(set.getKey(), this.normalGrants.get(set.getKey()) & ~set.getValue());
+    // finally, remove all remaining empty grants
+    iterator = this.normalGrants.entrySet().iterator();
+    while (iterator.hasNext()) {
+      Entry<ResponsibleAgent, Long> next = iterator.next();
+      long bitset = next.getValue();
+      if (bitset == 0L) {
+        iterator.remove();
       }
     }
   }
@@ -206,4 +250,63 @@ public abstract class AbstractEntityACLFactory<T extends EntityACL> {
   }
 
   protected abstract T create(Collection<EntityACI> acis);
+
+  /**
+   * Remove all rules of the `other` EntityACL from this factory.
+   *
+   * <p>This is mainly used for removing all rules which belong to the global entity ACL from this
+   * ACL before storing it to the backend.
+   *
+   * @param other
+   * @return the same object with changed rule set.
+   */
+  public AbstractEntityACLFactory<T> remove(EntityACL other) {
+    if (other != null) {
+      normalize();
+      for (EntityACI aci : other.getRules()) {
+        if (EntityACL.isAllowance(aci.getBitSet())) {
+          if (EntityACL.isPriorityBitSet(aci.getBitSet())) {
+            Long bitset = this.priorityGrants.get(aci.getResponsibleAgent());
+            if (bitset == null) {
+              continue;
+            }
+            long bitset2 = bitset;
+            bitset2 &= aci.getBitSet();
+            bitset ^= bitset2;
+            this.priorityGrants.put(aci.getResponsibleAgent(), bitset);
+          } else {
+            Long bitset = this.normalGrants.get(aci.getResponsibleAgent());
+            if (bitset == null) {
+              continue;
+            }
+            long bitset2 = bitset;
+            bitset2 &= aci.getBitSet();
+            bitset ^= bitset2;
+            this.normalGrants.put(aci.getResponsibleAgent(), bitset);
+          }
+        } else {
+          if (EntityACL.isPriorityBitSet(aci.getBitSet())) {
+            Long bitset = this.priorityDenials.get(aci.getResponsibleAgent());
+            if (bitset == null) {
+              continue;
+            }
+            long bitset2 = bitset;
+            bitset2 &= aci.getBitSet();
+            bitset ^= bitset2;
+            this.priorityDenials.put(aci.getResponsibleAgent(), bitset);
+          } else {
+            Long bitset = this.normalDenials.get(aci.getResponsibleAgent());
+            if (bitset == null) {
+              continue;
+            }
+            long bitset2 = bitset;
+            bitset2 &= aci.getBitSet();
+            bitset ^= bitset2;
+            this.normalDenials.put(aci.getResponsibleAgent(), bitset);
+          }
+        }
+      }
+    }
+    return this;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/permissions/CaosPermission.java b/src/main/java/org/caosdb/server/permissions/CaosPermission.java
index 1b793bb27f6a14bcf7fd0c5f828c20b73f3d11e5..bfbb7eb2e8515f71bb2d611d4fd75916cfae50e7 100644
--- a/src/main/java/org/caosdb/server/permissions/CaosPermission.java
+++ b/src/main/java/org/caosdb/server/permissions/CaosPermission.java
@@ -24,7 +24,9 @@ package org.caosdb.server.permissions;
 
 import java.util.HashSet;
 import java.util.Map;
+import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authz.Permission;
+import org.apache.shiro.subject.Subject;
 import org.eclipse.jetty.util.ajax.JSON;
 
 public class CaosPermission extends HashSet<PermissionRule> implements Permission {
@@ -52,9 +54,10 @@ public class CaosPermission extends HashSet<PermissionRule> implements Permissio
     boolean grant = false;
     boolean deny = false;
     boolean grant_priority = false;
+    Subject subject = SecurityUtils.getSubject();
 
     for (final PermissionRule r : this) {
-      if (r.getPermission().implies(p)) {
+      if (r.getPermission(subject).implies(p)) {
         if (r.isGrant()) {
           if (r.isPriority()) {
             grant_priority = true;
diff --git a/src/main/java/org/caosdb/server/permissions/EntityACL.java b/src/main/java/org/caosdb/server/permissions/EntityACL.java
index df1915bd460079eb52dfc6d4f3bbf4fe42795918..cfa436d59ae25971a08d4314a7f668a70cf75bbf 100644
--- a/src/main/java/org/caosdb/server/permissions/EntityACL.java
+++ b/src/main/java/org/caosdb/server/permissions/EntityACL.java
@@ -253,7 +253,9 @@ public class EntityACL {
   public final Element toElement() {
     final Element ret = new Element("EntityACL");
 
-    for (final EntityACI aci : this.acl) {
+    final List<EntityACI> localAcl = new ArrayList<>(this.acl);
+    localAcl.addAll(GLOBAL_PERMISSIONS.acl);
+    for (final EntityACI aci : localAcl) {
       final boolean isDenial = isDenial(aci.getBitSet());
       final boolean isPriority = isPriorityBitSet(aci.getBitSet());
       final Element e = new Element(isDenial ? "Deny" : "Grant");
@@ -323,7 +325,7 @@ public class EntityACL {
         }
       }
     }
-    return factory.create();
+    return factory.remove(GLOBAL_PERMISSIONS).create();
   }
 
   public static BitSet convert(final long value) {
@@ -379,7 +381,9 @@ public class EntityACL {
 
   public Element getPermissionsFor(final Subject subject) {
     final Element ret = new Element("Permissions");
-    final Set<EntityPermission> permissionsFor = getPermissionsFor(subject, this.acl);
+    final List<EntityACI> localAcl = new ArrayList<>(this.acl);
+    localAcl.addAll(GLOBAL_PERMISSIONS.acl);
+    final Set<EntityPermission> permissionsFor = getPermissionsFor(subject, localAcl);
     for (final EntityPermission p : permissionsFor) {
       ret.addContent(p.toElement());
     }
diff --git a/src/main/java/org/caosdb/server/permissions/PermissionRule.java b/src/main/java/org/caosdb/server/permissions/PermissionRule.java
index b6ee915156771e1164f3a0ee221d3e14ab964833..85d3b62834a67a4fc46ea9b3c38d19e4b8261d74 100644
--- a/src/main/java/org/caosdb/server/permissions/PermissionRule.java
+++ b/src/main/java/org/caosdb/server/permissions/PermissionRule.java
@@ -26,23 +26,21 @@ import java.util.HashMap;
 import java.util.Map;
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.authz.permission.WildcardPermission;
+import org.apache.shiro.subject.Subject;
+import org.caosdb.server.accessControl.Principal;
 import org.jdom2.Element;
 
 public class PermissionRule {
 
-  private final WildcardPermission permission;
+  private final String permission;
   private final boolean priority;
   private final boolean grant;
 
   public PermissionRule(final String grant, final String priority, final String permission) {
-    this(
-        Boolean.parseBoolean(grant),
-        Boolean.parseBoolean(priority),
-        new WildcardPermission(permission));
+    this(Boolean.parseBoolean(grant), Boolean.parseBoolean(priority), permission);
   }
 
-  public PermissionRule(
-      final boolean grant, final boolean priority, final WildcardPermission permission) {
+  public PermissionRule(final boolean grant, final boolean priority, final String permission) {
     this.grant = grant;
     this.priority = priority;
     this.permission = permission;
@@ -56,8 +54,9 @@ public class PermissionRule {
     return this.priority;
   }
 
-  public Permission getPermission() {
-    return this.permission;
+  public Permission getPermission(String realm, String username) {
+    return new WildcardPermission(
+        permission.replaceAll("\\?REALM\\?", realm).replaceAll("\\?USERNAME\\?", username));
   }
 
   public static PermissionRule parse(final Map<String, String> rule) {
@@ -69,7 +68,7 @@ public class PermissionRule {
     if (isPriority()) {
       ret.setAttribute("priority", Boolean.toString(true));
     }
-    ret.setAttribute("permission", getPermission().toString());
+    ret.setAttribute("permission", permission);
     return ret;
   }
 
@@ -77,14 +76,19 @@ public class PermissionRule {
     return new PermissionRule(
         e.getName().equalsIgnoreCase("Grant"),
         e.getAttribute("priority") != null && Boolean.parseBoolean(e.getAttributeValue("priority")),
-        new WildcardPermission(e.getAttributeValue("permission")));
+        e.getAttributeValue("permission"));
   }
 
   public Map<String, String> getMap() {
     final HashMap<String, String> ret = new HashMap<String, String>();
     ret.put("priority", Boolean.toString(isPriority()));
     ret.put("grant", Boolean.toString(isGrant()));
-    ret.put("permission", getPermission().toString());
+    ret.put("permission", permission);
     return ret;
   }
+
+  public Permission getPermission(Subject subject) {
+    Principal principal = (Principal) subject.getPrincipal();
+    return getPermission(principal.getRealm(), principal.getUsername());
+  }
 }
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/Query.java b/src/main/java/org/caosdb/server/query/Query.java
index 702c6fa22a0528ec0a99b1f463e18151501d0a36..058fc619e021b96e1ac3d83750d7ed84c5acc2c0 100644
--- a/src/main/java/org/caosdb/server/query/Query.java
+++ b/src/main/java/org/caosdb/server/query/Query.java
@@ -39,6 +39,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.UUID;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
@@ -181,9 +182,31 @@ 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 =
+  private boolean filterEntitiesWithoutRetrievePermisions =
       !CaosDBServer.getServerProperty(
               ServerProperties.KEY_QUERY_FILTER_ENTITIES_WITHOUT_RETRIEVE_PERMISSIONS)
           .equalsIgnoreCase("FALSE");
@@ -216,6 +239,14 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    */
   private boolean cachable = true;
 
+  /**
+   * Tags the query cache and is renewed each time the cache is being cleared, i.e. each time the
+   * database is being updated.
+   *
+   * <p>As the name suggests, the idea is similar to the ETag header of the HTTP protocol.
+   */
+  private static String cacheETag = UUID.randomUUID().toString();
+
   public Type getType() {
     return this.type;
   }
@@ -445,7 +476,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));
@@ -471,29 +508,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<>();
@@ -559,13 +663,15 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     if (this.container != null && this.type == Type.FIND) {
       for (final IdVersionPair p : this.resultSet) {
 
-        final Entity e = new RetrieveEntity(p.id, p.version);
+        if (p.id > 99) {
+          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);
+          // if query has select-clause:
+          if (this.selections != null && !this.selections.isEmpty()) {
+            e.addSelections(this.selections);
+          }
+          this.container.add(e);
         }
-        this.container.add(e);
       }
     }
     return this;
@@ -573,6 +679,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
   /** Remove all cached queries from the cache. */
   public static void clearCache() {
+    cacheETag = UUID.randomUUID().toString();
     cache.clear();
   }
 
@@ -583,10 +690,12 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
    * @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));
+    synchronized (cache) {
+      if (resultSet instanceof Serializable) {
+        cache.put(key, (Serializable) resultSet);
+      } else {
+        cache.put(key, new ArrayList<>(resultSet));
+      }
     }
   }
 
@@ -764,6 +873,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
       ret.setAttribute("results", "0");
     }
     ret.setAttribute("cached", Boolean.toString(this.cached));
+    ret.setAttribute("etag", cacheETag);
 
     final Element parseTreeElem = new Element("ParseTree");
     if (this.el.hasErrors()) {
@@ -869,4 +979,24 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     if (this.filter != null) sb.append(this.filter.getCacheKey());
     return sb.toString();
   }
+
+  public Pattern getEntity() {
+    return this.entity;
+  }
+
+  public Role getRole() {
+    return this.role;
+  }
+
+  /**
+   * Return the ETag.
+   *
+   * <p>The ETag tags the query cache and is renewed each time the cache is being cleared, i.e. each
+   * time the database is being updated.
+   *
+   * @return The ETag
+   */
+  public static String getETag() {
+    return cacheETag;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/resource/JdomRepresentation.java b/src/main/java/org/caosdb/server/resource/JdomRepresentation.java
index 1af634b0ae784ccaf4aff4aac399771cb4774f60..4be6743d9ff959c278f80322c92fe2ee9f42d7cd 100644
--- a/src/main/java/org/caosdb/server/resource/JdomRepresentation.java
+++ b/src/main/java/org/caosdb/server/resource/JdomRepresentation.java
@@ -28,6 +28,7 @@ import java.io.Writer;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.ServerProperties;
 import org.jdom2.Document;
+import org.jdom2.Element;
 import org.jdom2.ProcessingInstruction;
 import org.jdom2.output.Format;
 import org.jdom2.output.XMLOutputter;
@@ -68,6 +69,12 @@ public class JdomRepresentation extends WriterRepresentation {
     super(MediaType.TEXT_XML);
     this.indent = indent;
     this.document = document;
+    Element noscript = new Element("noscript");
+    Element div = new Element("h1");
+
+    div.addContent("Please enable JavaScript!");
+    noscript.addContent(div);
+    document.getRootElement().addContent(0, noscript);
     if (xslPath != null && document != null) {
       addStyleSheet(document, xslPath);
     }
diff --git a/src/main/java/org/caosdb/server/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java
index fd7bae8a82e95554d527135f76124d397c23f0c4..19f840e92a1a6812e2ae2610ed3ace23b7e3cee7 100644
--- a/src/main/java/org/caosdb/server/transaction/Retrieve.java
+++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java
@@ -30,6 +30,7 @@ import org.caosdb.server.entity.container.RetrieveContainer;
 import org.caosdb.server.entity.xml.SetFieldStrategy;
 import org.caosdb.server.entity.xml.ToElementStrategy;
 import org.caosdb.server.entity.xml.ToElementable;
+import org.caosdb.server.jobs.ScheduledJob;
 import org.caosdb.server.jobs.core.Mode;
 import org.caosdb.server.jobs.core.RemoveDuplicates;
 import org.caosdb.server.jobs.core.ResolveNames;
@@ -50,15 +51,19 @@ public class Retrieve extends Transaction<RetrieveContainer> {
     setAccess(getAccessManager().acquireReadAccess(this));
 
     // resolve names
-    final ResolveNames r = new ResolveNames();
-    r.init(Mode.SHOULD, null, this);
-    getSchedule().add(r);
-    getSchedule().runJob(r);
-
-    final RemoveDuplicates job = new RemoveDuplicates();
-    job.init(Mode.MUST, null, this);
-    getSchedule().add(job);
-    getSchedule().runJob(job);
+    {
+      final ResolveNames r = new ResolveNames();
+      r.init(Mode.SHOULD, null, this);
+      ScheduledJob scheduledJob = getSchedule().add(r);
+      getSchedule().runJob(scheduledJob);
+    }
+
+    {
+      final RemoveDuplicates job = new RemoveDuplicates();
+      job.init(Mode.MUST, null, this);
+      ScheduledJob scheduledJob = getSchedule().add(job);
+      getSchedule().runJob(scheduledJob);
+    }
 
     // make schedule for all parsed entities
     makeSchedule();
diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java
index c2bd7e0feb62fa31dadd5580990350b829ff4591..97ed4954b93c55830d1bed5f74bad6e157e70776 100644
--- a/src/main/java/org/caosdb/server/transaction/Transaction.java
+++ b/src/main/java/org/caosdb/server/transaction/Transaction.java
@@ -41,6 +41,7 @@ import org.caosdb.server.entity.container.TransactionContainer;
 import org.caosdb.server.jobs.Job;
 import org.caosdb.server.jobs.JobExecutionTime;
 import org.caosdb.server.jobs.Schedule;
+import org.caosdb.server.jobs.ScheduledJob;
 import org.caosdb.server.jobs.core.AccessControl;
 import org.caosdb.server.jobs.core.CheckDatatypePresent;
 import org.caosdb.server.jobs.core.CheckEntityACLRoles;
@@ -88,8 +89,8 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
   protected void makeSchedule() throws Exception {
     // load flag jobs
     final Job loadContainerFlags = Job.getJob("LoadContainerFlagJobs", Mode.MUST, null, this);
-    this.schedule.add(loadContainerFlags);
-    this.schedule.runJob(loadContainerFlags);
+    ScheduledJob scheduledJob = this.schedule.add(loadContainerFlags);
+    this.schedule.runJob(scheduledJob);
 
     // AccessControl
     this.schedule.add(Job.getJob(AccessControl.class.getSimpleName(), Mode.MUST, null, this));
@@ -104,17 +105,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
 
       // additionally load datatype job
       if (e.hasValue()) {
-        boolean found = false;
-        for (final Job j : loadJobs) {
-
-          if (CheckDatatypePresent.class.isInstance(j)
-              && ((CheckDatatypePresent) j).getEntity() == e) {
-            found = true;
-          }
-        }
-        if (!found) {
-          this.schedule.add(new CheckDatatypePresent().init(Mode.MUST, e, this));
-        }
+        this.schedule.add(new CheckDatatypePresent().init(Mode.MUST, e, this));
       }
 
       // load pickup job if necessary
diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
index abda78b6feeb0de7104de5365877b28d474058fe..570fce2c2b001699fdb48c1379e807ae05d62996 100644
--- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java
@@ -215,13 +215,7 @@ public class WriteTransaction extends Transaction<WritableContainer>
                     .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());
-              }
+              ((UpdateEntity) entity).setOriginal(oldEntity);
             }
             break innerLoop;
           }
@@ -290,6 +284,18 @@ public class WriteTransaction extends Transaction<WritableContainer>
   @Override
   protected void preCheck() throws InterruptedException, Exception {
     for (final EntityInterface entity : getContainer()) {
+      try {
+        if (entity.getEntityStatus() == EntityStatus.QUALIFIED) {
+          checkPermissions(entity, deriveUpdate(entity, ((UpdateEntity) entity).getOriginal()));
+        }
+      } catch (final AuthorizationException exc) {
+        entity.setEntityStatus(EntityStatus.UNQUALIFIED);
+        entity.addError(ServerMessages.AUTHORIZATION_ERROR);
+        entity.addInfo(exc.getMessage());
+      } catch (ClassCastException exc) {
+        // not an update entity. ignore.
+      }
+
       // set default EntityACL if none present
       if (entity.getEntityACL() == null) {
         entity.setEntityACL(EntityACL.getOwnerACLFor(SecurityUtils.getSubject()));
diff --git a/src/main/java/org/caosdb/server/utils/AbstractObservable.java b/src/main/java/org/caosdb/server/utils/AbstractObservable.java
index a19fff9d44ce7339a9cdc6712e0806ef42bdfd40..abc7f0a0ae1cdcbeff613c2778637de97a507c57 100644
--- a/src/main/java/org/caosdb/server/utils/AbstractObservable.java
+++ b/src/main/java/org/caosdb/server/utils/AbstractObservable.java
@@ -37,6 +37,7 @@ public abstract class AbstractObservable implements Observable {
     return this.observers.add(o);
   }
 
+  /** @param e A String denoting the notification event. */
   @Override
   public void notifyObservers(final String e) {
     if (this.observers != null) {
diff --git a/src/main/java/org/caosdb/server/utils/Observer.java b/src/main/java/org/caosdb/server/utils/Observer.java
index be89dff4b882adf24bcfd250c3dea1387997360d..b4c8f5444b957a1d18efc7f864c67b3daff8ca33 100644
--- a/src/main/java/org/caosdb/server/utils/Observer.java
+++ b/src/main/java/org/caosdb/server/utils/Observer.java
@@ -25,6 +25,8 @@ package org.caosdb.server.utils;
 public interface Observer {
 
   /**
+   * Notify this observer that an event {@code e} has happened to the {@code sender}.
+   *
    * @param e
    * @param sender
    * @return true, iff the Observable has to keep it in the list of observers.
diff --git a/src/main/java/org/caosdb/server/utils/fsm/ActionNotAllowedException.java b/src/main/java/org/caosdb/server/utils/fsm/ActionNotAllowedException.java
deleted file mode 100644
index ad643a0aade5e8285fa0aea7a92026995b9ccd61..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/ActionNotAllowedException.java
+++ /dev/null
@@ -1,39 +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.utils.fsm;
-
-public class ActionNotAllowedException extends RuntimeException {
-
-  private final String action;
-
-  public ActionNotAllowedException(final String action) {
-    this.action = action;
-  }
-
-  private static final long serialVersionUID = -4324962066954446942L;
-
-  @Override
-  public String getMessage() {
-    return "The action `" + this.action + "` is not allowed in the current state.";
-  }
-}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/FiniteStateMachine.java b/src/main/java/org/caosdb/server/utils/fsm/FiniteStateMachine.java
deleted file mode 100644
index f8bdccac726bfe25d7644f6dc689b54113f981c7..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/FiniteStateMachine.java
+++ /dev/null
@@ -1,88 +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.utils.fsm;
-
-import java.util.List;
-import java.util.Map;
-
-public abstract class FiniteStateMachine<S extends State, T extends Transition> {
-
-  public FiniteStateMachine(final S initial, final Map<S, Map<T, S>> transitions)
-      throws StateNotReachableException {
-    this.currentState = initial;
-    this.transitions = transitions;
-    checkEveryStateReachable();
-  }
-
-  private void checkEveryStateReachable() throws StateNotReachableException {
-    for (final State s : getAllStates()) {
-      if (!s.equals(this.currentState) && !stateIsReachable(s)) {
-        throw new StateNotReachableException(s);
-      }
-    }
-  }
-
-  private boolean stateIsReachable(final State s) {
-    for (final Map<T, S> map : this.transitions.values()) {
-      if (map.containsValue(s)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private final Map<S, Map<T, S>> transitions;
-  private S currentState = null;
-
-  public void trigger(final T t) throws TransitionNotAllowedException {
-    final S old = this.currentState;
-    this.currentState = getNextState(t);
-    onAfterTransition(old, t, this.currentState);
-  }
-
-  S getNextState(final T t) throws TransitionNotAllowedException {
-    final Map<T, S> map = this.transitions.get(this.currentState);
-    if (map != null && map.containsKey(t)) {
-      return map.get(t);
-    }
-    throw new TransitionNotAllowedException(this.getCurrentState(), t);
-  }
-
-  public S getCurrentState() {
-    return this.currentState;
-  }
-
-  public List<? extends State> getAllStates() {
-    return this.currentState.getAllStates();
-  }
-
-  /**
-   * Override this method in subclasses. The method is called immediately after a transition
-   * finished.
-   *
-   * @param from
-   * @param transition
-   * @param to
-   */
-  protected void onAfterTransition(final S from, final T transition, final S to) {}
-}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/MissingImplementationException.java b/src/main/java/org/caosdb/server/utils/fsm/MissingImplementationException.java
deleted file mode 100644
index 8282e5dd7e7bea4368acf2c7e77b536fab301ad6..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/MissingImplementationException.java
+++ /dev/null
@@ -1,32 +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.utils.fsm;
-
-public class MissingImplementationException extends Exception {
-
-  public MissingImplementationException(final State s) {
-    super("The state `" + s.toString() + "` has no implementation.");
-  }
-
-  private static final long serialVersionUID = -1138551658177420875L;
-}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/State.java b/src/main/java/org/caosdb/server/utils/fsm/State.java
deleted file mode 100644
index 81b76aa599a90de2c8b3c8ddd295e62ce47dae12..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/State.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.utils.fsm;
-
-import java.util.List;
-
-public interface State {
-
-  public List<State> getAllStates();
-}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/StateNotReachableException.java b/src/main/java/org/caosdb/server/utils/fsm/StateNotReachableException.java
deleted file mode 100644
index 3970f60e5cf6e86096b0992419b14350c43b42ee..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/StateNotReachableException.java
+++ /dev/null
@@ -1,32 +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.utils.fsm;
-
-public class StateNotReachableException extends Exception {
-
-  public StateNotReachableException(final State s) {
-    super("The state `" + s.toString() + "` is not reachable.");
-  }
-
-  private static final long serialVersionUID = -1826791324169513493L;
-}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/StrategyFiniteStateMachine.java b/src/main/java/org/caosdb/server/utils/fsm/StrategyFiniteStateMachine.java
deleted file mode 100644
index d37d83783bd7f4aaa1db573f76315d0b51bd5fbb..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/StrategyFiniteStateMachine.java
+++ /dev/null
@@ -1,60 +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.utils.fsm;
-
-import java.util.Map;
-
-public class StrategyFiniteStateMachine<S extends State, T extends Transition, I>
-    extends FiniteStateMachine<S, T> {
-
-  public StrategyFiniteStateMachine(
-      final S initial, final Map<S, I> stateImplementations, final Map<S, Map<T, S>> transitions)
-      throws MissingImplementationException, StateNotReachableException {
-    super(initial, transitions);
-    this.stateImplementations = stateImplementations;
-    checkImplementationsComplete();
-  }
-
-  /**
-   * Check if every state has it's implementation.
-   *
-   * @throws MissingImplementationException
-   */
-  private void checkImplementationsComplete() throws MissingImplementationException {
-    for (final State s : getAllStates()) {
-      if (!this.stateImplementations.containsKey(s)) {
-        throw new MissingImplementationException(s);
-      }
-    }
-  }
-
-  private final Map<S, I> stateImplementations;
-
-  public I getImplementation() {
-    return getImplementation(getCurrentState());
-  }
-
-  public I getImplementation(final S state) {
-    return this.stateImplementations.get(state);
-  }
-}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/Transition.java b/src/main/java/org/caosdb/server/utils/fsm/Transition.java
deleted file mode 100644
index 41921b76cfc7ceed6e396551807e4b199486f0cf..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/Transition.java
+++ /dev/null
@@ -1,25 +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.utils.fsm;
-
-public interface Transition {}
diff --git a/src/main/java/org/caosdb/server/utils/fsm/TransitionNotAllowedException.java b/src/main/java/org/caosdb/server/utils/fsm/TransitionNotAllowedException.java
deleted file mode 100644
index f0547704e250db5c5088c5019a85974c2f4e6254..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/utils/fsm/TransitionNotAllowedException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.utils.fsm;
-
-public class TransitionNotAllowedException extends Exception {
-
-  private static final long serialVersionUID = -7236981582249457939L;
-
-  public TransitionNotAllowedException(final State state, final Transition transition) {
-    super(
-        "The transition `"
-            + transition.toString()
-            + "` is not allowed in state `"
-            + state.toString()
-            + ".");
-  }
-}
diff --git a/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java b/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
index 8e08131f3b18d9e4a495301ee570683facb4d9c5..0ba085b1fd050578b50e443613f82167890ea61f 100644
--- a/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
+++ b/src/test/java/org/caosdb/server/database/DatabaseAccessManagerTest.java
@@ -22,10 +22,91 @@
  */
 package org.caosdb.server.database;
 
+import static org.junit.Assert.assertFalse;
+
+import java.util.LinkedList;
+import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class DatabaseAccessManagerTest {
+  Thread createReadThread(long wait, String name, ReadAccessSemaphore readAccess) {
+    return new Thread(
+        new Runnable() {
+
+          @Override
+          public void run() {
+            try {
+              readAccess.acquire();
+              Thread.sleep(wait);
+            } catch (InterruptedException e) {
+              e.printStackTrace();
+            } finally {
+              readAccess.release();
+            }
+          }
+        },
+        name);
+  }
+
+  Thread createWriteThread(long wait, String name, WriteAccessLock writeAccess) {
+    return new Thread(
+        new Runnable() {
+
+          @Override
+          public void run() {
+            try {
+              writeAccess.reserve();
+              Thread.sleep(wait);
+              writeAccess.lockInterruptibly();
+              Thread.sleep(wait);
+            } catch (InterruptedException e) {
+              e.printStackTrace();
+            } finally {
+              writeAccess.release();
+            }
+          }
+        },
+        name);
+  }
+
+  @Test
+  public void testDeadLock() throws InterruptedException {
+    final ReadAccessSemaphore readAccess = new ReadAccessSemaphore();
+    final WriteAccessLock writeAccess = new WriteAccessLock(readAccess);
+    List<Thread> ts = new LinkedList<>();
+    for (int i = 0; i < 1000; i++) {
+      Thread t1 = createReadThread(1, "Ra" + i, readAccess);
+      Thread t2 = createReadThread(2, "Rb" + i, readAccess);
+      Thread t3 = createReadThread(3, "Rc" + i, readAccess);
+      Thread t5 = createReadThread(5, "Rd" + i, readAccess);
+      Thread t7 = createReadThread(7, "Re" + i, readAccess);
+      Thread t11 = createReadThread(11, "Rf" + i, readAccess);
+      Thread w5 = createWriteThread(2, "W" + i, writeAccess);
+
+      t1.start();
+      t2.start();
+      w5.start();
+      t3.start();
+      t5.start();
+      t7.start();
+      t11.start();
+
+      ts.add(t1);
+      ts.add(t2);
+      ts.add(t3);
+      ts.add(t5);
+      ts.add(t7);
+      ts.add(t11);
+      ts.add(w5);
+    }
+
+    for (Thread t : ts) {
+      t.join(10000);
+      assertFalse(t.isAlive());
+    }
+  }
+
   public static final ReadAccessSemaphore readAccess = new ReadAccessSemaphore();
   public static final WriteAccessLock writeAccess = new WriteAccessLock(readAccess);
 
diff --git a/src/test/java/org/caosdb/server/permissions/EntityACLTest.java b/src/test/java/org/caosdb/server/permissions/EntityACLTest.java
index 0e0e3ee121b09332b6cbb7d86a22458bb5fbf17f..1787c902f48124d692f8c53e4a73ed04564dfe8f 100644
--- a/src/test/java/org/caosdb/server/permissions/EntityACLTest.java
+++ b/src/test/java/org/caosdb/server/permissions/EntityACLTest.java
@@ -22,6 +22,7 @@
  */
 package org.caosdb.server.permissions;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -307,7 +308,7 @@ public class EntityACLTest {
 
   @Test
   public void testFactory() {
-    final EntityACLFactory f = new EntityACLFactory();
+    final AbstractEntityACLFactory<EntityACL> f = new EntityACLFactory();
 
     org.caosdb.server.permissions.Role role1 = org.caosdb.server.permissions.Role.create("role1");
     Config config1 = new Config();
@@ -349,60 +350,49 @@ public class EntityACLTest {
     Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.UPDATE_NAME)));
   }
 
-  //   @Test
-  //   public void niceFactoryStuff() {
-  //   final EntityACLFactory f = new EntityACLFactory();
-  //   f.grant("user1", "*");
-  //   final EntityACL acl1 = f.create();
-  //   Assert.assertTrue(acl1.isPermitted("user1", EntityPermission.EDIT_ACL));
-  //   Assert.assertTrue(acl1.isPermitted("user1", EntityPermission.DELETE));
-  //   Assert.assertTrue(acl1.isPermitted("user1",
-  //   EntityPermission.RETRIEVE_ENTITY));
-  //   Assert.assertTrue(acl1.isPermitted("user1",
-  //   EntityPermission.UPDATE_DATA_TYPE));
-  //   Assert.assertTrue(acl1.isPermitted("user1",
-  //   EntityPermission.USE_AS_PROPERTY));
-  //
-  //   f.grant("?OWNER?", "DELETE", "EDIT:ACL", "RETRIEVE:*", "UPDATE:*",
-  //   "USE:*");
-  //   f.grant("user2", "EDIT:ACL");
-  //   final EntityACL acl2 = f.create();
-  //   Assert.assertTrue(acl2.isPermitted("user2", EntityPermission.EDIT_ACL));
-  //   Assert.assertTrue(acl2.isPermitted("user2", EntityPermission.DELETE));
-  //   Assert.assertTrue(acl2.isPermitted("user2",
-  //   EntityPermission.RETRIEVE_ENTITY));
-  //   Assert.assertTrue(acl2.isPermitted("user2",
-  //   EntityPermission.UPDATE_DATA_TYPE));
-  //   Assert.assertTrue(acl2.isPermitted("user2",
-  //   EntityPermission.USE_AS_PROPERTY));
-  //
-  //   }
-  //
-  //   @Test
-  //   public void testDeny() {
-  //   EntityACLFactory f = new EntityACLFactory();
-  //   f.deny("test", "DELETE");
-  //   Assert.assertFalse(f.create().isPermitted("test",
-  //   EntityPermission.DELETE));
-  //
-  //   System.out.println(Utils.element2String(f.create().toElement()));
-  //
-  //   System.out.println(Utils.element2String(EntityACL.GLOBAL_PERMISSIONS.toElement()));
-  //
-  //   f.grant("test", "USE:*");
-  //   Assert.assertFalse(f.create().isPermitted("test",
-  //   EntityPermission.DELETE));
-  //
-  //   System.out.println(Utils.element2String(f.create().toElement()));
-  //
-  //   f = new EntityACLFactory();
-  //   f.grant(EntityACL.OTHER_ROLE, "RETRIEVE:*");
-  //   f.deny(EntityACL.OTHER_ROLE, "DELETE");
-  //   final EntityACL a = f.create();
-  //
-  //   System.out.println(Utils.element2String(a.toElement()));
-  //
-  //   System.out.println(Utils.element2String(EntityACL.deserialize(a.serialize()).toElement()));
-  //   }
+  @Test
+  public void testRemove() {
+    EntityACLFactory f = new EntityACLFactory();
+    f.grant(org.caosdb.server.permissions.Role.create("role1"), false, EntityPermission.DELETE);
+    f.deny(org.caosdb.server.permissions.Role.create("role2"), false, EntityPermission.EDIT_ACL);
+    f.grant(
+        org.caosdb.server.permissions.Role.create("role3"), true, EntityPermission.RETRIEVE_ACL);
+    f.deny(
+        org.caosdb.server.permissions.Role.create("role4"), true, EntityPermission.RETRIEVE_ENTITY);
+
+    EntityACL other = f.create();
+
+    f.grant(org.caosdb.server.permissions.Role.create("role2"), false, EntityPermission.EDIT_ACL);
+    f.grant(
+        org.caosdb.server.permissions.Role.create("role5"), false, EntityPermission.RETRIEVE_FILE);
+
+    f.remove(other); // normalize and remove "other"
+
+    EntityACL tester = f.create();
+    assertEquals(
+        "only the very last rule survived, the others have been overriden or removed",
+        1,
+        tester.getRules().size());
+    for (EntityACI aci : tester.getRules()) {
+      assertEquals(aci.getResponsibleAgent(), org.caosdb.server.permissions.Role.create("role5"));
+    }
+  }
 
+  @Test
+  public void testNormalize() {
+    EntityACLFactory f = new EntityACLFactory();
+    f.grant(org.caosdb.server.permissions.Role.create("role1"), false, EntityPermission.DELETE);
+    f.deny(org.caosdb.server.permissions.Role.create("role1"), false, EntityPermission.DELETE);
+    f.grant(org.caosdb.server.permissions.Role.create("role1"), true, EntityPermission.DELETE);
+    f.deny(org.caosdb.server.permissions.Role.create("role1"), true, EntityPermission.DELETE);
+
+    // priority denail overrides everything else
+    EntityACL denyDelete = f.create();
+    assertEquals(1, denyDelete.getRules().size());
+    for (EntityACI aci : denyDelete.getRules()) {
+      assertEquals(org.caosdb.server.permissions.Role.create("role1"), aci.getResponsibleAgent());
+      assertTrue(EntityACL.isDenial(aci.getBitSet()));
+      assertTrue(EntityACL.isPriorityBitSet(aci.getBitSet()));
+    }
+  }
 }
diff --git a/src/test/java/org/caosdb/server/query/QueryTest.java b/src/test/java/org/caosdb/server/query/QueryTest.java
index 092ebfcf833e6e60ff4edbb095a0759739975141..26e43ec6c65414808c72b9e04b95c1d67011fc9a 100644
--- a/src/test/java/org/caosdb/server/query/QueryTest.java
+++ b/src/test/java/org/caosdb/server/query/QueryTest.java
@@ -1,9 +1,15 @@
 package org.caosdb.server.query;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import org.caosdb.server.CaosDBServer;
+import org.caosdb.server.database.access.InitAccess;
+import org.caosdb.server.transaction.WriteTransaction;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -48,4 +54,52 @@ public class QueryTest {
         "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());
+  }
+
+  /**
+   * Assure that {@link WriteTransaction#commit()} calls {@link Query#clearCache()}.
+   *
+   * <p>Since currently the cache shall be cleared whenever there is a commit.
+   */
+  @Test
+  public void testEtagChangesAfterWrite() {
+    String old = Query.getETag();
+    assertNotNull(old);
+
+    WriteTransaction w =
+        new WriteTransaction(null) {
+
+          @Override
+          public boolean useCache() {
+            // this function is being overriden purely for the purpose of calling
+            // commit() (which is protected)
+            try {
+              // otherwise the test fails because getAccess() return null;
+              setAccess(new InitAccess(null));
+
+              commit();
+            } catch (Exception e) {
+              fail("this should not happen");
+            }
+            return false;
+          }
+        };
+
+    // trigger commit();
+    w.useCache();
+
+    String neu = Query.getETag();
+    assertNotEquals(old, neu, "old and new tag should not be equal");
+  }
 }
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/TestScriptingResource.java b/src/test/java/org/caosdb/server/resource/TestScriptingResource.java
index 845f589a9700a9d3d25eb8da2878c13ee114cd7d..7f7434528678dbd2e1886fade2116d0c9f766740 100644
--- a/src/test/java/org/caosdb/server/resource/TestScriptingResource.java
+++ b/src/test/java/org/caosdb/server/resource/TestScriptingResource.java
@@ -29,7 +29,6 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.authz.permission.WildcardPermission;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.accessControl.AnonymousAuthenticationToken;
@@ -95,9 +94,7 @@ public class TestScriptingResource {
       HashSet<PermissionRule> result = new HashSet<>();
       result.add(
           new PermissionRule(
-              true,
-              false,
-              new WildcardPermission(ScriptingPermissions.PERMISSION_EXECUTION("anonymous_ok"))));
+              true, false, ScriptingPermissions.PERMISSION_EXECUTION("anonymous_ok")));
       return result;
     }
 
diff --git a/src/test/java/org/caosdb/server/utils/fsm/TestFiniteStateMachine.java b/src/test/java/org/caosdb/server/utils/fsm/TestFiniteStateMachine.java
deleted file mode 100644
index 2a7b726b84464f3a7d287434b2767a34baaf4b50..0000000000000000000000000000000000000000
--- a/src/test/java/org/caosdb/server/utils/fsm/TestFiniteStateMachine.java
+++ /dev/null
@@ -1,90 +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.utils.fsm;
-
-import static org.junit.Assert.assertEquals;
-
-import com.google.common.collect.Lists;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-class SimpleFiniteStateMachine extends FiniteStateMachine<State, Transition> {
-
-  public SimpleFiniteStateMachine(
-      final State initial, final Map<State, Map<Transition, State>> transitions)
-      throws StateNotReachableException {
-    super(initial, transitions);
-  }
-}
-
-enum TestState implements State {
-  State1,
-  State2,
-  State3;
-
-  @Override
-  public List<State> getAllStates() {
-    return Lists.newArrayList(values());
-  }
-}
-
-enum TestTransition implements Transition {
-  toState2,
-  toState3
-}
-
-public class TestFiniteStateMachine {
-
-  @Rule public ExpectedException exc = ExpectedException.none();
-
-  @Test
-  public void testTransitionNotAllowedException()
-      throws StateNotReachableException, TransitionNotAllowedException {
-    final Map<State, Map<Transition, State>> map = new HashMap<>();
-    final HashMap<Transition, State> from1 = new HashMap<>();
-    from1.put(TestTransition.toState2, TestState.State2);
-    from1.put(TestTransition.toState3, TestState.State3);
-    map.put(TestState.State1, from1);
-
-    final SimpleFiniteStateMachine fsm = new SimpleFiniteStateMachine(TestState.State1, map);
-    assertEquals(TestState.State1, fsm.getCurrentState());
-    fsm.trigger(TestTransition.toState2);
-    assertEquals(TestState.State2, fsm.getCurrentState());
-
-    // only 1->2 and from 1->3 is allowed. not 2->3
-    this.exc.expect(TransitionNotAllowedException.class);
-    fsm.trigger(TestTransition.toState3);
-  }
-
-  @Test
-  public void testStateNotReachable() throws StateNotReachableException {
-    final Map<State, Map<Transition, State>> empty = new HashMap<>();
-
-    this.exc.expect(StateNotReachableException.class);
-    new SimpleFiniteStateMachine(TestState.State1, empty);
-  }
-}
diff --git a/src/test/java/org/caosdb/server/utils/fsm/TestStrategyFiniteStateMachine.java b/src/test/java/org/caosdb/server/utils/fsm/TestStrategyFiniteStateMachine.java
deleted file mode 100644
index ebf719b1eaa4b56f3b64bdd9371f121231b4ed68..0000000000000000000000000000000000000000
--- a/src/test/java/org/caosdb/server/utils/fsm/TestStrategyFiniteStateMachine.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.utils.fsm;
-
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-public class TestStrategyFiniteStateMachine {
-
-  @Rule public ExpectedException exc = ExpectedException.none();
-
-  @Test
-  public void testStateHasNoImplementation()
-      throws MissingImplementationException, StateNotReachableException {
-    final Map<State, Map<Transition, State>> map = new HashMap<>();
-    final HashMap<Transition, State> from1 = new HashMap<>();
-    from1.put(TestTransition.toState2, TestState.State2);
-    from1.put(TestTransition.toState3, TestState.State3);
-    map.put(TestState.State1, from1);
-
-    final Map<State, Object> stateImplementations = new HashMap<>();
-
-    this.exc.expect(MissingImplementationException.class);
-    new StrategyFiniteStateMachine<State, Transition, Object>(
-        TestState.State1, stateImplementations, map);
-  }
-}